# Pdfium Impl Fonts

> Use when embedding a custom TrueType or Type1 font into a PDF with pdfium-render, selecting one of the 14 built-in PDF fonts, inspecting font metadata, working with glyphs, or extracting an embedded font from a document. Prevents the removed-PdfFont-constructor compile error, garbled or unselectable text from a wrong is_cid_font flag, and the assumption that built-in fonts can render emoji. Covers PdfFonts loading constructors, PdfFontToken, PdfFontBuiltin, PdfFont inspection, PdfFontGlyphs, and embedded-font extraction across pdfium-render 0.8.x and 0.9.x. Keywords: pdfium-render, PdfFonts, PdfFont, PdfFontToken, PdfFontGlyphs, PdfFontBuiltin, load_true_type_from_bytes, load_type1_from_file, helvetica, embed font, custom font, is_cid_font, is_embedded, extract font, garbled text, unselectable text, text shows as boxes, copied text is wrong, emoji not rendering, how do I embed a font in a PDF, how to use a custom font.

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

---


# pdfium-render: Fonts

Registering, embedding, inspecting, and extracting fonts with pdfium-render. A font
must be added to a document's `PdfFonts` collection before any text object can use
it. This skill owns the font lifecycle. Creating the text objects that consume a
font is `pdfium-impl-page-objects-edit`; reading page text is `pdfium-syntax-text`.

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.

## Quick Reference

| Goal | API | Returns |
|------|-----|---------|
| Get the font collection | `PdfDocument::fonts()` / `fonts_mut()` | `&PdfFonts` / `&mut PdfFonts` |
| Use a built-in font | `PdfFonts::helvetica()` etc. | `PdfFontToken` |
| Use a built-in by enum | `PdfFonts::new_built_in(PdfFontBuiltin)` | `PdfFontToken` |
| Embed a TrueType font | `PdfFonts::load_true_type_from_bytes(data, is_cid)` | `Result<PdfFontToken, _>` |
| Embed a Type1 font | `PdfFonts::load_type1_from_bytes(data, is_cid)` | `Result<PdfFontToken, _>` |
| Look up a registered font | `PdfFonts::get(token)` | `Option<&PdfFont>` |
| Inspect a font | `PdfFont::name/family/weight/is_embedded` | see methods.md |
| Read raw font program | `PdfFont::data()` | `Result<Vec<u8>, _>` |
| Walk glyphs | `PdfFont::glyphs()` | `&PdfFontGlyphs` |

`PdfFontBuiltin` variants (14): `TimesRoman`, `TimesBold`, `TimesItalic`,
`TimesBoldItalic`, `Helvetica`, `HelveticaBold`, `HelveticaOblique`,
`HelveticaBoldOblique`, `Courier`, `CourierBold`, `CourierOblique`,
`CourierBoldOblique`, `Symbol`, `ZapfDingbats`.

Full signatures with version annotations: see `references/methods.md`.

## The Token Model

`PdfFonts` constructors do NOT return a `PdfFont`. They return a `PdfFontToken`: a
small `Copy` handle that references a font registered in the document. The flow is
always:

```
fonts_mut() -> load/built-in constructor -> PdfFontToken (Copy)
            -> pass token to PdfPageTextObject::new()
            -> fonts().get(token) -> Option<&PdfFont> for inspection
```

`PdfFontToken` is `Copy`, `Eq`, and `Hash`, so registering a font ONCE and reusing
the token everywhere is correct and cheap. `PdfFontToken` is NOT `Send`/`Sync`; do
not move it across threads.

ALWAYS register a font once per document and reuse its token. NEVER call a load
function repeatedly for the same font: each call embeds another copy of the font
program and inflates the file.

## Version Trap: removed PdfFont constructors

Direct `PdfFont` constructors (`PdfFont::new_*`) were deprecated in 0.8.3 and
REMOVED in 0.9.0. Code that constructs a `PdfFont` directly does not compile against
0.9.x.

