# Pdfium Syntax Page Objects

> Use when reading or inspecting the content objects on a PDF page with pdfium-render: counting them, iterating them, telling text from images from paths, reading an object's bounding box, or reaching objects nested inside an XObject form. Prevents the inspection mistakes: reading bounds() as a PdfRect after the 0.8.28 type change, indexing objects with the wrong integer type, missing objects nested inside XObject form containers, and unwrapping a narrowing call that returned None. Covers PdfPage::objects, the PdfPageObjects collection, the six-variant PdfPageObject enum, the as_text_object family of narrowing methods, the PdfPageObjectCommon trait, and recursing into PdfPageXObjectFormObject. This skill is read and inspect only; creating and editing objects is a separate skill. Keywords: pdfium-render, page objects, PdfPageObject, PdfPageObjects, objects, iter, as_text_object, as_image_object, as_path_object, bounds, PdfQuadPoints, XObjectForm, nested objects, object type, PdfPageObjectCommon, has_transparency

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

---


# pdfium-render Page Objects (Read and Inspect)

A PDF page's visible content is a flat list of **page objects**: text runs,
vector paths, images, shadings, and XObject form containers. This skill
covers reading and inspecting that list: reaching it, iterating it,
discriminating object types, reading object geometry, and recursing into
nested containers.

This skill is **read and inspect only**. Creating, transforming, and deleting
page objects is the scope of pdfium-impl-page-objects-edit.

## Quick Reference: Object Access Flow

```text
PdfPage
  .objects()  -> &PdfPageObjects        immutable collection (inspect)

PdfPageObjects   (methods from the PdfPageObjectsCommon trait)
  .len()       -> usize
  .is_empty()  -> bool
  .get(index)  -> Result<PdfPageObject, PdfiumError>   index is usize
  .first()     -> Result<PdfPageObject, PdfiumError>
  .last()      -> Result<PdfPageObject, PdfiumError>
  .iter()      -> PdfPageObjectsIterator   yields PdfPageObject

PdfPageObject  (enum, six variants)
  match it, or narrow with .as_text_object() etc.

PdfPageObjectCommon  (trait on every object)
  .bounds()           -> Result<PdfQuadPoints, PdfiumError>
  .width() .height()  -> Result<PdfPoints, PdfiumError>
  .has_transparency() -> bool
  .is_inside_rect(&r) / .does_overlap_rect(&r) -> bool
```

ALWAYS reach page objects through `PdfPage::objects()`. NEVER assume a page
object can be inspected standalone; it borrows from its `PdfPage`.

## 1. Reaching the Object Collection

`PdfPage::objects()` returns `&PdfPageObjects<'a>`, the immutable collection
of every content object on the page.

```rust
pub fn objects(&self) -> &PdfPageObjects<'a>
```

For mutation (adding, transforming, removing objects) the page exposes
`objects_mut()`; that path is documented in pdfium-impl-page-objects-edit.
This skill uses only the immutable `objects()`.

`PdfPageObjects` gets its methods from the `PdfPageObjectsCommon` trait:

```rust
fn len(&self) -> usize
fn is_empty(&self) -> bool
fn get(&self, index: usize) -> Result<PdfPageObject<'a>, PdfiumError>
fn first(&self) -> Result<PdfPageObject<'a>, PdfiumError>
fn last(&self) -> Result<PdfPageObject<'a>, PdfiumError>
fn iter(&self) -> PdfPageObjectsIterator<'a>
```

Page objects are indexed by `usize`. This differs from pages, which use the
`PdfPageIndex` (`c_int`) type. ALWAYS index page objects with `usize`. An
out-of-range index returns `PdfiumError::PageObjectIndexOutOfBounds`.

## 2. The PdfPageObject Enum

`PdfPageObject<'a>` is an enum with **six** variants, each wrapping a concrete
object type:

```rust
pub enum PdfPageObject<'a> {
    Text(PdfPageTextObject<'a>),
    Path(PdfPagePathObject<'a>),
    Image(PdfPageImageObject<'a>),
    Shading(PdfPageShadingObject<'a>),
    XObjectForm(PdfPageXObjectFormObject<'a>),
    Unsupported(PdfPageUnsupportedObject<'a>),
}
```

