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(PdfPoints::MIN, PdfPoints::MIN, PdfPoints::MAX, PdfPoints::MAX);
53
54 #[inline]
55 pub(crate) fn from_pdfium(rect: FS_RECTF) -> Self {
56 Self::new_from_values(rect.bottom, rect.left, rect.top, rect.right)
57 }
58
59 #[inline]
60 pub(crate) fn from_pdfium_as_result(
61 result: FPDF_BOOL,
62 rect: FS_RECTF,
63 bindings: &dyn PdfiumLibraryBindings,
64 ) -> Result<PdfRect, PdfiumError> {
65 if !bindings.is_true(result) {
66 Err(PdfiumError::PdfiumLibraryInternalError(PdfiumInternalError::Unknown))
67 } else {
68 Ok(PdfRect::from_pdfium(rect))
69 }
70 }
71
72 #[inline]
78 pub const fn new(bottom: PdfPoints, left: PdfPoints, top: PdfPoints, right: PdfPoints) -> Self {
79 let (ordered_bottom, ordered_top) = if bottom.value > top.value {
80 (top, bottom)
81 } else {
82 (bottom, top)
83 };
84
85 let (ordered_left, ordered_right) = if left.value > right.value {
86 (right, left)
87 } else {
88 (left, right)
89 };
90
91 #[allow(deprecated)]
92 Self {
93 bottom: ordered_bottom,
94 left: ordered_left,
95 top: ordered_top,
96 right: ordered_right,
97 }
98 }
99
100 #[inline]
106 pub const fn new_from_values(bottom: f32, left: f32, top: f32, right: f32) -> Self {
107 Self::new(
108 PdfPoints::new(bottom),
109 PdfPoints::new(left),
110 PdfPoints::new(top),
111 PdfPoints::new(right),
112 )
113 }
114
115 #[inline]
120 pub const fn zero() -> Self {
121 Self::new_from_values(0.0, 0.0, 0.0, 0.0)
122 }
123
124 #[inline]
126 pub const fn left(&self) -> PdfPoints {
127 #[allow(deprecated)]
128 self.left
129 }
130
131 #[inline]
133 pub const fn right(&self) -> PdfPoints {
134 #[allow(deprecated)]
135 self.right
136 }
137
138 #[inline]
140 pub const fn bottom(&self) -> PdfPoints {
141 #[allow(deprecated)]
142 self.bottom
143 }
144
145 #[inline]
147 pub const fn top(&self) -> PdfPoints {
148 #[allow(deprecated)]
149 self.top
150 }
151
152 #[inline]
154 pub fn width(&self) -> PdfPoints {
155 self.right() - self.left()
156 }
157
158 #[inline]
160 pub fn height(&self) -> PdfPoints {
161 self.top() - self.bottom()
162 }
163
164 #[inline]
165 pub fn contains(&self, x: PdfPoints, y: PdfPoints) -> bool {
167 self.contains_x(x) && self.contains_y(y)
168 }
169
170 #[inline]
172 pub fn contains_x(&self, x: PdfPoints) -> bool {
173 self.left() <= x && self.right() >= x
174 }
175
176 #[inline]
178 pub fn contains_y(&self, y: PdfPoints) -> bool {
179 self.bottom() <= y && self.top() >= y
180 }
181
182 #[inline]
184 pub fn is_inside(&self, other: &PdfRect) -> bool {
185 self.left() >= other.left()
186 && self.right() <= other.right()
187 && self.top() <= other.top()
188 && self.bottom() >= other.bottom()
189 }
190
191 #[inline]
194 pub fn does_overlap(&self, other: &PdfRect) -> bool {
195 self.left() < other.right()
196 && self.right() > other.left()
197 && self.top() > other.bottom()
198 && self.bottom() < other.top()
199 }
200
201 #[inline]
203 pub fn transform(&self, matrix: PdfMatrix) -> PdfRect {
204 let (x1, y1) = matrix.apply_to_points(self.left(), self.top());
205 let (x2, y2) = matrix.apply_to_points(self.left(), self.bottom());
206 let (x3, y3) = matrix.apply_to_points(self.right(), self.top());
207 let (x4, y4) = matrix.apply_to_points(self.right(), self.bottom());
208
209 PdfRect::new(
210 min([y1, y2, y3, y4]).unwrap_or(PdfPoints::ZERO),
211 min([x1, x2, x3, x4]).unwrap_or(PdfPoints::ZERO),
212 max([y1, y2, y3, y4]).unwrap_or(PdfPoints::ZERO),
213 max([x1, x2, x3, x4]).unwrap_or(PdfPoints::ZERO),
214 )
215 }
216
217 #[inline]
219 pub fn to_quad_points(&self) -> PdfQuadPoints {
220 PdfQuadPoints::from_rect(self)
221 }
222
223 #[inline]
224 pub(crate) fn as_pdfium(&self) -> FS_RECTF {
225 FS_RECTF {
226 left: self.left().value,
227 top: self.top().value,
228 right: self.right().value,
229 bottom: self.bottom().value,
230 }
231 }
232}
233
234impl PartialEq for PdfRect {
235 fn eq(&self, other: &Self) -> bool {
236 self.bottom() == other.bottom()
237 && self.left() == other.left()
238 && self.top() == other.top()
239 && self.right() == other.right()
240 }
241}
242
243impl Eq for PdfRect {}
244
245impl Hash for PdfRect {
246 fn hash<H: Hasher>(&self, state: &mut H) {
247 state.write_u32(self.bottom().value.to_bits());
248 state.write_u32(self.left().value.to_bits());
249 state.write_u32(self.top().value.to_bits());
250 state.write_u32(self.right().value.to_bits());
251 }
252}
253
254impl Display for PdfRect {
255 #[inline]
256 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
257 f.write_fmt(format_args!(
258 "PdfRect(bottom: {}, left: {}, top: {}, right: {})",
259 self.bottom().value,
260 self.left().value,
261 self.top().value,
262 self.right().value
263 ))
264 }
265}
266
267#[cfg(test)]
268mod tests {
269 use crate::prelude::*;
270
271 #[test]
272 fn test_rect_is_inside() {
273 assert!(
274 PdfRect::new_from_values(3.0, 3.0, 9.0, 9.0).is_inside(&PdfRect::new_from_values(2.0, 2.0, 10.0, 10.0))
275 );
276
277 assert!(
278 !PdfRect::new_from_values(2.0, 2.0, 10.0, 10.0).is_inside(&PdfRect::new_from_values(3.0, 3.0, 9.0, 9.0))
279 );
280
281 assert!(
282 !PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0).is_inside(&PdfRect::new_from_values(5.0, 4.0, 10.0, 10.0))
283 );
284
285 assert!(
286 !PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0).is_inside(&PdfRect::new_from_values(8.0, 4.0, 10.0, 10.0))
287 );
288
289 assert!(
290 !PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0).is_inside(&PdfRect::new_from_values(5.0, 8.0, 10.0, 10.0))
291 );
292 }
293
294 #[test]
295 fn test_rect_does_overlap() {
296 assert!(
297 PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0).does_overlap(&PdfRect::new_from_values(5.0, 4.0, 10.0, 10.0))
298 );
299
300 assert!(
301 !PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0).does_overlap(&PdfRect::new_from_values(8.0, 4.0, 10.0, 10.0))
302 );
303
304 assert!(
305 !PdfRect::new_from_values(2.0, 2.0, 7.0, 7.0).does_overlap(&PdfRect::new_from_values(5.0, 8.0, 10.0, 10.0))
306 );
307 }
308
309 #[test]
310 fn test_transform_rect() {
311 let delta_x = PdfPoints::new(50.0);
312 let delta_y = PdfPoints::new(-25.0);
313
314 let matrix = PdfMatrix::identity().translate(delta_x, delta_y).unwrap();
315
316 let bottom = PdfPoints::new(100.0);
317 let top = PdfPoints::new(200.0);
318 let left = PdfPoints::new(300.0);
319 let right = PdfPoints::new(400.0);
320
321 let rect = PdfRect::new(bottom, left, top, right);
322
323 let result = rect.transform(matrix);
324
325 assert_eq!(result.bottom(), bottom + delta_y);
326 assert_eq!(result.top(), top + delta_y);
327 assert_eq!(result.left(), left + delta_x);
328 assert_eq!(result.right(), right + delta_x);
329 }
330
331 #[test]
332 fn test_coordinate_space_order_guard() {
333 let result = PdfRect::new_from_values(149.0, 544.0, 73.0, 48.0);
334
335 assert_eq!(result.bottom().value, 73.0);
336 assert_eq!(result.top().value, 149.0);
337 assert_eq!(result.left().value, 48.0);
338 assert_eq!(result.right().value, 544.0);
339 }
340}