# Pdfium Impl Annotations

> 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,

- Skill: `impertio-studio/pdfium-impl-annotations` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add impertio-studio/pdfium-impl-annotations`
- Raw SKILL.md: https://api.skillmd.com/api/skills/impertio-studio/pdfium-impl-annotations/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- License: MIT
- Author: Impertio-Studio (https://skillmd.com/u/impertio-studio)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/impertio-studio/pdfium-impl-annotations

---


# 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.

```rust
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`).

```rust
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.

```rust
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.

