| name | pdfium-impl-annotations |
| description | 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, cannot create annotation, how do I annotate a PDF
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires pdfium-render 0.8,0.9. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
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> |
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.