How-to guides/Annotations

Read and parse annotations from a PDF in Rust

Iterate over every annotation on every page. Read annotation type, author, contents, bounding box, colour, and creation date.

rust
use pdfluent::PdfDocument;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let doc = PdfDocument::open("reviewed.pdf")?;

    for (page_idx, page) in doc.pages().enumerate() {
        for ann in page.annotations() {
            println!(
                "Page {}: [{:?}] by {} - {}",
                page_idx + 1,
                ann.annotation_type(),
                ann.author().unwrap_or("unknown"),
                ann.contents().unwrap_or(""),
            );
        }
    }
    Ok(())
}
Install:cargo add pdfluentDownload SDK →

Step by step

1

Add PDFluent to your project

Add the pdfluent crate to Cargo.toml.

rust
[dependencies]
pdfluent = "0.9"
2

Open the PDF

Open the file as a read-only document. You do not need a mutable reference just to read annotations.

rust
use pdfluent::PdfDocument;

let doc = PdfDocument::open("reviewed_contract.pdf")?;
3

Iterate over pages and annotations

Call annotations() on each page to get a slice of Annotation objects. Each object exposes its type, position, and metadata.

rust
for (page_idx, page) in doc.pages().enumerate() {
    let annotations = page.annotations();
    println!("Page {} has {} annotations", page_idx + 1, annotations.len());

    for ann in annotations {
        println!("  Type: {:?}", ann.annotation_type());
        println!("  Rect: {:?}", ann.rect());
        println!("  Author: {:?}", ann.author());
        println!("  Contents: {:?}", ann.contents());
        println!("  Created: {:?}", ann.creation_date());
    }
}
4

Filter by annotation type

Use annotation_type() to select only the types you care about. AnnotationType is an enum with variants for each standard PDF annotation type.

rust
use pdfluent::AnnotationType;

for page in doc.pages() {
    let highlights: Vec<_> = page
        .annotations()
        .iter()
        .filter(|a| a.annotation_type() == AnnotationType::Highlight)
        .collect();

    for h in highlights {
        println!("Highlight at {:?}: {}", h.rect(), h.contents().unwrap_or(""));
    }
}
5

Export annotations to JSON

Collect all annotations into a serialisable struct. This is useful for syncing review comments to an external system.

rust
use serde::Serialize;

#[derive(Serialize)]
struct AnnotationRecord {
    page: usize,
    kind: String,
    author: String,
    contents: String,
    x: f32,
    y: f32,
}

let mut records: Vec<AnnotationRecord> = Vec::new();

for (i, page) in doc.pages().enumerate() {
    for ann in page.annotations() {
        records.push(AnnotationRecord {
            page: i + 1,
            kind: format!("{:?}", ann.annotation_type()),
            author: ann.author().unwrap_or("").to_string(),
            contents: ann.contents().unwrap_or("").to_string(),
            x: ann.rect().x1,
            y: ann.rect().y1,
        });
    }
}

let json = serde_json::to_string_pretty(&records)?;
std::fs::write("annotations.json", json)?;

Notes and tips

  • annotations() returns an empty slice on pages with no annotations. No error is raised.
  • FreeText annotations may have content rendered on the page. The text is also available via contents().
  • Ink annotations have a list of point paths instead of a single rect. Use ann.ink_paths() to read them.
  • Reply annotations (threaded comments) expose reply_to() which returns the ID of the parent annotation.

Why PDFluent for this

Pure Rust

No JVM, no runtime, no DLL dependencies. Ships as a single native binary or WASM module.

Memory safe

Rust's ownership model prevents buffer overflows and use-after-free. No segfaults in PDF parsing.

Runs anywhere

Same code runs server-side, in Docker, on AWS Lambda, on Cloudflare Workers, or in the browser via WASM.

Frequently asked questions