Skip to main content

pdfium_render/pdf/document/page/
boundaries.rs

1//! Defines the [PdfPageBoundaries] struct, exposing functionality related to the
2//! boundary boxes of a single [PdfPage].
3
4use crate::bindgen::{FPDF_BOOL, FPDF_PAGE, FS_RECTF};
5use crate::error::PdfiumError;
6use crate::pdf::rect::PdfRect;
7use crate::pdfium::PdfiumLibraryBindingsAccessor;
8use std::marker::PhantomData;
9use std::os::raw::c_float;
10
11#[cfg(doc)]
12use crate::pdf::document::page::PdfPage;
13
14/// The box type of a single boundary box in a [PdfPage].
15#[derive(Debug, Copy, Clone, PartialEq)]
16pub enum PdfPageBoundaryBoxType {
17    Media,
18    Art,
19    Bleed,
20    Trim,
21    Crop,
22    Bounding,
23}
24
25/// The type and bounds of a single boundary box in a [PdfPage].
26#[derive(Debug, Copy, Clone, PartialEq)]
27pub struct PdfPageBoundaryBox {
28    pub box_type: PdfPageBoundaryBoxType,
29    pub bounds: PdfRect,
30}
31
32impl PdfPageBoundaryBox {
33    #[inline]
34    pub(crate) fn new(boundary: PdfPageBoundaryBoxType, bounds: PdfRect) -> Self {
35        Self {
36            box_type: boundary,
37            bounds,
38        }
39    }
40}
41
42/// The page boundaries of a single [PdfPage].
43///
44/// The content of a page can be bounded by up to six different boxes:
45///
46/// * Media box: the full page size, equivalent to the target paper size when the document is printed.
47///   All other page boundaries must fit inside the Media box.
48/// * Art box: the maximum extent of out-of-bleed page art when offset printing.
49///   Typically cropped out when viewing the document on-screen.
50/// * Bleed box: the maximum extent of outside-trim page bleeds when offset printing.
51///   Typically cropped out when viewing the document on-screen.
52/// * Trim box: the maximum extent of page trims when offset printing.
53///   Typically cropped out when viewing the document on-screen.
54/// * Crop box: the maximum extent of user-visible content when viewing the document on-screen.
55/// * Bounding box ("BBox"): the smallest rectangle that can enclose all the content contained in the page.
56///
57/// These boundaries are concentric, i.e. the Bounding box must fit within the Crop box,
58/// which must fit within the Trim box, and so on. The Media box therefore contains all other boxes.
59/// Not all boxes are guaranteed to exist for all pages.
60///
61/// For more information, see section 10.10.1 on page 962 of the PDF Reference Manual version 1.7,
62/// or visit: <https://www.pdfscripting.com/public/PDF-Page-Coordinates.cfm#UserSpace>
63pub struct PdfPageBoundaries<'a> {
64    page_handle: FPDF_PAGE,
65    lifetime: PhantomData<&'a FPDF_PAGE>,
66}
67
68impl<'a> PdfPageBoundaries<'a> {
69    #[inline]
70    pub(crate) fn from_pdfium(page_handle: FPDF_PAGE) -> Self {
71        Self {
72            page_handle,
73            lifetime: PhantomData,
74        }
75    }
76
77    /// Returns the boundary box defined for the containing [PdfPage] matching the
78    /// given [PdfPageBoundaryBoxType], if any.
79    #[inline]
80    pub fn get(&self, boundary: PdfPageBoundaryBoxType) -> Result<PdfPageBoundaryBox, PdfiumError> {
81        match boundary {
82            PdfPageBoundaryBoxType::Media => self.media(),
83            PdfPageBoundaryBoxType::Art => self.art(),
84            PdfPageBoundaryBoxType::Bleed => self.bleed(),
85            PdfPageBoundaryBoxType::Trim => self.trim(),
86            PdfPageBoundaryBoxType::Crop => self.crop(),
87            PdfPageBoundaryBoxType::Bounding => self.bounding(),
88        }
89    }
90
91    /// Sets the boundary box matching the given [PdfPageBoundaryBoxType] to the given [PdfRect]
92    /// for the containing [PdfPage].
93    #[inline]
94    pub fn set(
95        &mut self,
96        box_type: PdfPageBoundaryBoxType,
97        rect: PdfRect,
98    ) -> Result<(), PdfiumError> {
99        match box_type {
100            PdfPageBoundaryBoxType::Media => self.set_media(rect),
101            PdfPageBoundaryBoxType::Art => self.set_art(rect),
102            PdfPageBoundaryBoxType::Bleed => self.set_bleed(rect),
103            PdfPageBoundaryBoxType::Trim => self.set_trim(rect),
104            PdfPageBoundaryBoxType::Crop => self.set_crop(rect),
105            PdfPageBoundaryBoxType::Bounding => Ok(()), // The bounding box is implicit and cannot be set directly.
106        }
107    }
108
109    /// Returns the Media boundary box defined for the containing [PdfPage], if any.
110    /// The Media box is the full page size, equivalent to the target paper size when the document
111    /// is printed.
112    #[inline]
113    pub fn media(&self) -> Result<PdfPageBoundaryBox, PdfiumError> {
114        self.get_bounding_box_rect(|page, left, bottom, right, top| unsafe {
115            self.bindings()
116                .FPDFPage_GetMediaBox(page, left, bottom, right, top)
117        })
118        .map(|rect| PdfPageBoundaryBox::new(PdfPageBoundaryBoxType::Media, rect))
119    }
120
121    /// Sets the Media boundary box for the containing [PdfPage] to the given [PdfRect].
122    pub fn set_media(&mut self, rect: PdfRect) -> Result<(), PdfiumError> {
123        unsafe {
124            self.bindings().FPDFPage_SetMediaBox(
125                self.page_handle,
126                rect.left().value,
127                rect.bottom().value,
128                rect.right().value,
129                rect.top().value,
130            );
131        }
132
133        Ok(())
134    }
135
136    /// Returns the Art boundary box defined for the containing [PdfPage], if any.
137    /// The Art box is the maximum extent of out-of-bleed page art when offset printing.
138    /// It is typically cropped out when viewing the document on-screen.
139    #[inline]
140    pub fn art(&self) -> Result<PdfPageBoundaryBox, PdfiumError> {
141        self.get_bounding_box_rect(|page, left, bottom, right, top| unsafe {
142            self.bindings()
143                .FPDFPage_GetArtBox(page, left, bottom, right, top)
144        })
145        .map(|rect| PdfPageBoundaryBox::new(PdfPageBoundaryBoxType::Art, rect))
146    }
147
148    /// Sets the Art boundary box for the containing [PdfPage] to the given [PdfRect].
149    pub fn set_art(&mut self, rect: PdfRect) -> Result<(), PdfiumError> {
150        unsafe {
151            self.bindings().FPDFPage_SetArtBox(
152                self.page_handle,
153                rect.left().value,
154                rect.bottom().value,
155                rect.right().value,
156                rect.top().value,
157            );
158        }
159
160        Ok(())
161    }
162
163    /// Returns the Bleed boundary box defined for the containing [PdfPage], if any.
164    /// The Bleed box is the maximum extent of outside-trim page bleeds when offset printing.
165    /// It is typically cropped out when viewing the document on-screen.
166    #[inline]
167    pub fn bleed(&self) -> Result<PdfPageBoundaryBox, PdfiumError> {
168        self.get_bounding_box_rect(|page, left, bottom, right, top| unsafe {
169            self.bindings()
170                .FPDFPage_GetBleedBox(page, left, bottom, right, top)
171        })
172        .map(|rect| PdfPageBoundaryBox::new(PdfPageBoundaryBoxType::Bleed, rect))
173    }
174
175    /// Sets the Bleed boundary box for the containing [PdfPage] to the given [PdfRect].
176    pub fn set_bleed(&mut self, rect: PdfRect) -> Result<(), PdfiumError> {
177        unsafe {
178            self.bindings().FPDFPage_SetBleedBox(
179                self.page_handle,
180                rect.left().value,
181                rect.bottom().value,
182                rect.right().value,
183                rect.top().value,
184            );
185        }
186
187        Ok(())
188    }
189
190    /// Returns the Trim boundary box defined for the containing [PdfPage], if any.
191    /// The Trim box is the maximum extent of page trims when offset printing.
192    /// It is typically cropped out when viewing the document on-screen.
193    #[inline]
194    pub fn trim(&self) -> Result<PdfPageBoundaryBox, PdfiumError> {
195        self.get_bounding_box_rect(|page, left, bottom, right, top| unsafe {
196            self.bindings()
197                .FPDFPage_GetTrimBox(page, left, bottom, right, top)
198        })
199        .map(|rect| PdfPageBoundaryBox::new(PdfPageBoundaryBoxType::Trim, rect))
200    }
201
202    /// Sets the Trim boundary box for the containing [PdfPage] to the given [PdfRect].
203    pub fn set_trim(&mut self, rect: PdfRect) -> Result<(), PdfiumError> {
204        unsafe {
205            self.bindings().FPDFPage_SetTrimBox(
206                self.page_handle,
207                rect.left().value,
208                rect.bottom().value,
209                rect.right().value,
210                rect.top().value,
211            );
212        }
213
214        Ok(())
215    }
216
217    /// Returns the Crop boundary box defined for the containing [PdfPage], if any.
218    /// The Crop box is the maximum extent of user-visible content when viewing the document on-screen.
219    #[inline]
220    pub fn crop(&self) -> Result<PdfPageBoundaryBox, PdfiumError> {
221        self.get_bounding_box_rect(|page, left, bottom, right, top| unsafe {
222            self.bindings()
223                .FPDFPage_GetCropBox(page, left, bottom, right, top)
224        })
225        .map(|rect| PdfPageBoundaryBox::new(PdfPageBoundaryBoxType::Crop, rect))
226    }
227
228    /// Sets the Crop boundary box for the containing [PdfPage] to the given [PdfRect].
229    pub fn set_crop(&mut self, rect: PdfRect) -> Result<(), PdfiumError> {
230        unsafe {
231            self.bindings().FPDFPage_SetCropBox(
232                self.page_handle,
233                rect.left().value,
234                rect.bottom().value,
235                rect.right().value,
236                rect.top().value,
237            );
238        }
239
240        Ok(())
241    }
242
243    /// Returns the Bounding box ("BBox") defined for the containing [PdfPage], if any.
244    /// The BBox is the smallest rectangle that can enclose all the content contained in the page.
245    /// Unlike other boundary boxes, the BBox is computed dynamically on request and cannot
246    /// be set explicitly.
247    #[inline]
248    pub fn bounding(&self) -> Result<PdfPageBoundaryBox, PdfiumError> {
249        let mut rect = FS_RECTF {
250            left: 0.0,
251            top: 0.0,
252            right: 0.0,
253            bottom: 0.0,
254        };
255
256        let result = unsafe {
257            self.bindings()
258                .FPDF_GetPageBoundingBox(self.page_handle, &mut rect)
259        };
260
261        PdfRect::from_pdfium_as_result(result, rect, self.bindings())
262            .map(|rect| PdfPageBoundaryBox::new(PdfPageBoundaryBoxType::Bounding, rect))
263    }
264
265    /// Returns the [PdfRect] obtained from calling the given `FPDF_*Box()` function.
266    #[inline]
267    fn get_bounding_box_rect<F>(&self, f: F) -> Result<PdfRect, PdfiumError>
268    where
269        F: FnOnce(FPDF_PAGE, *mut c_float, *mut c_float, *mut c_float, *mut c_float) -> FPDF_BOOL,
270    {
271        let mut left = 0_f32;
272        let mut bottom = 0_f32;
273        let mut right = 0_f32;
274        let mut top = 0_f32;
275
276        let result = f(
277            self.page_handle,
278            &mut left,
279            &mut bottom,
280            &mut right,
281            &mut top,
282        );
283
284        PdfRect::from_pdfium_as_result(
285            result,
286            FS_RECTF {
287                left,
288                top,
289                right,
290                bottom,
291            },
292            self.bindings(),
293        )
294    }
295
296    /// Returns an iterator over all defined [PdfPageBoundaryBox] boxes in the containing `PdfPage`.
297    /// Not all boxes are guaranteed to exist for all pages, but where they are defined they will
298    /// be returned strictly in enclosing order from outermost to innermost:
299    /// Media, Art, Bleed, Trim, Crop, Bounding.
300    pub fn iter(&'a self) -> PageBoundaryIterator<'a> {
301        PageBoundaryIterator::new(self)
302    }
303}
304
305impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfPageBoundaries<'a> {}
306
307#[cfg(feature = "thread_safe")]
308unsafe impl<'a> Send for PdfPageBoundaries<'a> {}
309
310#[cfg(feature = "thread_safe")]
311unsafe impl<'a> Sync for PdfPageBoundaries<'a> {}
312
313/// An iterator over all the [PdfPageBoundaryBox] objects defined for a [PdfPage].
314/// Not all boxes are guaranteed to exist for all pages, but where they are defined they will
315/// be returned strictly in enclosing order from outermost to innermost:
316/// Media, Art, Bleed, Trim, Crop, Bounding.
317pub struct PageBoundaryIterator<'a> {
318    boundaries: &'a PdfPageBoundaries<'a>,
319    next_index: usize,
320}
321
322impl<'a> PageBoundaryIterator<'a> {
323    #[inline]
324    pub(crate) fn new(boundaries: &'a PdfPageBoundaries<'a>) -> Self {
325        Self {
326            boundaries,
327            next_index: 0,
328        }
329    }
330}
331
332impl<'a> Iterator for PageBoundaryIterator<'a> {
333    type Item = PdfPageBoundaryBox;
334
335    fn next(&mut self) -> Option<Self::Item> {
336        let mut next = None;
337
338        while self.next_index < 5 && next.is_none() {
339            next = match self.next_index {
340                0 => self.boundaries.get(PdfPageBoundaryBoxType::Media).ok(),
341                1 => self.boundaries.get(PdfPageBoundaryBoxType::Art).ok(),
342                2 => self.boundaries.get(PdfPageBoundaryBoxType::Bleed).ok(),
343                3 => self.boundaries.get(PdfPageBoundaryBoxType::Trim).ok(),
344                4 => self.boundaries.get(PdfPageBoundaryBoxType::Crop).ok(),
345                5 => self.boundaries.get(PdfPageBoundaryBoxType::Bounding).ok(),
346                _ => None,
347            };
348
349            self.next_index += 1;
350        }
351
352        next
353    }
354}