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 */ PdfEditor(@onNull ParcelFileDescriptor input)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 */ getPageCount()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 */ removePage(int pageIndex)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 */ setTransformAndClip(int pageIndex, @Nullable Matrix transform, @Nullable Rect clip)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 */ getPageSize(int pageIndex, @NonNull Point outSize)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 */ getPageMediaBox(int pageIndex, @NonNull Rect outMediaBox)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 */ setPageMediaBox(int pageIndex, @NonNull Rect mediaBox)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 */ getPageCropBox(int pageIndex, @NonNull Rect outCropBox)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 */ setPageCropBox(int pageIndex, @NonNull Rect cropBox)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 */ shouldScaleForPrinting()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 */ write(ParcelFileDescriptor output)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 */ close()232 public void close() { 233 throwIfClosed(); 234 doClose(); 235 } 236 237 @Override finalize()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 doClose()249 private void doClose() { 250 nativeClose(mNativeDocument); 251 IoUtils.closeQuietly(mInput); 252 mInput = null; 253 mCloseGuard.close(); 254 } 255 throwIfClosed()256 private void throwIfClosed() { 257 if (mInput == null) { 258 throw new IllegalStateException("Already closed"); 259 } 260 } 261 throwIfPageNotInDocument(int pageIndex)262 private void throwIfPageNotInDocument(int pageIndex) { 263 if (pageIndex < 0 || pageIndex >= mPageCount) { 264 throw new IllegalArgumentException("Invalid page index"); 265 } 266 } 267 throwIfNotNullAndNotAfine(Matrix matrix)268 private void throwIfNotNullAndNotAfine(Matrix matrix) { 269 if (matrix != null && !matrix.isAffine()) { 270 throw new IllegalStateException("Matrix must be afine"); 271 } 272 } 273 throwIfOutSizeNull(Point outSize)274 private void throwIfOutSizeNull(Point outSize) { 275 if (outSize == null) { 276 throw new NullPointerException("outSize cannot be null"); 277 } 278 } 279 throwIfOutMediaBoxNull(Rect outMediaBox)280 private void throwIfOutMediaBoxNull(Rect outMediaBox) { 281 if (outMediaBox == null) { 282 throw new NullPointerException("outMediaBox cannot be null"); 283 } 284 } 285 throwIfMediaBoxNull(Rect mediaBox)286 private void throwIfMediaBoxNull(Rect mediaBox) { 287 if (mediaBox == null) { 288 throw new NullPointerException("mediaBox cannot be null"); 289 } 290 } 291 throwIfOutCropBoxNull(Rect outCropBox)292 private void throwIfOutCropBoxNull(Rect outCropBox) { 293 if (outCropBox == null) { 294 throw new NullPointerException("outCropBox cannot be null"); 295 } 296 } 297 throwIfCropBoxNull(Rect cropBox)298 private void throwIfCropBoxNull(Rect cropBox) { 299 if (cropBox == null) { 300 throw new NullPointerException("cropBox cannot be null"); 301 } 302 } 303 nativeOpen(int fd, long size)304 private static native long nativeOpen(int fd, long size); nativeClose(long documentPtr)305 private static native void nativeClose(long documentPtr); nativeGetPageCount(long documentPtr)306 private static native int nativeGetPageCount(long documentPtr); nativeRemovePage(long documentPtr, int pageIndex)307 private static native int nativeRemovePage(long documentPtr, int pageIndex); nativeWrite(long documentPtr, int fd)308 private static native void nativeWrite(long documentPtr, int fd); nativeSetTransformAndClip(long documentPtr, int pageIndex, long transformPtr, int clipLeft, int clipTop, int clipRight, int clipBottom)309 private static native void nativeSetTransformAndClip(long documentPtr, int pageIndex, 310 long transformPtr, int clipLeft, int clipTop, int clipRight, int clipBottom); nativeGetPageSize(long documentPtr, int pageIndex, Point outSize)311 private static native void nativeGetPageSize(long documentPtr, int pageIndex, Point outSize); nativeGetPageMediaBox(long documentPtr, int pageIndex, Rect outMediaBox)312 private static native boolean nativeGetPageMediaBox(long documentPtr, int pageIndex, 313 Rect outMediaBox); nativeSetPageMediaBox(long documentPtr, int pageIndex, Rect mediaBox)314 private static native void nativeSetPageMediaBox(long documentPtr, int pageIndex, 315 Rect mediaBox); nativeGetPageCropBox(long documentPtr, int pageIndex, Rect outMediaBox)316 private static native boolean nativeGetPageCropBox(long documentPtr, int pageIndex, 317 Rect outMediaBox); nativeSetPageCropBox(long documentPtr, int pageIndex, Rect mediaBox)318 private static native void nativeSetPageCropBox(long documentPtr, int pageIndex, 319 Rect mediaBox); nativeScaleForPrinting(long documentPtr)320 private static native boolean nativeScaleForPrinting(long documentPtr); 321 } 322