Skip to main content

agg_rust/
conv_marker.rs

1//! Marker placement converter.
2//!
3//! Port of `agg_conv_marker.h` — places marker shapes at positions along a
4//! path, rotating each marker to align with the edge direction.
5
6use crate::basics::{is_stop, VertexSource, PATH_CMD_MOVE_TO, PATH_CMD_STOP};
7use crate::trans_affine::TransAffine;
8
9// ============================================================================
10// ConvMarker
11// ============================================================================
12
13#[derive(Clone, Copy, PartialEq)]
14#[allow(dead_code)]
15enum Status {
16    Initial,
17    Markers,
18    Polygon,
19    Stop,
20}
21
22/// Place marker shapes at positions along a path.
23///
24/// Takes a marker locator (which provides edge start/end vertex pairs)
25/// and marker shapes (which define the shape to place). Each marker is
26/// rotated to match the edge direction and optionally transformed.
27///
28/// Port of C++ `conv_marker<MarkerLocator, MarkerShapes>`.
29pub struct ConvMarker<'a, ML: VertexSource, MS: VertexSource> {
30    marker_locator: &'a mut ML,
31    marker_shapes: &'a mut MS,
32    transform: TransAffine,
33    mtx: TransAffine,
34    status: Status,
35    marker: u32,
36    num_markers: u32,
37}
38
39impl<'a, ML: VertexSource, MS: VertexSource> ConvMarker<'a, ML, MS> {
40    pub fn new(marker_locator: &'a mut ML, marker_shapes: &'a mut MS) -> Self {
41        Self {
42            marker_locator,
43            marker_shapes,
44            transform: TransAffine::new(),
45            mtx: TransAffine::new(),
46            status: Status::Initial,
47            marker: 0,
48            num_markers: 1,
49        }
50    }
51
52    pub fn transform(&self) -> &TransAffine {
53        &self.transform
54    }
55
56    pub fn transform_mut(&mut self) -> &mut TransAffine {
57        &mut self.transform
58    }
59}
60
61impl<ML: VertexSource, MS: VertexSource> VertexSource for ConvMarker<'_, ML, MS> {
62    fn rewind(&mut self, _path_id: u32) {
63        self.status = Status::Initial;
64        self.marker = 0;
65        self.num_markers = 1;
66    }
67
68    fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
69        let mut cmd = PATH_CMD_MOVE_TO;
70        let mut x1: f64 = 0.0;
71        let mut y1: f64 = 0.0;
72        let mut x2: f64 = 0.0;
73        let mut y2: f64 = 0.0;
74
75        // C++ uses switch with fallthrough: initial → markers → polygon.
76        // Rust: loop { match { ... continue for fallthrough } }
77        loop {
78            if is_stop(cmd) {
79                return cmd;
80            }
81            match self.status {
82                Status::Initial => {
83                    if self.num_markers == 0 {
84                        cmd = PATH_CMD_STOP;
85                        continue;
86                    }
87                    self.marker_locator.rewind(self.marker);
88                    self.marker += 1;
89                    self.num_markers = 0;
90                    self.status = Status::Markers;
91                    // fallthrough to Markers
92                    continue;
93                }
94                Status::Markers => {
95                    if is_stop(self.marker_locator.vertex(&mut x1, &mut y1)) {
96                        self.status = Status::Initial;
97                        continue;
98                    }
99                    if is_stop(self.marker_locator.vertex(&mut x2, &mut y2)) {
100                        self.status = Status::Initial;
101                        continue;
102                    }
103                    self.num_markers += 1;
104                    self.mtx = self.transform;
105                    self.mtx
106                        .multiply(&TransAffine::new_rotation((y2 - y1).atan2(x2 - x1)));
107                    self.mtx.multiply(&TransAffine::new_translation(x1, y1));
108                    self.marker_shapes.rewind(self.marker - 1);
109                    self.status = Status::Polygon;
110                    // fallthrough to Polygon
111                    continue;
112                }
113                Status::Polygon => {
114                    cmd = self.marker_shapes.vertex(x, y);
115                    if is_stop(cmd) {
116                        cmd = PATH_CMD_MOVE_TO;
117                        self.status = Status::Markers;
118                        continue;
119                    }
120                    self.mtx.transform(x, y);
121                    return cmd;
122                }
123                Status::Stop => {
124                    cmd = PATH_CMD_STOP;
125                    continue;
126                }
127            }
128        }
129    }
130}
131
132// ============================================================================
133// Tests
134// ============================================================================
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139    use crate::basics::{is_move_to, is_vertex, PATH_CMD_LINE_TO, PATH_CMD_MOVE_TO, PATH_CMD_STOP};
140
141    /// Simple vertex source that emits edge vertices for path_id 0 only.
142    struct SimpleLocator {
143        vertices: Vec<(f64, f64, u32)>,
144        pos: usize,
145        active: bool,
146    }
147
148    impl SimpleLocator {
149        fn new(vertices: Vec<(f64, f64, u32)>) -> Self {
150            Self {
151                vertices,
152                pos: 0,
153                active: false,
154            }
155        }
156    }
157
158    impl VertexSource for SimpleLocator {
159        fn rewind(&mut self, path_id: u32) {
160            if path_id == 0 {
161                self.pos = 0;
162                self.active = true;
163            } else {
164                self.active = false;
165            }
166        }
167        fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
168            if !self.active || self.pos >= self.vertices.len() {
169                return PATH_CMD_STOP;
170            }
171            let (vx, vy, cmd) = self.vertices[self.pos];
172            *x = vx;
173            *y = vy;
174            self.pos += 1;
175            cmd
176        }
177    }
178
179    /// Simple triangle marker shape.
180    struct TriangleMarker {
181        vertices: [(f64, f64, u32); 4],
182        pos: usize,
183    }
184
185    impl TriangleMarker {
186        fn new() -> Self {
187            Self {
188                vertices: [
189                    (0.0, -5.0, PATH_CMD_MOVE_TO),
190                    (5.0, 5.0, PATH_CMD_LINE_TO),
191                    (-5.0, 5.0, PATH_CMD_LINE_TO),
192                    (0.0, 0.0, PATH_CMD_STOP),
193                ],
194                pos: 0,
195            }
196        }
197    }
198
199    impl VertexSource for TriangleMarker {
200        fn rewind(&mut self, _path_id: u32) {
201            self.pos = 0;
202        }
203        fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
204            if self.pos >= self.vertices.len() {
205                return PATH_CMD_STOP;
206            }
207            let (vx, vy, cmd) = self.vertices[self.pos];
208            *x = vx;
209            *y = vy;
210            self.pos += 1;
211            cmd
212        }
213    }
214
215    #[test]
216    fn test_single_marker_horizontal() {
217        // Locator with one horizontal edge: (0,0) → (10,0)
218        let mut locator = SimpleLocator::new(vec![
219            (0.0, 0.0, PATH_CMD_MOVE_TO),
220            (10.0, 0.0, PATH_CMD_LINE_TO),
221        ]);
222        let mut shape = TriangleMarker::new();
223        let mut marker = ConvMarker::new(&mut locator, &mut shape);
224        marker.rewind(0);
225
226        // First vertex should be a move_to
227        let mut x = 0.0;
228        let mut y = 0.0;
229        let cmd = marker.vertex(&mut x, &mut y);
230        assert!(is_move_to(cmd));
231
232        // Should get 2 more line_to vertices
233        let cmd = marker.vertex(&mut x, &mut y);
234        assert!(is_vertex(cmd));
235        let cmd = marker.vertex(&mut x, &mut y);
236        assert!(is_vertex(cmd));
237    }
238
239    #[test]
240    fn test_marker_at_origin_no_rotation() {
241        // Edge along x-axis: rotation = 0, translation = (0,0)
242        let mut locator = SimpleLocator::new(vec![
243            (0.0, 0.0, PATH_CMD_MOVE_TO),
244            (10.0, 0.0, PATH_CMD_LINE_TO),
245        ]);
246        let mut shape = TriangleMarker::new();
247        let mut marker = ConvMarker::new(&mut locator, &mut shape);
248        marker.rewind(0);
249
250        let mut x = 0.0;
251        let mut y = 0.0;
252        let cmd = marker.vertex(&mut x, &mut y);
253        assert!(is_move_to(cmd));
254        // First vertex of triangle (0, -5) rotated by 0 + translated to (0,0) = (0, -5)
255        assert!((x - 0.0).abs() < 1e-8);
256        assert!((y - (-5.0)).abs() < 1e-8);
257    }
258
259    #[test]
260    fn test_marker_terminates() {
261        // Single edge, after 3 triangle vertices we should get stop
262        let mut locator = SimpleLocator::new(vec![
263            (0.0, 0.0, PATH_CMD_MOVE_TO),
264            (10.0, 0.0, PATH_CMD_LINE_TO),
265        ]);
266        let mut shape = TriangleMarker::new();
267        let mut marker = ConvMarker::new(&mut locator, &mut shape);
268        marker.rewind(0);
269
270        let mut x = 0.0;
271        let mut y = 0.0;
272        // Read all 3 vertices of the triangle shape
273        marker.vertex(&mut x, &mut y);
274        marker.vertex(&mut x, &mut y);
275        marker.vertex(&mut x, &mut y);
276        // Next should be stop (no more edges from locator)
277        let cmd = marker.vertex(&mut x, &mut y);
278        assert!(is_stop(cmd));
279    }
280
281    #[test]
282    fn test_user_transform() {
283        let mut locator = SimpleLocator::new(vec![
284            (0.0, 0.0, PATH_CMD_MOVE_TO),
285            (10.0, 0.0, PATH_CMD_LINE_TO),
286        ]);
287        let mut shape = TriangleMarker::new();
288        let mut marker = ConvMarker::new(&mut locator, &mut shape);
289        // Apply a scale to the user transform
290        *marker.transform_mut() = TransAffine::new_scaling_uniform(2.0);
291        marker.rewind(0);
292
293        let mut x = 0.0;
294        let mut y = 0.0;
295        marker.vertex(&mut x, &mut y);
296        // First vertex: (0, -5) scaled by 2 = (0, -10), then rotated 0, translated to (0,0)
297        assert!((x - 0.0).abs() < 1e-8);
298        assert!((y - (-10.0)).abs() < 1e-8);
299    }
300
301    #[test]
302    fn test_empty_locator() {
303        let mut locator = SimpleLocator::new(vec![]);
304        let mut shape = TriangleMarker::new();
305        let mut marker = ConvMarker::new(&mut locator, &mut shape);
306        marker.rewind(0);
307
308        let mut x = 0.0;
309        let mut y = 0.0;
310        let cmd = marker.vertex(&mut x, &mut y);
311        assert!(is_stop(cmd));
312    }
313
314    #[test]
315    fn test_single_vertex_locator() {
316        // Only one vertex (need 2 for an edge) → should stop
317        let mut locator = SimpleLocator::new(vec![(0.0, 0.0, PATH_CMD_MOVE_TO)]);
318        let mut shape = TriangleMarker::new();
319        let mut marker = ConvMarker::new(&mut locator, &mut shape);
320        marker.rewind(0);
321
322        let mut x = 0.0;
323        let mut y = 0.0;
324        let cmd = marker.vertex(&mut x, &mut y);
325        assert!(is_stop(cmd));
326    }
327
328    #[test]
329    fn test_marker_vertical_edge() {
330        // Vertical edge: rotation = π/2
331        let mut locator = SimpleLocator::new(vec![
332            (0.0, 0.0, PATH_CMD_MOVE_TO),
333            (0.0, 10.0, PATH_CMD_LINE_TO),
334        ]);
335        let mut shape = TriangleMarker::new();
336        let mut marker = ConvMarker::new(&mut locator, &mut shape);
337        marker.rewind(0);
338
339        let mut x = 0.0;
340        let mut y = 0.0;
341        marker.vertex(&mut x, &mut y);
342        // First vertex: (0, -5) rotated by π/2 → (5, 0), translated to (0,0) → (5, 0)
343        assert!((x - 5.0).abs() < 1e-8);
344        assert!((y - 0.0).abs() < 1e-8);
345    }
346
347    #[test]
348    fn test_rewind_resets() {
349        let mut locator = SimpleLocator::new(vec![
350            (0.0, 0.0, PATH_CMD_MOVE_TO),
351            (10.0, 0.0, PATH_CMD_LINE_TO),
352        ]);
353        let mut shape = TriangleMarker::new();
354        let mut marker = ConvMarker::new(&mut locator, &mut shape);
355
356        marker.rewind(0);
357        let mut x = 0.0;
358        let mut y = 0.0;
359        marker.vertex(&mut x, &mut y);
360
361        // Rewind and try again — should produce same result
362        marker.rewind(0);
363        let mut x2 = 0.0;
364        let mut y2 = 0.0;
365        marker.vertex(&mut x2, &mut y2);
366        assert_eq!(x, x2);
367        assert_eq!(y, y2);
368    }
369}