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