• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
PdfRenderer(@onNull ParcelFileDescriptor input)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      */
close()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      */
getPageCount()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      */
shouldScaleForPrinting()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      */
openPage(int index)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
finalize()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 
doClose()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 
throwIfClosed()229     private void throwIfClosed() {
230         if (mInput == null) {
231             throw new IllegalStateException("Already closed");
232         }
233     }
234 
throwIfPageOpened()235     private void throwIfPageOpened() {
236         if (mCurrentPage != null) {
237             throw new IllegalStateException("Current page not closed");
238         }
239     }
240 
throwIfPageNotInDocument(int pageIndex)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 
Page(int index)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          */
getIndex()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          */
getWidth()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          */
getHeight()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          */
render(@onNull Bitmap destination, @Nullable Rect destClip, @Nullable Matrix transform, @RenderMode int renderMode)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
close()389         public void close() {
390             throwIfClosed();
391             doClose();
392         }
393 
394         @Override
finalize()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 
doClose()406         private void doClose() {
407             nativeClosePage(mNativePage);
408             mNativePage = 0;
409             mCloseGuard.close();
410             mCurrentPage = null;
411         }
412 
throwIfClosed()413         private void throwIfClosed() {
414             if (mNativePage == 0) {
415                 throw new IllegalStateException("Already closed");
416             }
417         }
418     }
419 
nativeCreate(int fd, long size)420     private static native long nativeCreate(int fd, long size);
nativeClose(long documentPtr)421     private static native void nativeClose(long documentPtr);
nativeGetPageCount(long documentPtr)422     private static native int nativeGetPageCount(long documentPtr);
nativeScaleForPrinting(long documentPtr)423     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)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);
nativeOpenPageAndGetSize(long documentPtr, int pageIndex, Point outSize)426     private static native long nativeOpenPageAndGetSize(long documentPtr, int pageIndex,
427             Point outSize);
nativeClosePage(long pagePtr)428     private static native void nativeClosePage(long pagePtr);
429 }
430