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}