LCOV - code coverage report
Current view: top level - src/document - pdf_document.dart Coverage Total Hit
Test: lcov.info Lines: 100.0 % 27 27
Test Date: 2026-08-24 04:28:45 Functions: - 0 0

            Line data    Source code
       1              : // Copyright 2026 The Authors.
       2              : //
       3              : // Licensed under the Apache License, Version 2.0 (the "License");
       4              : // you may not use this file except in compliance with the License.
       5              : // You may obtain a copy of the License at
       6              : //
       7              : //     https://www.apache.org/licenses/LICENSE-2.0
       8              : //
       9              : // Unless required by applicable law or agreed to in writing, software
      10              : // distributed under the License is distributed on an "AS IS" BASIS,
      11              : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12              : // See the License for the specific language governing permissions and
      13              : // limitations under the License.
      14              : 
      15              : /// Public PdfDocument interface with conditional platform import.
      16              : ///
      17              : /// The platform split is entirely hidden behind this file. Callers import only
      18              : /// pdf_document.dart and receive the correct backend automatically:
      19              : ///
      20              : ///   dart.library.ffi     → _document_native.dart (iOS, Android, macOS, Windows, Linux)
      21              : ///   dart.library.js_interop → _document_web.dart (web / WASM)
      22              : ///   (fallback)           → _document_stub.dart   (throws UnsupportedError)
      23              : library;
      24              : 
      25              : import 'dart:typed_data';
      26              : 
      27              : import '_document_stub.dart'
      28              :     if (dart.library.ffi) '_document_native.dart'
      29              :     if (dart.library.js_interop) '_document_web.dart';
      30              : 
      31              : import 'pdf_types.dart';
      32              : import '../rendering/pdf_page_size.dart';
      33              : 
      34              : export 'pdf_types.dart';
      35              : export '../rendering/pdf_page_size.dart';
      36              : 
      37              : /// A loaded PDF document.
      38              : ///
      39              : /// [PdfDocument] is the top-level Dart abstraction for a PDF file. It mirrors
      40              : /// the PDFium model: `FPDF_LoadMemDocument64` returns a single document handle
      41              : /// used for all subsequent operations. [PdfDocument] is the Dart owner of that
      42              : /// handle and exposes document-level capabilities as async methods.
      43              : ///
      44              : /// ## Loading
      45              : ///
      46              : /// Use [fromBytes] to load a document from raw PDF bytes:
      47              : ///
      48              : /// ```dart
      49              : /// final bytes = await File('document.pdf').readAsBytes();
      50              : /// final doc = await PdfDocument.fromBytes(bytes);
      51              : /// ```
      52              : ///
      53              : /// ## Error handling
      54              : ///
      55              : /// [fromBytes] throws [PdfExtractionException] when the document cannot be
      56              : /// loaded. Inspect [PdfExtractionException.error] to distinguish between
      57              : /// [PdfError.passwordRequired] and [PdfError.invalidDocument] so callers
      58              : /// can give users an actionable message.
      59              : ///
      60              : /// ## Resource management
      61              : ///
      62              : /// Always call [close] when the document is no longer needed to release the
      63              : /// native PDFium handle:
      64              : ///
      65              : /// ```dart
      66              : /// final doc = await PdfDocument.fromBytes(bytes);
      67              : /// try {
      68              : ///   final meta = await doc.getMetadata();
      69              : ///   // use meta…
      70              : /// } finally {
      71              : ///   await doc.close();
      72              : /// }
      73              : /// ```
      74              : ///
      75              : /// A [Finalizer] is registered internally as a safety net in case [close] is
      76              : /// forgotten, but explicit disposal is strongly preferred.
      77              : ///
      78              : /// ## Platform support
      79              : ///
      80              : /// The public API is identical on all platforms. The backend differs:
      81              : ///
      82              : /// | Platform              | Backend                   |
      83              : /// | --------------------- | ------------------------- |
      84              : /// | iOS, Android, macOS,  | dart:ffi + PdfiumIsolate  |
      85              : /// | Linux, Windows        |                           |
      86              : /// | Web                   | PDFium WASM               |
      87              : ///
      88              : /// On native platforms all PDFium calls run on a dedicated [Isolate] so the
      89              : /// caller's isolate (typically the UI isolate) is never blocked. On web,
      90              : /// `dart:isolate` is not supported on any compile target, so PDFium calls
      91              : /// run inside a dedicated Web Worker instead, communicating with the main
      92              : /// thread via a hand-rolled `postMessage` protocol.
      93              : ///
      94              : /// ## Future capabilities
      95              : ///
      96              : /// [PdfDocument] is designed to be the foundation for future capabilities:
      97              : /// text extraction (`document.openTextExtractor()`), annotation access, and
      98              : /// page rendering. This plan establishes the class and its metadata surface;
      99              : /// future plans add capabilities without breaking the existing API.
     100              : class PdfDocument {
     101           10 :   PdfDocument._(this._impl);
     102              : 
     103              :   final PdfDocumentImpl _impl;
     104              : 
     105              :   /// Loads a PDF document from raw [bytes].
     106              :   ///
     107              :   /// Returns a [PdfDocument] on success.
     108              :   ///
     109              :   /// Throws [PdfExtractionException] with:
     110              :   /// - [PdfError.passwordRequired] if the document is password-protected.
     111              :   /// - [PdfError.invalidDocument] if the bytes are corrupt or not a valid PDF.
     112              :   ///
     113              :   /// The optional [dylibPath] overrides the default PDFium dynamic library
     114              :   /// location. It is intended for testing only.
     115           10 :   static Future<PdfDocument> fromBytes(
     116              :     Uint8List bytes, {
     117              :     String? dylibPath,
     118              :   }) async {
     119           10 :     final impl = await PdfDocumentImpl.fromBytes(bytes, dylibPath: dylibPath);
     120           10 :     return PdfDocument._(impl);
     121              :   }
     122              : 
     123              :   /// Returns the metadata extracted from the PDF Info dictionary.
     124              :   ///
     125              :   /// All fields on [PdfMetadata] are nullable. A `null` field means the
     126              :   /// corresponding entry was not present in the document's Info dictionary —
     127              :   /// this is distinct from a field that is present but empty.
     128              :   ///
     129              :   /// Throws [StateError] if [close] has already been called.
     130            9 :   Future<PdfMetadata> getMetadata() => _impl.getMetadata();
     131              : 
     132              :   /// Returns document-level properties: PDF file version and file identifiers.
     133              :   ///
     134              :   /// File identifiers ([PdfDocumentInfo.permanentId] and
     135              :   /// [PdfDocumentInfo.changingId]) are raw bytes, typically 16-byte MD5
     136              :   /// hashes. Hex-encode them if a string representation is needed:
     137              :   ///
     138              :   /// ```dart
     139              :   /// final info = await doc.getDocumentInfo();
     140              :   /// final hex = info.permanentId
     141              :   ///     ?.map((b) => b.toRadixString(16).padLeft(2, '0'))
     142              :   ///     .join();
     143              :   /// ```
     144              :   ///
     145              :   /// Throws [StateError] if [close] has already been called.
     146            6 :   Future<PdfDocumentInfo> getDocumentInfo() => _impl.getDocumentInfo();
     147              : 
     148              :   /// The total number of pages in the document.
     149              :   ///
     150              :   /// Throws [StateError] if [close] has already been called.
     151           12 :   Future<int> get pageCount => _impl.pageCount;
     152              : 
     153              :   /// Extracts plain text from one or all pages of the document.
     154              :   ///
     155              :   /// When [pageIndex] is `null`, the stream yields all pages in index order.
     156              :   /// When [pageIndex] is specified, the stream yields exactly one [PdfPageText].
     157              :   ///
     158              :   /// Throws [RangeError] if [pageIndex] is out of range.
     159              :   /// Throws [StateError] if the document has been closed before or during
     160              :   /// extraction.
     161              :   ///
     162              :   /// Cancelling the stream subscription immediately stops further processing.
     163              :   /// Page-level PDFium handles are released after each round-trip, so there
     164              :   /// are no handle leaks on cancellation.
     165              :   ///
     166              :   /// [close] terminates any active stream: the stream stops emitting events
     167              :   /// and the subscription is silently cancelled.
     168              :   ///
     169              :   /// Example — extract all pages and print each one:
     170              :   ///
     171              :   /// ```dart
     172              :   /// await for (final page in doc.extractPlainText()) {
     173              :   ///   if (page.hasTextLayer) {
     174              :   ///     print('Page ${page.pageIndex}: ${page.text}');
     175              :   ///   } else {
     176              :   ///     print('Page ${page.pageIndex}: no text layer (scanned page)');
     177              :   ///   }
     178              :   /// }
     179              :   /// ```
     180            4 :   Stream<PdfPageText> extractPlainText({
     181              :     int? pageIndex,
     182              :     PdfTextExtractorConfig config = const PdfTextExtractorConfig(),
     183            8 :   }) => _impl.extractPlainText(pageIndex: pageIndex, config: config);
     184              : 
     185              :   /// Extracts all annotations from one or all pages of the document.
     186              :   ///
     187              :   /// When [pageIndex] is `null`, the stream yields one [PdfPageAnnotations] per
     188              :   /// page in index order. Pages with no annotations emit an entry with an empty
     189              :   /// [PdfPageAnnotations.annotations] list, so callers can track page coverage
     190              :   /// without gaps.
     191              :   ///
     192              :   /// When [pageIndex] is specified, the stream yields exactly one
     193              :   /// [PdfPageAnnotations] for that page.
     194              :   ///
     195              :   /// Throws [RangeError] if [pageIndex] is out of range.
     196              :   /// Throws [StateError] if the document has been closed before or during
     197              :   /// extraction.
     198              :   ///
     199              :   /// [close] terminates any active stream: the stream stops emitting events and
     200              :   /// all page-level annotation handles are released. Callers do not need to
     201              :   /// cancel streams manually before calling [close].
     202              :   ///
     203              :   /// **Note:** This method does not accept a page range — only a single optional
     204              :   /// page index. Range-based extraction is a known limitation to revisit in a
     205              :   /// future plan.
     206              :   ///
     207              :   /// **Platform support:** Native (dart:ffi) only. Stubs on unsupported
     208              :   /// platforms throw [UnsupportedError] immediately.
     209              :   ///
     210              :   /// Example — collect all highlights from every page:
     211              :   ///
     212              :   /// ```dart
     213              :   /// await for (final page in doc.extractAnnotations()) {
     214              :   ///   for (final annot in page.annotations) {
     215              :   ///     if (annot case PdfMarkupAnnotation(:final subtype, :final quadPoints)
     216              :   ///         when subtype == PdfAnnotationType.highlight) {
     217              :   ///       print('Highlight on page ${page.pageIndex}: $quadPoints');
     218              :   ///     }
     219              :   ///   }
     220              :   /// }
     221              :   /// ```
     222            2 :   Stream<PdfPageAnnotations> extractAnnotations({int? pageIndex}) =>
     223            4 :       _impl.extractAnnotations(pageIndex: pageIndex);
     224              : 
     225              :   /// Returns `true` when fewer than [PdfTextExtractorConfig.scannedPageRatio] of pages lack a
     226              :   /// text layer (i.e. the document is suitable for plain-text extraction).
     227              :   ///
     228              :   /// Internally runs [extractPlainText] to completion and counts pages where
     229              :   /// [PdfPageText.hasTextLayer] is `false`. Returns `false` when the proportion
     230              :   /// of such pages meets or exceeds [PdfTextExtractorConfig.scannedPageRatio].
     231              :   ///
     232              :   /// Use per-page [PdfPageText.hasTextLayer] for finer-grained control.
     233              :   ///
     234              :   /// Throws [StateError] if the document has been closed.
     235            2 :   Future<bool> isPlainTextExtractable({
     236              :     PdfTextExtractorConfig config = const PdfTextExtractorConfig(),
     237            4 :   }) => _impl.isPlainTextExtractable(config: config);
     238              : 
     239              :   /// Returns the intrinsic size of a page in PDF user units (points).
     240              :   ///
     241              :   /// One PDF user unit equals 1/72 inch. This is a storage-level measurement
     242              :   /// independent of rendering resolution. Use [PdfPageSize.sizeForDpi] to
     243              :   /// convert to pixel dimensions for a [renderPageToBytes] call.
     244              :   ///
     245              :   /// Throws [RangeError] if [pageIndex] is out of range.
     246              :   /// Throws [StateError] if [close] has already been called.
     247              :   ///
     248              :   /// ## Example
     249              :   ///
     250              :   /// ```dart
     251              :   /// final size = await doc.getPageSize(0);
     252              :   /// final px = size.sizeForDpi(150);
     253              :   /// final result = await doc.renderPageToBytes(0, px.width.round(), px.height.round());
     254              :   /// ```
     255            2 :   Future<PdfPageSize> getPageSize(int pageIndex) =>
     256            4 :       _impl.getPageSize(pageIndex);
     257              : 
     258              :   /// Renders a page to a raw BGRA pixel buffer.
     259              :   ///
     260              :   /// The page at [pageIndex] is rendered at [pixelWidth] × [pixelHeight]
     261              :   /// pixels. The returned record exposes `pixels` (BGRA bytes),
     262              :   /// [pixelWidth], and [pixelHeight].
     263              :   ///
     264              :   /// For Flutter apps, decode the returned BGRA bytes into a `dart:ui Image`
     265              :   /// via `decodeImageFromPixels`.
     266              :   ///
     267              :   /// [renderAnnotations] maps to the PDFium `FPDF_ANNOT` flag (default true).
     268              :   /// [lcdText] maps to `FPDF_LCD_TEXT` (default false).
     269              :   /// [backgroundColor] is an ARGB packed integer; default `0xFFFFFFFF`
     270              :   /// (opaque white).
     271              :   ///
     272              :   /// Throws [RangeError] if [pageIndex] is out of range.
     273              :   /// Throws [StateError] if [close] has been called before or during render.
     274              :   /// Throws [PdfiumException] if a PDFium native call fails.
     275            1 :   Future<({Uint8List pixels, int pixelWidth, int pixelHeight})>
     276              :   renderPageToBytes(
     277              :     int pageIndex,
     278              :     int pixelWidth,
     279              :     int pixelHeight, {
     280              :     bool renderAnnotations = true,
     281              :     bool lcdText = false,
     282              :     int backgroundColor = 0xFFFFFFFF,
     283            2 :   }) => _impl.renderPageToBytes(
     284              :     pageIndex,
     285              :     pixelWidth,
     286              :     pixelHeight,
     287              :     renderAnnotations: renderAnnotations,
     288              :     lcdText: lcdText,
     289              :     backgroundColor: backgroundColor,
     290              :   );
     291              : 
     292              :   /// Extracts all image objects from one or all pages of the document.
     293              :   ///
     294              :   /// When [pageIndex] is `null`, the stream yields one [PdfPageImages] per
     295              :   /// page in index order. Pages with no image objects emit an entry with an
     296              :   /// empty [PdfPageImages.images] list so callers can track page coverage
     297              :   /// without gaps.
     298              :   ///
     299              :   /// When [pageIndex] is specified, the stream yields exactly one
     300              :   /// [PdfPageImages] for that page.
     301              :   ///
     302              :   /// ## Bitmap mode
     303              :   ///
     304              :   /// When [includeBitmap] is `false` (the default), [PdfImage.bgra],
     305              :   /// [PdfImage.bitmapWidth], and [PdfImage.bitmapHeight] are `null` on every
     306              :   /// returned [PdfImage]. Only metadata ([PdfImage.metadata]) and the
     307              :   /// bounding box ([PdfImage.bounds]) are populated. This is the cheap,
     308              :   /// memory-efficient path for enumerating images.
     309              :   ///
     310              :   /// When [includeBitmap] is `true`, the rendered BGRA bitmap is fetched for
     311              :   /// every image object on each page. For documents with many large photographs
     312              :   /// this can produce very large allocations. Prefer calling [renderImage]
     313              :   /// selectively after inspecting [PdfImageMetadata] for image dimensions and
     314              :   /// colorspace.
     315              :   ///
     316              :   /// ## Image masks
     317              :   ///
     318              :   /// Image mask objects (`metadata.bitsPerPixel == 1`) are included in the
     319              :   /// output and are not suppressed automatically. Callers can identify them
     320              :   /// via `image.metadata.bitsPerPixel == 1`.
     321              :   ///
     322              :   /// ## Error handling
     323              :   ///
     324              :   /// Throws [RangeError] if [pageIndex] is out of range.
     325              :   /// Throws [StateError] if the document has been closed before or during
     326              :   /// extraction.
     327              :   ///
     328              :   /// [close] terminates any active stream: the stream stops emitting events
     329              :   /// and the subscription is silently cancelled. Callers do not need to cancel
     330              :   /// streams manually before calling [close].
     331              :   ///
     332              :   /// **Platform support:** Native (dart:ffi) only. Stubs on unsupported
     333              :   /// platforms throw [UnsupportedError] immediately.
     334              :   ///
     335              :   /// Example — collect all JPEG images from a document:
     336              :   ///
     337              :   /// ```dart
     338              :   /// await for (final page in doc.extractImages()) {
     339              :   ///   for (final img in page.images) {
     340              :   ///     if (img.filters.contains('DCTDecode')) {
     341              :   ///       final bitmap = await doc.renderImage(img.pageIndex, img.objectIndex);
     342              :   ///       // use bitmap…
     343              :   ///     }
     344              :   ///   }
     345              :   /// }
     346              :   /// ```
     347            2 :   Stream<PdfPageImages> extractImages({
     348              :     int? pageIndex,
     349              :     bool includeBitmap = false,
     350            4 :   }) => _impl.extractImages(pageIndex: pageIndex, includeBitmap: includeBitmap);
     351              : 
     352              :   /// Fetches the rendered BGRA bitmap for a single image object on a page.
     353              :   ///
     354              :   /// [pageIndex] is the zero-based page index. [objectIndex] is the position
     355              :   /// of the image object in the page's object list, as reported by
     356              :   /// [PdfImage.objectIndex] from [extractImages].
     357              :   ///
     358              :   /// Returns a [PdfImageBitmap] containing the composited BGRA pixel data,
     359              :   /// or `null` when the object has no renderable bitmap (e.g. a mask-only
     360              :   /// image where `FPDFImageObj_GetRenderedBitmap` returns null).
     361              :   ///
     362              :   /// Each call is one isolate round-trip. For bulk extraction of many images
     363              :   /// from the same page, prefer calling [extractImages] with
     364              :   /// `includeBitmap: true` instead.
     365              :   ///
     366              :   /// Throws [RangeError] if [pageIndex] is out of range for the document.
     367              :   /// Throws [RangeError] if [objectIndex] is negative, or if [objectIndex]
     368              :   /// is out of range for the page (the object does not exist).
     369              :   /// Throws [StateError] if the document has been closed.
     370              :   ///
     371              :   /// **Platform support:** Native (dart:ffi) only. Stubs on unsupported
     372              :   /// platforms throw [UnsupportedError].
     373              :   ///
     374              :   /// Example — render only large images:
     375              :   ///
     376              :   /// ```dart
     377              :   /// await for (final page in doc.extractImages()) {
     378              :   ///   for (final img in page.images) {
     379              :   ///     if (img.metadata.width > 500 && img.metadata.height > 500) {
     380              :   ///       final bitmap = await doc.renderImage(img.pageIndex, img.objectIndex);
     381              :   ///       if (bitmap != null) {
     382              :   ///         // process bitmap.bgra…
     383              :   ///       }
     384              :   ///     }
     385              :   ///   }
     386              :   /// }
     387              :   /// ```
     388            1 :   Future<PdfImageBitmap?> renderImage(int pageIndex, int objectIndex) =>
     389            2 :       _impl.renderImage(pageIndex, objectIndex);
     390              : 
     391              :   /// Searches the document for [query] and streams all matches.
     392              :   ///
     393              :   /// Results are yielded page-by-page in ascending page order. An empty stream
     394              :   /// means no matches were found. An empty [query] string returns an empty
     395              :   /// stream immediately without invoking any PDFium calls.
     396              :   ///
     397              :   /// [flags] controls case-sensitivity, whole-word matching, and overlapping
     398              :   /// matches. Defaults to case-insensitive, non-whole-word, non-overlapping.
     399              :   ///
     400              :   /// When [pageIndex] is specified, the search is restricted to that single
     401              :   /// page. Omit it to search all pages. Throws [RangeError] if [pageIndex] is
     402              :   /// out of range for the document.
     403              :   ///
     404              :   /// Bounding rectangles in each [PdfSearchMatch] are in **PDF user-space**
     405              :   /// (origin bottom-left, units in points). Callers that need screen-space
     406              :   /// coordinates must apply `FPDF_PageToDevice()` themselves.
     407              :   ///
     408              :   /// Cancelling the stream subscription immediately stops further processing.
     409              :   /// Page-level PDFium handles are released inside the isolate after each
     410              :   /// round-trip, so there are no handle leaks on cancellation.
     411              :   ///
     412              :   /// [close] terminates any active stream: the stream stops emitting events
     413              :   /// and the subscription is silently cancelled.
     414              :   ///
     415              :   /// Throws [StateError] if the document has been closed before or during
     416              :   /// the search.
     417              :   ///
     418              :   /// **Platform support:** Native (dart:ffi) only. Stubs on unsupported
     419              :   /// platforms throw [UnsupportedError] immediately.
     420              :   ///
     421              :   /// Example — search for a term and print each match location:
     422              :   ///
     423              :   /// ```dart
     424              :   /// await for (final match in doc.search('example')) {
     425              :   ///   print('Match on page ${match.pageIndex + 1}: '
     426              :   ///       'char ${match.charIndex}, '
     427              :   ///       '${match.rects.length} rect(s)');
     428              :   /// }
     429              :   /// ```
     430            1 :   Stream<PdfSearchMatch> search(
     431              :     String query, {
     432              :     Set<PdfSearchFlag> flags = const {},
     433              :     int? pageIndex,
     434            2 :   }) => _impl.search(query, flags: flags, pageIndex: pageIndex);
     435              : 
     436              :   /// Returns the complete Table of Contents (bookmark/outline tree) for the
     437              :   /// document.
     438              :   ///
     439              :   /// Each [PdfTocEntry] in the returned list is a root-level bookmark entry.
     440              :   /// [PdfTocEntry.children] provides nested sub-entries at arbitrary depth.
     441              :   ///
     442              :   /// Returns an empty list when the document has no bookmarks — this is not
     443              :   /// an error condition.
     444              :   ///
     445              :   /// Throws [StateError] if [close] has already been called.
     446              :   ///
     447              :   /// **Platform support:** Native (dart:ffi) only. Stubs on unsupported
     448              :   /// platforms throw [UnsupportedError] immediately.
     449              :   ///
     450              :   /// Example — print all top-level bookmark titles and their page numbers:
     451              :   ///
     452              :   /// ```dart
     453              :   /// final toc = await doc.tableOfContents;
     454              :   /// for (final entry in toc) {
     455              :   ///   final page = entry.pageIndex != null ? 'page ${entry.pageIndex! + 1}' : '(no target)';
     456              :   ///   print('${entry.title} → $page');
     457              :   /// }
     458              :   /// ```
     459            6 :   Future<List<PdfTocEntry>> get tableOfContents => _impl.tableOfContents;
     460              : 
     461              :   /// Returns a thumbnail image for the page at [pageIndex].
     462              :   ///
     463              :   /// When the page contains an embedded `/Thumb` stream, that bitmap is decoded
     464              :   /// and returned at its native dimensions with
     465              :   /// [PdfThumbnailSource.embedded]. Not all PDFs contain embedded thumbnails —
     466              :   /// modern tools such as `pdflatex` typically do not produce them.
     467              :   ///
     468              :   /// When no embedded thumbnail is present and [generateIfAbsent] is `true`
     469              :   /// (the default), the page is rendered at a size proportional to
     470              :   /// [maxDimension] (longest edge ≤ [maxDimension] pixels, aspect ratio
     471              :   /// preserved) and returned with [PdfThumbnailSource.rendered].
     472              :   ///
     473              :   /// When no embedded thumbnail is present and [generateIfAbsent] is `false`,
     474              :   /// `null` is returned without any render pass. This is useful for callers
     475              :   /// that only wish to surface natively-embedded previews.
     476              :   ///
     477              :   /// [maxDimension] is a **logical pixel budget** — it applies only to the
     478              :   /// fallback render path and is ignored for embedded thumbnails. On high-DPI
     479              :   /// displays (e.g. Retina), multiply [maxDimension] by
     480              :   /// `MediaQuery.of(context).devicePixelRatio` before calling to obtain a
     481              :   /// full-resolution fallback render. This method lives in the pure-Dart layer
     482              :   /// and cannot access `MediaQuery` itself.
     483              :   ///
     484              :   /// ## Error contract
     485              :   ///
     486              :   /// Throws [RangeError] if [pageIndex] is out of range for the document.
     487              :   /// Throws [ArgumentError] if [maxDimension] ≤ 0.
     488              :   /// Throws [StateError] if [close] has been called before or during the call.
     489              :   /// Throws [PdfiumException] if a PDFium native call fails unexpectedly.
     490              :   ///
     491              :   /// **Platform support:** Native (dart:ffi) only. Stubs on unsupported
     492              :   /// platforms throw [UnsupportedError] immediately.
     493              :   ///
     494              :   /// Example — display a thumbnail in a Flutter widget:
     495              :   ///
     496              :   /// ```dart
     497              :   /// final dpr = MediaQuery.of(context).devicePixelRatio;
     498              :   /// final thumb = await doc.getThumbnail(0, maxDimension: (256 * dpr).round());
     499              :   /// if (thumb != null) {
     500              :   ///   // thumb.bgra is BGRA bytes; thumb.width × thumb.height is the size.
     501              :   /// }
     502              :   /// ```
     503            2 :   Future<PdfThumbnail?> getThumbnail(
     504              :     int pageIndex, {
     505              :     bool generateIfAbsent = true,
     506              :     int maxDimension = 256,
     507            4 :   }) => _impl.getThumbnail(
     508              :     pageIndex,
     509              :     generateIfAbsent: generateIfAbsent,
     510              :     maxDimension: maxDimension,
     511              :   );
     512              : 
     513              :   /// Closes the document and releases the native PDFium handle.
     514              :   ///
     515              :   /// Safe to call more than once — subsequent calls are no-ops. After [close]
     516              :   /// returns, all other methods throw [StateError].
     517              :   ///
     518              :   /// Any active [extractPlainText] stream is terminated: the stream stops
     519              :   /// emitting events and the subscription is silently cancelled. Callers do
     520              :   /// not need to cancel streams manually before calling [close].
     521           30 :   Future<void> close() => _impl.close();
     522              : }
        

Generated by: LCOV version 2.0-1