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