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