Skip to main content

pdfium/page/
mod.rs

1// PDFium-rs -- Modern Rust interface to PDFium, the PDF library from Google
2//
3// Copyright (c) 2025-2026 Martin van der Werff <github (at) newinnovations.nl>
4//
5// This file is part of PDFium-rs.
6//
7// PDFium-rs is free software: you can redistribute it and/or modify it under the terms of
8// the GNU General Public License as published by the Free Software Foundation, either version 3
9// of the License, or (at your option) any later version.
10//
11// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
12// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
13// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
14// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
15// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
16// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
17// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
18// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
19
20pub mod boundaries;
21pub mod link;
22pub mod object;
23pub mod pages;
24pub mod range;
25pub mod render;
26pub mod text;
27
28use crate::{
29    PdfiumDocument, PdfiumPageObject, PdfiumTextPage,
30    error::{PdfiumError, PdfiumResult},
31    lib,
32    page::{boundaries::PdfiumPageBoundaries, object::objects::PdfiumPageObjects},
33    pdfium_constants::FALSE,
34    pdfium_types::{FPDF_PAGE, Handle, PageHandle},
35};
36
37/// Rotation transformation that can be applied to a [`PdfiumPage`] and during rendering
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39#[repr(i32)]
40pub enum PdfiumRotation {
41    /// No rotation
42    None = 0,
43    /// 90° clockwise
44    Cw90 = 1,
45    /// 180° clockwise
46    Cw180 = 2,
47    /// 270° clockwise (90° counterclockwise)
48    Cw270 = 3,
49}
50
51impl PdfiumRotation {
52    /// Checks if rotation is 90°/270° and height/width needs to be flipped
53    pub fn needs_transpose(&self) -> bool {
54        *self == PdfiumRotation::Cw90 || *self == PdfiumRotation::Cw270
55    }
56}
57
58impl From<i32> for PdfiumRotation {
59    fn from(value: i32) -> Self {
60        match value % 4 {
61            1 => Self::Cw90,
62            2 => Self::Cw180,
63            3 => Self::Cw270,
64            _ => Self::None,
65        }
66    }
67}
68
69impl std::ops::Add for PdfiumRotation {
70    type Output = PdfiumRotation;
71
72    fn add(self, rhs: Self) -> Self::Output {
73        Self::Output::from(self as i32 + rhs as i32)
74    }
75}
76
77/// # Rust interface to FPDF_PAGE
78#[derive(Debug, Clone)]
79pub struct PdfiumPage {
80    handle: PageHandle,
81    owner: Option<PdfiumDocument>,
82}
83
84impl PdfiumPage {
85    pub(crate) fn new_from_handle(handle: FPDF_PAGE) -> PdfiumResult<Self> {
86        if handle.is_null() {
87            Err(PdfiumError::NullHandle)
88        } else {
89            Ok(Self {
90                handle: Handle::new(handle, Some(close_page)),
91                owner: None,
92            })
93        }
94    }
95
96    pub(crate) fn set_owner(&mut self, owner: PdfiumDocument) {
97        self.owner = Some(owner);
98    }
99
100    /// Rust interface to the boundary boxes of a page
101    pub fn boundaries(&self) -> PdfiumPageBoundaries<'_> {
102        PdfiumPageBoundaries::new(self)
103    }
104
105    /// Get number of page objects inside this [`PdfiumPage`].
106    pub fn object_count(&self) -> i32 {
107        lib().FPDFPage_CountObjects(self)
108    }
109
110    /// Returns the [`PdfiumPageObject`] indicated by `index` from this [`PdfiumPage`].
111    pub fn object(&self, index: i32) -> PdfiumResult<PdfiumPageObject> {
112        let mut object = lib().FPDFPage_GetObject(self, index)?;
113        object.set_owner(self.clone());
114        Ok(object)
115    }
116
117    /// Return an [`Iterator`] for the ojects in this [`PdfiumPage`].
118    pub fn objects(&self) -> PdfiumPageObjects<'_> {
119        PdfiumPageObjects::new(self)
120    }
121
122    /// Get text page information structure
123    ///
124    /// Contains information about all characters in a page.
125    pub fn text(&self) -> PdfiumResult<PdfiumTextPage> {
126        lib().FPDFText_LoadPage(self)
127    }
128
129    /// Get the rotation of this [`PdfiumPage`].
130    ///
131    /// Returns a [`PdfiumRotation`] indicating the page rotation
132    pub fn rotation(&self) -> PdfiumRotation {
133        PdfiumRotation::from(lib().FPDFPage_GetRotation(self))
134    }
135
136    /// Set rotation for this [`PdfiumPage`].
137    ///
138    /// rotate - the rotation value as [`PdfiumRotation`]
139    pub fn set_rotation(&self, rotate: PdfiumRotation) {
140        lib().FPDFPage_SetRotation(self, rotate as i32)
141    }
142
143    /// Get page height.
144    ///
145    /// Return value:
146    /// * Page height (excluding non-displayable area) measured in points.
147    ///   One point is 1/72 inch (around 0.3528 mm)
148    ///
149    /// Comments:
150    /// * Changing the rotation of |page| affects the return value.
151    pub fn height(&self) -> f32 {
152        lib().FPDF_GetPageHeightF(self)
153    }
154
155    /// Get page width.
156    ///
157    /// Return value:
158    /// * Page width (excluding non-displayable area) measured in points.
159    ///   One point is 1/72 inch (around 0.3528 mm).
160    ///
161    /// Comments:
162    /// * Changing the rotation of |page| affects the return value.
163    pub fn width(&self) -> f32 {
164        lib().FPDF_GetPageWidthF(self)
165    }
166
167    /// Check for landscape orientation.
168    ///
169    /// Return value:
170    /// * `true` if width is larger than height, otherwise `false`
171    ///
172    /// Comments:
173    /// * Square is neither landscape nor portrait
174    /// * Changing the rotation of the page affects the return value.
175    pub fn is_landscape(&self) -> bool {
176        self.width() > self.height()
177    }
178
179    /// Check for portrait orientation.
180    ///
181    /// Return value:
182    /// * `true` if width is smaller than height, otherwise `false`
183    ///
184    /// Comments:
185    /// * Square is neither landscape nor portrait
186    /// * Changing the rotation of the page affects the return value.
187    pub fn is_portrait(&self) -> bool {
188        self.width() < self.height()
189    }
190
191    /// Checks if this [`PdfiumPage`] contains transparency.
192    ///
193    /// Returns true if this contains transparency.
194    pub fn has_transparency(&self) -> bool {
195        lib().FPDFPage_HasTransparency(self) != FALSE
196    }
197}
198
199impl From<&PdfiumPage> for FPDF_PAGE {
200    #[inline]
201    fn from(page: &PdfiumPage) -> Self {
202        page.handle.handle()
203    }
204}
205
206/// Closes this [`PdfiumPage`], releasing held memory.
207fn close_page(page: FPDF_PAGE) {
208    lib().FPDF_ClosePage(page);
209}
210
211#[cfg(test)]
212mod tests {
213    use crate::{PdfiumDocument, PdfiumRotation};
214
215    #[test]
216    fn test_rotations() {
217        assert_eq!(
218            PdfiumRotation::Cw90 + PdfiumRotation::Cw90,
219            PdfiumRotation::Cw180
220        );
221        assert_eq!(
222            PdfiumRotation::Cw90 + PdfiumRotation::Cw270,
223            PdfiumRotation::None
224        );
225        assert_eq!(
226            PdfiumRotation::Cw270 + PdfiumRotation::Cw270,
227            PdfiumRotation::Cw180
228        );
229    }
230
231    #[test]
232    fn test_sequential_page_access() {
233        let document = PdfiumDocument::new_from_path("resources/groningen.pdf", None).unwrap();
234        let _: Vec<_> = (0..8)
235            .map(|i| {
236                let page = document.page(i % 2);
237                assert!(page.is_ok());
238            })
239            .collect();
240    }
241
242    #[test]
243    fn test_concurrent_page_access() {
244        use std::thread;
245
246        let handles: Vec<_> = (0..8)
247            .map(|i| {
248                thread::spawn(move || {
249                    let document =
250                        PdfiumDocument::new_from_path("resources/groningen.pdf", None).unwrap();
251                    let page = document.page(i % 2);
252                    assert!(page.is_ok());
253                })
254            })
255            .collect();
256
257        for handle in handles {
258            handle.join().unwrap();
259        }
260    }
261
262    #[test]
263    fn test_load_pages_out_of_range() {
264        let document = PdfiumDocument::new_from_path("resources/groningen.pdf", None).unwrap();
265        let page = document.page(-1);
266        assert!(page.is_err());
267        let page = document.page(2);
268        assert!(page.is_err());
269    }
270}