Skip to content

Annotation Editing

PDF Oxide provides DOM-level access to annotations through the PdfPage object. You can read existing annotations, add new ones (links, highlights, sticky notes), modify annotation properties, remove annotations, and flatten them into page content.

Binding coverage. DOM-level annotation editing (read / add / modify / remove) is exposed in Python, Rust, and WASM. Reading annotations is also available in Go and C# (doc.Annotations(page) / doc.GetAnnotations(page)). Annotation flattening is exposed across Rust, Python, WASM, Go (editor.FlattenAnnotations / FlattenAllAnnotations) and C# (via FlattenForms for form widgets). Creating or editing non-form annotations from Go or C# currently requires bridging through Rust/Python/WASM.

Getting Annotations

List All Annotations

from pdf_oxide import PdfDocument

doc = PdfDocument("annotated.pdf")
page = doc.page(0)

for ann in page.annotations():
    print(f"Type: {ann.subtype}")
    print(f"Rect: {ann.rect}")
    if ann.contents:
        print(f"Contents: {ann.contents}")
import { WasmPdfDocument } from "pdf-oxide-wasm";

const doc = new WasmPdfDocument(bytes);
const annotations = doc.getAnnotations(0);

for (const ann of annotations) {
  console.log(`Type: ${ann.subtype}`);
  console.log(`Rect: ${JSON.stringify(ann.rect)}`);
  if (ann.contents) {
    console.log(`Contents: ${ann.contents}`);
  }
}
doc.free();
use pdf_oxide::api::Pdf;

let mut doc = Pdf::open("annotated.pdf")?;
let page = doc.page(0)?;

for ann in page.annotations() {
    println!("Type: {:?}", ann.subtype());
    println!("Rect: {:?}", ann.rect());
    if let Some(contents) = ann.contents() {
        println!("Contents: {}", contents);
    }
    if let Some(color) = ann.color() {
        println!("Color: {:?}", color);
    }
}
doc, _ := pdfoxide.Open("annotated.pdf")
defer doc.Close()

anns, _ := doc.Annotations(0)
for _, a := range anns {
    fmt.Printf("Type: %s\nRect: (%.1f, %.1f, %.1f, %.1f)\n",
        a.Subtype, a.X, a.Y, a.Width, a.Height)
    if a.Contents != "" {
        fmt.Printf("Contents: %s\n", a.Contents)
    }
}
using PdfOxide;

using var doc = PdfDocument.Open("annotated.pdf");
foreach (var a in doc.GetAnnotations(0))
{
    Console.WriteLine($"Type: {a.Subtype}");
    Console.WriteLine($"Rect: ({a.X}, {a.Y}, {a.Width}, {a.Height})");
    if (!string.IsNullOrEmpty(a.Contents))
        Console.WriteLine($"Contents: {a.Contents}");
}

Access by Index

let page = doc.page(0)?;

if let Some(ann) = page.annotation(0) {
    println!("First annotation: {:?}", ann.subtype());
}

println!("Total annotations: {}", page.annotation_count());

Find Annotations

By ID

let page = doc.page(0)?;
let id = page.annotations()[0].id();

if let Some(ann) = page.find_annotation(id) {
    println!("Found: {:?}", ann.subtype());
}

By Region

Find annotations within a rectangular area.

use pdf_oxide::geometry::Rect;

let page = doc.page(0)?;
let region = Rect::new(0.0, 700.0, 612.0, 92.0);
let top_annotations = page.find_annotations_in_region(region);

for ann in top_annotations {
    println!("Annotation in header area: {:?}", ann.subtype());
}

By Type

use pdf_oxide::AnnotationSubtype;

let page = doc.page(0)?;
let highlights = page.find_annotations_by_type(AnnotationSubtype::Highlight);
println!("Found {} highlights", highlights.len());

Adding Annotations

doc = PdfDocument("input.pdf")
page = doc.page(0)

# Add a clickable URL link
page.add_link(100, 700, 150, 12, "https://example.com")
doc.save_page(page)
doc.save("with-link.pdf")
use pdf_oxide::api::Pdf;
use pdf_oxide::writer::LinkAnnotation;
use pdf_oxide::geometry::Rect;

