Home | History | Annotate | Download | only in pdf
      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 
     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 
     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 
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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