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