• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #define LOG_TAG "PdfEditor"
17 
18 #include <sys/types.h>
19 #include <unistd.h>
20 
21 #include <vector>
22 
23 #include <log/log.h>
24 #include <utils/Log.h>
25 
26 #include "PdfUtils.h"
27 
28 #include "jni.h"
29 #include "JNIHelp.h"
30 
31 #pragma GCC diagnostic push
32 #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
33 #include "fpdfview.h"
34 #include "fpdf_edit.h"
35 #include "fpdf_save.h"
36 #include "fpdf_transformpage.h"
37 #pragma GCC diagnostic pop
38 
39 #include "SkMatrix.h"
40 
41 #include <core_jni_helpers.h>
42 
43 namespace android {
44 
45 enum PageBox {PAGE_BOX_MEDIA, PAGE_BOX_CROP};
46 
47 static struct {
48     jfieldID x;
49     jfieldID y;
50 } gPointClassInfo;
51 
52 static struct {
53     jfieldID left;
54     jfieldID top;
55     jfieldID right;
56     jfieldID bottom;
57 } gRectClassInfo;
58 
nativeRemovePage(JNIEnv * env,jclass thiz,jlong documentPtr,jint pageIndex)59 static jint nativeRemovePage(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex) {
60     FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
61 
62     FPDFPage_Delete(document, pageIndex);
63     HANDLE_PDFIUM_ERROR_STATE_WITH_RET_CODE(env, -1)
64 
65     int pageCount = FPDF_GetPageCount(document);
66     HANDLE_PDFIUM_ERROR_STATE_WITH_RET_CODE(env, -1)
67 
68     return pageCount;
69 }
70 
71 struct PdfToFdWriter : FPDF_FILEWRITE {
72     int dstFd;
73 };
74 
writeAllBytes(const int fd,const void * buffer,const size_t byteCount)75 static bool writeAllBytes(const int fd, const void* buffer, const size_t byteCount) {
76     char* writeBuffer = static_cast<char*>(const_cast<void*>(buffer));
77     size_t remainingBytes = byteCount;
78     while (remainingBytes > 0) {
79         ssize_t writtenByteCount = write(fd, writeBuffer, remainingBytes);
80         if (writtenByteCount == -1) {
81             if (errno == EINTR) {
82                 continue;
83             }
84             ALOGE("Error writing to buffer: %d", errno);
85             return false;
86         }
87         remainingBytes -= writtenByteCount;
88         writeBuffer += writtenByteCount;
89     }
90     return true;
91 }
92 
writeBlock(FPDF_FILEWRITE * owner,const void * buffer,unsigned long size)93 static int writeBlock(FPDF_FILEWRITE* owner, const void* buffer, unsigned long size) {
94     const PdfToFdWriter* writer = reinterpret_cast<PdfToFdWriter*>(owner);
95     const bool success = writeAllBytes(writer->dstFd, buffer, size);
96     if (!success) {
97         ALOGE("Cannot write to file descriptor. Error:%d", errno);
98         return 0;
99     }
100     return 1;
101 }
102 
nativeWrite(JNIEnv * env,jclass thiz,jlong documentPtr,jint fd)103 static void nativeWrite(JNIEnv* env, jclass thiz, jlong documentPtr, jint fd) {
104     FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
105     PdfToFdWriter writer;
106     writer.dstFd = fd;
107     writer.WriteBlock = &writeBlock;
108     const bool success = FPDF_SaveAsCopy(document, &writer, FPDF_NO_INCREMENTAL);
109     if (!success) {
110         jniThrowExceptionFmt(env, "java/io/IOException",
111                 "cannot write to fd. Error: %d", errno);
112     }
113     HANDLE_PDFIUM_ERROR_STATE(env)
114 }
115 
nativeSetTransformAndClip(JNIEnv * env,jclass thiz,jlong documentPtr,jint pageIndex,jlong transformPtr,jint clipLeft,jint clipTop,jint clipRight,jint clipBottom)116 static void nativeSetTransformAndClip(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex,
117         jlong transformPtr, jint clipLeft, jint clipTop, jint clipRight, jint clipBottom) {
118     FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
119 
120     FPDF_PAGE* page = (FPDF_PAGE*) FPDF_LoadPage(document, pageIndex);
121     if (!page) {
122         jniThrowException(env, "java/lang/IllegalStateException",
123                 "cannot open page");
124         return;
125     }
126     HANDLE_PDFIUM_ERROR_STATE(env);
127 
128     double width = 0;
129     double height = 0;
130 
131     const int result = FPDF_GetPageSizeByIndex(document, pageIndex, &width, &height);
132     if (!result) {
133         jniThrowException(env, "java/lang/IllegalStateException",
134                     "cannot get page size");
135         return;
136     }
137     bool isExceptionPending = forwardPdfiumError(env);
138     if (isExceptionPending) {
139         FPDF_ClosePage(page);
140         return;
141     }
142 
143     // PDF's coordinate system origin is left-bottom while in graphics it
144     // is the top-left. So, translate the PDF coordinates to ours.
145     SkMatrix reflectOnX = SkMatrix::MakeScale(1, -1);
146     SkMatrix moveUp = SkMatrix::MakeTrans(0, FPDF_GetPageHeight(page));
147     SkMatrix coordinateChange = SkMatrix::Concat(moveUp, reflectOnX);
148 
149     // Apply the transformation what was created in our coordinates.
150     SkMatrix matrix = SkMatrix::Concat(*reinterpret_cast<SkMatrix*>(transformPtr),
151             coordinateChange);
152 
153     // Translate the result back to PDF coordinates.
154     matrix.setConcat(coordinateChange, matrix);
155 
156     SkScalar transformValues[6];
157     if (!matrix.asAffine(transformValues)) {
158         FPDF_ClosePage(page);
159 
160         jniThrowException(env, "java/lang/IllegalArgumentException",
161                 "transform matrix has perspective. Only affine matrices are allowed.");
162         return;
163     }
164 
165     FS_MATRIX transform = {transformValues[SkMatrix::kAScaleX], transformValues[SkMatrix::kASkewY],
166                            transformValues[SkMatrix::kASkewX], transformValues[SkMatrix::kAScaleY],
167                            transformValues[SkMatrix::kATransX],
168                            transformValues[SkMatrix::kATransY]};
169 
170     FS_RECTF clip = {(float) clipLeft, (float) clipTop, (float) clipRight, (float) clipBottom};
171 
172     FPDFPage_TransFormWithClip(page, &transform, &clip);
173     isExceptionPending = forwardPdfiumError(env);
174     if (isExceptionPending) {
175         FPDF_ClosePage(page);
176         return;
177     }
178 
179     FPDF_ClosePage(page);
180     HANDLE_PDFIUM_ERROR_STATE(env);
181 }
182 
nativeGetPageSize(JNIEnv * env,jclass thiz,jlong documentPtr,jint pageIndex,jobject outSize)183 static void nativeGetPageSize(JNIEnv* env, jclass thiz, jlong documentPtr,
184         jint pageIndex, jobject outSize) {
185     FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
186 
187     FPDF_PAGE page = FPDF_LoadPage(document, pageIndex);
188     if (!page) {
189         jniThrowException(env, "java/lang/IllegalStateException",
190                 "cannot open page");
191         return;
192     }
193     HANDLE_PDFIUM_ERROR_STATE(env);
194 
195     double width = 0;
196     double height = 0;
197 
198     const int result = FPDF_GetPageSizeByIndex(document, pageIndex, &width, &height);
199     if (!result) {
200         jniThrowException(env, "java/lang/IllegalStateException",
201                     "cannot get page size");
202         return;
203     }
204     bool isExceptionPending = forwardPdfiumError(env);
205     if (isExceptionPending) {
206         FPDF_ClosePage(page);
207         return;
208     }
209 
210     env->SetIntField(outSize, gPointClassInfo.x, width);
211     env->SetIntField(outSize, gPointClassInfo.y, height);
212 
213     FPDF_ClosePage(page);
214     HANDLE_PDFIUM_ERROR_STATE(env);
215 }
216 
nativeGetPageBox(JNIEnv * env,jclass thiz,jlong documentPtr,jint pageIndex,PageBox pageBox,jobject outBox)217 static bool nativeGetPageBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex,
218         PageBox pageBox, jobject outBox) {
219     FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
220 
221     FPDF_PAGE page = FPDF_LoadPage(document, pageIndex);
222     if (!page) {
223         jniThrowException(env, "java/lang/IllegalStateException",
224                 "cannot open page");
225         return false;
226     }
227     HANDLE_PDFIUM_ERROR_STATE_WITH_RET_CODE(env, false);
228 
229     float left;
230     float top;
231     float right;
232     float bottom;
233 
234     const FPDF_BOOL success = (pageBox == PAGE_BOX_MEDIA)
235         ? FPDFPage_GetMediaBox(page, &left, &top, &right, &bottom)
236         : FPDFPage_GetCropBox(page, &left, &top, &right, &bottom);
237     bool isExceptionPending = forwardPdfiumError(env);
238     if (isExceptionPending) {
239         FPDF_ClosePage(page);
240         return false;
241     }
242 
243     FPDF_ClosePage(page);
244     HANDLE_PDFIUM_ERROR_STATE_WITH_RET_CODE(env, false);
245 
246     if (!success) {
247         return false;
248     }
249 
250     env->SetIntField(outBox, gRectClassInfo.left, (int) left);
251     env->SetIntField(outBox, gRectClassInfo.top, (int) top);
252     env->SetIntField(outBox, gRectClassInfo.right, (int) right);
253     env->SetIntField(outBox, gRectClassInfo.bottom, (int) bottom);
254 
255     return true;
256 }
257 
nativeGetPageMediaBox(JNIEnv * env,jclass thiz,jlong documentPtr,jint pageIndex,jobject outMediaBox)258 static jboolean nativeGetPageMediaBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex,
259         jobject outMediaBox) {
260     const bool success = nativeGetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_MEDIA,
261             outMediaBox);
262     return success ? JNI_TRUE : JNI_FALSE;
263 }
264 
nativeGetPageCropBox(JNIEnv * env,jclass thiz,jlong documentPtr,jint pageIndex,jobject outMediaBox)265 static jboolean nativeGetPageCropBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex,
266         jobject outMediaBox) {
267     const bool success = nativeGetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_CROP,
268          outMediaBox);
269     return success ? JNI_TRUE : JNI_FALSE;
270 }
271 
nativeSetPageBox(JNIEnv * env,jclass thiz,jlong documentPtr,jint pageIndex,PageBox pageBox,jobject box)272 static void nativeSetPageBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex,
273         PageBox pageBox, jobject box) {
274     FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
275 
276     FPDF_PAGE page = FPDF_LoadPage(document, pageIndex);
277     if (!page) {
278         jniThrowException(env, "java/lang/IllegalStateException",
279                 "cannot open page");
280         return;
281     }
282     HANDLE_PDFIUM_ERROR_STATE(env);
283 
284     const int left = env->GetIntField(box, gRectClassInfo.left);
285     const int top = env->GetIntField(box, gRectClassInfo.top);
286     const int right = env->GetIntField(box, gRectClassInfo.right);
287     const int bottom = env->GetIntField(box, gRectClassInfo.bottom);
288 
289     if (pageBox == PAGE_BOX_MEDIA) {
290         FPDFPage_SetMediaBox(page, left, top, right, bottom);
291     } else {
292         FPDFPage_SetCropBox(page, left, top, right, bottom);
293     }
294     bool isExceptionPending = forwardPdfiumError(env);
295     if (isExceptionPending) {
296         FPDF_ClosePage(page);
297         return;
298     }
299 
300     FPDF_ClosePage(page);
301     HANDLE_PDFIUM_ERROR_STATE(env);
302 }
303 
nativeSetPageMediaBox(JNIEnv * env,jclass thiz,jlong documentPtr,jint pageIndex,jobject mediaBox)304 static void nativeSetPageMediaBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex,
305         jobject mediaBox) {
306     nativeSetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_MEDIA, mediaBox);
307 }
308 
nativeSetPageCropBox(JNIEnv * env,jclass thiz,jlong documentPtr,jint pageIndex,jobject mediaBox)309 static void nativeSetPageCropBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex,
310         jobject mediaBox) {
311     nativeSetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_CROP, mediaBox);
312 }
313 
314 static const JNINativeMethod gPdfEditor_Methods[] = {
315     {"nativeOpen", "(IJ)J", (void*) nativeOpen},
316     {"nativeClose", "(J)V", (void*) nativeClose},
317     {"nativeGetPageCount", "(J)I", (void*) nativeGetPageCount},
318     {"nativeRemovePage", "(JI)I", (void*) nativeRemovePage},
319     {"nativeWrite", "(JI)V", (void*) nativeWrite},
320     {"nativeSetTransformAndClip", "(JIJIIII)V", (void*) nativeSetTransformAndClip},
321     {"nativeGetPageSize", "(JILandroid/graphics/Point;)V", (void*) nativeGetPageSize},
322     {"nativeScaleForPrinting", "(J)Z", (void*) nativeScaleForPrinting},
323     {"nativeGetPageMediaBox", "(JILandroid/graphics/Rect;)Z", (void*) nativeGetPageMediaBox},
324     {"nativeSetPageMediaBox", "(JILandroid/graphics/Rect;)V", (void*) nativeSetPageMediaBox},
325     {"nativeGetPageCropBox", "(JILandroid/graphics/Rect;)Z", (void*) nativeGetPageCropBox},
326     {"nativeSetPageCropBox", "(JILandroid/graphics/Rect;)V", (void*) nativeSetPageCropBox}
327 };
328 
register_android_graphics_pdf_PdfEditor(JNIEnv * env)329 int register_android_graphics_pdf_PdfEditor(JNIEnv* env) {
330     const int result = RegisterMethodsOrDie(
331             env, "android/graphics/pdf/PdfEditor", gPdfEditor_Methods,
332             NELEM(gPdfEditor_Methods));
333 
334     jclass pointClass = FindClassOrDie(env, "android/graphics/Point");
335     gPointClassInfo.x = GetFieldIDOrDie(env, pointClass, "x", "I");
336     gPointClassInfo.y = GetFieldIDOrDie(env, pointClass, "y", "I");
337 
338     jclass rectClass = FindClassOrDie(env, "android/graphics/Rect");
339     gRectClassInfo.left = GetFieldIDOrDie(env, rectClass, "left", "I");
340     gRectClassInfo.top = GetFieldIDOrDie(env, rectClass, "top", "I");
341     gRectClassInfo.right = GetFieldIDOrDie(env, rectClass, "right", "I");
342     gRectClassInfo.bottom = GetFieldIDOrDie(env, rectClass, "bottom", "I");
343 
344     return result;
345 };
346 
347 };
348