```rust
// WRONG: no such constructor on 0.9.x.
let font = PdfFont::new_true_type_from_bytes(&document, &bytes, true)?;

// CORRECT: register through the document's PdfFonts collection.
let token = document.fonts_mut().load_true_type_from_bytes(&bytes, true)?;
```

ALWAYS obtain fonts through `document.fonts_mut()`. A `PdfFont` reference is only
ever read back with `fonts().get(token)` or from a text object's `font()`.

## Decision Tree: which font

```
Need a standard Latin font, no embedding, smallest file?
  -> PdfFonts::helvetica() / times_roman() / courier() (+ bold/italic variants)
     These are the 14 built-in PDF fonts; viewers supply the glyphs.

Need a specific brand / non-Latin / Unicode font in the output?
  -> embed it: load_true_type_from_bytes / load_type1_from_bytes
     The font program is stored IN the PDF.

Embedding a CJK or large-Unicode font (multi-byte character codes)?
  -> is_cid_font = true

Embedding a simple single-byte Latin font?
  -> is_cid_font = false

Need to read or save a font already inside a PDF?
  -> text_object.font() -> is_embedded()? -> data()
```

## Pattern: Built-In Font

The 14 built-in fonts need no font file. The constructor takes `&mut self`, so call
it through `fonts_mut()`:

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

let pdfium = Pdfium::default();
let mut document = pdfium.create_new_pdf()?;

let helvetica = document.fonts_mut().helvetica();        // PdfFontToken
// `helvetica` is now usable as the `font` argument of PdfPageTextObject::new.
```

`new_built_in(PdfFontBuiltin)` is the enum-driven equivalent:

```rust
let token = document.fonts_mut().new_built_in(PdfFontBuiltin::TimesBold);
```

Built-in fonts cover Latin text only. They CANNOT render emoji, CJK, or other
scripts outside their character set. For anything else, embed a font.

## Pattern: Embed a Custom TrueType Font

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

let pdfium = Pdfium::default();
let mut document = pdfium.create_new_pdf()?;

// Embed from bytes already in memory.
let font_bytes = std::fs::read("Inter-Regular.ttf")?;
let inter = document.fonts_mut()
    .load_true_type_from_bytes(&font_bytes, false)?;     // simple Latin font

// Or embed straight from a path.
let other = document.fonts_mut()
    .load_true_type_from_file("NotoSansJP-Regular.ttf", true)?;  // CID font

let _ = (inter, other);
```

`load_true_type_from_file`, `load_true_type_from_reader`, and
`load_true_type_from_bytes` all take `(source, is_cid_font: bool)` and return
`Result<PdfFontToken, PdfiumError>`. Type1 fonts use the matching
`load_type1_from_*` functions. The `_from_fetch` and `_from_blob` async variants
exist for WASM targets only (see `pdfium-impl-wasm`).

## The is_cid_font Flag