let mut doc = Pdf::open("input.pdf")?;
let mut page = doc.page(0)?;

let link = LinkAnnotation::uri(
    Rect::new(100.0, 700.0, 150.0, 12.0),
    "https://example.com"
);
page.add_annotation(link);
doc.save_page(page)?;
doc.save("with-link.pdf")?;

Add a Text Highlight

doc = PdfDocument("input.pdf")
page = doc.page(0)

# Yellow highlight
page.add_highlight(100, 700, 200, 12, (1.0, 1.0, 0.0))
doc.save_page(page)
doc.save("highlighted.pdf")
use pdf_oxide::writer::TextMarkupAnnotation;
use pdf_oxide::TextMarkupType;
use pdf_oxide::geometry::Rect;

let mut doc = Pdf::open("input.pdf")?;
let mut page = doc.page(0)?;

let highlight = TextMarkupAnnotation::from_rect(
    TextMarkupType::Highlight,
    Rect::new(100.0, 700.0, 200.0, 12.0),
).with_color(1.0, 1.0, 0.0);  // Yellow

page.add_annotation(highlight);
doc.save_page(page)?;
doc.save("highlighted.pdf")?;

Add a Sticky Note

doc = PdfDocument("input.pdf")
page = doc.page(0)

page.add_note(50, 750, "Review this section before publishing.")
doc.save_page(page)
doc.save("with-notes.pdf")
use pdf_oxide::writer::TextAnnotation;
use pdf_oxide::geometry::Rect;

let mut doc = Pdf::open("input.pdf")?;
let mut page = doc.page(0)?;

let note = TextAnnotation::new(
    Rect::new(50.0, 750.0, 24.0, 24.0),
    "Review this section before publishing."
);
page.add_annotation(note);
doc.save_page(page)?;
doc.save("with-notes.pdf")?;

Removing Annotations

Remove by Index

doc = PdfDocument("input.pdf")
page = doc.page(0)

# Remove the first annotation
page.remove_annotation(0)
doc.save_page(page)
doc.save("cleaned.pdf")
let mut page = doc.page(0)?;
page.remove_annotation(0);  // Returns Option<AnnotationWrapper>
doc.save_page(page)?;

Remove by ID

let mut page = doc.page(0)?;

// Get the ID of an annotation to remove
let ann_id = page.annotations()[0].id();
page.remove_annotation_by_id(ann_id);

doc.save_page(page)?;
doc.save("cleaned.pdf")?;

Modifying Annotations

Access mutable annotations to change their properties.

let mut page = doc.page(0)?;

// Modify annotations through mutable access
for ann in page.annotations_mut() {
    // Change contents text
    ann.set_contents("Updated comment");

    // Change position
    ann.set_rect(pdf_oxide::geometry::Rect::new(100.0, 700.0, 200.0, 20.0));

    // Change color
    ann.set_color(1.0, 0.0, 0.0);  // Red
}

doc.save_page(page)?;
doc.save("modified.pdf")?;

Modify a Specific Annotation

let mut page = doc.page(0)?;

if let Some(ann) = page.annotation_mut(0) {
    ann.set_contents("First annotation - updated");
}

doc.save_page(page)?;

Mutable Find

let mut page = doc.page(0)?;
let target_id = page.annotations()[0].id();

if let Some(ann) = page.find_annotation_mut(target_id) {
    ann.set_contents("Found and updated");
    ann.set_color(0.0, 1.0, 0.0);  // Green
}

doc.save_page(page)?;

Flattening Annotations

Flattening renders annotation appearance streams into the page content and removes the annotation objects. This makes annotations permanent and non-editable.

Flatten a Single Page

doc = PdfDocument("annotated.pdf")
doc.flatten_page_annotations(0)
doc.save("flat.pdf")
import { WasmPdfDocument } from "pdf-oxide-wasm";

const doc = new WasmPdfDocument(bytes);
doc.flattenPageAnnotations(0);
const output = doc.save();
doc.free();
let mut editor = DocumentEditor::open("annotated.pdf")?;
editor.flatten_page_annotations(0)?;
editor.save("flat.pdf")?;
editor, _ := pdfoxide.OpenEditor("annotated.pdf")
defer editor.Close()

