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 package android.graphics.pdf; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.graphics.Bitmap; 23 import android.graphics.Bitmap.Config; 24 import android.graphics.Matrix; 25 import android.graphics.Point; 26 import android.graphics.Rect; 27 import android.os.ParcelFileDescriptor; 28 import android.system.ErrnoException; 29 import android.system.Os; 30 import android.system.OsConstants; 31 import com.android.internal.util.Preconditions; 32 import dalvik.system.CloseGuard; 33 34 import libcore.io.IoUtils; 35 36 import java.io.IOException; 37 import java.lang.annotation.Retention; 38 import java.lang.annotation.RetentionPolicy; 39 40 /** 41 * <p> 42 * This class enables rendering a PDF document. This class is not thread safe. 43 * </p> 44 * <p> 45 * If you want to render a PDF, you create a renderer and for every page you want 46 * to render, you open the page, render it, and close the page. After you are done 47 * with rendering, you close the renderer. After the renderer is closed it should not 48 * be used anymore. Note that the pages are rendered one by one, i.e. you can have 49 * only a single page opened at any given time. 50 * </p> 51 * <p> 52 * A typical use of the APIs to render a PDF looks like this: 53 * </p> 54 * <pre> 55 * // create a new renderer 56 * PdfRenderer renderer = new PdfRenderer(getSeekableFileDescriptor()); 57 * 58 * // let us just render all pages 59 * final int pageCount = renderer.getPageCount(); 60 * for (int i = 0; i < pageCount; i++) { 61 * Page page = renderer.openPage(i); 62 * 63 * // say we render for showing on the screen 64 * page.render(mBitmap, null, null, Page.RENDER_MODE_FOR_DISPLAY); 65 * 66 * // do stuff with the bitmap 67 * 68 * // close the page 69 * page.close(); 70 * } 71 * 72 * // close the renderer 73 * renderer.close(); 74 * </pre> 75 * 76 * <h3>Print preview and print output</h3> 77 * <p> 78 * If you are using this class to rasterize a PDF for printing or show a print 79 * preview, it is recommended that you respect the following contract in order 80 * to provide a consistent user experience when seeing a preview and printing, 81 * i.e. the user sees a preview that is the same as the printout. 82 * </p> 83 * <ul> 84 * <li> 85 * Respect the property whether the document would like to be scaled for printing 86 * as per {@link #shouldScaleForPrinting()}. 87 * </li> 88 * <li> 89 * When scaling a document for printing the aspect ratio should be preserved. 90 * </li> 91 * <li> 92 * Do not inset the content with any margins from the {@link android.print.PrintAttributes} 93 * as the application is responsible to render it such that the margins are respected. 94 * </li> 95 * <li> 96 * If document page size is greater than the printed media size the content should 97 * be anchored to the upper left corner of the page for left-to-right locales and 98 * top right corner for right-to-left locales. 99 * </li> 100 * </ul> 101 * 102 * @see #close() 103 */ 104 public final class PdfRenderer implements AutoCloseable { 105 /** 106 * Any call the native pdfium code has to be single threaded as the library does not support 107 * parallel use. 108 */ 109 final static Object sPdfiumLock = new Object(); 110 111 private final CloseGuard mCloseGuard = CloseGuard.get(); 112 113 private final Point mTempPoint = new Point(); 114 115 private long mNativeDocument; 116 117 private final int mPageCount; 118 119 private ParcelFileDescriptor mInput; 120 121 private Page mCurrentPage; 122 123 /** @hide */ 124 @IntDef({ 125 Page.RENDER_MODE_FOR_DISPLAY, 126 Page.RENDER_MODE_FOR_PRINT 127 }) 128 @Retention(RetentionPolicy.SOURCE) 129 public @interface RenderMode {} 130 131 /** 132 * Creates a new instance. 133 * <p> 134 * <strong>Note:</strong> The provided file descriptor must be <strong>seekable</strong>, 135 * i.e. its data being randomly accessed, e.g. pointing to a file. 136 * </p> 137 * <p> 138 * <strong>Note:</strong> This class takes ownership of the passed in file descriptor 139 * and is responsible for closing it when the renderer is closed. 140 * </p> 141 * <p> 142 * If the file is from an untrusted source it is recommended to run the renderer in a separate, 143 * isolated process with minimal permissions to limit the impact of security exploits. 144 * </p> 145 * 146 * @param input Seekable file descriptor to read from. 147 * 148 * @throws java.io.IOException If an error occurs while reading the file. 149 * @throws java.lang.SecurityException If the file requires a password or 150 * the security scheme is not supported. 151 */ 152 public PdfRenderer(@NonNull ParcelFileDescriptor input) throws IOException { 153 if (input == null) { 154 throw new NullPointerException("input cannot be null"); 155 } 156 157 final long size; 158 try { 159 Os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET); 160 size = Os.fstat(input.getFileDescriptor()).st_size; 161 } catch (ErrnoException ee) { 162 throw new IllegalArgumentException("file descriptor not seekable"); 163 } 164 mInput = input; 165 166 synchronized (sPdfiumLock) { 167 mNativeDocument = nativeCreate(mInput.getFd(), size); 168 try { 169 mPageCount = nativeGetPageCount(mNativeDocument); 170 } catch (Throwable t) { 171 nativeClose(mNativeDocument); 172 mNativeDocument = 0; 173 throw t; 174 } 175 } 176 177 mCloseGuard.open("close"); 178 } 179 180 /** 181 * Closes this renderer. You should not use this instance 182 * after this method is called. 183 */ 184 public void close() { 185 throwIfClosed(); 186 throwIfPageOpened(); 187 doClose(); 188 } 189 190 /** 191 * Gets the number of pages in the document. 192 * 193 * @return The page count. 194 */ 195 public int getPageCount() { 196 throwIfClosed(); 197 return mPageCount; 198 } 199 200 /** 201 * Gets whether the document prefers to be scaled for printing. 202 * You should take this info account if the document is rendered 203 * for printing and the target media size differs from the page 204 * size. 205 * 206 * @return If to scale the document. 207 */ 208 public boolean shouldScaleForPrinting() { 209 throwIfClosed(); 210 211 synchronized (sPdfiumLock) { 212 return nativeScaleForPrinting(mNativeDocument); 213 } 214 } 215 216 /** 217 * Opens a page for rendering. 218 * 219 * @param index The page index. 220 * @return A page that can be rendered. 221 * 222 * @see android.graphics.pdf.PdfRenderer.Page#close() PdfRenderer.Page.close() 223 */ 224 public Page openPage(int index) { 225 throwIfClosed(); 226 throwIfPageOpened(); 227 throwIfPageNotInDocument(index); 228 mCurrentPage = new Page(index); 229 return mCurrentPage; 230 } 231 232 @Override 233 protected void finalize() throws Throwable { 234 try { 235 if (mCloseGuard != null) { 236 mCloseGuard.warnIfOpen(); 237 } 238 239 doClose(); 240 } finally { 241 super.finalize(); 242 } 243 } 244 245 private void doClose() { 246 if (mCurrentPage != null) { 247 mCurrentPage.close(); 248 mCurrentPage = null; 249 } 250 251 if (mNativeDocument != 0) { 252 synchronized (sPdfiumLock) { 253 nativeClose(mNativeDocument); 254 } 255 mNativeDocument = 0; 256 } 257 258 if (mInput != null) { 259 IoUtils.closeQuietly(mInput); 260 mInput = null; 261 } 262 mCloseGuard.close(); 263 } 264 265 private void throwIfClosed() { 266 if (mInput == null) { 267 throw new IllegalStateException("Already closed"); 268 } 269 } 270 271 private void throwIfPageOpened() { 272 if (mCurrentPage != null) { 273 throw new IllegalStateException("Current page not closed"); 274 } 275 } 276 277 private void throwIfPageNotInDocument(int pageIndex) { 278 if (pageIndex < 0 || pageIndex >= mPageCount) { 279 throw new IllegalArgumentException("Invalid page index"); 280 } 281 } 282 283 /** 284 * This class represents a PDF document page for rendering. 285 */ 286 public final class Page implements AutoCloseable { 287 288 private final CloseGuard mCloseGuard = CloseGuard.get(); 289 290 /** 291 * Mode to render the content for display on a screen. 292 */ 293 public static final int RENDER_MODE_FOR_DISPLAY = 1; 294 295 /** 296 * Mode to render the content for printing. 297 */ 298 public static final int RENDER_MODE_FOR_PRINT = 2; 299 300 private final int mIndex; 301 private final int mWidth; 302 private final int mHeight; 303 304 private long mNativePage; 305 306 private Page(int index) { 307 Point size = mTempPoint; 308 synchronized (sPdfiumLock) { 309 mNativePage = nativeOpenPageAndGetSize(mNativeDocument, index, size); 310 } 311 mIndex = index; 312 mWidth = size.x; 313 mHeight = size.y; 314 mCloseGuard.open("close"); 315 } 316 317 /** 318 * Gets the page index. 319 * 320 * @return The index. 321 */ 322 public int getIndex() { 323 return mIndex; 324 } 325 326 /** 327 * Gets the page width in points (1/72"). 328 * 329 * @return The width in points. 330 */ 331 public int getWidth() { 332 return mWidth; 333 } 334 335 /** 336 * Gets the page height in points (1/72"). 337 * 338 * @return The height in points. 339 */ 340 public int getHeight() { 341 return mHeight; 342 } 343 344 /** 345 * Renders a page to a bitmap. 346 * <p> 347 * You may optionally specify a rectangular clip in the bitmap bounds. No rendering 348 * outside the clip will be performed, hence it is your responsibility to initialize 349 * the bitmap outside the clip. 350 * </p> 351 * <p> 352 * You may optionally specify a matrix to transform the content from page coordinates 353 * which are in points (1/72") to bitmap coordinates which are in pixels. If this 354 * matrix is not provided this method will apply a transformation that will fit the 355 * whole page to the destination clip if provided or the destination bitmap if no 356 * clip is provided. 357 * </p> 358 * <p> 359 * The clip and transformation are useful for implementing tile rendering where the 360 * destination bitmap contains a portion of the image, for example when zooming. 361 * Another useful application is for printing where the size of the bitmap holding 362 * the page is too large and a client can render the page in stripes. 363 * </p> 364 * <p> 365 * <strong>Note: </strong> The destination bitmap format must be 366 * {@link Config#ARGB_8888 ARGB}. 367 * </p> 368 * <p> 369 * <strong>Note: </strong> The optional transformation matrix must be affine as per 370 * {@link android.graphics.Matrix#isAffine() Matrix.isAffine()}. Hence, you can specify 371 * rotation, scaling, translation but not a perspective transformation. 372 * </p> 373 * 374 * @param destination Destination bitmap to which to render. 375 * @param destClip Optional clip in the bitmap bounds. 376 * @param transform Optional transformation to apply when rendering. 377 * @param renderMode The render mode. 378 * 379 * @see #RENDER_MODE_FOR_DISPLAY 380 * @see #RENDER_MODE_FOR_PRINT 381 */ 382 public void render(@NonNull Bitmap destination, @Nullable Rect destClip, 383 @Nullable Matrix transform, @RenderMode int renderMode) { 384 if (mNativePage == 0) { 385 throw new NullPointerException(); 386 } 387 388 destination = Preconditions.checkNotNull(destination, "bitmap null"); 389 390 if (destination.getConfig() != Config.ARGB_8888) { 391 throw new IllegalArgumentException("Unsupported pixel format"); 392 } 393 394 if (destClip != null) { 395 if (destClip.left < 0 || destClip.top < 0 396 || destClip.right > destination.getWidth() 397 || destClip.bottom > destination.getHeight()) { 398 throw new IllegalArgumentException("destBounds not in destination"); 399 } 400 } 401 402 if (transform != null && !transform.isAffine()) { 403 throw new IllegalArgumentException("transform not affine"); 404 } 405 406 if (renderMode != RENDER_MODE_FOR_PRINT && renderMode != RENDER_MODE_FOR_DISPLAY) { 407 throw new IllegalArgumentException("Unsupported render mode"); 408 } 409 410 if (renderMode == RENDER_MODE_FOR_PRINT && renderMode == RENDER_MODE_FOR_DISPLAY) { 411 throw new IllegalArgumentException("Only single render mode supported"); 412 } 413 414 final int contentLeft = (destClip != null) ? destClip.left : 0; 415 final int contentTop = (destClip != null) ? destClip.top : 0; 416 final int contentRight = (destClip != null) ? destClip.right 417 : destination.getWidth(); 418 final int contentBottom = (destClip != null) ? destClip.bottom 419 : destination.getHeight(); 420 421 // If transform is not set, stretch page to whole clipped area 422 if (transform == null) { 423 int clipWidth = contentRight - contentLeft; 424 int clipHeight = contentBottom - contentTop; 425 426 transform = new Matrix(); 427 transform.postScale((float)clipWidth / getWidth(), 428 (float)clipHeight / getHeight()); 429 transform.postTranslate(contentLeft, contentTop); 430 } 431 432 final long transformPtr = transform.native_instance; 433 434 synchronized (sPdfiumLock) { 435 nativeRenderPage(mNativeDocument, mNativePage, destination, contentLeft, 436 contentTop, contentRight, contentBottom, transformPtr, renderMode); 437 } 438 } 439 440 /** 441 * Closes this page. 442 * 443 * @see android.graphics.pdf.PdfRenderer#openPage(int) 444 */ 445 @Override 446 public void close() { 447 throwIfClosed(); 448 doClose(); 449 } 450 451 @Override 452 protected void finalize() throws Throwable { 453 try { 454 if (mCloseGuard != null) { 455 mCloseGuard.warnIfOpen(); 456 } 457 458 doClose(); 459 } finally { 460 super.finalize(); 461 } 462 } 463 464 private void doClose() { 465 if (mNativePage != 0) { 466 synchronized (sPdfiumLock) { 467 nativeClosePage(mNativePage); 468 } 469 mNativePage = 0; 470 } 471 472 mCloseGuard.close(); 473 mCurrentPage = null; 474 } 475 476 private void throwIfClosed() { 477 if (mNativePage == 0) { 478 throw new IllegalStateException("Already closed"); 479 } 480 } 481 } 482 483 private static native long nativeCreate(int fd, long size); 484 private static native void nativeClose(long documentPtr); 485 private static native int nativeGetPageCount(long documentPtr); 486 private static native boolean nativeScaleForPrinting(long documentPtr); 487 private static native void nativeRenderPage(long documentPtr, long pagePtr, Bitmap dest, 488 int clipLeft, int clipTop, int clipRight, int clipBottom, long transformPtr, 489 int renderMode); 490 private static native long nativeOpenPageAndGetSize(long documentPtr, int pageIndex, 491 Point outSize); 492 private static native void nativeClosePage(long pagePtr); 493 } 494