| name | pdfium-impl-navigation |
| description | Use when reading the navigation structure of a PDF with pdfium-render: the bookmark outline tree, the clickable links on a page, the destinations links and bookmarks point at, and the action objects behind them. Prevents the wrong-method mistakes link.bounds() (the real name is link.rect()) and destination.view() (renamed to view_settings()), the missing-method assumption (PdfBookmarks has no len() or is_empty()), and the non-exhaustive match on the PdfAction and PdfActionType enums. Covers PdfBookmarks, PdfBookmark, PdfPageLinks, PdfLink, PdfDestination, PdfDestinationViewSettings, PdfAction, PdfActionType, and PdfActionUri. Keywords: pdfium-render bookmarks, PDF outline tree, PdfBookmarks, PdfBookmark, PdfPageLinks, PdfLink, PdfDestination, PdfAction, PdfActionUri, link.rect, view_settings, find_first_by_title, link_at_point, read PDF links, extract PDF table of contents, where does this link go, traverse bookmark tree, get URL from PDF link
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires pdfium-render 0.8,0.9. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
pdfium-impl-navigation
This skill covers reading the navigation structure of a PDF with pdfium-render.
Four cooperating concepts:
- Bookmarks: the document outline tree (the table of contents a viewer shows in
its side panel).
- Links: the clickable rectangles on a page.
- Destinations: a target page plus a view setting (where a link or bookmark
jumps to).
- Actions: what happens on interaction (jump to a page, open a URL, launch a
file).
The whole cluster is read-oriented. PdfDocument::bookmarks() returns an
immutable reference and there is no bookmarks_mut(); this skill reads the
navigation structure rather than building it.
Default API surface: pdfium-render 0.9.x. Version notes for 0.8.x are inline
and collected in the version table.
Quick reference
| Goal | Call | Returns |
|---|
| Get the bookmark collection | document.bookmarks() | &PdfBookmarks |
| Get the root bookmark | bookmarks.root() | Option<PdfBookmark> |
| Walk every bookmark | bookmarks.iter() | PdfBookmarksIterator |
| Find a bookmark by title | bookmarks.find_first_by_title("Intro") | Result<PdfBookmark, PdfiumError> |
| Read a bookmark title | bookmark.title() | Option<String> |
| Bookmark target page | bookmark.destination() | Option<PdfDestination> |
| Bookmark child count | bookmark.children_len() | usize |
| Get the link collection of a page | page.links() | &PdfPageLinks |
| Count links | links.len() | PdfPageLinkIndex |
| Walk every link | links.iter() | PdfPageLinksIterator |
| Hit-test a coordinate | links.link_at_point(x, y) | Option<PdfLink> |
| Clickable rectangle of a link | link.rect() | Result<PdfRect, PdfiumError> |
| Link action | link.action() | Option<PdfAction> |
| Link target | link.destination() | Option<PdfDestination> |
| Destination target page | destination.page_index() | Result<PdfPageIndex, PdfiumError> |
| Destination view mode | destination.view_settings() |
Full signatures: references/methods.md. Working code: references/examples.md.
Bookmarks: the outline tree
document.bookmarks() returns &PdfBookmarks. The collection has four methods:
root(), find_first_by_title(), find_all_by_title(), and iter().
PdfBookmarks has NO len() and NO is_empty() method. To count bookmarks,
use bookmarks.iter().count(). To test for an empty outline, use
bookmarks.root().is_none().
bookmarks.iter() walks the entire tree depth-first in prefix order, which is
the simplest way to read every entry. For structural traversal, a PdfBookmark
exposes parent(), first_child(), next_sibling(), children_len(), and
three sub-iterators: iter_siblings(), iter_direct_children(), and
iter_all_descendants().
A bookmark's target is reached two ways. bookmark.destination() returns an
Option<PdfDestination> for a direct page target. bookmark.action() returns
an Option<PdfAction> for an action-based target (a URL, a remote file). ALWAYS
check both: a bookmark that opens a URL has an action() but no destination().
Links: the clickable rectangles on a page
page.links() returns &PdfPageLinks. page.links_mut() returns a mutable
reference; both exist since 0.7.31.
PdfPageLinks has len(), is_empty(), get(), first(), last(), iter(),
as_range(), as_range_inclusive(), and link_at_point(x, y) for hit-testing
a coordinate.
A PdfLink exposes exactly three methods: action(), destination(), and
rect(). The clickable area is rect(), returning Result<PdfRect, PdfiumError>.
The rect-not-bounds trap
The link's clickable area method is named rect(), not bounds(), and it
returns PdfRect, not PdfQuadPoints. This differs from
PdfPageObject::bounds(), which returns PdfQuadPoints since 0.8.28. ALWAYS
call link.rect(). There is no link.bounds().
Destinations: target page plus view
A PdfDestination has exactly two methods:
page_index() returns Result<PdfPageIndex, PdfiumError>, the zero-based
target page index.
view_settings() returns Result<PdfDestinationViewSettings, PdfiumError>,
the zoom and fit mode the viewer applies on arrival.
The view-settings-not-view trap
In pdfium-render 0.8.10 this method was named view(). It was RENAMED to
view_settings(). The current 0.8.x and 0.9.x API has only view_settings().
ALWAYS call view_settings(). Treat view() as a removed older name.
PdfDestinationViewSettings is an enum with 9 variants: Unknown,
SpecificCoordinatesAndZoom, FitPageToWindow, FitPageHorizontallyToWindow,
FitPageVerticallyToWindow, FitPageToRectangle, FitBoundsToWindow,
FitBoundsHorizontallyToWindow, and FitBoundsVerticallyToWindow. Several
carry data (coordinates, a zoom factor, a rectangle). See references/methods.md.
Actions: what happens on interaction
PdfAction is an enum with 6 variants. ALWAYS match it with an Unsupported
arm and a catch-all:
| Variant | Wrapped type | Meaning |
|---|
LocalDestination | PdfActionLocalDestination | jump within this document |
RemoteDestination | PdfActionRemoteDestination | jump into another PDF file |
EmbeddedDestination | PdfActionEmbeddedDestination | jump into an embedded PDF |
Launch | PdfActionLaunch | launch an external file or application |
Uri | PdfActionUri | open a URL |
Unsupported | PdfActionUnsupported | an action type pdfium-render does not model |
PdfAction methods: action_type() returns a PdfActionType (6 variants:
GoToDestinationInSameDocument, GoToDestinationInRemoteDocument,
GoToDestinationInEmbeddedDocument, Launch, Uri, Unsupported),
is_supported(), is_unsupported(), and five as_*_action() narrowing
methods, each with a _mut companion.
Verified data accessors on the subtype structs:
PdfActionUri::uri() returns Result<String, PdfiumError>, the target URL
(read as 7-bit ASCII). Added 0.7.31.
PdfActionLocalDestination::destination() returns
Result<PdfDestination, PdfiumError>.
PdfActionLaunch, PdfActionRemoteDestination, PdfActionEmbeddedDestination,
and PdfActionUnsupported have NO public methods of their own in 0.8.x or
0.9.x. Do not expect a file_path() or similar accessor.
PdfActionCommon is a real public trait but is currently empty (reserved for
future expansion). Do not look for methods on it.
PdfAction has NO bookmark() or link() back-reference method. An action
does not know which link or bookmark owns it.
Decision tree
Reading PDF navigation structure?
|
+-- Need the table of contents / outline?
| -> document.bookmarks().iter(), read title() + destination()
|
+-- Need a specific bookmark by name?
| -> bookmarks.find_first_by_title("...") or find_all_by_title("...")
|
+-- Need every clickable link on a page?
| -> page.links().iter()
|
+-- Need the link under a known x,y coordinate?
| -> page.links().link_at_point(x, y)
|
+-- Have a link or bookmark, need where it goes?
| -> try .destination() for a direct page target
| -> try .action() and match the PdfAction enum
|
+-- Have a PdfAction, need the URL?
-> match PdfActionType::Uri, narrow with as_uri_action(), call uri()
Pattern: traverse the bookmark tree
use pdfium_render::prelude::*;
let pdfium = Pdfium::default();
let document = pdfium.load_pdf_from_file("input.pdf", None)?;
for bookmark in document.bookmarks().iter() {
let title = bookmark.title().unwrap_or_default();
match bookmark.destination() {
Some(destination) => {
let page = destination.page_index()?;
println!("{title} -> page {page}");
}
None => println!("{title} -> (action or no target)"),
}
}
bookmarks.iter() is depth-first prefix order, so the output mirrors the
visual outline.
Pattern: read links and branch on action type
for (page_index, page) in document.pages().iter().enumerate() {
for link in page.links().iter() {
let Some(action) = link.action() else { continue };
match action.action_type() {
PdfActionType::Uri => {
if let Some(uri) = action.as_uri_action() {
println!("page {page_index}: URL {}", uri.uri()?);
}
}
PdfActionType::GoToDestinationInSameDocument => {
if let Some(local) = action.as_local_destination_action() {
let target = local.destination()?.page_index()?;
println!("page {page_index}: jump to page {target}");
}
}
other => println!("page {page_index}: {other:?}"),
}
}
}
Version table
| Item | 0.8.x | 0.9.x |
|---|
PdfBookmarks, PdfBookmark | present (since 0.5.3) | present |
PdfPageLinks, PdfLink, PdfDestination, action structs | present (since 0.7.31) | present |
PdfBookmark::destination() | added 0.8.16 | present |
PdfBookmark::children_len() | added 0.8.19 | present |
| Destination view method name | view() in 0.8.10, then view_settings() | view_settings() |
PdfDestinationViewSettings enum | added 0.8.10 | present |
PdfPageIndex underlying type | u16 | c_int since 0.9.0 |
Critical rules
- ALWAYS call
link.rect() for a link's clickable area. There is no
link.bounds(), and the type is PdfRect, not PdfQuadPoints.
- ALWAYS call
destination.view_settings(). The old name view() was removed.
- NEVER call
len() or is_empty() on PdfBookmarks; those methods do not
exist. Use iter().count() and root().is_none().
- ALWAYS match
PdfAction and PdfActionType with an Unsupported arm and a
catch-all _.
- ALWAYS check both
destination() and action() to resolve where a bookmark
or link leads. A URL bookmark has an action() but no destination().
- NEVER expect methods on
PdfActionLaunch, PdfActionRemoteDestination,
PdfActionEmbeddedDestination, PdfActionUnsupported, or the empty
PdfActionCommon trait. Verified data accessors exist only on
PdfActionUri and PdfActionLocalDestination.
- NEVER pin
PdfPageIndex to u16; it became c_int in 0.9.0.
Companion skills
pdfium-syntax-pages for pages(), len(), and reaching a PdfPage.
pdfium-impl-document-features for metadata, attachments, permissions.
pdfium-core-coordinates for PdfRect, PdfPoints, PdfPageIndex.
pdfium-impl-annotations for Widget and link annotations on a page.
pdfium-errors-runtime for PdfiumError handling.
Reference files
references/methods.md : complete API signatures with version annotations.
references/examples.md : working, verified Rust examples.
references/anti-patterns.md : real failures, why they happen, and the fix.