Skip to main content

agg_rust/
conv_contour.rs

1//! Contour converter for vertex sources.
2//!
3//! Port of `agg_conv_contour.h` — convenience wrapper that combines
4//! `ConvAdaptorVcgen` with `VcgenContour` to produce offset contours.
5
6use crate::basics::VertexSource;
7use crate::conv_adaptor_vcgen::ConvAdaptorVcgen;
8use crate::math_stroke::{InnerJoin, LineJoin};
9use crate::vcgen_contour::VcgenContour;
10
11// ============================================================================
12// ConvContour
13// ============================================================================
14
15/// Contour converter: generates an offset contour from a closed polygon.
16///
17/// Port of C++ `conv_contour<VertexSource>`.
18pub struct ConvContour<VS: VertexSource> {
19    base: ConvAdaptorVcgen<VS, VcgenContour>,
20}
21
22impl<VS: VertexSource> ConvContour<VS> {
23    pub fn new(source: VS) -> Self {
24        Self {
25            base: ConvAdaptorVcgen::new(source, VcgenContour::new()),
26        }
27    }
28
29    // Parameter forwarding
30    pub fn set_line_join(&mut self, lj: LineJoin) {
31        self.base.generator_mut().set_line_join(lj);
32    }
33    pub fn line_join(&self) -> LineJoin {
34        self.base.generator().line_join()
35    }
36
37    pub fn set_inner_join(&mut self, ij: InnerJoin) {
38        self.base.generator_mut().set_inner_join(ij);
39    }
40    pub fn inner_join(&self) -> InnerJoin {
41        self.base.generator().inner_join()
42    }
43
44    pub fn set_width(&mut self, w: f64) {
45        self.base.generator_mut().set_width(w);
46    }
47    pub fn width(&self) -> f64 {
48        self.base.generator().width()
49    }
50
51    pub fn set_miter_limit(&mut self, ml: f64) {
52        self.base.generator_mut().set_miter_limit(ml);
53    }
54    pub fn miter_limit(&self) -> f64 {
55        self.base.generator().miter_limit()
56    }
57
58    pub fn set_miter_limit_theta(&mut self, t: f64) {
59        self.base.generator_mut().set_miter_limit_theta(t);
60    }
61
62    pub fn set_inner_miter_limit(&mut self, ml: f64) {
63        self.base.generator_mut().set_inner_miter_limit(ml);
64    }
65    pub fn inner_miter_limit(&self) -> f64 {
66        self.base.generator().inner_miter_limit()
67    }
68
69    pub fn set_approximation_scale(&mut self, s: f64) {
70        self.base.generator_mut().set_approximation_scale(s);
71    }
72    pub fn approximation_scale(&self) -> f64 {
73        self.base.generator().approximation_scale()
74    }
75
76    pub fn set_auto_detect_orientation(&mut self, v: bool) {
77        self.base.generator_mut().set_auto_detect_orientation(v);
78    }
79    pub fn auto_detect_orientation(&self) -> bool {
80        self.base.generator().auto_detect_orientation()
81    }
82
83    pub fn source(&self) -> &VS {
84        self.base.source()
85    }
86
87    pub fn source_mut(&mut self) -> &mut VS {
88        self.base.source_mut()
89    }
90}
91
92impl<VS: VertexSource> VertexSource for ConvContour<VS> {
93    fn rewind(&mut self, path_id: u32) {
94        self.base.rewind(path_id);
95    }
96
97    fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
98        self.base.vertex(x, y)
99    }
100}
101
102// ============================================================================
103// Tests
104// ============================================================================
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use crate::basics::{is_stop, is_vertex, PATH_CMD_MOVE_TO};
110    use crate::path_storage::PathStorage;
111
112    fn collect_vertices<VS: VertexSource>(vs: &mut VS) -> Vec<(f64, f64, u32)> {
113        let mut result = Vec::new();
114        vs.rewind(0);
115        loop {
116            let (mut x, mut y) = (0.0, 0.0);
117            let cmd = vs.vertex(&mut x, &mut y);
118            if is_stop(cmd) {
119                break;
120            }
121            result.push((x, y, cmd));
122        }
123        result
124    }
125
126    #[test]
127    fn test_contour_empty_path() {
128        let path = PathStorage::new();
129        let mut contour = ConvContour::new(path);
130        let verts = collect_vertices(&mut contour);
131        assert!(verts.is_empty());
132    }
133
134    #[test]
135    fn test_contour_width() {
136        let path = PathStorage::new();
137        let mut contour = ConvContour::new(path);
138        contour.set_width(5.0);
139        assert!((contour.width() - 5.0).abs() < 1e-10);
140    }
141
142    #[test]
143    fn test_contour_closed_triangle() {
144        let mut path = PathStorage::new();
145        path.move_to(50.0, 10.0);
146        path.line_to(90.0, 90.0);
147        path.line_to(10.0, 90.0);
148        path.close_polygon(0);
149
150        let mut contour = ConvContour::new(path);
151        contour.set_width(5.0);
152        contour.set_auto_detect_orientation(true);
153        let verts = collect_vertices(&mut contour);
154
155        assert!(
156            verts.len() >= 3,
157            "Expected contour vertices, got {}",
158            verts.len()
159        );
160        assert_eq!(verts[0].2, PATH_CMD_MOVE_TO);
161    }
162
163    #[test]
164    fn test_contour_expands_polygon() {
165        let mut path = PathStorage::new();
166        path.move_to(20.0, 20.0);
167        path.line_to(80.0, 20.0);
168        path.line_to(80.0, 80.0);
169        path.line_to(20.0, 80.0);
170        path.close_polygon(0);
171
172        let mut contour = ConvContour::new(path);
173        contour.set_width(10.0);
174        contour.set_auto_detect_orientation(true);
175        let verts = collect_vertices(&mut contour);
176
177        let max_x = verts
178            .iter()
179            .filter(|v| is_vertex(v.2))
180            .map(|v| v.0)
181            .fold(f64::MIN, f64::max);
182        let min_x = verts
183            .iter()
184            .filter(|v| is_vertex(v.2))
185            .map(|v| v.0)
186            .fold(f64::MAX, f64::min);
187
188        assert!(max_x > 80.0, "Max x={} should exceed original 80", max_x);
189        assert!(
190            min_x < 20.0,
191            "Min x={} should be less than original 20",
192            min_x
193        );
194    }
195
196    #[test]
197    fn test_contour_auto_detect() {
198        let path = PathStorage::new();
199        let mut contour = ConvContour::new(path);
200        contour.set_auto_detect_orientation(true);
201        assert!(contour.auto_detect_orientation());
202    }
203}