Skip to main content

fop_types/
geometry.rs

1//! Geometry types for positions, sizes, and rectangles
2
3use crate::Length;
4use std::fmt;
5
6/// A 2D point with x and y coordinates
7#[derive(Copy, Clone, PartialEq, Eq, Hash)]
8pub struct Point {
9    pub x: Length,
10    pub y: Length,
11}
12
13impl Point {
14    /// Origin point (0, 0)
15    pub const ZERO: Self = Self {
16        x: Length::ZERO,
17        y: Length::ZERO,
18    };
19
20    /// Create a new point
21    #[inline]
22    #[must_use = "this returns a new value without modifying anything"]
23    pub const fn new(x: Length, y: Length) -> Self {
24        Self { x, y }
25    }
26
27    /// Translate the point by an offset
28    #[inline]
29    #[must_use = "this returns a new value without modifying the original"]
30    pub fn translate(self, dx: Length, dy: Length) -> Self {
31        Self {
32            x: self.x + dx,
33            y: self.y + dy,
34        }
35    }
36}
37
38impl fmt::Debug for Point {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        write!(f, "Point({}, {})", self.x, self.y)
41    }
42}
43
44impl fmt::Display for Point {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        write!(f, "({}, {})", self.x, self.y)
47    }
48}
49
50/// A 2D size with width and height
51#[derive(Copy, Clone, PartialEq, Eq, Hash)]
52pub struct Size {
53    pub width: Length,
54    pub height: Length,
55}
56
57impl Size {
58    /// Zero size
59    pub const ZERO: Self = Self {
60        width: Length::ZERO,
61        height: Length::ZERO,
62    };
63
64    /// Create a new size
65    #[inline]
66    #[must_use = "this returns a new value without modifying anything"]
67    pub const fn new(width: Length, height: Length) -> Self {
68        Self { width, height }
69    }
70
71    /// Check if the size is empty (width or height is zero)
72    #[inline]
73    #[must_use = "the result should be used"]
74    pub const fn is_empty(self) -> bool {
75        self.width.millipoints() <= 0 || self.height.millipoints() <= 0
76    }
77
78    /// Get the area (width * height)
79    #[inline]
80    #[must_use = "the result should be used"]
81    pub fn area(self) -> i64 {
82        self.width.millipoints() as i64 * self.height.millipoints() as i64
83    }
84}
85
86impl fmt::Debug for Size {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        write!(f, "Size({}×{})", self.width, self.height)
89    }
90}
91
92impl fmt::Display for Size {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        write!(f, "{}×{}", self.width, self.height)
95    }
96}
97
98/// A rectangle defined by position and size
99#[derive(Copy, Clone, PartialEq, Eq, Hash)]
100pub struct Rect {
101    pub x: Length,
102    pub y: Length,
103    pub width: Length,
104    pub height: Length,
105}
106
107impl Rect {
108    /// Zero rectangle
109    pub const ZERO: Self = Self {
110        x: Length::ZERO,
111        y: Length::ZERO,
112        width: Length::ZERO,
113        height: Length::ZERO,
114    };
115
116    /// Create a new rectangle from position and size
117    #[inline]
118    #[must_use = "this returns a new value without modifying anything"]
119    pub const fn new(x: Length, y: Length, width: Length, height: Length) -> Self {
120        Self {
121            x,
122            y,
123            width,
124            height,
125        }
126    }
127
128    /// Create a rectangle from a point and size
129    #[inline]
130    #[must_use = "this returns a new value without modifying anything"]
131    pub const fn from_point_size(origin: Point, size: Size) -> Self {
132        Self {
133            x: origin.x,
134            y: origin.y,
135            width: size.width,
136            height: size.height,
137        }
138    }
139
140    /// Get the origin point (top-left corner)
141    #[inline]
142    #[must_use = "the result should be used"]
143    pub const fn origin(self) -> Point {
144        Point::new(self.x, self.y)
145    }
146
147    /// Get the size
148    #[inline]
149    #[must_use = "the result should be used"]
150    pub const fn size(self) -> Size {
151        Size::new(self.width, self.height)
152    }
153
154    /// Get the right edge x-coordinate
155    #[inline]
156    #[must_use = "the result should be used"]
157    pub fn right(self) -> Length {
158        self.x + self.width
159    }
160
161    /// Get the bottom edge y-coordinate
162    #[inline]
163    #[must_use = "the result should be used"]
164    pub fn bottom(self) -> Length {
165        self.y + self.height
166    }
167
168    /// Check if the rectangle is empty
169    #[inline]
170    #[must_use = "the result should be used"]
171    pub const fn is_empty(self) -> bool {
172        self.width.millipoints() <= 0 || self.height.millipoints() <= 0
173    }
174
175    /// Check if the rectangle contains a point
176    #[inline]
177    #[must_use = "the result should be used"]
178    pub fn contains(self, point: Point) -> bool {
179        point.x.millipoints() >= self.x.millipoints()
180            && point.x.millipoints() < self.right().millipoints()
181            && point.y.millipoints() >= self.y.millipoints()
182            && point.y.millipoints() < self.bottom().millipoints()
183    }
184
185    /// Check if this rectangle intersects another
186    #[must_use = "the result should be used"]
187    pub fn intersects(self, other: Rect) -> bool {
188        self.x.millipoints() < other.right().millipoints()
189            && self.right().millipoints() > other.x.millipoints()
190            && self.y.millipoints() < other.bottom().millipoints()
191            && self.bottom().millipoints() > other.y.millipoints()
192    }
193
194    /// Translate the rectangle by an offset
195    #[inline]
196    #[must_use = "this returns a new value without modifying the original"]
197    pub fn translate(self, dx: Length, dy: Length) -> Self {
198        Self {
199            x: self.x + dx,
200            y: self.y + dy,
201            width: self.width,
202            height: self.height,
203        }
204    }
205}
206
207impl fmt::Debug for Rect {
208    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209        write!(
210            f,
211            "Rect(x={}, y={}, w={}, h={})",
212            self.x, self.y, self.width, self.height
213        )
214    }
215}
216
217impl fmt::Display for Rect {
218    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219        write!(f, "({},{},{}×{})", self.x, self.y, self.width, self.height)
220    }
221}
222
223#[cfg(test)]
224mod tests {
225    use super::*;
226
227    #[test]
228    fn test_point() {
229        let p = Point::new(Length::from_pt(10.0), Length::from_pt(20.0));
230        assert_eq!(p.x, Length::from_pt(10.0));
231        assert_eq!(p.y, Length::from_pt(20.0));
232
233        let translated = p.translate(Length::from_pt(5.0), Length::from_pt(3.0));
234        assert_eq!(translated.x, Length::from_pt(15.0));
235        assert_eq!(translated.y, Length::from_pt(23.0));
236    }
237
238    #[test]
239    fn test_size() {
240        let s = Size::new(Length::from_pt(100.0), Length::from_pt(200.0));
241        assert_eq!(s.width, Length::from_pt(100.0));
242        assert_eq!(s.height, Length::from_pt(200.0));
243        assert!(!s.is_empty());
244
245        let empty = Size::new(Length::ZERO, Length::from_pt(100.0));
246        assert!(empty.is_empty());
247    }
248
249    #[test]
250    fn test_rect() {
251        let r = Rect::new(
252            Length::from_pt(10.0),
253            Length::from_pt(20.0),
254            Length::from_pt(100.0),
255            Length::from_pt(50.0),
256        );
257
258        assert_eq!(r.right(), Length::from_pt(110.0));
259        assert_eq!(r.bottom(), Length::from_pt(70.0));
260
261        let p_inside = Point::new(Length::from_pt(50.0), Length::from_pt(40.0));
262        assert!(r.contains(p_inside));
263
264        let p_outside = Point::new(Length::from_pt(5.0), Length::from_pt(40.0));
265        assert!(!r.contains(p_outside));
266    }
267
268    #[test]
269    fn test_rect_intersection() {
270        let r1 = Rect::new(
271            Length::from_pt(0.0),
272            Length::from_pt(0.0),
273            Length::from_pt(100.0),
274            Length::from_pt(100.0),
275        );
276
277        let r2 = Rect::new(
278            Length::from_pt(50.0),
279            Length::from_pt(50.0),
280            Length::from_pt(100.0),
281            Length::from_pt(100.0),
282        );
283
284        assert!(r1.intersects(r2));
285        assert!(r2.intersects(r1));
286
287        let r3 = Rect::new(
288            Length::from_pt(200.0),
289            Length::from_pt(200.0),
290            Length::from_pt(100.0),
291            Length::from_pt(100.0),
292        );
293
294        assert!(!r1.intersects(r3));
295    }
296
297    #[test]
298    fn test_point_display() {
299        let p = Point::new(Length::from_pt(10.0), Length::from_pt(20.0));
300        assert_eq!(format!("{}", p), "(10pt, 20pt)");
301
302        let p_zero = Point::ZERO;
303        assert_eq!(format!("{}", p_zero), "(0pt, 0pt)");
304
305        let p_neg = Point::new(Length::from_pt(-5.5), Length::from_pt(12.25));
306        assert_eq!(format!("{}", p_neg), "(-5.5pt, 12.25pt)");
307    }
308
309    #[test]
310    fn test_size_display() {
311        let s = Size::new(Length::from_pt(100.0), Length::from_pt(200.0));
312        assert_eq!(format!("{}", s), "100pt×200pt");
313
314        let s_zero = Size::ZERO;
315        assert_eq!(format!("{}", s_zero), "0pt×0pt");
316
317        let s_fractional = Size::new(Length::from_pt(12.5), Length::from_pt(7.75));
318        assert_eq!(format!("{}", s_fractional), "12.5pt×7.75pt");
319    }
320
321    #[test]
322    fn test_rect_display() {
323        let r = Rect::new(
324            Length::from_pt(10.0),
325            Length::from_pt(20.0),
326            Length::from_pt(100.0),
327            Length::from_pt(50.0),
328        );
329        assert_eq!(format!("{}", r), "(10pt,20pt,100pt×50pt)");
330
331        let r_zero = Rect::ZERO;
332        assert_eq!(format!("{}", r_zero), "(0pt,0pt,0pt×0pt)");
333
334        let r_neg = Rect::new(
335            Length::from_pt(-10.0),
336            Length::from_pt(-20.0),
337            Length::from_pt(30.0),
338            Length::from_pt(40.0),
339        );
340        assert_eq!(format!("{}", r_neg), "(-10pt,-20pt,30pt×40pt)");
341    }
342}
343
344#[cfg(test)]
345mod geometry_extra_tests {
346    use super::*;
347
348    // --- Point ---
349
350    #[test]
351    fn test_point_zero() {
352        assert_eq!(Point::ZERO.x, Length::ZERO);
353        assert_eq!(Point::ZERO.y, Length::ZERO);
354    }
355
356    #[test]
357    fn test_point_translate_positive() {
358        let p = Point::new(Length::from_pt(5.0), Length::from_pt(10.0));
359        let t = p.translate(Length::from_pt(3.0), Length::from_pt(7.0));
360        assert_eq!(t.x, Length::from_pt(8.0));
361        assert_eq!(t.y, Length::from_pt(17.0));
362    }
363
364    #[test]
365    fn test_point_translate_negative() {
366        let p = Point::new(Length::from_pt(20.0), Length::from_pt(30.0));
367        let t = p.translate(Length::from_pt(-5.0), Length::from_pt(-10.0));
368        assert_eq!(t.x, Length::from_pt(15.0));
369        assert_eq!(t.y, Length::from_pt(20.0));
370    }
371
372    #[test]
373    fn test_point_translate_zero() {
374        let p = Point::new(Length::from_pt(5.0), Length::from_pt(5.0));
375        let t = p.translate(Length::ZERO, Length::ZERO);
376        assert_eq!(t, p);
377    }
378
379    #[test]
380    fn test_point_equality() {
381        let a = Point::new(Length::from_pt(3.0), Length::from_pt(4.0));
382        let b = Point::new(Length::from_pt(3.0), Length::from_pt(4.0));
383        assert_eq!(a, b);
384    }
385
386    #[test]
387    fn test_point_inequality() {
388        let a = Point::new(Length::from_pt(3.0), Length::from_pt(4.0));
389        let b = Point::new(Length::from_pt(3.0), Length::from_pt(5.0));
390        assert_ne!(a, b);
391    }
392
393    #[test]
394    fn test_point_debug_format() {
395        let p = Point::new(Length::from_pt(10.0), Length::from_pt(20.0));
396        let s = format!("{:?}", p);
397        assert!(s.contains("10pt"));
398        assert!(s.contains("20pt"));
399    }
400
401    // --- Size ---
402
403    #[test]
404    fn test_size_zero() {
405        assert!(Size::ZERO.is_empty());
406    }
407
408    #[test]
409    fn test_size_non_empty() {
410        let s = Size::new(Length::from_pt(10.0), Length::from_pt(20.0));
411        assert!(!s.is_empty());
412    }
413
414    #[test]
415    fn test_size_zero_width_is_empty() {
416        let s = Size::new(Length::ZERO, Length::from_pt(100.0));
417        assert!(s.is_empty());
418    }
419
420    #[test]
421    fn test_size_zero_height_is_empty() {
422        let s = Size::new(Length::from_pt(100.0), Length::ZERO);
423        assert!(s.is_empty());
424    }
425
426    #[test]
427    fn test_size_area() {
428        // 100pt × 200pt → millipoints: 100000 × 200000 = 20_000_000_000
429        let s = Size::new(Length::from_pt(100.0), Length::from_pt(200.0));
430        let expected: i64 = 100_000_i64 * 200_000_i64;
431        assert_eq!(s.area(), expected);
432    }
433
434    #[test]
435    fn test_size_area_zero() {
436        assert_eq!(Size::ZERO.area(), 0);
437    }
438
439    #[test]
440    fn test_size_equality() {
441        let a = Size::new(Length::from_pt(50.0), Length::from_pt(80.0));
442        let b = Size::new(Length::from_pt(50.0), Length::from_pt(80.0));
443        assert_eq!(a, b);
444    }
445
446    #[test]
447    fn test_size_debug_format() {
448        let s = Size::new(Length::from_pt(100.0), Length::from_pt(200.0));
449        let dbg = format!("{:?}", s);
450        assert!(dbg.contains("100pt"));
451        assert!(dbg.contains("200pt"));
452    }
453
454    // --- Rect ---
455
456    #[test]
457    fn test_rect_zero() {
458        let r = Rect::ZERO;
459        assert_eq!(r.x, Length::ZERO);
460        assert_eq!(r.width, Length::ZERO);
461        assert!(r.is_empty());
462    }
463
464    #[test]
465    fn test_rect_from_point_size() {
466        let origin = Point::new(Length::from_pt(5.0), Length::from_pt(10.0));
467        let size = Size::new(Length::from_pt(100.0), Length::from_pt(50.0));
468        let r = Rect::from_point_size(origin, size);
469        assert_eq!(r.x, Length::from_pt(5.0));
470        assert_eq!(r.y, Length::from_pt(10.0));
471        assert_eq!(r.width, Length::from_pt(100.0));
472        assert_eq!(r.height, Length::from_pt(50.0));
473    }
474
475    #[test]
476    fn test_rect_origin() {
477        let r = Rect::new(
478            Length::from_pt(3.0),
479            Length::from_pt(7.0),
480            Length::from_pt(20.0),
481            Length::from_pt(15.0),
482        );
483        let origin = r.origin();
484        assert_eq!(
485            origin,
486            Point::new(Length::from_pt(3.0), Length::from_pt(7.0))
487        );
488    }
489
490    #[test]
491    fn test_rect_size() {
492        let r = Rect::new(
493            Length::from_pt(0.0),
494            Length::from_pt(0.0),
495            Length::from_pt(100.0),
496            Length::from_pt(200.0),
497        );
498        let size = r.size();
499        assert_eq!(
500            size,
501            Size::new(Length::from_pt(100.0), Length::from_pt(200.0))
502        );
503    }
504
505    #[test]
506    fn test_rect_right() {
507        let r = Rect::new(
508            Length::from_pt(10.0),
509            Length::from_pt(0.0),
510            Length::from_pt(50.0),
511            Length::from_pt(0.0),
512        );
513        assert_eq!(r.right(), Length::from_pt(60.0));
514    }
515
516    #[test]
517    fn test_rect_bottom() {
518        let r = Rect::new(
519            Length::from_pt(0.0),
520            Length::from_pt(20.0),
521            Length::from_pt(0.0),
522            Length::from_pt(30.0),
523        );
524        assert_eq!(r.bottom(), Length::from_pt(50.0));
525    }
526
527    #[test]
528    fn test_rect_contains_boundary_left() {
529        // contains uses >= for left, < for right
530        let r = Rect::new(
531            Length::from_pt(10.0),
532            Length::from_pt(10.0),
533            Length::from_pt(100.0),
534            Length::from_pt(100.0),
535        );
536        let p_left_edge = Point::new(Length::from_pt(10.0), Length::from_pt(50.0));
537        assert!(r.contains(p_left_edge));
538    }
539
540    #[test]
541    fn test_rect_does_not_contain_right_edge() {
542        let r = Rect::new(
543            Length::from_pt(10.0),
544            Length::from_pt(10.0),
545            Length::from_pt(100.0),
546            Length::from_pt(100.0),
547        );
548        let p_right_edge = Point::new(Length::from_pt(110.0), Length::from_pt(50.0));
549        assert!(!r.contains(p_right_edge));
550    }
551
552    #[test]
553    fn test_rect_contains_top_edge() {
554        let r = Rect::new(
555            Length::from_pt(0.0),
556            Length::from_pt(0.0),
557            Length::from_pt(100.0),
558            Length::from_pt(100.0),
559        );
560        let p = Point::new(Length::from_pt(50.0), Length::from_pt(0.0));
561        assert!(r.contains(p));
562    }
563
564    #[test]
565    fn test_rect_does_not_contain_bottom_edge() {
566        let r = Rect::new(
567            Length::from_pt(0.0),
568            Length::from_pt(0.0),
569            Length::from_pt(100.0),
570            Length::from_pt(100.0),
571        );
572        let p = Point::new(Length::from_pt(50.0), Length::from_pt(100.0));
573        assert!(!r.contains(p));
574    }
575
576    #[test]
577    fn test_rect_not_contains_outside() {
578        let r = Rect::new(
579            Length::from_pt(0.0),
580            Length::from_pt(0.0),
581            Length::from_pt(50.0),
582            Length::from_pt(50.0),
583        );
584        let p = Point::new(Length::from_pt(100.0), Length::from_pt(100.0));
585        assert!(!r.contains(p));
586    }
587
588    #[test]
589    fn test_rect_intersects_self() {
590        let r = Rect::new(
591            Length::from_pt(10.0),
592            Length::from_pt(10.0),
593            Length::from_pt(100.0),
594            Length::from_pt(100.0),
595        );
596        assert!(r.intersects(r));
597    }
598
599    #[test]
600    fn test_rect_intersects_adjacent_no_overlap() {
601        let r1 = Rect::new(
602            Length::from_pt(0.0),
603            Length::from_pt(0.0),
604            Length::from_pt(100.0),
605            Length::from_pt(100.0),
606        );
607        // r2 starts exactly at r1's right edge — no intersection
608        let r2 = Rect::new(
609            Length::from_pt(100.0),
610            Length::from_pt(0.0),
611            Length::from_pt(100.0),
612            Length::from_pt(100.0),
613        );
614        assert!(!r1.intersects(r2));
615    }
616
617    #[test]
618    fn test_rect_translate() {
619        let r = Rect::new(
620            Length::from_pt(10.0),
621            Length::from_pt(20.0),
622            Length::from_pt(50.0),
623            Length::from_pt(30.0),
624        );
625        let t = r.translate(Length::from_pt(5.0), Length::from_pt(-5.0));
626        assert_eq!(t.x, Length::from_pt(15.0));
627        assert_eq!(t.y, Length::from_pt(15.0));
628        // Size unchanged
629        assert_eq!(t.width, Length::from_pt(50.0));
630        assert_eq!(t.height, Length::from_pt(30.0));
631    }
632
633    #[test]
634    fn test_rect_is_empty_zero_width() {
635        let r = Rect::new(
636            Length::from_pt(0.0),
637            Length::from_pt(0.0),
638            Length::ZERO,
639            Length::from_pt(100.0),
640        );
641        assert!(r.is_empty());
642    }
643
644    #[test]
645    fn test_rect_is_empty_zero_height() {
646        let r = Rect::new(
647            Length::from_pt(0.0),
648            Length::from_pt(0.0),
649            Length::from_pt(100.0),
650            Length::ZERO,
651        );
652        assert!(r.is_empty());
653    }
654
655    #[test]
656    fn test_rect_debug_format() {
657        let r = Rect::ZERO;
658        let s = format!("{:?}", r);
659        assert!(s.contains("Rect"));
660    }
661}