# Pdfium Impl Navigation

> 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

- Skill: `impertio-studio/pdfium-impl-navigation` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add impertio-studio/pdfium-impl-navigation`
- Raw SKILL.md: https://api.skillmd.com/api/skills/impertio-studio/pdfium-impl-navigation/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- License: MIT
- Author: Impertio-Studio (https://skillmd.com/u/impertio-studio)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/impertio-studio/pdfium-impl-navigation

---


# 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()` | `Result<PdfDestinationViewSettings, PdfiumError>` |
| Action subtype tag | `action.action_type()` | `PdfActionType` |
| URL of a Uri action | `uri_action.uri()` | `Result<String, PdfiumError>` |

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

```rust
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

```rust
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.

