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