pdfium_render/pdf/document/page.rs
1//! Defines the [PdfPage] struct, exposing functionality related to a single page in a
2//! [PdfPages] collection.
3
4pub mod annotation;
5pub mod annotations;
6pub mod boundaries;
7pub mod field;
8pub(crate) mod index_cache;
9pub mod links;
10pub mod object;
11pub mod objects;
12pub mod render_config;
13pub mod size;
14pub mod text;
15
16#[cfg(feature = "paragraph")]
17pub mod paragraph;
18
19#[cfg(feature = "flatten")]
20mod flatten; // Keep internal flatten operation private.
21
22use object::ownership::PdfPageObjectOwnership;
23
24use crate::bindgen::{
25 FLATTEN_FAIL, FLATTEN_NOTHINGTODO, FLATTEN_SUCCESS, FLAT_PRINT, FPDF_DOCUMENT, FPDF_FORMHANDLE,
26 FPDF_PAGE,
27};
28use crate::bindings::PdfiumLibraryBindings;
29use crate::create_transform_setters;
30use crate::error::{PdfiumError, PdfiumInternalError};
31use crate::pdf::bitmap::{PdfBitmap, PdfBitmapFormat, Pixels};
32use crate::pdf::document::page::annotations::PdfPageAnnotations;
33use crate::pdf::document::page::boundaries::PdfPageBoundaries;
34use crate::pdf::document::page::index_cache::PdfPageIndexCache;
35use crate::pdf::document::page::links::PdfPageLinks;
36use crate::pdf::document::page::objects::common::PdfPageObjectsCommon;
37use crate::pdf::document::page::objects::PdfPageObjects;
38use crate::pdf::document::page::render_config::{PdfPageRenderSettings, PdfRenderConfig};
39use crate::pdf::document::page::size::PdfPagePaperSize;
40use crate::pdf::document::page::text::PdfPageText;
41use crate::pdf::font::PdfFont;
42use crate::pdf::matrix::{PdfMatrix, PdfMatrixValue};
43use crate::pdf::points::PdfPoints;
44use crate::pdf::rect::PdfRect;
45use std::collections::{hash_map::Entry, HashMap};
46use std::f32::consts::{FRAC_PI_2, PI};
47use std::os::raw::{c_double, c_int};
48
49#[cfg(doc)]
50use crate::pdf::document::{PdfDocument, PdfPages};
51
52/// The orientation of a [PdfPage].
53#[derive(Copy, Clone, Debug, PartialEq)]
54pub enum PdfPageOrientation {
55 Portrait,
56 Landscape,
57}
58
59impl PdfPageOrientation {
60 #[inline]
61 pub(crate) fn from_width_and_height(width: PdfPoints, height: PdfPoints) -> Self {
62 if width.value > height.value {
63 PdfPageOrientation::Landscape
64 } else {
65 PdfPageOrientation::Portrait
66 }
67 }
68}
69
70/// A rotation transformation that should be applied to a [PdfPage] when it is rendered
71/// into a [PdfBitmap].
72#[derive(Copy, Clone, Debug, PartialEq)]
73pub enum PdfPageRenderRotation {
74 None,
75 Degrees90,
76 Degrees180,
77 Degrees270,
78}
79
80impl PdfPageRenderRotation {
81 #[inline]
82 pub(crate) fn from_pdfium(value: i32) -> Result<Self, PdfiumError> {
83 match value {
84 0 => Ok(PdfPageRenderRotation::None),
85 1 => Ok(PdfPageRenderRotation::Degrees90),
86 2 => Ok(PdfPageRenderRotation::Degrees180),
87 3 => Ok(PdfPageRenderRotation::Degrees270),
88 _ => Err(PdfiumError::UnknownBitmapRotation),
89 }
90 }
91
92 #[inline]
93 pub(crate) fn as_pdfium(&self) -> i32 {
94 match self {
95 PdfPageRenderRotation::None => 0,
96 PdfPageRenderRotation::Degrees90 => 1,
97 PdfPageRenderRotation::Degrees180 => 2,
98 PdfPageRenderRotation::Degrees270 => 3,
99 }
100 }
101
102 /// Returns the equivalent clockwise rotation of this [PdfPageRenderRotation] variant, in degrees.
103 #[inline]
104 pub const fn as_degrees(&self) -> f32 {
105 match self {
106 PdfPageRenderRotation::None => 0.0,
107 PdfPageRenderRotation::Degrees90 => 90.0,
108 PdfPageRenderRotation::Degrees180 => 180.0,
109 PdfPageRenderRotation::Degrees270 => 270.0,
110 }
111 }
112
113 pub(crate) const DEGREES_90_AS_RADIANS: f32 = FRAC_PI_2;
114
115 pub(crate) const DEGREES_180_AS_RADIANS: f32 = PI;
116
117 pub(crate) const DEGREES_270_AS_RADIANS: f32 = FRAC_PI_2 + PI;
118
119 /// Returns the equivalent clockwise rotation of this [PdfPageRenderRotation] variant, in radians.
120 #[inline]
121 pub const fn as_radians(&self) -> f32 {
122 match self {
123 PdfPageRenderRotation::None => 0.0,
124 PdfPageRenderRotation::Degrees90 => Self::DEGREES_90_AS_RADIANS,
125 PdfPageRenderRotation::Degrees180 => Self::DEGREES_180_AS_RADIANS,
126 PdfPageRenderRotation::Degrees270 => Self::DEGREES_270_AS_RADIANS,
127 }
128 }
129}
130
131// TODO: AJRC - 19/6/23 - remove deprecated PdfBitmapRotation type in 0.9.0
132// as part of tracking issue https://github.com/ajrcarey/pdfium-render/issues/36
133#[deprecated(
134 since = "0.8.6",
135 note = "This enum has been renamed to better reflect its purpose. Use the PdfPageRenderRotation enum instead."
136)]
137#[doc(hidden)]
138pub type PdfBitmapRotation = PdfPageRenderRotation;
139
140/// Content regeneration strategies that instruct `pdfium-render` when, if ever, it should
141/// automatically regenerate the content of a [PdfPage].
142///
143/// Updates to a [PdfPage] are not committed to the underlying [PdfDocument] until the page's
144/// content is regenerated. If a page is reloaded or closed without regenerating the page's
145/// content, any changes not applied are lost.
146///
147/// By default, `pdfium-render` will trigger content regeneration on any change to a [PdfPage];
148/// this removes the possibility of data loss, and ensures changes can be read back from other
149/// data structures as soon as they are made. However, if many changes are made to a page at once,
150/// then regenerating the content after every change is inefficient; it is faster to stage
151/// all changes first, then regenerate the page's content just once. In this case,
152/// changing the content regeneration strategy for a [PdfPage] can improve performance,
153/// but you must be careful not to forget to commit your changes before the [PdfPage] moves out of scope.
154#[derive(Copy, Clone, Debug, PartialEq)]
155pub enum PdfPageContentRegenerationStrategy {
156 /// `pdfium-render` will call the [PdfPage::regenerate_content()] function on any
157 /// change to this [PdfPage]. This is the default setting.
158 AutomaticOnEveryChange,
159
160 /// `pdfium-render` will call the [PdfPage::regenerate_content()] function only when
161 /// this [PdfPage] is about to move out of scope.
162 AutomaticOnDrop,
163
164 /// `pdfium-render` will never call the [PdfPage::regenerate_content()] function.
165 /// You must do so manually after staging your changes, or your changes will be lost
166 /// when this [PdfPage] moves out of scope.
167 Manual,
168}
169
170/// A single page in a `PdfDocument`.
171///
172/// In addition to its own intrinsic properties, a [PdfPage] serves as the entry point
173/// to all object collections related to a single page in a document. These collections include:
174/// * [PdfPage::annotations()], an immutable collection of all the user annotations attached to the [PdfPage].
175/// * [PdfPage::annotations_mut()], a mutable collection of all the user annotations attached to the [PdfPage].
176/// * [PdfPage::boundaries()], an immutable collection of the boundary boxes relating to the [PdfPage].
177/// * [PdfPage::boundaries_mut()], a mutable collection of the boundary boxes relating to the [PdfPage].
178/// * [PdfPage::links()], an immutable collection of the links on the [PdfPage].
179/// * [PdfPage::links_mut()], a mutable collection of the links on the [PdfPage].
180/// * [PdfPage::objects()], an immutable collection of all the displayable objects on the [PdfPage].
181/// * [PdfPage::objects_mut()], a mutable collection of all the displayable objects on the [PdfPage].
182pub struct PdfPage<'a> {
183 document_handle: FPDF_DOCUMENT,
184 page_handle: FPDF_PAGE,
185 form_handle: Option<FPDF_FORMHANDLE>,
186 label: Option<String>,
187 regeneration_strategy: PdfPageContentRegenerationStrategy,
188 is_content_regeneration_required: bool,
189 annotations: PdfPageAnnotations<'a>,
190 boundaries: PdfPageBoundaries<'a>,
191 links: PdfPageLinks<'a>,
192 objects: PdfPageObjects<'a>,
193 bindings: &'a dyn PdfiumLibraryBindings,
194}
195
196impl<'a> PdfPage<'a> {
197 /// The default content regeneration strategy used by `pdfium-render`. This can be overridden
198 /// on a page-by-page basis using the [PdfPage::set_content_regeneration_strategy()] function.
199 const DEFAULT_CONTENT_REGENERATION_STRATEGY: PdfPageContentRegenerationStrategy =
200 PdfPageContentRegenerationStrategy::AutomaticOnEveryChange;
201
202 #[inline]
203 pub(crate) fn from_pdfium(
204 document_handle: FPDF_DOCUMENT,
205 page_handle: FPDF_PAGE,
206 form_handle: Option<FPDF_FORMHANDLE>,
207 label: Option<String>,
208 bindings: &'a dyn PdfiumLibraryBindings,
209 ) -> Self {
210 let mut result = PdfPage {
211 document_handle,
212 page_handle,
213 form_handle,
214 label,
215 regeneration_strategy: PdfPageContentRegenerationStrategy::Manual,
216 is_content_regeneration_required: false,
217 annotations: PdfPageAnnotations::from_pdfium(
218 document_handle,
219 page_handle,
220 form_handle,
221 bindings,
222 ),
223 boundaries: PdfPageBoundaries::from_pdfium(page_handle, bindings),
224 links: PdfPageLinks::from_pdfium(page_handle, document_handle, bindings),
225 objects: PdfPageObjects::from_pdfium(document_handle, page_handle, bindings),
226 bindings,
227 };
228
229 // Make sure the default content regeneration strategy is applied to child containers.
230
231 result.set_content_regeneration_strategy(Self::DEFAULT_CONTENT_REGENERATION_STRATEGY);
232
233 result
234 }
235
236 /// Returns the internal `FPDF_PAGE` handle for this [PdfPage].
237 #[inline]
238 pub(crate) fn page_handle(&self) -> FPDF_PAGE {
239 self.page_handle
240 }
241
242 /// Returns the internal `FPDF_DOCUMENT` handle of the [PdfDocument] containing this [PdfPage].
243 #[inline]
244 pub(crate) fn document_handle(&self) -> FPDF_DOCUMENT {
245 self.document_handle
246 }
247
248 /// Returns the [PdfiumLibraryBindings] used by this [PdfPage].
249 #[inline]
250 pub fn bindings(&self) -> &'a dyn PdfiumLibraryBindings {
251 self.bindings
252 }
253
254 /// Returns the label assigned to this [PdfPage], if any.
255 #[inline]
256 pub fn label(&self) -> Option<&str> {
257 self.label.as_deref()
258 }
259
260 /// Returns the width of this [PdfPage] in device-independent points.
261 /// One point is 1/72 inches, roughly 0.358 mm.
262 #[inline]
263 pub fn width(&self) -> PdfPoints {
264 PdfPoints::new(self.bindings.FPDF_GetPageWidthF(self.page_handle))
265 }
266
267 /// Returns the height of this [PdfPage] in device-independent points.
268 /// One point is 1/72 inches, roughly 0.358 mm.
269 #[inline]
270 pub fn height(&self) -> PdfPoints {
271 PdfPoints::new(self.bindings.FPDF_GetPageHeightF(self.page_handle))
272 }
273
274 /// Returns the width and height of this [PdfPage] expressed as a [PdfRect].
275 #[inline]
276 pub fn page_size(&self) -> PdfRect {
277 PdfRect::new(
278 PdfPoints::ZERO,
279 PdfPoints::ZERO,
280 self.height(),
281 self.width(),
282 )
283 }
284
285 /// Returns [PdfPageOrientation::Landscape] if the width of this [PdfPage]
286 /// is greater than its height; otherwise returns [PdfPageOrientation::Portrait].
287 #[inline]
288 pub fn orientation(&self) -> PdfPageOrientation {
289 PdfPageOrientation::from_width_and_height(self.width(), self.height())
290 }
291
292 /// Returns `true` if this [PdfPage] has orientation [PdfPageOrientation::Portrait].
293 #[inline]
294 pub fn is_portrait(&self) -> bool {
295 self.orientation() == PdfPageOrientation::Portrait
296 }
297
298 /// Returns `true` if this [PdfPage] has orientation [PdfPageOrientation::Landscape].
299 #[inline]
300 pub fn is_landscape(&self) -> bool {
301 self.orientation() == PdfPageOrientation::Landscape
302 }
303
304 /// Returns any intrinsic rotation encoded into this document indicating a rotation
305 /// should be applied to this [PdfPage] during rendering.
306 #[inline]
307 pub fn rotation(&self) -> Result<PdfPageRenderRotation, PdfiumError> {
308 PdfPageRenderRotation::from_pdfium(self.bindings.FPDFPage_GetRotation(self.page_handle))
309 }
310
311 /// Sets the intrinsic rotation that should be applied to this [PdfPage] during rendering.
312 #[inline]
313 pub fn set_rotation(&mut self, rotation: PdfPageRenderRotation) {
314 self.bindings
315 .FPDFPage_SetRotation(self.page_handle, rotation.as_pdfium());
316 }
317
318 /// Returns `true` if any object on the page contains transparency.
319 #[inline]
320 pub fn has_transparency(&self) -> bool {
321 self.bindings
322 .is_true(self.bindings.FPDFPage_HasTransparency(self.page_handle))
323 }
324
325 /// Returns the paper size of this [PdfPage].
326 #[inline]
327 pub fn paper_size(&self) -> PdfPagePaperSize {
328 PdfPagePaperSize::from_points(self.width(), self.height())
329 }
330
331 /// Returns `true` if this [PdfPage] contains an embedded thumbnail.
332 ///
333 /// Embedded thumbnails can be generated as a courtesy by PDF generators to save PDF consumers
334 /// the burden of having to render their own thumbnails on the fly. If a thumbnail for this page
335 /// was not embedded at the time the document was created, one can easily be rendered using the
336 /// standard rendering functions:
337 ///
338 /// ```
339 /// let thumbnail_desired_pixel_size = 128;
340 ///
341 /// let thumbnail = page.render_with_config(
342 /// &PdfRenderConfig::thumbnail(thumbnail_desired_pixel_size)
343 /// )?; // Renders a 128 x 128 thumbnail of the page
344 /// ```
345 #[inline]
346 pub fn has_embedded_thumbnail(&self) -> bool {
347 // To determine whether the page includes a thumbnail, we ask Pdfium to return the
348 // size of the thumbnail data. A non-zero value indicates a thumbnail exists.
349
350 self.bindings
351 .FPDFPage_GetRawThumbnailData(self.page_handle, std::ptr::null_mut(), 0)
352 > 0
353 }
354
355 /// Returns the embedded thumbnail for this [PdfPage], if any.
356 ///
357 /// Embedded thumbnails can be generated as a courtesy by PDF generators to save PDF consumers
358 /// the burden of having to render their own thumbnails on the fly. If a thumbnail for this page
359 /// was not embedded at the time the document was created, one can easily be rendered using the
360 /// standard rendering functions:
361 ///
362 /// ```
363 /// let thumbnail_desired_pixel_size = 128;
364 ///
365 /// let thumbnail = page.render_with_config(
366 /// &PdfRenderConfig::thumbnail(thumbnail_desired_pixel_size)
367 /// )?; // Renders a 128 x 128 thumbnail of the page
368 /// ```
369 pub fn embedded_thumbnail(&self) -> Result<PdfBitmap, PdfiumError> {
370 let thumbnail_handle = self
371 .bindings()
372 .FPDFPage_GetThumbnailAsBitmap(self.page_handle);
373
374 if thumbnail_handle.is_null() {
375 // No thumbnail is available for this page.
376
377 Err(PdfiumError::PageMissingEmbeddedThumbnail)
378 } else {
379 Ok(PdfBitmap::from_pdfium(thumbnail_handle, self.bindings))
380 }
381 }
382
383 /// Returns the collection of text boxes contained within this [PdfPage].
384 pub fn text(&self) -> Result<PdfPageText, PdfiumError> {
385 let text_handle = self.bindings().FPDFText_LoadPage(self.page_handle);
386
387 if text_handle.is_null() {
388 Err(PdfiumError::PdfiumLibraryInternalError(
389 PdfiumInternalError::Unknown,
390 ))
391 } else {
392 Ok(PdfPageText::from_pdfium(text_handle, self, self.bindings))
393 }
394 }
395
396 /// Returns an immutable collection of the annotations that have been added to this [PdfPage].
397 pub fn annotations(&self) -> &PdfPageAnnotations<'a> {
398 &self.annotations
399 }
400
401 /// Returns a mutable collection of the annotations that have been added to this [PdfPage].
402 pub fn annotations_mut(&mut self) -> &mut PdfPageAnnotations<'a> {
403 &mut self.annotations
404 }
405
406 /// Returns an immutable collection of the bounding boxes defining the extents of this [PdfPage].
407 #[inline]
408 pub fn boundaries(&self) -> &PdfPageBoundaries<'a> {
409 &self.boundaries
410 }
411
412 /// Returns a mutable collection of the bounding boxes defining the extents of this [PdfPage].
413 #[inline]
414 pub fn boundaries_mut(&mut self) -> &mut PdfPageBoundaries<'a> {
415 &mut self.boundaries
416 }
417
418 /// Returns an immutable collection of the links on this [PdfPage].
419 #[inline]
420 pub fn links(&self) -> &PdfPageLinks<'a> {
421 &self.links
422 }
423
424 /// Returns a mutable collection of the links on this [PdfPage].
425 #[inline]
426 pub fn links_mut(&mut self) -> &mut PdfPageLinks<'a> {
427 &mut self.links
428 }
429
430 /// Returns an immutable collection of all the page objects on this [PdfPage].
431 pub fn objects(&self) -> &PdfPageObjects<'a> {
432 &self.objects
433 }
434
435 /// Returns a mutable collection of all the page objects on this [PdfPage].
436 pub fn objects_mut(&mut self) -> &mut PdfPageObjects<'a> {
437 &mut self.objects
438 }
439
440 /// Returns a list of all the distinct [PdfFont] instances used by the page text objects
441 /// on this [PdfPage], if any.
442 pub fn fonts(&self) -> Vec<PdfFont> {
443 let mut distinct_font_handles = HashMap::new();
444
445 let mut result = Vec::new();
446
447 for object in self.objects().iter() {
448 if let Some(object) = object.as_text_object() {
449 let font = object.font();
450
451 if let Entry::Vacant(entry) = distinct_font_handles.entry(font.handle()) {
452 entry.insert(true);
453 result.push(font.handle());
454 }
455 }
456 }
457
458 result
459 .into_iter()
460 .map(|handle| PdfFont::from_pdfium(handle, self.bindings, None, false))
461 .collect()
462 }
463
464 /// Converts from a bitmap coordinate system, measured in [Pixels] and with constraints
465 /// and dimensions determined by the given [PdfRenderConfig] object, to the equivalent
466 /// position on this page, measured in [PdfPoints].
467 pub fn pixels_to_points(
468 &self,
469 x: Pixels,
470 y: Pixels,
471 config: &PdfRenderConfig,
472 ) -> Result<(PdfPoints, PdfPoints), PdfiumError> {
473 let mut page_x: c_double = 0.0;
474 let mut page_y: c_double = 0.0;
475
476 let settings = config.apply_to_page(self);
477
478 if self.bindings.is_true(self.bindings.FPDF_DeviceToPage(
479 self.page_handle,
480 settings.clipping.left as c_int,
481 settings.clipping.top as c_int,
482 (settings.clipping.right - settings.clipping.left) as c_int,
483 (settings.clipping.bottom - settings.clipping.top) as c_int,
484 settings.rotate,
485 x as c_int,
486 y as c_int,
487 &mut page_x,
488 &mut page_y,
489 )) {
490 Ok((PdfPoints::new(page_x as f32), PdfPoints::new(page_y as f32)))
491 } else {
492 Err(PdfiumError::CoordinateConversionFunctionIndicatedError)
493 }
494 }
495
496 /// Converts from the page coordinate system, measured in [PdfPoints], to the equivalent position
497 /// in a bitmap coordinate system measured in [Pixels] and with constraints and dimensions
498 /// defined by the given [PdfRenderConfig] object.
499 pub fn points_to_pixels(
500 &self,
501 x: PdfPoints,
502 y: PdfPoints,
503 config: &PdfRenderConfig,
504 ) -> Result<(Pixels, Pixels), PdfiumError> {
505 let mut device_x: c_int = 0;
506 let mut device_y: c_int = 0;
507
508 let settings = config.apply_to_page(self);
509
510 if self.bindings.is_true(self.bindings.FPDF_PageToDevice(
511 self.page_handle,
512 settings.clipping.left as c_int,
513 settings.clipping.top as c_int,
514 (settings.clipping.right - settings.clipping.left) as c_int,
515 (settings.clipping.bottom - settings.clipping.top) as c_int,
516 settings.rotate,
517 x.value.into(),
518 y.value.into(),
519 &mut device_x,
520 &mut device_y,
521 )) {
522 Ok((device_x as Pixels, device_y as Pixels))
523 } else {
524 Err(PdfiumError::CoordinateConversionFunctionIndicatedError)
525 }
526 }
527
528 /// Renders this [PdfPage] into a [PdfBitmap] with the given pixel dimensions and page rotation.
529 ///
530 /// It is the responsibility of the caller to ensure the given pixel width and height
531 /// correctly maintain the page's aspect ratio.
532 ///
533 /// See also [PdfPage::render_with_config()], which calculates the correct pixel dimensions,
534 /// rotation settings, and rendering options to apply from a [PdfRenderConfig] object.
535 ///
536 /// Each call to `PdfPage::render()` creates a new [PdfBitmap] object and allocates memory
537 /// for it. To avoid repeated allocations, create a single [PdfBitmap] object
538 /// using [PdfBitmap::empty()] and reuse it across multiple calls to [PdfPage::render_into_bitmap()].
539 pub fn render(
540 &self,
541 width: Pixels,
542 height: Pixels,
543 rotation: Option<PdfPageRenderRotation>,
544 ) -> Result<PdfBitmap, PdfiumError> {
545 let mut bitmap =
546 PdfBitmap::empty(width, height, PdfBitmapFormat::default(), self.bindings)?;
547
548 let mut config = PdfRenderConfig::new()
549 .set_target_width(width)
550 .set_target_height(height);
551
552 if let Some(rotation) = rotation {
553 config = config.rotate(rotation, true);
554 }
555
556 self.render_into_bitmap_with_config(&mut bitmap, &config)?;
557
558 Ok(bitmap)
559 }
560
561 /// Renders this [PdfPage] into a new [PdfBitmap] using pixel dimensions, page rotation settings,
562 /// and rendering options configured in the given [PdfRenderConfig].
563 ///
564 /// Each call to `PdfPage::render_with_config()` creates a new [PdfBitmap] object and
565 /// allocates memory for it. To avoid repeated allocations, create a single [PdfBitmap] object
566 /// using [PdfBitmap::empty()] and reuse it across multiple calls to
567 /// [PdfPage::render_into_bitmap_with_config()].
568 pub fn render_with_config(&self, config: &PdfRenderConfig) -> Result<PdfBitmap, PdfiumError> {
569 let settings = config.apply_to_page(self);
570
571 let mut bitmap = PdfBitmap::empty(
572 settings.width as Pixels,
573 settings.height as Pixels,
574 PdfBitmapFormat::from_pdfium(settings.format as u32)
575 .unwrap_or_else(|_| PdfBitmapFormat::default()),
576 self.bindings,
577 )?;
578
579 self.render_into_bitmap_with_settings(&mut bitmap, settings)?;
580
581 Ok(bitmap)
582 }
583
584 /// Renders this [PdfPage] into the given [PdfBitmap] using the given pixel dimensions
585 /// and page rotation.
586 ///
587 /// It is the responsibility of the caller to ensure the given pixel width and height
588 /// correctly maintain the page's aspect ratio. The size of the buffer backing the given bitmap
589 /// must be sufficiently large to hold the rendered image or an error will be returned.
590 ///
591 /// See also [PdfPage::render_into_bitmap_with_config()], which calculates the correct pixel dimensions,
592 /// rotation settings, and rendering options to apply from a [PdfRenderConfig] object.
593 pub fn render_into_bitmap(
594 &self,
595 bitmap: &mut PdfBitmap,
596 width: Pixels,
597 height: Pixels,
598 rotation: Option<PdfPageRenderRotation>,
599 ) -> Result<(), PdfiumError> {
600 let mut config = PdfRenderConfig::new()
601 .set_target_width(width)
602 .set_target_height(height);
603
604 if let Some(rotation) = rotation {
605 config = config.rotate(rotation, true);
606 }
607
608 self.render_into_bitmap_with_config(bitmap, &config)
609 }
610
611 /// Renders this [PdfPage] into the given [PdfBitmap] using pixel dimensions, page rotation settings,
612 /// and rendering options configured in the given [PdfRenderConfig].
613 ///
614 /// The size of the buffer backing the given bitmap must be sufficiently large to hold the
615 /// rendered image or an error will be returned.
616 #[inline]
617 pub fn render_into_bitmap_with_config(
618 &self,
619 bitmap: &mut PdfBitmap,
620 config: &PdfRenderConfig,
621 ) -> Result<(), PdfiumError> {
622 self.render_into_bitmap_with_settings(bitmap, config.apply_to_page(self))
623 }
624
625 /// Renders this [PdfPage] into the given [PdfBitmap] using the given [PdfRenderSettings].
626 /// The size of the buffer backing the given bitmap must be sufficiently large to hold
627 /// the rendered image or an error will be returned.
628 pub(crate) fn render_into_bitmap_with_settings(
629 &self,
630 bitmap: &mut PdfBitmap,
631 settings: PdfPageRenderSettings,
632 ) -> Result<(), PdfiumError> {
633 let bitmap_handle = bitmap.handle();
634
635 if settings.do_clear_bitmap_before_rendering {
636 // Clear the bitmap buffer by setting every pixel to a known color.
637
638 self.bindings().FPDFBitmap_FillRect(
639 bitmap_handle,
640 0,
641 0,
642 settings.width,
643 settings.height,
644 settings.clear_color,
645 );
646 }
647
648 if settings.do_render_form_data {
649 // Render the PDF page into the bitmap buffer, ignoring any custom transformation matrix.
650 // (Custom transforms cannot be applied to the rendering of form fields.)
651
652 self.bindings.FPDF_RenderPageBitmap(
653 bitmap_handle,
654 self.page_handle,
655 0,
656 0,
657 settings.width,
658 settings.height,
659 settings.rotate,
660 settings.render_flags,
661 );
662
663 if let Some(form_handle) = self.form_handle {
664 // Render user-supplied form data, if any, as an overlay on top of the page.
665
666 if let Some(form_field_highlight) = settings.form_field_highlight.as_ref() {
667 for (form_field_type, (color, alpha)) in form_field_highlight.iter() {
668 self.bindings.FPDF_SetFormFieldHighlightColor(
669 form_handle,
670 *form_field_type,
671 *color,
672 );
673
674 self.bindings
675 .FPDF_SetFormFieldHighlightAlpha(form_handle, *alpha);
676 }
677 }
678
679 self.bindings.FPDF_FFLDraw(
680 form_handle,
681 bitmap_handle,
682 self.page_handle,
683 0,
684 0,
685 settings.width,
686 settings.height,
687 settings.rotate,
688 settings.render_flags,
689 );
690 }
691 } else {
692 // Render the PDF page into the bitmap buffer, applying any custom transformation matrix.
693
694 self.bindings.FPDF_RenderPageBitmapWithMatrix(
695 bitmap_handle,
696 self.page_handle,
697 &settings.matrix,
698 &settings.clipping,
699 settings.render_flags,
700 );
701 }
702
703 bitmap.set_byte_order_from_render_settings(&settings);
704
705 Ok(())
706 }
707
708 // TODO: AJRC - 29/7/22 - remove deprecated PdfPage::get_bitmap_*() functions in 0.9.0
709 // as part of tracking issue https://github.com/ajrcarey/pdfium-render/issues/36
710 /// Renders this [PdfPage] into a new [PdfBitmap] using pixel dimensions, rotation settings,
711 /// and rendering options configured in the given [PdfRenderConfig].
712 #[deprecated(
713 since = "0.7.12",
714 note = "This function has been renamed to better reflect its purpose. Use the PdfPage::render_with_config() function instead."
715 )]
716 #[doc(hidden)]
717 #[inline]
718 pub fn get_bitmap_with_config(
719 &self,
720 config: &PdfRenderConfig,
721 ) -> Result<PdfBitmap, PdfiumError> {
722 self.render_with_config(config)
723 }
724
725 /// Renders this [PdfPage] into a new [PdfBitmap] with the given pixel dimensions and
726 /// rotation setting.
727 ///
728 /// It is the responsibility of the caller to ensure the given pixel width and height
729 /// correctly maintain the page's aspect ratio.
730 ///
731 /// See also [PdfPage::render_with_config()], which calculates the correct pixel dimensions,
732 /// rotation settings, and rendering options to apply from a [PdfRenderConfig] object.
733 #[deprecated(
734 since = "0.7.12",
735 note = "This function has been renamed to better reflect its purpose. Use the PdfPage::render() function instead."
736 )]
737 #[doc(hidden)]
738 pub fn get_bitmap(
739 &self,
740 width: Pixels,
741 height: Pixels,
742 rotation: Option<PdfPageRenderRotation>,
743 ) -> Result<PdfBitmap, PdfiumError> {
744 self.render(width, height, rotation)
745 }
746
747 /// Applies the given transformation, expressed as six values representing the six configurable
748 /// elements of a nine-element 3x3 PDF transformation matrix, to the objects on this [PdfPage],
749 /// restricting the effects of the transformation to the given clipping rectangle.
750 ///
751 /// To move, scale, rotate, or skew the objects on this [PdfPage], consider using one or more of
752 /// the following functions. Internally they all use [PdfPage::transform()], but are
753 /// probably easier to use (and certainly clearer in their intent) in most situations.
754 ///
755 /// * [PdfPage::translate()]: changes the position of each object on this [PdfPage].
756 /// * [PdfPage::scale()]: changes the size of each object on this [PdfPage].
757 /// * [PdfPage::flip_horizontally()]: flips each object on this [PdfPage] horizontally around
758 /// the page origin point.
759 /// * [PdfPage::flip_vertically()]: flips each object on this [PdfPage] vertically around
760 /// the page origin point.
761 /// * [PdfPage::rotate_clockwise_degrees()], [PdfPage::rotate_counter_clockwise_degrees()],
762 /// [PdfPage::rotate_clockwise_radians()], [PdfPage::rotate_counter_clockwise_radians()]:
763 /// rotates each object on this [PdfPage] around its origin.
764 /// * [PdfPage::skew_degrees()], [PdfPage::skew_radians()]: skews each object
765 /// on this [PdfPage] relative to its axes.
766 ///
767 /// **The order in which transformations are applied is significant.**
768 /// For example, the result of rotating _then_ translating an object may be vastly different
769 /// from translating _then_ rotating the same object.
770 ///
771 /// An overview of PDF transformation matrices can be found in the PDF Reference Manual
772 /// version 1.7 on page 204; a detailed description can be found in section 4.2.3 on page 207.
773 #[inline]
774 #[allow(clippy::too_many_arguments)]
775 pub fn transform_with_clip(
776 &mut self,
777 a: PdfMatrixValue,
778 b: PdfMatrixValue,
779 c: PdfMatrixValue,
780 d: PdfMatrixValue,
781 e: PdfMatrixValue,
782 f: PdfMatrixValue,
783 clip: PdfRect,
784 ) -> Result<(), PdfiumError> {
785 self.apply_matrix_with_clip(PdfMatrix::new(a, b, c, d, e, f), clip)
786 }
787
788 // TODO: AJRC - 3/11/23 - remove deprecated PdfPage::set_matrix_with_clip() function in 0.9.0
789 // as part of tracking issue https://github.com/ajrcarey/pdfium-render/issues/36
790 #[deprecated(
791 since = "0.8.15",
792 note = "This function has been renamed to better reflect its behaviour. Use the apply_matrix_with_clip() function instead."
793 )]
794 #[doc(hidden)]
795 #[inline]
796 pub fn set_matrix_with_clip(
797 &mut self,
798 matrix: PdfMatrix,
799 clip: PdfRect,
800 ) -> Result<(), PdfiumError> {
801 self.apply_matrix_with_clip(matrix, clip)
802 }
803
804 /// Applies the given transformation, expressed as a [PdfMatrix], to this [PdfPage],
805 /// restricting the effects of the transformation matrix to the given clipping rectangle.
806 pub fn apply_matrix_with_clip(
807 &mut self,
808 matrix: PdfMatrix,
809 clip: PdfRect,
810 ) -> Result<(), PdfiumError> {
811 if self
812 .bindings()
813 .is_true(self.bindings().FPDFPage_TransFormWithClip(
814 self.page_handle,
815 &matrix.as_pdfium(),
816 &clip.as_pdfium(),
817 ))
818 {
819 // A probable bug in Pdfium means we must reload the page in order for the
820 // transformation to take effect. For more information, see:
821 // https://github.com/ajrcarey/pdfium-render/issues/93
822
823 self.reload_in_place();
824 Ok(())
825 } else {
826 Err(PdfiumError::PdfiumLibraryInternalError(
827 PdfiumInternalError::Unknown,
828 ))
829 }
830 }
831
832 create_transform_setters!(
833 &mut Self,
834 Result<(), PdfiumError>,
835 "each object on this [PdfPage]",
836 "each object on this [PdfPage].",
837 "each object on this [PdfPage],",
838 "",
839 pub(self)
840 ); // pub(self) visibility for the generated reset_matrix() function will effectively make it
841 // private. This is what we want, since Pdfium does not expose a function to directly set
842 // the transformation matrix of a page.
843
844 #[inline]
845 fn transform_impl(
846 &mut self,
847 a: PdfMatrixValue,
848 b: PdfMatrixValue,
849 c: PdfMatrixValue,
850 d: PdfMatrixValue,
851 e: PdfMatrixValue,
852 f: PdfMatrixValue,
853 ) -> Result<(), PdfiumError> {
854 self.transform_with_clip(a, b, c, d, e, f, PdfRect::MAX)
855 }
856
857 // The reset_matrix() function created by the create_transform_setters!() macro
858 // is not publicly visible, so this function should never be called.
859 #[allow(dead_code)]
860 fn reset_matrix_impl(&mut self, _: PdfMatrix) -> Result<(), PdfiumError> {
861 unreachable!();
862 }
863
864 /// Flattens all annotations and form fields on this [PdfPage] into the page contents.
865 #[cfg(feature = "flatten")]
866 // Use a custom-written flatten operation, rather than Pdfium's built-in flatten. See:
867 // https://github.com/ajrcarey/pdfium-render/issues/140
868 pub fn flatten(&mut self) -> Result<(), PdfiumError> {
869 flatten_page(self.handle())
870 }
871
872 /// Flattens all annotations and form fields on this [PdfPage] into the page contents.
873 #[cfg(not(feature = "flatten"))]
874 // Use Pdfium's built-in flatten. This has some problems; see:
875 // https://github.com/ajrcarey/pdfium-render/issues/140
876 pub fn flatten(&mut self) -> Result<(), PdfiumError> {
877 // TODO: AJRC - 28/5/22 - consider allowing the caller to set the FLAT_NORMALDISPLAY or FLAT_PRINT flag.
878 let flag = FLAT_PRINT;
879
880 match self
881 .bindings()
882 .FPDFPage_Flatten(self.page_handle, flag as c_int) as u32
883 {
884 FLATTEN_SUCCESS => {
885 self.regenerate_content()?;
886
887 // As noted at https://bugs.chromium.org/p/pdfium/issues/detail?id=2055,
888 // FPDFPage_Flatten() updates the underlying dictionaries and content streams for
889 // the page, but does not update the FPDF_Page structure. We must reload the
890 // page for the effects of the flatten operation to be visible. For more information, see:
891 // https://github.com/ajrcarey/pdfium-render/issues/140
892
893 self.reload_in_place();
894 Ok(())
895 }
896 FLATTEN_NOTHINGTODO => Ok(()),
897 FLATTEN_FAIL => Err(PdfiumError::PageFlattenFailure),
898 _ => Err(PdfiumError::PageFlattenFailure),
899 }
900 }
901
902 /// Deletes this [PdfPage] from its containing `PdfPages` collection, consuming this [PdfPage].
903 pub fn delete(self) -> Result<(), PdfiumError> {
904 let index = PdfPageIndexCache::get_index_for_page(self.document_handle, self.page_handle)
905 .ok_or(PdfiumError::SourcePageIndexNotInCache)?;
906
907 self.bindings
908 .FPDFPage_Delete(self.document_handle, index as c_int);
909
910 PdfPageIndexCache::delete_pages_at_index(self.document_handle, index, 1);
911
912 Ok(())
913 }
914
915 /// Returns the strategy used by `pdfium-render` to regenerate the content of a [PdfPage].
916 ///
917 /// Updates to a [PdfPage] are not committed to the underlying `PdfDocument` until the page's
918 /// content is regenerated. If a page is reloaded or closed without regenerating the page's
919 /// content, all uncommitted changes will be lost.
920 ///
921 /// By default, `pdfium-render` will trigger content regeneration on any change to a [PdfPage];
922 /// this removes the possibility of data loss, and ensures changes can be read back from other
923 /// data structures as soon as they are made. However, if many changes are made to a page at once,
924 /// then regenerating the content after every change is inefficient; it is faster to stage
925 /// all changes first, then regenerate the page's content just once. In this case,
926 /// changing the content regeneration strategy for a [PdfPage] can improve performance,
927 /// but you must be careful not to forget to commit your changes before closing
928 /// or reloading the page.
929 #[inline]
930 pub fn content_regeneration_strategy(&self) -> PdfPageContentRegenerationStrategy {
931 self.regeneration_strategy
932 }
933
934 /// Sets the strategy used by `pdfium-render` to regenerate the content of a [PdfPage].
935 ///
936 /// Updates to a [PdfPage] are not committed to the underlying `PdfDocument` until the page's
937 /// content is regenerated. If a page is reloaded or closed without regenerating the page's
938 /// content, all uncommitted changes will be lost.
939 ///
940 /// By default, `pdfium-render` will trigger content regeneration on any change to a [PdfPage];
941 /// this removes the possibility of data loss, and ensures changes can be read back from other
942 /// data structures as soon as they are made. However, if many changes are made to a page at once,
943 /// then regenerating the content after every change is inefficient; it is faster to stage
944 /// all changes first, then regenerate the page's content just once. In this case,
945 /// changing the content regeneration strategy for a [PdfPage] can improve performance,
946 /// but you must be careful not to forget to commit your changes before closing
947 /// or reloading the page.
948 #[inline]
949 pub fn set_content_regeneration_strategy(
950 &mut self,
951 strategy: PdfPageContentRegenerationStrategy,
952 ) {
953 self.regeneration_strategy = strategy;
954
955 if let Some(index) =
956 PdfPageIndexCache::get_index_for_page(self.document_handle(), self.page_handle())
957 {
958 PdfPageIndexCache::cache_props_for_page(
959 self.document_handle(),
960 self.page_handle(),
961 index,
962 strategy,
963 );
964 }
965 }
966
967 /// Commits any staged but unsaved changes to this [PdfPage] to the underlying [PdfDocument].
968 ///
969 /// Updates to a [PdfPage] are not committed to the underlying [PdfDocument] until the page's
970 /// content is regenerated. If a page is reloaded or closed without regenerating the page's
971 /// content, all uncommitted changes will be lost.
972 ///
973 /// By default, `pdfium-render` will trigger content regeneration on any change to a [PdfPage];
974 /// this removes the possibility of data loss, and ensures changes can be read back from other
975 /// data structures as soon as they are made. However, if many changes are made to a page at once,
976 /// then regenerating the content after every change is inefficient; it is faster to stage
977 /// all changes first, then regenerate the page's content just once. In this case,
978 /// changing the content regeneration strategy for a [PdfPage] can improve performance,
979 /// but you must be careful not to forget to commit your changes before closing
980 /// or reloading the page.
981 #[inline]
982 pub fn regenerate_content(&mut self) -> Result<(), PdfiumError> {
983 // This is a publicly-visible wrapper for the private regenerate_content_immut() function.
984 // It is only available to callers who hold a mutable reference to the page.
985
986 self.regenerate_content_immut()
987 }
988
989 /// Commits any staged but unsaved changes to this [PdfPage] to the underlying [PdfDocument].
990 #[inline]
991 pub(crate) fn regenerate_content_immut(&self) -> Result<(), PdfiumError> {
992 Self::regenerate_content_immut_for_handle(self.page_handle, self.bindings)
993 }
994
995 /// Commits any staged but unsaved changes to the page identified by the given internal
996 /// `FPDF_PAGE` handle to the underlying [PdfDocument] containing that page.
997 ///
998 /// This function always commits changes, irrespective of the page's currently set
999 /// content regeneration strategy.
1000 pub(crate) fn regenerate_content_immut_for_handle(
1001 page: FPDF_PAGE,
1002 bindings: &dyn PdfiumLibraryBindings,
1003 ) -> Result<(), PdfiumError> {
1004 if bindings.is_true(bindings.FPDFPage_GenerateContent(page)) {
1005 Ok(())
1006 } else {
1007 Err(PdfiumError::PdfiumLibraryInternalError(
1008 PdfiumInternalError::Unknown,
1009 ))
1010 }
1011 }
1012
1013 /// Reloads the page transparently to any caller, forcing a refresh of all page data structures.
1014 /// This will replace this page's `FPDF_PAGE` handle. The page index cache will be updated.
1015 fn reload_in_place(&mut self) {
1016 if let Some(page_index) =
1017 PdfPageIndexCache::get_index_for_page(self.document_handle, self.page_handle)
1018 {
1019 self.drop_impl();
1020
1021 self.page_handle = self
1022 .bindings
1023 .FPDF_LoadPage(self.document_handle, page_index as c_int);
1024
1025 PdfPageIndexCache::cache_props_for_page(
1026 self.document_handle,
1027 self.page_handle,
1028 page_index,
1029 self.content_regeneration_strategy(),
1030 );
1031 }
1032 }
1033
1034 /// Drops the page by calling `FPDF_ClosePage()`, freeing held memory. This will invalidate
1035 /// this page's `FPDF_PAGE` handle. The page index cache will be updated.
1036 fn drop_impl(&mut self) {
1037 if self.regeneration_strategy != PdfPageContentRegenerationStrategy::Manual
1038 && self.is_content_regeneration_required
1039 {
1040 // Regenerate page content now if necessary, before the PdfPage moves out of scope.
1041
1042 let result = self.regenerate_content();
1043
1044 debug_assert!(result.is_ok());
1045 }
1046
1047 self.bindings.FPDF_ClosePage(self.page_handle);
1048
1049 PdfPageIndexCache::remove_index_for_page(self.document_handle, self.page_handle);
1050 }
1051}
1052
1053impl<'a> Drop for PdfPage<'a> {
1054 /// Closes this [PdfPage], releasing held memory.
1055 #[inline]
1056 fn drop(&mut self) {
1057 self.drop_impl();
1058 }
1059}
1060
1061#[cfg(test)]
1062mod tests {
1063 use crate::prelude::*;
1064 use crate::utils::test::test_bind_to_pdfium;
1065 use image_025::{GenericImageView, ImageFormat};
1066
1067 #[test]
1068 fn test_page_rendering_reusing_bitmap() -> Result<(), PdfiumError> {
1069 // Renders each page in the given test PDF file to a separate JPEG file
1070 // by re-using the same bitmap buffer for each render.
1071
1072 let pdfium = test_bind_to_pdfium();
1073
1074 let document = pdfium.load_pdf_from_file("./test/export-test.pdf", None)?;
1075
1076 let render_config = PdfRenderConfig::new()
1077 .set_target_width(2000)
1078 .set_maximum_height(2000)
1079 .rotate_if_landscape(PdfPageRenderRotation::Degrees90, true);
1080
1081 let mut bitmap =
1082 PdfBitmap::empty(2500, 2500, PdfBitmapFormat::default(), pdfium.bindings())?;
1083
1084 for (index, page) in document.pages().iter().enumerate() {
1085 page.render_into_bitmap_with_config(&mut bitmap, &render_config)?; // Re-uses the same bitmap for rendering each page.
1086
1087 bitmap
1088 .as_image()
1089 .into_rgb8()
1090 .save_with_format(format!("test-page-{}.jpg", index), ImageFormat::Jpeg)
1091 .map_err(|_| PdfiumError::ImageError)?;
1092 }
1093
1094 Ok(())
1095 }
1096
1097 #[test]
1098 fn test_rendered_image_dimension() -> Result<(), PdfiumError> {
1099 // Checks that downscaled dimensions are rounded correctly during page rendering.
1100 // See: https://github.com/ajrcarey/pdfium-render/pull/87
1101
1102 let pdfium = test_bind_to_pdfium();
1103
1104 let document = pdfium.load_pdf_from_file("./test/dimensions-test.pdf", None)?;
1105
1106 let render_config = PdfRenderConfig::new()
1107 .set_target_width(500)
1108 .set_maximum_height(500);
1109
1110 for (_index, page) in document.pages().iter().enumerate() {
1111 let rendered_page = page.render_with_config(&render_config)?.as_image();
1112
1113 let (width, _height) = rendered_page.dimensions();
1114
1115 assert_eq!(width, 500);
1116 }
1117
1118 Ok(())
1119 }
1120}