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