PdfEditor.java revision 0768a7dc450caf4c873c5b0883a75135536e1546
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
17package android.graphics.pdf;
18
19import android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.graphics.Matrix;
22import android.graphics.Point;
23import android.graphics.Rect;
24import android.os.ParcelFileDescriptor;
25import android.system.ErrnoException;
26import android.system.OsConstants;
27import dalvik.system.CloseGuard;
28import libcore.io.IoUtils;
29import libcore.io.Libcore;
30
31import java.io.IOException;
32
33/**
34 * Class for editing PDF files.
35 *
36 * @hide
37 */
38public 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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
265    public void close() {
266        throwIfClosed();
267        doClose();
268    }
269
270    @Override
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
282    private void doClose() {
283        synchronized (PdfRenderer.sPdfiumLock) {
284            nativeClose(mNativeDocument);
285        }
286        IoUtils.closeQuietly(mInput);
287        mInput = null;
288        mCloseGuard.close();
289    }
290
291    private void throwIfClosed() {
292        if (mInput == null) {
293            throw new IllegalStateException("Already closed");
294        }
295    }
296
297    private void throwIfPageNotInDocument(int pageIndex) {
298        if (pageIndex < 0 || pageIndex >= mPageCount) {
299            throw new IllegalArgumentException("Invalid page index");
300        }
301    }
302
303    private void throwIfNotNullAndNotAfine(Matrix matrix) {
304        if (matrix != null && !matrix.isAffine()) {
305            throw new IllegalStateException("Matrix must be afine");
306        }
307    }
308
309    private void throwIfOutSizeNull(Point outSize) {
310        if (outSize == null) {
311            throw new NullPointerException("outSize cannot be null");
312        }
313    }
314
315    private void throwIfOutMediaBoxNull(Rect outMediaBox) {
316        if (outMediaBox == null) {
317            throw new NullPointerException("outMediaBox cannot be null");
318        }
319    }
320
321    private void throwIfMediaBoxNull(Rect mediaBox) {
322        if (mediaBox == null) {
323            throw new NullPointerException("mediaBox cannot be null");
324        }
325    }
326
327    private void throwIfOutCropBoxNull(Rect outCropBox) {
328        if (outCropBox == null) {
329            throw new NullPointerException("outCropBox cannot be null");
330        }
331    }
332
333    private void throwIfCropBoxNull(Rect cropBox) {
334        if (cropBox == null) {
335            throw new NullPointerException("cropBox cannot be null");
336        }
337    }
338
339    private static native long nativeOpen(int fd, long size);
340    private static native void nativeClose(long documentPtr);
341    private static native int nativeGetPageCount(long documentPtr);
342    private static native int nativeRemovePage(long documentPtr, int pageIndex);
343    private static native void nativeWrite(long documentPtr, int fd);
344    private static native void nativeSetTransformAndClip(long documentPtr, int pageIndex,
345            long transformPtr, int clipLeft, int clipTop, int clipRight, int clipBottom);
346    private static native void nativeGetPageSize(long documentPtr, int pageIndex, Point outSize);
347    private static native boolean nativeGetPageMediaBox(long documentPtr, int pageIndex,
348            Rect outMediaBox);
349    private static native void nativeSetPageMediaBox(long documentPtr, int pageIndex,
350            Rect mediaBox);
351    private static native boolean nativeGetPageCropBox(long documentPtr, int pageIndex,
352            Rect outMediaBox);
353    private static native void nativeSetPageCropBox(long documentPtr, int pageIndex,
354            Rect mediaBox);
355    private static native boolean nativeScaleForPrinting(long documentPtr);
356}
357