PdfEditor.cpp revision 79bd8d48ad69c39834291809fe78ea478d067b68
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
17#include "jni.h"
18#include "JNIHelp.h"
19
20#pragma GCC diagnostic push
21#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor"
22#include "fpdfview.h"
23#include "fpdf_edit.h"
24#include "fpdf_save.h"
25#include "fsdk_rendercontext.h"
26#include "fpdf_transformpage.h"
27#pragma GCC diagnostic pop
28
29#include "SkMatrix.h"
30
31#include <core_jni_helpers.h>
32#include <vector>
33#include <utils/Log.h>
34#include <unistd.h>
35#include <sys/types.h>
36#include <unistd.h>
37
38namespace android {
39
40enum PageBox {PAGE_BOX_MEDIA, PAGE_BOX_CROP};
41
42static struct {
43    jfieldID x;
44    jfieldID y;
45} gPointClassInfo;
46
47static struct {
48    jfieldID left;
49    jfieldID top;
50    jfieldID right;
51    jfieldID bottom;
52} gRectClassInfo;
53
54static Mutex sLock;
55
56static int sUnmatchedInitRequestCount = 0;
57
58static void initializeLibraryIfNeeded() {
59    Mutex::Autolock _l(sLock);
60    if (sUnmatchedInitRequestCount == 0) {
61        FPDF_InitLibrary();
62    }
63    sUnmatchedInitRequestCount++;
64}
65
66static void destroyLibraryIfNeeded() {
67    Mutex::Autolock _l(sLock);
68    sUnmatchedInitRequestCount--;
69    if (sUnmatchedInitRequestCount == 0) {
70       FPDF_DestroyLibrary();
71    }
72}
73
74static int getBlock(void* param, unsigned long position, unsigned char* outBuffer,
75        unsigned long size) {
76    const int fd = reinterpret_cast<intptr_t>(param);
77    const int readCount = pread(fd, outBuffer, size, position);
78    if (readCount < 0) {
79        ALOGE("Cannot read from file descriptor. Error:%d", errno);
80        return 0;
81    }
82    return 1;
83}
84
85static jlong nativeOpen(JNIEnv* env, jclass thiz, jint fd, jlong size) {
86    initializeLibraryIfNeeded();
87
88    FPDF_FILEACCESS loader;
89    loader.m_FileLen = size;
90    loader.m_Param = reinterpret_cast<void*>(intptr_t(fd));
91    loader.m_GetBlock = &getBlock;
92
93    FPDF_DOCUMENT document = FPDF_LoadCustomDocument(&loader, NULL);
94
95    if (!document) {
96        const long error = FPDF_GetLastError();
97        switch (error) {
98            case FPDF_ERR_PASSWORD:
99            case FPDF_ERR_SECURITY: {
100                jniThrowExceptionFmt(env, "java/lang/SecurityException",
101                        "cannot create document. Error: %ld", error);
102            } break;
103            default: {
104                jniThrowExceptionFmt(env, "java/io/IOException",
105                        "cannot create document. Error: %ld", error);
106            } break;
107        }
108        destroyLibraryIfNeeded();
109        return -1;
110    }
111
112    return reinterpret_cast<jlong>(document);
113}
114
115static void nativeClose(JNIEnv* env, jclass thiz, jlong documentPtr) {
116    FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
117    FPDF_CloseDocument(document);
118    destroyLibraryIfNeeded();
119}
120
121static jint nativeGetPageCount(JNIEnv* env, jclass thiz, jlong documentPtr) {
122    FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
123    return FPDF_GetPageCount(document);
124}
125
126static jint nativeRemovePage(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex) {
127    FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
128    FPDFPage_Delete(document, pageIndex);
129    return FPDF_GetPageCount(document);
130}
131
132struct PdfToFdWriter : FPDF_FILEWRITE {
133    int dstFd;
134};
135
136static bool writeAllBytes(const int fd, const void* buffer, const size_t byteCount) {
137    char* writeBuffer = static_cast<char*>(const_cast<void*>(buffer));
138    size_t remainingBytes = byteCount;
139    while (remainingBytes > 0) {
140        ssize_t writtenByteCount = write(fd, writeBuffer, remainingBytes);
141        if (writtenByteCount == -1) {
142            if (errno == EINTR) {
143                continue;
144            }
145            __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
146                    "Error writing to buffer: %d", errno);
147            return false;
148        }
149        remainingBytes -= writtenByteCount;
150        writeBuffer += writtenByteCount;
151    }
152    return true;
153}
154
155static int writeBlock(FPDF_FILEWRITE* owner, const void* buffer, unsigned long size) {
156    const PdfToFdWriter* writer = reinterpret_cast<PdfToFdWriter*>(owner);
157    const bool success = writeAllBytes(writer->dstFd, buffer, size);
158    if (!success) {
159        ALOGE("Cannot write to file descriptor. Error:%d", errno);
160        return 0;
161    }
162    return 1;
163}
164
165static void nativeWrite(JNIEnv* env, jclass thiz, jlong documentPtr, jint fd) {
166    FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
167    PdfToFdWriter writer;
168    writer.dstFd = fd;
169    writer.WriteBlock = &writeBlock;
170    const bool success = FPDF_SaveAsCopy(document, &writer, FPDF_NO_INCREMENTAL);
171    if (!success) {
172        jniThrowExceptionFmt(env, "java/io/IOException",
173                "cannot write to fd. Error: %d", errno);
174        destroyLibraryIfNeeded();
175    }
176}
177
178static void nativeSetTransformAndClip(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex,
179        jlong transformPtr, jint clipLeft, jint clipTop, jint clipRight, jint clipBottom) {
180    FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
181
182    CPDF_Page* page = (CPDF_Page*) FPDF_LoadPage(document, pageIndex);
183    if (!page) {
184        jniThrowException(env, "java/lang/IllegalStateException",
185                "cannot open page");
186        return;
187    }
188
189    double width = 0;
190    double height = 0;
191
192    const int result = FPDF_GetPageSizeByIndex(document, pageIndex, &width, &height);
193    if (!result) {
194        jniThrowException(env, "java/lang/IllegalStateException",
195                    "cannot get page size");
196        return;
197    }
198
199    CFX_Matrix matrix;
200
201    SkMatrix* skTransform = reinterpret_cast<SkMatrix*>(transformPtr);
202
203    SkScalar transformValues[6];
204    if (!skTransform->asAffine(transformValues)) {
205        jniThrowException(env, "java/lang/IllegalArgumentException",
206                "transform matrix has perspective. Only affine matrices are allowed.");
207        return;
208    }
209
210    // PDF's coordinate system origin is left-bottom while in graphics it
211    // is the top-left. So, translate the PDF coordinates to ours.
212    matrix.Set(1, 0, 0, -1, 0, page->GetPageHeight());
213
214    // Apply the transformation what was created in our coordinates.
215    matrix.Concat(transformValues[SkMatrix::kAScaleX], transformValues[SkMatrix::kASkewY],
216            transformValues[SkMatrix::kASkewX], transformValues[SkMatrix::kAScaleY],
217            transformValues[SkMatrix::kATransX], transformValues[SkMatrix::kATransY]);
218
219    // Translate the result back to PDF coordinates.
220    matrix.Concat(1, 0, 0, -1, 0, page->GetPageHeight());
221
222    FS_MATRIX transform = {matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f};
223    FS_RECTF clip = {(float) clipLeft, (float) clipTop, (float) clipRight, (float) clipBottom};
224
225    FPDFPage_TransFormWithClip(page, &transform, &clip);
226
227    FPDF_ClosePage(page);
228}
229
230static void nativeGetPageSize(JNIEnv* env, jclass thiz, jlong documentPtr,
231        jint pageIndex, jobject outSize) {
232    FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
233
234    FPDF_PAGE page = FPDF_LoadPage(document, pageIndex);
235    if (!page) {
236        jniThrowException(env, "java/lang/IllegalStateException",
237                "cannot open page");
238        return;
239    }
240
241    double width = 0;
242    double height = 0;
243
244    const int result = FPDF_GetPageSizeByIndex(document, pageIndex, &width, &height);
245    if (!result) {
246        jniThrowException(env, "java/lang/IllegalStateException",
247                    "cannot get page size");
248        return;
249    }
250
251    env->SetIntField(outSize, gPointClassInfo.x, width);
252    env->SetIntField(outSize, gPointClassInfo.y, height);
253
254    FPDF_ClosePage(page);
255}
256
257static jboolean nativeScaleForPrinting(JNIEnv* env, jclass thiz, jlong documentPtr) {
258    FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
259    FPDF_BOOL success = FPDF_VIEWERREF_GetPrintScaling(document);
260    return success ? JNI_TRUE : JNI_FALSE;
261}
262
263static bool nativeGetPageBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex,
264        PageBox pageBox, jobject outBox) {
265    FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
266
267    FPDF_PAGE page = FPDF_LoadPage(document, pageIndex);
268    if (!page) {
269        jniThrowException(env, "java/lang/IllegalStateException",
270                "cannot open page");
271        return false;
272    }
273
274    float left;
275    float top;
276    float right;
277    float bottom;
278
279    const FPDF_BOOL success = (pageBox == PAGE_BOX_MEDIA)
280        ? FPDFPage_GetMediaBox(page, &left, &top, &right, &bottom)
281        : FPDFPage_GetCropBox(page, &left, &top, &right, &bottom);
282
283    FPDF_ClosePage(page);
284
285    if (!success) {
286        return false;
287    }
288
289    env->SetIntField(outBox, gRectClassInfo.left, (int) left);
290    env->SetIntField(outBox, gRectClassInfo.top, (int) top);
291    env->SetIntField(outBox, gRectClassInfo.right, (int) right);
292    env->SetIntField(outBox, gRectClassInfo.bottom, (int) bottom);
293
294    return true;
295}
296
297static jboolean nativeGetPageMediaBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex,
298        jobject outMediaBox) {
299    const bool success = nativeGetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_MEDIA,
300            outMediaBox);
301    return success ? JNI_TRUE : JNI_FALSE;
302}
303
304static jboolean nativeGetPageCropBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex,
305        jobject outMediaBox) {
306    const bool success = nativeGetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_CROP,
307         outMediaBox);
308    return success ? JNI_TRUE : JNI_FALSE;
309}
310
311static void nativeSetPageBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex,
312        PageBox pageBox, jobject box) {
313    FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr);
314
315    FPDF_PAGE page = FPDF_LoadPage(document, pageIndex);
316    if (!page) {
317        jniThrowException(env, "java/lang/IllegalStateException",
318                "cannot open page");
319        return;
320    }
321
322    const int left = env->GetIntField(box, gRectClassInfo.left);
323    const int top = env->GetIntField(box, gRectClassInfo.top);
324    const int right = env->GetIntField(box, gRectClassInfo.right);
325    const int bottom = env->GetIntField(box, gRectClassInfo.bottom);
326
327    if (pageBox == PAGE_BOX_MEDIA) {
328        FPDFPage_SetMediaBox(page, left, top, right, bottom);
329    } else {
330        FPDFPage_SetCropBox(page, left, top, right, bottom);
331    }
332
333    FPDF_ClosePage(page);
334}
335
336static void nativeSetPageMediaBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex,
337        jobject mediaBox) {
338    nativeSetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_MEDIA, mediaBox);
339}
340
341static void nativeSetPageCropBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex,
342        jobject mediaBox) {
343    nativeSetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_CROP, mediaBox);
344}
345
346static const JNINativeMethod gPdfEditor_Methods[] = {
347    {"nativeOpen", "(IJ)J", (void*) nativeOpen},
348    {"nativeClose", "(J)V", (void*) nativeClose},
349    {"nativeGetPageCount", "(J)I", (void*) nativeGetPageCount},
350    {"nativeRemovePage", "(JI)I", (void*) nativeRemovePage},
351    {"nativeWrite", "(JI)V", (void*) nativeWrite},
352    {"nativeSetTransformAndClip", "(JIJIIII)V", (void*) nativeSetTransformAndClip},
353    {"nativeGetPageSize", "(JILandroid/graphics/Point;)V", (void*) nativeGetPageSize},
354    {"nativeScaleForPrinting", "(J)Z", (void*) nativeScaleForPrinting},
355    {"nativeGetPageMediaBox", "(JILandroid/graphics/Rect;)Z", (void*) nativeGetPageMediaBox},
356    {"nativeSetPageMediaBox", "(JILandroid/graphics/Rect;)V", (void*) nativeSetPageMediaBox},
357    {"nativeGetPageCropBox", "(JILandroid/graphics/Rect;)Z", (void*) nativeGetPageCropBox},
358    {"nativeSetPageCropBox", "(JILandroid/graphics/Rect;)V", (void*) nativeSetPageCropBox}
359};
360
361int register_android_graphics_pdf_PdfEditor(JNIEnv* env) {
362    const int result = RegisterMethodsOrDie(
363            env, "android/graphics/pdf/PdfEditor", gPdfEditor_Methods,
364            NELEM(gPdfEditor_Methods));
365
366    jclass pointClass = FindClassOrDie(env, "android/graphics/Point");
367    gPointClassInfo.x = GetFieldIDOrDie(env, pointClass, "x", "I");
368    gPointClassInfo.y = GetFieldIDOrDie(env, pointClass, "y", "I");
369
370    jclass rectClass = FindClassOrDie(env, "android/graphics/Rect");
371    gRectClassInfo.left = GetFieldIDOrDie(env, rectClass, "left", "I");
372    gRectClassInfo.top = GetFieldIDOrDie(env, rectClass, "top", "I");
373    gRectClassInfo.right = GetFieldIDOrDie(env, rectClass, "right", "I");
374    gRectClassInfo.bottom = GetFieldIDOrDie(env, rectClass, "bottom", "I");
375
376    return result;
377};
378
379};
380