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(())
}Add the pdfluent crate to Cargo.toml.
[dependencies]
pdfluent = "1.0.0-beta.8"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);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.