This guide shows developers how to use PDFluent to manipulate PDF structure and markup. It is for Rust developers working with PDF documents.
Add the pdfluent crate to Cargo.toml.
[dependencies]
pdfluent = "1.0.0"Iterate over every annotation on every page. Read annotation type, author, contents, bounding box, colour, and creation date.
use pdfluent::PdfDocument;
fn main() -> pdfluent::Result<()> {
let doc = PdfDocument::open("reviewed.pdf")?;
for page in 0..doc.page_count() {
for a in doc.annotations(page)? {
println!("{}: {:?}", a.subtype, a.contents);
}
}
Ok(())
}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 in 0..doc.page_count() {
let annotations = doc.annotations(page_idx)?;
println!("Page {} has {} annotations", page_idx + 1, annotations.len());
for ann in &annotations {
println!(" Type: {}", ann.subtype);
println!(" Rect: {:?}", ann.rect);
println!(" Contents: {:?}", ann.contents);
}
}Use annotation_type() to select only the types you care about. AnnotationType is an enum with variants for each standard PDF annotation type.
for page_idx in 0..doc.page_count() {
for ann in doc.annotations(page_idx)? {
if ann.subtype == "Highlight" {
println!("Highlight at {:?}: {}", ann.rect, ann.contents.unwrap_or_default());
}
}
}Collect all annotations into a serialisable struct. This is useful for syncing review comments to an external system.
// AnnotationInfo exposes: subtype, rect (Option<[f64;4]>), contents (Option<String>)
let mut records = Vec::new();
for page_idx in 0..doc.page_count() {
for ann in doc.annotations(page_idx)? {
records.push(format!(
"{{\"page\":{},\"type\":\"{}\",\"contents\":{:?}}}",
page_idx + 1, ann.subtype, ann.contents.unwrap_or_default()
));
}
}
println!("[{}]", records.join(","));Build a nested bookmark outline and attach it to any PDF. Bookmarks appear in the navigation panel of PDF viewers and make long documents easier to navigate.
use pdfluent::PdfDocument;
use pdfluent::structure::Outline;
fn main() -> pdfluent::Result<()> {
let mut doc = PdfDocument::open("report.pdf")?;
doc.set_outlines(&[
Outline::new("Introduction", 0),
Outline::new("Results", 4),
])?;
doc.save("bookmarked.pdf")?;
Ok(())
}Load the PDF you want to add bookmarks to. The document must already exist; bookmarks reference page indices in the file.
use pdfluent::PdfDocument;
let mut doc = PdfDocument::open("manual.pdf")?;
println!("Pages: {}", doc.page_count());Create an Outline and add OutlineItem entries. Each item needs a title and a target page index (zero-based). Nest children with .child().
use pdfluent::structure::Outline;
let mut part1 = Outline::new("Part I: Basics", 2);
part1.children.push(Outline::new("Chapter 1", 2));
part1.children.push(Outline::new("Chapter 2", 6));
let mut part2 = Outline::new("Part II: Advanced", 10);
part2.children.push(Outline::new("Chapter 3", 10));
part2.children.push(Outline::new("Chapter 4", 14));
let outline = vec![
Outline::new("Cover", 0),
Outline::new("Table of Contents", 1),
part1,
part2,
Outline::new("Index", 18),
];Call set_outlines() to replace any existing bookmark tree with the new one. The previous outline is discarded.
doc.set_outlines(&outline)?;Write the PDF with the new bookmark tree to disk.
doc.save("manual_with_bookmarks.pdf")?;
println!("Bookmarks written.");Extract the complete bookmark tree from a PDF. Read titles, page destinations, nesting depth, and link targets for every outline entry.
use pdfluent::PdfDocument;
fn main() -> pdfluent::Result<()> {
let doc = PdfDocument::open("report.pdf")?;
for o in doc.outlines()? {
println!("{} -> page {:?}", o.title, o.page);
}
Ok(())
}A read-only borrow is enough to access the outline.
use pdfluent::PdfDocument;
let doc = PdfDocument::open("ebook.pdf")?;Call doc.outline() which returns an Option<Outline>. It is None if the PDF has no bookmarks.
let outlines = doc.outlines()?;
if outlines.is_empty() {
println!("No bookmarks found in this PDF");
} else {
println!("Document has {} top-level bookmarks", outlines.len());
}Each OutlineItem has a title(), page_index(), and a children() slice. Use a recursive function or a stack to walk the full tree.
use pdfluent::structure::Outline;
fn walk(items: &[Outline], depth: usize) {
for item in items {
let page = item.page.map(|p| p + 1).unwrap_or(0);
println!("{}{} (page {})", " ".repeat(depth), item.title, page);
walk(&item.children, depth + 1);
}
}
walk(&doc.outlines()?, 0);Flatten the nested tree into a Vec for downstream processing such as building a table of contents.
use pdfluent::structure::Outline;
#[derive(Debug)]
struct BookmarkEntry {
title: String,
page: usize,
depth: usize,
}
fn flatten(items: &[Outline], depth: usize, out: &mut Vec<BookmarkEntry>) {
for item in items {
out.push(BookmarkEntry {
title: item.title.clone(),
page: item.page.unwrap_or(0),
depth,
});
flatten(&item.children, depth + 1, out);
}
}
let mut entries = Vec::new();
flatten(&doc.outlines()?, 0, &mut entries);