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