• 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.NonNull;
20 import android.annotation.Nullable;
21 import android.graphics.Matrix;
22 import android.graphics.Point;
23 import android.graphics.Rect;
24 import android.os.ParcelFileDescriptor;
25 import android.system.ErrnoException;
26 import android.system.Os;
27 import android.system.OsConstants;
28 import dalvik.system.CloseGuard;
29 import libcore.io.IoUtils;
30 import libcore.io.Libcore;
31 
32 import java.io.IOException;
33 
34 /**
35  * Class for editing PDF files.
36  *
37  * @hide
38  */
39 public final class PdfEditor {
40 
41     private final CloseGuard mCloseGuard = CloseGuard.get();
42 
43     private long mNativeDocument;
44 
45     private int mPageCount;
46 
47     private ParcelFileDescriptor mInput;
48 
49     /**
50      * Creates a new instance.
51      * <p>
52      * <strong>Note:</strong> The provided file descriptor must be <strong>seekable</strong>,
53      * i.e. its data being randomly accessed, e.g. pointing to a file. After finishing
54      * with this class you must call {@link #close()}.
55      * </p>
56      * <p>
57      * <strong>Note:</strong> This class takes ownership of the passed in file descriptor
58      * and is responsible for closing it when the editor is closed.
59      * </p>
60      *
61      * @param input Seekable file descriptor to read from.
62      *
63      * @throws java.io.IOException If an error occurs while reading the file.
64      * @throws java.lang.SecurityException If the file requires a password or
65      *         the security scheme is not supported.
66      *
67      * @see #close()
68      */
PdfEditor(@onNull ParcelFileDescriptor input)69     public PdfEditor(@NonNull ParcelFileDescriptor input) throws IOException {
70         if (input == null) {
71             throw new NullPointerException("input cannot be null");
72         }
73 
74         final long size;
75         try {
76             Os.lseek(input.getFileDescriptor(), 0, OsConstants.SEEK_SET);
77             size = Os.fstat(input.getFileDescriptor()).st_size;
78         } catch (ErrnoException ee) {
79             throw new IllegalArgumentException("file descriptor not seekable");
80         }
81         mInput = input;
82 
83         synchronized (PdfRenderer.sPdfiumLock) {
84             mNativeDocument = nativeOpen(mInput.getFd(), size);
85             try {
86                 mPageCount = nativeGetPageCount(mNativeDocument);
87             } catch (Throwable t) {
88                 nativeClose(mNativeDocument);
89                 mNativeDocument = 0;
90                 throw t;
91             }
92         }
93 
94         mCloseGuard.open("close");
95     }
96 
97     /**
98      * Gets the number of pages in the document.
99      *
100      * @return The page count.
101      */
getPageCount()102     public int getPageCount() {
103         throwIfClosed();
104         return mPageCount;
105     }
106 
107     /**
108      * Removes the page with a given index.
109      *
110      * @param pageIndex The page to remove.
111      */
removePage(int pageIndex)112     public void removePage(int pageIndex) {
113         throwIfClosed();
114         throwIfPageNotInDocument(pageIndex);
115 
116         synchronized (PdfRenderer.sPdfiumLock) {
117             mPageCount = nativeRemovePage(mNativeDocument, pageIndex);
118         }
119     }
120 
121     /**
122      * Sets a transformation and clip for a given page. The transformation matrix if
123      * non-null must be affine as per {@link android.graphics.Matrix#isAffine()}. If
124      * the clip is null, then no clipping is performed.
125      *
126      * @param pageIndex The page whose transform to set.
127      * @param transform The transformation to apply.
128      * @param clip The clip to apply.
129      */
setTransformAndClip(int pageIndex, @Nullable Matrix transform, @Nullable Rect clip)130     public void setTransformAndClip(int pageIndex, @Nullable Matrix transform,
131             @Nullable Rect clip) {
132         throwIfClosed();
133         throwIfPageNotInDocument(pageIndex);
134         throwIfNotNullAndNotAfine(transform);
135         if (transform == null) {
136             transform = Matrix.IDENTITY_MATRIX;
137         }
138         if (clip == null) {
139             Point size = new Point();
140             getPageSize(pageIndex, size);
141 
142             synchronized (PdfRenderer.sPdfiumLock) {
143                 nativeSetTransformAndClip(mNativeDocument, pageIndex, transform.native_instance,
144                         0, 0, size.x, size.y);
145             }
146         } else {
147             synchronized (PdfRenderer.sPdfiumLock) {
148                 nativeSetTransformAndClip(mNativeDocument, pageIndex, transform.native_instance,
149                         clip.left, clip.top, clip.right, clip.bottom);
150             }
151         }
152     }
153 
154     /**
155      * Gets the size of a given page in mils (1/72").
156      *
157      * @param pageIndex The page index.
158      * @param outSize The size output.
159      */
getPageSize(int pageIndex, @NonNull Point outSize)160     public void getPageSize(int pageIndex, @NonNull Point outSize) {
161         throwIfClosed();
162         throwIfOutSizeNull(outSize);
163         throwIfPageNotInDocument(pageIndex);
164 
165         synchronized (PdfRenderer.sPdfiumLock) {
166             nativeGetPageSize(mNativeDocument, pageIndex, outSize);
167         }
168     }
169 
170     /**
171      * Gets the media box of a given page in mils (1/72").
172      *
173      * @param pageIndex The page index.
174      * @param outMediaBox The media box output.
175      */
getPageMediaBox(int pageIndex, @NonNull Rect outMediaBox)176     public boolean getPageMediaBox(int pageIndex, @NonNull Rect outMediaBox) {
177         throwIfClosed();
178         throwIfOutMediaBoxNull(outMediaBox);
179         throwIfPageNotInDocument(pageIndex);
180 
181         synchronized (PdfRenderer.sPdfiumLock) {
182             return nativeGetPageMediaBox(mNativeDocument, pageIndex, outMediaBox);
183         }
184     }
185 
186     /**
187      * Sets the media box of a given page in mils (1/72").
188      *
189      * @param pageIndex The page index.
190      * @param mediaBox The media box.
191      */
setPageMediaBox(int pageIndex, @NonNull Rect mediaBox)192     public void setPageMediaBox(int pageIndex, @NonNull Rect mediaBox) {
193         throwIfClosed();
194         throwIfMediaBoxNull(mediaBox);
195         throwIfPageNotInDocument(pageIndex);
196 
197         synchronized (PdfRenderer.sPdfiumLock) {
198             nativeSetPageMediaBox(mNativeDocument, pageIndex, mediaBox);
199         }
200     }
201 
202     /**
203      * Gets the crop box of a given page in mils (1/72").
204      *
205      * @param pageIndex The page index.
206      * @param outCropBox The crop box output.
207      */
getPageCropBox(int pageIndex, @NonNull Rect outCropBox)208     public boolean getPageCropBox(int pageIndex, @NonNull Rect outCropBox) {
209         throwIfClosed();
210         throwIfOutCropBoxNull(outCropBox);
211         throwIfPageNotInDocument(pageIndex);
212 
213         synchronized (PdfRenderer.sPdfiumLock) {
214             return nativeGetPageCropBox(mNativeDocument, pageIndex, outCropBox);
215         }
216     }
217 
218     /**
219      * Sets the crop box of a given page in mils (1/72").
220      *
221      * @param pageIndex The page index.
222      * @param cropBox The crop box.
223      */
setPageCropBox(int pageIndex, @NonNull Rect cropBox)224     public void setPageCropBox(int pageIndex, @NonNull Rect cropBox) {
225         throwIfClosed();
226         throwIfCropBoxNull(cropBox);
227         throwIfPageNotInDocument(pageIndex);
228 
229         synchronized (PdfRenderer.sPdfiumLock) {
230             nativeSetPageCropBox(mNativeDocument, pageIndex, cropBox);
231         }
232     }
233 
234     /**
235      * Gets whether the document prefers to be scaled for printing.
236      *
237      * @return Whether to scale the document.
238      */
shouldScaleForPrinting()239     public boolean shouldScaleForPrinting() {
240         throwIfClosed();
241 
242         synchronized (PdfRenderer.sPdfiumLock) {
243             return nativeScaleForPrinting(mNativeDocument);
244         }
245     }
246 
247     /**
248      * Writes the PDF file to the provided destination.
249      * <p>
250      * <strong>Note:</strong> This method takes ownership of the passed in file
251      * descriptor and is responsible for closing it when writing completes.
252      * </p>
253      * @param output The destination.
254      */
write(ParcelFileDescriptor output)255     public void write(ParcelFileDescriptor output) throws IOException {
256         try {
257             throwIfClosed();
258 
259             synchronized (PdfRenderer.sPdfiumLock) {
260                 nativeWrite(mNativeDocument, output.getFd());
261             }
262         } finally {
263             IoUtils.closeQuietly(output);
264         }
265     }
266 
267     /**
268      * Closes this editor. You should not use this instance
269      * after this method is called.
270      */
close()271     public void close() {
272         throwIfClosed();
273         doClose();
274     }
275 
276     @Override
finalize()277     protected void finalize() throws Throwable {
278         try {
279             if (mCloseGuard != null) {
280                 mCloseGuard.warnIfOpen();
281             }
282 
283             doClose();
284         } finally {
285             super.finalize();
286         }
287     }
288 
doClose()289     private void doClose() {
290         if (mNativeDocument != 0) {
291             synchronized (PdfRenderer.sPdfiumLock) {
292                 nativeClose(mNativeDocument);
293             }
294             mNativeDocument = 0;
295         }
296 
297         if (mInput != null) {
298             IoUtils.closeQuietly(mInput);
299             mInput = null;
300         }
301         mCloseGuard.close();
302     }
303 
throwIfClosed()304     private void throwIfClosed() {
305         if (mInput == null) {
306             throw new IllegalStateException("Already closed");
307         }
308     }
309 
throwIfPageNotInDocument(int pageIndex)310     private void throwIfPageNotInDocument(int pageIndex) {
311         if (pageIndex < 0 || pageIndex >= mPageCount) {
312             throw new IllegalArgumentException("Invalid page index");
313         }
314     }
315 
throwIfNotNullAndNotAfine(Matrix matrix)316     private void throwIfNotNullAndNotAfine(Matrix matrix) {
317         if (matrix != null && !matrix.isAffine()) {
318             throw new IllegalStateException("Matrix must be afine");
319         }
320     }
321 
throwIfOutSizeNull(Point outSize)322     private void throwIfOutSizeNull(Point outSize) {
323         if (outSize == null) {
324             throw new NullPointerException("outSize cannot be null");
325         }
326     }
327 
throwIfOutMediaBoxNull(Rect outMediaBox)328     private void throwIfOutMediaBoxNull(Rect outMediaBox) {
329         if (outMediaBox == null) {
330             throw new NullPointerException("outMediaBox cannot be null");
331         }
332     }
333 
throwIfMediaBoxNull(Rect mediaBox)334     private void throwIfMediaBoxNull(Rect mediaBox) {
335         if (mediaBox == null) {
336             throw new NullPointerException("mediaBox cannot be null");
337         }
338     }
339 
throwIfOutCropBoxNull(Rect outCropBox)340     private void throwIfOutCropBoxNull(Rect outCropBox) {
341         if (outCropBox == null) {
342             throw new NullPointerException("outCropBox cannot be null");
343         }
344     }
345 
throwIfCropBoxNull(Rect cropBox)346     private void throwIfCropBoxNull(Rect cropBox) {
347         if (cropBox == null) {
348             throw new NullPointerException("cropBox cannot be null");
349         }
350     }
351 
nativeOpen(int fd, long size)352     private static native long nativeOpen(int fd, long size);
nativeClose(long documentPtr)353     private static native void nativeClose(long documentPtr);
nativeGetPageCount(long documentPtr)354     private static native int nativeGetPageCount(long documentPtr);
nativeRemovePage(long documentPtr, int pageIndex)355     private static native int nativeRemovePage(long documentPtr, int pageIndex);
nativeWrite(long documentPtr, int fd)356     private static native void nativeWrite(long documentPtr, int fd);
nativeSetTransformAndClip(long documentPtr, int pageIndex, long transformPtr, int clipLeft, int clipTop, int clipRight, int clipBottom)357     private static native void nativeSetTransformAndClip(long documentPtr, int pageIndex,
358             long transformPtr, int clipLeft, int clipTop, int clipRight, int clipBottom);
nativeGetPageSize(long documentPtr, int pageIndex, Point outSize)359     private static native void nativeGetPageSize(long documentPtr, int pageIndex, Point outSize);
nativeGetPageMediaBox(long documentPtr, int pageIndex, Rect outMediaBox)360     private static native boolean nativeGetPageMediaBox(long documentPtr, int pageIndex,
361             Rect outMediaBox);
nativeSetPageMediaBox(long documentPtr, int pageIndex, Rect mediaBox)362     private static native void nativeSetPageMediaBox(long documentPtr, int pageIndex,
363             Rect mediaBox);
nativeGetPageCropBox(long documentPtr, int pageIndex, Rect outMediaBox)364     private static native boolean nativeGetPageCropBox(long documentPtr, int pageIndex,
365             Rect outMediaBox);
nativeSetPageCropBox(long documentPtr, int pageIndex, Rect mediaBox)366     private static native void nativeSetPageCropBox(long documentPtr, int pageIndex,
367             Rect mediaBox);
nativeScaleForPrinting(long documentPtr)368     private static native boolean nativeScaleForPrinting(long documentPtr);
369 }
370