# Pdfium Impl Page Manipulation

> Use when rotating, deleting, flattening, cropping, or watermarking the pages of a PDF with pdfium-render, or when reading and setting MediaBox, CropBox, BleedBox, TrimBox, or ArtBox page geometry. Prevents the borrow-checker failure from calling delete() on a borrowed page, the index-shift bug when deleting multiple pages, the flatten() form-fields trap, and lost edits when the content-regeneration strategy is Manual. Covers PdfPage::rotation/set_rotation/delete/flatten, PdfPageBoundaries box geometry, PdfPages::watermark, and content regeneration across pdfium-render 0.8.x and 0.9.x. Keywords: pdfium-render, page rotation, set_rotation, PdfPageRenderRotation, delete page, flatten, PdfPageBoundaries, PdfPageBoundaryBox, MediaBox, CropBox, BleedBox, TrimBox, ArtBox, crop PDF, watermark, regenerate_content, content_regeneration_strategy, rotated page looks wrong, page not deleted, form fields disappeared after flatten, edits lost after save, blank page, how do I rotate a PDF page, how to crop a PDF, how to add

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

---


# pdfium-render: Page Manipulation

Mutating whole pages of a `PdfDocument`: rotation, deletion, flattening, page-box
geometry (cropping), and watermarking. This skill is for operations ON a page as a
unit. For editing the objects INSIDE a page (text, paths, images) use
`pdfium-impl-page-objects-edit`. For navigating the page collection use
`pdfium-syntax-pages`.

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

| Operation | API | Receiver | Verified |
|-----------|-----|----------|----------|
| Read rotation | `PdfPage::rotation()` | `&self` | `Result<PdfPageRenderRotation, PdfiumError>` |
| Set rotation | `PdfPage::set_rotation(rotation)` | `&mut self` | returns `()` |
| Delete page | `PdfPage::delete()` | `self` (consumes) | `Result<(), PdfiumError>` |
| Flatten page | `PdfPage::flatten()` | `&mut self` | `Result<(), PdfiumError>` |
| Read a box | `PdfPageBoundaries::crop()` etc. | `&self` | `Result<PdfPageBoundaryBox, PdfiumError>` |
| Set a box | `PdfPageBoundaries::set_crop(rect)` etc. | `&mut self` | `Result<(), PdfiumError>` |
| Mutable boxes | `PdfPage::boundaries_mut()` | `&mut self` | `&mut PdfPageBoundaries<'a>` |
| Read boxes | `PdfPage::boundaries()` | `&self` | `&PdfPageBoundaries<'a>` |
| Watermark all pages | `PdfPages::watermark(closure)` | `&self` | `Result<(), PdfiumError>` |
| Commit edits | `PdfPage::regenerate_content()` | `&mut self` | `Result<(), PdfiumError>` |

`PdfPageRenderRotation` variants: `None`, `Degrees90`, `Degrees180`, `Degrees270`.
`PdfPageBoundaryBoxType` variants: `Media`, `Crop`, `Bleed`, `Trim`, `Art`, `Bounding`.

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

## How to Get a Mutable Page

Page mutation needs `&mut PdfPage` or an owned `PdfPage`. `PdfPages::get(index)`
returns an OWNED `PdfPage<'a>`, so binding it `mut` is enough:

```rust
let mut page = document.pages().get(0)?;   // owned PdfPage<'a>
page.set_rotation(PdfPageRenderRotation::Degrees90);
```

ALWAYS bind the page `mut` when you will rotate, flatten, regenerate, or call
`boundaries_mut()`. NEVER try to mutate a `PdfPage` reference yielded by
`pages().iter()` while the iterator borrow is live: the iterator holds `&PdfPages`
and the mutation needs an owned page. Iterate by index instead when mutating.

## Decision Tree: which operation

```
Need to change page orientation?
  -> set_rotation(PdfPageRenderRotation::DegreesNN)   [does NOT change MediaBox]

Need to remove a page from the document?
  -> get(index).delete()   [delete() CONSUMES the page]
  -> deleting MANY pages: delete highest index FIRST (indices shift down)

Need annotations / form fields baked into the page content?
  -> flatten()   [0.8.19+ reloads the page so the effect is visible]

Need to crop / resize the visible area?
  -> boundaries_mut().set_crop(PdfRect)   [cropping == setting the CropBox]

Need print production boxes (bleed/trim/art)?
  -> boundaries_mut().set_bleed/set_trim/set_art(PdfRect)

Need a stamp on every page?
  -> document.pages().watermark(closure)
```

