Iterate over every annotation on every page. Read annotation type, author, contents, bounding box, colour, and creation date.
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(())
}Add the pdfluent crate to Cargo.toml.
[dependencies]
pdfluent = "0.9"Open the file as a read-only document. You do not need a mutable reference just to read annotations.
use pdfluent::PdfDocument;
let doc = PdfDocument::open("reviewed_contract.pdf")?;Call annotations() on each page to get a slice of Annotation objects. Each object exposes its type, position, and metadata.
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());
}
}Use annotation_type() to select only the types you care about. AnnotationType is an enum with variants for each standard PDF annotation type.
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(""));
}
}Collect all annotations into a serialisable struct. This is useful for syncing review comments to an external system.
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)?;No JVM, no runtime, no DLL dependencies. Ships as a single native binary or WASM module.
Rust's ownership model prevents buffer overflows and use-after-free. No segfaults in PDF parsing.
Same code runs server-side, in Docker, on AWS Lambda, on Cloudflare Workers, or in the browser via WASM.