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