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     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