Home | History | Annotate | Download | only in pdf
      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     /**
    103      * Any call the native pdfium code has to be single threaded as the library does not support
    104      * parallel use.
    105      */
    106     final static Object sPdfiumLock = new Object();
    107 
    108     private final CloseGuard mCloseGuard = CloseGuard.get();
    109 
    110     private final Point mTempPoint = new Point();
    111 
    112     private final long mNativeDocument;
    113 
    114     private final int mPageCount;
    115 
    116     private ParcelFileDescriptor mInput;
    117 
    118     private Page mCurrentPage;
    119 
    120     /** @hide */
    121     @IntDef({
    122         Page.RENDER_MODE_FOR_DISPLAY,
    123         Page.RENDER_MODE_FOR_PRINT
    124     })
    125     @Retention(RetentionPolicy.SOURCE)
    126     public @interface RenderMode {}
    127 
    128     /**
    129      * Creates a new instance.
    130      * <p>
    131      * <strong>Note:</strong> The provided file descriptor must be <strong>seekable</strong>,
    132      * i.e. its data being randomly accessed, e.g. pointing to a file.
    133      * </p>
    134      * <p>
    135      * <strong>Note:</strong> This class takes ownership of the passed in file descriptor
    136      * and is responsible for closing it when the renderer is closed.
    137      * </p>
    138      * <p>
    139      * If the file is from an untrusted source it is recommended to run the renderer in a separate,
    140      * isolated process with minimal permissions to limit the impact of security exploits.
    141      * </p>
    142      *
    143      * @param input Seekable file descriptor to read from.
    144      *
    145      * @throws java.io.IOException If an error occurs while reading the file.
    146      * @throws java.lang.SecurityException If the file requires a password or
    147      *         the security scheme is not supported.
    148      */
    149     public PdfRenderer(@NonNull ParcelFileDescriptor input) throws IOException {
    150         if (input == null) {
    151             throw new NullPointerException("input cannot be null");
    152         }
    153 
    154         final long size;
    155         try {
    156             Libcore.os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET);
    157             size = Libcore.os.fstat(input.getFileDescriptor()).st_size;
    158         } catch (ErrnoException ee) {
    159             throw new IllegalArgumentException("file descriptor not seekable");
    160         }
    161 
    162         mInput = input;
    163 
    164         synchronized (sPdfiumLock) {
    165             mNativeDocument = nativeCreate(mInput.getFd(), size);
    166             mPageCount = nativeGetPageCount(mNativeDocument);
    167         }
    168 
    169         mCloseGuard.open("close");
    170     }
    171 
    172     /**
    173      * Closes this renderer. You should not use this instance
    174      * after this method is called.
    175      */
    176     public void close() {
    177         throwIfClosed();
    178         throwIfPageOpened();
    179         doClose();
    180     }
    181 
    182     /**
    183      * Gets the number of pages in the document.
    184      *
    185      * @return The page count.
    186      */
    187     public int getPageCount() {
    188         throwIfClosed();
    189         return mPageCount;
    190     }
    191 
    192     /**
    193      * Gets whether the document prefers to be scaled for printing.
    194      * You should take this info account if the document is rendered
    195      * for printing and the target media size differs from the page
    196      * size.
    197      *
    198      * @return If to scale the document.
    199      */
    200     public boolean shouldScaleForPrinting() {
    201         throwIfClosed();
    202 
    203         synchronized (sPdfiumLock) {
    204             return nativeScaleForPrinting(mNativeDocument);
    205         }
    206     }
    207 
    208     /**
    209      * Opens a page for rendering.
    210      *
    211      * @param index The page index.
    212      * @return A page that can be rendered.
    213      *
    214      * @see android.graphics.pdf.PdfRenderer.Page#close() PdfRenderer.Page.close()
    215      */
    216     public Page openPage(int index) {
    217         throwIfClosed();
    218         throwIfPageOpened();
    219         throwIfPageNotInDocument(index);
    220         mCurrentPage = new Page(index);
    221         return mCurrentPage;
    222     }
    223 
    224     @Override
    225     protected void finalize() throws Throwable {
    226         try {
    227             mCloseGuard.warnIfOpen();
    228             if (mInput != null) {
    229                 doClose();
    230             }
    231         } finally {
    232             super.finalize();
    233         }
    234     }
    235 
    236     private void doClose() {
    237         if (mCurrentPage != null) {
    238             mCurrentPage.close();
    239         }
    240         synchronized (sPdfiumLock) {
    241             nativeClose(mNativeDocument);
    242         }
    243         try {
    244             mInput.close();
    245         } catch (IOException ioe) {
    246             /* ignore - best effort */
    247         }
    248         mInput = null;
    249         mCloseGuard.close();
    250     }
    251 
    252     private void throwIfClosed() {
    253         if (mInput == null) {
    254             throw new IllegalStateException("Already closed");
    255         }
    256     }
    257 
    258     private void throwIfPageOpened() {
    259         if (mCurrentPage != null) {
    260             throw new IllegalStateException("Current page not closed");
    261         }
    262     }
    263 
    264     private void throwIfPageNotInDocument(int pageIndex) {
    265         if (pageIndex < 0 || pageIndex >= mPageCount) {
    266             throw new IllegalArgumentException("Invalid page index");
    267         }
    268     }
    269 
    270     /**
    271      * This class represents a PDF document page for rendering.
    272      */
    273     public final class Page implements AutoCloseable {
    274 
    275         private final CloseGuard mCloseGuard = CloseGuard.get();
    276 
    277         /**
    278          * Mode to render the content for display on a screen.
    279          */
    280         public static final int RENDER_MODE_FOR_DISPLAY = 1;
    281 
    282         /**
    283          * Mode to render the content for printing.
    284          */
    285         public static final int RENDER_MODE_FOR_PRINT = 2;
    286 
    287         private final int mIndex;
    288         private final int mWidth;
    289         private final int mHeight;
    290 
    291         private long mNativePage;
    292 
    293         private Page(int index) {
    294             Point size = mTempPoint;
    295             synchronized (sPdfiumLock) {
    296                 mNativePage = nativeOpenPageAndGetSize(mNativeDocument, index, size);
    297             }
    298             mIndex = index;
    299             mWidth = size.x;
    300             mHeight = size.y;
    301             mCloseGuard.open("close");
    302         }
    303 
    304         /**
    305          * Gets the page index.
    306          *
    307          * @return The index.
    308          */
    309         public int getIndex() {
    310             return  mIndex;
    311         }
    312 
    313         /**
    314          * Gets the page width in points (1/72").
    315          *
    316          * @return The width in points.
    317          */
    318         public int getWidth() {
    319             return mWidth;
    320         }
    321 
    322         /**
    323          * Gets the page height in points (1/72").
    324          *
    325          * @return The height in points.
    326          */
    327         public int getHeight() {
    328             return mHeight;
    329         }
    330 
    331         /**
    332          * Renders a page to a bitmap.
    333          * <p>
    334          * You may optionally specify a rectangular clip in the bitmap bounds. No rendering
    335          * outside the clip will be performed, hence it is your responsibility to initialize
    336          * the bitmap outside the clip.
    337          * </p>
    338          * <p>
    339          * You may optionally specify a matrix to transform the content from page coordinates
    340          * which are in points (1/72") to bitmap coordinates which are in pixels. If this
    341          * matrix is not provided this method will apply a transformation that will fit the
    342          * whole page to the destination clip if provided or the destination bitmap if no
    343          * clip is provided.
    344          * </p>
    345          * <p>
    346          * The clip and transformation are useful for implementing tile rendering where the
    347          * destination bitmap contains a portion of the image, for example when zooming.
    348          * Another useful application is for printing where the size of the bitmap holding
    349          * the page is too large and a client can render the page in stripes.
    350          * </p>
    351          * <p>
    352          * <strong>Note: </strong> The destination bitmap format must be
    353          * {@link Config#ARGB_8888 ARGB}.
    354          * </p>
    355          * <p>
    356          * <strong>Note: </strong> The optional transformation matrix must be affine as per
    357          * {@link android.graphics.Matrix#isAffine() Matrix.isAffine()}. Hence, you can specify
    358          * rotation, scaling, translation but not a perspective transformation.
    359          * </p>
    360          *
    361          * @param destination Destination bitmap to which to render.
    362          * @param destClip Optional clip in the bitmap bounds.
    363          * @param transform Optional transformation to apply when rendering.
    364          * @param renderMode The render mode.
    365          *
    366          * @see #RENDER_MODE_FOR_DISPLAY
    367          * @see #RENDER_MODE_FOR_PRINT
    368          */
    369         public void render(@NonNull Bitmap destination, @Nullable Rect destClip,
    370                            @Nullable Matrix transform, @RenderMode int renderMode) {
    371             if (destination.getConfig() != Config.ARGB_8888) {
    372                 throw new IllegalArgumentException("Unsupported pixel format");
    373             }
    374 
    375             if (destClip != null) {
    376                 if (destClip.left < 0 || destClip.top < 0
    377                         || destClip.right > destination.getWidth()
    378                         || destClip.bottom > destination.getHeight()) {
    379                     throw new IllegalArgumentException("destBounds not in destination");
    380                 }
    381             }
    382 
    383             if (transform != null && !transform.isAffine()) {
    384                  throw new IllegalArgumentException("transform not affine");
    385             }
    386 
    387             if (renderMode != RENDER_MODE_FOR_PRINT && renderMode != RENDER_MODE_FOR_DISPLAY) {
    388                 throw new IllegalArgumentException("Unsupported render mode");
    389             }
    390 
    391             if (renderMode == RENDER_MODE_FOR_PRINT && renderMode == RENDER_MODE_FOR_DISPLAY) {
    392                 throw new IllegalArgumentException("Only single render mode supported");
    393             }
    394 
    395             final int contentLeft = (destClip != null) ? destClip.left : 0;
    396             final int contentTop = (destClip != null) ? destClip.top : 0;
    397             final int contentRight = (destClip != null) ? destClip.right
    398                     : destination.getWidth();
    399             final int contentBottom = (destClip != null) ? destClip.bottom
    400                     : destination.getHeight();
    401 
    402             final long transformPtr = (transform != null) ? transform.native_instance : 0;
    403 
    404             synchronized (sPdfiumLock) {
    405                 nativeRenderPage(mNativeDocument, mNativePage, destination, contentLeft,
    406                         contentTop, contentRight, contentBottom, transformPtr, renderMode);
    407             }
    408         }
    409 
    410         /**
    411          * Closes this page.
    412          *
    413          * @see android.graphics.pdf.PdfRenderer#openPage(int)
    414          */
    415         @Override
    416         public void close() {
    417             throwIfClosed();
    418             doClose();
    419         }
    420 
    421         @Override
    422         protected void finalize() throws Throwable {
    423             try {
    424                 mCloseGuard.warnIfOpen();
    425                 if (mNativePage != 0) {
    426                     doClose();
    427                 }
    428             } finally {
    429                 super.finalize();
    430             }
    431         }
    432 
    433         private void doClose() {
    434             synchronized (sPdfiumLock) {
    435                 nativeClosePage(mNativePage);
    436             }
    437             mNativePage = 0;
    438             mCloseGuard.close();
    439             mCurrentPage = null;
    440         }
    441 
    442         private void throwIfClosed() {
    443             if (mNativePage == 0) {
    444                 throw new IllegalStateException("Already closed");
    445             }
    446         }
    447     }
    448 
    449     private static native long nativeCreate(int fd, long size);
    450     private static native void nativeClose(long documentPtr);
    451     private static native int nativeGetPageCount(long documentPtr);
    452     private static native boolean nativeScaleForPrinting(long documentPtr);
    453     private static native void nativeRenderPage(long documentPtr, long pagePtr, Bitmap dest,
    454             int destLeft, int destTop, int destRight, int destBottom, long matrixPtr, int renderMode);
    455     private static native long nativeOpenPageAndGetSize(long documentPtr, int pageIndex,
    456             Point outSize);
    457     private static native void nativeClosePage(long pagePtr);
    458 }
    459