/* * 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. */ #undef LOG_TAG #define LOG_TAG "PdfEditor" #include #include #include #include #include #include "PdfUtils.h" #include "graphics_jni_helpers.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 "fpdf_transformpage.h" #pragma GCC diagnostic pop #include "SkMatrix.h" 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 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; } ALOGE("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); } } 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); 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; } // PDF's coordinate system origin is left-bottom while in graphics it // is the top-left. So, translate the PDF coordinates to ours. SkMatrix reflectOnX = SkMatrix::Scale(1, -1); SkMatrix moveUp = SkMatrix::Translate(0, FPDF_GetPageHeight(page)); SkMatrix coordinateChange = SkMatrix::Concat(moveUp, reflectOnX); // Apply the transformation what was created in our coordinates. SkMatrix matrix = SkMatrix::Concat(*reinterpret_cast(transformPtr), coordinateChange); // Translate the result back to PDF coordinates. matrix.setConcat(coordinateChange, matrix); SkScalar transformValues[6]; if (!matrix.asAffine(transformValues)) { FPDF_ClosePage(page); jniThrowException(env, "java/lang/IllegalArgumentException", "transform matrix has perspective. Only affine matrices are allowed."); return; } FS_MATRIX transform = {transformValues[SkMatrix::kAScaleX], transformValues[SkMatrix::kASkewY], transformValues[SkMatrix::kASkewX], transformValues[SkMatrix::kAScaleY], transformValues[SkMatrix::kATransX], transformValues[SkMatrix::kATransY]}; 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 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 const 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; }; };