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