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