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