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