# Pdfium Core Coordinates

> Use when working with page sizes, object positions, rendering dimensions, or click-to-document mapping in pdfium-render, and you need to know which unit a value is in and how to convert it. Prevents the unit-confusion bugs: treating PdfPoints as pixels, converting points to pixels without a render config, forgetting the bottom-left to top-left y-axis flip when overlaying PDF coordinates on a raster image, and the 0.8.28 bounds() return-type change. Covers PdfPoints (the 1/72 inch document unit), Pixels (the raster unit, i32), PdfPage::points_to_pixels and pixels_to_points, PdfRect versus PdfQuadPoints, and the PDF coordinate origin. Keywords: pdfium-render, coordinates, units, PdfPoints, Pixels, points_to_pixels, pixels_to_points, PdfRect, PdfQuadPoints, bounds, page size, DPI, scale, origin, y-axis flip, bounding box, "wrong size", "object in wrong place", "coordinates upside down", "image is too small", "bounds does not compile", "expected PdfRect found PdfQuadPoints", "how big is a PDF page", "convert poin

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

---


# pdfium-render Coordinates and Units

pdfium-render uses two coordinate systems with different units and different
origins. Mixing them is the root cause of "object in the wrong place",
"rendered image is the wrong size", and "coordinates are upside down" bugs.
This skill states which unit each value is in and how to convert correctly.

## Quick Reference: Two Coordinate Systems

| Aspect | PDF user space | Raster space |
|--------|----------------|--------------|
| Unit type | `PdfPoints` (`f32`, 1/72 inch) | `Pixels` (`type Pixels = i32`) |
| Origin | Bottom-left of the page | Top-left of the bitmap |
| Y axis | Increases **upward** | Increases **downward** |
| Used by | `PdfPage::width()`, object `bounds()`, `PdfRect`, `PdfQuadPoints` | `PdfBitmap::width()`, `PdfRenderConfig` targets |
| Resolution | Resolution-independent | Depends on the render config |

ALWAYS know which system a value belongs to before doing arithmetic with it.
NEVER add or compare a `PdfPoints` value to a `Pixels` value directly,
because they are different units with different origins.

## 1. PdfPoints: the Document Unit

`PdfPoints` is the device-independent unit of the PDF document interior. The
docs define it: "the internal coordinate system inside a PdfDocument is
measured in Points, a device-independent unit equal to 1/72 inches, roughly
0.358 mm. Points are converted to pixels when a PdfPage is rendered into a
PdfBitmap."

```rust
pub struct PdfPoints { pub value: f32 }
```

`PdfPoints` wraps a single `f32` in its public `value` field. It carries
constants `ZERO`, `MAX`, `MIN` and physical-unit conversions
`from_inches`/`from_cm`/`from_mm` and `to_inches`/`to_cm`/`to_mm`. These
types return `PdfPoints` and stay inside PDF user space:

- `PdfPage::width()` and `height()` return `PdfPoints`.
- `PdfRect` and `PdfQuadPoints` measure their corners in `PdfPoints`.
- `PdfPageObjectCommon::bounds()`, `width()`, `height()` return `PdfPoints`.

A US Letter page is `612.0 x 792.0` points (8.5 x 11 inches). A4 is
`595.28 x 841.89` points. ALWAYS read a page size as `PdfPoints` and use the
physical-unit helpers when a real-world measurement is required.

## 2. Pixels: the Raster Unit

`Pixels` is the unit of a rendered raster image. It is a plain type alias:

```rust
pub type Pixels = i32;
```

Pixels appear only after rendering. `PdfBitmap::width()` and `height()`
return `Pixels`. `PdfRenderConfig` builder methods (`set_target_width`,
`set_maximum_height`, the `set_fixed_*` family) take `Pixels`. A page has no
pixel size until a `PdfRenderConfig` decides one.

NEVER treat `PdfPage::width()` as a pixel count. It is points. The pixel size
is whatever the render config produces. See pdfium-syntax-rendering.

