# Pdfium Core Bindings Setup

> Use when setting up the link between a Rust program and the PDFium library: choosing a binding strategy (dynamic, system, or static), wiring Cargo feature flags, sourcing prebuilt PDFium binaries, or fixing a binding that will not load. Prevents the most common pdfium-render startup failure, a PdfiumError::LoadLibraryError caused by a missing, misnamed, wrong-architecture, or API-version-mismatched library. Covers Pdfium::bind_to_library, bind_to_system_library, bind_to_statically_linked_library, Pdfium::default, the pdfium_* and image_* feature flags, per-platform library file names, and the PDFIUM_STATIC_LIB_PATH / PDFIUM_DYNAMIC_LIB_PATH build variables across pdfium-render 0.8.x and 0.9.x. Keywords: pdfium-render bindings, bind_to_library, bind_to_system_library, bind_to_statically_linked_library, Pdfium::default, LoadLibraryError, libpdfium.so, pdfium.dll, libpdfium.dylib, pdfium_latest feature flag, PDFIUM_STATIC_LIB_PATH, static linking, library not found, pdfium fails to load, missing symbols, how do

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

---


# pdfium-core-bindings-setup

## What this skill covers

`pdfium-render` does NOT build or bundle PDFium. It is a Rust wrapper that binds
to a separately compiled PDFium library **at run time**. Binding is the first
operation in every `pdfium-render` program: it produces a
`Box<dyn PdfiumLibraryBindings>`, which `Pdfium::new` wraps into the `Pdfium`
root handle. If binding fails, nothing else in the crate works.

This skill covers choosing a binding strategy, wiring the Cargo feature flags,
sourcing prebuilt PDFium binaries, placing the library file correctly per
platform, and the 0.8.x to 0.9.x setup traps. For the ownership tree and the
late-binding rationale see `pdfium-core-architecture`. For diagnosing a binding
that fails at run time see `pdfium-errors-binding`.

## Quick reference

Three binding functions exist on the `Pdfium` struct. ALL are associated
functions (no `self`), and ALL return
`Result<Box<dyn PdfiumLibraryBindings>, PdfiumError>`:

| Function | Binds to | Requires |
|----------|----------|----------|
| `Pdfium::bind_to_library(path)` | a dynamic library at an explicit path | the file on disk |
| `Pdfium::bind_to_system_library()` | a PDFium installed on the OS | PDFium on the system search path |
| `Pdfium::bind_to_statically_linked_library()` | PDFium compiled into the executable | the `static` crate feature |

Two path helpers build platform-correct names:

- `Pdfium::pdfium_platform_library_name() -> OsString` returns the bare file
  name for the current platform (`libpdfium.so`, `pdfium.dll`, `libpdfium.dylib`).
- `Pdfium::pdfium_platform_library_name_at_path(path) -> PathBuf` joins that
  name onto a directory.

`Pdfium::new(bindings)` wraps a `Box<dyn PdfiumLibraryBindings>` into a `Pdfium`.

## Decision tree: which binding strategy

```
Need WASM / browser target?
  YES -> dynamic binding; bundle a separate WASM PDFium module
         (use the paulocoutinhox build). See pdfium-impl-wasm.
  NO  -> Need a single self-contained executable, no sidecar files?
           YES -> static linking (`static` feature + PDFIUM_STATIC_LIB_PATH)
           NO  -> Is PDFium already installed system-wide on every target?
                    YES -> bind_to_system_library()
                    NO  -> dynamic binding with fallback (DEFAULT, simplest)
```

ALWAYS default to dynamic binding with fallback unless a requirement above
forces another path. It is the pattern every official example uses.

## Minimal Cargo.toml

```toml
[dependencies]
pdfium-render = "0.9"
image = "0.25"
```

The default features are `pdfium_latest`, `image_latest`, and `thread_safe`.
`pdfium_latest` resolves to a specific PDFium API version (`pdfium_7763` as of
0.9.1). See "The critical pinning rule" below.

## Pattern: dynamic binding with fallback (default choice)

This is the canonical pattern from the `pdfium-render` README and
`examples/export.rs`. It tries a library next to the executable first, then
falls back to a system-installed PDFium:

```rust
use pdfium_render::prelude::*;

let bindings = Pdfium::bind_to_library(
    Pdfium::pdfium_platform_library_name_at_path("./"),
)
.or_else(|_| Pdfium::bind_to_system_library())?;

let pdfium = Pdfium::new(bindings);
```

