pdfium-impl-annotations
A PDF annotation is a markup object layered on top of a page: a sticky note, a
highlight, a link, a rubber-stamp image, an ink drawing. This skill covers the
full annotation workflow with pdfium-render: enumerating the annotations on a
page, reading their metadata, modifying existing annotations, and creating new
ones.
Scope boundary: form fields (text boxes, checkboxes, radio buttons) are carried
by Widget and XfaWidget annotations but have a separate API surface. This
skill handles the annotation container; for getting and setting form-field
values use pdfium-impl-form-fields.
Default API surface: pdfium-render 0.9.x. Version gates for 0.8.x are flagged
inline and collected in the version table below.
Quick reference
| Goal |
Call |
Returns |
| Read the annotation collection |
page.annotations() |
&PdfPageAnnotations<'a> |
| Get a mutable collection (create or delete) |
page.annotations_mut() |
&mut PdfPageAnnotations<'a> |
| Count annotations |
annotations.len() |
PdfPageAnnotationIndex |
| Iterate all annotations |
annotations.iter() |
PdfPageAnnotationsIterator |
| Get one by index |
annotations.get(index) |
Result<PdfPageAnnotation, PdfiumError> |
| First / last |
annotations.first() / .last() |
Result<PdfPageAnnotation, PdfiumError> |
| Identify the type |
annotation.annotation_type() |
PdfPageAnnotationType |
| Narrow to a concrete type |
annotation.as_highlight_annotation() |
Option<&PdfPageHighlightAnnotation> |
| Narrow mutably |
annotation.as_highlight_annotation_mut() |
Option<&mut PdfPageHighlightAnnotation> |
| Read the note text |
annotation.contents() |
Option<String> |
| Write the note text |
annotation.set_contents("...") |
Result<(), PdfiumError> |
| Read the bounding box |
annotation.bounds() |
Result<PdfRect, PdfiumError> |
| Create a sticky note |
annotations_mut().create_text_annotation("...") |
Result<PdfPageTextAnnotation, PdfiumError> |
| Create a highlight |
annotations_mut().create_highlight_annotation() |
Result<PdfPageHighlightAnnotation, PdfiumError> |
| Create a link |
annotations_mut().create_link_annotation("https://...") |
Result<PdfPageLinkAnnotation, PdfiumError> |
| Delete an annotation |
annotations_mut().delete_annotation(annotation) |
Result<(), PdfiumError> |
Full signatures: references/methods.md. Working code: references/examples.md.
The two collections
PdfPage exposes the annotation collection through two methods. ALWAYS pick
the method that matches the operation:
annotations() returns &PdfPageAnnotations<'a>. Use it for reading only:
iter(), len(), get(), first(), last().
annotations_mut() returns &mut PdfPageAnnotations<'a>. Use it for every
create_*_annotation call and for delete_annotation.
The create_* and delete_annotation methods take &mut self. NEVER call
them on the result of annotations(): the borrow checker rejects it because
that handle is shared and immutable. This is the single most common annotation
mistake. See references/anti-patterns.md.
annotations_mut(), the create_*_annotation family, and delete_annotation
were ADDED in pdfium-render 0.8.20. On 0.8.0 through 0.8.19 the annotation
collection is read-only. The read API (annotations(), iter(), get())
exists since 0.5.6.
The annotation type model
Two distinct types describe an annotation. Do not confuse them:
PdfPageAnnotationType is the raw PDF annotation subtype. It has 29 variants:
Text, Link, FreeText, Line, Square, Circle, Polygon,
Polyline, Highlight, Underline, Squiggly, Strikeout, Stamp,
Caret, Ink, Popup, FileAttachment, Sound, Movie, Widget,
Screen, PrinterMark, TrapNet, Watermark, ThreeD, RichMedia,
XfaWidget, Redacted, and Unknown.
PdfPageAnnotation is the Rust enum you actually pattern-match. It has 16
variants: Circle, FreeText, Highlight, Ink, Link, Popup,
Square, Squiggly, Stamp, Strikeout, Text, Underline, Widget,
XfaWidget, Redacted, and Unsupported.
The enum has fewer variants than the type list. Annotation subtypes pdfium-render
does not model directly (Line, Polygon, Polyline, Caret,
FileAttachment, Sound, Movie, Screen, PrinterMark, TrapNet,
Watermark, ThreeD, RichMedia) all arrive as PdfPageAnnotation::Unsupported.
ALWAYS include a catch-all arm when matching PdfPageAnnotation, and check
annotation.is_supported() before assuming a concrete variant is available.
Decision tree: which operation
Need to work with a page annotation?
|
+-- Just reading metadata or geometry?
| -> page.annotations(), then iter() or get()
| -> read via the PdfPageAnnotationCommon trait
|
+-- Changing an existing annotation (text, position, color, flags)?
| -> page.annotations_mut(), get the annotation
| -> narrow with as_*_annotation_mut()
| -> mutate via PdfPageAnnotationCommon, then save the document
|
+-- Adding a new annotation?
| -> page.annotations_mut().create_*_annotation(...)
| -> set bounds, contents, color on the returned handle
| -> save the document
|
+-- The annotation is Widget or XfaWidget (a form field)?
| -> use annotation.as_form_field() / as_form_field_mut()
| -> see pdfium-impl-form-fields
|
+-- The annotation type is Line/Polygon/Caret/Sound/... ?
-> it is PdfPageAnnotation::Unsupported
-> read annotation_type() for the subtype; concrete editing is unavailable
Pattern: read every annotation on a page
ALWAYS match on the PdfPageAnnotation enum to branch by type, and ALWAYS
include the Unsupported and catch-all arms.
use pdfium_render::prelude::*;
let pdfium = Pdfium::default();
let document = pdfium.load_pdf_from_file("input.pdf", None)?;
for page in document.pages().iter() {
for annotation in page.annotations().iter() {
let kind = annotation.annotation_type();
let contents = annotation.contents().unwrap_or_default();
match annotation {
PdfPageAnnotation::Highlight(_) => println!("highlight: {contents}"),
PdfPageAnnotation::Text(_) => println!("sticky note: {contents}"),
PdfPageAnnotation::Link(_) => println!("link"),
PdfPageAnnotation::Widget(_) => println!("form-field widget"),
PdfPageAnnotation::Unsupported(_) => println!("unsupported: {kind:?}"),
_ => println!("other: {kind:?}"),
}
}
}
The PdfPageAnnotationCommon trait is in the prelude. Its getters
(contents(), name(), creator(), creation_date(), modification_date(),
bounds()) are callable on every PdfPageAnnotation variant.
Pattern: modify an existing annotation
Mutation goes through annotations_mut(), then a mutable narrowing. After any
change, the document must be saved (see pdfium-impl-saving).
let page = document.pages().get(0)?;
let mut annotations = page.annotations_mut();
let mut annotation = annotations.get(0)?;
annotation.set_contents("Reviewed 2026-05-20")?;
annotation.set_bounds(PdfRect::new(
PdfPoints::new(100.0), PdfPoints::new(700.0),
PdfPoints::new(300.0), PdfPoints::new(740.0),
))?;
Pattern: create a new annotation
Every create_*_annotation method returns a typed handle. Set its geometry and
metadata before saving.
let page = document.pages().get(0)?;
let mut annotations = page.annotations_mut();
let mut note = annotations.create_text_annotation("Please confirm this figure")?;
note.set_bounds(PdfRect::new(
PdfPoints::new(72.0), PdfPoints::new(720.0),
PdfPoints::new(96.0), PdfPoints::new(744.0),
))?;
document.save_to_file("annotated.pdf")?;
The constructors on PdfPageAnnotations: create_text_annotation(text),
create_free_text_annotation(text), create_highlight_annotation(),
create_link_annotation(uri), create_ink_annotation(),
create_square_annotation(), create_stamp_annotation(),
create_squiggly_annotation(), create_strikeout_annotation(),
create_underline_annotation(), create_popup_annotation(). Four
object-relative helpers place a markup over an existing page object:
create_highlight_annotation_over_object(),
create_underline_annotation_under_object(),
create_squiggly_annotation_under_object(),
create_strikeout_annotation_through_object(). Full signatures in
references/methods.md.
Bounds use PdfRect, not PdfQuadPoints
PdfPageAnnotationCommon::bounds() returns Result<PdfRect, PdfiumError> and
set_bounds() takes a PdfRect. This differs from PdfPageObject::bounds(),
which returns PdfQuadPoints since 0.8.28. ALWAYS construct a PdfRect for
annotation geometry. The convenience setters set_position(x, y),
set_width(w), and set_height(h) move and resize without building a full
rectangle. All coordinates are PdfPoints (PDF user space, origin bottom-left);
see pdfium-core-coordinates.
Annotation flags
The PdfPageAnnotationCommon trait carries boolean flag accessors, each a
getter and a matching set_ setter:
| Flag getter |
Setter |
Meaning |
is_hidden() |
set_is_hidden() |
annotation is not displayed or printed |
is_printed() |
set_is_printed() |
annotation appears when the page is printed |
is_invisible_if_unsupported() |
set_is_invisible_if_unsupported() |
hide if the viewer cannot render this subtype |
is_printable_but_not_viewable() |
set_is_printable_but_not_viewable() |
print only, not shown on screen |
is_read_only() |
set_is_read_only() |
user cannot interact with it |
is_locked() |
set_is_locked() |
annotation cannot be deleted or moved |
is_editable() |
set_is_editable() |
content may be changed |
is_zoomable() |
set_is_zoomable() |
scales with page zoom |
is_rotatable() |
set_is_rotatable() |
rotates with the page |
These flag accessors were ADDED in pdfium-render 0.8.34. On earlier 0.8.x
releases they do not exist.
Stamp and ink annotation content
PdfPageStampAnnotation and PdfPageInkAnnotation each expose
objects_mut(), returning &mut PdfPageAnnotationObjects<'a>. That collection
implements PdfPageObjectsCommon, so a stamp's visible content is built by
adding page objects: create_image_object(...), create_path_object_rect(...),
create_text_object(...), or add_object(...). An empty stamp annotation
renders nothing until at least one object is added.
Version table
| Item |
0.8.x |
0.9.x |
annotations(), iter(), get() (read) |
present since 0.5.6 |
present |
annotations_mut(), create_*_annotation, delete_annotation |
ADDED 0.8.20 |
present |
| Annotation flag getters and setters |
ADDED 0.8.34 |
present |
PdfPageObject::bounds() return type (contrast) |
PdfQuadPoints since 0.8.28 |
PdfQuadPoints |
Annotation bounds() return type |
PdfRect |
PdfRect |
| Lifetime handling on annotation handles |
stricter |
simplified in 0.9.0 |
Send / Sync on annotation instances |
not implemented |
implemented in 0.9.0 |
Critical rules
- ALWAYS call
annotations_mut() (not annotations()) before any
create_*_annotation or delete_annotation call.
- ALWAYS save the document with
pdfium-impl-saving after creating, modifying,
or deleting an annotation. In-memory changes are lost otherwise.
- ALWAYS include an
Unsupported arm and a catch-all _ arm when matching the
PdfPageAnnotation enum.
- NEVER assume a
PdfPageAnnotationType value maps to a concrete
PdfPageAnnotation enum variant. Thirteen subtypes resolve to Unsupported.
- NEVER use
as_text_annotation() on a Widget or XfaWidget annotation to
reach a form field. Use as_form_field() and pdfium-impl-form-fields.
- For pdfium-render 0.8.0 through 0.8.19, treat annotations as read-only;
creation requires 0.8.20 or later.
Companion skills
pdfium-impl-form-fields for Widget and XfaWidget annotations.
pdfium-impl-saving for persisting annotation changes.
pdfium-core-coordinates for PdfRect, PdfPoints, and the PDF origin.
pdfium-syntax-pages for reaching a PdfPage from a document.
pdfium-errors-runtime for PdfiumError handling.
Reference files
references/methods.md : complete API signatures with version annotations.
references/examples.md : working, verified Rust examples.
references/anti-patterns.md : real failures, why they happen, and the fix.
1---2name: pdfium-impl-annotations3description: Use when reading, modifying, or creating PDF annotations with pdfium-render: text notes, highlights, links, stamps, ink strokes, squares, and free text. Prevents the read-only-collection mistake (calling a create method on annotations() instead of annotations_mut()), the annotation-type confusion (assuming every PdfPageAnnotationType has a matching PdfPageAnnotation enum variant), and the bounds-type mix-up (annotation bounds are PdfRect, not PdfQuadPoints). Covers PdfPageAnnotations, the PdfPageAnnotation enum, the PdfPageAnnotationCommon trait, PdfPageAnnotationType, annotation flags, and the version gates at 0.8.20 and 0.8.34. Keywords: pdfium-render annotations, PdfPageAnnotations, PdfPageAnnotation, PdfPageAnnotationCommon, PdfPageAnnotationType, create_highlight_annotation, create_text_annotation, create_link_annotation, create_stamp_annotation, annotation flags, is_hidden, set_bounds, annotation contents, add a comment to a PDF, highlight text in a PDF, annotation not showing, annotations is immutable,4license: MIT5---67# pdfium-impl-annotations89A PDF annotation is a markup object layered on top of a page: a sticky note, a10highlight, a link, a rubber-stamp image, an ink drawing. This skill covers the11full annotation workflow with pdfium-render: enumerating the annotations on a12page, reading their metadata, modifying existing annotations, and creating new13ones.1415Scope boundary: form fields (text boxes, checkboxes, radio buttons) are carried16by `Widget` and `XfaWidget` annotations but have a separate API surface. This17skill handles the annotation container; for getting and setting form-field18values use `pdfium-impl-form-fields`.1920Default API surface: pdfium-render 0.9.x. Version gates for 0.8.x are flagged21inline and collected in the version table below.2223## Quick reference2425| Goal | Call | Returns |26|------|------|---------|27| Read the annotation collection | `page.annotations()` | `&PdfPageAnnotations<'a>` |28| Get a mutable collection (create or delete) | `page.annotations_mut()` | `&mut PdfPageAnnotations<'a>` |29| Count annotations | `annotations.len()` | `PdfPageAnnotationIndex` |30| Iterate all annotations | `annotations.iter()` | `PdfPageAnnotationsIterator` |31| Get one by index | `annotations.get(index)` | `Result<PdfPageAnnotation, PdfiumError>` |32| First / last | `annotations.first()` / `.last()` | `Result<PdfPageAnnotation, PdfiumError>` |33| Identify the type | `annotation.annotation_type()` | `PdfPageAnnotationType` |34| Narrow to a concrete type | `annotation.as_highlight_annotation()` | `Option<&PdfPageHighlightAnnotation>` |35| Narrow mutably | `annotation.as_highlight_annotation_mut()` | `Option<&mut PdfPageHighlightAnnotation>` |36| Read the note text | `annotation.contents()` | `Option<String>` |37| Write the note text | `annotation.set_contents("...")` | `Result<(), PdfiumError>` |38| Read the bounding box | `annotation.bounds()` | `Result<PdfRect, PdfiumError>` |39| Create a sticky note | `annotations_mut().create_text_annotation("...")` | `Result<PdfPageTextAnnotation, PdfiumError>` |40| Create a highlight | `annotations_mut().create_highlight_annotation()` | `Result<PdfPageHighlightAnnotation, PdfiumError>` |41| Create a link | `annotations_mut().create_link_annotation("https://...")` | `Result<PdfPageLinkAnnotation, PdfiumError>` |42| Delete an annotation | `annotations_mut().delete_annotation(annotation)` | `Result<(), PdfiumError>` |4344Full signatures: `references/methods.md`. Working code: `references/examples.md`.4546## The two collections4748`PdfPage` exposes the annotation collection through two methods. ALWAYS pick49the method that matches the operation:5051- `annotations()` returns `&PdfPageAnnotations<'a>`. Use it for reading only:52 `iter()`, `len()`, `get()`, `first()`, `last()`.53- `annotations_mut()` returns `&mut PdfPageAnnotations<'a>`. Use it for every54 `create_*_annotation` call and for `delete_annotation`.5556The `create_*` and `delete_annotation` methods take `&mut self`. NEVER call57them on the result of `annotations()`: the borrow checker rejects it because58that handle is shared and immutable. This is the single most common annotation59mistake. See `references/anti-patterns.md`.6061`annotations_mut()`, the `create_*_annotation` family, and `delete_annotation`62were ADDED in pdfium-render 0.8.20. On 0.8.0 through 0.8.19 the annotation63collection is read-only. The read API (`annotations()`, `iter()`, `get()`)64exists since 0.5.6.6566## The annotation type model6768Two distinct types describe an annotation. Do not confuse them:6970- `PdfPageAnnotationType` is the raw PDF annotation subtype. It has 29 variants:71 `Text`, `Link`, `FreeText`, `Line`, `Square`, `Circle`, `Polygon`,72 `Polyline`, `Highlight`, `Underline`, `Squiggly`, `Strikeout`, `Stamp`,73 `Caret`, `Ink`, `Popup`, `FileAttachment`, `Sound`, `Movie`, `Widget`,74 `Screen`, `PrinterMark`, `TrapNet`, `Watermark`, `ThreeD`, `RichMedia`,75 `XfaWidget`, `Redacted`, and `Unknown`.76- `PdfPageAnnotation` is the Rust enum you actually pattern-match. It has 1677 variants: `Circle`, `FreeText`, `Highlight`, `Ink`, `Link`, `Popup`,78 `Square`, `Squiggly`, `Stamp`, `Strikeout`, `Text`, `Underline`, `Widget`,79 `XfaWidget`, `Redacted`, and `Unsupported`.8081The enum has fewer variants than the type list. Annotation subtypes pdfium-render82does not model directly (`Line`, `Polygon`, `Polyline`, `Caret`,83`FileAttachment`, `Sound`, `Movie`, `Screen`, `PrinterMark`, `TrapNet`,84`Watermark`, `ThreeD`, `RichMedia`) all arrive as `PdfPageAnnotation::Unsupported`.85ALWAYS include a catch-all arm when matching `PdfPageAnnotation`, and check86`annotation.is_supported()` before assuming a concrete variant is available.8788## Decision tree: which operation8990```91Need to work with a page annotation?92|93+-- Just reading metadata or geometry?94| -> page.annotations(), then iter() or get()95| -> read via the PdfPageAnnotationCommon trait96|97+-- Changing an existing annotation (text, position, color, flags)?98| -> page.annotations_mut(), get the annotation99| -> narrow with as_*_annotation_mut()100| -> mutate via PdfPageAnnotationCommon, then save the document101|102+-- Adding a new annotation?103| -> page.annotations_mut().create_*_annotation(...)104| -> set bounds, contents, color on the returned handle105| -> save the document106|107+-- The annotation is Widget or XfaWidget (a form field)?108| -> use annotation.as_form_field() / as_form_field_mut()109| -> see pdfium-impl-form-fields110|111+-- The annotation type is Line/Polygon/Caret/Sound/... ?112 -> it is PdfPageAnnotation::Unsupported113 -> read annotation_type() for the subtype; concrete editing is unavailable114```115116## Pattern: read every annotation on a page117118ALWAYS match on the `PdfPageAnnotation` enum to branch by type, and ALWAYS119include the `Unsupported` and catch-all arms.120121```rust122use pdfium_render::prelude::*;123124let pdfium = Pdfium::default();125let document = pdfium.load_pdf_from_file("input.pdf", None)?;126127for page in document.pages().iter() {128 for annotation in page.annotations().iter() {129 let kind = annotation.annotation_type();130 let contents = annotation.contents().unwrap_or_default();131 match annotation {132 PdfPageAnnotation::Highlight(_) => println!("highlight: {contents}"),133 PdfPageAnnotation::Text(_) => println!("sticky note: {contents}"),134 PdfPageAnnotation::Link(_) => println!("link"),135 PdfPageAnnotation::Widget(_) => println!("form-field widget"),136 PdfPageAnnotation::Unsupported(_) => println!("unsupported: {kind:?}"),137 _ => println!("other: {kind:?}"),138 }139 }140}141```142143The `PdfPageAnnotationCommon` trait is in the prelude. Its getters144(`contents()`, `name()`, `creator()`, `creation_date()`, `modification_date()`,145`bounds()`) are callable on every `PdfPageAnnotation` variant.146147## Pattern: modify an existing annotation148149Mutation goes through `annotations_mut()`, then a mutable narrowing. After any150change, the document must be saved (see `pdfium-impl-saving`).151152```rust153let page = document.pages().get(0)?;154let mut annotations = page.annotations_mut();155let mut annotation = annotations.get(0)?;156157annotation.set_contents("Reviewed 2026-05-20")?;158annotation.set_bounds(PdfRect::new(159 PdfPoints::new(100.0), PdfPoints::new(700.0),160 PdfPoints::new(300.0), PdfPoints::new(740.0),161))?;162```163164## Pattern: create a new annotation165166Every `create_*_annotation` method returns a typed handle. Set its geometry and167metadata before saving.168169```rust170let page = document.pages().get(0)?;171let mut annotations = page.annotations_mut();172173let mut note = annotations.create_text_annotation("Please confirm this figure")?;174note.set_bounds(PdfRect::new(175 PdfPoints::new(72.0), PdfPoints::new(720.0),176 PdfPoints::new(96.0), PdfPoints::new(744.0),177))?;178179document.save_to_file("annotated.pdf")?;180```181182The constructors on `PdfPageAnnotations`: `create_text_annotation(text)`,183`create_free_text_annotation(text)`, `create_highlight_annotation()`,184`create_link_annotation(uri)`, `create_ink_annotation()`,185`create_square_annotation()`, `create_stamp_annotation()`,186`create_squiggly_annotation()`, `create_strikeout_annotation()`,187`create_underline_annotation()`, `create_popup_annotation()`. Four188object-relative helpers place a markup over an existing page object:189`create_highlight_annotation_over_object()`,190`create_underline_annotation_under_object()`,191`create_squiggly_annotation_under_object()`,192`create_strikeout_annotation_through_object()`. Full signatures in193`references/methods.md`.194195## Bounds use PdfRect, not PdfQuadPoints196197`PdfPageAnnotationCommon::bounds()` returns `Result<PdfRect, PdfiumError>` and198`set_bounds()` takes a `PdfRect`. This differs from `PdfPageObject::bounds()`,199which returns `PdfQuadPoints` since 0.8.28. ALWAYS construct a `PdfRect` for200annotation geometry. The convenience setters `set_position(x, y)`,201`set_width(w)`, and `set_height(h)` move and resize without building a full202rectangle. All coordinates are `PdfPoints` (PDF user space, origin bottom-left);203see `pdfium-core-coordinates`.204205## Annotation flags206207The `PdfPageAnnotationCommon` trait carries boolean flag accessors, each a208getter and a matching `set_` setter:209210| Flag getter | Setter | Meaning |211|-------------|--------|---------|212| `is_hidden()` | `set_is_hidden()` | annotation is not displayed or printed |213| `is_printed()` | `set_is_printed()` | annotation appears when the page is printed |214| `is_invisible_if_unsupported()` | `set_is_invisible_if_unsupported()` | hide if the viewer cannot render this subtype |215| `is_printable_but_not_viewable()` | `set_is_printable_but_not_viewable()` | print only, not shown on screen |216| `is_read_only()` | `set_is_read_only()` | user cannot interact with it |217| `is_locked()` | `set_is_locked()` | annotation cannot be deleted or moved |218| `is_editable()` | `set_is_editable()` | content may be changed |219| `is_zoomable()` | `set_is_zoomable()` | scales with page zoom |220| `is_rotatable()` | `set_is_rotatable()` | rotates with the page |221222These flag accessors were ADDED in pdfium-render 0.8.34. On earlier 0.8.x223releases they do not exist.224225## Stamp and ink annotation content226227`PdfPageStampAnnotation` and `PdfPageInkAnnotation` each expose228`objects_mut()`, returning `&mut PdfPageAnnotationObjects<'a>`. That collection229implements `PdfPageObjectsCommon`, so a stamp's visible content is built by230adding page objects: `create_image_object(...)`, `create_path_object_rect(...)`,231`create_text_object(...)`, or `add_object(...)`. An empty stamp annotation232renders nothing until at least one object is added.233234## Version table235236| Item | 0.8.x | 0.9.x |237|------|-------|-------|238| `annotations()`, `iter()`, `get()` (read) | present since 0.5.6 | present |239| `annotations_mut()`, `create_*_annotation`, `delete_annotation` | ADDED 0.8.20 | present |240| Annotation flag getters and setters | ADDED 0.8.34 | present |241| `PdfPageObject::bounds()` return type (contrast) | `PdfQuadPoints` since 0.8.28 | `PdfQuadPoints` |242| Annotation `bounds()` return type | `PdfRect` | `PdfRect` |243| Lifetime handling on annotation handles | stricter | simplified in 0.9.0 |244| `Send` / `Sync` on annotation instances | not implemented | implemented in 0.9.0 |245246## Critical rules247248- ALWAYS call `annotations_mut()` (not `annotations()`) before any249 `create_*_annotation` or `delete_annotation` call.250- ALWAYS save the document with `pdfium-impl-saving` after creating, modifying,251 or deleting an annotation. In-memory changes are lost otherwise.252- ALWAYS include an `Unsupported` arm and a catch-all `_` arm when matching the253 `PdfPageAnnotation` enum.254- NEVER assume a `PdfPageAnnotationType` value maps to a concrete255 `PdfPageAnnotation` enum variant. Thirteen subtypes resolve to `Unsupported`.256- NEVER use `as_text_annotation()` on a `Widget` or `XfaWidget` annotation to257 reach a form field. Use `as_form_field()` and `pdfium-impl-form-fields`.258- For pdfium-render 0.8.0 through 0.8.19, treat annotations as read-only;259 creation requires 0.8.20 or later.260261## Companion skills262263- `pdfium-impl-form-fields` for `Widget` and `XfaWidget` annotations.264- `pdfium-impl-saving` for persisting annotation changes.265- `pdfium-core-coordinates` for `PdfRect`, `PdfPoints`, and the PDF origin.266- `pdfium-syntax-pages` for reaching a `PdfPage` from a document.267- `pdfium-errors-runtime` for `PdfiumError` handling.268269## Reference files270271- `references/methods.md` : complete API signatures with version annotations.272- `references/examples.md` : working, verified Rust examples.273- `references/anti-patterns.md` : real failures, why they happen, and the fix.