## 3. Converting Between Points and Pixels

Conversion is **config-dependent**: the same point value maps to different
pixel values depending on the render config's scale and target size. Two
`PdfPage` methods do the conversion, both added in **0.8.12**, both requiring
a `&PdfRenderConfig`:

```rust
pub fn points_to_pixels(
    &self,
    x: PdfPoints,
    y: PdfPoints,
    config: &PdfRenderConfig,
) -> Result<(Pixels, Pixels), PdfiumError>

pub fn pixels_to_points(
    &self,
    x: Pixels,
    y: Pixels,
    config: &PdfRenderConfig,
) -> Result<(PdfPoints, PdfPoints), PdfiumError>
```

ALWAYS use the same `PdfRenderConfig` for conversion that you use (or will
use) to render the page. The conversion is only meaningful relative to that
config. NEVER hardcode a points-to-pixels factor such as "multiply by 72" or
"multiply by 96 / 72"; that ignores the config's scale and target dimensions
and produces values that do not match the actual bitmap.

Note: there is **no** `PdfPoints::from_pixels` constructor. Pixels become
points only through `PdfPage::pixels_to_points` with a config.

## 4. The Origin Flip

The two systems do not share an origin. This is the most common silent bug.

```text
PDF user space (points)          Raster space (pixels)

  y                                +---------------> x
  ^                                |
  |                                |
  |                                |
  +---------------> x              v
 (0,0) bottom-left                 y   (0,0) top-left
```

When PDFium **renders** a page to a `PdfBitmap` it performs this flip for
you: the rendered image looks correct. The flip becomes your responsibility
when you take a coordinate from PDF user space (an object's `bounds()`, a
`PdfRect`) and want to draw or hit-test it on the **raster image** with an
external library.

To map a PDF y-coordinate onto raster pixels:

```text
raster_y_px  =  page_height_px  -  pdf_y_px
```

ALWAYS flip the y-axis when overlaying point-space geometry on a raster
image. NEVER assume a `bounds()` rectangle can be drawn directly with a
top-left-origin image library; its y values grow the opposite direction.
The x-axis does not flip.

## 5. PdfRect vs PdfQuadPoints

Two rectangle-like types exist, both measured in `PdfPoints`.

`PdfRect` is an **axis-aligned** rectangle. Its corners are named by edge:
`bottom`, `left`, `top`, `right`.

```rust
PdfRect::new(bottom: PdfPoints, left: PdfPoints, top: PdfPoints, right: PdfPoints) -> Self
PdfRect::new_from_values(bottom: f32, left: f32, top: f32, right: f32) -> Self
PdfRect::ZERO            // (0.0, 0.0, 0.0, 0.0)
fn width(&self) -> PdfPoints
fn height(&self) -> PdfPoints
fn contains(&self, x: PdfPoints, y: PdfPoints) -> bool
fn is_inside(&self, other: &PdfRect) -> bool
fn does_overlap(&self, other: &PdfRect) -> bool
fn to_quad_points(&self) -> PdfQuadPoints
```

`PdfQuadPoints` is a **quadrilateral**: four independent vertices in
counter-clockwise order, `(x1,y1)` through `(x4,y4)`. It can represent a
**rotated** box, which an axis-aligned `PdfRect` cannot.

```rust
PdfQuadPoints::new(x1, y1, x2, y2, x3, y3, x4, y4)            // eight PdfPoints
PdfQuadPoints::new_from_values(x1, y1, x2, y2, x3, y3, x4, y4) // eight f32
PdfQuadPoints::from_rect(rect: &PdfRect) -> Self
fn to_rect(&self) -> PdfRect   // smallest enclosing axis-aligned rectangle
fn width(&self) -> PdfPoints
fn height(&self) -> PdfPoints
```

