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