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