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 */ 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 */ 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 */ 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 */ 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 */ 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 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 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 233 private void throwIfClosed() { 234 if (mInput == null) { 235 throw new IllegalStateException("Already closed"); 236 } 237 } 238 239 private void throwIfPageOpened() { 240 if (mCurrentPage != null) { 241 throw new IllegalStateException("Current page not closed"); 242 } 243 } 244 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 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 */ 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 */ 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 */ 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 */ 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 393 public void close() { 394 throwIfClosed(); 395 doClose(); 396 } 397 398 @Override 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 410 private void doClose() { 411 nativeClosePage(mNativePage); 412 mNativePage = 0; 413 mCloseGuard.close(); 414 mCurrentPage = null; 415 } 416 417 private void throwIfClosed() { 418 if (mNativePage == 0) { 419 throw new IllegalStateException("Already closed"); 420 } 421 } 422 } 423 424 private static native long nativeCreate(int fd, long size); 425 private static native void nativeClose(long documentPtr); 426 private static native int nativeGetPageCount(long documentPtr); 427 private static native boolean nativeScaleForPrinting(long documentPtr); 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); 430 private static native long nativeOpenPageAndGetSize(long documentPtr, int pageIndex, 431 Point outSize); 432 private static native void nativeClosePage(long pagePtr); 433 } 434