| name | pdfium-impl-page-manipulation |
| description | 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 a watermark.
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires pdfium-render 0.8,0.9. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
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:
let mut page = document.pages().get(0)?;
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.
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);
drop(page);
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.
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:
let mut to_delete = vec![1, 3, 5];
to_delete.sort_unstable();
for index in to_delete.into_iter().rev() {
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.
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.
let mut page = document.pages().get(0)?;
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.
let media = page.boundaries().media()?;
println!("MediaBox: {:?}", media.bounds);
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:
PdfPages::watermark<F>(&self, watermarker: F) -> Result<(), PdfiumError>
where
F: Fn(&mut PdfPageGroupObject<'a>, PdfPageIndex, PdfPoints, PdfPoints)
-> Result<(), PdfiumError>
document.pages().watermark(|group, _index, width, height| {
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.
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()?;
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).