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(
569        &self,
570        config: &PdfRenderConfig,
571    ) -> Result<PdfBitmap<'_>, PdfiumError> {
572        let settings = config.apply_to_page(self);
573
574        let mut bitmap = PdfBitmap::empty(
575            settings.width as Pixels,
576            settings.height as Pixels,
577            PdfBitmapFormat::from_pdfium(settings.format as u32)
578                .unwrap_or_else(|_| PdfBitmapFormat::default()),
579            self.bindings,
580        )?;
581
582        self.render_into_bitmap_with_settings(&mut bitmap, settings)?;
583
584        Ok(bitmap)
585    }
586
587    /// Renders this [PdfPage] into the given [PdfBitmap] using the given pixel dimensions
588    /// and page rotation.
589    ///
590    /// It is the responsibility of the caller to ensure the given pixel width and height
591    /// correctly maintain the page's aspect ratio. The size of the buffer backing the given bitmap
592    /// must be sufficiently large to hold the rendered image or an error will be returned.
593    ///
594    /// See also [PdfPage::render_into_bitmap_with_config()], which calculates the correct pixel dimensions,
595    /// rotation settings, and rendering options to apply from a [PdfRenderConfig] object.
596    pub fn render_into_bitmap(
597        &self,
598        bitmap: &mut PdfBitmap,
599        width: Pixels,
600        height: Pixels,
601        rotation: Option<PdfPageRenderRotation>,
602    ) -> Result<(), PdfiumError> {
603        let mut config = PdfRenderConfig::new()
604            .set_target_width(width)
605            .set_target_height(height);
606
607        if let Some(rotation) = rotation {
608            config = config.rotate(rotation, true);
609        }
610
611        self.render_into_bitmap_with_config(bitmap, &config)
612    }
613
614    /// Renders this [PdfPage] into the given [PdfBitmap] using pixel dimensions, page rotation settings,
615    /// and rendering options configured in the given [PdfRenderConfig].
616    ///
617    /// The size of the buffer backing the given bitmap must be sufficiently large to hold the
618    /// rendered image or an error will be returned.
619    #[inline]
620    pub fn render_into_bitmap_with_config(
621        &self,
622        bitmap: &mut PdfBitmap,
623        config: &PdfRenderConfig,
624    ) -> Result<(), PdfiumError> {
625        self.render_into_bitmap_with_settings(bitmap, config.apply_to_page(self))
626    }
627
628    /// Renders this [PdfPage] into the given [PdfBitmap] using the given [PdfRenderSettings].
629    /// The size of the buffer backing the given bitmap must be sufficiently large to hold
630    /// the rendered image or an error will be returned.
631    pub(crate) fn render_into_bitmap_with_settings(
632        &self,
633        bitmap: &mut PdfBitmap,
634        settings: PdfPageRenderSettings,
635    ) -> Result<(), PdfiumError> {
636        let bitmap_handle = bitmap.handle();
637
638        if settings.do_clear_bitmap_before_rendering {
639            // Clear the bitmap buffer by setting every pixel to a known color.
640
641            self.bindings().FPDFBitmap_FillRect(
642                bitmap_handle,
643                0,
644                0,
645                settings.width,
646                settings.height,
647                settings.clear_color,
648            );
649        }
650
651        if settings.do_render_form_data {
652            // Render the PDF page into the bitmap buffer, ignoring any custom transformation matrix.
653            // (Custom transforms cannot be applied to the rendering of form fields.)
654
655            self.bindings.FPDF_RenderPageBitmap(
656                bitmap_handle,
657                self.page_handle,
658                0,
659                0,
660                settings.width,
661                settings.height,
662                settings.rotate,
663                settings.render_flags,
664            );
665
666            if let Some(form_handle) = self.form_handle {
667                // Render user-supplied form data, if any, as an overlay on top of the page.
668
669                if let Some(form_field_highlight) = settings.form_field_highlight.as_ref() {
670                    for (form_field_type, (color, alpha)) in form_field_highlight.iter() {
671                        self.bindings.FPDF_SetFormFieldHighlightColor(
672                            form_handle,
673                            *form_field_type,
674                            *color,
675                        );
676
677                        self.bindings
678                            .FPDF_SetFormFieldHighlightAlpha(form_handle, *alpha);
679                    }
680                }
681
682                self.bindings.FPDF_FFLDraw(
683                    form_handle,
684                    bitmap_handle,
685                    self.page_handle,
686                    0,
687                    0,
688                    settings.width,
689                    settings.height,
690                    settings.rotate,
691                    settings.render_flags,
692                );
693            }
694        } else {
695            // Render the PDF page into the bitmap buffer, applying any custom transformation matrix.
696
697            self.bindings.FPDF_RenderPageBitmapWithMatrix(
698                bitmap_handle,
699                self.page_handle,
700                &settings.matrix,
701                &settings.clipping,
702                settings.render_flags,
703            );
704        }
705
706        bitmap.set_byte_order_from_render_settings(&settings);
707
708        Ok(())
709    }
710
711    // TODO: AJRC - 29/7/22 - remove deprecated PdfPage::get_bitmap_*() functions in 0.9.0
712    // as part of tracking issue https://github.com/ajrcarey/pdfium-render/issues/36
713    /// Renders this [PdfPage] into a new [PdfBitmap] using pixel dimensions, rotation settings,
714    /// and rendering options configured in the given [PdfRenderConfig].
715    #[deprecated(
716        since = "0.7.12",
717        note = "This function has been renamed to better reflect its purpose. Use the PdfPage::render_with_config() function instead."
718    )]
719    #[doc(hidden)]
720    #[inline]
721    pub fn get_bitmap_with_config(
722        &self,
723        config: &PdfRenderConfig,
724    ) -> Result<PdfBitmap<'_>, PdfiumError> {
725        self.render_with_config(config)
726    }
727
728    /// Renders this [PdfPage] into a new [PdfBitmap] with the given pixel dimensions and
729    /// rotation setting.
730    ///
731    /// It is the responsibility of the caller to ensure the given pixel width and height
732    /// correctly maintain the page's aspect ratio.
733    ///
734    /// See also [PdfPage::render_with_config()], which calculates the correct pixel dimensions,
735    /// rotation settings, and rendering options to apply from a [PdfRenderConfig] object.
736    #[deprecated(
737        since = "0.7.12",
738        note = "This function has been renamed to better reflect its purpose. Use the PdfPage::render() function instead."
739    )]
740    #[doc(hidden)]
741    pub fn get_bitmap(
742        &self,
743        width: Pixels,
744        height: Pixels,
745        rotation: Option<PdfPageRenderRotation>,
746    ) -> Result<PdfBitmap<'_>, PdfiumError> {
747        self.render(width, height, rotation)
748    }
749
750    /// Applies the given transformation, expressed as six values representing the six configurable
751    /// elements of a nine-element 3x3 PDF transformation matrix, to the objects on this [PdfPage],
752    /// restricting the effects of the transformation to the given clipping rectangle.
753    ///
754    /// To move, scale, rotate, or skew the objects on this [PdfPage], consider using one or more of
755    /// the following functions. Internally they all use [PdfPage::transform()], but are
756    /// probably easier to use (and certainly clearer in their intent) in most situations.
757    ///
758    /// * [PdfPage::translate()]: changes the position of each object on this [PdfPage].
759    /// * [PdfPage::scale()]: changes the size of each object on this [PdfPage].
760    /// * [PdfPage::flip_horizontally()]: flips each object on this [PdfPage] horizontally around
761    ///   the page origin point.
762    /// * [PdfPage::flip_vertically()]: flips each object on this [PdfPage] vertically around
763    ///   the page origin point.
764    /// * [PdfPage::rotate_clockwise_degrees()], [PdfPage::rotate_counter_clockwise_degrees()],
765    ///   [PdfPage::rotate_clockwise_radians()], [PdfPage::rotate_counter_clockwise_radians()]:
766    ///   rotates each object on this [PdfPage] around its origin.
767    /// * [PdfPage::skew_degrees()], [PdfPage::skew_radians()]: skews each object
768    ///   on this [PdfPage] relative to its axes.
769    ///
770    /// **The order in which transformations are applied is significant.**
771    /// For example, the result of rotating _then_ translating an object may be vastly different
772    /// from translating _then_ rotating the same object.
773    ///
774    /// An overview of PDF transformation matrices can be found in the PDF Reference Manual
775    /// version 1.7 on page 204; a detailed description can be found in section 4.2.3 on page 207.
776    #[inline]
777    #[allow(clippy::too_many_arguments)]
778    pub fn transform_with_clip(
779        &mut self,
780        a: PdfMatrixValue,
781        b: PdfMatrixValue,
782        c: PdfMatrixValue,
783        d: PdfMatrixValue,
784        e: PdfMatrixValue,
785        f: PdfMatrixValue,
786        clip: PdfRect,
787    ) -> Result<(), PdfiumError> {
788        self.apply_matrix_with_clip(PdfMatrix::new(a, b, c, d, e, f), clip)
789    }
790
791    // TODO: AJRC - 3/11/23 - remove deprecated PdfPage::set_matrix_with_clip() function in 0.9.0
792    // as part of tracking issue https://github.com/ajrcarey/pdfium-render/issues/36
793    #[deprecated(
794        since = "0.8.15",
795        note = "This function has been renamed to better reflect its behaviour. Use the apply_matrix_with_clip() function instead."
796    )]
797    #[doc(hidden)]
798    #[inline]
799    pub fn set_matrix_with_clip(
800        &mut self,
801        matrix: PdfMatrix,
802        clip: PdfRect,
803    ) -> Result<(), PdfiumError> {
804        self.apply_matrix_with_clip(matrix, clip)
805    }
806
807    /// Applies the given transformation, expressed as a [PdfMatrix], to this [PdfPage],
808    /// restricting the effects of the transformation matrix to the given clipping rectangle.
809    pub fn apply_matrix_with_clip(
810        &mut self,
811        matrix: PdfMatrix,
812        clip: PdfRect,
813    ) -> Result<(), PdfiumError> {
814        if self
815            .bindings()
816            .is_true(self.bindings().FPDFPage_TransFormWithClip(
817                self.page_handle,
818                &matrix.as_pdfium(),
819                &clip.as_pdfium(),
820            ))
821        {
822            // A probable bug in Pdfium means we must reload the page in order for the
823            // transformation to take effect. For more information, see:
824            // https://github.com/ajrcarey/pdfium-render/issues/93
825
826            self.reload_in_place();
827            Ok(())
828        } else {
829            Err(PdfiumError::PdfiumLibraryInternalError(
830                PdfiumInternalError::Unknown,
831            ))
832        }
833    }
834
835    create_transform_setters!(
836        &mut Self,
837        Result<(), PdfiumError>,
838        "each object on this [PdfPage]",
839        "each object on this [PdfPage].",
840        "each object on this [PdfPage],",
841        "",
842        pub(self)
843    ); // pub(self) visibility for the generated reset_matrix() function will effectively make it
844       // private. This is what we want, since Pdfium does not expose a function to directly set
845       // the transformation matrix of a page.
846
847    #[inline]
848    fn transform_impl(
849        &mut self,
850        a: PdfMatrixValue,
851        b: PdfMatrixValue,
852        c: PdfMatrixValue,
853        d: PdfMatrixValue,
854        e: PdfMatrixValue,
855        f: PdfMatrixValue,
856    ) -> Result<(), PdfiumError> {
857        self.transform_with_clip(a, b, c, d, e, f, PdfRect::MAX)
858    }
859
860    // The reset_matrix() function created by the create_transform_setters!() macro
861    // is not publicly visible, so this function should never be called.
862    #[allow(dead_code)]
863    fn reset_matrix_impl(&mut self, _: PdfMatrix) -> Result<(), PdfiumError> {
864        unreachable!();
865    }
866
867    /// Flattens all annotations and form fields on this [PdfPage] into the page contents.
868    #[cfg(feature = "flatten")]
869    // Use a custom-written flatten operation, rather than Pdfium's built-in flatten. See:
870    // https://github.com/ajrcarey/pdfium-render/issues/140
871    pub fn flatten(&mut self) -> Result<(), PdfiumError> {
872        flatten_page(self.handle())
873    }
874
875    /// Flattens all annotations and form fields on this [PdfPage] into the page contents.
876    #[cfg(not(feature = "flatten"))]
877    // Use Pdfium's built-in flatten. This has some problems; see:
878    // https://github.com/ajrcarey/pdfium-render/issues/140
879    pub fn flatten(&mut self) -> Result<(), PdfiumError> {
880        // TODO: AJRC - 28/5/22 - consider allowing the caller to set the FLAT_NORMALDISPLAY or FLAT_PRINT flag.
881        let flag = FLAT_PRINT;
882
883        match self
884            .bindings()
885            .FPDFPage_Flatten(self.page_handle, flag as c_int) as u32
886        {
887            FLATTEN_SUCCESS => {
888                self.regenerate_content()?;
889
890                // As noted at https://bugs.chromium.org/p/pdfium/issues/detail?id=2055,
891                // FPDFPage_Flatten() updates the underlying dictionaries and content streams for
892                // the page, but does not update the FPDF_Page structure. We must reload the
893                // page for the effects of the flatten operation to be visible. For more information, see:
894                // https://github.com/ajrcarey/pdfium-render/issues/140
895
896                self.reload_in_place();
897                Ok(())
898            }
899            FLATTEN_NOTHINGTODO => Ok(()),
900            FLATTEN_FAIL => Err(PdfiumError::PageFlattenFailure),
901            _ => Err(PdfiumError::PageFlattenFailure),
902        }
903    }
904
905    /// Deletes this [PdfPage] from its containing `PdfPages` collection, consuming this [PdfPage].
906    pub fn delete(self) -> Result<(), PdfiumError> {
907        let index = PdfPageIndexCache::get_index_for_page(self.document_handle, self.page_handle)
908            .ok_or(PdfiumError::SourcePageIndexNotInCache)?;
909
910        self.bindings
911            .FPDFPage_Delete(self.document_handle, index as c_int);
912
913        PdfPageIndexCache::delete_pages_at_index(self.document_handle, index, 1);
914
915        Ok(())
916    }
917
918    /// Returns the strategy used by `pdfium-render` to regenerate the content of a [PdfPage].
919    ///
920    /// Updates to a [PdfPage] are not committed to the underlying `PdfDocument` until the page's
921    /// content is regenerated. If a page is reloaded or closed without regenerating the page's
922    /// content, all uncommitted changes will be lost.
923    ///
924    /// By default, `pdfium-render` will trigger content regeneration on any change to a [PdfPage];
925    /// this removes the possibility of data loss, and ensures changes can be read back from other
926    /// data structures as soon as they are made. However, if many changes are made to a page at once,
927    /// then regenerating the content after every change is inefficient; it is faster to stage
928    /// all changes first, then regenerate the page's content just once. In this case,
929    /// changing the content regeneration strategy for a [PdfPage] can improve performance,
930    /// but you must be careful not to forget to commit your changes before closing
931    /// or reloading the page.
932    #[inline]
933    pub fn content_regeneration_strategy(&self) -> PdfPageContentRegenerationStrategy {
934        self.regeneration_strategy
935    }
936
937    /// Sets the strategy used by `pdfium-render` to regenerate the content of a [PdfPage].
938    ///
939    /// Updates to a [PdfPage] are not committed to the underlying `PdfDocument` until the page's
940    /// content is regenerated. If a page is reloaded or closed without regenerating the page's
941    /// content, all uncommitted changes will be lost.
942    ///
943    /// By default, `pdfium-render` will trigger content regeneration on any change to a [PdfPage];
944    /// this removes the possibility of data loss, and ensures changes can be read back from other
945    /// data structures as soon as they are made. However, if many changes are made to a page at once,
946    /// then regenerating the content after every change is inefficient; it is faster to stage
947    /// all changes first, then regenerate the page's content just once. In this case,
948    /// changing the content regeneration strategy for a [PdfPage] can improve performance,
949    /// but you must be careful not to forget to commit your changes before closing
950    /// or reloading the page.
951    #[inline]
952    pub fn set_content_regeneration_strategy(
953        &mut self,
954        strategy: PdfPageContentRegenerationStrategy,
955    ) {
956        self.regeneration_strategy = strategy;
957
958        if let Some(index) =
959            PdfPageIndexCache::get_index_for_page(self.document_handle(), self.page_handle())
960        {
961            PdfPageIndexCache::cache_props_for_page(
962                self.document_handle(),
963                self.page_handle(),
964                index,
965                strategy,
966            );
967        }
968    }
969
970    /// Commits any staged but unsaved changes to this [PdfPage] to the underlying [PdfDocument].
971    ///
972    /// Updates to a [PdfPage] are not committed to the underlying [PdfDocument] until the page's
973    /// content is regenerated. If a page is reloaded or closed without regenerating the page's
974    /// content, all uncommitted changes will be lost.
975    ///
976    /// By default, `pdfium-render` will trigger content regeneration on any change to a [PdfPage];
977    /// this removes the possibility of data loss, and ensures changes can be read back from other
978    /// data structures as soon as they are made. However, if many changes are made to a page at once,
979    /// then regenerating the content after every change is inefficient; it is faster to stage
980    /// all changes first, then regenerate the page's content just once. In this case,
981    /// changing the content regeneration strategy for a [PdfPage] can improve performance,
982    /// but you must be careful not to forget to commit your changes before closing
983    /// or reloading the page.
984    #[inline]
985    pub fn regenerate_content(&mut self) -> Result<(), PdfiumError> {
986        // This is a publicly-visible wrapper for the private regenerate_content_immut() function.
987        // It is only available to callers who hold a mutable reference to the page.
988
989        self.regenerate_content_immut()
990    }
991
992    /// Commits any staged but unsaved changes to this [PdfPage] to the underlying [PdfDocument].
993    #[inline]
994    pub(crate) fn regenerate_content_immut(&self) -> Result<(), PdfiumError> {
995        Self::regenerate_content_immut_for_handle(self.page_handle, self.bindings)
996    }
997
998    /// Commits any staged but unsaved changes to the page identified by the given internal
999    /// `FPDF_PAGE` handle to the underlying [PdfDocument] containing that page.
1000    ///
1001    /// This function always commits changes, irrespective of the page's currently set
1002    /// content regeneration strategy.
1003    pub(crate) fn regenerate_content_immut_for_handle(
1004        page: FPDF_PAGE,
1005        bindings: &dyn PdfiumLibraryBindings,
1006    ) -> Result<(), PdfiumError> {
1007        if bindings.is_true(bindings.FPDFPage_GenerateContent(page)) {
1008            Ok(())
1009        } else {
1010            Err(PdfiumError::PdfiumLibraryInternalError(
1011                PdfiumInternalError::Unknown,
1012            ))
1013        }
1014    }
1015
1016    /// Reloads the page transparently to any caller, forcing a refresh of all page data structures.
1017    /// This will replace this page's `FPDF_PAGE` handle. The page index cache will be updated.
1018    fn reload_in_place(&mut self) {
1019        if let Some(page_index) =
1020            PdfPageIndexCache::get_index_for_page(self.document_handle, self.page_handle)
1021        {
1022            self.drop_impl();
1023
1024            self.page_handle = self
1025                .bindings
1026                .FPDF_LoadPage(self.document_handle, page_index as c_int);
1027
1028            PdfPageIndexCache::cache_props_for_page(
1029                self.document_handle,
1030                self.page_handle,
1031                page_index,
1032                self.content_regeneration_strategy(),
1033            );
1034        }
1035    }
1036
1037    /// Drops the page by calling `FPDF_ClosePage()`, freeing held memory. This will invalidate
1038    /// this page's `FPDF_PAGE` handle. The page index cache will be updated.
1039    fn drop_impl(&mut self) {
1040        if self.regeneration_strategy != PdfPageContentRegenerationStrategy::Manual
1041            && self.is_content_regeneration_required
1042        {
1043            // Regenerate page content now if necessary, before the PdfPage moves out of scope.
1044
1045            let result = self.regenerate_content();
1046
1047            debug_assert!(result.is_ok());
1048        }
1049
1050        self.bindings.FPDF_ClosePage(self.page_handle);
1051
1052        PdfPageIndexCache::remove_index_for_page(self.document_handle, self.page_handle);
1053    }
1054}
1055
1056impl<'a> Drop for PdfPage<'a> {
1057    /// Closes this [PdfPage], releasing held memory.
1058    #[inline]
1059    fn drop(&mut self) {
1060        self.drop_impl();
1061    }
1062}
1063
1064#[cfg(test)]
1065mod tests {
1066    use crate::prelude::*;
1067    use crate::utils::test::test_bind_to_pdfium;
1068    use image_025::{GenericImageView, ImageFormat};
1069
1070    #[test]
1071    fn test_page_rendering_reusing_bitmap() -> Result<(), PdfiumError> {
1072        // Renders each page in the given test PDF file to a separate JPEG file
1073        // by re-using the same bitmap buffer for each render.
1074
1075        let pdfium = test_bind_to_pdfium();
1076
1077        let document = pdfium.load_pdf_from_file("./test/export-test.pdf", None)?;
1078
1079        let render_config = PdfRenderConfig::new()
1080            .set_target_width(2000)
1081            .set_maximum_height(2000)
1082            .rotate_if_landscape(PdfPageRenderRotation::Degrees90, true);
1083
1084        let mut bitmap =
1085            PdfBitmap::empty(2500, 2500, PdfBitmapFormat::default(), pdfium.bindings())?;
1086
1087        for (index, page) in document.pages().iter().enumerate() {
1088            page.render_into_bitmap_with_config(&mut bitmap, &render_config)?; // Re-uses the same bitmap for rendering each page.
1089
1090            bitmap
1091                .as_image()
1092                .into_rgb8()
1093                .save_with_format(format!("test-page-{}.jpg", index), ImageFormat::Jpeg)
1094                .map_err(|_| PdfiumError::ImageError)?;
1095        }
1096
1097        Ok(())
1098    }
1099
1100    #[test]
1101    fn test_rendered_image_dimension() -> Result<(), PdfiumError> {
1102        // Checks that downscaled dimensions are rounded correctly during page rendering.
1103        // See: https://github.com/ajrcarey/pdfium-render/pull/87
1104
1105        let pdfium = test_bind_to_pdfium();
1106
1107        let document = pdfium.load_pdf_from_file("./test/dimensions-test.pdf", None)?;
1108
1109        let render_config = PdfRenderConfig::new()
1110            .set_target_width(500)
1111            .set_maximum_height(500);
1112
1113        for (_index, page) in document.pages().iter().enumerate() {
1114            let rendered_page = page.render_with_config(&render_config)?.as_image();
1115
1116            let (width, _height) = rendered_page.dimensions();
1117
1118            assert_eq!(width, 500);
1119        }
1120
1121        Ok(())
1122    }
1123}