Home | History | Annotate | Download | only in pdf
      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      */
     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