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#include "fpdfview.h" 20#include "fpdfedit.h" 21#include "fpdfsave.h" 22#include "fsdk_rendercontext.h" 23#include "fpdf_transformpage.h" 24#include "SkMatrix.h" 25 26#include <android_runtime/AndroidRuntime.h> 27#include <vector> 28#include <utils/Log.h> 29#include <unistd.h> 30#include <sys/types.h> 31#include <unistd.h> 32 33namespace android { 34 35enum PageBox {PAGE_BOX_MEDIA, PAGE_BOX_CROP}; 36 37static struct { 38 jfieldID x; 39 jfieldID y; 40} gPointClassInfo; 41 42static struct { 43 jfieldID left; 44 jfieldID top; 45 jfieldID right; 46 jfieldID bottom; 47} gRectClassInfo; 48 49static Mutex sLock; 50 51static int sUnmatchedInitRequestCount = 0; 52 53static void initializeLibraryIfNeeded() { 54 Mutex::Autolock _l(sLock); 55 if (sUnmatchedInitRequestCount == 0) { 56 FPDF_InitLibrary(NULL); 57 } 58 sUnmatchedInitRequestCount++; 59} 60 61static void destroyLibraryIfNeeded() { 62 Mutex::Autolock _l(sLock); 63 sUnmatchedInitRequestCount--; 64 if (sUnmatchedInitRequestCount == 0) { 65 FPDF_DestroyLibrary(); 66 } 67} 68 69static int getBlock(void* param, unsigned long position, unsigned char* outBuffer, 70 unsigned long size) { 71 const int fd = reinterpret_cast<intptr_t>(param); 72 const int readCount = pread(fd, outBuffer, size, position); 73 if (readCount < 0) { 74 ALOGE("Cannot read from file descriptor. Error:%d", errno); 75 return 0; 76 } 77 return 1; 78} 79 80static jlong nativeOpen(JNIEnv* env, jclass thiz, jint fd, jlong size) { 81 initializeLibraryIfNeeded(); 82 83 FPDF_FILEACCESS loader; 84 loader.m_FileLen = size; 85 loader.m_Param = reinterpret_cast<void*>(intptr_t(fd)); 86 loader.m_GetBlock = &getBlock; 87 88 FPDF_DOCUMENT document = FPDF_LoadCustomDocument(&loader, NULL); 89 90 if (!document) { 91 const long error = FPDF_GetLastError(); 92 switch (error) { 93 case FPDF_ERR_PASSWORD: 94 case FPDF_ERR_SECURITY: { 95 jniThrowException(env, "java/lang/SecurityException", 96 "cannot create document. Error:" + error); 97 } break; 98 default: { 99 jniThrowException(env, "java/io/IOException", 100 "cannot create document. Error:" + error); 101 } break; 102 } 103 destroyLibraryIfNeeded(); 104 return -1; 105 } 106 107 return reinterpret_cast<jlong>(document); 108} 109 110static void nativeClose(JNIEnv* env, jclass thiz, jlong documentPtr) { 111 FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); 112 FPDF_CloseDocument(document); 113 destroyLibraryIfNeeded(); 114} 115 116static jint nativeGetPageCount(JNIEnv* env, jclass thiz, jlong documentPtr) { 117 FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); 118 return FPDF_GetPageCount(document); 119} 120 121static jint nativeRemovePage(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex) { 122 FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); 123 FPDFPage_Delete(document, pageIndex); 124 return FPDF_GetPageCount(document); 125} 126 127struct PdfToFdWriter : FPDF_FILEWRITE { 128 int dstFd; 129}; 130 131static bool writeAllBytes(const int fd, const void* buffer, const size_t byteCount) { 132 char* writeBuffer = static_cast<char*>(const_cast<void*>(buffer)); 133 size_t remainingBytes = byteCount; 134 while (remainingBytes > 0) { 135 ssize_t writtenByteCount = write(fd, writeBuffer, remainingBytes); 136 if (writtenByteCount == -1) { 137 if (errno == EINTR) { 138 continue; 139 } 140 __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, 141 "Error writing to buffer: %d", errno); 142 return false; 143 } 144 remainingBytes -= writtenByteCount; 145 writeBuffer += writtenByteCount; 146 } 147 return true; 148} 149 150static int writeBlock(FPDF_FILEWRITE* owner, const void* buffer, unsigned long size) { 151 const PdfToFdWriter* writer = reinterpret_cast<PdfToFdWriter*>(owner); 152 const bool success = writeAllBytes(writer->dstFd, buffer, size); 153 if (success < 0) { 154 ALOGE("Cannot write to file descriptor. Error:%d", errno); 155 return 0; 156 } 157 return 1; 158} 159 160static void nativeWrite(JNIEnv* env, jclass thiz, jlong documentPtr, jint fd) { 161 FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); 162 PdfToFdWriter writer; 163 writer.dstFd = fd; 164 writer.WriteBlock = &writeBlock; 165 const bool success = FPDF_SaveAsCopy(document, &writer, FPDF_NO_INCREMENTAL); 166 if (!success) { 167 jniThrowException(env, "java/io/IOException", 168 "cannot write to fd. Error:" + errno); 169 destroyLibraryIfNeeded(); 170 } 171} 172 173static void nativeSetTransformAndClip(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, 174 jlong transformPtr, jint clipLeft, jint clipTop, jint clipRight, jint clipBottom) { 175 FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); 176 177 CPDF_Page* page = (CPDF_Page*) FPDF_LoadPage(document, pageIndex); 178 if (!page) { 179 jniThrowException(env, "java/lang/IllegalStateException", 180 "cannot open page"); 181 return; 182 } 183 184 double width = 0; 185 double height = 0; 186 187 const int result = FPDF_GetPageSizeByIndex(document, pageIndex, &width, &height); 188 if (!result) { 189 jniThrowException(env, "java/lang/IllegalStateException", 190 "cannot get page size"); 191 return; 192 } 193 194 CFX_AffineMatrix matrix; 195 196 SkMatrix* skTransform = reinterpret_cast<SkMatrix*>(transformPtr); 197 198 SkScalar transformValues[6]; 199 skTransform->asAffine(transformValues); 200 201 // PDF's coordinate system origin is left-bottom while in graphics it 202 // is the top-left. So, translate the PDF coordinates to ours. 203 matrix.Set(1, 0, 0, -1, 0, page->GetPageHeight()); 204 205 // Apply the transformation what was created in our coordinates. 206 matrix.Concat(transformValues[SkMatrix::kAScaleX], transformValues[SkMatrix::kASkewY], 207 transformValues[SkMatrix::kASkewX], transformValues[SkMatrix::kAScaleY], 208 transformValues[SkMatrix::kATransX], transformValues[SkMatrix::kATransY]); 209 210 // Translate the result back to PDF coordinates. 211 matrix.Concat(1, 0, 0, -1, 0, page->GetPageHeight()); 212 213 FS_MATRIX transform = {matrix.a, matrix.b, matrix.c, matrix.d, matrix.e, matrix.f}; 214 FS_RECTF clip = {(float) clipLeft, (float) clipTop, (float) clipRight, (float) clipBottom}; 215 216 FPDFPage_TransFormWithClip(page, &transform, &clip); 217 218 FPDF_ClosePage(page); 219} 220 221static void nativeGetPageSize(JNIEnv* env, jclass thiz, jlong documentPtr, 222 jint pageIndex, jobject outSize) { 223 FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); 224 225 FPDF_PAGE page = FPDF_LoadPage(document, pageIndex); 226 if (!page) { 227 jniThrowException(env, "java/lang/IllegalStateException", 228 "cannot open page"); 229 return; 230 } 231 232 double width = 0; 233 double height = 0; 234 235 const int result = FPDF_GetPageSizeByIndex(document, pageIndex, &width, &height); 236 if (!result) { 237 jniThrowException(env, "java/lang/IllegalStateException", 238 "cannot get page size"); 239 return; 240 } 241 242 env->SetIntField(outSize, gPointClassInfo.x, width); 243 env->SetIntField(outSize, gPointClassInfo.y, height); 244 245 FPDF_ClosePage(page); 246} 247 248static jboolean nativeScaleForPrinting(JNIEnv* env, jclass thiz, jlong documentPtr) { 249 FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); 250 FPDF_BOOL success = FPDF_VIEWERREF_GetPrintScaling(document); 251 return success ? JNI_TRUE : JNI_FALSE; 252} 253 254static bool nativeGetPageBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, 255 PageBox pageBox, jobject outBox) { 256 FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); 257 258 FPDF_PAGE page = FPDF_LoadPage(document, pageIndex); 259 if (!page) { 260 jniThrowException(env, "java/lang/IllegalStateException", 261 "cannot open page"); 262 return false; 263 } 264 265 float left; 266 float top; 267 float right; 268 float bottom; 269 270 const FPDF_BOOL success = (pageBox == PAGE_BOX_MEDIA) 271 ? FPDFPage_GetMediaBox(page, &left, &top, &right, &bottom) 272 : FPDFPage_GetCropBox(page, &left, &top, &right, &bottom); 273 274 FPDF_ClosePage(page); 275 276 if (!success) { 277 return false; 278 } 279 280 env->SetIntField(outBox, gRectClassInfo.left, (int) left); 281 env->SetIntField(outBox, gRectClassInfo.top, (int) top); 282 env->SetIntField(outBox, gRectClassInfo.right, (int) right); 283 env->SetIntField(outBox, gRectClassInfo.bottom, (int) bottom); 284 285 return true; 286} 287 288static jboolean nativeGetPageMediaBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, 289 jobject outMediaBox) { 290 const bool success = nativeGetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_MEDIA, 291 outMediaBox); 292 return success ? JNI_TRUE : JNI_FALSE; 293} 294 295static jboolean nativeGetPageCropBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, 296 jobject outMediaBox) { 297 const bool success = nativeGetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_CROP, 298 outMediaBox); 299 return success ? JNI_TRUE : JNI_FALSE; 300} 301 302static void nativeSetPageBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, 303 PageBox pageBox, jobject box) { 304 FPDF_DOCUMENT document = reinterpret_cast<FPDF_DOCUMENT>(documentPtr); 305 306 FPDF_PAGE page = FPDF_LoadPage(document, pageIndex); 307 if (!page) { 308 jniThrowException(env, "java/lang/IllegalStateException", 309 "cannot open page"); 310 return; 311 } 312 313 const int left = env->GetIntField(box, gRectClassInfo.left); 314 const int top = env->GetIntField(box, gRectClassInfo.top); 315 const int right = env->GetIntField(box, gRectClassInfo.right); 316 const int bottom = env->GetIntField(box, gRectClassInfo.bottom); 317 318 if (pageBox == PAGE_BOX_MEDIA) { 319 FPDFPage_SetMediaBox(page, left, top, right, bottom); 320 } else { 321 FPDFPage_SetCropBox(page, left, top, right, bottom); 322 } 323 324 FPDF_ClosePage(page); 325} 326 327static void nativeSetPageMediaBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, 328 jobject mediaBox) { 329 nativeSetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_MEDIA, mediaBox); 330} 331 332static void nativeSetPageCropBox(JNIEnv* env, jclass thiz, jlong documentPtr, jint pageIndex, 333 jobject mediaBox) { 334 nativeSetPageBox(env, thiz, documentPtr, pageIndex, PAGE_BOX_CROP, mediaBox); 335} 336 337static JNINativeMethod gPdfEditor_Methods[] = { 338 {"nativeOpen", "(IJ)J", (void*) nativeOpen}, 339 {"nativeClose", "(J)V", (void*) nativeClose}, 340 {"nativeGetPageCount", "(J)I", (void*) nativeGetPageCount}, 341 {"nativeRemovePage", "(JI)I", (void*) nativeRemovePage}, 342 {"nativeWrite", "(JI)V", (void*) nativeWrite}, 343 {"nativeSetTransformAndClip", "(JIJIIII)V", (void*) nativeSetTransformAndClip}, 344 {"nativeGetPageSize", "(JILandroid/graphics/Point;)V", (void*) nativeGetPageSize}, 345 {"nativeScaleForPrinting", "(J)Z", (void*) nativeScaleForPrinting}, 346 {"nativeGetPageMediaBox", "(JILandroid/graphics/Rect;)Z", (void*) nativeGetPageMediaBox}, 347 {"nativeSetPageMediaBox", "(JILandroid/graphics/Rect;)V", (void*) nativeSetPageMediaBox}, 348 {"nativeGetPageCropBox", "(JILandroid/graphics/Rect;)Z", (void*) nativeGetPageCropBox}, 349 {"nativeSetPageCropBox", "(JILandroid/graphics/Rect;)V", (void*) nativeSetPageCropBox} 350}; 351 352int register_android_graphics_pdf_PdfEditor(JNIEnv* env) { 353 const int result = android::AndroidRuntime::registerNativeMethods( 354 env, "android/graphics/pdf/PdfEditor", gPdfEditor_Methods, 355 NELEM(gPdfEditor_Methods)); 356 357 jclass pointClass = env->FindClass("android/graphics/Point"); 358 gPointClassInfo.x = env->GetFieldID(pointClass, "x", "I"); 359 gPointClassInfo.y = env->GetFieldID(pointClass, "y", "I"); 360 361 jclass rectClass = env->FindClass("android/graphics/Rect"); 362 gRectClassInfo.left = env->GetFieldID(rectClass, "left", "I"); 363 gRectClassInfo.top = env->GetFieldID(rectClass, "top", "I"); 364 gRectClassInfo.right = env->GetFieldID(rectClass, "right", "I"); 365 gRectClassInfo.bottom = env->GetFieldID(rectClass, "bottom", "I"); 366 367 return result; 368}; 369 370}; 371