Skip to main content

agg_rust/
conv_adaptor_vpgen.rs

1//! Generic vertex processor generator adaptor.
2//!
3//! Port of `agg_conv_adaptor_vpgen.h`.
4//! Feeds source vertices through a vertex processor generator (vpgen),
5//! handling move_to/line_to/close logic automatically.
6
7use crate::basics::{
8    is_closed, is_end_poly, is_move_to, is_stop, is_vertex, VertexSource, PATH_CMD_END_POLY,
9    PATH_CMD_STOP, PATH_FLAGS_CLOSE,
10};
11
12/// Trait for vertex processor generators (vpgen).
13///
14/// A vpgen processes one line segment at a time: `move_to` sets the start
15/// point, `line_to` feeds each subsequent point, and `vertex` produces
16/// the output subdivided/processed vertices.
17pub trait VpgenProcessor {
18    fn reset(&mut self);
19    fn move_to(&mut self, x: f64, y: f64);
20    fn line_to(&mut self, x: f64, y: f64);
21    fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32;
22    fn auto_close() -> bool;
23    fn auto_unclose() -> bool;
24}
25
26/// Generic adaptor that feeds a `VertexSource` through a `VpgenProcessor`.
27///
28/// Port of C++ `conv_adaptor_vpgen<VertexSource, VPGen>`.
29/// Handles the state machine for polygon close/open and auto-close logic.
30pub struct ConvAdaptorVpgen<VS, Gen> {
31    source: VS,
32    vpgen: Gen,
33    start_x: f64,
34    start_y: f64,
35    poly_flags: u32,
36    vertices: i32,
37}
38
39impl<VS: VertexSource, Gen: VpgenProcessor> ConvAdaptorVpgen<VS, Gen> {
40    pub fn new(source: VS, vpgen: Gen) -> Self {
41        Self {
42            source,
43            vpgen,
44            start_x: 0.0,
45            start_y: 0.0,
46            poly_flags: 0,
47            vertices: 0,
48        }
49    }
50
51    pub fn source(&self) -> &VS {
52        &self.source
53    }
54
55    pub fn source_mut(&mut self) -> &mut VS {
56        &mut self.source
57    }
58
59    pub fn vpgen(&self) -> &Gen {
60        &self.vpgen
61    }
62
63    pub fn vpgen_mut(&mut self) -> &mut Gen {
64        &mut self.vpgen
65    }
66}
67
68impl<VS: VertexSource, Gen: VpgenProcessor> VertexSource for ConvAdaptorVpgen<VS, Gen> {
69    fn rewind(&mut self, path_id: u32) {
70        self.source.rewind(path_id);
71        self.vpgen.reset();
72        self.start_x = 0.0;
73        self.start_y = 0.0;
74        self.poly_flags = 0;
75        self.vertices = 0;
76    }
77
78    fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
79        loop {
80            let cmd = self.vpgen.vertex(x, y);
81            if !is_stop(cmd) {
82                return cmd;
83            }
84
85            if self.poly_flags != 0 && !Gen::auto_unclose() {
86                *x = 0.0;
87                *y = 0.0;
88                let cmd = self.poly_flags;
89                self.poly_flags = 0;
90                return cmd;
91            }
92
93            if self.vertices < 0 {
94                if self.vertices < -1 {
95                    self.vertices = 0;
96                    return PATH_CMD_STOP;
97                }
98                self.vpgen.move_to(self.start_x, self.start_y);
99                self.vertices = 1;
100                continue;
101            }
102
103            let mut tx = 0.0;
104            let mut ty = 0.0;
105            let cmd = self.source.vertex(&mut tx, &mut ty);
106
107            if is_vertex(cmd) {
108                if is_move_to(cmd) {
109                    if Gen::auto_close() && self.vertices > 2 {
110                        self.vpgen.line_to(self.start_x, self.start_y);
111                        self.poly_flags = PATH_CMD_END_POLY | PATH_FLAGS_CLOSE;
112                        self.start_x = tx;
113                        self.start_y = ty;
114                        self.vertices = -1;
115                        continue;
116                    }
117                    self.vpgen.move_to(tx, ty);
118                    self.start_x = tx;
119                    self.start_y = ty;
120                    self.vertices = 1;
121                } else {
122                    self.vpgen.line_to(tx, ty);
123                    self.vertices += 1;
124                }
125            } else if is_end_poly(cmd) {
126                self.poly_flags = cmd;
127                if is_closed(cmd) || Gen::auto_close() {
128                    if Gen::auto_close() {
129                        self.poly_flags |= PATH_FLAGS_CLOSE;
130                    }
131                    if self.vertices > 2 {
132                        self.vpgen.line_to(self.start_x, self.start_y);
133                    }
134                    self.vertices = 0;
135                }
136            } else {
137                // PATH_CMD_STOP
138                if Gen::auto_close() && self.vertices > 2 {
139                    self.vpgen.line_to(self.start_x, self.start_y);
140                    self.poly_flags = PATH_CMD_END_POLY | PATH_FLAGS_CLOSE;
141                    self.vertices = -2;
142                    continue;
143                }
144                return PATH_CMD_STOP;
145            }
146        }
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153    use crate::basics::{PATH_CMD_LINE_TO, PATH_CMD_MOVE_TO};
154    use crate::vpgen_segmentator::VpgenSegmentator;
155
156    /// Simple vertex source that yields a single move_to + line_to pair.
157    struct SimpleLineSource {
158        idx: usize,
159    }
160
161    impl SimpleLineSource {
162        fn new() -> Self {
163            Self { idx: 0 }
164        }
165    }
166
167    impl VertexSource for SimpleLineSource {
168        fn rewind(&mut self, _path_id: u32) {
169            self.idx = 0;
170        }
171
172        fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
173            self.idx += 1;
174            match self.idx {
175                1 => {
176                    *x = 0.0;
177                    *y = 0.0;
178                    PATH_CMD_MOVE_TO
179                }
180                2 => {
181                    *x = 100.0;
182                    *y = 0.0;
183                    PATH_CMD_LINE_TO
184                }
185                _ => PATH_CMD_STOP,
186            }
187        }
188    }
189
190    #[test]
191    fn test_adaptor_with_segmentator() {
192        let source = SimpleLineSource::new();
193        let vpgen = VpgenSegmentator::new();
194        let mut adaptor = ConvAdaptorVpgen::new(source, vpgen);
195        adaptor.vpgen_mut().set_approximation_scale(1.0);
196        adaptor.rewind(0);
197
198        let (mut x, mut y) = (0.0, 0.0);
199        let mut count = 0;
200        loop {
201            let cmd = adaptor.vertex(&mut x, &mut y);
202            if is_stop(cmd) {
203                break;
204            }
205            count += 1;
206        }
207        // Should have at least move_to + line_to
208        assert!(count >= 2, "Expected at least 2 vertices, got {count}");
209    }
210
211    #[test]
212    fn test_adaptor_subdivides_long_segment() {
213        let source = SimpleLineSource::new();
214        let vpgen = VpgenSegmentator::new();
215        let mut adaptor = ConvAdaptorVpgen::new(source, vpgen);
216        adaptor.vpgen_mut().set_approximation_scale(10.0); // force subdivisions
217        adaptor.rewind(0);
218
219        let (mut x, mut y) = (0.0, 0.0);
220        let mut count = 0;
221        loop {
222            let cmd = adaptor.vertex(&mut x, &mut y);
223            if is_stop(cmd) {
224                break;
225            }
226            count += 1;
227        }
228        assert!(
229            count > 2,
230            "Long segment with scale=10 should subdivide: got {count}"
231        );
232    }
233}