How-to guides/Document Structure

Read the bookmark outline from a PDF in Rust

Extract the complete bookmark tree from a PDF. Read titles, page destinations, nesting depth, and link targets for every outline entry.

rust
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(())
}

Step by step

1

Add PDFluent to your project

Add the pdfluent crate to Cargo.toml.

rust
[dependencies]
pdfluent = "1.0.0-beta.8"
2

Open the PDF

A read-only borrow is enough to access the outline.

rust
use pdfluent::PdfDocument;

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

Access the outline

Call doc.outline() which returns an Option<Outline>. It is None if the PDF has no bookmarks.

rust
let outlines = doc.outlines()?;

if outlines.is_empty() {
    println!("No bookmarks found in this PDF");
} else {
    println!("Document has {} top-level bookmarks", outlines.len());
}
4

Traverse the bookmark tree

Each OutlineItem has a title(), page_index(), and a children() slice. Use a recursive function or a stack to walk the full tree.

rust
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);
5

Export the outline to a flat list

Flatten the nested tree into a Vec for downstream processing such as building a table of contents.

rust
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);

Notes and tips

  • page_index() returns a zero-based index. Add 1 to convert to the human-readable page number.
  • Some bookmarks point to named destinations rather than a direct page index. Use item.destination() to read those.
  • Bookmarks can also link to external URIs or other documents. Check item.action_type() before assuming a page target.
  • outline() returns None for PDFs without an /Outlines entry in the document catalog.

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