1 /* 2 * Copyright (C) 2023 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 static android.graphics.pdf.PdfLinearizationTypes.PDF_DOCUMENT_TYPE_LINEARIZED; 20 21 import android.annotation.FlaggedApi; 22 import android.annotation.IntDef; 23 import android.annotation.IntRange; 24 import android.annotation.Nullable; 25 import android.annotation.SuppressLint; 26 import android.graphics.Bitmap; 27 import android.graphics.Matrix; 28 import android.graphics.Rect; 29 import android.graphics.pdf.content.PdfPageGotoLinkContent; 30 import android.graphics.pdf.content.PdfPageImageContent; 31 import android.graphics.pdf.content.PdfPageLinkContent; 32 import android.graphics.pdf.content.PdfPageTextContent; 33 import android.graphics.pdf.flags.Flags; 34 import android.graphics.pdf.models.FormEditRecord; 35 import android.graphics.pdf.models.FormWidgetInfo; 36 import android.graphics.pdf.models.PageMatchBounds; 37 import android.graphics.pdf.models.selection.PageSelection; 38 import android.graphics.pdf.models.selection.SelectionBoundary; 39 import android.graphics.pdf.utils.Preconditions; 40 import android.os.ParcelFileDescriptor; 41 42 import androidx.annotation.NonNull; 43 import androidx.annotation.RestrictTo; 44 45 import java.io.IOException; 46 import java.lang.annotation.Retention; 47 import java.lang.annotation.RetentionPolicy; 48 import java.util.List; 49 50 /** 51 * <p> 52 * This class enables rendering a PDF document and selecting, searching, fast scrolling, 53 * annotations, etc from Android R till Android U. This class is thread safe and can be called by 54 * multiple threads however only one thread will be executed at a time as the access is 55 * synchronized by internal locking. 56 * <p> 57 * If you want to render a PDF, you will need to create a new instance of renderer for each 58 * document. To render each page, you open the page using the renderer instance created earlier, 59 * render it, and close the page. After you are done with rendering, you close the renderer. After 60 * the renderer is closed it should not be used anymore. Note that the pages are rendered one by 61 * one, i.e. you can have only a single page opened at any given time. 62 * <p> 63 * A typical use of the APIs to render a PDF looks like this: 64 * <pre> 65 * // create a new renderer 66 * try (PdfRendererPreV renderer = new PdfRendererPreV(getSeekableFileDescriptor(), loadParams)) { 67 * // let us just render all pages 68 * final int pageCount = renderer.getPageCount(); 69 * for (int i = 0; i < pageCount; i++) { 70 * Page page = renderer.openPage(i); 71 * RenderParams params = new RenderParams.Builder(Page.RENDER_MODE_FOR_DISPLAY).build(); 72 * 73 * // say we render for showing on the screen 74 * page.render(mBitmap, params, null, null); 75 * 76 * // do stuff with the bitmap 77 * 78 * // close the page 79 * page.close(); 80 * } 81 * } 82 * </pre> 83 * <h3>Print preview and print output</h3> 84 * <p> 85 * Please refer to {@link PdfRenderer} for fulfilling this usecase. 86 */ 87 @FlaggedApi(Flags.FLAG_ENABLE_PDF_VIEWER) 88 public final class PdfRendererPreV implements AutoCloseable { 89 /** Represents a non-linearized PDF document. */ 90 public static final int DOCUMENT_LINEARIZED_TYPE_NON_LINEARIZED = 0; 91 /** Represents a linearized PDF document. */ 92 public static final int DOCUMENT_LINEARIZED_TYPE_LINEARIZED = 1; 93 94 /** Represents a PDF without form fields */ 95 @FlaggedApi(Flags.FLAG_ENABLE_FORM_FILLING) 96 public static final int PDF_FORM_TYPE_NONE = 0; 97 98 /** Represents a PDF with form fields specified using the AcroForm spec */ 99 @FlaggedApi(Flags.FLAG_ENABLE_FORM_FILLING) 100 public static final int PDF_FORM_TYPE_ACRO_FORM = 1; 101 102 /** Represents a PDF with form fields specified using the entire XFA spec */ 103 @FlaggedApi(Flags.FLAG_ENABLE_FORM_FILLING) 104 public static final int PDF_FORM_TYPE_XFA_FULL = 2; 105 106 /** Represents a PDF with form fields specified using the XFAF subset of the XFA spec */ 107 @FlaggedApi(Flags.FLAG_ENABLE_FORM_FILLING) 108 public static final int PDF_FORM_TYPE_XFA_FOREGROUND = 3; 109 private final int mPageCount; 110 private PdfProcessor mPdfProcessor; 111 112 /** 113 * Creates a new instance of PdfRendererPreV class. 114 * <p> 115 * <strong>Note:</strong> The provided file descriptor must be <strong>seekable</strong>, 116 * i.e. its data being randomly accessed, e.g. pointing to a file. 117 * <p> 118 * <strong>Note:</strong> This class takes ownership of the passed in file descriptor 119 * and is responsible for closing it when the renderer is closed. 120 * <p> 121 * If the file is from an untrusted source it is recommended to run the renderer in a separate, 122 * isolated process with minimal permissions to limit the impact of security exploits. 123 * 124 * @param fileDescriptor Seekable file descriptor to read from. 125 * @throws java.io.IOException If an error occurs while reading the file. 126 * @throws java.lang.SecurityException If the file requires a password or 127 * the security scheme is not supported by the renderer. 128 * @throws IllegalArgumentException If the {@link ParcelFileDescriptor} is not seekable. 129 * @throws NullPointerException If the file descriptor is null. 130 */ PdfRendererPreV(@onNull ParcelFileDescriptor fileDescriptor)131 public PdfRendererPreV(@NonNull ParcelFileDescriptor fileDescriptor) throws IOException { 132 Preconditions.checkNotNull(fileDescriptor, "Input FD cannot be null"); 133 134 try { 135 mPdfProcessor = new PdfProcessor(); 136 mPdfProcessor.create(fileDescriptor, null); 137 mPageCount = mPdfProcessor.getNumPages(); 138 } catch (Throwable t) { 139 doClose(); 140 throw t; 141 } 142 } 143 144 /** 145 * Creates a new instance of PdfRendererPreV class. 146 * <p> 147 * <strong>Note:</strong> The provided file descriptor must be <strong>seekable</strong>, 148 * i.e. its data being randomly accessed, e.g. pointing to a file. If the password passed in 149 * {@link android.graphics.pdf.LoadParams} is incorrect, the 150 * {@link android.graphics.pdf.PdfRendererPreV} will throw a {@link SecurityException}. 151 * <p> 152 * <strong>Note:</strong> This class takes ownership of the passed in file descriptor 153 * and is responsible for closing it when the renderer is closed. 154 * <p> 155 * If the file is from an untrusted source it is recommended to run the renderer in a separate, 156 * isolated process with minimal permissions to limit the impact of security exploits. 157 * 158 * @param fileDescriptor Seekable file descriptor to read from. 159 * @param params Instance of {@link LoadParams} specifying params for loading PDF 160 * document. 161 * @throws java.io.IOException If an error occurs while reading the file. 162 * @throws java.lang.SecurityException If the file requires a password or 163 * the security scheme is not supported by the renderer. 164 * @throws IllegalArgumentException If the {@link ParcelFileDescriptor} is not seekable. 165 * @throws NullPointerException If the file descriptor or load params is null. 166 */ PdfRendererPreV(@onNull ParcelFileDescriptor fileDescriptor, @NonNull LoadParams params)167 public PdfRendererPreV(@NonNull ParcelFileDescriptor fileDescriptor, @NonNull LoadParams params) 168 throws IOException { 169 Preconditions.checkNotNull(fileDescriptor, "Input FD cannot be null"); 170 Preconditions.checkNotNull(params, "LoadParams cannot be null"); 171 172 try { 173 mPdfProcessor = new PdfProcessor(); 174 mPdfProcessor.create(fileDescriptor, params); 175 mPageCount = mPdfProcessor.getNumPages(); 176 } catch (Throwable t) { 177 doClose(); 178 throw t; 179 } 180 } 181 182 /** 183 * Gets the number of pages in the document. 184 * 185 * @return The page count. 186 * @throws IllegalStateException If {@link #close()} is called before invoking this. 187 */ 188 @IntRange(from = 0) getPageCount()189 public int getPageCount() { 190 throwIfDocumentClosed(); 191 return mPageCount; 192 } 193 194 /** 195 * Gets the type of the PDF document. 196 * 197 * @return The PDF document type. 198 * @throws IllegalStateException If {@link #close()} is called before invoking this. 199 */ 200 @PdfDocumentLinearizationType getDocumentLinearizationType()201 public int getDocumentLinearizationType() { 202 throwIfDocumentClosed(); 203 int documentType = mPdfProcessor.getDocumentLinearizationType(); 204 if (documentType == PDF_DOCUMENT_TYPE_LINEARIZED) { 205 return DOCUMENT_LINEARIZED_TYPE_LINEARIZED; 206 } else { 207 return DOCUMENT_LINEARIZED_TYPE_NON_LINEARIZED; 208 } 209 } 210 211 /** 212 * Opens a {@link Page} for rendering. 213 * 214 * @param index The page index to open, starting from index 0. 215 * @return A page that can be rendered. 216 * @throws IllegalStateException If {@link #close()} is called before invoking this. 217 * @throws IllegalArgumentException If the page number is less than 0 or greater than or equal 218 * to the total page count. 219 */ 220 @NonNull openPage(@ntRangefrom = 0) int index)221 public Page openPage(@IntRange(from = 0) int index) { 222 throwIfDocumentClosed(); 223 throwIfPageNotInDocument(index); 224 return new Page(index); 225 } 226 227 /** 228 * Returns the form type of the loaded PDF 229 * 230 * @throws IllegalStateException if the renderer is closed 231 * @throws IllegalArgumentException if an unexpected PDF form type is returned 232 */ 233 @PdfFormType 234 @FlaggedApi(Flags.FLAG_ENABLE_FORM_FILLING) getPdfFormType()235 public int getPdfFormType() { 236 throwIfDocumentClosed(); 237 int pdfFormType = mPdfProcessor.getPdfFormType(); 238 if (pdfFormType == PDF_FORM_TYPE_ACRO_FORM) { 239 return PDF_FORM_TYPE_ACRO_FORM; 240 } else if (pdfFormType == PDF_FORM_TYPE_XFA_FULL) { 241 return PDF_FORM_TYPE_XFA_FULL; 242 } else if (pdfFormType == PDF_FORM_TYPE_XFA_FOREGROUND) { 243 return PDF_FORM_TYPE_XFA_FOREGROUND; 244 } else { 245 return PDF_FORM_TYPE_NONE; 246 } 247 } 248 249 /** 250 * <p> 251 * Saves the current state of the loaded PDF document to the given writable 252 * {@link ParcelFileDescriptor}. If the document is password-protected then setting 253 * {@code removePasswordProtection} removes the protection before saving. The PDF document 254 * should already be decrypted with the correct password before writing. Useful for printing or 255 * sharing. 256 * <strong>Note:</strong> This method closes the provided file descriptor. 257 * 258 * @param destination The writable {@link ParcelFileDescriptor} 259 * @param removePasswordProtection If true, removes password protection from the PDF before 260 * saving. 261 * @throws IOException If there's a write error, or if 'removePasswordSecurity' is 262 * {@code true} but the document remains encrypted. 263 * @throws IllegalStateException If {@link #close()} is called before invoking this. 264 */ write(@onNull ParcelFileDescriptor destination, boolean removePasswordProtection)265 public void write(@NonNull ParcelFileDescriptor destination, boolean removePasswordProtection) 266 throws IOException { 267 throwIfDocumentClosed(); 268 mPdfProcessor.write(destination, removePasswordProtection); 269 } 270 271 /** 272 * Closes this renderer and destroys any cached instance of the document. You should not use 273 * this instance after this method is called. 274 * 275 * @throws IllegalStateException If {@link #close()} is called before invoking this. 276 */ 277 @Override close()278 public void close() { 279 throwIfDocumentClosed(); 280 doClose(); 281 } 282 283 // SuppressLint: Finalize needs to be overridden to make sure all resources are closed 284 // gracefully 285 @Override 286 @SuppressLint("GenericException") finalize()287 protected void finalize() throws Throwable { 288 try { 289 doClose(); 290 } finally { 291 super.finalize(); 292 } 293 } 294 doClose()295 private void doClose() { 296 if (mPdfProcessor != null) { 297 mPdfProcessor.ensurePdfDestroyed(); 298 mPdfProcessor = null; 299 } 300 } 301 throwIfDocumentClosed()302 private void throwIfDocumentClosed() { 303 if (mPdfProcessor == null) { 304 throw new IllegalStateException("Document already closed!"); 305 } 306 } 307 throwIfPageNotInDocument(int pageIndex)308 private void throwIfPageNotInDocument(int pageIndex) { 309 if (pageIndex < 0 || pageIndex >= mPageCount) { 310 throw new IllegalArgumentException("Invalid page index"); 311 } 312 } 313 314 /** @hide */ 315 @IntDef({PDF_FORM_TYPE_NONE, PDF_FORM_TYPE_ACRO_FORM, PDF_FORM_TYPE_XFA_FULL, 316 PDF_FORM_TYPE_XFA_FOREGROUND}) 317 @Retention(RetentionPolicy.SOURCE) 318 public @interface PdfFormType { 319 } 320 321 /** @hide */ 322 @RestrictTo(RestrictTo.Scope.LIBRARY) 323 @Retention(RetentionPolicy.SOURCE) 324 @IntDef(prefix = {"PDF_DOCUMENT_TYPE_"}, value = {DOCUMENT_LINEARIZED_TYPE_NON_LINEARIZED, 325 DOCUMENT_LINEARIZED_TYPE_LINEARIZED}) 326 public @interface PdfDocumentLinearizationType { 327 } 328 329 /** 330 * This class represents a PDF document page for rendering. 331 */ 332 public final class Page implements AutoCloseable { 333 private final int mWidth; 334 private final int mHeight; 335 private int mIndex; 336 Page(int index)337 private Page(int index) { 338 this.mIndex = index; 339 mWidth = mPdfProcessor.getPageWidth(index); 340 mHeight = mPdfProcessor.getPageHeight(index); 341 } 342 343 /** 344 * Gets the page index. 345 * 346 * @return The index. 347 */ 348 @IntRange(from = 0) getIndex()349 public int getIndex() { 350 return mIndex; 351 } 352 353 /** 354 * Renders a page to a bitmap. In case of default zoom, the {@link Bitmap} dimensions will 355 * be equal to the page dimensions. In this case, {@link Rect} parameter can be null. 356 * 357 * <p>In case of zoom, the {@link Rect} parameter needs to be specified which represents 358 * the offset from top and left for tile generation purposes. In this case, the 359 * {@link Bitmap} dimensions should be equal to the tile dimensions. 360 * <p> 361 * <strong>Note:</strong> The method will take care of closing the bitmap. Should be 362 * invoked 363 * on the {@link android.annotation.WorkerThread} as it is long-running task. 364 * 365 * @param destination Destination bitmap to write to. 366 * @param destClip If null, default zoom is applied. In case the value is non-null, the 367 * value specifies the top top-left corner of the tile. 368 * @param transform Applied to scale the bitmap up/down from default 1/72 points. 369 * @param params Render params for the changing display mode and/or annotations. 370 * @throws IllegalStateException If the document/page is closed before invocation. 371 */ render(@onNull Bitmap destination, @Nullable Rect destClip, @Nullable Matrix transform, @NonNull RenderParams params)372 public void render(@NonNull Bitmap destination, @Nullable Rect destClip, 373 @Nullable Matrix transform, @NonNull RenderParams params) { 374 throwIfDocumentOrPageClosed(); 375 mPdfProcessor.renderPage(mIndex, destination, destClip, transform, 376 params, /* renderFormFields= */ true); 377 } 378 379 /** 380 * Return list of {@link PdfPageTextContent} found on the page, ordered left to right 381 * and top to bottom. It contains all the content associated with text found on the page. 382 * The list will be empty if there are no results found. Currently, localisation does 383 * not have any impact on the order in which {@link PdfPageTextContent} is returned. 384 * 385 * @return list of text content found on the page. 386 * @throws IllegalStateException If the document/page is closed before invocation. 387 */ 388 @NonNull getTextContents()389 public List<PdfPageTextContent> getTextContents() { 390 throwIfDocumentOrPageClosed(); 391 return mPdfProcessor.getPageTextContents(mIndex); 392 } 393 394 /** 395 * Return list of {@link PdfPageImageContent} found on the page, ordered left to right 396 * and top to bottom. It contains all the content associated with images found on the 397 * page including alt text. The list will be empty if there are no results found. 398 * Currently, localisation does not have any impact on the order in which 399 * {@link PdfPageImageContent} is returned. 400 * 401 * @return list of image content found on the page. 402 * @throws IllegalStateException If the document/page is closed before invocation. 403 */ 404 @NonNull getImageContents()405 public List<PdfPageImageContent> getImageContents() { 406 throwIfDocumentOrPageClosed(); 407 return mPdfProcessor.getPageImageContents(mIndex); 408 } 409 410 /** 411 * Returns the width of the given {@link Page} object in points (1/72"). It is not 412 * guaranteed that all pages will have the same width and the viewport should be resized to 413 * the given page width. 414 * 415 * @return width of the given page 416 * @throws IllegalStateException If the document/page is closed before invocation. 417 */ 418 @IntRange(from = 0) getWidth()419 public int getWidth() { 420 throwIfDocumentOrPageClosed(); 421 return mWidth; 422 } 423 424 /** 425 * Returns the height of the given {@link Page} object in points (1/72"). It is not 426 * guaranteed that all pages will have the same height and the viewport should be resized to 427 * the given page height. 428 * 429 * @return height of the given page 430 * @throws IllegalStateException If the document/page is closed before invocation. 431 */ 432 @IntRange(from = 0) getHeight()433 public int getHeight() { 434 throwIfDocumentOrPageClosed(); 435 return mHeight; 436 } 437 438 /** 439 * Search for the given string on the page and returns the bounds of all the matches. The 440 * list will be empty if there are no matches on the given page. If this function was 441 * invoked previously for any page, it will wait for that operation to 442 * complete before this operation is started. 443 * <p> 444 * <strong>Note:</strong> Should be invoked on the {@link android.annotation.WorkerThread} 445 * as it is long-running task. 446 * 447 * @param query plain search string for querying the document 448 * @return List of {@link PageMatchBounds} representing the bounds of each match on the 449 * page. 450 * @throws IllegalStateException If the document/page is closed before invocation. 451 */ 452 @NonNull searchText(@onNull String query)453 public List<PageMatchBounds> searchText(@NonNull String query) { 454 throwIfDocumentOrPageClosed(); 455 return mPdfProcessor.searchPageText(mIndex, query); 456 } 457 458 /** 459 * Return a {@link PageSelection} which represents the selected content that spans between 460 * the two boundaries. The boundaries can be either exactly defined with text indexes, or 461 * approximately defined with points on the page. The resulting selection will also be 462 * exactly defined with both indexes and points. If the start and stop boundary are both at 463 * the same point, selects the word at that point. In case the selection from the given 464 * boundaries result in an empty space, then the method returns {@code null}. The start and 465 * stop {@link SelectionBoundary} in {@link PageSelection} resolves to the "nearest" index 466 * when returned. 467 * <p> 468 * <strong>Note:</strong> Should be invoked on a {@link android.annotation.WorkerThread} 469 * as it is long-running task. 470 * 471 * @param start boundary where the selection starts (inclusive) 472 * @param stop boundary where the selection stops (exclusive) 473 * @return collection of the selected content for text, images, etc. 474 * @throws IllegalStateException If the document/page is closed before invocation. 475 */ 476 @Nullable selectContent(@onNull SelectionBoundary start, @NonNull SelectionBoundary stop)477 public PageSelection selectContent(@NonNull SelectionBoundary start, 478 @NonNull SelectionBoundary stop) { 479 throwIfDocumentOrPageClosed(); 480 return mPdfProcessor.selectPageText(mIndex, start, stop); 481 } 482 483 484 /** 485 * Get the bounds and URLs of all the links on the given page. 486 * 487 * @return list of all links on the page. 488 * @throws IllegalStateException If the document/page is closed before invocation. 489 */ 490 @NonNull getLinkContents()491 public List<PdfPageLinkContent> getLinkContents() { 492 throwIfDocumentOrPageClosed(); 493 return mPdfProcessor.getPageLinkContents(mIndex); 494 } 495 496 /** 497 * Gets bookmarks and goto links present on the page of a pdf document. Goto Links 498 * are the internal navigation links which directs the user to different location 499 * within the same document. 500 * 501 * @return list of all goto links {@link PdfPageGotoLinkContent} on a page, ordered 502 * left to right and top to bottom. 503 * @throws IllegalStateException If the document/page is closed before invocation. 504 */ 505 @NonNull getGotoLinks()506 public List<PdfPageGotoLinkContent> getGotoLinks() { 507 throwIfDocumentOrPageClosed(); 508 return mPdfProcessor.getPageGotoLinks(mIndex); 509 } 510 511 512 /** 513 * Returns information about all form widgets on the page, or an empty list if there are no 514 * form widgets on the page. 515 * 516 * @throws IllegalStateException If the document is already closed. 517 * @throws IllegalStateException If the page is already closed. 518 */ 519 @NonNull 520 @FlaggedApi(Flags.FLAG_ENABLE_FORM_FILLING) getFormWidgetInfos()521 public List<FormWidgetInfo> getFormWidgetInfos() { 522 return getFormWidgetInfos(new int[0]); 523 } 524 525 /** 526 * Returns information about all form widgets of the specified types on the page, or an 527 * empty list if there are no form widgets of the specified types on the page. 528 * 529 * @param types the types of form widgets to return, or an empty array to return all widgets 530 * @throws IllegalStateException If the document is already closed. 531 * @throws IllegalStateException If the page is already closed. 532 */ 533 @NonNull 534 @FlaggedApi(Flags.FLAG_ENABLE_FORM_FILLING) getFormWidgetInfos( @onNull @ormWidgetInfo.WidgetType int[] types)535 public List<FormWidgetInfo> getFormWidgetInfos( 536 @NonNull @FormWidgetInfo.WidgetType int[] types) { 537 throwIfDocumentClosed(); 538 throwIfPageClosed(); 539 return mPdfProcessor.getFormWidgetInfos(mIndex, types); 540 } 541 542 /** 543 * Returns information about the widget with {@code annotationIndex}. 544 * 545 * @param annotationIndex the index of the widget within the page's "Annot" array in the 546 * PDF document, available on results of previous calls to 547 * {@link #getFormWidgetInfos(int[])} or 548 * {@link #getFormWidgetInfoAtPosition(int, int)} via 549 * {@link FormWidgetInfo#getWidgetIndex()}. 550 * @throws IllegalArgumentException if there is no form widget at the provided index. 551 * @throws IllegalStateException If the document is already closed. 552 * @throws IllegalStateException If the page is already closed. 553 */ 554 @NonNull 555 @FlaggedApi(Flags.FLAG_ENABLE_FORM_FILLING) getFormWidgetInfoAtIndex(@ntRangefrom = 0) int annotationIndex)556 public FormWidgetInfo getFormWidgetInfoAtIndex(@IntRange(from = 0) int annotationIndex) { 557 throwIfDocumentClosed(); 558 throwIfPageClosed(); 559 return mPdfProcessor.getFormWidgetInfoAtIndex(mIndex, annotationIndex); 560 } 561 562 /** 563 * Returns information about the widget at the given point. 564 * 565 * @param x the x position of the widget on the page, in points 566 * @param y the y position of the widget on the page, in points 567 * @throws IllegalArgumentException if there is no form widget at the provided position. 568 * @throws IllegalStateException If the document is already closed. 569 * @throws IllegalStateException If the page is already closed. 570 */ 571 @NonNull 572 @FlaggedApi(Flags.FLAG_ENABLE_FORM_FILLING) getFormWidgetInfoAtPosition(int x, int y)573 public FormWidgetInfo getFormWidgetInfoAtPosition(int x, int y) { 574 throwIfDocumentClosed(); 575 throwIfPageClosed(); 576 return mPdfProcessor.getFormWidgetInfoAtPosition(mIndex, x, y); 577 } 578 579 /** 580 * Applies a {@link FormEditRecord} to the PDF. 581 * 582 * <p>Apps must call {@link #render(Bitmap, Rect, Matrix, RenderParams)} to render new 583 * bitmaps for the corresponding areas of the page. 584 * 585 * <p>For click type {@link FormEditRecord}s, performs a click on {@link 586 * FormEditRecord#getClickPoint()} 587 * 588 * <p>For set text type {@link FormEditRecord}s, sets the text value of the form widget. 589 * 590 * <p>For set indices type {@link FormEditRecord}s, sets the {@link 591 * FormEditRecord#getSelectedIndices()} as selected and all others as unselected for the 592 * form widget indicated by the record. 593 * 594 * @param editRecord the {@link FormEditRecord} to be applied 595 * @return Rectangular areas of the page bitmap that have been invalidated by this action. 596 * @throws IllegalArgumentException if the provided {@link FormEditRecord} cannot be 597 * applied to the widget indicated by the index, or if the 598 * index does not correspond to a widget on the page. 599 * @throws IllegalStateException If the document is already closed. 600 * @throws IllegalStateException If the page is already closed. 601 */ 602 @android.annotation.NonNull 603 @FlaggedApi(Flags.FLAG_ENABLE_FORM_FILLING) applyEdit(@onNull FormEditRecord editRecord)604 public List<Rect> applyEdit(@NonNull FormEditRecord editRecord) { 605 throwIfDocumentClosed(); 606 throwIfPageClosed(); 607 return mPdfProcessor.applyEdit(mIndex, editRecord); 608 } 609 610 /** 611 * Closes this page. 612 * 613 * @see android.graphics.pdf.PdfRenderer#openPage(int) 614 */ 615 @Override close()616 public void close() { 617 throwIfDocumentOrPageClosed(); 618 doClose(); 619 } 620 621 // SuppressLint: Finalize needs to be overridden to make sure all resources are closed 622 // gracefully. 623 @Override 624 @SuppressLint("GenericException") finalize()625 protected void finalize() throws Throwable { 626 try { 627 doClose(); 628 } finally { 629 super.finalize(); 630 } 631 } 632 doClose()633 private void doClose() { 634 if (mPdfProcessor != null) { 635 mPdfProcessor.releasePage(mIndex); 636 mIndex = -1; 637 } 638 } 639 throwIfPageClosed()640 private void throwIfPageClosed() { 641 if (mIndex == -1) { 642 throw new IllegalStateException("Page already closed!"); 643 } 644 } 645 throwIfDocumentOrPageClosed()646 private void throwIfDocumentOrPageClosed() { 647 throwIfDocumentClosed(); 648 throwIfPageClosed(); 649 } 650 } 651 } 652