Convert between them with `PdfRect::to_quad_points()` and
`PdfQuadPoints::to_rect()`. ALWAYS use `to_rect()` when you need a simple
axis-aligned box from a `bounds()` result. NEVER assume a `PdfQuadPoints` is
axis-aligned; a rotated text object has a slanted quadrilateral, and reading
only `x1`/`y1` as a corner of an upright box gives the wrong region.

## 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 against 0.8.28+ |
| `points_to_pixels` / `pixels_to_points` | added 0.8.12; absent before | present | Not available on pre-0.8.12 |

When upgrading and the compiler reports "expected `PdfRect`, found
`PdfQuadPoints`" on a `bounds()` call, append `.to_rect()` to restore an
axis-aligned rectangle, or rework the code to handle the quadrilateral.

## Decision Tree: Which Conversion

```text
I have a value and need another unit.

  Page size in a physical unit (mm, inches)
    -> PdfPage::width()/height() give PdfPoints
       -> PdfPoints::to_mm() / to_inches()

  Object position for drawing on the rendered image
    -> bounds() gives PdfQuadPoints (points)
       -> .to_rect() for an axis-aligned PdfRect
       -> PdfPage::points_to_pixels(x, y, &config) per corner
       -> flip y : raster_y = bitmap_height - y_px

  User clicked pixel (px, py) on the rendered image, need the PDF location
    -> flip y back : pdf_y_px = bitmap_height - py
    -> PdfPage::pixels_to_points(px, pdf_y_px, &config)

  Rotated bounding box
    -> keep PdfQuadPoints, do NOT collapse to PdfRect
```

## ALWAYS / NEVER

- ALWAYS know whether a value is `PdfPoints` or `Pixels` before arithmetic.
- ALWAYS convert points to pixels with `points_to_pixels` and the actual
  `PdfRenderConfig`; the result is config-dependent.
- ALWAYS flip the y-axis when mapping point-space geometry onto a
  top-left-origin raster image.
- ALWAYS use `PdfQuadPoints::to_rect()` to get an axis-aligned box from a
  `bounds()` result.
- NEVER treat `PdfPage::width()`/`height()` as pixel counts; they are points.
- NEVER hardcode a points-to-pixels multiplier; it ignores the render config.
- NEVER look for `PdfPoints::from_pixels`; it does not exist. Use
  `pixels_to_points`.
- NEVER read a `PdfQuadPoints` as if it were axis-aligned.
- NEVER read `bounds()` as a `PdfRect` on 0.8.28 or later.

## Cross-References

- **pdfium-syntax-rendering** : `PdfRenderConfig`, `PdfBitmap`, how target
  dimensions and scale determine the pixel size.
- **pdfium-syntax-page-objects** : `PdfPageObjectCommon::bounds()` and object
  geometry.
- **pdfium-impl-page-manipulation** : page boundary boxes and cropping, which
  also use `PdfPoints`.
- **pdfium-core-architecture** : where `PdfPage` and page objects sit in the
  ownership tree.

## Reference Files

- `references/methods.md` : signatures of `PdfPoints`, `Pixels`, `PdfRect`,
  `PdfQuadPoints`, and the `PdfPage` conversion methods with version notes.
- `references/examples.md` : verified Rust code for reading sizes, converting
  units, the y-axis flip, and quadrilateral handling.
- `references/anti-patterns.md` : real unit-confusion 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/struct.PdfPoints.html`
- `https://docs.rs/pdfium-render/latest/pdfium_render/prelude/type.Pixels.html`
- `https://docs.rs/pdfium-render/latest/pdfium_render/prelude/struct.PdfRect.html`
- `https://docs.rs/pdfium-render/latest/pdfium_render/prelude/struct.PdfQuadPoints.html`
- `https://docs.rs/pdfium-render/latest/pdfium_render/prelude/struct.PdfPage.html`
- `https://docs.rs/pdfium-render/latest/pdfium_render/prelude/trait.PdfPageObjectCommon.html`