## Pattern: Rotate a Page

`set_rotation` takes a `PdfPageRenderRotation` value. Rotation is page metadata; it
does NOT change the `MediaBox` dimensions, only how viewers and `render_with_config`
present the page.

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

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

let mut page = document.pages().get(0)?;
page.set_rotation(PdfPageRenderRotation::Degrees90);   // 90deg clockwise
drop(page);                                            // release the page borrow

document.save_to_file("rotated.pdf")?;
```

`PdfPageRenderRotation::as_degrees()` and `as_radians()` (both `const fn`) convert a
variant to a numeric clockwise rotation when you need the value.

ALWAYS use `PdfPageRenderRotation`. The old `PdfBitmapRotation` enum was deprecated
in 0.8.6 and REMOVED in 0.9.0; code referencing it will not compile against 0.9.x.

## Pattern: Delete a Page

`delete()` consumes the `PdfPage` by value (`pub fn delete(self) -> Result<...>`).
You MUST own the page; you cannot delete through a shared reference.

```rust
// Delete the third page (zero-based index 2).
document.pages().get(2)?.delete()?;
document.save_to_file("output.pdf")?;
```

Deleting a page shifts every later page's index down by one. When deleting MULTIPLE
pages, ALWAYS process indices from highest to lowest so earlier deletions never
invalidate the indices you have not reached yet:

```rust
let mut to_delete = vec![1, 3, 5];
to_delete.sort_unstable();
for index in to_delete.into_iter().rev() {        // 5, 3, 1
    document.pages().get(index)?.delete()?;
}
```

`delete()` was added in 0.7.30. Requesting an out-of-range index returns
`PdfiumError::PageIndexOutOfBounds` (a recoverable error). See `pdfium-errors-runtime`.

## Pattern: Flatten a Page

`flatten()` merges annotation and form-field appearances into the page's content
stream, producing a static page. It needs `&mut self`.

```rust
let mut page = document.pages().get(0)?;
page.flatten()?;
drop(page);
document.save_to_file("flattened.pdf")?;
```

Version behaviour (issue #140): before 0.8.19, rendering a page right after
`flatten()` still showed the unflattened content because the in-memory page was
stale; you had to drop and reload the page manually. From 0.8.19 `flatten()`
reloads the page itself, so the flattened result is immediately visible to
`render_with_config()`. ALWAYS target 0.8.19 or later when you flatten then render
in the same run. A failed flatten returns `PdfiumError::PageFlattenFailure`.

## Pattern: Crop and Page-Box Geometry

A PDF page carries up to five nested boxes. `pdfium-render` exposes them through
`PdfPageBoundaries` (the collection added in 0.5.2):

| Box | Meaning | Getter / Setter |
|-----|---------|-----------------|
| MediaBox | Full physical sheet | `media()` / `set_media()` |
| CropBox | Visible / clipped region | `crop()` / `set_crop()` |
| BleedBox | Production bleed area | `bleed()` / `set_bleed()` |
| TrimBox | Finished trimmed size | `trim()` / `set_trim()` |
| ArtBox | Meaningful content extent | `art()` / `set_art()` |

Cropping a page means SETTING the CropBox. It does not delete content; it changes
the region viewers and renderers display.

```rust
let mut page = document.pages().get(0)?;

// PdfRect::new_from_values order is (bottom, left, top, right) in PdfPoints units.
let crop = PdfRect::new_from_values(72.0, 72.0, 720.0, 540.0);
page.boundaries_mut().set_crop(crop)?;

