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