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 "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 43namespace android { 44 45enum PageBox {PAGE_BOX_MEDIA, PAGE_BOX_CROP}; 46 47static struct { 48 jfieldID x; 49 jfieldID y; 50} gPointClassInfo; 51 52static struct { 53 jfieldID left; 54 jfieldID top; 55 jfieldID right; 56 jfieldID bottom; 57} gRectClassInfo; 58 59static 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 71struct PdfToFdWriter : FPDF_FILEWRITE { 72 int dstFd; 73}; 74 75static 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 93static 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 103static 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 116static 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 183static 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 217static 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 258static 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 265static 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 272static 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 304static 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 309static 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 314static 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 329int 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