• 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 <nativehelper/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     return FPDF_GetPageCount(document);
64 }
65 
66 struct PdfToFdWriter : FPDF_FILEWRITE {
67     int dstFd;
68 };
69 
writeAllBytes(const int fd,const void * buffer,const size_t byteCount)70 static bool writeAllBytes(const int fd, const void* buffer, const size_t byteCount) {
71     char* writeBuffer = static_cast<char*>(const_cast<void*>(buffer));
72     size_t remainingBytes = byteCount;
73     while (remainingBytes > 0) {
74         ssize_t writtenByteCount = write(fd, writeBuffer, remainingBytes);
75         if (writtenByteCount == -1) {
76             if (errno == EINTR) {
77                 continue;
78             }
79             ALOGE("Error writing to buffer: %d", errno);
80             return false;
81         }
82         remainingBytes -= writtenByteCount;
83         writeBuffer += writtenByteCount;
84     }
85     return true;
86 }
87 
writeBlock(FPDF_FILEWRITE * owner,const void * buffer,unsigned long size)88 static int writeBlock(FPDF_FILEWRITE* owner, const void* buffer, unsigned long size) {
89     const PdfToFdWriter* writer = reinterpret_cast<PdfToFdWriter*>(owner);
90     const bool success = writeAllBytes(writer->dstFd, buffer, size);
91     if (!success) {
92         ALOGE("Cannot write to file descriptor. Error:%d", errno);
93         return 0;
94     }
95     return 1;
96 }
97 
nativeWrite(JNIEnv * env,jclass thiz,jlong documentPtr,jint fd)98 static void nativeWrite(JNIEnv* env, jclass thiz, jlong documentPtr, jint fd) {
99     FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
100     PdfToFdWriter writer;
101     writer.dstFd = fd;
102     writer.WriteBlock = &writeBlock;
103     const bool success = FPDF_SaveAsCopy(document, &writer, FPDF_NO_INCREMENTAL);
104     if (!success) {
105         jniThrowExceptionFmt(env, "java/io/IOException",
106                 "cannot write to fd. Error: %d", errno);
107     }
108 }
109 
nativeSetTransformAndClip(JNIEnv * env,jclass thiz,jlong documentPtr,jint pageIndex,jlong transformPtr,jint clipLeft,jint clipTop,jint clipRight,jint clipBottom)110 static void nativeSetTransformAndClip(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex,
111         jlong transformPtr, jint clipLeft, jint clipTop, jint clipRight, jint clipBottom) {
112     FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
113 
114     FPDF_PAGE* page = (FPDF_PAGE*) FPDF_LoadPage(document, pageIndex);
115     if (!page) {
116         jniThrowException(env, "java/lang/IllegalStateException",
117                 "cannot open page");
118         return;
119     }
120 
121     double width = 0;
122     double height = 0;
123 
124     const int result = FPDF_GetPageSizeByIndex(document, pageIndex, &width, &height);
125     if (!result) {
126         jniThrowException(env, "java/lang/IllegalStateException",
127                     "cannot get page size");
128         return;
129     }
130 
131     // PDF's coordinate system origin is left-bottom while in graphics it
132     // is the top-left. So, translate the PDF coordinates to ours.
133     SkMatrix reflectOnX = SkMatrix::MakeScale(1, -1);
134     SkMatrix moveUp = SkMatrix::MakeTrans(0, FPDF_GetPageHeight(page));
135     SkMatrix coordinateChange = SkMatrix::Concat(moveUp, reflectOnX);
136 
137     // Apply the transformation what was created in our coordinates.
138     SkMatrix matrix = SkMatrix::Concat(*reinterpret_cast<SkMatrix*>(transformPtr),
139             coordinateChange);
140 
141     // Translate the result back to PDF coordinates.
142     matrix.setConcat(coordinateChange, matrix);
143 
144     SkScalar transformValues[6];
145     if (!matrix.asAffine(transformValues)) {
146         FPDF_ClosePage(page);
147 
148         jniThrowException(env, "java/lang/IllegalArgumentException",
149                 "transform matrix has perspective. Only affine matrices are allowed.");
150         return;
151     }
152 
153     FS_MATRIX transform = {transformValues[SkMatrix::kAScaleX], transformValues[SkMatrix::kASkewY],
154                            transformValues[SkMatrix::kASkewX], transformValues[SkMatrix::kAScaleY],
155                            transformValues[SkMatrix::kATransX],
156                            transformValues[SkMatrix::kATransY]};
157 
158     FS_RECTF clip = {(float) clipLeft, (float) clipTop, (float) clipRight, (float) clipBottom};
159 
160     FPDFPage_TransFormWithClip(page, &transform, &clip);
161 
162     FPDF_ClosePage(page);
163 }
164 
nativeGetPageSize(JNIEnv * env,jclass thiz,jlong documentPtr,jint pageIndex,jobject outSize)165 static void nativeGetPageSize(JNIEnv* env, jclass thiz, jlong documentPtr,
166         jint pageIndex, jobject outSize) {
167     FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
168 
169     FPDF_PAGE page = FPDF_LoadPage(document, pageIndex);
170     if (!page) {
171         jniThrowException(env, "java/lang/IllegalStateException",
172                 "cannot open page");
173         return;
174     }
175 
176     double width = 0;
177     double height = 0;
178 
179     const int result = FPDF_GetPageSizeByIndex(document, pageIndex, &width, &height);
180     if (!result) {
181         jniThrowException(env, "java/lang/IllegalStateException",
182                     "cannot get page size");
183         return;
184     }
185 
186     env->SetIntField(outSize, gPointClassInfo.x, width);
187     env->SetIntField(outSize, gPointClassInfo.y, height);
188 
189     FPDF_ClosePage(page);
190 }
191 
nativeGetPageBox(JNIEnv * env,jclass thiz,jlong documentPtr,jint pageIndex,PageBox pageBox,jobject outBox)192 static bool nativeGetPageBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex,
193         PageBox pageBox, jobject outBox) {
194     FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
195 
196     FPDF_PAGE page = FPDF_LoadPage(document, pageIndex);
197     if (!page) {
198         jniThrowException(env, "java/lang/IllegalStateException",
199                 "cannot open page");
200         return false;
201     }
202 
203     float left;
204     float top;
205     float right;
206     float bottom;
207 
208     const FPDF_BOOL success = (pageBox == PAGE_BOX_MEDIA)
209         ? FPDFPage_GetMediaBox(page, &left, &top, &right, &bottom)
210         : FPDFPage_GetCropBox(page, &left, &top, &right, &bottom);
211 
212     FPDF_ClosePage(page);
213 
214     if (!success) {
215         return false;
216     }
217 
218     env->SetIntField(outBox, gRectClassInfo.left, (int) left);
219     env->SetIntField(outBox, gRectClassInfo.top, (int) top);
220     env->SetIntField(outBox, gRectClassInfo.right, (int) right);
221     env->SetIntField(outBox, gRectClassInfo.bottom, (int) bottom);
222 
223     return true;
224 }
225 
nativeGetPageMediaBox(JNIEnv * env,jclass thiz,jlong documentPtr,jint pageIndex,jobject outMediaBox)226 static jboolean nativeGetPageMediaBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex,
227         jobject outMediaBox) {
228     const bool success = nativeGetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_MEDIA,
229             outMediaBox);
230     return success ? JNI_TRUE : JNI_FALSE;
231 }
232 
nativeGetPageCropBox(JNIEnv * env,jclass thiz,jlong documentPtr,jint pageIndex,jobject outMediaBox)233 static jboolean nativeGetPageCropBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex,
234         jobject outMediaBox) {
235     const bool success = nativeGetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_CROP,
236          outMediaBox);
237     return success ? JNI_TRUE : JNI_FALSE;
238 }
239 
nativeSetPageBox(JNIEnv * env,jclass thiz,jlong documentPtr,jint pageIndex,PageBox pageBox,jobject box)240 static void nativeSetPageBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex,
241         PageBox pageBox, jobject box) {
242     FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
243 
244     FPDF_PAGE page = FPDF_LoadPage(document, pageIndex);
245     if (!page) {
246         jniThrowException(env, "java/lang/IllegalStateException",
247                 "cannot open page");
248         return;
249     }
250 
251     const int left = env->GetIntField(box, gRectClassInfo.left);
252     const int top = env->GetIntField(box, gRectClassInfo.top);
253     const int right = env->GetIntField(box, gRectClassInfo.right);
254     const int bottom = env->GetIntField(box, gRectClassInfo.bottom);
255 
256     if (pageBox == PAGE_BOX_MEDIA) {
257         FPDFPage_SetMediaBox(page, left, top, right, bottom);
258     } else {
259         FPDFPage_SetCropBox(page, left, top, right, bottom);
260     }
261 
262     FPDF_ClosePage(page);
263 }
264 
nativeSetPageMediaBox(JNIEnv * env,jclass thiz,jlong documentPtr,jint pageIndex,jobject mediaBox)265 static void nativeSetPageMediaBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex,
266         jobject mediaBox) {
267     nativeSetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_MEDIA, mediaBox);
268 }
269 
nativeSetPageCropBox(JNIEnv * env,jclass thiz,jlong documentPtr,jint pageIndex,jobject mediaBox)270 static void nativeSetPageCropBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex,
271         jobject mediaBox) {
272     nativeSetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_CROP, mediaBox);
273 }
274 
275 static const JNINativeMethod gPdfEditor_Methods[] = {
276     {"nativeOpen", "(IJ)J", (void*) nativeOpen},
277     {"nativeClose", "(J)V", (void*) nativeClose},
278     {"nativeGetPageCount", "(J)I", (void*) nativeGetPageCount},
279     {"nativeRemovePage", "(JI)I", (void*) nativeRemovePage},
280     {"nativeWrite", "(JI)V", (void*) nativeWrite},
281     {"nativeSetTransformAndClip", "(JIJIIII)V", (void*) nativeSetTransformAndClip},
282     {"nativeGetPageSize", "(JILandroid/graphics/Point;)V", (void*) nativeGetPageSize},
283     {"nativeScaleForPrinting", "(J)Z", (void*) nativeScaleForPrinting},
284     {"nativeGetPageMediaBox", "(JILandroid/graphics/Rect;)Z", (void*) nativeGetPageMediaBox},
285     {"nativeSetPageMediaBox", "(JILandroid/graphics/Rect;)V", (void*) nativeSetPageMediaBox},
286     {"nativeGetPageCropBox", "(JILandroid/graphics/Rect;)Z", (void*) nativeGetPageCropBox},
287     {"nativeSetPageCropBox", "(JILandroid/graphics/Rect;)V", (void*) nativeSetPageCropBox}
288 };
289 
register_android_graphics_pdf_PdfEditor(JNIEnv * env)290 int register_android_graphics_pdf_PdfEditor(JNIEnv* env) {
291     const int result = RegisterMethodsOrDie(
292             env, "android/graphics/pdf/PdfEditor", gPdfEditor_Methods,
293             NELEM(gPdfEditor_Methods));
294 
295     jclass pointClass = FindClassOrDie(env, "android/graphics/Point");
296     gPointClassInfo.x = GetFieldIDOrDie(env, pointClass, "x", "I");
297     gPointClassInfo.y = GetFieldIDOrDie(env, pointClass, "y", "I");
298 
299     jclass rectClass = FindClassOrDie(env, "android/graphics/Rect");
300     gRectClassInfo.left = GetFieldIDOrDie(env, rectClass, "left", "I");
301     gRectClassInfo.top = GetFieldIDOrDie(env, rectClass, "top", "I");
302     gRectClassInfo.right = GetFieldIDOrDie(env, rectClass, "right", "I");
303     gRectClassInfo.bottom = GetFieldIDOrDie(env, rectClass, "bottom", "I");
304 
305     return result;
306 };
307 
308 };
309