- `Text` : a run of rendered text. Character-level reading is
  pdfium-syntax-text.
- `Path` : a vector path (lines, bezier curves, fills).
- `Image` : a raster image embedded on the page.
- `Shading` : a gradient or shading pattern.
- `XObjectForm` : a reusable content container holding nested page objects.
- `Unsupported` : an object type pdfium-render does not model further.

ALWAYS handle the `Unsupported` variant in a `match`. NEVER write a `match`
on only five variants and assume it compiles; the enum has six.

## 3. Discriminating Object Type

Two ways to tell what kind of object you have.

**A `match` on the enum** is exhaustive and the compiler enforces coverage:

```rust
for object in page.objects().iter() {
    match object {
        PdfPageObject::Text(text) => { /* inspect text object */ }
        PdfPageObject::Path(path) => { /* inspect path */ }
        PdfPageObject::Image(image) => { /* inspect image */ }
        PdfPageObject::Shading(shading) => { /* inspect shading */ }
        PdfPageObject::XObjectForm(form) => { /* recurse, see section 5 */ }
        PdfPageObject::Unsupported(_) => { /* skip */ }
    }
}
```

**Narrowing methods** return an `Option` and are useful when only one type
matters:

```rust
fn as_text_object(&self) -> Option<&PdfPageTextObject<'_>>
fn as_path_object(&self) -> Option<&PdfPagePathObject<'_>>
fn as_image_object(&self) -> Option<&PdfPageImageObject<'_>>
fn as_shading_object(&self) -> Option<&PdfPageShadingObject<'_>>
fn as_x_object_form_object(&self) -> Option<&PdfPageXObjectFormObject<'_>>
```

Each returns `Some(&...)` only when the variant matches, otherwise `None`.
Mutable `_mut` variants exist for the edit skill.

ALWAYS treat a narrowing call as fallible: it returns `None` for every other
variant. NEVER `unwrap()` an `as_*_object()` result unless the variant is
already known.

## 4. Common Properties: the PdfPageObjectCommon Trait

`PdfPageObjectCommon` is implemented by every concrete object type. It carries
the geometry and transparency reads that work regardless of object kind:

```rust
fn bounds(&self) -> Result<PdfQuadPoints, PdfiumError>
fn width(&self) -> Result<PdfPoints, PdfiumError>
fn height(&self) -> Result<PdfPoints, PdfiumError>
fn has_transparency(&self) -> bool
fn is_inside_rect(&self, rect: &PdfRect) -> bool
fn does_overlap_rect(&self, rect: &PdfRect) -> bool
```

`bounds()` returns a `PdfQuadPoints` (a quadrilateral, because an object can
be rotated). Call `.to_rect()` on it for an axis-aligned `PdfRect`. All
geometry is in `PdfPoints`; converting to pixels is pdfium-core-coordinates.

`is_inside_rect()` and `does_overlap_rect()` answer spatial queries directly,
which is simpler and more reliable than comparing `bounds()` coordinates by
hand.

ALWAYS use `is_inside_rect()` / `does_overlap_rect()` for "is this object in
this region" checks. NEVER hand-compare quadrilateral corners for a spatial
test when the trait already provides it.

## 5. Recursing into XObject Form Containers

A `PdfPageXObjectFormObject` is a content container: it holds its own list of
nested page objects. `PdfPage::objects().iter()` yields the form container as
a single object; it does **not** flatten the container's children into the
page-level iteration.

`PdfPageXObjectFormObject` implements the same `PdfPageObjectsCommon` access
methods, so its children are reached the same way:

```rust
fn iter(&self) -> PdfPageObjectsIterator<'a>
fn len(&self) -> usize
fn get(&self, index: usize) -> Result<PdfPageObject<'a>, PdfiumError>
```

To inspect every object on a page including nested ones, write a recursive
visitor: for each object, if it is an `XObjectForm`, recurse into its
`iter()`. The repository text-extraction example documents exactly this:
text can live inside XObject form containers, so a complete extractor needs
the recursive walk.

