Skip to main content

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