# Pdfium Syntax Text

> Use when extracting text from a PDF page with pdfium-render, reading text character by character, getting the text inside a rectangle, or searching a page for a string. Prevents missing text held inside XObject form containers and trusting chars_for_object on an outdated PDFium build. Covers PdfPage::text, PdfPageText::all, the chars and segments collections, PdfPageTextChar, region extraction, and PdfPageText::search with PdfSearchOptions. Keywords: pdfium-render extract text, PdfPageText, page text all, PdfPageTextChar, PdfPageTextSegment, text search, PdfSearchOptions, PdfSearchDirection, chars_for_object, is_generated, is_hyphen, how do I extract text from a PDF, text extraction misses words, garbled text from PDF, search text in PDF, unicode_char

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

---


# pdfium-syntax-text

Text extraction and search both start from `PdfPage::text()`, which returns a
`PdfPageText` handle. From that handle you read the whole page as a `String`,
walk individual characters, extract text inside a rectangle, or run a search.

Scope: reading and searching text on a page. For page objects and the
read-versus-edit split see `pdfium-syntax-page-objects`; for the units used by
bounds and rectangles see `pdfium-core-coordinates`.

## Quick Reference

### Entry point

```rust
let text = page.text()?;          // Result<PdfPageText<'_>, PdfiumError>
let content: String = text.all(); // whole-page text
```

`PdfPageText` borrows the `PdfPage` and implements `Drop` (it closes the
underlying PDFium text page). Keep it only as long as you need it.

### The text API map (0.9.x)

| You want | Call | Returns |
|----------|------|---------|
| whole-page text | `text.all()` | `String` |
| text inside a rectangle | `text.inside_rect(rect)` | `String` |
| text of one text object | `text.for_object(&obj)` | `String` |
| every character | `text.chars()` | `PdfPageTextChars` |
| characters in a rectangle | `text.chars_inside_rect(rect)` | `Result<PdfPageTextChars, _>` |
| characters of one object | `text.chars_for_object(&obj)` | `Result<PdfPageTextChars, _>` |
| rectangular text segments | `text.segments()` | `PdfPageTextSegments` |
| search for a string | `text.search(needle, &options)` | `Result<PdfPageTextSearch, _>` |

### Core rules

- ALWAYS get text through `page.text()?`. There is no text accessor on
  `PdfDocument`; text is per page.
- ALWAYS use `text.all()` for plain whole-page extraction. It already covers
  text nested inside XObject form objects.
- NEVER assume `text.for_object()` / `chars_for_object()` reaches nested text:
  when you walk `page.objects()` yourself you MUST recurse into
  `PdfPageXObjectFormObject` containers (see `pdfium-syntax-page-objects`).
