1use crate::bindgen::{FPDF_BOOL, FS_RECTF};
4use crate::bindings::PdfiumLibraryBindings;
5use crate::error::{PdfiumError, PdfiumInternalError};
6use crate::pdf::matrix::PdfMatrix;
7use crate::pdf::points::PdfPoints;
8use crate::pdf::quad_points::PdfQuadPoints;
9use itertools::{max, min};
10use std::fmt::{Display, Formatter};
11use std::hash::{Hash, Hasher};
12
13#[cfg(doc)]
14use crate::pdf::document::page::PdfPage;
15
16#[derive(Debug, Copy, Clone)]
22pub struct PdfRect {
23 bottom: PdfPoints,
24 left: PdfPoints,
25 top: PdfPoints,
26 right: PdfPoints,
27}
28
29impl PdfRect {
30 pub const ZERO: PdfRect = PdfRect::zero();
32
33 pub const MAX: PdfRect = PdfRect::new(
36 PdfPoints::MIN,
37 PdfPoints::MIN,
38 PdfPoints::MAX,
39 PdfPoints::MAX,
40 );
41
42 #[inline]
43 pub(crate) fn from_pdfium(rect: FS_RECTF) -> Self {
44 Self::new_from_values(rect.bottom, rect.left, rect.top, rect.right)
45 }
46
47 #[inline]
48 pub(crate) fn from_pdfium_as_result(
49 result: FPDF_BOOL,
50 rect: FS_RECTF,
51 bindings: &dyn PdfiumLibraryBindings,
52 ) -> Result<PdfRect, PdfiumError> {
53 if !bindings.is_true(result) {
54 Err(PdfiumError::PdfiumLibraryInternalError(
55 PdfiumInternalError::Unknown,
56 ))
57 } else {
58 Ok(PdfRect::from_pdfium(rect))
59 }
60 }
61
62 #[inline]
68 pub const fn new(bottom: PdfPoints, left: PdfPoints, top: PdfPoints, right: PdfPoints) -> Self {
69 let (ordered_bottom, ordered_top) = if bottom.value > top.value {
74 (top, bottom)
75 } else {
76 (bottom, top)
77 };
78
79 let (ordered_left, ordered_right) = if left.value > right.value {
80 (right, left)
81 } else {
82 (left, right)
83 };
84
85 Self {
86 bottom: ordered_bottom,
87 left: ordered_left,
88 top: ordered_top,
89 right: ordered_right,
90 }
91 }
92
93 #[inline]
99 pub const fn new_from_values(bottom: f32, left: f32, top: f32, right: f32) -> Self {
100 Self::new(
101 PdfPoints::new(bottom),
102 PdfPoints::new(left),
103 PdfPoints::new(top),
104 PdfPoints::new(right),
105 )
106 }
107
108 #[inline]
113 pub const fn zero() -> Self {
114 Self::new_from_values(0.0, 0.0, 0.0, 0.0)
115 }
116
117 #[inline]
119 pub const fn left(&self) -> PdfPoints {
120 self.left
121 }
122
123 #[inline]
125 pub const fn right(&self) -> PdfPoints {
126 self.right
127 }
128
129 #[inline]
131 pub const fn bottom(&self) -> PdfPoints {
132 self.bottom
133 }
134
135 #[inline]
137 pub const fn top(&self) -> PdfPoints {
138 self.top
139 }
140
141 #[inline]
143 pub fn width(&self) -> PdfPoints {
144 self.right() - self.left()
145 }
146
147 #[inline]
149 pub fn height(&self) -> PdfPoints {
150 self.top() - self.bottom()
151 }
152
153 #[inline]
154 pub fn contains(&self, x: PdfPoints, y: PdfPoints) -> bool {
156 self.contains_x(x) && self.contains_y(y)
157 }
158
159 #[inline]
161 pub fn contains_x(&self, x: PdfPoints) -> bool {
162 self.left() <= x && self.right() >= x
163 }
164
165 #[inline]
167 pub fn contains_y(&self, y: PdfPoints) -> bool {
168 self.bottom() <= y && self.top() >= y
169 }
170
171 #[inline]
173 pub fn is_inside(&self, other: &PdfRect) -> bool {
174 self.left() >= other.left()
175 && self.right() <= other.right()
176 && self.top() <= other.top()
177 && self.bottom() >= other.bottom()
178 }
179
180 #[inline]
183 pub fn does_overlap(&self, other: &PdfRect) -> bool {
184 self.left() < other.right()
187 && self.right() > other.left()
188 && self.top() > other.bottom()
189 && self.bottom() < other.top()
190 }
191
192 #[inline]
194 pub fn transform(&self, matrix: PdfMatrix) -> PdfRect {
195 let (x1, y1) = matrix.apply_to_points(self.left(), self.top());
196 let (x2, y2) = matrix.apply_to_points(self.left(), self.bottom());
197 let (x3, y3) = matrix.apply_to_points(self.right(), self.top());
198 let (x4, y4) = matrix.apply_to_points(self.right(), self.bottom());
199
200 PdfRect::new(
201 min([y1, y2, y3, y4]).unwrap_or(PdfPoints::ZERO),
202 min([x1, x2, x3, x4]).unwrap_or(PdfPoints::ZERO),
203 max([y1, y2, y3, y4]).unwrap_or(PdfPoints::ZERO),
204 max([x1, x2, x3, x4]).unwrap_or(PdfPoints::ZERO),
205 )
206 }
207
208 #[inline]
210 pub fn to_quad_points(&self) -> PdfQuadPoints {
211 PdfQuadPoints::from_rect(self)
212 }
213
214 #[inline]
215 pub(crate) fn as_pdfium(&self) -> FS_RECTF {
216 FS_RECTF {
217 left: self.left().value,
218 top: self.top().value,
219 right: self.right().value,
220 bottom: self.bottom().value,
221 }
222 }
223}
224
225impl PartialEq for PdfRect {
229 fn eq(&self, other: &Self) -> bool {
230 self.bottom() == other.bottom()
231 && self.left() == other.left()
232 && self.top() == other.top()
233 && self.right() == other.right()
234 }
235}
236
237impl Eq for PdfRect {}
241
242impl Hash for PdfRect {
243 fn hash<H: Hasher>(&self, state: &mut H) {
244 state.write_u32(self.bottom().value.to_bits());
245 state.write_u32(self.left().value.to_bits());
246 state.write_u32(self.top().value.to_bits());
247 state.write_u32(self.right().value.to_bits());
248 }
249}
250
251impl Display for PdfRect {
252 #[inline]
253 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
254 f.write_fmt(format_args!(
255 "PdfRect(bottom: {}, left: {}, top: {}, right: {})",
256 self.bottom().value,
257 self.left().value,
258 self.top().value,
259 self.right().value
260 ))
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use crate::prelude::*;
267
268 #[test]
269 fn test_rect_is_inside() {
270 assert!(PdfRect::new_from_values(3.0, 3.0, 9.0, 9.0)
271 .is_inside(&PdfRect::new_from_values(2.0, 2.0, 10.0, 10.0)));
272
273 assert!(!PdfRect::new_from_values(2.0, 2.0, 10.0, 10.0)
274 .is_inside(&PdfRect::new_from_values(3.0, 3.0, 9.0, 9.0)));
275
276 assert!(!PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0)
277 .is_inside(&PdfRect::new_from_values(5.0, 4.0, 10.0, 10.0)));
278
279 assert!(!PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0)
280 .is_inside(&PdfRect::new_from_values(8.0, 4.0, 10.0, 10.0)));
281
282 assert!(!PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0)
283 .is_inside(&PdfRect::new_from_values(5.0, 8.0, 10.0, 10.0)));
284 }
285
286 #[test]
287 fn test_rect_does_overlap() {
288 assert!(PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0)
289 .does_overlap(&PdfRect::new_from_values(5.0, 4.0, 10.0, 10.0)));
290
291 assert!(!PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0)
292 .does_overlap(&PdfRect::new_from_values(8.0, 4.0, 10.0, 10.0)));
293
294 assert!(!PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0)
295 .does_overlap(&PdfRect::new_from_values(5.0, 8.0, 10.0, 10.0)));
296 }
297
298 #[test]
299 fn test_transform_rect() {
300 let delta_x = PdfPoints::new(50.0);
301 let delta_y = PdfPoints::new(-25.0);
302
303 let matrix = PdfMatrix::identity().translate(delta_x, delta_y).unwrap();
304
305 let bottom = PdfPoints::new(100.0);
306 let top = PdfPoints::new(200.0);
307 let left = PdfPoints::new(300.0);
308 let right = PdfPoints::new(400.0);
309
310 let rect = PdfRect::new(bottom, left, top, right);
311
312 let result = rect.transform(matrix);
313
314 assert_eq!(result.bottom(), bottom + delta_y);
315 assert_eq!(result.top(), top + delta_y);
316 assert_eq!(result.left(), left + delta_x);
317 assert_eq!(result.right(), right + delta_x);
318 }
319
320 #[test]
321 fn test_coordinate_space_order_guard() {
322 let result = PdfRect::new_from_values(
326 149.0, 544.0, 73.0, 48.0, );
329
330 assert_eq!(result.bottom().value, 73.0);
334 assert_eq!(result.top().value, 149.0);
335 assert_eq!(result.left().value, 48.0);
336 assert_eq!(result.right().value, 544.0);
337 }
338}