xberg_native_pdf/geometry/mod.rs
1//! Geometric primitives for layout analysis.
2//!
3//! This module provides basic geometric types and operations used throughout
4//! the layout analysis algorithms.
5
6#![forbid(unsafe_code)]
7
8/// A 2D point in document space.
9#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, Default)]
10pub struct Point {
11 /// X coordinate
12 pub x: f32,
13 /// Y coordinate
14 pub y: f32,
15}
16
17impl Point {
18 /// Create a new point.
19 ///
20 /// # Examples
21 ///
22 /// ```
23 /// use xberg_native_pdf::geometry::Point;
24 ///
25 /// let point = Point::new(10.0, 20.0);
26 /// assert_eq!(point.x, 10.0);
27 /// assert_eq!(point.y, 20.0);
28 /// ```
29 pub fn new(x: f32, y: f32) -> Self {
30 Self { x, y }
31 }
32}
33
34/// A rectangle in document space.
35#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, Default)]
36pub struct Rect {
37 /// X coordinate of top-left corner
38 pub x: f32,
39 /// Y coordinate of top-left corner
40 pub y: f32,
41 /// Width of rectangle
42 pub width: f32,
43 /// Height of rectangle
44 pub height: f32,
45}
46
47impl Rect {
48 /// Create a new rectangle from position and dimensions.
49 /// Normalizes dimensions so width and height are always non-negative.
50 ///
51 /// # Examples
52 ///
53 /// ```
54 /// use xberg_native_pdf::geometry::Rect;
55 ///
56 /// let rect = Rect::new(0.0, 50.0, 100.0, -50.0);
57 /// assert_eq!(rect.y, 0.0);
58 /// assert_eq!(rect.height, 50.0);
59 /// ```
60 pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
61 let (nx, nw) = if width < 0.0 { (x + width, -width) } else { (x, width) };
62 let (ny, nh) = if height < 0.0 {
63 (y + height, -height)
64 } else {
65 (y, height)
66 };
67 Self {
68 x: nx,
69 y: ny,
70 width: nw,
71 height: nh,
72 }
73 }
74
75 /// Ensure width and height are non-negative.
76 pub fn normalize(self) -> Self {
77 Self::new(self.x, self.y, self.width, self.height)
78 }
79
80 /// Create a rectangle from two corner points.
81 ///
82 /// # Examples
83 ///
84 /// ```
85 /// use xberg_native_pdf::geometry::Rect;
86 ///
87 /// let rect = Rect::from_points(10.0, 20.0, 110.0, 70.0);
88 /// assert_eq!(rect.x, 10.0);
89 /// assert_eq!(rect.y, 20.0);
90 /// assert_eq!(rect.width, 100.0);
91 /// assert_eq!(rect.height, 50.0);
92 /// ```
93 pub fn from_points(x0: f32, y0: f32, x1: f32, y1: f32) -> Self {
94 Self {
95 x: x0,
96 y: y0,
97 width: x1 - x0,
98 height: y1 - y0,
99 }
100 }
101
102 /// Get the left edge x-coordinate.
103 pub fn left(&self) -> f32 {
104 self.x
105 }
106
107 /// Get the right edge x-coordinate.
108 pub fn right(&self) -> f32 {
109 self.x + self.width
110 }
111
112 /// Get the top edge y-coordinate.
113 pub fn top(&self) -> f32 {
114 self.y
115 }
116
117 /// Get the bottom edge y-coordinate.
118 pub fn bottom(&self) -> f32 {
119 self.y + self.height
120 }
121
122 /// Get the center point of the rectangle.
123 ///
124 /// # Examples
125 ///
126 /// ```
127 /// use xberg_native_pdf::geometry::Rect;
128 ///
129 /// let rect = Rect::new(0.0, 0.0, 100.0, 50.0);
130 /// let center = rect.center();
131 /// assert_eq!(center.x, 50.0);
132 /// assert_eq!(center.y, 25.0);
133 /// ```
134 pub fn center(&self) -> Point {
135 Point {
136 x: self.x + self.width / 2.0,
137 y: self.y + self.height / 2.0,
138 }
139 }
140
141 /// Check if this rectangle intersects with another.
142 ///
143 /// # Examples
144 ///
145 /// ```
146 /// use xberg_native_pdf::geometry::Rect;
147 ///
148 /// let r1 = Rect::new(0.0, 0.0, 100.0, 100.0);
149 /// let r2 = Rect::new(50.0, 50.0, 100.0, 100.0);
150 /// let r3 = Rect::new(200.0, 200.0, 100.0, 100.0);
151 ///
152 /// assert!(r1.intersects(&r2));
153 /// assert!(!r1.intersects(&r3));
154 /// ```
155 pub fn intersects(&self, other: &Rect) -> bool {
156 self.left() < other.right()
157 && self.right() > other.left()
158 && self.top() < other.bottom()
159 && self.bottom() > other.top()
160 }
161
162 /// Compute the intersection of this rectangle with another.
163 ///
164 /// Returns the overlapping region as a new rectangle, or None if they don't overlap.
165 ///
166 /// # Examples
167 ///
168 /// ```
169 /// use xberg_native_pdf::geometry::Rect;
170 ///
171 /// let r1 = Rect::new(0.0, 0.0, 100.0, 100.0);
172 /// let r2 = Rect::new(50.0, 50.0, 100.0, 100.0);
173 /// let r3 = Rect::new(200.0, 200.0, 100.0, 100.0);
174 ///
175 /// let inter = r1.intersection(&r2).unwrap();
176 /// assert_eq!(inter.x, 50.0);
177 /// assert_eq!(inter.y, 50.0);
178 /// assert_eq!(inter.width, 50.0);
179 /// assert_eq!(inter.height, 50.0);
180 ///
181 /// assert!(r1.intersection(&r3).is_none());
182 /// ```
183 pub fn intersection(&self, other: &Rect) -> Option<Rect> {
184 if !self.intersects(other) {
185 return None;
186 }
187
188 let x = self.left().max(other.left());
189 let y = self.top().max(other.top());
190 let right = self.right().min(other.right());
191 let bottom = self.bottom().min(other.bottom());
192
193 Some(Rect::new(x, y, right - x, bottom - y))
194 }
195
196 /// Check if this rectangle contains a point.
197 ///
198 /// # Examples
199 ///
200 /// ```
201 /// use xberg_native_pdf::geometry::{Rect, Point};
202 ///
203 /// let rect = Rect::new(0.0, 0.0, 100.0, 100.0);
204 /// let p1 = Point::new(50.0, 50.0);
205 /// let p2 = Point::new(150.0, 150.0);
206 ///
207 /// assert!(rect.contains_point(&p1));
208 /// assert!(!rect.contains_point(&p2));
209 /// ```
210 pub fn contains_point(&self, p: &Point) -> bool {
211 p.x >= self.left() && p.x <= self.right() && p.y >= self.top() && p.y <= self.bottom()
212 }
213
214 /// Check if this rectangle fully contains another rectangle.
215 ///
216 /// # Examples
217 ///
218 /// ```
219 /// use xberg_native_pdf::geometry::Rect;
220 ///
221 /// let r1 = Rect::new(0.0, 0.0, 100.0, 100.0);
222 /// let r2 = Rect::new(10.0, 10.0, 50.0, 50.0);
223 /// let r3 = Rect::new(50.0, 50.0, 100.0, 100.0);
224 ///
225 /// assert!(r1.contains_rect(&r2));
226 /// assert!(!r1.contains_rect(&r3));
227 /// ```
228 pub fn contains_rect(&self, other: &Rect) -> bool {
229 other.left() >= self.left()
230 && other.right() <= self.right()
231 && other.top() >= self.top()
232 && other.bottom() <= self.bottom()
233 }
234
235 /// Compute the union of this rectangle with another.
236 ///
237 /// Returns the smallest rectangle that contains both rectangles.
238 ///
239 /// # Examples
240 ///
241 /// ```
242 /// use xberg_native_pdf::geometry::Rect;
243 ///
244 /// let r1 = Rect::new(0.0, 0.0, 50.0, 50.0);
245 /// let r2 = Rect::new(25.0, 25.0, 50.0, 50.0);
246 /// let union = r1.union(&r2);
247 ///
248 /// assert_eq!(union.x, 0.0);
249 /// assert_eq!(union.y, 0.0);
250 /// assert_eq!(union.right(), 75.0);
251 /// assert_eq!(union.bottom(), 75.0);
252 /// ```
253 pub fn union(&self, other: &Rect) -> Rect {
254 let x0 = self.left().min(other.left());
255 let y0 = self.top().min(other.top());
256 let x1 = self.right().max(other.right());
257 let y1 = self.bottom().max(other.bottom());
258 Rect::from_points(x0, y0, x1, y1)
259 }
260
261 /// Compute the area of the rectangle.
262 ///
263 /// # Examples
264 ///
265 /// ```
266 /// use xberg_native_pdf::geometry::Rect;
267 ///
268 /// let rect = Rect::new(0.0, 0.0, 100.0, 50.0);
269 /// assert_eq!(rect.area(), 5000.0);
270 /// ```
271 pub fn area(&self) -> f32 {
272 self.width * self.height
273 }
274}
275
276/// Compute the Euclidean distance between two points.
277///
278/// # Examples
279///
280/// ```
281/// use xberg_native_pdf::geometry::{Point, euclidean_distance};
282///
283/// let p1 = Point::new(0.0, 0.0);
284/// let p2 = Point::new(3.0, 4.0);
285///
286/// assert_eq!(euclidean_distance(&p1, &p2), 5.0);
287/// ```
288pub fn euclidean_distance(p1: &Point, p2: &Point) -> f32 {
289 ((p2.x - p1.x).powi(2) + (p2.y - p1.y).powi(2)).sqrt()
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295
296 #[test]
297 fn test_point_creation() {
298 let p = Point::new(10.0, 20.0);
299 assert_eq!(p.x, 10.0);
300 assert_eq!(p.y, 20.0);
301 }
302
303 #[test]
304 fn test_rect_creation() {
305 let r = Rect::new(5.0, 10.0, 100.0, 50.0);
306 assert_eq!(r.x, 5.0);
307 assert_eq!(r.y, 10.0);
308 assert_eq!(r.width, 100.0);
309 assert_eq!(r.height, 50.0);
310 }
311
312 #[test]
313 fn test_rect_from_points() {
314 let r = Rect::from_points(10.0, 20.0, 110.0, 70.0);
315 assert_eq!(r.x, 10.0);
316 assert_eq!(r.y, 20.0);
317 assert_eq!(r.width, 100.0);
318 assert_eq!(r.height, 50.0);
319 }
320
321 #[test]
322 fn test_rect_edges() {
323 let r = Rect::new(10.0, 20.0, 100.0, 50.0);
324 assert_eq!(r.left(), 10.0);
325 assert_eq!(r.right(), 110.0);
326 assert_eq!(r.top(), 20.0);
327 assert_eq!(r.bottom(), 70.0);
328 }
329
330 #[test]
331 fn test_rect_center() {
332 let r = Rect::new(0.0, 0.0, 100.0, 50.0);
333 let center = r.center();
334 assert_eq!(center.x, 50.0);
335 assert_eq!(center.y, 25.0);
336 }
337
338 #[test]
339 fn test_rect_intersects() {
340 let r1 = Rect::new(0.0, 0.0, 100.0, 100.0);
341 let r2 = Rect::new(50.0, 50.0, 100.0, 100.0);
342 let r3 = Rect::new(200.0, 200.0, 100.0, 100.0);
343
344 assert!(r1.intersects(&r2));
345 assert!(r2.intersects(&r1));
346 assert!(!r1.intersects(&r3));
347 assert!(!r3.intersects(&r1));
348 }
349
350 #[test]
351 fn test_rect_contains_point() {
352 let r = Rect::new(0.0, 0.0, 100.0, 100.0);
353 let p1 = Point::new(50.0, 50.0);
354 let p2 = Point::new(150.0, 150.0);
355 let p3 = Point::new(0.0, 0.0);
356 let p4 = Point::new(100.0, 100.0);
357
358 assert!(r.contains_point(&p1));
359 assert!(!r.contains_point(&p2));
360 assert!(r.contains_point(&p3));
361 assert!(r.contains_point(&p4));
362 }
363
364 #[test]
365 fn test_rect_union() {
366 let r1 = Rect::new(0.0, 0.0, 50.0, 50.0);
367 let r2 = Rect::new(25.0, 25.0, 50.0, 50.0);
368 let union = r1.union(&r2);
369
370 assert_eq!(union.x, 0.0);
371 assert_eq!(union.y, 0.0);
372 assert_eq!(union.right(), 75.0);
373 assert_eq!(union.bottom(), 75.0);
374 }
375
376 #[test]
377 fn test_rect_area() {
378 let r = Rect::new(0.0, 0.0, 100.0, 50.0);
379 assert_eq!(r.area(), 5000.0);
380 }
381
382 #[test]
383 fn test_euclidean_distance() {
384 let p1 = Point::new(0.0, 0.0);
385 let p2 = Point::new(3.0, 4.0);
386 assert_eq!(euclidean_distance(&p1, &p2), 5.0);
387
388 let p3 = Point::new(1.0, 1.0);
389 let p4 = Point::new(1.0, 1.0);
390 assert_eq!(euclidean_distance(&p3, &p4), 0.0);
391 }
392}