# Pdfium Errors Runtime

> Use when handling a PdfiumError returned at run time by pdfium-render: a wrong password, a corrupt or unreadable PDF, an index out of bounds, a flatten failure, or when rendered or extracted image colors look wrong with no error raised. Prevents unwrap-panics on recoverable errors, treating a wrong-password error as fatal, comparing errors with == when the enum has no PartialEq, and the silent BGRA-versus-RGBA channel swap. Covers the PdfiumError enum (82 variants in 0.9.1), the PdfiumInternalError codes wrapping FPDF_GetLastError, recoverable-versus-fatal classification, and the byte-order runtime bug across pdfium-render 0.8.x and 0.9.x. Keywords: pdfium-render, PdfiumError, PdfiumInternalError, PdfiumLibraryInternalError, PageIndexOutOfBounds, PageObjectIndexOutOfBounds, PasswordError, FormatError, FileError, PageFlattenFailure, FPDF_GetLastError, wrong password, corrupt PDF, index out of bounds, panic on unwrap, colors look wrong, blue and red swapped, image channels swapped, BGRA, RGBA, how do I handle p

- Skill: `impertio-studio/pdfium-errors-runtime` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add impertio-studio/pdfium-errors-runtime`
- Raw SKILL.md: https://api.skillmd.com/api/skills/impertio-studio/pdfium-errors-runtime/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-errors-runtime

---


# pdfium-render: Runtime Errors

Handling errors that surface while a PDF is being processed: loading, page access,
object access, text, rendering, and saving. Every fallible pdfium-render function
returns `Result<T, PdfiumError>`. This skill owns the runtime error surface.
Diagnosing a failed library bind is `pdfium-errors-binding`.

All API names below are verified against `docs.rs/pdfium-render` 0.9.1. The default
target is the 0.9.x API; 0.8.x differences are flagged inline.

## The PdfiumError Enum

`PdfiumError` has 82 variants in 0.9.1. It implements `Display`, `Debug`, and
`std::error::Error` (since 0.7.26). It does NOT implement `Clone` and does NOT
implement `PartialEq`.

The variants fall into two groups:

1. Data-carrying variants wrap another error type:
   - `PdfiumLibraryInternalError(PdfiumInternalError)`: PDFium's own error, the
     single most important variant (see below).
   - `IoError(std::io::Error)`: a filesystem error during load or save.
   - `LoadLibraryError(...)` / `LoadLibraryFunctionNameError(String)`: binding
     failures, owned by `pdfium-errors-binding`.
   - Conversion errors: `ParseHexadecimalColorError(ParseIntError)`,
     `CStringConversionError(IntoStringError)`,
     `UnableToConvertPdfiumColorValueToRustu8(TryFromIntError)`,
     `InvalidUserFontPath(NulError)`.
2. Data-free logic and bounds variants, for example `PageIndexOutOfBounds`,
   `PageObjectIndexOutOfBounds`, `PageAnnotationIndexOutOfBounds`,
   `CharIndexOutOfBounds`, `NoPagesInDocument`, `UnknownBitmapFormat`,
   `PageFlattenFailure`, `CannotMoveObjectAcrossDocuments`,
   `PageObjectsCollectionIsImmutable`, `DataBufferLengthMismatch`,
   `OwnershipNotAttachedToDocument`.

The full variant list is in `references/methods.md`.

## The Critical Variant: PdfiumLibraryInternalError

`PdfiumError::PdfiumLibraryInternalError(PdfiumInternalError)` carries the error
PDFium itself reported through `FPDF_GetLastError()`. Loading a PDF that exists but
cannot be opened always surfaces here. `PdfiumInternalError` has six variants:

| Variant | PDFium code | Meaning | Class |
|---------|-------------|---------|-------|
| `Unknown` | 1 | Generic, no detail available | depends on context |
| `FileError` | 2 | File system error reading the document | recoverable |
| `FormatError` | 3 | Document is not valid PDF / parse failure | recoverable |
| `PasswordError` | 4 | Wrong or missing password | recoverable |
| `SecurityError` | 5 | Document security settings block the load | recoverable |
| `PageError` | 6 | A page failed to load (internal error) | recoverable |

ALWAYS match the inner `PdfiumInternalError` before deciding what to do: a
`PasswordError` means re-prompt and retry, a `FormatError` means skip the file.
NEVER treat every `PdfiumLibraryInternalError` the same.

## Decision Tree: recoverable or fatal

```
Got a PdfiumError. Which variant?

