pdfium/page/boundaries.rs
1// PDFium-rs -- Modern Rust interface to PDFium, the PDF library from Google
2//
3// Copyright (c) 2025 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
20use crate::{error::PdfiumResult, lib, page::PdfiumPage, PdfiumRect};
21
22/// Rust interface to the boundary boxes of a page
23///
24/// PDF pages define multiple nested boundary boxes that serve different purposes in the
25/// document lifecycle, from creation to final output.
26///
27/// **MediaBox** The MediaBox represents the full physical size of a PDF page. It includes every
28/// possible part of the layout—bleed area, crop marks, and artwork. It’s essentially the outermost
29/// boundary and acts like the canvas that all other boxes sit on. Every PDF must have a MediaBox,
30/// and it's the largest of the five.
31///
32/// **CropBox** The CropBox defines the visible area of the page when viewed on a screen or printed
33/// by default. It acts as a “window” through which the page content is displayed in most PDF viewers.
34/// This box doesn’t usually affect professional printing, but it’s important for digital previews and
35/// general display.
36///
37/// **BleedBox** The BleedBox extends slightly beyond the final trim size to include artwork that
38/// "bleeds" off the edge of the page. This is especially important in printing, as it prevents unwanted
39/// white margins if the paper is cut slightly off-target. Designers typically allow 3 to 5 mm of bleed
40/// outside the TrimBox.
41///
42/// **TrimBox** The TrimBox defines the finished size of the printed page after it’s been trimmed. In
43/// professional printing workflows, this box is the most critical—it determines how the page will be cut
44/// and positioned. For example, if you're printing a business card or a poster, the TrimBox sets the
45/// final dimensions.
46///
47/// **ArtBox** The ArtBox outlines the area that contains the meaningful content—like text, logos, or
48/// illustrations. It's useful in cases where you want to mark a "safe zone" so that nothing important
49/// sits too close to the edge. While not used as often, it’s handy for laying out advertisements or design
50/// elements within a page.
51pub struct PdfiumPageBoundaries<'a> {
52 page: &'a PdfiumPage,
53}
54
55impl<'a> PdfiumPageBoundaries<'a> {
56 pub(crate) fn new(page: &'a PdfiumPage) -> PdfiumPageBoundaries<'a> {
57 Self { page }
58 }
59
60 /// Gets the "ArtBox" entry from the page dictionary.
61 ///
62 /// The ArtBox defines the extent of the page's meaningful content (including potential
63 /// white space) as intended by the page's creator. This is typically used by print
64 /// production software and represents the "artistic" boundary of the page content.
65 /// It should be contained within or equal to the CropBox.
66 #[inline]
67 pub fn art(&self) -> PdfiumResult<PdfiumRect> {
68 let mut rect = PdfiumRect::zero();
69 lib().FPDFPage_GetArtBox(
70 self.page,
71 &mut rect.left,
72 &mut rect.bottom,
73 &mut rect.right,
74 &mut rect.top,
75 )?;
76 Ok(rect)
77 }
78
79 /// Gets the "BleedBox" entry from the page dictionary.
80 ///
81 /// The BleedBox defines the region to which the contents of the page should be clipped
82 /// when output in a production environment. This may include any extra bleed area needed
83 /// to accommodate the physical limitations of cutting, folding, and trimming equipment.
84 /// The bleed box should be larger than or equal to the TrimBox.
85 #[inline]
86 pub fn bleed(&self) -> PdfiumResult<PdfiumRect> {
87 let mut rect = PdfiumRect::zero();
88 lib().FPDFPage_GetBleedBox(
89 self.page,
90 &mut rect.left,
91 &mut rect.bottom,
92 &mut rect.right,
93 &mut rect.top,
94 )?;
95 Ok(rect)
96 }
97
98 /// Gets the "CropBox" entry from the page dictionary.
99 ///
100 /// The CropBox defines the visible region of default user space. When the page is displayed
101 /// or printed, its contents should be clipped to this rectangle and then imposed on the
102 /// output medium. This is what viewers typically show as the "page" and defaults to the
103 /// MediaBox if not specified. The CropBox should be contained within the MediaBox.
104 #[inline]
105 pub fn crop(&self) -> PdfiumResult<PdfiumRect> {
106 let mut rect = PdfiumRect::zero();
107 lib().FPDFPage_GetCropBox(
108 self.page,
109 &mut rect.left,
110 &mut rect.bottom,
111 &mut rect.right,
112 &mut rect.top,
113 )?;
114 Ok(rect)
115 }
116
117 /// Gets the "MediaBox" entry from the page dictionary.
118 ///
119 /// The MediaBox defines the boundaries of the physical medium on which the page is to be
120 /// printed. This represents the largest possible page size and is required for every page.
121 /// All other page boundary boxes should be contained within or equal to the MediaBox.
122 /// This is typically the paper size (e.g., A4, Letter, etc.).
123 #[inline]
124 pub fn media(&self) -> PdfiumResult<PdfiumRect> {
125 let mut rect = PdfiumRect::zero();
126 lib().FPDFPage_GetMediaBox(
127 self.page,
128 &mut rect.left,
129 &mut rect.bottom,
130 &mut rect.right,
131 &mut rect.top,
132 )?;
133 Ok(rect)
134 }
135
136 /// Gets the "TrimBox" entry from the page dictionary.
137 ///
138 /// The TrimBox defines the intended dimensions of the finished page after trimming.
139 /// This represents the final size of the page as it will appear to the end user after
140 /// any production cutting/trimming processes. It should be contained within or equal to
141 /// the BleedBox and is commonly used in professional printing workflows.
142 #[inline]
143 pub fn trim(&self) -> PdfiumResult<PdfiumRect> {
144 let mut rect = PdfiumRect::zero();
145 lib().FPDFPage_GetTrimBox(
146 self.page,
147 &mut rect.left,
148 &mut rect.bottom,
149 &mut rect.right,
150 &mut rect.top,
151 )?;
152 Ok(rect)
153 }
154
155 /// Gets the default boundary for use by PDF viewers
156 ///
157 /// Returns the most appropriate boundary box for displaying the page in a PDF viewer
158 /// or similar application. This method implements a fallback hierarchy to determine
159 /// the best viewing area:
160 ///
161 /// 1. **CropBox** - The primary choice, as it defines the visible region intended
162 /// for display and is what most PDF viewers show by default
163 /// 2. **TrimBox** - Used if CropBox is not available, representing the final
164 /// page dimensions after trimming
165 /// 3. **MediaBox** - The fallback option, representing the full physical page size
166 ///
167 /// This method ensures that viewer applications always have a valid boundary to work
168 /// with, since MediaBox is required to exist in every PDF page. The returned rectangle
169 /// represents the area that should be visible to end users when viewing the document.
170 pub fn default(&self) -> PdfiumResult<PdfiumRect> {
171 self.crop()
172 .or_else(|_| self.trim())
173 .or_else(|_| self.media())
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use crate::document::PdfiumDocument;
180
181 #[test]
182 fn test_media_boundary() {
183 let document = PdfiumDocument::new_from_path("resources/groningen.pdf", None).unwrap();
184 let page = document.page(0).unwrap();
185 let boundary = page.boundaries().media().unwrap();
186
187 assert_eq!(boundary.left, 0.0);
188 assert_eq!(boundary.top, 841.92);
189 assert_eq!(boundary.bottom, 0.0);
190 assert_eq!(boundary.right, 594.95996);
191 }
192
193 #[test]
194 fn test_default_boundary() {
195 let document = PdfiumDocument::new_from_path("resources/groningen.pdf", None).unwrap();
196 let page = document.page(0).unwrap();
197 let boundary = page.boundaries().default().unwrap();
198
199 assert_eq!(boundary.left, 0.0);
200 assert_eq!(boundary.top, 841.92);
201 assert_eq!(boundary.bottom, 0.0);
202 assert_eq!(boundary.right, 594.95996);
203 }
204}