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