`is_cid_font` is the single most important font-embedding decision and a documented
cause of garbled text (issues #84, #226).

- `is_cid_font = true`: the font is registered as a CID-keyed font, using
  multi-byte character codes. ALWAYS use `true` for CJK fonts and for large-Unicode
  TrueType fonts that must cover characters beyond a single-byte range.
- `is_cid_font = false`: the font is registered as a simple font with single-byte
  character codes. Use `false` for simple Latin fonts.

A mismatch between the flag and the font's actual encoding maps character codes to
the wrong glyphs: the rendered text looks like garbage, boxes, or `ÿ` characters,
and copied text comes back wrong. See `references/anti-patterns.md`.

## Pattern: Inspect a Font

Look a registered font back up with `get(token)`, or take it from a text object:

```rust
if let Some(font) = document.fonts().get(token) {
    println!("name:     {}", font.name());
    println!("family:   {}", font.family());
    println!("embedded: {:?}", font.is_embedded());      // Result<bool, _>
    println!("serif:    {}", font.is_serif());
    println!("italic:   {}", font.is_italic());
    println!("weight:   {:?}", font.weight());           // Result<PdfFontWeight, _>
}
```

`PdfFont::name()` and `family()` return `String`. The `is_*` predicate methods
return `bool` directly. `is_embedded()`, `weight()`, `italic_angle()`, `ascent()`,
and `descent()` return a `Result` because they query the PDFium font program.
`ascent(font_size)` and `descent(font_size)` take a `PdfPoints` size.

## Pattern: Extract an Embedded Font

`PdfFont::data()` returns the raw font program. Combined with `is_embedded()` this
extracts fonts from an existing PDF:

```rust
let pdfium = Pdfium::default();
let document = pdfium.load_pdf_from_file("input.pdf", None)?;

for page in document.pages().iter() {
    for object in page.objects().iter() {
        if let Some(text_object) = object.as_text_object() {
            let font = text_object.font();               // PdfFont<'_>
            if font.is_embedded()? {
                let program = font.data()?;              // Vec<u8>
                std::fs::write(format!("{}.fontdata", font.name()), program)?;
            }
        }
    }
}
```

`text_object.font()` returns a `PdfFont` by value. Narrowing a `PdfPageObject` to
`PdfPageTextObject` with `as_text_object()` is covered by `pdfium-syntax-page-objects`.
NEVER call `data()` on a non-embedded font expecting the font file: built-in fonts
are supplied by the viewer, not stored in the PDF.

## Glyphs

`PdfFont::glyphs()` returns `&PdfFontGlyphs`, the font's glyph collection:

```rust
let glyphs = font.glyphs();
println!("glyph count: {}", glyphs.len());
for glyph in glyphs.iter() {
    let width = glyph.width_at_font_size(PdfPoints::new(12.0));
    let _ = width;
}
```

`PdfFontGlyphs` exposes `len()`, `is_empty()`, `as_range()`, `as_range_inclusive()`,
`get(index)`, and `iter()`. A `PdfFontGlyph` gives `width_at_font_size(PdfPoints)`
and `segments_at_font_size(PdfPoints)` for the glyph outline path. Glyph indices use
the `PdfFontGlyphIndex` type alias.

## Version Traps

| Item | 0.8.x | 0.9.x | Action |
|------|-------|-------|--------|
| `PdfFont::new_*` direct constructors | deprecated 0.8.3 | REMOVED | use `PdfFonts` load/built-in functions |
| `PdfFonts` collection | present | present | reach it via `fonts()` / `fonts_mut()` |
| `_from_fetch` / `_from_blob` loaders | WASM only | WASM only | native code uses `_from_file/_bytes/_reader` |

## Anti-Patterns (summary)

- Calling a removed `PdfFont::new_*` constructor: register via `fonts_mut()`.
- Wrong `is_cid_font` flag: garbled, boxed, or unselectable text.
- Expecting a built-in font to render emoji or CJK: it cannot; embed a font.
- Re-loading the same font per text object: bloats the file; register once, reuse
  the `Copy` token.
- Calling `data()` on a non-embedded font and expecting the font program.

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

## Cross-References

- `pdfium-impl-page-objects-edit`: building the `PdfPageTextObject` that uses a
  `PdfFontToken` (`PdfPageTextObject::new` takes `impl ToPdfFontToken`).
- `pdfium-syntax-text`: reading existing page text and per-character font info.
- `pdfium-syntax-page-objects`: narrowing `PdfPageObject` with `as_text_object()`.
- `pdfium-impl-wasm`: the `load_*_from_fetch` / `load_*_from_blob` async loaders.
- `pdfium-core-memory`: the self-referential trap when storing a document with its
  fonts in one struct (0.9.0 relaxed this).
- `pdfium-errors-runtime`: `PdfiumError` variants from font loading.

## Reference Files

- `references/methods.md`: complete API signatures with version annotations.
- `references/examples.md`: verified, runnable Rust programs.
- `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 struct pages for `PdfFonts`, `PdfFont`, `PdfFontToken`, `PdfFontGlyphs`,
`PdfFontGlyph`, `PdfPageTextObject`, `PdfDocument`, and the `PdfFontBuiltin` enum).

