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 */ PdfEditor(@onNull ParcelFileDescriptor input)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 */ getPageCount()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 */ removePage(int pageIndex)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 */ setTransformAndClip(int pageIndex, @Nullable Matrix transform, @Nullable Rect clip)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 */ getPageSize(int pageIndex, @NonNull Point outSize)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 */ getPageMediaBox(int pageIndex, @NonNull Rect outMediaBox)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 */ setPageMediaBox(int pageIndex, @NonNull Rect mediaBox)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 */ getPageCropBox(int pageIndex, @NonNull Rect outCropBox)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 */ setPageCropBox(int pageIndex, @NonNull Rect cropBox)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 */ shouldScaleForPrinting()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 */ write(ParcelFileDescriptor output)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 */ close()271 public void close() { 272 throwIfClosed(); 273 doClose(); 274 } 275 276 @Override finalize()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 doClose()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 throwIfClosed()304 private void throwIfClosed() { 305 if (mInput == null) { 306 throw new IllegalStateException("Already closed"); 307 } 308 } 309 throwIfPageNotInDocument(int pageIndex)310 private void throwIfPageNotInDocument(int pageIndex) { 311 if (pageIndex < 0 || pageIndex >= mPageCount) { 312 throw new IllegalArgumentException("Invalid page index"); 313 } 314 } 315 throwIfNotNullAndNotAfine(Matrix matrix)316 private void throwIfNotNullAndNotAfine(Matrix matrix) { 317 if (matrix != null && !matrix.isAffine()) { 318 throw new IllegalStateException("Matrix must be afine"); 319 } 320 } 321 throwIfOutSizeNull(Point outSize)322 private void throwIfOutSizeNull(Point outSize) { 323 if (outSize == null) { 324 throw new NullPointerException("outSize cannot be null"); 325 } 326 } 327 throwIfOutMediaBoxNull(Rect outMediaBox)328 private void throwIfOutMediaBoxNull(Rect outMediaBox) { 329 if (outMediaBox == null) { 330 throw new NullPointerException("outMediaBox cannot be null"); 331 } 332 } 333 throwIfMediaBoxNull(Rect mediaBox)334 private void throwIfMediaBoxNull(Rect mediaBox) { 335 if (mediaBox == null) { 336 throw new NullPointerException("mediaBox cannot be null"); 337 } 338 } 339 throwIfOutCropBoxNull(Rect outCropBox)340 private void throwIfOutCropBoxNull(Rect outCropBox) { 341 if (outCropBox == null) { 342 throw new NullPointerException("outCropBox cannot be null"); 343 } 344 } 345 throwIfCropBoxNull(Rect cropBox)346 private void throwIfCropBoxNull(Rect cropBox) { 347 if (cropBox == null) { 348 throw new NullPointerException("cropBox cannot be null"); 349 } 350 } 351 nativeOpen(int fd, long size)352 private static native long nativeOpen(int fd, long size); nativeClose(long documentPtr)353 private static native void nativeClose(long documentPtr); nativeGetPageCount(long documentPtr)354 private static native int nativeGetPageCount(long documentPtr); nativeRemovePage(long documentPtr, int pageIndex)355 private static native int nativeRemovePage(long documentPtr, int pageIndex); nativeWrite(long documentPtr, int fd)356 private static native void nativeWrite(long documentPtr, int fd); nativeSetTransformAndClip(long documentPtr, int pageIndex, long transformPtr, int clipLeft, int clipTop, int clipRight, int clipBottom)357 private static native void nativeSetTransformAndClip(long documentPtr, int pageIndex, 358 long transformPtr, int clipLeft, int clipTop, int clipRight, int clipBottom); nativeGetPageSize(long documentPtr, int pageIndex, Point outSize)359 private static native void nativeGetPageSize(long documentPtr, int pageIndex, Point outSize); nativeGetPageMediaBox(long documentPtr, int pageIndex, Rect outMediaBox)360 private static native boolean nativeGetPageMediaBox(long documentPtr, int pageIndex, 361 Rect outMediaBox); nativeSetPageMediaBox(long documentPtr, int pageIndex, Rect mediaBox)362 private static native void nativeSetPageMediaBox(long documentPtr, int pageIndex, 363 Rect mediaBox); nativeGetPageCropBox(long documentPtr, int pageIndex, Rect outMediaBox)364 private static native boolean nativeGetPageCropBox(long documentPtr, int pageIndex, 365 Rect outMediaBox); nativeSetPageCropBox(long documentPtr, int pageIndex, Rect mediaBox)366 private static native void nativeSetPageCropBox(long documentPtr, int pageIndex, 367 Rect mediaBox); nativeScaleForPrinting(long documentPtr)368 private static native boolean nativeScaleForPrinting(long documentPtr); 369 } 370