PdfEditor.cpp revision f4e341d99c4c29cb6cc7380829a316cf4847f3df
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 "fpdfedit.h" 24#include "fpdfsave.h" 25#include "fsdk_rendercontext.h" 26#include "fpdf_transformpage.h" 27#pragma GCC diagnostic pop 28 29#include "SkMatrix.h" 30 31#include <android_runtime/AndroidRuntime.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(NULL); 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 jniThrowException(env, "java/lang/SecurityException", 101 "cannot create document. Error:" + error); 102 } break; 103 default: { 104 jniThrowException(env, "java/io/IOException", 105 "cannot create document. Error:" + 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 < 0) { 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 jniThrowException(env, "java/io/IOException", 173 "cannot write to fd. Error:" + 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_AffineMatrix matrix; 200 201 SkMatrix* skTransform = reinterpret_cast<SkMatrix*>(transformPtr); 202 203 SkScalar transformValues[6]; 204 skTransform->asAffine(transformValues); 205 206 // PDF's coordinate system origin is left-bottom while in graphics it 207 // is the top-left. So, translate the PDF coordinates to ours. 208 matrix.Set(1, 0, 0, -1, 0, page->GetPageHeight()); 209 210 // Apply the transformation what was created in our coordinates. 211 matrix.Concat(transformValues[SkMatrix::kAScaleX], transformValues[SkMatrix::kASkewY], 212 transformValues[SkMatrix::kASkewX], transformValues[SkMatrix::kAScaleY], 213 transformValues[SkMatrix::kATransX], transformValues[SkMatrix::kATransY]); 214 215 // Translate the result back to PDF coordinates. 216 matrix.Concat(1, 0, 0, -1, 0, page->GetPageHeight()); 217 218 FS_MATRIX transform = {matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f}; 219 FS_RECTF clip = {(float) clipLeft, (float) clipTop, (float) clipRight, (float) clipBottom}; 220 221 FPDFPage_TransFormWithClip(page, &transform, &clip); 222 223 FPDF_ClosePage(page); 224} 225 226static void nativeGetPageSize(JNIEnv* env, jclass thiz, jlong documentPtr, 227 jint pageIndex, jobject outSize) { 228 FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); 229 230 FPDF_PAGE page = FPDF_LoadPage(document, pageIndex); 231 if (!page) { 232 jniThrowException(env, "java/lang/IllegalStateException", 233 "cannot open page"); 234 return; 235 } 236 237 double width = 0; 238 double height = 0; 239 240 const int result = FPDF_GetPageSizeByIndex(document, pageIndex, &width, &height); 241 if (!result) { 242 jniThrowException(env, "java/lang/IllegalStateException", 243 "cannot get page size"); 244 return; 245 } 246 247 env->SetIntField(outSize, gPointClassInfo.x, width); 248 env->SetIntField(outSize, gPointClassInfo.y, height); 249 250 FPDF_ClosePage(page); 251} 252 253static jboolean nativeScaleForPrinting(JNIEnv* env, jclass thiz, jlong documentPtr) { 254 FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); 255 FPDF_BOOL success = FPDF_VIEWERREF_GetPrintScaling(document); 256 return success ? JNI_TRUE : JNI_FALSE; 257} 258 259static bool nativeGetPageBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, 260 PageBox pageBox, jobject outBox) { 261 FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); 262 263 FPDF_PAGE page = FPDF_LoadPage(document, pageIndex); 264 if (!page) { 265 jniThrowException(env, "java/lang/IllegalStateException", 266 "cannot open page"); 267 return false; 268 } 269 270 float left; 271 float top; 272 float right; 273 float bottom; 274 275 const FPDF_BOOL success = (pageBox == PAGE_BOX_MEDIA) 276 ? FPDFPage_GetMediaBox(page, &left, &top, &right, &bottom) 277 : FPDFPage_GetCropBox(page, &left, &top, &right, &bottom); 278 279 FPDF_ClosePage(page); 280 281 if (!success) { 282 return false; 283 } 284 285 env->SetIntField(outBox, gRectClassInfo.left, (int) left); 286 env->SetIntField(outBox, gRectClassInfo.top, (int) top); 287 env->SetIntField(outBox, gRectClassInfo.right, (int) right); 288 env->SetIntField(outBox, gRectClassInfo.bottom, (int) bottom); 289 290 return true; 291} 292 293static jboolean nativeGetPageMediaBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, 294 jobject outMediaBox) { 295 const bool success = nativeGetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_MEDIA, 296 outMediaBox); 297 return success ? JNI_TRUE : JNI_FALSE; 298} 299 300static jboolean nativeGetPageCropBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, 301 jobject outMediaBox) { 302 const bool success = nativeGetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_CROP, 303 outMediaBox); 304 return success ? JNI_TRUE : JNI_FALSE; 305} 306 307static void nativeSetPageBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, 308 PageBox pageBox, jobject box) { 309 FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); 310 311 FPDF_PAGE page = FPDF_LoadPage(document, pageIndex); 312 if (!page) { 313 jniThrowException(env, "java/lang/IllegalStateException", 314 "cannot open page"); 315 return; 316 } 317 318 const int left = env->GetIntField(box, gRectClassInfo.left); 319 const int top = env->GetIntField(box, gRectClassInfo.top); 320 const int right = env->GetIntField(box, gRectClassInfo.right); 321 const int bottom = env->GetIntField(box, gRectClassInfo.bottom); 322 323 if (pageBox == PAGE_BOX_MEDIA) { 324 FPDFPage_SetMediaBox(page, left, top, right, bottom); 325 } else { 326 FPDFPage_SetCropBox(page, left, top, right, bottom); 327 } 328 329 FPDF_ClosePage(page); 330} 331 332static void nativeSetPageMediaBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, 333 jobject mediaBox) { 334 nativeSetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_MEDIA, mediaBox); 335} 336 337static void nativeSetPageCropBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, 338 jobject mediaBox) { 339 nativeSetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_CROP, mediaBox); 340} 341 342static JNINativeMethod gPdfEditor_Methods[] = { 343 {"nativeOpen", "(IJ)J", (void*) nativeOpen}, 344 {"nativeClose", "(J)V", (void*) nativeClose}, 345 {"nativeGetPageCount", "(J)I", (void*) nativeGetPageCount}, 346 {"nativeRemovePage", "(JI)I", (void*) nativeRemovePage}, 347 {"nativeWrite", "(JI)V", (void*) nativeWrite}, 348 {"nativeSetTransformAndClip", "(JIJIIII)V", (void*) nativeSetTransformAndClip}, 349 {"nativeGetPageSize", "(JILandroid/graphics/Point;)V", (void*) nativeGetPageSize}, 350 {"nativeScaleForPrinting", "(J)Z", (void*) nativeScaleForPrinting}, 351 {"nativeGetPageMediaBox", "(JILandroid/graphics/Rect;)Z", (void*) nativeGetPageMediaBox}, 352 {"nativeSetPageMediaBox", "(JILandroid/graphics/Rect;)V", (void*) nativeSetPageMediaBox}, 353 {"nativeGetPageCropBox", "(JILandroid/graphics/Rect;)Z", (void*) nativeGetPageCropBox}, 354 {"nativeSetPageCropBox", "(JILandroid/graphics/Rect;)V", (void*) nativeSetPageCropBox} 355}; 356 357int register_android_graphics_pdf_PdfEditor(JNIEnv* env) { 358 const int result = android::AndroidRuntime::registerNativeMethods( 359 env, "android/graphics/pdf/PdfEditor", gPdfEditor_Methods, 360 NELEM(gPdfEditor_Methods)); 361 362 jclass pointClass = env->FindClass("android/graphics/Point"); 363 gPointClassInfo.x = env->GetFieldID(pointClass, "x", "I"); 364 gPointClassInfo.y = env->GetFieldID(pointClass, "y", "I"); 365 366 jclass rectClass = env->FindClass("android/graphics/Rect"); 367 gRectClassInfo.left = env->GetFieldID(rectClass, "left", "I"); 368 gRectClassInfo.top = env->GetFieldID(rectClass, "top", "I"); 369 gRectClassInfo.right = env->GetFieldID(rectClass, "right", "I"); 370 gRectClassInfo.bottom = env->GetFieldID(rectClass, "bottom", "I"); 371 372 return result; 373}; 374 375}; 376