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