/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "jni.h" #include "JNIHelp.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" #include "fpdfview.h" #include "fpdf_edit.h" #include "fpdf_save.h" #include "fsdk_rendercontext.h" #include "fpdf_transformpage.h" #pragma GCC diagnostic pop #include "SkMatrix.h" #include #include #include #include #include #include namespace android { enum PageBox {PAGE_BOX_MEDIA, PAGE_BOX_CROP}; static struct { jfieldID x; jfieldID y; } gPointClassInfo; static struct { jfieldID left; jfieldID top; jfieldID right; jfieldID bottom; } gRectClassInfo; static Mutex sLock; static int sUnmatchedInitRequestCount = 0; static void initializeLibraryIfNeeded() { Mutex::Autolock _l(sLock); if (sUnmatchedInitRequestCount == 0) { FPDF_InitLibrary(); } sUnmatchedInitRequestCount++; } static void destroyLibraryIfNeeded() { Mutex::Autolock _l(sLock); sUnmatchedInitRequestCount--; if (sUnmatchedInitRequestCount == 0) { FPDF_DestroyLibrary(); } } static int getBlock(void* param, unsigned long position, unsigned char* outBuffer, unsigned long size) { const int fd = reinterpret_cast(param); const int readCount = pread(fd, outBuffer, size, position); if (readCount < 0) { ALOGE("Cannot read from file descriptor. Error:%d", errno); return 0; } return 1; } static jlong nativeOpen(JNIEnv* env, jclass thiz, jint fd, jlong size) { initializeLibraryIfNeeded(); FPDF_FILEACCESS loader; loader.m_FileLen = size; loader.m_Param = reinterpret_cast(intptr_t(fd)); loader.m_GetBlock = &getBlock; FPDF_DOCUMENT document = FPDF_LoadCustomDocument(&loader, NULL); if (!document) { const long error = FPDF_GetLastError(); switch (error) { case FPDF_ERR_PASSWORD: case FPDF_ERR_SECURITY: { jniThrowExceptionFmt(env, "java/lang/SecurityException", "cannot create document. Error: %ld", error); } break; default: { jniThrowExceptionFmt(env, "java/io/IOException", "cannot create document. Error: %ld", error); } break; } destroyLibraryIfNeeded(); return -1; } return reinterpret_cast(document); } static void nativeClose(JNIEnv* env, jclass thiz, jlong documentPtr) { FPDF_DOCUMENT document = reinterpret_cast(documentPtr); FPDF_CloseDocument(document); destroyLibraryIfNeeded(); } static jint nativeGetPageCount(JNIEnv* env, jclass thiz, jlong documentPtr) { FPDF_DOCUMENT document = reinterpret_cast(documentPtr); return FPDF_GetPageCount(document); } static jint nativeRemovePage(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex) { FPDF_DOCUMENT document = reinterpret_cast(documentPtr); FPDFPage_Delete(document, pageIndex); return FPDF_GetPageCount(document); } struct PdfToFdWriter : FPDF_FILEWRITE { int dstFd; }; static bool writeAllBytes(const int fd, const void* buffer, const size_t byteCount) { char* writeBuffer = static_cast(const_cast(buffer)); size_t remainingBytes = byteCount; while (remainingBytes > 0) { ssize_t writtenByteCount = write(fd, writeBuffer, remainingBytes); if (writtenByteCount == -1) { if (errno == EINTR) { continue; } __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Error writing to buffer: %d", errno); return false; } remainingBytes -= writtenByteCount; writeBuffer += writtenByteCount; } return true; } static int writeBlock(FPDF_FILEWRITE* owner, const void* buffer, unsigned long size) { const PdfToFdWriter* writer = reinterpret_cast(owner); const bool success = writeAllBytes(writer->dstFd, buffer, size); if (!success) { ALOGE("Cannot write to file descriptor. Error:%d", errno); return 0; } return 1; } static void nativeWrite(JNIEnv* env, jclass thiz, jlong documentPtr, jint fd) { FPDF_DOCUMENT document = reinterpret_cast(documentPtr); PdfToFdWriter writer; writer.dstFd = fd; writer.WriteBlock = &writeBlock; const bool success = FPDF_SaveAsCopy(document, &writer, FPDF_NO_INCREMENTAL); if (!success) { jniThrowExceptionFmt(env, "java/io/IOException", "cannot write to fd. Error: %d", errno); destroyLibraryIfNeeded(); } } static void nativeSetTransformAndClip(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, jlong transformPtr, jint clipLeft, jint clipTop, jint clipRight, jint clipBottom) { FPDF_DOCUMENT document = reinterpret_cast(documentPtr); CPDF_Page* page = (CPDF_Page*) FPDF_LoadPage(document, pageIndex); if (!page) { jniThrowException(env, "java/lang/IllegalStateException", "cannot open page"); return; } double width = 0; double height = 0; const int result = FPDF_GetPageSizeByIndex(document, pageIndex, &width, &height); if (!result) { jniThrowException(env, "java/lang/IllegalStateException", "cannot get page size"); return; } CFX_AffineMatrix matrix; SkMatrix* skTransform = reinterpret_cast(transformPtr); SkScalar transformValues[6]; if (!skTransform->asAffine(transformValues)) { jniThrowException(env, "java/lang/IllegalArgumentException", "transform matrix has perspective. Only affine matrices are allowed."); return; } // PDF's coordinate system origin is left-bottom while in graphics it // is the top-left. So, translate the PDF coordinates to ours. matrix.Set(1, 0, 0, -1, 0, page->GetPageHeight()); // Apply the transformation what was created in our coordinates. matrix.Concat(transformValues[SkMatrix::kAScaleX], transformValues[SkMatrix::kASkewY], transformValues[SkMatrix::kASkewX], transformValues[SkMatrix::kAScaleY], transformValues[SkMatrix::kATransX], transformValues[SkMatrix::kATransY]); // Translate the result back to PDF coordinates. matrix.Concat(1, 0, 0, -1, 0, page->GetPageHeight()); FS_MATRIX transform = {matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f}; FS_RECTF clip = {(float) clipLeft, (float) clipTop, (float) clipRight, (float) clipBottom}; FPDFPage_TransFormWithClip(page, &transform, &clip); FPDF_ClosePage(page); } static void nativeGetPageSize(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, jobject outSize) { FPDF_DOCUMENT document = reinterpret_cast(documentPtr); FPDF_PAGE page = FPDF_LoadPage(document, pageIndex); if (!page) { jniThrowException(env, "java/lang/IllegalStateException", "cannot open page"); return; } double width = 0; double height = 0; const int result = FPDF_GetPageSizeByIndex(document, pageIndex, &width, &height); if (!result) { jniThrowException(env, "java/lang/IllegalStateException", "cannot get page size"); return; } env->SetIntField(outSize, gPointClassInfo.x, width); env->SetIntField(outSize, gPointClassInfo.y, height); FPDF_ClosePage(page); } static jboolean nativeScaleForPrinting(JNIEnv* env, jclass thiz, jlong documentPtr) { FPDF_DOCUMENT document = reinterpret_cast(documentPtr); FPDF_BOOL success = FPDF_VIEWERREF_GetPrintScaling(document); return success ? JNI_TRUE : JNI_FALSE; } static bool nativeGetPageBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, PageBox pageBox, jobject outBox) { FPDF_DOCUMENT document = reinterpret_cast(documentPtr); FPDF_PAGE page = FPDF_LoadPage(document, pageIndex); if (!page) { jniThrowException(env, "java/lang/IllegalStateException", "cannot open page"); return false; } float left; float top; float right; float bottom; const FPDF_BOOL success = (pageBox == PAGE_BOX_MEDIA) ? FPDFPage_GetMediaBox(page, &left, &top, &right, &bottom) : FPDFPage_GetCropBox(page, &left, &top, &right, &bottom); FPDF_ClosePage(page); if (!success) { return false; } env->SetIntField(outBox, gRectClassInfo.left, (int) left); env->SetIntField(outBox, gRectClassInfo.top, (int) top); env->SetIntField(outBox, gRectClassInfo.right, (int) right); env->SetIntField(outBox, gRectClassInfo.bottom, (int) bottom); return true; } static jboolean nativeGetPageMediaBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, jobject outMediaBox) { const bool success = nativeGetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_MEDIA, outMediaBox); return success ? JNI_TRUE : JNI_FALSE; } static jboolean nativeGetPageCropBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, jobject outMediaBox) { const bool success = nativeGetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_CROP, outMediaBox); return success ? JNI_TRUE : JNI_FALSE; } static void nativeSetPageBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, PageBox pageBox, jobject box) { FPDF_DOCUMENT document = reinterpret_cast(documentPtr); FPDF_PAGE page = FPDF_LoadPage(document, pageIndex); if (!page) { jniThrowException(env, "java/lang/IllegalStateException", "cannot open page"); return; } const int left = env->GetIntField(box, gRectClassInfo.left); const int top = env->GetIntField(box, gRectClassInfo.top); const int right = env->GetIntField(box, gRectClassInfo.right); const int bottom = env->GetIntField(box, gRectClassInfo.bottom); if (pageBox == PAGE_BOX_MEDIA) { FPDFPage_SetMediaBox(page, left, top, right, bottom); } else { FPDFPage_SetCropBox(page, left, top, right, bottom); } FPDF_ClosePage(page); } static void nativeSetPageMediaBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, jobject mediaBox) { nativeSetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_MEDIA, mediaBox); } static void nativeSetPageCropBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, jobject mediaBox) { nativeSetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_CROP, mediaBox); } static JNINativeMethod gPdfEditor_Methods[] = { {"nativeOpen", "(IJ)J", (void*) nativeOpen}, {"nativeClose", "(J)V", (void*) nativeClose}, {"nativeGetPageCount", "(J)I", (void*) nativeGetPageCount}, {"nativeRemovePage", "(JI)I", (void*) nativeRemovePage}, {"nativeWrite", "(JI)V", (void*) nativeWrite}, {"nativeSetTransformAndClip", "(JIJIIII)V", (void*) nativeSetTransformAndClip}, {"nativeGetPageSize", "(JILandroid/graphics/Point;)V", (void*) nativeGetPageSize}, {"nativeScaleForPrinting", "(J)Z", (void*) nativeScaleForPrinting}, {"nativeGetPageMediaBox", "(JILandroid/graphics/Rect;)Z", (void*) nativeGetPageMediaBox}, {"nativeSetPageMediaBox", "(JILandroid/graphics/Rect;)V", (void*) nativeSetPageMediaBox}, {"nativeGetPageCropBox", "(JILandroid/graphics/Rect;)Z", (void*) nativeGetPageCropBox}, {"nativeSetPageCropBox", "(JILandroid/graphics/Rect;)V", (void*) nativeSetPageCropBox} }; int register_android_graphics_pdf_PdfEditor(JNIEnv* env) { const int result = RegisterMethodsOrDie( env, "android/graphics/pdf/PdfEditor", gPdfEditor_Methods, NELEM(gPdfEditor_Methods)); jclass pointClass = FindClassOrDie(env, "android/graphics/Point"); gPointClassInfo.x = GetFieldIDOrDie(env, pointClass, "x", "I"); gPointClassInfo.y = GetFieldIDOrDie(env, pointClass, "y", "I"); jclass rectClass = FindClassOrDie(env, "android/graphics/Rect"); gRectClassInfo.left = GetFieldIDOrDie(env, rectClass, "left", "I"); gRectClassInfo.top = GetFieldIDOrDie(env, rectClass, "top", "I"); gRectClassInfo.right = GetFieldIDOrDie(env, rectClass, "right", "I"); gRectClassInfo.bottom = GetFieldIDOrDie(env, rectClass, "bottom", "I"); return result; }; };