LCOV - code coverage report
Current view: top level - src/document - pdf_types.dart Coverage Total Hit
Test: lcov.info Lines: 100.0 % 497 497
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              : // Shared types for PDF document handling. These types have no platform
      16              : // dependencies and are used by both the native (dart:ffi) and web (WASM)
      17              : // backends. They represent the public API surface for metadata and error
      18              : // handling.
      19              : 
      20              : import 'dart:typed_data';
      21              : 
      22              : // Compares two lists for deep equality using element-wise [==].
      23              : //
      24              : // Dart's built-in List equality is reference equality, so callers that hold
      25              : // separate list instances with identical content would compare unequal without
      26              : // this helper. Used by annotation types that carry [List] fields.
      27            4 : bool _listEqual<T>(List<T> a, List<T> b) {
      28           12 :   if (a.length != b.length) return false;
      29           10 :   for (var i = 0; i < a.length; i++) {
      30           12 :     if (a[i] != b[i]) return false;
      31              :   }
      32              :   return true;
      33              : }
      34              : 
      35              : /// Errors that can occur during PDF document operations.
      36              : enum PdfError {
      37              :   /// The document bytes are corrupt, not a valid PDF, or otherwise unloadable.
      38              :   invalidDocument,
      39              : 
      40              :   /// The document is password-protected. Passwords are not supported in v1;
      41              :   /// the caller should inform the user why the file could not be opened.
      42              :   passwordRequired,
      43              : }
      44              : 
      45              : /// Thrown when a PDF operation fails.
      46              : ///
      47              : /// The [error] field provides the reason for the failure. Callers should
      48              : /// handle [PdfError.passwordRequired] and [PdfError.invalidDocument]
      49              : /// separately to give users actionable error messages.
      50              : ///
      51              : /// Example:
      52              : /// ```dart
      53              : /// try {
      54              : ///   final doc = await PdfDocument.fromBytes(bytes);
      55              : /// } on PdfExtractionException catch (e) {
      56              : ///   if (e.error == PdfError.passwordRequired) {
      57              : ///     // prompt user for a password
      58              : ///   } else {
      59              : ///     // report a corrupt or invalid file
      60              : ///   }
      61              : /// }
      62              : /// ```
      63              : class PdfExtractionException implements Exception {
      64              :   /// Creates a [PdfExtractionException] with the given [error].
      65            2 :   const PdfExtractionException(this.error);
      66              : 
      67              :   /// The reason for the failure.
      68              :   final PdfError error;
      69              : 
      70            1 :   @override
      71            3 :   String toString() => 'PdfExtractionException(${error.name})';
      72              : }
      73              : 
      74              : /// A PDF date value, preserving both the raw string and the parsed [DateTime].
      75              : ///
      76              : /// PDF dates use the format `D:YYYYMMDDHHmmSSOHH'mm'` (where O is +, -, or Z).
      77              : /// The `D:` prefix is optional in practice and the string may be truncated.
      78              : /// When parsing fails, [value] is `null` but [raw] is always preserved so
      79              : /// callers can inspect or log the original string.
      80              : class PdfDate {
      81              :   /// Creates a [PdfDate] with the given raw string and parsed [DateTime].
      82            5 :   const PdfDate({required this.raw, required this.value});
      83              : 
      84              :   /// The raw date string as stored in the PDF Info dictionary.
      85              :   ///
      86              :   /// This value is always non-empty when the field is present. It follows the
      87              :   /// PDF date format `D:YYYYMMDDHHmmSSOHH'mm'` but real-world PDFs may deviate.
      88              :   final String raw;
      89              : 
      90              :   /// The parsed [DateTime], or `null` if [raw] could not be parsed.
      91              :   ///
      92              :   /// The returned [DateTime] is always in UTC (offset is converted). When the
      93              :   /// raw string contains an invalid or partial date, this field is `null` and
      94              :   /// [raw] is preserved for debugging purposes.
      95              :   final DateTime? value;
      96              : 
      97            1 :   @override
      98            3 :   String toString() => 'PdfDate(raw: $raw, value: $value)';
      99              : 
     100            2 :   @override
     101              :   bool operator ==(Object other) =>
     102              :       identical(this, other) ||
     103           11 :       other is PdfDate && raw == other.raw && value == other.value;
     104              : 
     105            2 :   @override
     106            6 :   int get hashCode => Object.hash(raw, value);
     107              : }
     108              : 
     109              : /// Metadata extracted from the PDF Info dictionary.
     110              : ///
     111              : /// All fields are nullable; a `null` value means the field was not present in
     112              : /// the Info dictionary (as opposed to being present but empty). This mirrors
     113              : /// the PDF specification where field presence and field value are distinct states.
     114              : ///
     115              : /// The eight standard Info dictionary fields are: [title], [author], [subject],
     116              : /// [keywords], [creator], [producer], [creationDate], and [modDate].
     117              : class PdfMetadata {
     118              :   /// Creates an immutable [PdfMetadata] value object.
     119            3 :   const PdfMetadata({
     120              :     this.title,
     121              :     this.author,
     122              :     this.subject,
     123              :     this.keywords,
     124              :     this.creator,
     125              :     this.producer,
     126              :     this.creationDate,
     127              :     this.modDate,
     128              :   });
     129              : 
     130              :   /// The document title, or `null` if not present.
     131              :   final String? title;
     132              : 
     133              :   /// The document author, or `null` if not present.
     134              :   final String? author;
     135              : 
     136              :   /// The document subject or description, or `null` if not present.
     137              :   final String? subject;
     138              : 
     139              :   /// Comma-separated keywords, or `null` if not present.
     140              :   final String? keywords;
     141              : 
     142              :   /// The application that created the original document, or `null` if not present.
     143              :   final String? creator;
     144              : 
     145              :   /// The application that converted the document to PDF, or `null` if not present.
     146              :   final String? producer;
     147              : 
     148              :   /// The date and time the document was created, or `null` if not present.
     149              :   final PdfDate? creationDate;
     150              : 
     151              :   /// The date and time the document was last modified, or `null` if not present.
     152              :   final PdfDate? modDate;
     153              : 
     154            1 :   @override
     155            1 :   String toString() =>
     156              :       'PdfMetadata('
     157            1 :       'title: $title, '
     158            1 :       'author: $author, '
     159            1 :       'subject: $subject, '
     160            1 :       'keywords: $keywords, '
     161            1 :       'creator: $creator, '
     162            1 :       'producer: $producer, '
     163            1 :       'creationDate: $creationDate, '
     164            1 :       'modDate: $modDate'
     165              :       ')';
     166              : }
     167              : 
     168              : // ---------------------------------------------------------------------------
     169              : // Text extraction types
     170              : // ---------------------------------------------------------------------------
     171              : 
     172              : /// The result of plain text extraction for a single PDF page.
     173              : ///
     174              : /// Produced by [PdfDocument.extractPlainText]. Each item in the stream
     175              : /// corresponds to one page.
     176              : ///
     177              : /// [hasTextLayer] is the primary signal for whether useful text was extracted.
     178              : /// Use [PdfDocument.isPlainTextExtractable] when you need a document-level
     179              : /// heuristic rather than per-page signals.
     180              : final class PdfPageText {
     181              :   /// Creates an immutable [PdfPageText] value.
     182            3 :   const PdfPageText({
     183              :     required this.pageIndex,
     184              :     required this.text,
     185              :     required this.hasUnicodeErrors,
     186              :     required this.hasTextLayer,
     187              :   });
     188              : 
     189              :   /// Zero-based index of the page this result corresponds to.
     190              :   final int pageIndex;
     191              : 
     192              :   /// The extracted Unicode text for this page.
     193              :   ///
     194              :   /// Soft hyphens (U+00AD) that appear at line-break positions are stripped
     195              :   /// and the surrounding words are joined. When [hasTextLayer] is false
     196              :   /// (scanned page), this is an empty string.
     197              :   final String text;
     198              : 
     199              :   /// True when at least one character on this page had a broken Unicode
     200              :   /// mapping (i.e. `FPDFText_HasUnicodeMapError` returned non-zero for it).
     201              :   ///
     202              :   /// Such characters are silently omitted from [text] by PDFium. This flag
     203              :   /// warns callers that the extracted text may be incomplete.
     204              :   final bool hasUnicodeErrors;
     205              : 
     206              :   /// True when PDFium extracted at least one character from this page.
     207              :   ///
     208              :   /// False indicates a scanned or image-only page with no text layer; [text]
     209              :   /// will be an empty string in that case.
     210              :   final bool hasTextLayer;
     211              : 
     212            1 :   @override
     213            1 :   String toString() =>
     214              :       'PdfPageText('
     215            1 :       'pageIndex: $pageIndex, '
     216            1 :       'hasTextLayer: $hasTextLayer, '
     217            1 :       'hasUnicodeErrors: $hasUnicodeErrors, '
     218            7 :       'text: ${text.length > 40 ? '${text.substring(0, 40)}…' : text}'
     219              :       ')';
     220              : 
     221            1 :   @override
     222              :   bool operator ==(Object other) =>
     223              :       identical(this, other) ||
     224            1 :       other is PdfPageText &&
     225            3 :           pageIndex == other.pageIndex &&
     226            3 :           text == other.text &&
     227            3 :           hasUnicodeErrors == other.hasUnicodeErrors &&
     228            3 :           hasTextLayer == other.hasTextLayer;
     229              : 
     230            1 :   @override
     231              :   int get hashCode =>
     232            5 :       Object.hash(pageIndex, text, hasUnicodeErrors, hasTextLayer);
     233              : }
     234              : 
     235              : /// Configuration for text extraction heuristics.
     236              : ///
     237              : /// [scannedPageRatio] affects [PdfDocument.isPlainTextExtractable].
     238              : /// [PdfPageText.hasTextLayer] is always determined by whether PDFium can
     239              : /// extract any characters from the page — no configuration is needed.
     240              : final class PdfTextExtractorConfig {
     241              :   /// Creates a [PdfTextExtractorConfig].
     242              :   ///
     243              :   /// [scannedPageRatio] must be > 0 and ≤ 1.
     244           19 :   const PdfTextExtractorConfig({this.scannedPageRatio = 0.5})
     245              :     : assert(
     246           57 :         scannedPageRatio > 0 && scannedPageRatio <= 1,
     247              :         'scannedPageRatio must be > 0 and <= 1',
     248              :       );
     249              : 
     250              :   /// Fraction of pages that must have no text layer for
     251              :   /// [PdfDocument.isPlainTextExtractable] to return false. Default: 0.5.
     252              :   ///
     253              :   /// A value of 0.5 means a document is only considered predominantly scanned
     254              :   /// when more than half its pages yield no characters from PDFium. A single
     255              :   /// image or figure page in an otherwise text-based document will not trigger
     256              :   /// this flag.
     257              :   final double scannedPageRatio;
     258              : 
     259            1 :   @override
     260              :   String toString() =>
     261            2 :       'PdfTextExtractorConfig(scannedPageRatio: $scannedPageRatio)';
     262              : }
     263              : 
     264              : // ---------------------------------------------------------------------------
     265              : // Annotation types
     266              : // ---------------------------------------------------------------------------
     267              : 
     268              : /// The subtype of a PDF annotation, corresponding to the `fpdf_annot.h`
     269              : /// `FPDF_ANNOT_*` constants.
     270              : ///
     271              : /// Only subtypes that are in scope for v0.02 are listed; form-field types
     272              : /// (`FPDF_ANNOT_WIDGET`, `FPDF_ANNOT_XFAWIDGET`) are out of scope and are
     273              : /// mapped to [unknown]. See `docs/spec/annotation_extraction.md` for details.
     274              : enum PdfAnnotationType {
     275              :   /// Sticky note annotation (`FPDF_ANNOT_TEXT = 1`).
     276              :   text,
     277              : 
     278              :   /// Hyperlink annotation (`FPDF_ANNOT_LINK = 2`).
     279              :   link,
     280              : 
     281              :   /// Free-text (typewriter) annotation (`FPDF_ANNOT_FREETEXT = 3`).
     282              :   freeText,
     283              : 
     284              :   /// Line annotation (`FPDF_ANNOT_LINE = 4`).
     285              :   line,
     286              : 
     287              :   /// Rectangle annotation (`FPDF_ANNOT_SQUARE = 5`).
     288              :   square,
     289              : 
     290              :   /// Ellipse annotation (`FPDF_ANNOT_CIRCLE = 6`).
     291              :   circle,
     292              : 
     293              :   /// Polygon annotation (`FPDF_ANNOT_POLYGON = 7`).
     294              :   polygon,
     295              : 
     296              :   /// Polyline annotation (`FPDF_ANNOT_POLYLINE = 8`).
     297              :   polyline,
     298              : 
     299              :   /// Highlight annotation (`FPDF_ANNOT_HIGHLIGHT = 9`).
     300              :   highlight,
     301              : 
     302              :   /// Underline annotation (`FPDF_ANNOT_UNDERLINE = 10`).
     303              :   underline,
     304              : 
     305              :   /// Squiggly underline annotation (`FPDF_ANNOT_SQUIGGLY = 11`).
     306              :   squiggly,
     307              : 
     308              :   /// Strikeout annotation (`FPDF_ANNOT_STRIKEOUT = 12`).
     309              :   strikeout,
     310              : 
     311              :   /// Rubber stamp annotation (`FPDF_ANNOT_STAMP = 13`).
     312              :   stamp,
     313              : 
     314              :   /// Free-draw ink annotation (`FPDF_ANNOT_INK = 15`).
     315              :   ink,
     316              : 
     317              :   /// Popup annotation (`FPDF_ANNOT_POPUP = 16`). Inlined on parent, not emitted
     318              :   /// as a top-level annotation.
     319              :   popup,
     320              : 
     321              :   /// Any annotation subtype not recognised by this library version.
     322              :   unknown,
     323              : }
     324              : 
     325              : /// An ARGB colour value as extracted from a PDF annotation.
     326              : ///
     327              : /// Component values range from 0 to 255.
     328              : final class PdfColor {
     329              :   /// Creates a [PdfColor] from its RGBA components.
     330            2 :   const PdfColor({
     331              :     required this.r,
     332              :     required this.g,
     333              :     required this.b,
     334              :     required this.a,
     335              :   });
     336              : 
     337              :   /// Red component, 0–255.
     338              :   final int r;
     339              : 
     340              :   /// Green component, 0–255.
     341              :   final int g;
     342              : 
     343              :   /// Blue component, 0–255.
     344              :   final int b;
     345              : 
     346              :   /// Alpha (opacity) component, 0–255 where 255 is fully opaque.
     347              :   final int a;
     348              : 
     349            1 :   @override
     350              :   bool operator ==(Object other) =>
     351              :       identical(this, other) ||
     352            1 :       other is PdfColor &&
     353            3 :           r == other.r &&
     354            3 :           g == other.g &&
     355            3 :           b == other.b &&
     356            3 :           a == other.a;
     357              : 
     358            1 :   @override
     359            5 :   int get hashCode => Object.hash(r, g, b, a);
     360              : 
     361            1 :   @override
     362            5 :   String toString() => 'PdfColor(r: $r, g: $g, b: $b, a: $a)';
     363              : }
     364              : 
     365              : /// A bounding rectangle in PDF page coordinates.
     366              : ///
     367              : /// PDFium uses a bottom-left page origin, so [bottom] < [top] in typical
     368              : /// usage. Coordinates are in PDF user space units (points).
     369              : ///
     370              : /// Callers that need screen coordinates must apply `FPDF_PageToDevice()` /
     371              : /// `FPDF_DeviceToPage()` themselves; this library exposes raw PDF coordinates.
     372              : final class PdfRect {
     373              :   /// Creates a [PdfRect] from PDF page coordinates.
     374           19 :   const PdfRect({
     375              :     required this.left,
     376              :     required this.bottom,
     377              :     required this.right,
     378              :     required this.top,
     379              :   });
     380              : 
     381              :   /// Left edge in PDF user space (lower x value).
     382              :   final double left;
     383              : 
     384              :   /// Bottom edge in PDF user space (lower y value for bottom-left origin).
     385              :   final double bottom;
     386              : 
     387              :   /// Right edge in PDF user space (higher x value).
     388              :   final double right;
     389              : 
     390              :   /// Top edge in PDF user space (higher y value for bottom-left origin).
     391              :   final double top;
     392              : 
     393            3 :   @override
     394              :   bool operator ==(Object other) =>
     395              :       identical(this, other) ||
     396            2 :       other is PdfRect &&
     397            6 :           left == other.left &&
     398            6 :           bottom == other.bottom &&
     399            3 :           right == other.right &&
     400            3 :           top == other.top;
     401              : 
     402            3 :   @override
     403           15 :   int get hashCode => Object.hash(left, bottom, right, top);
     404              : 
     405            2 :   @override
     406              :   String toString() =>
     407           10 :       'PdfRect(left: $left, bottom: $bottom, right: $right, top: $top)';
     408              : }
     409              : 
     410              : /// A point in PDF page coordinates.
     411              : ///
     412              : /// PDFium uses a bottom-left page origin. Coordinates are in PDF user space
     413              : /// units (points).
     414              : final class PdfPoint {
     415              :   /// Creates a [PdfPoint] at the given [x] and [y] coordinates.
     416            3 :   const PdfPoint({required this.x, required this.y});
     417              : 
     418              :   /// The x coordinate in PDF user space.
     419              :   final double x;
     420              : 
     421              :   /// The y coordinate in PDF user space.
     422              :   final double y;
     423              : 
     424            2 :   @override
     425              :   bool operator ==(Object other) =>
     426              :       identical(this, other) ||
     427           11 :       other is PdfPoint && x == other.x && y == other.y;
     428              : 
     429            1 :   @override
     430            3 :   int get hashCode => Object.hash(x, y);
     431              : 
     432            2 :   @override
     433            6 :   String toString() => 'PdfPoint(x: $x, y: $y)';
     434              : }
     435              : 
     436              : /// A set of four corner points defining one quadrilateral region.
     437              : ///
     438              : /// Used for text markup annotations (highlight, underline, squiggly,
     439              : /// strikeout) to describe the precise area of marked-up text, which may not
     440              : /// be axis-aligned. Points are ordered: top-left, top-right, bottom-left,
     441              : /// bottom-right (following the PDF specification quad-point ordering).
     442              : final class PdfQuadPoints {
     443              :   /// Creates a [PdfQuadPoints] from its four corner points.
     444            2 :   const PdfQuadPoints({
     445              :     required this.p1,
     446              :     required this.p2,
     447              :     required this.p3,
     448              :     required this.p4,
     449              :   });
     450              : 
     451              :   /// First point (top-left of the quadrilateral).
     452              :   final PdfPoint p1;
     453              : 
     454              :   /// Second point (top-right of the quadrilateral).
     455              :   final PdfPoint p2;
     456              : 
     457              :   /// Third point (bottom-left of the quadrilateral).
     458              :   final PdfPoint p3;
     459              : 
     460              :   /// Fourth point (bottom-right of the quadrilateral).
     461              :   final PdfPoint p4;
     462              : 
     463            1 :   @override
     464              :   bool operator ==(Object other) =>
     465              :       identical(this, other) ||
     466            1 :       other is PdfQuadPoints &&
     467            3 :           p1 == other.p1 &&
     468            3 :           p2 == other.p2 &&
     469            3 :           p3 == other.p3 &&
     470            3 :           p4 == other.p4;
     471              : 
     472            1 :   @override
     473            5 :   int get hashCode => Object.hash(p1, p2, p3, p4);
     474              : 
     475            1 :   @override
     476            5 :   String toString() => 'PdfQuadPoints(p1: $p1, p2: $p2, p3: $p3, p4: $p4)';
     477              : }
     478              : 
     479              : /// Popup annotation data inlined onto a parent annotation.
     480              : ///
     481              : /// `FPDF_ANNOT_POPUP` annotations are child annotations of sticky-note and
     482              : /// free-text annotations. Rather than exposing them as top-level entries (which
     483              : /// would confuse callers), the library inlines their data on the parent
     484              : /// annotation as an optional [PdfPopupAnnotation] field.
     485              : final class PdfPopupAnnotation {
     486              :   /// Creates a [PdfPopupAnnotation].
     487            2 :   const PdfPopupAnnotation({this.rect, required this.flags});
     488              : 
     489              :   /// The bounding rectangle of the popup window, or `null` if not available.
     490              :   final PdfRect? rect;
     491              : 
     492              :   /// Raw `FPDF_ANNOT_FLAG_*` bitmask for the popup annotation.
     493              :   final int flags;
     494              : 
     495            1 :   @override
     496              :   bool operator ==(Object other) =>
     497              :       identical(this, other) ||
     498            7 :       other is PdfPopupAnnotation && rect == other.rect && flags == other.flags;
     499              : 
     500            1 :   @override
     501            3 :   int get hashCode => Object.hash(rect, flags);
     502              : 
     503            1 :   @override
     504            3 :   String toString() => 'PdfPopupAnnotation(rect: $rect, flags: $flags)';
     505              : }
     506              : 
     507              : /// Base class for all PDF annotation types.
     508              : ///
     509              : /// Use a `switch` expression on the concrete subtype to access type-specific
     510              : /// fields:
     511              : ///
     512              : /// ```dart
     513              : /// switch (annotation) {
     514              : ///   PdfMarkupAnnotation(:final quadPoints, :final color) => ...,
     515              : ///   PdfLinkAnnotation(:final uri) => ...,
     516              : ///   _ => ...,
     517              : /// }
     518              : /// ```
     519              : ///
     520              : /// ## Common fields
     521              : ///
     522              : /// Every annotation carries [pageIndex], [flags], and optionally [rect],
     523              : /// [color], [contents], [author], and [modifiedDate]. Type-specific fields
     524              : /// live only on the appropriate subclass.
     525              : ///
     526              : /// ## Coordinate system
     527              : ///
     528              : /// [rect] is in raw PDF page coordinates (bottom-left origin). Callers that
     529              : /// need screen coordinates must apply `FPDF_PageToDevice()` themselves.
     530              : sealed class PdfAnnotation {
     531              :   /// Creates a [PdfAnnotation] with common fields.
     532            3 :   const PdfAnnotation({
     533              :     required this.pageIndex,
     534              :     this.contents,
     535              :     this.author,
     536              :     this.rect,
     537              :     this.color,
     538              :     this.modifiedDate,
     539              :     required this.flags,
     540              :     this.popup,
     541              :   });
     542              : 
     543              :   /// Zero-based index of the page this annotation belongs to.
     544              :   final int pageIndex;
     545              : 
     546              :   /// The annotation's `Contents` string, or `null` if absent.
     547              :   ///
     548              :   /// An empty string means the field is present but empty; `null` means the
     549              :   /// field is absent. These cases are intentionally distinguishable.
     550              :   final String? contents;
     551              : 
     552              :   /// The annotation author (`/T` dictionary entry), or `null` if absent.
     553              :   final String? author;
     554              : 
     555              :   /// The bounding rectangle in PDF page coordinates, or `null` if unavailable.
     556              :   final PdfRect? rect;
     557              : 
     558              :   /// The annotation colour, or `null` if no colour entry is present.
     559              :   final PdfColor? color;
     560              : 
     561              :   /// The last-modified date (`/M` dictionary entry), or `null` if absent.
     562              :   ///
     563              :   /// Parsed via `pdf_date_parser.dart`, consistent with [PdfMetadata.modDate].
     564              :   final PdfDate? modifiedDate;
     565              : 
     566              :   /// Raw `FPDF_ANNOT_FLAG_*` bitmask.
     567              :   ///
     568              :   /// See `FPDF_ANNOT_FLAG_HIDDEN`, `FPDF_ANNOT_FLAG_PRINT`, etc.
     569              :   final int flags;
     570              : 
     571              :   /// Inlined popup annotation data, or `null` if this annotation has no popup.
     572              :   ///
     573              :   /// Popup annotations (`FPDF_ANNOT_POPUP`) are child annotations of sticky
     574              :   /// notes and free-text annotations. They are not emitted as top-level
     575              :   /// entries; their data is inlined here instead.
     576              :   final PdfPopupAnnotation? popup;
     577              : }
     578              : 
     579              : /// A sticky note annotation (`FPDF_ANNOT_TEXT`).
     580              : ///
     581              : /// Sticky notes carry [contents], [author], [color], and optionally a [popup].
     582              : final class PdfTextAnnotation extends PdfAnnotation {
     583              :   /// Creates a [PdfTextAnnotation].
     584            3 :   const PdfTextAnnotation({
     585              :     required super.pageIndex,
     586              :     super.contents,
     587              :     super.author,
     588              :     super.rect,
     589              :     super.color,
     590              :     super.modifiedDate,
     591              :     required super.flags,
     592              :     super.popup,
     593              :   });
     594              : 
     595            1 :   @override
     596              :   bool operator ==(Object other) =>
     597              :       identical(this, other) ||
     598            1 :       other is PdfTextAnnotation &&
     599            3 :           pageIndex == other.pageIndex &&
     600            3 :           contents == other.contents &&
     601            3 :           author == other.author &&
     602            3 :           rect == other.rect &&
     603            3 :           color == other.color &&
     604            3 :           modifiedDate == other.modifiedDate &&
     605            3 :           flags == other.flags &&
     606            3 :           popup == other.popup;
     607              : 
     608            1 :   @override
     609            1 :   int get hashCode => Object.hash(
     610            1 :     pageIndex,
     611            1 :     contents,
     612            1 :     author,
     613            1 :     rect,
     614            1 :     color,
     615            1 :     modifiedDate,
     616            1 :     flags,
     617            1 :     popup,
     618              :   );
     619              : 
     620            1 :   @override
     621            1 :   String toString() =>
     622            2 :       'PdfTextAnnotation(pageIndex: $pageIndex, contents: $contents, '
     623            3 :       'author: $author, rect: $rect, color: $color, '
     624            3 :       'modifiedDate: $modifiedDate, flags: $flags, popup: $popup)';
     625              : }
     626              : 
     627              : /// A free-text (typewriter) annotation (`FPDF_ANNOT_FREETEXT`).
     628              : final class PdfFreeTextAnnotation extends PdfAnnotation {
     629              :   /// Creates a [PdfFreeTextAnnotation].
     630            2 :   const PdfFreeTextAnnotation({
     631              :     required super.pageIndex,
     632              :     super.contents,
     633              :     super.author,
     634              :     super.rect,
     635              :     super.color,
     636              :     super.modifiedDate,
     637              :     required super.flags,
     638              :     super.popup,
     639              :   });
     640              : 
     641            1 :   @override
     642              :   bool operator ==(Object other) =>
     643              :       identical(this, other) ||
     644            1 :       other is PdfFreeTextAnnotation &&
     645            3 :           pageIndex == other.pageIndex &&
     646            3 :           contents == other.contents &&
     647            3 :           author == other.author &&
     648            3 :           rect == other.rect &&
     649            3 :           color == other.color &&
     650            3 :           modifiedDate == other.modifiedDate &&
     651            3 :           flags == other.flags &&
     652            3 :           popup == other.popup;
     653              : 
     654            1 :   @override
     655            1 :   int get hashCode => Object.hash(
     656            1 :     pageIndex,
     657            1 :     contents,
     658            1 :     author,
     659            1 :     rect,
     660            1 :     color,
     661            1 :     modifiedDate,
     662            1 :     flags,
     663            1 :     popup,
     664              :   );
     665              : 
     666            1 :   @override
     667            1 :   String toString() =>
     668            2 :       'PdfFreeTextAnnotation(pageIndex: $pageIndex, contents: $contents, '
     669            3 :       'author: $author, rect: $rect, color: $color, '
     670            3 :       'modifiedDate: $modifiedDate, flags: $flags, popup: $popup)';
     671              : }
     672              : 
     673              : /// A text markup annotation: highlight, underline, squiggly, or strikeout.
     674              : ///
     675              : /// The [subtype] field distinguishes the four variants. [quadPoints] describes
     676              : /// the exact region(s) of marked-up text; each element corresponds to one
     677              : /// quadrilateral covering a line of text.
     678              : final class PdfMarkupAnnotation extends PdfAnnotation {
     679              :   /// Creates a [PdfMarkupAnnotation].
     680            2 :   const PdfMarkupAnnotation({
     681              :     required super.pageIndex,
     682              :     required this.subtype,
     683              :     required this.quadPoints,
     684              :     this.markedText,
     685              :     super.contents,
     686              :     super.author,
     687              :     super.rect,
     688              :     super.color,
     689              :     super.modifiedDate,
     690              :     required super.flags,
     691              :     super.popup,
     692              :   });
     693              : 
     694              :   /// The markup subtype: [PdfAnnotationType.highlight], [PdfAnnotationType.underline],
     695              :   /// [PdfAnnotationType.squiggly], or [PdfAnnotationType.strikeout].
     696              :   final PdfAnnotationType subtype;
     697              : 
     698              :   /// Quad-point sets describing the marked-up text regions.
     699              :   ///
     700              :   /// Each element covers one line of text. An empty list means no quad-points
     701              :   /// were found (the bounding [rect] can still be used as a fallback).
     702              :   final List<PdfQuadPoints> quadPoints;
     703              : 
     704              :   /// The text covered by this markup annotation, extracted from the page's text layer.
     705              :   ///
     706              :   /// Null when the text page could not be loaded (e.g. the page has no text
     707              :   /// layer, as with scanned documents). An empty string means the text layer
     708              :   /// exists but no characters fall within the annotated region.
     709              :   final String? markedText;
     710              : 
     711            1 :   @override
     712              :   bool operator ==(Object other) =>
     713              :       identical(this, other) ||
     714            1 :       other is PdfMarkupAnnotation &&
     715            3 :           pageIndex == other.pageIndex &&
     716            3 :           subtype == other.subtype &&
     717            3 :           _listEqual(quadPoints, other.quadPoints) &&
     718            3 :           markedText == other.markedText &&
     719            3 :           contents == other.contents &&
     720            3 :           author == other.author &&
     721            3 :           rect == other.rect &&
     722            3 :           color == other.color &&
     723            3 :           modifiedDate == other.modifiedDate &&
     724            3 :           flags == other.flags &&
     725            3 :           popup == other.popup;
     726              : 
     727            1 :   @override
     728            1 :   int get hashCode => Object.hash(
     729            1 :     pageIndex,
     730            1 :     subtype,
     731            2 :     Object.hashAll(quadPoints),
     732            1 :     markedText,
     733            1 :     contents,
     734            1 :     author,
     735            1 :     rect,
     736            1 :     color,
     737            1 :     modifiedDate,
     738            1 :     flags,
     739            1 :     popup,
     740              :   );
     741              : 
     742            1 :   @override
     743            1 :   String toString() =>
     744            2 :       'PdfMarkupAnnotation(pageIndex: $pageIndex, subtype: $subtype, '
     745            3 :       'quadPoints: ${quadPoints.length} quads, markedText: $markedText, '
     746            4 :       'contents: $contents, author: $author, color: $color, flags: $flags)';
     747              : }
     748              : 
     749              : /// A shape annotation: rectangle or ellipse.
     750              : ///
     751              : /// [subtype] is either [PdfAnnotationType.square] (rectangle) or
     752              : /// [PdfAnnotationType.circle] (ellipse). [interiorColor] is the fill colour
     753              : /// of the shape, distinct from the border [color].
     754              : final class PdfShapeAnnotation extends PdfAnnotation {
     755              :   /// Creates a [PdfShapeAnnotation].
     756            2 :   const PdfShapeAnnotation({
     757              :     required super.pageIndex,
     758              :     required this.subtype,
     759              :     this.interiorColor,
     760              :     super.contents,
     761              :     super.author,
     762              :     super.rect,
     763              :     super.color,
     764              :     super.modifiedDate,
     765              :     required super.flags,
     766              :     super.popup,
     767              :   });
     768              : 
     769              :   /// The shape subtype: [PdfAnnotationType.square] or [PdfAnnotationType.circle].
     770              :   final PdfAnnotationType subtype;
     771              : 
     772              :   /// The fill (interior) colour, or `null` if not set.
     773              :   final PdfColor? interiorColor;
     774              : 
     775            1 :   @override
     776              :   bool operator ==(Object other) =>
     777              :       identical(this, other) ||
     778            1 :       other is PdfShapeAnnotation &&
     779            3 :           pageIndex == other.pageIndex &&
     780            3 :           subtype == other.subtype &&
     781            3 :           interiorColor == other.interiorColor &&
     782            3 :           contents == other.contents &&
     783            3 :           author == other.author &&
     784            3 :           rect == other.rect &&
     785            3 :           color == other.color &&
     786            3 :           modifiedDate == other.modifiedDate &&
     787            3 :           flags == other.flags &&
     788            3 :           popup == other.popup;
     789              : 
     790            1 :   @override
     791            1 :   int get hashCode => Object.hash(
     792            1 :     pageIndex,
     793            1 :     subtype,
     794            1 :     interiorColor,
     795            1 :     contents,
     796            1 :     author,
     797            1 :     rect,
     798            1 :     color,
     799            1 :     modifiedDate,
     800            1 :     flags,
     801            1 :     popup,
     802              :   );
     803              : 
     804            1 :   @override
     805            1 :   String toString() =>
     806            2 :       'PdfShapeAnnotation(pageIndex: $pageIndex, subtype: $subtype, '
     807            4 :       'interiorColor: $interiorColor, rect: $rect, color: $color, flags: $flags)';
     808              : }
     809              : 
     810              : /// A line annotation (`FPDF_ANNOT_LINE`).
     811              : ///
     812              : /// The line runs from [lineStart] to [lineEnd] in PDF page coordinates.
     813              : final class PdfLineAnnotation extends PdfAnnotation {
     814              :   /// Creates a [PdfLineAnnotation].
     815            2 :   const PdfLineAnnotation({
     816              :     required super.pageIndex,
     817              :     required this.lineStart,
     818              :     required this.lineEnd,
     819              :     super.contents,
     820              :     super.author,
     821              :     super.rect,
     822              :     super.color,
     823              :     super.modifiedDate,
     824              :     required super.flags,
     825              :     super.popup,
     826              :   });
     827              : 
     828              :   /// The starting point of the line in PDF page coordinates.
     829              :   final PdfPoint lineStart;
     830              : 
     831              :   /// The ending point of the line in PDF page coordinates.
     832              :   final PdfPoint lineEnd;
     833              : 
     834            1 :   @override
     835              :   bool operator ==(Object other) =>
     836              :       identical(this, other) ||
     837            1 :       other is PdfLineAnnotation &&
     838            3 :           pageIndex == other.pageIndex &&
     839            3 :           lineStart == other.lineStart &&
     840            3 :           lineEnd == other.lineEnd &&
     841            3 :           contents == other.contents &&
     842            3 :           author == other.author &&
     843            3 :           rect == other.rect &&
     844            3 :           color == other.color &&
     845            3 :           modifiedDate == other.modifiedDate &&
     846            3 :           flags == other.flags &&
     847            3 :           popup == other.popup;
     848              : 
     849            1 :   @override
     850            1 :   int get hashCode => Object.hash(
     851            1 :     pageIndex,
     852            1 :     lineStart,
     853            1 :     lineEnd,
     854            1 :     contents,
     855            1 :     author,
     856            1 :     rect,
     857            1 :     color,
     858            1 :     modifiedDate,
     859            1 :     flags,
     860            1 :     popup,
     861              :   );
     862              : 
     863            1 :   @override
     864            1 :   String toString() =>
     865            2 :       'PdfLineAnnotation(pageIndex: $pageIndex, lineStart: $lineStart, '
     866            3 :       'lineEnd: $lineEnd, color: $color, flags: $flags)';
     867              : }
     868              : 
     869              : /// A free-draw ink annotation (`FPDF_ANNOT_INK`).
     870              : ///
     871              : /// [strokes] is a list of strokes; each stroke is a list of [PdfPoint]s
     872              : /// forming a continuous path. Multiple strokes represent separate pen-down
     873              : /// gestures.
     874              : final class PdfInkAnnotation extends PdfAnnotation {
     875              :   /// Creates a [PdfInkAnnotation].
     876            2 :   const PdfInkAnnotation({
     877              :     required super.pageIndex,
     878              :     required this.strokes,
     879              :     super.contents,
     880              :     super.author,
     881              :     super.rect,
     882              :     super.color,
     883              :     super.modifiedDate,
     884              :     required super.flags,
     885              :     super.popup,
     886              :   });
     887              : 
     888              :   /// The list of ink strokes. Each inner list is one pen-down gesture.
     889              :   ///
     890              :   /// An empty outer list means no ink paths could be read (e.g. the annotation
     891              :   /// is present but contains no ink data). An inner list may be empty if a
     892              :   /// stroke has zero points.
     893              :   final List<List<PdfPoint>> strokes;
     894              : 
     895            1 :   @override
     896              :   bool operator ==(Object other) =>
     897              :       identical(this, other) ||
     898            1 :       other is PdfInkAnnotation &&
     899            3 :           pageIndex == other.pageIndex &&
     900            3 :           _strokesEqual(strokes, other.strokes) &&
     901            3 :           contents == other.contents &&
     902            3 :           author == other.author &&
     903            3 :           rect == other.rect &&
     904            3 :           color == other.color &&
     905            3 :           modifiedDate == other.modifiedDate &&
     906            3 :           flags == other.flags &&
     907            3 :           popup == other.popup;
     908              : 
     909              :   /// Deep-equality helper for the nested strokes list.
     910            1 :   static bool _strokesEqual(List<List<PdfPoint>> a, List<List<PdfPoint>> b) {
     911            3 :     if (a.length != b.length) return false;
     912            3 :     for (var i = 0; i < a.length; i++) {
     913            5 :       if (a[i].length != b[i].length) return false;
     914            4 :       for (var j = 0; j < a[i].length; j++) {
     915            5 :         if (a[i][j] != b[i][j]) return false;
     916              :       }
     917              :     }
     918              :     return true;
     919              :   }
     920              : 
     921            1 :   @override
     922            1 :   int get hashCode => Object.hash(
     923            1 :     pageIndex,
     924            3 :     Object.hashAll(strokes.map(Object.hashAll)),
     925            1 :     contents,
     926            1 :     author,
     927            1 :     rect,
     928            1 :     color,
     929            1 :     modifiedDate,
     930            1 :     flags,
     931            1 :     popup,
     932              :   );
     933              : 
     934            1 :   @override
     935            1 :   String toString() =>
     936            3 :       'PdfInkAnnotation(pageIndex: $pageIndex, strokes: ${strokes.length}, '
     937            2 :       'color: $color, flags: $flags)';
     938              : }
     939              : 
     940              : /// A polygon or polyline annotation.
     941              : ///
     942              : /// [subtype] is either [PdfAnnotationType.polygon] or
     943              : /// [PdfAnnotationType.polyline]. [vertices] are the corner points.
     944              : final class PdfPolygonAnnotation extends PdfAnnotation {
     945              :   /// Creates a [PdfPolygonAnnotation].
     946            2 :   const PdfPolygonAnnotation({
     947              :     required super.pageIndex,
     948              :     required this.subtype,
     949              :     required this.vertices,
     950              :     super.contents,
     951              :     super.author,
     952              :     super.rect,
     953              :     super.color,
     954              :     super.modifiedDate,
     955              :     required super.flags,
     956              :     super.popup,
     957              :   });
     958              : 
     959              :   /// The polygon/polyline subtype.
     960              :   final PdfAnnotationType subtype;
     961              : 
     962              :   /// The vertex points of the polygon or polyline, in order.
     963              :   final List<PdfPoint> vertices;
     964              : 
     965            1 :   @override
     966              :   bool operator ==(Object other) =>
     967              :       identical(this, other) ||
     968            1 :       other is PdfPolygonAnnotation &&
     969            3 :           pageIndex == other.pageIndex &&
     970            3 :           subtype == other.subtype &&
     971            3 :           _listEqual(vertices, other.vertices) &&
     972            3 :           contents == other.contents &&
     973            3 :           author == other.author &&
     974            3 :           rect == other.rect &&
     975            3 :           color == other.color &&
     976            3 :           modifiedDate == other.modifiedDate &&
     977            3 :           flags == other.flags &&
     978            3 :           popup == other.popup;
     979              : 
     980            1 :   @override
     981            1 :   int get hashCode => Object.hash(
     982            1 :     pageIndex,
     983            1 :     subtype,
     984            2 :     Object.hashAll(vertices),
     985            1 :     contents,
     986            1 :     author,
     987            1 :     rect,
     988            1 :     color,
     989            1 :     modifiedDate,
     990            1 :     flags,
     991            1 :     popup,
     992              :   );
     993              : 
     994            1 :   @override
     995            1 :   String toString() =>
     996            2 :       'PdfPolygonAnnotation(pageIndex: $pageIndex, subtype: $subtype, '
     997            4 :       'vertices: ${vertices.length}, color: $color, flags: $flags)';
     998              : }
     999              : 
    1000              : /// A link annotation (`FPDF_ANNOT_LINK`).
    1001              : ///
    1002              : /// Links carry either a [uri] (for URI actions) or a page destination (not
    1003              : /// yet exposed — [uri] is `null` for non-URI actions). Callers should check
    1004              : /// [uri] and filter appropriately; not all links have URI actions.
    1005              : final class PdfLinkAnnotation extends PdfAnnotation {
    1006              :   /// Creates a [PdfLinkAnnotation].
    1007            1 :   const PdfLinkAnnotation({
    1008              :     required super.pageIndex,
    1009              :     this.uri,
    1010              :     super.contents,
    1011              :     super.author,
    1012              :     super.rect,
    1013              :     super.color,
    1014              :     super.modifiedDate,
    1015              :     required super.flags,
    1016              :     super.popup,
    1017              :   });
    1018              : 
    1019              :   /// The URI target of this link, or `null` if the link does not have a URI
    1020              :   /// action (e.g. it is a page-destination link or an unsupported action type).
    1021              :   final String? uri;
    1022              : 
    1023            1 :   @override
    1024              :   bool operator ==(Object other) =>
    1025              :       identical(this, other) ||
    1026            1 :       other is PdfLinkAnnotation &&
    1027            3 :           pageIndex == other.pageIndex &&
    1028            3 :           uri == other.uri &&
    1029            3 :           contents == other.contents &&
    1030            3 :           author == other.author &&
    1031            3 :           rect == other.rect &&
    1032            3 :           color == other.color &&
    1033            3 :           modifiedDate == other.modifiedDate &&
    1034            3 :           flags == other.flags &&
    1035            3 :           popup == other.popup;
    1036              : 
    1037            1 :   @override
    1038            1 :   int get hashCode => Object.hash(
    1039            1 :     pageIndex,
    1040            1 :     uri,
    1041            1 :     contents,
    1042            1 :     author,
    1043            1 :     rect,
    1044            1 :     color,
    1045            1 :     modifiedDate,
    1046            1 :     flags,
    1047            1 :     popup,
    1048              :   );
    1049              : 
    1050            1 :   @override
    1051            1 :   String toString() =>
    1052            2 :       'PdfLinkAnnotation(pageIndex: $pageIndex, uri: $uri, '
    1053            2 :       'rect: $rect, flags: $flags)';
    1054              : }
    1055              : 
    1056              : /// A rubber stamp annotation (`FPDF_ANNOT_STAMP`).
    1057              : final class PdfStampAnnotation extends PdfAnnotation {
    1058              :   /// Creates a [PdfStampAnnotation].
    1059            2 :   const PdfStampAnnotation({
    1060              :     required super.pageIndex,
    1061              :     super.contents,
    1062              :     super.author,
    1063              :     super.rect,
    1064              :     super.color,
    1065              :     super.modifiedDate,
    1066              :     required super.flags,
    1067              :     super.popup,
    1068              :   });
    1069              : 
    1070            1 :   @override
    1071              :   bool operator ==(Object other) =>
    1072              :       identical(this, other) ||
    1073            1 :       other is PdfStampAnnotation &&
    1074            3 :           pageIndex == other.pageIndex &&
    1075            3 :           contents == other.contents &&
    1076            3 :           author == other.author &&
    1077            3 :           rect == other.rect &&
    1078            3 :           color == other.color &&
    1079            3 :           modifiedDate == other.modifiedDate &&
    1080            3 :           flags == other.flags &&
    1081            3 :           popup == other.popup;
    1082              : 
    1083            1 :   @override
    1084            1 :   int get hashCode => Object.hash(
    1085            1 :     pageIndex,
    1086            1 :     contents,
    1087            1 :     author,
    1088            1 :     rect,
    1089            1 :     color,
    1090            1 :     modifiedDate,
    1091            1 :     flags,
    1092            1 :     popup,
    1093              :   );
    1094              : 
    1095            1 :   @override
    1096            1 :   String toString() =>
    1097            2 :       'PdfStampAnnotation(pageIndex: $pageIndex, contents: $contents, '
    1098            2 :       'rect: $rect, flags: $flags)';
    1099              : }
    1100              : 
    1101              : /// An annotation whose subtype is not recognised by this library version.
    1102              : ///
    1103              : /// The [rawSubtype] field carries the original `FPDF_ANNOT_*` integer so
    1104              : /// callers can inspect it for debugging or future-proofing purposes.
    1105              : final class PdfUnknownAnnotation extends PdfAnnotation {
    1106              :   /// Creates a [PdfUnknownAnnotation].
    1107            2 :   const PdfUnknownAnnotation({
    1108              :     required super.pageIndex,
    1109              :     required this.rawSubtype,
    1110              :     super.contents,
    1111              :     super.author,
    1112              :     super.rect,
    1113              :     super.color,
    1114              :     super.modifiedDate,
    1115              :     required super.flags,
    1116              :     super.popup,
    1117              :   });
    1118              : 
    1119              :   /// The raw `FPDF_ANNOT_*` integer value that was not recognised.
    1120              :   final int rawSubtype;
    1121              : 
    1122            1 :   @override
    1123              :   bool operator ==(Object other) =>
    1124              :       identical(this, other) ||
    1125            1 :       other is PdfUnknownAnnotation &&
    1126            3 :           pageIndex == other.pageIndex &&
    1127            3 :           rawSubtype == other.rawSubtype &&
    1128            3 :           contents == other.contents &&
    1129            3 :           author == other.author &&
    1130            3 :           rect == other.rect &&
    1131            3 :           color == other.color &&
    1132            3 :           modifiedDate == other.modifiedDate &&
    1133            3 :           flags == other.flags &&
    1134            3 :           popup == other.popup;
    1135              : 
    1136            1 :   @override
    1137            1 :   int get hashCode => Object.hash(
    1138            1 :     pageIndex,
    1139            1 :     rawSubtype,
    1140            1 :     contents,
    1141            1 :     author,
    1142            1 :     rect,
    1143            1 :     color,
    1144            1 :     modifiedDate,
    1145            1 :     flags,
    1146            1 :     popup,
    1147              :   );
    1148              : 
    1149            1 :   @override
    1150            1 :   String toString() =>
    1151            2 :       'PdfUnknownAnnotation(pageIndex: $pageIndex, rawSubtype: $rawSubtype, '
    1152            1 :       'flags: $flags)';
    1153              : }
    1154              : 
    1155              : /// The annotations extracted from a single PDF page.
    1156              : ///
    1157              : /// Produced by [PdfDocument.extractAnnotations]. Each item in the stream
    1158              : /// corresponds to one page. Pages with no annotations emit an entry with an
    1159              : /// empty [annotations] list, so callers can track page coverage without gaps.
    1160              : final class PdfPageAnnotations {
    1161              :   /// Creates an immutable [PdfPageAnnotations] value.
    1162            2 :   const PdfPageAnnotations({
    1163              :     required this.pageIndex,
    1164              :     required this.annotations,
    1165              :   });
    1166              : 
    1167              :   /// Zero-based index of the page this result corresponds to.
    1168              :   final int pageIndex;
    1169              : 
    1170              :   /// The annotations found on this page, in the order returned by PDFium.
    1171              :   ///
    1172              :   /// Popup annotations are inlined as [PdfAnnotation.popup] on their parent
    1173              :   /// and are not present as top-level entries in this list.
    1174              :   final List<PdfAnnotation> annotations;
    1175              : 
    1176            1 :   @override
    1177            1 :   String toString() =>
    1178            1 :       'PdfPageAnnotations(pageIndex: $pageIndex, '
    1179            2 :       'annotations: ${annotations.length})';
    1180              : }
    1181              : 
    1182              : // ---------------------------------------------------------------------------
    1183              : // Table of contents types
    1184              : // ---------------------------------------------------------------------------
    1185              : 
    1186              : /// A single entry in the PDF bookmark/outline tree (Table of Contents).
    1187              : ///
    1188              : /// A PDF's "Outline" dictionary is its native Table of Contents structure.
    1189              : /// Each entry has a [title], an optional destination ([pageIndex] and
    1190              : /// [scrollPosition]), and an optional [uri] for URI-action entries.
    1191              : /// [children] holds any nested sub-entries in the same tree shape.
    1192              : ///
    1193              : /// ## Destination resolution
    1194              : ///
    1195              : /// Bookmark destinations are resolved as follows:
    1196              : /// - If the bookmark has a `PDFACTION_GOTO` action, [pageIndex] is the
    1197              : ///   zero-based page index and [scrollPosition] is the XYZ anchor (if any).
    1198              : /// - If the bookmark has a `PDFACTION_URI` action, [uri] is the URI string and
    1199              : ///   [pageIndex] is `null`.
    1200              : /// - If neither a matching action nor a direct destination is found, both
    1201              : ///   [pageIndex] and [uri] are `null` (section-label entry with no target).
    1202              : ///
    1203              : /// ## Zoom omission
    1204              : ///
    1205              : /// `FPDFDest_GetLocationInPage` returns an (x, y, zoom) triple for
    1206              : /// `PDFDEST_VIEW_XYZ` destinations. The zoom value is intentionally **not**
    1207              : /// surfaced here. Exposing zoom risks overriding the user's OS accessibility
    1208              : /// zoom settings or Flutter's `textScaleFactor`, which would create a hostile
    1209              : /// experience for users who rely on display magnification. Callers that need
    1210              : /// precise magnification control should manage zoom independently of the
    1211              : /// bookmark destination. Only the (x, y) scroll anchor is captured via
    1212              : /// [scrollPosition].
    1213              : final class PdfTocEntry {
    1214              :   /// Creates an immutable [PdfTocEntry].
    1215            1 :   const PdfTocEntry({
    1216              :     required this.title,
    1217              :     this.pageIndex,
    1218              :     this.uri,
    1219              :     this.scrollPosition,
    1220              :     this.children = const [],
    1221              :   });
    1222              : 
    1223              :   /// The display title of this bookmark entry.
    1224              :   ///
    1225              :   /// An empty string is valid — PDFium found a bookmark with no title text.
    1226              :   final String title;
    1227              : 
    1228              :   /// The zero-based page index this entry navigates to, or `null` if the
    1229              :   /// entry has no internal-page destination (e.g. it is a URI action or a
    1230              :   /// section label with no target).
    1231              :   ///
    1232              :   /// A value of `null` does not indicate an error; it indicates that this
    1233              :   /// entry either has a [uri] target or is a pure structural label.
    1234              :   final int? pageIndex;
    1235              : 
    1236              :   /// The URI this entry navigates to, or `null` if the entry is not a URI
    1237              :   /// action.
    1238              :   ///
    1239              :   /// Non-null only for bookmarks with a `PDFACTION_URI` action.
    1240              :   /// [pageIndex] is always `null` when [uri] is non-null.
    1241              :   final String? uri;
    1242              : 
    1243              :   /// The XYZ scroll anchor within the destination page, or `null` if either
    1244              :   /// the entry has no page destination or the destination does not carry
    1245              :   /// explicit position coordinates.
    1246              :   ///
    1247              :   /// Coordinates are in PDF user space (points, bottom-left origin), matching
    1248              :   /// the coordinate system used by [PdfRect] and [PdfPoint] throughout this
    1249              :   /// library. Callers that need screen-space coordinates must apply
    1250              :   /// `FPDF_PageToDevice()` themselves.
    1251              :   ///
    1252              :   /// See the class-level doc comment for why the zoom component of XYZ
    1253              :   /// destinations is not surfaced here.
    1254              :   final PdfPoint? scrollPosition;
    1255              : 
    1256              :   /// The child entries nested under this entry, in document order.
    1257              :   ///
    1258              :   /// An empty list means this is a leaf entry with no sub-items.
    1259              :   final List<PdfTocEntry> children;
    1260              : 
    1261            1 :   @override
    1262              :   bool operator ==(Object other) =>
    1263              :       identical(this, other) ||
    1264            1 :       other is PdfTocEntry &&
    1265            3 :           title == other.title &&
    1266            3 :           pageIndex == other.pageIndex &&
    1267            3 :           uri == other.uri &&
    1268            3 :           scrollPosition == other.scrollPosition &&
    1269            3 :           _listEqual(children, other.children);
    1270              : 
    1271            1 :   @override
    1272            1 :   int get hashCode => Object.hash(
    1273            1 :     title,
    1274            1 :     pageIndex,
    1275            1 :     uri,
    1276            1 :     scrollPosition,
    1277            2 :     Object.hashAll(children),
    1278              :   );
    1279              : 
    1280            1 :   @override
    1281            1 :   String toString() =>
    1282            3 :       'PdfTocEntry(title: $title, pageIndex: $pageIndex, uri: $uri, '
    1283            3 :       'scrollPosition: $scrollPosition, children: ${children.length})';
    1284              : }
    1285              : 
    1286              : // ---------------------------------------------------------------------------
    1287              : // Image extraction types
    1288              : // ---------------------------------------------------------------------------
    1289              : 
    1290              : /// The colorspace of a PDF image object, corresponding to the
    1291              : /// `FPDF_COLORSPACE_*` constants in `fpdf_edit.h`.
    1292              : ///
    1293              : /// Raw PDFium integer constants are not exposed in the public API; all
    1294              : /// colorspaces are mapped to this enum. Use [unknown] as the fallback for
    1295              : /// any value not recognised by this version of the library.
    1296              : enum PdfColorspace {
    1297              :   /// `FPDF_COLORSPACE_UNKNOWN = 0` — colorspace not identified.
    1298              :   unknown,
    1299              : 
    1300              :   /// `FPDF_COLORSPACE_DEVICEGRAY = 1` — single-channel grey.
    1301              :   deviceGray,
    1302              : 
    1303              :   /// `FPDF_COLORSPACE_DEVICERGB = 2` — additive RGB.
    1304              :   deviceRgb,
    1305              : 
    1306              :   /// `FPDF_COLORSPACE_DEVICECMYK = 3` — subtractive CMYK.
    1307              :   deviceCmyk,
    1308              : 
    1309              :   /// `FPDF_COLORSPACE_CALGRAY = 4` — calibrated greyscale.
    1310              :   calGray,
    1311              : 
    1312              :   /// `FPDF_COLORSPACE_CALRGB = 5` — calibrated RGB.
    1313              :   calRgb,
    1314              : 
    1315              :   /// `FPDF_COLORSPACE_LAB = 6` — CIE L*a*b*.
    1316              :   lab,
    1317              : 
    1318              :   /// `FPDF_COLORSPACE_ICCBASED = 7` — ICC profile-based colorspace.
    1319              :   iccBased,
    1320              : 
    1321              :   /// `FPDF_COLORSPACE_SEPARATION = 8` — separation (spot colour).
    1322              :   separation,
    1323              : 
    1324              :   /// `FPDF_COLORSPACE_DEVICEN = 9` — DeviceN (multi-ink).
    1325              :   deviceN,
    1326              : 
    1327              :   /// `FPDF_COLORSPACE_INDEXED = 10` — indexed / palette.
    1328              :   indexed,
    1329              : 
    1330              :   /// `FPDF_COLORSPACE_PATTERN = 11` — pattern colorspace.
    1331              :   pattern,
    1332              : }
    1333              : 
    1334              : /// Source-level metadata for a single image object in a PDF page.
    1335              : ///
    1336              : /// These values come directly from the `FPDF_IMAGEOBJ_METADATA` struct and
    1337              : /// describe the image as stored in the PDF (before any transforms are applied).
    1338              : /// The rendered output may differ in dimensions — see [PdfImage.bitmapWidth]
    1339              : /// and [PdfImage.bitmapHeight].
    1340              : ///
    1341              : /// [markedContentId] links the image to the document's structure tree for
    1342              : /// alt-text lookup via `fpdf_structtree.h`. A value of `-1` means no
    1343              : /// marked-content identifier is present.
    1344              : final class PdfImageMetadata {
    1345              :   /// Creates an immutable [PdfImageMetadata].
    1346            2 :   const PdfImageMetadata({
    1347              :     required this.width,
    1348              :     required this.height,
    1349              :     required this.horizontalDpi,
    1350              :     required this.verticalDpi,
    1351              :     required this.bitsPerPixel,
    1352              :     required this.colorspace,
    1353              :     required this.markedContentId,
    1354              :   });
    1355              : 
    1356              :   /// Source pixel width of the image as stored in the PDF.
    1357              :   final int width;
    1358              : 
    1359              :   /// Source pixel height of the image as stored in the PDF.
    1360              :   final int height;
    1361              : 
    1362              :   /// Horizontal resolution in dots per inch.
    1363              :   final double horizontalDpi;
    1364              : 
    1365              :   /// Vertical resolution in dots per inch.
    1366              :   final double verticalDpi;
    1367              : 
    1368              :   /// Bits per pixel of the source image data (e.g. 1, 8, 24).
    1369              :   ///
    1370              :   /// A value of 1 typically indicates an image mask (stencil). Image mask
    1371              :   /// objects appear in the [PdfPageImages.images] list and are not suppressed
    1372              :   /// automatically — callers can identify them via this field.
    1373              :   final int bitsPerPixel;
    1374              : 
    1375              :   /// The colorspace of the source image data.
    1376              :   final PdfColorspace colorspace;
    1377              : 
    1378              :   /// The marked-content identifier linking this image to the structure tree,
    1379              :   /// or `-1` if the image has no marked-content entry.
    1380              :   ///
    1381              :   /// Callers that need the associated alt-text must look up this identifier
    1382              :   /// in the document structure tree via `fpdf_structtree.h` independently.
    1383              :   final int markedContentId;
    1384              : 
    1385            2 :   @override
    1386              :   bool operator ==(Object other) =>
    1387              :       identical(this, other) ||
    1388            2 :       other is PdfImageMetadata &&
    1389            6 :           width == other.width &&
    1390            3 :           height == other.height &&
    1391            3 :           horizontalDpi == other.horizontalDpi &&
    1392            3 :           verticalDpi == other.verticalDpi &&
    1393            3 :           bitsPerPixel == other.bitsPerPixel &&
    1394            3 :           colorspace == other.colorspace &&
    1395            3 :           markedContentId == other.markedContentId;
    1396              : 
    1397            2 :   @override
    1398            2 :   int get hashCode => Object.hash(
    1399            2 :     width,
    1400            2 :     height,
    1401            2 :     horizontalDpi,
    1402            2 :     verticalDpi,
    1403            2 :     bitsPerPixel,
    1404            2 :     colorspace,
    1405            2 :     markedContentId,
    1406              :   );
    1407              : 
    1408            2 :   @override
    1409            2 :   String toString() =>
    1410              :       'PdfImageMetadata('
    1411            4 :       'width: $width, height: $height, '
    1412            4 :       'horizontalDpi: $horizontalDpi, verticalDpi: $verticalDpi, '
    1413            4 :       'bitsPerPixel: $bitsPerPixel, colorspace: $colorspace, '
    1414            2 :       'markedContentId: $markedContentId)';
    1415              : }
    1416              : 
    1417              : /// A single image object on a PDF page.
    1418              : ///
    1419              : /// Produced by [PdfDocument.extractImages]. [objectIndex] is the stable
    1420              : /// per-page integer position of this object in the page's object list; pass it
    1421              : /// to [PdfDocument.renderImage] to fetch the bitmap on demand.
    1422              : ///
    1423              : /// ## Bitmap fields
    1424              : ///
    1425              : /// [bgra], [bitmapWidth], and [bitmapHeight] are `null` when
    1426              : /// [PdfDocument.extractImages] was called with `includeBitmap: false` (the
    1427              : /// default, metadata-only mode). Use [PdfDocument.renderImage] to retrieve the
    1428              : /// composited BGRA bitmap for a specific image without re-enumerating the page.
    1429              : ///
    1430              : /// When `includeBitmap: true` is passed, all three fields are populated for
    1431              : /// every image that has a renderable bitmap; they remain `null` for mask-only
    1432              : /// or otherwise unrenderable objects (when `FPDFImageObj_GetRenderedBitmap`
    1433              : /// returns null).
    1434              : ///
    1435              : /// ## Image masks
    1436              : ///
    1437              : /// Image mask objects (`bits_per_pixel == 1`) are included in the output and
    1438              : /// are not suppressed automatically. Callers can identify them via
    1439              : /// `metadata.bitsPerPixel == 1`.
    1440              : final class PdfImage {
    1441              :   /// Creates an immutable [PdfImage].
    1442            2 :   const PdfImage({
    1443              :     required this.pageIndex,
    1444              :     required this.objectIndex,
    1445              :     required this.metadata,
    1446              :     required this.bounds,
    1447              :     required this.filters,
    1448              :     this.bgra,
    1449              :     this.bitmapWidth,
    1450              :     this.bitmapHeight,
    1451              :   });
    1452              : 
    1453              :   /// Zero-based index of the page this image belongs to.
    1454              :   final int pageIndex;
    1455              : 
    1456              :   /// Position of this object in the page's object list (zero-based).
    1457              :   ///
    1458              :   /// This index is stable for the lifetime of the open document and can be
    1459              :   /// passed directly to [PdfDocument.renderImage] to fetch the BGRA bitmap
    1460              :   /// on demand.
    1461              :   final int objectIndex;
    1462              : 
    1463              :   /// Source-level metadata for this image (dimensions, DPI, colorspace).
    1464              :   final PdfImageMetadata metadata;
    1465              : 
    1466              :   /// Axis-aligned bounding box of the image in PDF user-space coordinates.
    1467              :   ///
    1468              :   /// Coordinates use the PDF bottom-left origin. The box reflects the image's
    1469              :   /// position and scaling on the page after all transforms are applied.
    1470              :   final PdfRect bounds;
    1471              : 
    1472              :   /// The compression filter names applied to the image data, in order.
    1473              :   ///
    1474              :   /// For example, `['DCTDecode']` indicates JPEG encoding, and
    1475              :   /// `['FlateDecode']` indicates zlib/deflate. An empty list means no filters
    1476              :   /// were found (or the image uses an inline/uncompressed format).
    1477              :   final List<String> filters;
    1478              : 
    1479              :   /// The rendered BGRA pixel bytes, or `null` if the bitmap was not requested.
    1480              :   ///
    1481              :   /// Non-null only when [PdfDocument.extractImages] was called with
    1482              :   /// `includeBitmap: true` and `FPDFImageObj_GetRenderedBitmap` succeeded.
    1483              :   /// The byte length equals [bitmapWidth]! * [bitmapHeight]! * 4.
    1484              :   final Uint8List? bgra;
    1485              : 
    1486              :   /// The rendered pixel width, or `null` if the bitmap was not requested.
    1487              :   ///
    1488              :   /// May differ from [PdfImageMetadata.width] after transforms are applied.
    1489              :   final int? bitmapWidth;
    1490              : 
    1491              :   /// The rendered pixel height, or `null` if the bitmap was not requested.
    1492              :   ///
    1493              :   /// May differ from [PdfImageMetadata.height] after transforms are applied.
    1494              :   final int? bitmapHeight;
    1495              : 
    1496            2 :   @override
    1497              :   bool operator ==(Object other) =>
    1498              :       identical(this, other) ||
    1499            2 :       other is PdfImage &&
    1500            6 :           pageIndex == other.pageIndex &&
    1501            6 :           objectIndex == other.objectIndex &&
    1502            6 :           metadata == other.metadata &&
    1503            6 :           bounds == other.bounds &&
    1504            6 :           _listEqual(filters, other.filters) &&
    1505            6 :           bitmapWidth == other.bitmapWidth &&
    1506            6 :           bitmapHeight == other.bitmapHeight;
    1507              :   // bgra is intentionally excluded from equality to avoid comparing large
    1508              :   // byte buffers by value; callers that need bitmap equality should compare
    1509              :   // the bgra lists directly.
    1510              : 
    1511            2 :   @override
    1512            2 :   int get hashCode => Object.hash(
    1513            2 :     pageIndex,
    1514            2 :     objectIndex,
    1515            2 :     metadata,
    1516            2 :     bounds,
    1517            4 :     Object.hashAll(filters),
    1518            2 :     bitmapWidth,
    1519            2 :     bitmapHeight,
    1520              :   );
    1521              : 
    1522            2 :   @override
    1523            2 :   String toString() =>
    1524              :       'PdfImage('
    1525            4 :       'pageIndex: $pageIndex, objectIndex: $objectIndex, '
    1526            4 :       'metadata: $metadata, bounds: $bounds, '
    1527            2 :       'filters: $filters, '
    1528            4 :       'bitmapWidth: $bitmapWidth, bitmapHeight: $bitmapHeight, '
    1529            5 :       'bgra: ${bgra != null ? '${bgra!.length} bytes' : 'null'})';
    1530              : }
    1531              : 
    1532              : /// A rendered bitmap returned by [PdfDocument.renderImage].
    1533              : ///
    1534              : /// [bgra] is the composited BGRA pixel buffer produced by
    1535              : /// `FPDFImageObj_GetRenderedBitmap`. The rendering includes mask composition
    1536              : /// and transform application, so the output dimensions ([width] × [height])
    1537              : /// may differ from the source image dimensions in [PdfImageMetadata].
    1538              : final class PdfImageBitmap {
    1539              :   /// Creates an immutable [PdfImageBitmap].
    1540            2 :   const PdfImageBitmap({
    1541              :     required this.bgra,
    1542              :     required this.width,
    1543              :     required this.height,
    1544              :   });
    1545              : 
    1546              :   /// Rendered BGRA pixel bytes. Length equals [width] * [height] * 4.
    1547              :   final Uint8List bgra;
    1548              : 
    1549              :   /// Rendered pixel width.
    1550              :   final int width;
    1551              : 
    1552              :   /// Rendered pixel height.
    1553              :   final int height;
    1554              : 
    1555            2 :   @override
    1556              :   bool operator ==(Object other) =>
    1557              :       identical(this, other) ||
    1558           14 :       other is PdfImageBitmap && width == other.width && height == other.height;
    1559              :   // bgra is intentionally excluded from equality; compare lists directly if
    1560              :   // pixel-exact equality is required.
    1561              : 
    1562            2 :   @override
    1563            6 :   int get hashCode => Object.hash(width, height);
    1564              : 
    1565            2 :   @override
    1566            2 :   String toString() =>
    1567            4 :       'PdfImageBitmap(width: $width, height: $height, '
    1568            4 :       'bgra: ${bgra.length} bytes)';
    1569              : }
    1570              : 
    1571              : /// The image objects extracted from a single PDF page.
    1572              : ///
    1573              : /// Produced by [PdfDocument.extractImages]. Each item in the stream
    1574              : /// corresponds to one page. Pages with no image objects emit an entry with an
    1575              : /// empty [images] list, so callers can track page coverage without gaps.
    1576              : final class PdfPageImages {
    1577              :   /// Creates an immutable [PdfPageImages] value.
    1578            2 :   const PdfPageImages({required this.pageIndex, required this.images});
    1579              : 
    1580              :   /// Zero-based index of the page this result corresponds to.
    1581              :   final int pageIndex;
    1582              : 
    1583              :   /// The image objects found on this page, in object-list order.
    1584              :   ///
    1585              :   /// Includes image mask objects (`metadata.bitsPerPixel == 1`); these are
    1586              :   /// not suppressed automatically. An empty list means the page has no image
    1587              :   /// objects.
    1588              :   final List<PdfImage> images;
    1589              : 
    1590            2 :   @override
    1591              :   String toString() =>
    1592            8 :       'PdfPageImages(pageIndex: $pageIndex, images: ${images.length})';
    1593              : }
    1594              : 
    1595              : // ---------------------------------------------------------------------------
    1596              : // Search types
    1597              : // ---------------------------------------------------------------------------
    1598              : 
    1599              : /// Flags that control the text-search behaviour of [PdfDocument.search].
    1600              : ///
    1601              : /// Combine flags using a [Set]:
    1602              : ///
    1603              : /// ```dart
    1604              : /// final matches = doc.search('example',
    1605              : ///   flags: {PdfSearchFlag.matchCase, PdfSearchFlag.matchWholeWord});
    1606              : /// ```
    1607              : ///
    1608              : /// Flag values correspond to the PDFium `FPDF_MATCHCASE`,
    1609              : /// `FPDF_MATCHWHOLEWORD`, and `FPDF_CONSECUTIVE` constants defined in
    1610              : /// `fpdf_text.h`.
    1611              : enum PdfSearchFlag {
    1612              :   /// Case-sensitive matching (`FPDF_MATCHCASE = 0x00000001`).
    1613              :   ///
    1614              :   /// When set, "Apple" does not match "apple". When absent the search is
    1615              :   /// case-insensitive.
    1616              :   matchCase,
    1617              : 
    1618              :   /// Whole-word matching (`FPDF_MATCHWHOLEWORD = 0x00000002`).
    1619              :   ///
    1620              :   /// When set, "art" does not match the substring "art" inside "artist".
    1621              :   matchWholeWord,
    1622              : 
    1623              :   /// Allow overlapping / consecutive matches (`FPDF_CONSECUTIVE = 0x00000004`).
    1624              :   ///
    1625              :   /// When set, searching for "aa" in "aaa" produces two overlapping matches
    1626              :   /// (at index 0 and index 1). When absent each match starts immediately after
    1627              :   /// the previous match ends.
    1628              :   consecutive,
    1629              : }
    1630              : 
    1631              : /// A single text-search match returned by [PdfDocument.search].
    1632              : ///
    1633              : /// Each instance describes one occurrence of the search query on a specific
    1634              : /// page. Multi-line matches (where the matching text wraps across visual rows)
    1635              : /// produce a single [PdfSearchMatch] with multiple entries in [rects] — one
    1636              : /// per visual line fragment.
    1637              : ///
    1638              : /// ## Coordinate system
    1639              : ///
    1640              : /// All coordinates in [rects] are in **PDF user space** (origin bottom-left,
    1641              : /// units in points), consistent with [PdfRect] and page-size coordinates used
    1642              : /// throughout this library. Callers that need screen-space coordinates must
    1643              : /// apply `FPDF_PageToDevice()` / `FPDF_DeviceToPage()` themselves.
    1644              : final class PdfSearchMatch {
    1645              :   /// Creates an immutable [PdfSearchMatch].
    1646            1 :   const PdfSearchMatch({
    1647              :     required this.pageIndex,
    1648              :     required this.charIndex,
    1649              :     required this.charCount,
    1650              :     required this.rects,
    1651              :   });
    1652              : 
    1653              :   /// Zero-based index of the page on which this match was found.
    1654              :   final int pageIndex;
    1655              : 
    1656              :   /// Zero-based character index of the first matched character on this page.
    1657              :   ///
    1658              :   /// This index is relative to the page's text layer, consistent with the
    1659              :   /// character indices used by `FPDFText_GetText` and `FPDFText_GetCharBox`.
    1660              :   final int charIndex;
    1661              : 
    1662              :   /// Number of matched characters.
    1663              :   ///
    1664              :   /// The matched text spans characters `[charIndex, charIndex + charCount)`.
    1665              :   final int charCount;
    1666              : 
    1667              :   /// Bounding rectangles of this match in PDF user-space (origin bottom-left,
    1668              :   /// units in points).
    1669              :   ///
    1670              :   /// A match that spans a single visual line produces one rect. A match that
    1671              :   /// wraps across multiple visual rows produces one rect per row fragment.
    1672              :   /// Callers should treat all rects as fragments that together cover the full
    1673              :   /// extent of this match.
    1674              :   ///
    1675              :   /// Uses [PdfRect] — the same coordinate space as page sizes and annotation
    1676              :   /// bounding boxes throughout this library.
    1677              :   final List<PdfRect> rects;
    1678              : 
    1679            1 :   @override
    1680              :   bool operator ==(Object other) =>
    1681              :       identical(this, other) ||
    1682            1 :       other is PdfSearchMatch &&
    1683            3 :           pageIndex == other.pageIndex &&
    1684            3 :           charIndex == other.charIndex &&
    1685            3 :           charCount == other.charCount &&
    1686            3 :           _listEqual(rects, other.rects);
    1687              : 
    1688            1 :   @override
    1689              :   int get hashCode =>
    1690            6 :       Object.hash(pageIndex, charIndex, charCount, Object.hashAll(rects));
    1691              : 
    1692            1 :   @override
    1693            1 :   String toString() =>
    1694              :       'PdfSearchMatch('
    1695            1 :       'pageIndex: $pageIndex, '
    1696            1 :       'charIndex: $charIndex, '
    1697            1 :       'charCount: $charCount, '
    1698            2 :       'rects: ${rects.length})';
    1699              : }
    1700              : 
    1701              : // ---------------------------------------------------------------------------
    1702              : // Thumbnail types
    1703              : // ---------------------------------------------------------------------------
    1704              : 
    1705              : /// Whether a [PdfThumbnail] came from an embedded stream or was rendered.
    1706              : ///
    1707              : /// Embedded thumbnails are smaller than rendered ones (typically 64–256 px)
    1708              : /// and are returned at their native size. Rendered thumbnails are produced on
    1709              : /// demand by the rendering engine at the caller-controlled `maxDimension`.
    1710              : enum PdfThumbnailSource {
    1711              :   /// Decoded from an embedded `/Thumb` stream in the PDF page dictionary.
    1712              :   ///
    1713              :   /// Not all PDFs contain embedded thumbnails. Modern tools like `pdflatex`
    1714              :   /// and many web-based PDF creators do not produce `/Thumb` streams. When a
    1715              :   /// thumbnail is embedded, [PdfDocument.getThumbnail] returns it at its
    1716              :   /// native dimensions without any scaling.
    1717              :   embedded,
    1718              : 
    1719              :   /// Rendered from the page content at [PdfDocument.getThumbnail]'s
    1720              :   /// `maxDimension` because no embedded thumbnail was present.
    1721              :   ///
    1722              :   /// The rendered thumbnail is produced by the same rendering engine as
    1723              :   /// [PdfDocument.renderPageToBytes]. On high-DPI displays, multiply
    1724              :   /// `maxDimension` by the device pixel ratio before calling to obtain a
    1725              :   /// retina-sharp result.
    1726              :   rendered,
    1727              : }
    1728              : 
    1729              : /// A thumbnail image for a PDF page.
    1730              : ///
    1731              : /// Obtain via [PdfDocument.getThumbnail]. Pixel data is in BGRA format
    1732              : /// (4 bytes per pixel, blue first). The [source] field indicates whether
    1733              : /// the thumbnail was decoded from an embedded stream or synthesised by
    1734              : /// rendering the page.
    1735              : ///
    1736              : /// ## Pixel format
    1737              : ///
    1738              : /// [bgra] is always a compact BGRA buffer: `length == width * height * 4`.
    1739              : /// The bytes are ordered B, G, R, A per pixel, row-major. Row padding from
    1740              : /// the underlying PDFium bitmap is stripped before delivery.
    1741              : ///
    1742              : /// ## Embedded vs rendered thumbnails
    1743              : ///
    1744              : /// Embedded thumbnails ([PdfThumbnailSource.embedded]) are stored directly in
    1745              : /// the PDF and returned at whatever dimensions the authoring tool chose.
    1746              : /// Rendered thumbnails ([PdfThumbnailSource.rendered]) respect `maxDimension`.
    1747              : ///
    1748              : /// Example — converting a [PdfThumbnail] to a Flutter `dart:ui Image`:
    1749              : ///
    1750              : /// ```dart
    1751              : /// final thumb = await doc.getThumbnail(0);
    1752              : /// if (thumb != null) {
    1753              : ///   final codec = await ui.instantiateImageCodec(
    1754              : ///     thumb.bgra,
    1755              : ///     targetWidth: thumb.width,
    1756              : ///     targetHeight: thumb.height,
    1757              : ///   );
    1758              : ///   final frame = await codec.getNextFrame();
    1759              : ///   final image = frame.image;
    1760              : /// }
    1761              : /// ```
    1762              : final class PdfThumbnail {
    1763              :   /// Creates an immutable [PdfThumbnail].
    1764            1 :   const PdfThumbnail({
    1765              :     required this.bgra,
    1766              :     required this.width,
    1767              :     required this.height,
    1768              :     required this.source,
    1769              :   });
    1770              : 
    1771              :   /// BGRA pixel bytes. Length is always [width] * [height] * 4.
    1772              :   ///
    1773              :   /// Bytes are ordered B, G, R, A per pixel, row-major. Row padding from the
    1774              :   /// underlying PDFium bitmap has been stripped; the buffer is compact.
    1775              :   final Uint8List bgra;
    1776              : 
    1777              :   /// Width of the thumbnail in pixels.
    1778              :   final int width;
    1779              : 
    1780              :   /// Height of the thumbnail in pixels.
    1781              :   final int height;
    1782              : 
    1783              :   /// Whether this thumbnail was decoded from an embedded stream or rendered.
    1784              :   final PdfThumbnailSource source;
    1785              : 
    1786            1 :   @override
    1787              :   bool operator ==(Object other) =>
    1788              :       identical(this, other) ||
    1789            1 :       other is PdfThumbnail &&
    1790            3 :           width == other.width &&
    1791            3 :           height == other.height &&
    1792            3 :           source == other.source;
    1793              :   // bgra is intentionally excluded from equality to avoid comparing large
    1794              :   // byte buffers by value. Callers that need pixel-exact equality should
    1795              :   // compare the bgra lists directly.
    1796              : 
    1797            1 :   @override
    1798            4 :   int get hashCode => Object.hash(width, height, source);
    1799              : 
    1800            1 :   @override
    1801            1 :   String toString() =>
    1802            3 :       'PdfThumbnail(width: $width, height: $height, source: $source, '
    1803            2 :       'bgra: ${bgra.length} bytes)';
    1804              : }
    1805              : 
    1806              : // ---------------------------------------------------------------------------
    1807              : // Document info types
    1808              : // ---------------------------------------------------------------------------
    1809              : 
    1810              : /// Document-level properties that are distinct from content metadata.
    1811              : ///
    1812              : /// These are low-level PDF document properties: the PDF file version and the
    1813              : /// two file identifier entries (permanent and changing). They are returned as
    1814              : /// a single batched call to avoid multiple isolate round-trips.
    1815              : ///
    1816              : /// File identifiers are typically 16-byte MD5 hashes. They are returned as
    1817              : /// [Uint8List] (raw bytes) so callers can choose their own encoding (e.g.
    1818              : /// hex string via `hex.encode()`).
    1819              : class PdfDocumentInfo {
    1820              :   /// Creates an immutable [PdfDocumentInfo] value object.
    1821            3 :   const PdfDocumentInfo({this.fileVersion, this.permanentId, this.changingId});
    1822              : 
    1823              :   /// The PDF file version as an integer (e.g. 17 for PDF 1.7), or `null` if
    1824              :   /// the version could not be read.
    1825              :   final int? fileVersion;
    1826              : 
    1827              :   /// The permanent file identifier (typically a 16-byte MD5 hash), or `null`
    1828              :   /// if the document has no file identifier array.
    1829              :   ///
    1830              :   /// The permanent ID is set when the document is first created and does not
    1831              :   /// change across save operations. Callers that need a hex string can use
    1832              :   /// `permanentId?.map((b) => b.toRadixString(16).padLeft(2, '0')).join()`.
    1833              :   final Uint8List? permanentId;
    1834              : 
    1835              :   /// The changing file identifier (typically a 16-byte MD5 hash), or `null`
    1836              :   /// if the document has no file identifier array.
    1837              :   ///
    1838              :   /// The changing ID is updated on each save. Together with [permanentId] it
    1839              :   /// can be used to detect whether a file is a revision of a known document.
    1840              :   final Uint8List? changingId;
    1841              : 
    1842            2 :   @override
    1843              :   String toString() {
    1844            2 :     String? hexEncode(Uint8List? bytes) =>
    1845           10 :         bytes?.map((b) => b.toRadixString(16).padLeft(2, '0')).join();
    1846            2 :     return 'PdfDocumentInfo('
    1847            2 :         'fileVersion: $fileVersion, '
    1848            4 :         'permanentId: ${hexEncode(permanentId)}, '
    1849            4 :         'changingId: ${hexEncode(changingId)}'
    1850              :         ')';
    1851              :   }
    1852              : }
        

Generated by: LCOV version 2.0-1