- NEVER trust `chars_for_object()` on a PDFium build older than 6611: it
  returned wrong characters before pdfium-render 0.8.31 (issue #98).
- ALWAYS treat `unicode_char()` as `Option<char>`: PDFium returns `None` for
  characters with no single-`char` mapping. NEVER `.unwrap()` it blindly.
- ALWAYS handle `is_generated()` / `is_hyphen()` as `Result<bool, PdfiumError>`,
  not as a plain `bool`.

## Decision Trees

### Which text accessor do I use?

```text
What do you need?
  The full readable text of the page        -> text.all()
  Text within a known rectangle              -> text.inside_rect(rect)
  Per-character data (position, font, flags) -> text.chars(), then iterate
  Text belonging to one text object          -> text.for_object(&text_object)
  Rectangular layout blocks (lines / runs)   -> text.segments(), then iterate
  Find where a string appears                -> text.search(needle, &options)
```

### How do I find every occurrence of a string?

```text
Build PdfSearchOptions with new() and the builder methods you need:
  case-sensitive   -> .match_case(true)
  whole words only -> .match_whole_word(true)
Call text.search(needle, &options)?
Iterate matches with .iter(PdfSearchDirection::SearchForward).
Each match is a PdfPageTextSegments collection: iterate its segments for the
matched text and its bounding rectangles.
```

## Patterns

### Pattern 1: Extract whole-page text

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

fn page_text(page: &PdfPage) -> Result<String, PdfiumError> {
    Ok(page.text()?.all())
}
```

`all()` returns every readable character on the page, including text inside
XObject form containers. This is the correct default for plain extraction.

### Pattern 2: Iterate characters

```rust
fn visible_chars(page: &PdfPage) -> Result<String, PdfiumError> {
    let text = page.text()?;
    let mut out = String::new();
    for ch in text.chars().iter() {
        // is_generated() is a Result: a generated char is one PDFium
        // synthesized (such as a space between words) that is not in the
        // content stream itself.
        if ch.is_generated()? {
            continue;
        }
        if let Some(c) = ch.unicode_char() {
            out.push(c);
        }
    }
    Ok(out)
}
```

`PdfPageTextChar` also exposes `tight_bounds()`, `loose_bounds()`, `origin()`,
`font_name()`, `scaled_font_size()`, and `index()`; see `references/methods.md`.

### Pattern 3: Text inside a rectangle

```rust
fn text_in_region(page: &PdfPage, rect: PdfRect) -> Result<String, PdfiumError> {
    Ok(page.text()?.inside_rect(rect))
}
```

`inside_rect` returns a `String`; `chars_inside_rect` returns the matching
`PdfPageTextChars`. Construct a `PdfRect` per `pdfium-core-coordinates`; PDFium
coordinates have their origin at the bottom-left of the page.

### Pattern 4: Search a page

```rust
fn find_all(page: &PdfPage, needle: &str) -> Result<usize, PdfiumError> {
    let text = page.text()?;
    let options = PdfSearchOptions::new().match_whole_word(true);
    let search = text.search(needle, &options)?;
    let mut count = 0;
    for matched in search.iter(PdfSearchDirection::SearchForward) {
        // `matched` is a PdfPageTextSegments collection: one match.
        for segment in matched.iter() {
            let _matched_text: String = segment.text();
            let _box: PdfRect = segment.bounds();
        }
        count += 1;
    }
    Ok(count)
}
```

`PdfSearchOptions` defaults to case-insensitive, substring matching. Use
`.match_case(true)` and `.match_whole_word(true)` to narrow it.

### Pattern 5: Text of a single object

```rust
fn object_text(text: &PdfPageText, object: &PdfPageTextObject) -> String {
    text.for_object(object)
}
```

`for_object` and `chars_for_object` take a `&PdfPageTextObject`, NOT the
general `PdfPageObject` enum. When you walk `page.objects()` to collect text
object by object, a `PdfPageXObjectFormObject` is a container: its child text
objects are reached only by recursing. See `pdfium-syntax-page-objects`.

## Version Notes: 0.8.x vs 0.9.x

- `PdfPageTextSegment` and `PdfPageTextChar` were added in 0.7.6.
- `PdfSearchOptions` and `PdfPageText::search()` were added in 0.8.13.
- `PdfPageTextChar::text_object()` was added in 0.8.23.
- `PdfPageTextChar::is_hyphen()` was added in 0.8.34.
- `chars_for_object()` returns correct characters only with PDFium build 6611
  or newer; the fix landed in pdfium-render 0.8.31 (issue #98). Pin a
  `pdfium_*` feature of `pdfium_6611` or higher.
- 0.9.1 relaxed lifetime restrictions on `PdfPageTextSegment` and
  `PdfPageTextChars`, which makes storing them in your own types easier.

## Anti-Patterns (summary)

| Anti-pattern | Why it fails | Fix |
|--------------|--------------|-----|
| object-by-object text without recursing | XObject form containers hide child text objects | recurse into `as_x_object_form_object()`, or use `text.all()` |
| `chars_for_object` on PDFium build < 6611 | returned wrong characters before the #98 fix | pin `pdfium_6611`+ and pdfium-render 0.8.31+ |
| `unicode_char().unwrap()` | PDFium returns `None` for chars with no single-`char` mapping | match the `Option`, or use `unicode_string()` |
| treating `is_generated()` as `bool` | it returns `Result<bool, PdfiumError>` | propagate with `?` |
| `search` with an empty needle | an empty search string yields no useful matches | guard against an empty needle before searching |

Full failure transcripts and fixes are in `references/anti-patterns.md`.

## Reference Files

- `references/methods.md`: exact verified signatures and version annotations
  for `PdfPageText`, `PdfPageTextChar(s)`, `PdfPageTextSegment(s)`,
  `PdfPageTextSearch`, and `PdfSearchOptions`.
- `references/examples.md`: complete, verified Rust programs for whole-page
  extraction, character iteration, region extraction, and search.
- `references/anti-patterns.md`: each text-extraction failure with the cause
  and the corrected code.

## Related Skills

- `pdfium-syntax-pages`: getting a `PdfPage` from the document.
- `pdfium-syntax-page-objects`: walking `page.objects()` and the XObject form
  recursion needed for object-level text.
- `pdfium-core-coordinates`: `PdfRect`, `PdfPoints`, and the PDF coordinate
  origin used by bounds and region accessors.
- `pdfium-impl-fonts`: font handling and the garbled-text failure mode.

