• 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.annotation.UnsupportedAppUsage;
23 import android.graphics.Bitmap;
24 import android.graphics.Bitmap.Config;
25 import android.graphics.Matrix;
26 import android.graphics.Point;
27 import android.graphics.Rect;
28 import android.os.ParcelFileDescriptor;
29 import android.system.ErrnoException;
30 import android.system.Os;
31 import android.system.OsConstants;
32 import com.android.internal.util.Preconditions;
33 import dalvik.system.CloseGuard;
34 
35 import libcore.io.IoUtils;
36 
37 import java.io.IOException;
38 import java.lang.annotation.Retention;
39 import java.lang.annotation.RetentionPolicy;
40 
41 /**
42  * <p>
43  * This class enables rendering a PDF document. This class is not thread safe.
44  * </p>
45  * <p>
46  * If you want to render a PDF, you create a renderer and for every page you want
47  * to render, you open the page, render it, and close the page. After you are done
48  * with rendering, you close the renderer. After the renderer is closed it should not
49  * be used anymore. Note that the pages are rendered one by one, i.e. you can have
50  * only a single page opened at any given time.
51  * </p>
52  * <p>
53  * A typical use of the APIs to render a PDF looks like this:
54  * </p>
55  * <pre>
56  * // create a new renderer
57  * PdfRenderer renderer = new PdfRenderer(getSeekableFileDescriptor());
58  *
59  * // let us just render all pages
60  * final int pageCount = renderer.getPageCount();
61  * for (int i = 0; i < pageCount; i++) {
62  *     Page page = renderer.openPage(i);
63  *
64  *     // say we render for showing on the screen
65  *     page.render(mBitmap, null, null, Page.RENDER_MODE_FOR_DISPLAY);
66  *
67  *     // do stuff with the bitmap
68  *
69  *     // close the page
70  *     page.close();
71  * }
72  *
73  * // close the renderer
74  * renderer.close();
75  * </pre>
76  *
77  * <h3>Print preview and print output</h3>
78  * <p>
79  * If you are using this class to rasterize a PDF for printing or show a print
80  * preview, it is recommended that you respect the following contract in order
81  * to provide a consistent user experience when seeing a preview and printing,
82  * i.e. the user sees a preview that is the same as the printout.
83  * </p>
84  * <ul>
85  * <li>
86  * Respect the property whether the document would like to be scaled for printing
87  * as per {@link #shouldScaleForPrinting()}.
88  * </li>
89  * <li>
90  * When scaling a document for printing the aspect ratio should be preserved.
91  * </li>
92  * <li>
93  * Do not inset the content with any margins from the {@link android.print.PrintAttributes}
94  * as the application is responsible to render it such that the margins are respected.
95  * </li>
96  * <li>
97  * If document page size is greater than the printed media size the content should
98  * be anchored to the upper left corner of the page for left-to-right locales and
99  * top right corner for right-to-left locales.
100  * </li>
101  * </ul>
102  *
103  * @see #close()
104  */
105 public final class PdfRenderer implements AutoCloseable {
106     /**
107      * Any call the native pdfium code has to be single threaded as the library does not support
108      * parallel use.
109      */
110     final static Object sPdfiumLock = new Object();
111 
112     private final CloseGuard mCloseGuard = CloseGuard.get();
113 
114     private final Point mTempPoint = new Point();
115 
116     private long mNativeDocument;
117 
118     private final int mPageCount;
119 
120     private ParcelFileDescriptor mInput;
121 
122     @UnsupportedAppUsage
123     private Page mCurrentPage;
124 
125     /** @hide */
126     @IntDef({
127         Page.RENDER_MODE_FOR_DISPLAY,
128         Page.RENDER_MODE_FOR_PRINT
129     })
130     @Retention(RetentionPolicy.SOURCE)
131     public @interface RenderMode {}
132 
133     /**
134      * Creates a new instance.
135      * <p>
136      * <strong>Note:</strong> The provided file descriptor must be <strong>seekable</strong>,
137      * i.e. its data being randomly accessed, e.g. pointing to a file.
138      * </p>
139      * <p>
140      * <strong>Note:</strong> This class takes ownership of the passed in file descriptor
141      * and is responsible for closing it when the renderer is closed.
142      * </p>
143      * <p>
144      * If the file is from an untrusted source it is recommended to run the renderer in a separate,
145      * isolated process with minimal permissions to limit the impact of security exploits.
146      * </p>
147      *
148      * @param input Seekable file descriptor to read from.
149      *
150      * @throws java.io.IOException If an error occurs while reading the file.
151      * @throws java.lang.SecurityException If the file requires a password or
152      *         the security scheme is not supported.
153      */
PdfRenderer(@onNull ParcelFileDescriptor input)154     public PdfRenderer(@NonNull ParcelFileDescriptor input) throws IOException {
155         if (input == null) {
156             throw new NullPointerException("input cannot be null");
157         }
158 
159         final long size;
160         try {
161             Os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET);
162             size = Os.fstat(input.getFileDescriptor()).st_size;
163         } catch (ErrnoException ee) {
164             throw new IllegalArgumentException("file descriptor not seekable");
165         }
166         mInput = input;
167 
168         synchronized (sPdfiumLock) {
169             mNativeDocument = nativeCreate(mInput.getFd(), size);
170             try {
171                 mPageCount = nativeGetPageCount(mNativeDocument);
172             } catch (Throwable t) {
173                 nativeClose(mNativeDocument);
174                 mNativeDocument = 0;
175                 throw t;
176             }
177         }
178 
179         mCloseGuard.open("close");
180     }
181 
182     /**
183      * Closes this renderer. You should not use this instance
184      * after this method is called.
185      */
close()186     public void close() {
187         throwIfClosed();
188         throwIfPageOpened();
189         doClose();
190     }
191 
192     /**
193      * Gets the number of pages in the document.
194      *
195      * @return The page count.
196      */
getPageCount()197     public int getPageCount() {
198         throwIfClosed();
199         return mPageCount;
200     }
201 
202     /**
203      * Gets whether the document prefers to be scaled for printing.
204      * You should take this info account if the document is rendered
205      * for printing and the target media size differs from the page
206      * size.
207      *
208      * @return If to scale the document.
209      */
shouldScaleForPrinting()210     public boolean shouldScaleForPrinting() {
211         throwIfClosed();
212 
213         synchronized (sPdfiumLock) {
214             return nativeScaleForPrinting(mNativeDocument);
215         }
216     }
217 
218     /**
219      * Opens a page for rendering.
220      *
221      * @param index The page index.
222      * @return A page that can be rendered.
223      *
224      * @see android.graphics.pdf.PdfRenderer.Page#close() PdfRenderer.Page.close()
225      */
openPage(int index)226     public Page openPage(int index) {
227         throwIfClosed();
228         throwIfPageOpened();
229         throwIfPageNotInDocument(index);
230         mCurrentPage = new Page(index);
231         return mCurrentPage;
232     }
233 
234     @Override
finalize()235     protected void finalize() throws Throwable {
236         try {
237             if (mCloseGuard != null) {
238                 mCloseGuard.warnIfOpen();
239             }
240 
241             doClose();
242         } finally {
243             super.finalize();
244         }
245     }
246 
247     @UnsupportedAppUsage
doClose()248     private void doClose() {
249         if (mCurrentPage != null) {
250             mCurrentPage.close();
251             mCurrentPage = null;
252         }
253 
254         if (mNativeDocument != 0) {
255             synchronized (sPdfiumLock) {
256                 nativeClose(mNativeDocument);
257             }
258             mNativeDocument = 0;
259         }
260 
261         if (mInput != null) {
262             IoUtils.closeQuietly(mInput);
263             mInput = null;
264         }
265         mCloseGuard.close();
266     }
267 
throwIfClosed()268     private void throwIfClosed() {
269         if (mInput == null) {
270             throw new IllegalStateException("Already closed");
271         }
272     }
273 
throwIfPageOpened()274     private void throwIfPageOpened() {
275         if (mCurrentPage != null) {
276             throw new IllegalStateException("Current page not closed");
277         }
278     }
279 
throwIfPageNotInDocument(int pageIndex)280     private void throwIfPageNotInDocument(int pageIndex) {
281         if (pageIndex < 0 || pageIndex >= mPageCount) {
282             throw new IllegalArgumentException("Invalid page index");
283         }
284     }
285 
286     /**
287      * This class represents a PDF document page for rendering.
288      */
289     public final class Page implements AutoCloseable {
290 
291         private final CloseGuard mCloseGuard = CloseGuard.get();
292 
293         /**
294          * Mode to render the content for display on a screen.
295          */
296         public static final int RENDER_MODE_FOR_DISPLAY = 1;
297 
298         /**
299          * Mode to render the content for printing.
300          */
301         public static final int RENDER_MODE_FOR_PRINT = 2;
302 
303         private final int mIndex;
304         private final int mWidth;
305         private final int mHeight;
306 
307         private long mNativePage;
308 
Page(int index)309         private Page(int index) {
310             Point size = mTempPoint;
311             synchronized (sPdfiumLock) {
312                 mNativePage = nativeOpenPageAndGetSize(mNativeDocument, index, size);
313             }
314             mIndex = index;
315             mWidth = size.x;
316             mHeight = size.y;
317             mCloseGuard.open("close");
318         }
319 
320         /**
321          * Gets the page index.
322          *
323          * @return The index.
324          */
getIndex()325         public int getIndex() {
326             return  mIndex;
327         }
328 
329         /**
330          * Gets the page width in points (1/72").
331          *
332          * @return The width in points.
333          */
getWidth()334         public int getWidth() {
335             return mWidth;
336         }
337 
338         /**
339          * Gets the page height in points (1/72").
340          *
341          * @return The height in points.
342          */
getHeight()343         public int getHeight() {
344             return mHeight;
345         }
346 
347         /**
348          * Renders a page to a bitmap.
349          * <p>
350          * You may optionally specify a rectangular clip in the bitmap bounds. No rendering
351          * outside the clip will be performed, hence it is your responsibility to initialize
352          * the bitmap outside the clip.
353          * </p>
354          * <p>
355          * You may optionally specify a matrix to transform the content from page coordinates
356          * which are in points (1/72") to bitmap coordinates which are in pixels. If this
357          * matrix is not provided this method will apply a transformation that will fit the
358          * whole page to the destination clip if provided or the destination bitmap if no
359          * clip is provided.
360          * </p>
361          * <p>
362          * The clip and transformation are useful for implementing tile rendering where the
363          * destination bitmap contains a portion of the image, for example when zooming.
364          * Another useful application is for printing where the size of the bitmap holding
365          * the page is too large and a client can render the page in stripes.
366          * </p>
367          * <p>
368          * <strong>Note: </strong> The destination bitmap format must be
369          * {@link Config#ARGB_8888 ARGB}.
370          * </p>
371          * <p>
372          * <strong>Note: </strong> The optional transformation matrix must be affine as per
373          * {@link android.graphics.Matrix#isAffine() Matrix.isAffine()}. Hence, you can specify
374          * rotation, scaling, translation but not a perspective transformation.
375          * </p>
376          *
377          * @param destination Destination bitmap to which to render.
378          * @param destClip Optional clip in the bitmap bounds.
379          * @param transform Optional transformation to apply when rendering.
380          * @param renderMode The render mode.
381          *
382          * @see #RENDER_MODE_FOR_DISPLAY
383          * @see #RENDER_MODE_FOR_PRINT
384          */
render(@onNull Bitmap destination, @Nullable Rect destClip, @Nullable Matrix transform, @RenderMode int renderMode)385         public void render(@NonNull Bitmap destination, @Nullable Rect destClip,
386                            @Nullable Matrix transform, @RenderMode int renderMode) {
387             if (mNativePage == 0) {
388                 throw new NullPointerException();
389             }
390 
391             destination = Preconditions.checkNotNull(destination, "bitmap null");
392 
393             if (destination.getConfig() != Config.ARGB_8888) {
394                 throw new IllegalArgumentException("Unsupported pixel format");
395             }
396 
397             if (destClip != null) {
398                 if (destClip.left < 0 || destClip.top < 0
399                         || destClip.right > destination.getWidth()
400                         || destClip.bottom > destination.getHeight()) {
401                     throw new IllegalArgumentException("destBounds not in destination");
402                 }
403             }
404 
405             if (transform != null && !transform.isAffine()) {
406                  throw new IllegalArgumentException("transform not affine");
407             }
408 
409             if (renderMode != RENDER_MODE_FOR_PRINT && renderMode != RENDER_MODE_FOR_DISPLAY) {
410                 throw new IllegalArgumentException("Unsupported render mode");
411             }
412 
413             if (renderMode == RENDER_MODE_FOR_PRINT && renderMode == RENDER_MODE_FOR_DISPLAY) {
414                 throw new IllegalArgumentException("Only single render mode supported");
415             }
416 
417             final int contentLeft = (destClip != null) ? destClip.left : 0;
418             final int contentTop = (destClip != null) ? destClip.top : 0;
419             final int contentRight = (destClip != null) ? destClip.right
420                     : destination.getWidth();
421             final int contentBottom = (destClip != null) ? destClip.bottom
422                     : destination.getHeight();
423 
424             // If transform is not set, stretch page to whole clipped area
425             if (transform == null) {
426                 int clipWidth = contentRight - contentLeft;
427                 int clipHeight = contentBottom - contentTop;
428 
429                 transform = new Matrix();
430                 transform.postScale((float)clipWidth / getWidth(),
431                         (float)clipHeight / getHeight());
432                 transform.postTranslate(contentLeft, contentTop);
433             }
434 
435             final long transformPtr = transform.native_instance;
436 
437             synchronized (sPdfiumLock) {
438                 nativeRenderPage(mNativeDocument, mNativePage, destination.getNativeInstance(),
439                         contentLeft, contentTop, contentRight, contentBottom, transformPtr,
440                         renderMode);
441             }
442         }
443 
444         /**
445          * Closes this page.
446          *
447          * @see android.graphics.pdf.PdfRenderer#openPage(int)
448          */
449         @Override
close()450         public void close() {
451             throwIfClosed();
452             doClose();
453         }
454 
455         @Override
finalize()456         protected void finalize() throws Throwable {
457             try {
458                 if (mCloseGuard != null) {
459                     mCloseGuard.warnIfOpen();
460                 }
461 
462                 doClose();
463             } finally {
464                 super.finalize();
465             }
466         }
467 
doClose()468         private void doClose() {
469             if (mNativePage != 0) {
470                 synchronized (sPdfiumLock) {
471                     nativeClosePage(mNativePage);
472                 }
473                 mNativePage = 0;
474             }
475 
476             mCloseGuard.close();
477             mCurrentPage = null;
478         }
479 
throwIfClosed()480         private void throwIfClosed() {
481             if (mNativePage == 0) {
482                 throw new IllegalStateException("Already closed");
483             }
484         }
485     }
486 
nativeCreate(int fd, long size)487     private static native long nativeCreate(int fd, long size);
nativeClose(long documentPtr)488     private static native void nativeClose(long documentPtr);
nativeGetPageCount(long documentPtr)489     private static native int nativeGetPageCount(long documentPtr);
nativeScaleForPrinting(long documentPtr)490     private static native boolean nativeScaleForPrinting(long documentPtr);
nativeRenderPage(long documentPtr, long pagePtr, long bitmapHandle, int clipLeft, int clipTop, int clipRight, int clipBottom, long transformPtr, int renderMode)491     private static native void nativeRenderPage(long documentPtr, long pagePtr, long bitmapHandle,
492             int clipLeft, int clipTop, int clipRight, int clipBottom, long transformPtr,
493             int renderMode);
nativeOpenPageAndGetSize(long documentPtr, int pageIndex, Point outSize)494     private static native long nativeOpenPageAndGetSize(long documentPtr, int pageIndex,
495             Point outSize);
nativeClosePage(long pagePtr)496     private static native void nativeClosePage(long pagePtr);
497 }
498