PdfEditor.java revision 366262dc7854ba54f64905df8d275358be41edf5
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 17package android.graphics.pdf; 18 19import android.annotation.NonNull; 20import android.annotation.Nullable; 21import android.graphics.Matrix; 22import android.graphics.Point; 23import android.graphics.Rect; 24import android.os.ParcelFileDescriptor; 25import android.system.ErrnoException; 26import android.system.OsConstants; 27import dalvik.system.CloseGuard; 28import libcore.io.IoUtils; 29import libcore.io.Libcore; 30 31import java.io.IOException; 32 33/** 34 * Class for editing PDF files. 35 * 36 * @hide 37 */ 38public 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 265 public void close() { 266 throwIfClosed(); 267 doClose(); 268 } 269 270 @Override 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 282 private void doClose() { 283 synchronized (PdfRenderer.sPdfiumLock) { 284 nativeClose(mNativeDocument); 285 } 286 IoUtils.closeQuietly(mInput); 287 mInput = null; 288 mCloseGuard.close(); 289 } 290 291 private void throwIfClosed() { 292 if (mInput == null) { 293 throw new IllegalStateException("Already closed"); 294 } 295 } 296 297 private void throwIfPageNotInDocument(int pageIndex) { 298 if (pageIndex < 0 || pageIndex >= mPageCount) { 299 throw new IllegalArgumentException("Invalid page index"); 300 } 301 } 302 303 private void throwIfNotNullAndNotAfine(Matrix matrix) { 304 if (matrix != null && !matrix.isAffine()) { 305 throw new IllegalStateException("Matrix must be afine"); 306 } 307 } 308 309 private void throwIfOutSizeNull(Point outSize) { 310 if (outSize == null) { 311 throw new NullPointerException("outSize cannot be null"); 312 } 313 } 314 315 private void throwIfOutMediaBoxNull(Rect outMediaBox) { 316 if (outMediaBox == null) { 317 throw new NullPointerException("outMediaBox cannot be null"); 318 } 319 } 320 321 private void throwIfMediaBoxNull(Rect mediaBox) { 322 if (mediaBox == null) { 323 throw new NullPointerException("mediaBox cannot be null"); 324 } 325 } 326 327 private void throwIfOutCropBoxNull(Rect outCropBox) { 328 if (outCropBox == null) { 329 throw new NullPointerException("outCropBox cannot be null"); 330 } 331 } 332 333 private void throwIfCropBoxNull(Rect cropBox) { 334 if (cropBox == null) { 335 throw new NullPointerException("cropBox cannot be null"); 336 } 337 } 338 339 private static native long nativeOpen(int fd, long size); 340 private static native void nativeClose(long documentPtr); 341 private static native int nativeGetPageCount(long documentPtr); 342 private static native int nativeRemovePage(long documentPtr, int pageIndex); 343 private static native void nativeWrite(long documentPtr, int fd); 344 private static native void nativeSetTransformAndClip(long documentPtr, int pageIndex, 345 long transformPtr, int clipLeft, int clipTop, int clipRight, int clipBottom); 346 private static native void nativeGetPageSize(long documentPtr, int pageIndex, Point outSize); 347 private static native boolean nativeGetPageMediaBox(long documentPtr, int pageIndex, 348 Rect outMediaBox); 349 private static native void nativeSetPageMediaBox(long documentPtr, int pageIndex, 350 Rect mediaBox); 351 private static native boolean nativeGetPageCropBox(long documentPtr, int pageIndex, 352 Rect outMediaBox); 353 private static native void nativeSetPageCropBox(long documentPtr, int pageIndex, 354 Rect mediaBox); 355 private static native boolean nativeScaleForPrinting(long documentPtr); 356} 357