LoadLibraryError / LoadLibraryFunctionNameError
  -> FATAL. No bound library means nothing works. See pdfium-errors-binding.

PdfiumLibraryInternalError(PasswordError)
  -> RECOVERABLE. Re-prompt for the password, retry load_pdf_from_* with Some(pw).

PdfiumLibraryInternalError(FileError | FormatError | SecurityError | PageError)
  -> RECOVERABLE per document. Log and skip this PDF; keep processing the rest.

PageIndexOutOfBounds / PageObjectIndexOutOfBounds / CharIndexOutOfBounds / ...
  -> RECOVERABLE. A programming or input error. Clamp the index or skip the item.

IoError(_)
  -> RECOVERABLE. A filesystem problem; retry, fall back, or report it.

Image colors look wrong but NO error was raised
  -> Not an error. A byte-order bug. See "The Silent Byte-Order Bug" below.
```

## Pattern: Handle a Load Failure

`load_pdf_from_*` is where most recoverable runtime errors appear. Match the inner
PDFium error to react correctly:

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

fn open(pdfium: &Pdfium, path: &str, password: Option<&str>)
    -> Result<(), PdfiumError>
{
    match pdfium.load_pdf_from_file(path, password) {
        Ok(document) => {
            println!("{} has {} pages", path, document.pages().len());
            Ok(())
        }
        Err(PdfiumError::PdfiumLibraryInternalError(
            PdfiumInternalError::PasswordError,
        )) => {
            eprintln!("{}: wrong password", path);
            Err(PdfiumError::PdfiumLibraryInternalError(
                PdfiumInternalError::PasswordError,
            ))
        }
        Err(PdfiumError::PdfiumLibraryInternalError(
            PdfiumInternalError::FormatError,
        )) => {
            eprintln!("{}: not a valid PDF, skipping", path);
            Ok(())                              // skip, keep going
        }
        Err(other) => Err(other),
    }
}
```

A wrong password is a normal, expected outcome; ALWAYS handle it as recoverable.

## Pattern: Bounds Errors Are Inputs, Not Crashes

Index-based access returns a `Result`. An out-of-range index returns a bounds
variant such as `PageIndexOutOfBounds`. ALWAYS handle it; NEVER `.unwrap()` an
index access on input you did not validate:

```rust
match document.pages().get(requested_index) {
    Ok(page)  => process(&page),
    Err(PdfiumError::PageIndexOutOfBounds) => {
        eprintln!("page {} does not exist", requested_index);
    }
    Err(e) => return Err(e),
}
```

`NoPagesInDocument` is the related whole-document case: a structurally valid PDF
with zero pages. Check it before iterating.

## Pattern: Matching Without PartialEq

`PdfiumError` does not implement `PartialEq`, so `err == PdfiumError::X` does not
compile. ALWAYS classify with `match` or the `matches!` macro:

```rust
fn is_recoverable(error: &PdfiumError) -> bool {
    matches!(
        error,
        PdfiumError::PageIndexOutOfBounds
            | PdfiumError::PageObjectIndexOutOfBounds
            | PdfiumError::CharIndexOutOfBounds
            | PdfiumError::NoPagesInDocument
            | PdfiumError::PdfiumLibraryInternalError(
                PdfiumInternalError::PasswordError
                    | PdfiumInternalError::FormatError
                    | PdfiumInternalError::FileError,
            )
    )
}
```

`PdfiumError` also does not implement `Clone`. NEVER try to clone an error to keep
a copy; propagate it with `?` or move it where it is needed.

