oxidize_pdf/
geometry.rs

1//! Basic geometric types for PDF
2
3/// A point in 2D space
4#[derive(Debug, Clone, Copy, PartialEq)]
5pub struct Point {
6    /// X coordinate
7    pub x: f64,
8    /// Y coordinate  
9    pub y: f64,
10}
11
12impl Point {
13    /// Create a new point
14    pub fn new(x: f64, y: f64) -> Self {
15        Self { x, y }
16    }
17
18    /// Origin point (0, 0)
19    pub fn origin() -> Self {
20        Self { x: 0.0, y: 0.0 }
21    }
22}
23
24/// A rectangle defined by two points
25#[derive(Debug, Clone, Copy, PartialEq)]
26pub struct Rectangle {
27    /// Lower-left corner
28    pub lower_left: Point,
29    /// Upper-right corner
30    pub upper_right: Point,
31}
32
33impl Rectangle {
34    /// Create a new rectangle from two points
35    pub fn new(lower_left: Point, upper_right: Point) -> Self {
36        Self {
37            lower_left,
38            upper_right,
39        }
40    }
41
42    /// Create a rectangle from position and size
43    pub fn from_position_and_size(x: f64, y: f64, width: f64, height: f64) -> Self {
44        Self {
45            lower_left: Point::new(x, y),
46            upper_right: Point::new(x + width, y + height),
47        }
48    }
49
50    /// Get the width
51    pub fn width(&self) -> f64 {
52        self.upper_right.x - self.lower_left.x
53    }
54
55    /// Get the height
56    pub fn height(&self) -> f64 {
57        self.upper_right.y - self.lower_left.y
58    }
59
60    /// Get the center point
61    pub fn center(&self) -> Point {
62        Point::new(
63            (self.lower_left.x + self.upper_right.x) / 2.0,
64            (self.lower_left.y + self.upper_right.y) / 2.0,
65        )
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn test_point() {
75        let p = Point::new(10.0, 20.0);
76        assert_eq!(p.x, 10.0);
77        assert_eq!(p.y, 20.0);
78
79        let origin = Point::origin();
80        assert_eq!(origin.x, 0.0);
81        assert_eq!(origin.y, 0.0);
82    }
83
84    #[test]
85    fn test_rectangle() {
86        let rect = Rectangle::new(Point::new(10.0, 20.0), Point::new(110.0, 120.0));
87
88        assert_eq!(rect.width(), 100.0);
89        assert_eq!(rect.height(), 100.0);
90
91        let center = rect.center();
92        assert_eq!(center.x, 60.0);
93        assert_eq!(center.y, 70.0);
94    }
95
96    #[test]
97    fn test_rectangle_from_position_and_size() {
98        let rect = Rectangle::from_position_and_size(10.0, 20.0, 50.0, 30.0);
99        assert_eq!(rect.lower_left.x, 10.0);
100        assert_eq!(rect.lower_left.y, 20.0);
101        assert_eq!(rect.upper_right.x, 60.0);
102        assert_eq!(rect.upper_right.y, 50.0);
103    }
104
105    #[test]
106    fn test_point_edge_cases() {
107        // Test with negative coordinates
108        let p = Point::new(-10.0, -20.0);
109        assert_eq!(p.x, -10.0);
110        assert_eq!(p.y, -20.0);
111
112        // Test with zero coordinates
113        let p = Point::new(0.0, 0.0);
114        assert_eq!(p.x, 0.0);
115        assert_eq!(p.y, 0.0);
116
117        // Test with very large coordinates
118        let p = Point::new(f64::MAX, f64::MIN);
119        assert_eq!(p.x, f64::MAX);
120        assert_eq!(p.y, f64::MIN);
121
122        // Test with infinity and NaN
123        let p = Point::new(f64::INFINITY, f64::NEG_INFINITY);
124        assert_eq!(p.x, f64::INFINITY);
125        assert_eq!(p.y, f64::NEG_INFINITY);
126
127        let p = Point::new(f64::NAN, 0.0);
128        assert!(p.x.is_nan());
129        assert_eq!(p.y, 0.0);
130    }
131
132    #[test]
133    fn test_point_copy_clone_debug() {
134        let p1 = Point::new(5.0, 10.0);
135        let p2 = p1; // Copy
136        let p3 = p1.clone(); // Clone
137
138        assert_eq!(p1, p2);
139        assert_eq!(p1, p3);
140        assert_eq!(p2, p3);
141
142        // Test debug formatting
143        let debug_str = format!("{:?}", p1);
144        assert!(debug_str.contains("Point"));
145        assert!(debug_str.contains("5.0"));
146        assert!(debug_str.contains("10.0"));
147    }
148
149    #[test]
150    fn test_point_partial_eq() {
151        let p1 = Point::new(1.0, 2.0);
152        let p2 = Point::new(1.0, 2.0);
153        let p3 = Point::new(1.0, 3.0);
154        let p4 = Point::new(2.0, 2.0);
155
156        assert_eq!(p1, p2);
157        assert_ne!(p1, p3);
158        assert_ne!(p1, p4);
159        assert_ne!(p3, p4);
160
161        // Test with special values
162        let p_inf = Point::new(f64::INFINITY, f64::INFINITY);
163        let p_inf2 = Point::new(f64::INFINITY, f64::INFINITY);
164        assert_eq!(p_inf, p_inf2);
165
166        let p_nan = Point::new(f64::NAN, 0.0);
167        let p_nan2 = Point::new(f64::NAN, 0.0);
168        assert_ne!(p_nan, p_nan2); // NaN != NaN
169    }
170
171    #[test]
172    fn test_rectangle_edge_cases() {
173        // Test with negative dimensions
174        let rect = Rectangle::from_position_and_size(-10.0, -20.0, 5.0, 10.0);
175        assert_eq!(rect.width(), 5.0);
176        assert_eq!(rect.height(), 10.0);
177        assert_eq!(rect.lower_left.x, -10.0);
178        assert_eq!(rect.lower_left.y, -20.0);
179        assert_eq!(rect.upper_right.x, -5.0);
180        assert_eq!(rect.upper_right.y, -10.0);
181
182        // Test with zero dimensions
183        let rect = Rectangle::from_position_and_size(0.0, 0.0, 0.0, 0.0);
184        assert_eq!(rect.width(), 0.0);
185        assert_eq!(rect.height(), 0.0);
186        let center = rect.center();
187        assert_eq!(center.x, 0.0);
188        assert_eq!(center.y, 0.0);
189
190        // Test with very large dimensions
191        let rect = Rectangle::from_position_and_size(0.0, 0.0, f64::MAX / 2.0, f64::MAX / 2.0);
192        assert_eq!(rect.width(), f64::MAX / 2.0);
193        assert_eq!(rect.height(), f64::MAX / 2.0);
194    }
195
196    #[test]
197    fn test_rectangle_negative_dimensions() {
198        // Test inverted rectangle (lower_left actually higher than upper_right)
199        let rect = Rectangle::new(Point::new(10.0, 20.0), Point::new(5.0, 15.0));
200        assert_eq!(rect.width(), -5.0); // Negative width
201        assert_eq!(rect.height(), -5.0); // Negative height
202
203        let center = rect.center();
204        assert_eq!(center.x, 7.5);
205        assert_eq!(center.y, 17.5);
206    }
207
208    #[test]
209    fn test_rectangle_center_precision() {
210        // Test center calculation with odd dimensions
211        let rect = Rectangle::from_position_and_size(1.0, 1.0, 3.0, 5.0);
212        let center = rect.center();
213        assert_eq!(center.x, 2.5); // (1 + 4) / 2
214        assert_eq!(center.y, 3.5); // (1 + 6) / 2
215
216        // Test with floating point precision issues
217        let rect = Rectangle::from_position_and_size(0.1, 0.1, 0.2, 0.2);
218        let center = rect.center();
219        assert!((center.x - 0.2).abs() < f64::EPSILON);
220        assert!((center.y - 0.2).abs() < f64::EPSILON);
221    }
222
223    #[test]
224    fn test_rectangle_copy_clone_debug_partial_eq() {
225        let rect1 = Rectangle::new(Point::new(0.0, 0.0), Point::new(10.0, 20.0));
226        let rect2 = rect1; // Copy
227        let rect3 = rect1.clone(); // Clone
228        let rect4 = Rectangle::new(Point::new(0.0, 0.0), Point::new(10.0, 21.0));
229
230        assert_eq!(rect1, rect2);
231        assert_eq!(rect1, rect3);
232        assert_ne!(rect1, rect4);
233
234        // Test debug formatting
235        let debug_str = format!("{:?}", rect1);
236        assert!(debug_str.contains("Rectangle"));
237        assert!(debug_str.contains("lower_left"));
238        assert!(debug_str.contains("upper_right"));
239    }
240
241    #[test]
242    fn test_rectangle_with_special_float_values() {
243        // Test with infinity
244        let rect = Rectangle::new(
245            Point::new(f64::NEG_INFINITY, f64::NEG_INFINITY),
246            Point::new(f64::INFINITY, f64::INFINITY),
247        );
248        assert_eq!(rect.width(), f64::INFINITY);
249        assert_eq!(rect.height(), f64::INFINITY);
250
251        // Test with NaN
252        let rect = Rectangle::new(Point::new(0.0, 0.0), Point::new(f64::NAN, 10.0));
253        assert!(rect.width().is_nan());
254        assert_eq!(rect.height(), 10.0);
255
256        let center = rect.center();
257        assert!(center.x.is_nan());
258        assert_eq!(center.y, 5.0);
259    }
260
261    #[test]
262    fn test_rectangle_extreme_coordinates() {
263        // Test with maximum and minimum finite values
264        let rect = Rectangle::new(
265            Point::new(f64::MIN, f64::MIN),
266            Point::new(f64::MAX, f64::MAX),
267        );
268
269        // Width and height should be positive infinity due to overflow
270        assert!(rect.width().is_infinite() && rect.width() > 0.0);
271        assert!(rect.height().is_infinite() && rect.height() > 0.0);
272    }
273
274    #[test]
275    fn test_point_origin_function() {
276        let origin1 = Point::origin();
277        let origin2 = Point::new(0.0, 0.0);
278
279        assert_eq!(origin1, origin2);
280        assert_eq!(origin1.x, 0.0);
281        assert_eq!(origin1.y, 0.0);
282
283        // Test multiple calls return equal points
284        let origin3 = Point::origin();
285        assert_eq!(origin1, origin3);
286    }
287
288    #[test]
289    fn test_rectangle_construction_methods() {
290        // Test both construction methods produce equivalent results
291        let rect1 = Rectangle::new(Point::new(5.0, 10.0), Point::new(15.0, 25.0));
292        let rect2 = Rectangle::from_position_and_size(5.0, 10.0, 10.0, 15.0);
293
294        assert_eq!(rect1.lower_left, rect2.lower_left);
295        assert_eq!(rect1.upper_right, rect2.upper_right);
296        assert_eq!(rect1.width(), rect2.width());
297        assert_eq!(rect1.height(), rect2.height());
298        assert_eq!(rect1.center(), rect2.center());
299    }
300
301    #[test]
302    fn test_geometric_calculations() {
303        let rect = Rectangle::from_position_and_size(2.0, 3.0, 8.0, 6.0);
304
305        // Verify all calculations
306        assert_eq!(rect.lower_left.x, 2.0);
307        assert_eq!(rect.lower_left.y, 3.0);
308        assert_eq!(rect.upper_right.x, 10.0);
309        assert_eq!(rect.upper_right.y, 9.0);
310        assert_eq!(rect.width(), 8.0);
311        assert_eq!(rect.height(), 6.0);
312
313        let center = rect.center();
314        assert_eq!(center.x, 6.0); // (2 + 10) / 2
315        assert_eq!(center.y, 6.0); // (3 + 9) / 2
316    }
317
318    #[test]
319    fn test_floating_point_precision() {
320        // Test with values that might have precision issues
321        let p1 = Point::new(1.0 / 3.0, 2.0 / 3.0);
322        let p2 = Point::new(4.0 / 3.0, 5.0 / 3.0);
323        let rect = Rectangle::new(p1, p2);
324
325        let width = rect.width();
326        let height = rect.height();
327        let center = rect.center();
328
329        // These should be exactly 1.0 due to the arithmetic
330        assert!((width - 1.0).abs() < f64::EPSILON);
331        assert!((height - 1.0).abs() < f64::EPSILON);
332
333        // Center should be at (5/6, 7/6)
334        let expected_center_x = 5.0 / 6.0;
335        let expected_center_y = 7.0 / 6.0;
336        assert!((center.x - expected_center_x).abs() < f64::EPSILON);
337        assert!((center.y - expected_center_y).abs() < f64::EPSILON);
338    }
339
340    #[test]
341    fn test_point_with_mathematical_constants() {
342        use std::f64::consts::*;
343        let p = Point::new(PI, E);
344        assert_eq!(p.x, PI);
345        assert_eq!(p.y, E);
346
347        let origin = Point::origin();
348        assert_ne!(p, origin);
349    }
350
351    #[test]
352    fn test_rectangle_area_and_perimeter_concepts() {
353        // While not implemented, test the values needed for these calculations
354        let rect = Rectangle::from_position_and_size(0.0, 0.0, 4.0, 3.0);
355
356        let width = rect.width();
357        let height = rect.height();
358
359        // These values would be used for area and perimeter
360        let calculated_area = width * height;
361        let calculated_perimeter = 2.0 * (width + height);
362
363        assert_eq!(calculated_area, 12.0);
364        assert_eq!(calculated_perimeter, 14.0);
365    }
366
367    #[test]
368    fn test_point_distance_concepts() {
369        // Test values that would be used for distance calculations
370        let p1 = Point::new(0.0, 0.0);
371        let p2 = Point::new(3.0, 4.0);
372
373        let dx = p2.x - p1.x;
374        let dy = p2.y - p1.y;
375        let distance_squared = dx * dx + dy * dy;
376        let distance = distance_squared.sqrt();
377
378        assert_eq!(dx, 3.0);
379        assert_eq!(dy, 4.0);
380        assert_eq!(distance_squared, 25.0);
381        assert_eq!(distance, 5.0);
382    }
383
384    #[test]
385    fn test_rectangle_contains_concepts() {
386        // Test values for point-in-rectangle calculations
387        let rect = Rectangle::from_position_and_size(10.0, 20.0, 30.0, 40.0);
388        let test_point = Point::new(25.0, 35.0);
389
390        // Values for contains check
391        let within_x = test_point.x >= rect.lower_left.x && test_point.x <= rect.upper_right.x;
392        let within_y = test_point.y >= rect.lower_left.y && test_point.y <= rect.upper_right.y;
393        let would_contain = within_x && within_y;
394
395        assert!(within_x);
396        assert!(within_y);
397        assert!(would_contain);
398
399        // Test point outside
400        let outside_point = Point::new(5.0, 35.0);
401        let outside_x =
402            outside_point.x >= rect.lower_left.x && outside_point.x <= rect.upper_right.x;
403        assert!(!outside_x);
404    }
405
406    #[test]
407    fn test_rectangle_intersection_concepts() {
408        let rect1 = Rectangle::from_position_and_size(0.0, 0.0, 10.0, 10.0);
409        let rect2 = Rectangle::from_position_and_size(5.0, 5.0, 10.0, 10.0);
410
411        // Calculate intersection bounds
412        let left = rect1.lower_left.x.max(rect2.lower_left.x);
413        let bottom = rect1.lower_left.y.max(rect2.lower_left.y);
414        let right = rect1.upper_right.x.min(rect2.upper_right.x);
415        let top = rect1.upper_right.y.min(rect2.upper_right.y);
416
417        let intersects = left < right && bottom < top;
418
419        assert_eq!(left, 5.0);
420        assert_eq!(bottom, 5.0);
421        assert_eq!(right, 10.0);
422        assert_eq!(top, 10.0);
423        assert!(intersects);
424
425        // Non-intersecting rectangles
426        let rect3 = Rectangle::from_position_and_size(20.0, 20.0, 5.0, 5.0);
427        let left3 = rect1.lower_left.x.max(rect3.lower_left.x);
428        let right3 = rect1.upper_right.x.min(rect3.upper_right.x);
429        let no_intersection = left3 >= right3;
430        assert!(no_intersection);
431    }
432}