ALWAYS recurse into `XObjectForm` objects when the task must see all content.
NEVER assume a single flat `objects().iter()` reaches nested objects; it
stops at the container.

## 6. Version Traps

| Item | 0.8.x | 0.9.x | Consequence |
|------|-------|-------|-------------|
| `PdfPageObjectCommon::bounds()` return | `PdfRect` before 0.8.28, `Result<PdfQuadPoints, _>` from 0.8.28 | `Result<PdfQuadPoints, PdfiumError>` | Code reading `bounds()` as a `PdfRect` fails to compile on 0.8.28+ |
| Text-object character reads | wrong characters before 0.8.31 (issue #98) | corrected | Reading characters per text object needs pdfium-render 0.8.31+ and a PDFium build at or above chromium build 6611 |

When the compiler reports "expected `PdfRect`, found `PdfQuadPoints`" on a
`bounds()` call, append `.to_rect()` or handle the quadrilateral. Reading the
characters that belong to a specific text object is pdfium-syntax-text;
respect the PDFium build requirement there.

## Decision Tree: Inspecting Page Objects

```text
What do I need from the page's objects?

  How many objects
    -> page.objects().len()

  Walk every object on the page surface
    -> page.objects().iter()  then match the variant

  Only one kind (for example, every image)
    -> iter(), then .as_image_object() and keep the Some results

  An object's position or size
    -> PdfPageObjectCommon::bounds() / width() / height()

  Objects inside a region
    -> PdfPageObjectCommon::is_inside_rect() / does_overlap_rect()

  Everything including nested content
    -> recursive visitor : recurse into XObjectForm.iter()

  Create / move / delete an object
    -> not this skill : see pdfium-impl-page-objects-edit
```

## ALWAYS / NEVER

- ALWAYS reach page objects through `PdfPage::objects()`.
- ALWAYS index page objects with `usize` (not `PdfPageIndex`).
- ALWAYS handle all six `PdfPageObject` variants in a `match`, including
  `Unsupported`.
- ALWAYS treat `as_*_object()` as returning an `Option`.
- ALWAYS recurse into `XObjectForm` objects to see nested content.
- ALWAYS use `is_inside_rect()` / `does_overlap_rect()` for spatial tests.
- NEVER read `bounds()` as a `PdfRect` on 0.8.28 or later; it is a
  `PdfQuadPoints`.
- NEVER `unwrap()` a narrowing call without knowing the variant.
- NEVER assume `objects().iter()` flattens XObject form children.
- NEVER mutate objects through `objects()`; mutation needs `objects_mut()`
  and the edit skill.

## Cross-References

- **pdfium-impl-page-objects-edit** : creating, transforming, and removing
  page objects through `objects_mut()`.
- **pdfium-syntax-text** : reading text content and characters, including the
  PDFium build requirement for per-object character reads.
- **pdfium-core-coordinates** : `PdfQuadPoints`, `PdfRect`, `PdfPoints`, and
  converting object geometry to pixels.
- **pdfium-core-architecture** : where `PdfPageObjects` sits in the ownership
  tree.

## Reference Files

- `references/methods.md` : signatures of `PdfPage::objects()`, the
  `PdfPageObjectsCommon` and `PdfPageObjectCommon` traits, the
  `PdfPageObject` enum, and `PdfPageXObjectFormObject`, with version notes.
- `references/examples.md` : verified Rust code for counting, iterating,
  discriminating, reading geometry, and recursing into containers.
- `references/anti-patterns.md` : real inspection failures with the cause and
  the fix.

## Sources

API names and signatures verified on 2026-05-20 via WebFetch against the
package `SOURCES.md`:

- `https://docs.rs/pdfium-render/latest/pdfium_render/prelude/enum.PdfPageObject.html`
- `https://docs.rs/pdfium-render/latest/pdfium_render/prelude/struct.PdfPageObjects.html`
- `https://docs.rs/pdfium-render/latest/pdfium_render/prelude/trait.PdfPageObjectCommon.html`
- `https://docs.rs/pdfium-render/latest/pdfium_render/prelude/struct.PdfPageXObjectFormObject.html`
- `https://github.com/ajrcarey/pdfium-render` (README, version history,
  issue #98).

