Skip to main content

agg_rust/
conv_transform.rs

1//! Transform converter for vertex sources.
2//!
3//! Port of `agg_conv_transform.h` — wraps a `VertexSource` and applies
4//! any `Transformer` (affine, polar, single-path, etc.) to each vertex coordinate.
5//!
6//! Copyright (c) 2025. BSD-3-Clause License.
7
8use crate::basics::{is_vertex, VertexSource};
9use crate::span_interpolator_linear::Transformer;
10use crate::trans_affine::TransAffine;
11
12// ============================================================================
13// ConvTransform
14// ============================================================================
15
16/// Applies a coordinate transform to each vertex from a source.
17///
18/// Port of C++ `conv_transform<VertexSource, Transformer>`.
19/// Generic over any `Transformer` implementation (defaults to `TransAffine`).
20/// Owns the source; use `ConvTransform<&mut PathStorage>` to borrow.
21pub struct ConvTransform<VS: VertexSource, T: Transformer = TransAffine> {
22    source: VS,
23    trans: T,
24}
25
26impl<VS: VertexSource, T: Transformer> ConvTransform<VS, T> {
27    pub fn new(source: VS, trans: T) -> Self {
28        Self { source, trans }
29    }
30
31    pub fn set_transform(&mut self, trans: T) {
32        self.trans = trans;
33    }
34
35    pub fn transform(&self) -> &T {
36        &self.trans
37    }
38
39    pub fn source(&self) -> &VS {
40        &self.source
41    }
42
43    pub fn source_mut(&mut self) -> &mut VS {
44        &mut self.source
45    }
46}
47
48impl<VS: VertexSource, T: Transformer> VertexSource for ConvTransform<VS, T> {
49    fn rewind(&mut self, path_id: u32) {
50        self.source.rewind(path_id);
51    }
52
53    fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
54        let cmd = self.source.vertex(x, y);
55        if is_vertex(cmd) {
56            self.trans.transform(x, y);
57        }
58        cmd
59    }
60}
61
62// ============================================================================
63// Tests
64// ============================================================================
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69    use crate::basics::{PATH_CMD_LINE_TO, PATH_CMD_MOVE_TO, PATH_CMD_STOP};
70    use crate::path_storage::PathStorage;
71
72    #[test]
73    fn test_identity_transform() {
74        let mut path = PathStorage::new();
75        path.move_to(10.0, 20.0);
76        path.line_to(30.0, 40.0);
77
78        let mut ct = ConvTransform::new(path, TransAffine::default());
79        ct.rewind(0);
80
81        let (mut x, mut y) = (0.0, 0.0);
82        let cmd = ct.vertex(&mut x, &mut y);
83        assert_eq!(cmd, PATH_CMD_MOVE_TO);
84        assert!((x - 10.0).abs() < 1e-10);
85        assert!((y - 20.0).abs() < 1e-10);
86
87        let cmd = ct.vertex(&mut x, &mut y);
88        assert_eq!(cmd, PATH_CMD_LINE_TO);
89        assert!((x - 30.0).abs() < 1e-10);
90        assert!((y - 40.0).abs() < 1e-10);
91    }
92
93    #[test]
94    fn test_translation() {
95        let mut path = PathStorage::new();
96        path.move_to(10.0, 20.0);
97
98        let trans = TransAffine::new_translation(100.0, 200.0);
99        let mut ct = ConvTransform::new(path, trans);
100        ct.rewind(0);
101
102        let (mut x, mut y) = (0.0, 0.0);
103        ct.vertex(&mut x, &mut y);
104        assert!((x - 110.0).abs() < 1e-10);
105        assert!((y - 220.0).abs() < 1e-10);
106    }
107
108    #[test]
109    fn test_scaling() {
110        let mut path = PathStorage::new();
111        path.move_to(10.0, 20.0);
112        path.line_to(30.0, 40.0);
113
114        let trans = TransAffine::new_scaling(2.0, 3.0);
115        let mut ct = ConvTransform::new(path, trans);
116        ct.rewind(0);
117
118        let (mut x, mut y) = (0.0, 0.0);
119        ct.vertex(&mut x, &mut y);
120        assert!((x - 20.0).abs() < 1e-10);
121        assert!((y - 60.0).abs() < 1e-10);
122
123        ct.vertex(&mut x, &mut y);
124        assert!((x - 60.0).abs() < 1e-10);
125        assert!((y - 120.0).abs() < 1e-10);
126    }
127
128    #[test]
129    fn test_stop_not_transformed() {
130        let path = PathStorage::new();
131        // Empty path — first vertex is stop
132        let mut ct = ConvTransform::new(path, TransAffine::new_scaling(2.0, 2.0));
133        ct.rewind(0);
134
135        let (mut x, mut y) = (0.0, 0.0);
136        let cmd = ct.vertex(&mut x, &mut y);
137        assert_eq!(cmd, PATH_CMD_STOP);
138    }
139
140    #[test]
141    fn test_set_transform() {
142        let mut path = PathStorage::new();
143        path.move_to(10.0, 10.0);
144
145        let mut ct = ConvTransform::new(path, TransAffine::default());
146        ct.set_transform(TransAffine::new_translation(5.0, 5.0));
147        ct.rewind(0);
148
149        let (mut x, mut y) = (0.0, 0.0);
150        ct.vertex(&mut x, &mut y);
151        assert!((x - 15.0).abs() < 1e-10);
152        assert!((y - 15.0).abs() < 1e-10);
153    }
154
155    #[test]
156    fn test_generic_transformer_via_reference() {
157        // Test that ConvTransform works with &T where T: Transformer
158        use crate::trans_polar::TransPolar;
159
160        let mut path = PathStorage::new();
161        path.move_to(0.0, 100.0);
162
163        let polar = TransPolar::new();
164        // Use a reference to the transformer
165        let mut ct = ConvTransform::new(path, &polar);
166        ct.rewind(0);
167
168        let (mut x, mut y) = (0.0, 0.0);
169        let cmd = ct.vertex(&mut x, &mut y);
170        assert_eq!(cmd, PATH_CMD_MOVE_TO);
171        // TransPolar with defaults: x1 = (0+0)*1 = 0, y1 = (100+0)*1 + 0 = 100
172        // x = cos(0)*100 + 0 = 100, y = sin(0)*100 + 0 = 0
173        assert!((x - 100.0).abs() < 1e-10);
174        assert!((y - 0.0).abs() < 1e-10);
175    }
176}