drop(page);
document.save_to_file("cropped.pdf")?;
```

CRITICAL: `PdfRect::new` and `PdfRect::new_from_values` take arguments in the order
`(bottom, left, top, right)`, NOT `(left, top, right, bottom)`. Passing them in the
intuitive order silently produces an inverted or zero-area rectangle. Use
`PdfRect::new(PdfPoints, PdfPoints, PdfPoints, PdfPoints)` for typed values or
`new_from_values(f32, f32, f32, f32)` for raw points.

Each getter returns a `PdfPageBoundaryBox` with two public fields:
`box_type: PdfPageBoundaryBoxType` and `bounds: PdfRect`.

```rust
let media = page.boundaries().media()?;
println!("MediaBox: {:?}", media.bounds);   // PdfRect
```

`PdfPageBoundaries::get(PdfPageBoundaryBoxType)` and `set(box_type, rect)` are the
generic equivalents of the named methods.

## Pattern: Watermark Every Page

`PdfPages::watermark` applies a closure to every page. The closure receives a
mutable `PdfPageGroupObject` to fill, the page index, and the page width and height
as `PdfPoints`:

```rust
PdfPages::watermark<F>(&self, watermarker: F) -> Result<(), PdfiumError>
where
    F: Fn(&mut PdfPageGroupObject<'a>, PdfPageIndex, PdfPoints, PdfPoints)
        -> Result<(), PdfiumError>
```

```rust
document.pages().watermark(|group, _index, width, height| {
    // Build watermark objects (text / image) and add them to `group`.
    // Object construction is covered by pdfium-impl-page-objects-edit.
    // `width` and `height` are PdfPoints; use them to centre the stamp.
    let _ = (group, width, height);
    Ok(())
})?;
document.save_to_file("watermarked.pdf")?;
```

ALWAYS build the watermark's page objects inside the closure with the techniques in
`pdfium-impl-page-objects-edit`; this skill owns the `watermark()` entry point and
its closure contract, not object construction.

## Content Regeneration

Page mutations (rotation, box changes, object edits) must be committed to the
underlying document before saving. `PdfPageContentRegenerationStrategy` controls
when that happens:

| Strategy | When content regenerates |
|----------|--------------------------|
| `AutomaticOnEveryChange` | After every mutating call. This is the DEFAULT. |
| `AutomaticOnDrop` | Once, when the `PdfPage` is dropped. |
| `Manual` | Never automatically; you must call `regenerate_content()`. |

With the default strategy, `set_rotation` and `set_crop` commit immediately and a
following `save_to_file` is correct. If you switch to `Manual` (with
`set_content_regeneration_strategy`) for batched edits, ALWAYS call
`regenerate_content()` before dropping the page or saving, or the edits are lost.

```rust
let mut page = document.pages().get(0)?;
page.set_content_regeneration_strategy(PdfPageContentRegenerationStrategy::Manual);
page.set_rotation(PdfPageRenderRotation::Degrees180);
page.boundaries_mut().set_crop(PdfRect::new_from_values(0.0, 0.0, 600.0, 800.0))?;
page.regenerate_content()?;                 // commit the batch
drop(page);
document.save_to_file("output.pdf")?;
```

Full working programs: see `references/examples.md`.

## Version Traps

| Item | 0.8.x | 0.9.x | Action |
|------|-------|-------|--------|
| `PdfBitmapRotation` | deprecated 0.8.6 | REMOVED | ALWAYS use `PdfPageRenderRotation` |
| `PdfPage::delete()` | added 0.7.30 | present | available on every supported version |
| `flatten()` reload | manual reload before 0.8.19 | reloads page | target >= 0.8.19 to flatten then render |
| `PdfPageBoundaries` | added 0.5.2 | present | available on every supported version |
| `PdfPageIndex` | `u16` | `c_int` (0.9.0) | do not hard-type the index |

## Anti-Patterns (summary)

- Calling `delete()` on a page reference from `pages().iter()`: `delete()` consumes
  `self`; you need an owned page from `pages().get(index)`.
- Deleting multiple pages low-index-first: indices shift; delete highest first.
- `PdfRect` argument order assumed `(left, top, right, bottom)`: it is
  `(bottom, left, top, right)`.
- Expecting `set_rotation` to change page dimensions: it does not touch the MediaBox.
- Switching to `Manual` regeneration and forgetting `regenerate_content()`.

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

## Cross-References

- `pdfium-syntax-pages`: the `PdfPages` collection, `get`, `iter`, `len`, indexing.
- `pdfium-impl-multi-page`: appending, importing, reordering pages between documents.
- `pdfium-impl-page-objects-edit`: constructing the objects used inside `watermark`.
- `pdfium-core-coordinates`: `PdfPoints`, `PdfRect`, the bottom-left PDF origin.
- `pdfium-impl-saving`: `save_to_file`, `save_to_bytes`, `save_to_writer`.
- `pdfium-errors-runtime`: `PageIndexOutOfBounds`, `PageFlattenFailure`.

## 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 `PdfPage`, `PdfPages`, `PdfPageBoundaries`,
`PdfPageBoundaryBox`, `PdfRect`, `PdfPoints`, and the `PdfPageRenderRotation` /
`PdfPageBoundaryBoxType` / `PdfPageContentRegenerationStrategy` enums).

