Skip to main content

agg_rust/
conv_adaptor_vcgen.rs

1//! Generic adapter connecting a vertex source to a vertex generator.
2//!
3//! Port of `agg_conv_adaptor_vcgen.h` — the core adapter pattern used by
4//! `ConvStroke`, `ConvDash`, and `ConvContour`.
5
6use crate::basics::{
7    is_end_poly, is_move_to, is_stop, is_vertex, VertexSource, PATH_CMD_MOVE_TO, PATH_CMD_STOP,
8};
9
10// ============================================================================
11// VcgenGenerator trait
12// ============================================================================
13
14/// Vertex generator interface used by `ConvAdaptorVcgen`.
15///
16/// Port of the implicit C++ "Generator" concept used by `conv_adaptor_vcgen`.
17pub trait VcgenGenerator {
18    fn remove_all(&mut self);
19    fn add_vertex(&mut self, x: f64, y: f64, cmd: u32);
20    fn rewind(&mut self, path_id: u32);
21    fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32;
22}
23
24// ============================================================================
25// ConvAdaptorVcgen
26// ============================================================================
27
28#[derive(Debug, Clone, Copy, PartialEq)]
29enum Status {
30    Initial,
31    Accumulate,
32    Generate,
33}
34
35/// Generic adapter connecting a `VertexSource` to a `VcgenGenerator`.
36///
37/// Reads vertices from the source, feeds them to the generator, then
38/// yields the generated vertices. Handles path splitting on `move_to`.
39///
40/// Port of C++ `conv_adaptor_vcgen<VertexSource, Generator, Markers>`.
41/// Markers omitted (null_markers behavior).
42pub struct ConvAdaptorVcgen<VS: VertexSource, Gen: VcgenGenerator> {
43    source: VS,
44    generator: Gen,
45    status: Status,
46    last_cmd: u32,
47    start_x: f64,
48    start_y: f64,
49}
50
51impl<VS: VertexSource, Gen: VcgenGenerator> ConvAdaptorVcgen<VS, Gen> {
52    pub fn new(source: VS, generator: Gen) -> Self {
53        Self {
54            source,
55            generator,
56            status: Status::Initial,
57            last_cmd: 0,
58            start_x: 0.0,
59            start_y: 0.0,
60        }
61    }
62
63    pub fn generator(&self) -> &Gen {
64        &self.generator
65    }
66
67    pub fn generator_mut(&mut self) -> &mut Gen {
68        &mut self.generator
69    }
70
71    pub fn source(&self) -> &VS {
72        &self.source
73    }
74
75    pub fn source_mut(&mut self) -> &mut VS {
76        &mut self.source
77    }
78}
79
80impl<VS: VertexSource, Gen: VcgenGenerator> VertexSource for ConvAdaptorVcgen<VS, Gen> {
81    fn rewind(&mut self, path_id: u32) {
82        self.source.rewind(path_id);
83        self.status = Status::Initial;
84    }
85
86    fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
87        loop {
88            match self.status {
89                Status::Initial => {
90                    // null_markers.remove_all() — no-op
91                    self.last_cmd = self.source.vertex(&mut self.start_x, &mut self.start_y);
92                    self.status = Status::Accumulate;
93                    // fall through to Accumulate
94                }
95                Status::Accumulate => {
96                    if is_stop(self.last_cmd) {
97                        return PATH_CMD_STOP;
98                    }
99
100                    self.generator.remove_all();
101                    self.generator
102                        .add_vertex(self.start_x, self.start_y, PATH_CMD_MOVE_TO);
103                    // null_markers.add_vertex(...) — no-op
104
105                    loop {
106                        let cmd = self.source.vertex(x, y);
107                        if is_vertex(cmd) {
108                            self.last_cmd = cmd;
109                            if is_move_to(cmd) {
110                                self.start_x = *x;
111                                self.start_y = *y;
112                                break;
113                            }
114                            self.generator.add_vertex(*x, *y, cmd);
115                            // null_markers.add_vertex(*x, *y, PATH_CMD_LINE_TO) — no-op
116                        } else {
117                            if is_stop(cmd) {
118                                self.last_cmd = PATH_CMD_STOP;
119                                break;
120                            }
121                            if is_end_poly(cmd) {
122                                self.generator.add_vertex(*x, *y, cmd);
123                                break;
124                            }
125                        }
126                    }
127                    self.generator.rewind(0);
128                    self.status = Status::Generate;
129                    // fall through to Generate
130                }
131                Status::Generate => {
132                    let cmd = self.generator.vertex(x, y);
133                    if is_stop(cmd) {
134                        // Generator exhausted — go back to Accumulate for next sub-path
135                        self.status = Status::Accumulate;
136                        continue;
137                    }
138                    return cmd;
139                }
140            }
141        }
142    }
143}
144
145// ============================================================================
146// Tests
147// ============================================================================
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152    use crate::basics::PATH_CMD_STOP;
153
154    /// Minimal generator that echoes input as-is.
155    struct EchoGenerator {
156        vertices: Vec<(f64, f64, u32)>,
157        idx: usize,
158    }
159
160    impl EchoGenerator {
161        fn new() -> Self {
162            Self {
163                vertices: Vec::new(),
164                idx: 0,
165            }
166        }
167    }
168
169    impl VcgenGenerator for EchoGenerator {
170        fn remove_all(&mut self) {
171            self.vertices.clear();
172        }
173        fn add_vertex(&mut self, x: f64, y: f64, cmd: u32) {
174            self.vertices.push((x, y, cmd));
175        }
176        fn rewind(&mut self, _path_id: u32) {
177            self.idx = 0;
178        }
179        fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
180            if self.idx >= self.vertices.len() {
181                return PATH_CMD_STOP;
182            }
183            let (vx, vy, cmd) = self.vertices[self.idx];
184            *x = vx;
185            *y = vy;
186            self.idx += 1;
187            cmd
188        }
189    }
190
191    #[test]
192    fn test_empty_source() {
193        use crate::path_storage::PathStorage;
194        let path = PathStorage::new();
195        let mut adaptor = ConvAdaptorVcgen::new(path, EchoGenerator::new());
196        adaptor.rewind(0);
197
198        let (mut x, mut y) = (0.0, 0.0);
199        let cmd = adaptor.vertex(&mut x, &mut y);
200        assert_eq!(cmd, PATH_CMD_STOP);
201    }
202
203    #[test]
204    fn test_passthrough_with_echo() {
205        use crate::path_storage::PathStorage;
206
207        let mut path = PathStorage::new();
208        path.move_to(10.0, 20.0);
209        path.line_to(30.0, 40.0);
210
211        let mut adaptor = ConvAdaptorVcgen::new(path, EchoGenerator::new());
212        adaptor.rewind(0);
213
214        let mut verts = Vec::new();
215        loop {
216            let (mut x, mut y) = (0.0, 0.0);
217            let cmd = adaptor.vertex(&mut x, &mut y);
218            if is_stop(cmd) {
219                break;
220            }
221            verts.push((x, y, cmd));
222        }
223        assert!(
224            verts.len() >= 2,
225            "Expected >= 2 vertices, got {}",
226            verts.len()
227        );
228    }
229
230    #[test]
231    fn test_generator_access() {
232        use crate::path_storage::PathStorage;
233        let path = PathStorage::new();
234        let adaptor = ConvAdaptorVcgen::new(path, EchoGenerator::new());
235        let _ = adaptor.generator();
236    }
237}