_ = editor.FlattenAnnotations(0)  // page 0
_ = editor.Save("flat.pdf")

Flatten All Annotations

doc = PdfDocument("annotated.pdf")
doc.flatten_all_annotations()
doc.save("flat.pdf")
import { WasmPdfDocument } from "pdf-oxide-wasm";

const doc = new WasmPdfDocument(bytes);
doc.flattenAllAnnotations();
const output = doc.save();
doc.free();
let mut editor = DocumentEditor::open("annotated.pdf")?;
editor.flatten_all_annotations()?;
editor.save("flat.pdf")?;
editor, _ := pdfoxide.OpenEditor("annotated.pdf")
defer editor.Close()

_ = editor.FlattenAllAnnotations()
_ = editor.Save("flat.pdf")

Check and Undo Flatten Marking

doc.flatten_page_annotations(0)
print(doc.is_page_marked_for_flatten(0))  # True

doc.unmark_page_for_flatten(0)
print(doc.is_page_marked_for_flatten(0))  # False
editor.flatten_page_annotations(0)?;
assert!(editor.is_page_marked_for_flatten(0));

editor.unmark_page_for_flatten(0);
assert!(!editor.is_page_marked_for_flatten(0));

Full API Reference

PdfPage Annotation Methods

Method Returns Description
annotations() &[AnnotationWrapper] Get all annotations
annotation(index) Option<&AnnotationWrapper> Get annotation by index
annotations_mut() &mut [AnnotationWrapper] Get mutable annotations
annotation_mut(index) Option<&mut AnnotationWrapper> Get mutable annotation by index
annotation_count() usize Number of annotations
has_annotations_modified() bool Check if annotations were changed
add_annotation(ann) AnnotationId Add a new annotation
remove_annotation(index) Option<AnnotationWrapper> Remove by index
remove_annotation_by_id(id) Option<AnnotationWrapper> Remove by ID
find_annotation(id) Option<&AnnotationWrapper> Find by ID
find_annotation_mut(id) Option<&mut AnnotationWrapper> Find mutable by ID
find_annotations_in_region(rect) Vec<&AnnotationWrapper> Find in area
find_annotations_by_type(subtype) Vec<&AnnotationWrapper> Find by type

AnnotationWrapper Properties

Method Returns Description
id() AnnotationId Unique identifier
subtype() AnnotationSubtype Annotation type
rect() Rect Position and size
contents() Option<&str> Text contents
color() Option<(f32, f32, f32)> RGB color
is_modified() bool Has been changed
is_new() bool Was added (not from source)
set_contents(text) () Set text contents
set_rect(rect) () Set position/size
set_color(r, g, b) () Set RGB color

DocumentEditor Annotation Methods

Method Returns Description
flatten_page_annotations(page) Result<()> Flatten one page
flatten_all_annotations() Result<()> Flatten all pages
is_page_marked_for_flatten(page) bool Check flatten status
unmark_page_for_flatten(page) () Cancel pending flatten

Advanced Example: Annotation Review Workflow

use pdf_oxide::api::Pdf;
use pdf_oxide::writer::{TextAnnotation, TextMarkupAnnotation};
use pdf_oxide::{AnnotationSubtype, TextMarkupType};
use pdf_oxide::geometry::Rect;

let mut doc = Pdf::open("draft.pdf")?;
let count = doc.page_count()?;

for i in 0..count {
    let mut page = doc.page(i)?;

    // Find all text containing "TODO"
    let todos = page.find_text_containing("TODO");
    for t in &todos {
        // Add a highlight over the TODO text
        let highlight = TextMarkupAnnotation::from_rect(
            TextMarkupType::Highlight,
            t.bbox(),
        ).with_color(1.0, 0.5, 0.0);  // Orange
        page.add_annotation(highlight);

        // Add a note next to it
        let note = TextAnnotation::new(
            Rect::new(t.bbox().x - 30.0, t.bbox().y, 24.0, 24.0),
            &format!("TODO found: {}", t.text()),
        );
        page.add_annotation(note);
    }

    doc.save_page(page)?;
}

doc.save("reviewed.pdf")?;