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