`Pdfium::default()` implements exactly this fallback (extended in 0.8.12 to also
try the current working directory). For the common case, write:

```rust
let pdfium = Pdfium::default();
```

ALWAYS bind PDFium **once** and reuse the `Pdfium` instance. NEVER re-bind per
request: re-loading the library on every call is slow (anti-pattern, issue #59).
For long-lived servers, store the bound `Pdfium` in a `once_cell` / `OnceLock`;
see `pdfium-impl-performance`.

The `or_else` fallback is the ONE sanctioned fallback in this skill, because each
arm is a distinct, documented binding strategy and the failure is reported
explicitly as a `PdfiumError`. Do NOT wrap binding in a silent `match` that
swallows the error: a failed bind must surface.

## Pattern: system library only

When PDFium is guaranteed installed system-wide (a controlled server image, a
Linux distro package), bind directly:

```rust
use pdfium_render::prelude::*;

let pdfium = Pdfium::new(Pdfium::bind_to_system_library()?);
```

`bind_to_system_library()` searches the OS dynamic-library path. On Linux that
is `LD_LIBRARY_PATH` and the standard `ld.so` directories; on Windows the `PATH`
and the executable directory; on macOS the `DYLD_*` paths.

## Pattern: static linking

Static linking produces a single self-contained executable with no sidecar
`.so` / `.dll` / `.dylib`. It requires the `static` crate feature:

```toml
pdfium-render = { version = "0.9", features = ["static"] }
```

```rust
use pdfium_render::prelude::*;

let pdfium = Pdfium::new(Pdfium::bind_to_statically_linked_library()?);
```

At build time, set `PDFIUM_STATIC_LIB_PATH` to the **directory** containing
`libpdfium.a` (`pdfium.lib` on Windows). The crate `build.rs` then emits
`cargo:rustc-link-lib=static=pdfium` and
`cargo:rustc-link-search=native=$PDFIUM_STATIC_LIB_PATH`.

Static builds also need a C++ standard library and, on macOS, CoreGraphics:

- Linux / GNU toolchain: add the `libstdc++` feature.
- LLVM toolchain: add the `libc++` feature.
- macOS: add the `core_graphics` feature to resolve `_CGBitmap` / `_CGContext`.

NEVER omit the C++ runtime feature on a static build: the link step fails with a
wall of undefined references (issue #51). See `references/anti-patterns.md`.

## Feature flags

| Group | Flags |
|-------|-------|
| Default | `pdfium_latest`, `image_latest`, `thread_safe` |
| PDFium API version | `pdfium_latest`, `pdfium_future`, explicit pins `pdfium_7763` ... `pdfium_5961` |
| `image` crate version | `image_latest`, `image_025`, `image_024`, `image_023` |
| Linking | `static`, `libstdc++`, `libc++`, `core_graphics` |
| Raw FFI bindings | `bindings` (regenerate the crate's raw Rust FFI bindings) |
| WASM | `console_log` (route logging to the browser console, added 0.9.1) |
| Compile-time PDFium feature gates | `pdfium_use_skia`, `pdfium_use_win32`, `pdfium_enable_xfa`, `pdfium_enable_v8` |

The PDFium API-version pins and the compile-time gates were introduced in
0.8.24 and 0.8.25; the `image_*` selectors in 0.8.26. The full pin list and
introduction versions are in `references/methods.md`.

### The critical pinning rule

The `pdfium_*` feature selects which PDFium **API version** the crate calls.
ALWAYS make this feature match the Chromium build number of the actual PDFium
binary you bind to. A mismatch produces missing-symbol errors or run-time
crashes, NOT a compile error.

To pin explicitly and disable thread safety for single-threaded throughput:

```toml
pdfium-render = { version = "0.9", default-features = false, features = [
    "pdfium_7763", "image_025", "image",
] }
```

`bblanchon/pdfium-binaries` releases are tagged `chromium/<number>` (for example
`chromium/7763`). Match the number to the `pdfium_<number>` feature.

## Per-platform library file names

The dynamic loader expects EXACTLY these names. A wrong name produces
`PdfiumError::LoadLibraryError`:

| Platform | Dynamic library | Static archive |
|----------|-----------------|----------------|
| Linux | `libpdfium.so` | `libpdfium.a` |
| macOS | `libpdfium.dylib` | `libpdfium.a` |
| Windows | `pdfium.dll` | `pdfium.lib` |

For dynamic binding, the simplest deployment places the platform library in the
same directory as the compiled executable and binds with
`pdfium_platform_library_name_at_path("./")`. ALWAYS use the path helpers rather
than hardcoding a file name: they pick the correct name per platform.

## Build environment variables

| Variable | Purpose | Emits |
|----------|---------|-------|
| `PDFIUM_STATIC_LIB_PATH` | directory holding `libpdfium.a` | `rustc-link-lib=static=pdfium` + `rustc-link-search` |
| `PDFIUM_DYNAMIC_LIB_PATH` | directory holding the dynamic library | `rustc-link-lib=dylib=pdfium` + `rustc-link-search` |
| `PDFIUM_STATIC_LIB_PATH_<triple>` | per-target override (0.9.0+) | same, for one target triple |

ALWAYS point these variables at the **directory**, NEVER at the file itself.
For the per-target form, replace the hyphens in the target triple with
underscores, for example `PDFIUM_STATIC_LIB_PATH_aarch64_apple_darwin`.

## Obtaining PDFium binaries

`pdfium-render` never builds PDFium. Source a prebuilt binary:

- `https://github.com/bblanchon/pdfium-binaries/releases`: native builds for
  Android, iOS, Linux (glibc and musl), macOS, Windows, plus an experimental
  WASM build. Archives are named `pdfium-<platform>-<arch>.tgz` (for example
  `pdfium-linux-x64.tgz`). Releases are tagged `chromium/<number>`.
- `https://github.com/paulocoutinhox/pdfium-lib/releases`: Android, iOS, macOS,
  and WASM builds. ALWAYS use this source for WASM: its WASM build uses a
  growable heap, while the bblanchon WASM build has a non-growable heap and runs
  out of memory on multi-page documents.

## Version traps: 0.8.x to 0.9.x

| Item | Trap |
|------|------|
| `bind_to_library` signature | Took / returned strings before 0.8.9; changed to `AsRef<Path>` / `PathBuf` in 0.8.9. Code written for 0.8.8 or earlier fails to compile against 0.9.x. |
| `Pdfium::default()` | Before 0.8.12 it did not search the current working directory; on 0.8.11 and earlier, binding via `default()` may miss a library placed in the cwd. |
| `Pdfium::get_bindings()` | Removed in 0.9.0. Accessing the raw bindings is covered by `pdfium-core-raw-ffi`. |
| Default API surface | This skill defaults to the 0.9.x API. Treat removed 0.8.x names as upgrade traps. |

The 0.9.0 release also marks all raw `FPDF_*` functions `unsafe` and implements
`Send` / `Sync`; those affect `pdfium-core-raw-ffi` and `pdfium-core-memory`,
not binding setup itself.

## Common failures (quick triage)

| Symptom | Likely cause | Fix |
|---------|--------------|-----|
| `LoadLibraryError` | library not next to executable and not on system path | place the correctly named file beside the binary, or install it system-wide |
| `LoadLibraryError` | wrong file name for the platform | rename to `libpdfium.so` / `pdfium.dll` / `libpdfium.dylib` |
| crash or missing symbols at run time | `pdfium_*` feature does not match the binary's Chromium build | pin `pdfium_<number>` to the binary's `chromium/<number>` tag |
| undefined references at link time (static) | C++ runtime not linked | add `libstdc++` or `libc++` |
| undefined `_CGBitmap` / `_CGContext` (static, macOS) | CoreGraphics not linked | add `core_graphics` |
| segfault loading any document | x64 binary on arm64 host, or glibc binary on musl | use the matching architecture and libc archive |

Full root-cause analysis lives in `pdfium-errors-binding`.

## Reference files

- `references/methods.md`: complete signatures for the binding functions, path
  helpers, `Pdfium::new` / `new_with_config` / `default`, and the full feature
  flag list with introduction versions.
- `references/examples.md`: working, verified Rust setup code for dynamic,
  system, and static binding, plus a `OnceLock` reuse example.
- `references/anti-patterns.md`: real binding failures from the GitHub issue
  tracker, why each fails, and the fix.

## Companion skills

- `pdfium-core-architecture`: the ownership tree and why late binding exists.
- `pdfium-errors-binding`: diagnosing a binding that fails at run time.
- `pdfium-impl-wasm`: binding and running PDFium in the browser.
- `pdfium-core-raw-ffi`: reaching the raw `PdfiumLibraryBindings` trait.
- `pdfium-impl-performance`: the bind-once `OnceLock` pattern for servers.

