Skip to main content

agg_rust/
bounding_rect.rs

1//! Bounding rectangle calculation.
2//!
3//! Port of `agg_bounding_rect.h` — computes the axis-aligned bounding box
4//! of a vertex source.
5
6use crate::basics::{is_stop, is_vertex, RectD, VertexSource};
7
8/// Compute the bounding rectangle of a single path from a vertex source.
9///
10/// Rewinds the vertex source to `path_id`, iterates all vertices, and
11/// returns the axis-aligned bounding box. Returns `None` if no vertices
12/// are found.
13///
14/// Port of C++ `agg::bounding_rect_single`.
15pub fn bounding_rect_single(vs: &mut dyn VertexSource, path_id: u32) -> Option<RectD> {
16    let mut x = 0.0;
17    let mut y = 0.0;
18    let mut first = true;
19    let mut x1 = 1.0_f64;
20    let mut y1 = 1.0_f64;
21    let mut x2 = 0.0_f64;
22    let mut y2 = 0.0_f64;
23
24    vs.rewind(path_id);
25    loop {
26        let cmd = vs.vertex(&mut x, &mut y);
27        if is_stop(cmd) {
28            break;
29        }
30        if is_vertex(cmd) {
31            if first {
32                x1 = x;
33                y1 = y;
34                x2 = x;
35                y2 = y;
36                first = false;
37            } else {
38                if x < x1 {
39                    x1 = x;
40                }
41                if y < y1 {
42                    y1 = y;
43                }
44                if x > x2 {
45                    x2 = x;
46                }
47                if y > y2 {
48                    y2 = y;
49                }
50            }
51        }
52    }
53
54    if x1 <= x2 && y1 <= y2 {
55        Some(RectD::new(x1, y1, x2, y2))
56    } else {
57        None
58    }
59}
60
61/// Compute the bounding rectangle across multiple paths from a vertex source.
62///
63/// Iterates paths from `start` to `start + num - 1`, rewinding each by
64/// its path ID (obtained from `path_ids`), and returns the combined
65/// bounding box. Returns `None` if no vertices are found.
66///
67/// Port of C++ `agg::bounding_rect`.
68pub fn bounding_rect(
69    vs: &mut dyn VertexSource,
70    path_ids: &[u32],
71    start: usize,
72    num: usize,
73) -> Option<RectD> {
74    let mut x = 0.0;
75    let mut y = 0.0;
76    let mut first = true;
77    let mut x1 = 1.0_f64;
78    let mut y1 = 1.0_f64;
79    let mut x2 = 0.0_f64;
80    let mut y2 = 0.0_f64;
81
82    for i in 0..num {
83        vs.rewind(path_ids[start + i]);
84        loop {
85            let cmd = vs.vertex(&mut x, &mut y);
86            if is_stop(cmd) {
87                break;
88            }
89            if is_vertex(cmd) {
90                if first {
91                    x1 = x;
92                    y1 = y;
93                    x2 = x;
94                    y2 = y;
95                    first = false;
96                } else {
97                    if x < x1 {
98                        x1 = x;
99                    }
100                    if y < y1 {
101                        y1 = y;
102                    }
103                    if x > x2 {
104                        x2 = x;
105                    }
106                    if y > y2 {
107                        y2 = y;
108                    }
109                }
110            }
111        }
112    }
113
114    if x1 <= x2 && y1 <= y2 {
115        Some(RectD::new(x1, y1, x2, y2))
116    } else {
117        None
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124    use crate::basics::{PATH_CMD_LINE_TO, PATH_CMD_MOVE_TO, PATH_CMD_STOP};
125    use crate::ellipse::Ellipse;
126
127    /// Minimal test vertex source: a triangle.
128    struct Triangle {
129        vertices: [(f64, f64); 3],
130        index: usize,
131    }
132
133    impl Triangle {
134        fn new(x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) -> Self {
135            Self {
136                vertices: [(x1, y1), (x2, y2), (x3, y3)],
137                index: 0,
138            }
139        }
140    }
141
142    impl VertexSource for Triangle {
143        fn rewind(&mut self, _path_id: u32) {
144            self.index = 0;
145        }
146
147        fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
148            if self.index < 3 {
149                *x = self.vertices[self.index].0;
150                *y = self.vertices[self.index].1;
151                self.index += 1;
152                if self.index == 1 {
153                    PATH_CMD_MOVE_TO
154                } else {
155                    PATH_CMD_LINE_TO
156                }
157            } else {
158                PATH_CMD_STOP
159            }
160        }
161    }
162
163    #[test]
164    fn test_bounding_rect_single_triangle() {
165        let mut tri = Triangle::new(10.0, 20.0, 50.0, 80.0, 30.0, 10.0);
166        let r = bounding_rect_single(&mut tri, 0).unwrap();
167        assert!((r.x1 - 10.0).abs() < 1e-10);
168        assert!((r.y1 - 10.0).abs() < 1e-10);
169        assert!((r.x2 - 50.0).abs() < 1e-10);
170        assert!((r.y2 - 80.0).abs() < 1e-10);
171    }
172
173    #[test]
174    fn test_bounding_rect_single_ellipse() {
175        let mut e = Ellipse::new(50.0, 50.0, 30.0, 20.0, 64, false);
176        let r = bounding_rect_single(&mut e, 0).unwrap();
177        // Ellipse center (50,50), rx=30, ry=20
178        assert!((r.x1 - 20.0).abs() < 1.0); // ~20
179        assert!((r.y1 - 30.0).abs() < 1.0); // ~30
180        assert!((r.x2 - 80.0).abs() < 1.0); // ~80
181        assert!((r.y2 - 70.0).abs() < 1.0); // ~70
182    }
183
184    #[test]
185    fn test_bounding_rect_empty_returns_none() {
186        struct Empty;
187        impl VertexSource for Empty {
188            fn rewind(&mut self, _: u32) {}
189            fn vertex(&mut self, _x: &mut f64, _y: &mut f64) -> u32 {
190                PATH_CMD_STOP
191            }
192        }
193        let mut e = Empty;
194        assert!(bounding_rect_single(&mut e, 0).is_none());
195    }
196
197    #[test]
198    fn test_bounding_rect_single_point() {
199        struct SinglePoint;
200        impl VertexSource for SinglePoint {
201            fn rewind(&mut self, _: u32) {}
202            fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
203                // Only return one vertex, then stop
204                static mut CALLED: bool = false;
205                unsafe {
206                    if !CALLED {
207                        CALLED = true;
208                        *x = 42.0;
209                        *y = 17.0;
210                        PATH_CMD_MOVE_TO
211                    } else {
212                        CALLED = false; // reset for next test
213                        PATH_CMD_STOP
214                    }
215                }
216            }
217        }
218        let mut sp = SinglePoint;
219        let r = bounding_rect_single(&mut sp, 0).unwrap();
220        assert!((r.x1 - 42.0).abs() < 1e-10);
221        assert!((r.y1 - 17.0).abs() < 1e-10);
222        assert!((r.x2 - 42.0).abs() < 1e-10);
223        assert!((r.y2 - 17.0).abs() < 1e-10);
224    }
225
226    #[test]
227    fn test_bounding_rect_multi_path() {
228        let mut tri = Triangle::new(10.0, 20.0, 50.0, 80.0, 30.0, 10.0);
229        let ids = [0u32];
230        let r = bounding_rect(&mut tri, &ids, 0, 1).unwrap();
231        assert!((r.x1 - 10.0).abs() < 1e-10);
232        assert!((r.y1 - 10.0).abs() < 1e-10);
233        assert!((r.x2 - 50.0).abs() < 1e-10);
234        assert!((r.y2 - 80.0).abs() < 1e-10);
235    }
236}