## Pattern: Propagation with ? and anyhow

Because `PdfiumError` implements `std::error::Error` and `Display`, it composes
with `?`, with `Box<dyn std::error::Error>`, and with the `anyhow` crate:

```rust
fn page_count(path: &str) -> anyhow::Result<usize> {
    let pdfium = Pdfium::default();
    let document = pdfium.load_pdf_from_file(path, None)?;   // PdfiumError -> anyhow
    Ok(document.pages().len() as usize)
}
```

Use `?` in pipeline code where any error aborts the run; use explicit `match` at
the boundary where an error must be classified as recoverable or fatal.

## The Silent Byte-Order Bug

Not every runtime fault raises a `PdfiumError`. PDFium's native bitmap format is
BGRA: blue and red are swapped relative to the RGBA8 layout the `image` crate
expects. Feeding native bytes into an RGBA consumer produces an image with blue
and red exchanged, and NO error is returned (issue #50).

- `PdfBitmap::as_raw_bytes()` returns bytes in the bitmap's NATIVE format (BGRA).
- `PdfBitmap::as_rgba_bytes()` returns bytes normalized to RGBA8 regardless of the
  native format (added 0.8.16).

ALWAYS use `as_rgba_bytes()` when handing pixels to the `image` crate or any RGBA
consumer. ALWAYS use `as_image()` for a ready `DynamicImage`. Reserve
`as_raw_bytes()` for code that explicitly handles the native byte order, and use
`PdfRenderConfig::set_reverse_byte_order(true)` to flip the order at render time.
Full rendering detail is in `pdfium-syntax-rendering` and `pdfium-impl-output-formats`.

## Version Notes

| Item | 0.8.x | 0.9.x | Note |
|------|-------|-------|------|
| `PdfiumError` implements `Error` + `Display` | yes (since 0.7.26) | yes | `anyhow` compatible |
| `as_rgba_bytes()` | added 0.8.16 | present | normalized RGBA, fixes byte order |
| `PdfiumError` variant count | grows per release | 82 in 0.9.1 | never assume an exhaustive match stays exhaustive |

`PdfiumError` is marked non-exhaustive in practice: a `match` on it ALWAYS needs a
`_ =>` arm so a crate upgrade that adds a variant still compiles.

## Anti-Patterns (summary)

- `.unwrap()` on `load_pdf_from_*` or an index access: a wrong password or a bad
  index becomes a panic instead of a handled case.
- Treating every `PdfiumLibraryInternalError` as fatal: `PasswordError` and
  `FormatError` are recoverable.
- `err == PdfiumError::PageIndexOutOfBounds`: the enum has no `PartialEq`.
- Feeding `as_raw_bytes()` into the `image` crate: blue and red come out swapped.
- A `match` on `PdfiumError` with no `_ =>` arm: breaks on the next crate upgrade.

Each anti-pattern with the failure detail and fix: see `references/anti-patterns.md`.

## Cross-References

- `pdfium-errors-binding`: `LoadLibraryError` diagnosis, the fatal binding errors.
- `pdfium-syntax-document-loading`: the loaders that surface `PasswordError` etc.
- `pdfium-syntax-rendering`: `PdfBitmap`, `set_reverse_byte_order`, byte order.
- `pdfium-impl-output-formats`: `as_rgba_bytes` vs `as_raw_bytes` when exporting.
- `pdfium-impl-page-manipulation`: `PageFlattenFailure` from `flatten()`.

## Reference Files

- `references/methods.md`: the full PdfiumError variant list and the error types.
- `references/examples.md`: verified error-handling Rust code.
- `references/anti-patterns.md`: real failures, why they fail, the fix.

## Source

Verified 2026-05-20 against `https://docs.rs/pdfium-render/latest/pdfium_render/`
(prelude `enum.PdfiumError.html` and `enum.PdfiumInternalError.html`). Runtime
anti-patterns including the byte-order bug (issue #50) are recorded in
vooronderzoek-pdfium.md sections 7 and 8.

