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