| name | pdfium-core-architecture |
| description | Use when starting a pdfium-render project, choosing an integration approach, or debugging confusing lifetime and threading errors, and you need the conceptual map of how the crate is built. Prevents the four wrong-mental-model mistakes: treating pdfium-render as a pure-Rust PDF parser, expecting compile-time linking of PDFium, storing a parent handle and a child handle together in one struct, and expecting multi-threading to speed up rendering. Covers the PDFium-to-pdfium-render wrapper relationship, the late run-time binding model, the lifetime-bound ownership tree (Pdfium to PdfDocument to PdfPages to PdfPage), the thread-safety stance, the 0.8.x to 0.9.x changes, and when to drop to raw FFI. Keywords: pdfium-render, PDFium, architecture, ownership tree, late binding, run-time binding, RAII wrapper, lifetime error, Send Sync, thread safety, thread_safe feature, raw FFI, FPDF_, PdfiumLibraryBindings, Pdfium, PdfDocument, PdfPage, "library not found", "cannot return value referencing local variable", "borrowed value does not live long enough", "is pdfium-render thread safe", "how does pdfium-render work", "what is pdfium-render", getting started.
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires pdfium-render 0.8,0.9. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
pdfium-render Architecture
This is the conceptual entry skill for the pdfium-render skill package. It is
a map, not an API reference. It explains how the crate is built so that every
later decision (binding, memory, rendering, text, objects) is made with the
correct mental model. For exact API signatures see the bindings-setup and
memory skills cross-referenced at the bottom.
What pdfium-render Is
pdfium-render is an idiomatic high-level Rust wrapper around PDFium, the
C++ PDF library that Google's Chromium project uses for PDF rendering. PDFium
exposes a flat C FFI surface of FPDF_* functions (for example
FPDF_LoadDocument, FPDFText_GetText, FPDF_RenderPageBitmap).
pdfium-render does not reimplement PDF parsing. It binds to a compiled
PDFium library and presents a safe, RAII-driven Rust API on top of the raw FFI.
Every operation (load, render, extract text, edit, save) is ultimately a call
into PDFium.
ALWAYS treat pdfium-render as a binding layer. NEVER assume it can open or
parse a PDF without a PDFium library present at run time, because all parsing
happens inside PDFium's C++ code.
Quick Reference: The Mental Model
A pdfium-render program has exactly three stages:
1. BIND Locate and load a PDFium library at run time.
-> Box<dyn PdfiumLibraryBindings>
2. WRAP Hand the bindings to the root object.
-> Pdfium
3. USE Load documents, walk pages, render, extract, edit, save.
-> PdfDocument<'a> -> PdfPages<'a> -> PdfPage -> objects/text/...
The root object (Pdfium) must outlive everything produced from it.
| Concern | One-line answer | Detail skill |
|---|
| Where does the library come from | Loaded at run time, not linked at build time | pdfium-core-bindings-setup |
| Who owns the bindings | The Pdfium root object | pdfium-core-bindings-setup |
| Why do lifetimes appear everywhere | Every child handle borrows its parent | pdfium-core-memory |
| Can I render on many threads | No speedup; PDFium is not thread safe | this skill, section 4 |
Can I call FPDF_* directly | Yes, through the bindings trait | pdfium-core-raw-ffi |
1. The Wrapper Relationship
Two distinct things share the name "pdfium":
- PDFium : Google's C++ library. It does the actual PDF work. It is
distributed as a compiled binary (
libpdfium.so, libpdfium.dylib,
pdfium.dll, or a static libpdfium.a).
- pdfium-render : the Rust crate. It is a thin, safe, idiomatic layer
that calls into PDFium and adds RAII cleanup,
Result-based error
handling, and Rust ownership semantics.
ALWAYS keep this split in mind when diagnosing problems: a missing-library or
missing-symbol error is a PDFium-binary problem (see pdfium-errors-binding),
while a borrow-checker or lifetime error is a pdfium-render API-shape problem
(see pdfium-core-memory).
2. Late Run-Time Binding
The single most important architectural decision is late (run-time)
binding. pdfium-render does not link PDFium at compile time by default.
Instead it locates and loads a PDFium library while the program is running.
Binding produces a Box<dyn PdfiumLibraryBindings>. Pdfium::new(bindings)
then wraps that boxed trait object into the Pdfium root.
Why late binding exists:
- WASM compatibility : a Rust application using pdfium-render can be
compiled to WASM and run in a browser alongside a separately packaged WASM
build of PDFium. Compile-time linking cannot express that.
- Flexible library selection : the same compiled Rust binary can use a
system-installed PDFium, a PDFium shipped next to the executable, or a
statically linked one, decided at run time.
- Idiomatic error handling : a missing PDFium library becomes a
Result::Err, not a load-time crash.
The static feature plus bind_to_statically_linked_library() opt back in to
compile-time linking when a single self-contained executable is required. That
is the exception; run-time binding is the default and the design center.
ALWAYS perform binding exactly once per process and reuse the Pdfium
instance. NEVER re-bind on every request (for example per HTTP request),
because loading the library is slow and wasteful (see anti-patterns).
3. The Ownership Tree
pdfium-render models the PDF object hierarchy as a strict tree of
lifetime-bound handles. Each handle borrows from its parent. A child can
never outlive its parent.
Pdfium root : owns Box<dyn PdfiumLibraryBindings>
|
+-- PdfDocument<'a> 'a is tied to the &Pdfium that created it
|
+-- PdfPages<'a> collection of pages (pages(), pages_mut())
| +-- PdfPage a single page
| +-- objects() -> &PdfPageObjects<'a>
| +-- text() -> Result<PdfPageText, PdfiumError>
| +-- annotations() -> &PdfPageAnnotations<'a>
| +-- boundaries() -> &PdfPageBoundaries<'a>
|
+-- form() -> Option<&PdfForm>
+-- metadata() -> document metadata collection
+-- attachments() -> document attachments collection
+-- bookmarks() -> document bookmark tree
+-- permissions() -> document permission flags
+-- fonts() -> document font collection
+-- signatures() -> document signature collection
The lifetime relationship is visible in the signatures. Pdfium::load_pdf_from_file
is declared as:
pub fn load_pdf_from_file<'a>(
&'a self,
path: &(impl AsRef<Path> + ?Sized),
password: Option<&str>,
) -> Result<PdfDocument<'a>, PdfiumError>
The returned PdfDocument<'a> carries the same 'a as the &'a self
reference to Pdfium. The compiler therefore guarantees the document cannot
outlive the Pdfium root. The same pattern repeats down the tree: a PdfPage
borrows from its PdfDocument, page objects borrow from their PdfPage.
ALWAYS keep the Pdfium root alive in an enclosing scope for as long as any
document, page, or page object derived from it is in use. NEVER store a parent
handle and a child handle together in the same struct, because that is a
self-referential borrow the compiler rejects (see pdfium-core-memory for the
correct pattern and anti-patterns for the failure).
4. Thread-Safety Stance
PDFium makes no guarantees about thread safety and must be assumed not
thread safe. The PDFium authors explicitly recommend parallel processing,
not multi-threading, to handle multiple documents at once.
pdfium-render exposes the optional thread_safe feature (enabled by
default). It works as follows:
- It locks every call into PDFium behind a single global mutex. Each thread
must acquire that mutex before any PDFium call.
- This prevents segfaults caused by concurrent access to PDFium's internal
state.
- It provides no performance benefit. Because every call serializes
through one mutex, two threads rendering pages run no faster than one.
Version note: release 0.9.0 additionally implements Send and Sync for
all object instances, so handles can be moved between threads. Send/Sync
plus the thread_safe mutex make multi-threaded code sound, but they do not
make it fast.
Decision: I need to process many PDFs faster.
Multi-threading with thread_safe ........ SOUND, but NO speedup (mutex).
Multi-threading without thread_safe ..... UNSOUND, random segfaults.
Process-level parallelism ............... CORRECT : one Pdfium per process.
ALWAYS use process-level parallelism (a separate process, each with its own
Pdfium instance) when real throughput matters. NEVER disable the
thread_safe feature while still calling PDFium from multiple threads,
because concurrent access to PDFium causes random segfaults. The canonical
examples/thread_safe.rs in the repository demonstrates this.
5. When To Reach For Raw FFI
The safe high-level API is built on top of the PdfiumLibraryBindings trait,
which is the Rust representation of PDFium's complete FPDF_* C surface. The
trait is reachable, so existing C or C++ PDFium code can be ported while
keeping late binding and WASM support.
Reach for raw FFI ONLY when:
- A specific
FPDF_* function has no safe wrapper yet in pdfium-render.
- You are porting existing C or C++ PDFium code and want a direct mapping.
Version note: in 0.9.0 every FPDF_* function on PdfiumLibraryBindings
is marked unsafe, matching the inherent unsafety of C FFI. In 0.8.x they
were safe-looking. In 0.9.2 PdfiumLibraryBindingsAccessor became public.
ALWAYS prefer the safe high-level API. NEVER drop to raw FPDF_* calls for
work the safe API already covers, because raw calls bypass the RAII cleanup
and lifetime guarantees this architecture provides. For the raw-FFI mechanics
see pdfium-core-raw-ffi.
6. Version Awareness (0.8.x to 0.9.x)
This package targets both the 0.8.x and 0.9.x lines. The default API surface
is 0.9.x. The dominant architectural event is the 0.9.0 cleanup
release, which removed every previously deprecated item, simplified lifetime
handling across all object instances, implemented Send/Sync, changed
PdfPageIndex from u16 to c_int, and marked all FPDF_* bindings
unsafe.
The practical consequence for architecture: code written against old
tutorials uses removed names. The most common upgrade traps are
PdfBitmapConfig (use PdfRenderConfig), PdfPage::get_bitmap() (use
render_with_config()), as_bytes() (use as_raw_bytes() /
as_rgba_bytes()), set_matrix() (use apply_matrix()),
load_pdf_from_bytes() (use load_pdf_from_byte_slice() /
load_pdf_from_byte_vec()), and Pdfium::get_bindings() (use bindings()).
ALWAYS write new code against the 0.9.x API names. NEVER copy an example using
a removed 0.8.x name without updating it. The full removed-name table lives in
references/anti-patterns.md and in pdfium-core-bindings-setup.
Decision Tree: Integration Approach
How will the PDFium binary be delivered?
Native app, simplest path
-> Dynamic binding. Ship libpdfium.{so,dylib,dll} next to the executable.
Bind with the fallback pattern or Pdfium::default().
See pdfium-core-bindings-setup.
Single self-contained executable required
-> Static linking. Enable the `static` feature, set PDFIUM_STATIC_LIB_PATH,
call bind_to_statically_linked_library().
See pdfium-core-bindings-setup.
Browser / WebAssembly target
-> Run-time binding to a separately packaged WASM PDFium module.
Use the paulocoutinhox build (growable heap).
See pdfium-impl-wasm.
Porting existing C / C++ PDFium code
-> Raw FFI through PdfiumLibraryBindings, then migrate to the safe API.
See pdfium-core-raw-ffi.
ALWAYS / NEVER
- ALWAYS bind PDFium exactly once per process and reuse the
Pdfium
instance for that process's lifetime.
- ALWAYS keep the
Pdfium root alive in an enclosing scope for as long as
any document or page derived from it is used.
- ALWAYS use process-level parallelism for throughput; one
Pdfium per
process.
- ALWAYS write new code against 0.9.x API names; the default API surface of
this package is 0.9.x.
- NEVER treat pdfium-render as a pure-Rust PDF parser; it requires a PDFium
binary at run time.
- NEVER store a parent handle (
Pdfium, PdfDocument) together with a child
handle (PdfPage, page objects) in the same struct; it is a
self-referential borrow the compiler rejects.
- NEVER expect the
thread_safe mutex to speed up rendering; it only
prevents segfaults.
- NEVER disable
thread_safe while still calling PDFium from multiple
threads.
- NEVER re-bind the library per request; binding is slow.
Cross-References
- pdfium-core-bindings-setup : the three bind functions, the fallback
pattern,
Pdfium::default(), feature flags, obtaining PDFium binaries,
per-platform library file names, build environment variables.
- pdfium-core-memory : the lifetime model in depth, the correct way to
hold handles, drop order, and how to avoid self-referential structs.
- pdfium-core-raw-ffi : the
PdfiumLibraryBindings trait, calling
FPDF_* directly, and the 0.9.0 unsafe change.
- pdfium-errors-binding : diagnosing a failed bind (missing file, wrong
architecture, feature mismatch, missing C++ runtime).
Reference Files
references/methods.md : API signatures of the architectural types
(Pdfium, PdfiumLibraryBindings, PdfDocument, PdfPages, PdfPage)
with 0.8.x / 0.9.x version annotations.
references/examples.md : verified Rust code showing the three-stage
program, walking the ownership tree, and the thread-safety pattern.
references/anti-patterns.md : real failures from the architecture (self-
referential structs, dropped roots, threading misuse, removed names) with
why each fails and the fix.
Sources
All API names and signatures in this skill were verified on 2026-05-20 via
WebFetch against the sources in the package SOURCES.md:
https://docs.rs/pdfium-render/latest/pdfium_render/prelude/struct.Pdfium.html
https://docs.rs/pdfium-render/latest/pdfium_render/prelude/struct.PdfDocument.html
https://docs.rs/pdfium-render/latest/pdfium_render/prelude/struct.PdfPage.html
https://docs.rs/pdfium-render/latest/pdfium_render/prelude/struct.PdfPages.html
https://docs.rs/pdfium-render/latest/pdfium_render/prelude/trait.PdfiumLibraryBindings.html
https://github.com/ajrcarey/pdfium-render (README, design philosophy,
thread-safety statement, version history).