• 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      *
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      */
PdfRenderer(@onNull ParcelFileDescriptor input)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      */
close()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      */
getPageCount()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      */
shouldScaleForPrinting()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      */
openPage(int index)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
finalize()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 
doClose()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 
throwIfClosed()233     private void throwIfClosed() {
234         if (mInput == null) {
235             throw new IllegalStateException("Already closed");
236         }
237     }
238 
throwIfPageOpened()239     private void throwIfPageOpened() {
240         if (mCurrentPage != null) {
241             throw new IllegalStateException("Current page not closed");
242         }
243     }
244 
throwIfPageNotInDocument(int pageIndex)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 
Page(int index)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          */
getIndex()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          */
getWidth()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          */
getHeight()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          */
render(@onNull Bitmap destination, @Nullable Rect destClip, @Nullable Matrix transform, @RenderMode int renderMode)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
close()393         public void close() {
394             throwIfClosed();
395             doClose();
396         }
397 
398         @Override
finalize()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 
doClose()410         private void doClose() {
411             nativeClosePage(mNativePage);
412             mNativePage = 0;
413             mCloseGuard.close();
414             mCurrentPage = null;
415         }
416 
throwIfClosed()417         private void throwIfClosed() {
418             if (mNativePage == 0) {
419                 throw new IllegalStateException("Already closed");
420             }
421         }
422     }
423 
nativeCreate(int fd, long size)424     private static native long nativeCreate(int fd, long size);
nativeClose(long documentPtr)425     private static native void nativeClose(long documentPtr);
nativeGetPageCount(long documentPtr)426     private static native int nativeGetPageCount(long documentPtr);
nativeScaleForPrinting(long documentPtr)427     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)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);
nativeOpenPageAndGetSize(long documentPtr, int pageIndex, Point outSize)430     private static native long nativeOpenPageAndGetSize(long documentPtr, int pageIndex,
431             Point outSize);
nativeClosePage(long pagePtr)432     private static native void nativeClosePage(long pagePtr);
433 }
434