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