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#[derive(Debug, Copy, Clone)]
19pub struct PdfRect {
20 #[deprecated(
22 since = "0.8.28",
23 note = "Use the PdfRect::bottom() function instead of direct field access. Direct field access will be removed in release 0.9.0."
24 )]
25 pub bottom: PdfPoints,
26
27 #[deprecated(
28 since = "0.8.28",
29 note = "Use the PdfRect::left() function instead of direct field access. Direct field access will be removed in release 0.9.0."
30 )]
31 pub left: PdfPoints,
32
33 #[deprecated(
34 since = "0.8.28",
35 note = "Use the PdfRect::top() function instead of direct field access. Direct field access will be removed in release 0.9.0."
36 )]
37 pub top: PdfPoints,
38
39 #[deprecated(
40 since = "0.8.28",
41 note = "Use the PdfRect::left() function instead of direct field access. Direct field access will be removed in release 0.9.0."
42 )]
43 pub right: PdfPoints,
44}
45
46impl PdfRect {
47 pub const ZERO: PdfRect = PdfRect::zero();
49
50 pub const MAX: PdfRect = PdfRect::new(
53 PdfPoints::MIN,
54 PdfPoints::MIN,
55 PdfPoints::MAX,
56 PdfPoints::MAX,
57 );
58
59 #[inline]
60 pub(crate) fn from_pdfium(rect: FS_RECTF) -> Self {
61 Self::new_from_values(rect.bottom, rect.left, rect.top, rect.right)
62 }
63
64 #[inline]
65 pub(crate) fn from_pdfium_as_result(
66 result: FPDF_BOOL,
67 rect: FS_RECTF,
68 bindings: &dyn PdfiumLibraryBindings,
69 ) -> Result<PdfRect, PdfiumError> {
70 if !bindings.is_true(result) {
71 Err(PdfiumError::PdfiumLibraryInternalError(
72 PdfiumInternalError::Unknown,
73 ))
74 } else {
75 Ok(PdfRect::from_pdfium(rect))
76 }
77 }
78
79 #[inline]
85 pub const fn new(bottom: PdfPoints, left: PdfPoints, top: PdfPoints, right: PdfPoints) -> Self {
86 let (ordered_bottom, ordered_top) = if bottom.value > top.value {
91 (top, bottom)
92 } else {
93 (bottom, top)
94 };
95
96 let (ordered_left, ordered_right) = if left.value > right.value {
97 (right, left)
98 } else {
99 (left, right)
100 };
101
102 #[allow(deprecated)]
103 Self {
104 bottom: ordered_bottom,
105 left: ordered_left,
106 top: ordered_top,
107 right: ordered_right,
108 }
109 }
110
111 #[inline]
117 pub const fn new_from_values(bottom: f32, left: f32, top: f32, right: f32) -> Self {
118 Self::new(
119 PdfPoints::new(bottom),
120 PdfPoints::new(left),
121 PdfPoints::new(top),
122 PdfPoints::new(right),
123 )
124 }
125
126 #[inline]
131 pub const fn zero() -> Self {
132 Self::new_from_values(0.0, 0.0, 0.0, 0.0)
133 }
134
135 #[inline]
137 pub const fn left(&self) -> PdfPoints {
138 #[allow(deprecated)]
139 self.left
140 }
141
142 #[inline]
144 pub const fn right(&self) -> PdfPoints {
145 #[allow(deprecated)]
146 self.right
147 }
148
149 #[inline]
151 pub const fn bottom(&self) -> PdfPoints {
152 #[allow(deprecated)]
153 self.bottom
154 }
155
156 #[inline]
158 pub const fn top(&self) -> PdfPoints {
159 #[allow(deprecated)]
160 self.top
161 }
162
163 #[inline]
165 pub fn width(&self) -> PdfPoints {
166 self.right() - self.left()
167 }
168
169 #[inline]
171 pub fn height(&self) -> PdfPoints {
172 self.top() - self.bottom()
173 }
174
175 #[inline]
176 pub fn contains(&self, x: PdfPoints, y: PdfPoints) -> bool {
178 self.contains_x(x) && self.contains_y(y)
179 }
180
181 #[inline]
183 pub fn contains_x(&self, x: PdfPoints) -> bool {
184 self.left() <= x && self.right() >= x
185 }
186
187 #[inline]
189 pub fn contains_y(&self, y: PdfPoints) -> bool {
190 self.bottom() <= y && self.top() >= y
191 }
192
193 #[inline]
195 pub fn is_inside(&self, other: &PdfRect) -> bool {
196 self.left() >= other.left()
197 && self.right() <= other.right()
198 && self.top() <= other.top()
199 && self.bottom() >= other.bottom()
200 }
201
202 #[inline]
205 pub fn does_overlap(&self, other: &PdfRect) -> bool {
206 self.left() < other.right()
209 && self.right() > other.left()
210 && self.top() > other.bottom()
211 && self.bottom() < other.top()
212 }
213
214 #[inline]
216 pub fn transform(&self, matrix: PdfMatrix) -> PdfRect {
217 let (x1, y1) = matrix.apply_to_points(self.left(), self.top());
218 let (x2, y2) = matrix.apply_to_points(self.left(), self.bottom());
219 let (x3, y3) = matrix.apply_to_points(self.right(), self.top());
220 let (x4, y4) = matrix.apply_to_points(self.right(), self.bottom());
221
222 PdfRect::new(
223 min([y1, y2, y3, y4]).unwrap_or(PdfPoints::ZERO),
224 min([x1, x2, x3, x4]).unwrap_or(PdfPoints::ZERO),
225 max([y1, y2, y3, y4]).unwrap_or(PdfPoints::ZERO),
226 max([x1, x2, x3, x4]).unwrap_or(PdfPoints::ZERO),
227 )
228 }
229
230 #[inline]
232 pub fn to_quad_points(&self) -> PdfQuadPoints {
233 PdfQuadPoints::from_rect(self)
234 }
235
236 #[inline]
237 pub(crate) fn as_pdfium(&self) -> FS_RECTF {
238 FS_RECTF {
239 left: self.left().value,
240 top: self.top().value,
241 right: self.right().value,
242 bottom: self.bottom().value,
243 }
244 }
245}
246
247impl PartialEq for PdfRect {
251 fn eq(&self, other: &Self) -> bool {
252 self.bottom() == other.bottom()
253 && self.left() == other.left()
254 && self.top() == other.top()
255 && self.right() == other.right()
256 }
257}
258
259impl Eq for PdfRect {}
263
264impl Hash for PdfRect {
265 fn hash<H: Hasher>(&self, state: &mut H) {
266 state.write_u32(self.bottom().value.to_bits());
267 state.write_u32(self.left().value.to_bits());
268 state.write_u32(self.top().value.to_bits());
269 state.write_u32(self.right().value.to_bits());
270 }
271}
272
273impl Display for PdfRect {
274 #[inline]
275 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
276 f.write_fmt(format_args!(
277 "PdfRect(bottom: {}, left: {}, top: {}, right: {})",
278 self.bottom().value,
279 self.left().value,
280 self.top().value,
281 self.right().value
282 ))
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use crate::prelude::*;
289
290 #[test]
291 fn test_rect_is_inside() {
292 assert!(PdfRect::new_from_values(3.0, 3.0, 9.0, 9.0)
293 .is_inside(&PdfRect::new_from_values(2.0, 2.0, 10.0, 10.0)));
294
295 assert!(!PdfRect::new_from_values(2.0, 2.0, 10.0, 10.0)
296 .is_inside(&PdfRect::new_from_values(3.0, 3.0, 9.0, 9.0)));
297
298 assert!(!PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0)
299 .is_inside(&PdfRect::new_from_values(5.0, 4.0, 10.0, 10.0)));
300
301 assert!(!PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0)
302 .is_inside(&PdfRect::new_from_values(8.0, 4.0, 10.0, 10.0)));
303
304 assert!(!PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0)
305 .is_inside(&PdfRect::new_from_values(5.0, 8.0, 10.0, 10.0)));
306 }
307
308 #[test]
309 fn test_rect_does_overlap() {
310 assert!(PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0)
311 .does_overlap(&PdfRect::new_from_values(5.0, 4.0, 10.0, 10.0)));
312
313 assert!(!PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0)
314 .does_overlap(&PdfRect::new_from_values(8.0, 4.0, 10.0, 10.0)));
315
316 assert!(!PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0)
317 .does_overlap(&PdfRect::new_from_values(5.0, 8.0, 10.0, 10.0)));
318 }
319
320 #[test]
321 fn test_transform_rect() {
322 let delta_x = PdfPoints::new(50.0);
323 let delta_y = PdfPoints::new(-25.0);
324
325 let matrix = PdfMatrix::identity().translate(delta_x, delta_y).unwrap();
326
327 let bottom = PdfPoints::new(100.0);
328 let top = PdfPoints::new(200.0);
329 let left = PdfPoints::new(300.0);
330 let right = PdfPoints::new(400.0);
331
332 let rect = PdfRect::new(bottom, left, top, right);
333
334 let result = rect.transform(matrix);
335
336 assert_eq!(result.bottom(), bottom + delta_y);
337 assert_eq!(result.top(), top + delta_y);
338 assert_eq!(result.left(), left + delta_x);
339 assert_eq!(result.right(), right + delta_x);
340 }
341
342 #[test]
343 fn test_coordinate_space_order_guard() {
344 let result = PdfRect::new_from_values(
348 149.0, 544.0, 73.0, 48.0, );
351
352 assert_eq!(result.bottom().value, 73.0);
356 assert_eq!(result.top().value, 149.0);
357 assert_eq!(result.left().value, 48.0);
358 assert_eq!(result.right().value, 544.0);
359 }
360}