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