Skip to main content

agg_rust/
arrowhead.rs

1//! Arrowhead / arrowtail vertex generator.
2//!
3//! Port of `agg_arrowhead.h` / `agg_arrowhead.cpp` — generates arrow marker
4//! geometry as a VertexSource. Used in conjunction with `conv_marker` to
5//! place arrowheads at line endpoints.
6
7use crate::basics::{
8    VertexSource, PATH_CMD_END_POLY, PATH_CMD_LINE_TO, PATH_CMD_MOVE_TO, PATH_CMD_STOP,
9    PATH_FLAGS_CCW, PATH_FLAGS_CLOSE,
10};
11
12/// Arrowhead / arrowtail vertex generator.
13///
14/// Generates arrow marker polygons. Path ID 0 = tail, path ID 1 = head.
15/// The arrow shape is defined by four parameters (d1-d4) for each end.
16///
17/// Port of C++ `agg::arrowhead`.
18pub struct Arrowhead {
19    head_d1: f64,
20    head_d2: f64,
21    head_d3: f64,
22    head_d4: f64,
23    tail_d1: f64,
24    tail_d2: f64,
25    tail_d3: f64,
26    tail_d4: f64,
27    head_flag: bool,
28    tail_flag: bool,
29    coord: [f64; 16],
30    cmd: [u32; 8],
31    curr_id: u32,
32    curr_coord: u32,
33}
34
35impl Arrowhead {
36    /// Create a new arrowhead with default dimensions.
37    pub fn new() -> Self {
38        Self {
39            head_d1: 1.0,
40            head_d2: 1.0,
41            head_d3: 1.0,
42            head_d4: 0.0,
43            tail_d1: 1.0,
44            tail_d2: 1.0,
45            tail_d3: 1.0,
46            tail_d4: 0.0,
47            head_flag: false,
48            tail_flag: false,
49            coord: [0.0; 16],
50            cmd: [0; 8],
51            curr_id: 0,
52            curr_coord: 0,
53        }
54    }
55
56    /// Set head arrow dimensions and enable head.
57    pub fn head(&mut self, d1: f64, d2: f64, d3: f64, d4: f64) {
58        self.head_d1 = d1;
59        self.head_d2 = d2;
60        self.head_d3 = d3;
61        self.head_d4 = d4;
62        self.head_flag = true;
63    }
64
65    /// Enable head arrow (uses current dimensions).
66    pub fn enable_head(&mut self) {
67        self.head_flag = true;
68    }
69
70    /// Disable head arrow.
71    pub fn no_head(&mut self) {
72        self.head_flag = false;
73    }
74
75    /// Set tail arrow dimensions and enable tail.
76    pub fn tail(&mut self, d1: f64, d2: f64, d3: f64, d4: f64) {
77        self.tail_d1 = d1;
78        self.tail_d2 = d2;
79        self.tail_d3 = d3;
80        self.tail_d4 = d4;
81        self.tail_flag = true;
82    }
83
84    /// Enable tail arrow (uses current dimensions).
85    pub fn enable_tail(&mut self) {
86        self.tail_flag = true;
87    }
88
89    /// Disable tail arrow.
90    pub fn no_tail(&mut self) {
91        self.tail_flag = false;
92    }
93}
94
95impl Default for Arrowhead {
96    fn default() -> Self {
97        Self::new()
98    }
99}
100
101impl VertexSource for Arrowhead {
102    fn rewind(&mut self, path_id: u32) {
103        self.curr_id = path_id;
104        self.curr_coord = 0;
105
106        if path_id == 0 {
107            if !self.tail_flag {
108                self.cmd[0] = PATH_CMD_STOP;
109                return;
110            }
111            self.coord[0] = self.tail_d1;
112            self.coord[1] = 0.0;
113            self.coord[2] = self.tail_d1 - self.tail_d4;
114            self.coord[3] = self.tail_d3;
115            self.coord[4] = -self.tail_d2 - self.tail_d4;
116            self.coord[5] = self.tail_d3;
117            self.coord[6] = -self.tail_d2;
118            self.coord[7] = 0.0;
119            self.coord[8] = -self.tail_d2 - self.tail_d4;
120            self.coord[9] = -self.tail_d3;
121            self.coord[10] = self.tail_d1 - self.tail_d4;
122            self.coord[11] = -self.tail_d3;
123
124            self.cmd[0] = PATH_CMD_MOVE_TO;
125            self.cmd[1] = PATH_CMD_LINE_TO;
126            self.cmd[2] = PATH_CMD_LINE_TO;
127            self.cmd[3] = PATH_CMD_LINE_TO;
128            self.cmd[4] = PATH_CMD_LINE_TO;
129            self.cmd[5] = PATH_CMD_LINE_TO;
130            self.cmd[7] = PATH_CMD_END_POLY | PATH_FLAGS_CLOSE | PATH_FLAGS_CCW;
131            self.cmd[6] = PATH_CMD_STOP;
132        } else if path_id == 1 {
133            if !self.head_flag {
134                self.cmd[0] = PATH_CMD_STOP;
135                return;
136            }
137            self.coord[0] = -self.head_d1;
138            self.coord[1] = 0.0;
139            self.coord[2] = self.head_d2 + self.head_d4;
140            self.coord[3] = -self.head_d3;
141            self.coord[4] = self.head_d2;
142            self.coord[5] = 0.0;
143            self.coord[6] = self.head_d2 + self.head_d4;
144            self.coord[7] = self.head_d3;
145
146            self.cmd[0] = PATH_CMD_MOVE_TO;
147            self.cmd[1] = PATH_CMD_LINE_TO;
148            self.cmd[2] = PATH_CMD_LINE_TO;
149            self.cmd[3] = PATH_CMD_LINE_TO;
150            self.cmd[4] = PATH_CMD_END_POLY | PATH_FLAGS_CLOSE | PATH_FLAGS_CCW;
151            self.cmd[5] = PATH_CMD_STOP;
152        }
153    }
154
155    fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
156        if self.curr_id < 2 {
157            let curr_idx = self.curr_coord as usize * 2;
158            *x = self.coord[curr_idx];
159            *y = self.coord[curr_idx + 1];
160            let cmd = self.cmd[self.curr_coord as usize];
161            self.curr_coord += 1;
162            return cmd;
163        }
164        PATH_CMD_STOP
165    }
166}
167
168// ============================================================================
169// Tests
170// ============================================================================
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175    use crate::basics::{is_end_poly, is_stop};
176
177    #[test]
178    fn test_arrowhead_default() {
179        let ah = Arrowhead::new();
180        assert!(!ah.head_flag);
181        assert!(!ah.tail_flag);
182    }
183
184    #[test]
185    fn test_arrowhead_tail_disabled() {
186        let mut ah = Arrowhead::new();
187        ah.rewind(0);
188        let mut x = 0.0;
189        let mut y = 0.0;
190        let cmd = ah.vertex(&mut x, &mut y);
191        assert!(is_stop(cmd));
192    }
193
194    #[test]
195    fn test_arrowhead_head_disabled() {
196        let mut ah = Arrowhead::new();
197        ah.rewind(1);
198        let mut x = 0.0;
199        let mut y = 0.0;
200        let cmd = ah.vertex(&mut x, &mut y);
201        assert!(is_stop(cmd));
202    }
203
204    #[test]
205    fn test_arrowhead_tail_polygon() {
206        let mut ah = Arrowhead::new();
207        ah.tail(5.0, 5.0, 3.0, 1.0);
208        ah.rewind(0);
209        let mut x = 0.0;
210        let mut y = 0.0;
211
212        // 6 vertices: move_to + 5 line_to
213        let cmd = ah.vertex(&mut x, &mut y);
214        assert_eq!(cmd, PATH_CMD_MOVE_TO);
215        assert!((x - 5.0).abs() < 1e-10); // tail_d1
216        assert!(y.abs() < 1e-10);
217
218        for _ in 0..5 {
219            let cmd = ah.vertex(&mut x, &mut y);
220            assert_eq!(cmd, PATH_CMD_LINE_TO);
221        }
222
223        // Then stop (cmd[6])
224        let cmd = ah.vertex(&mut x, &mut y);
225        assert!(is_stop(cmd));
226    }
227
228    #[test]
229    fn test_arrowhead_head_polygon() {
230        let mut ah = Arrowhead::new();
231        ah.head(5.0, 5.0, 3.0, 1.0);
232        ah.rewind(1);
233        let mut x = 0.0;
234        let mut y = 0.0;
235
236        // 4 vertices: move_to + 3 line_to
237        let cmd = ah.vertex(&mut x, &mut y);
238        assert_eq!(cmd, PATH_CMD_MOVE_TO);
239        assert!((x + 5.0).abs() < 1e-10); // -head_d1
240
241        for _ in 0..3 {
242            let cmd = ah.vertex(&mut x, &mut y);
243            assert_eq!(cmd, PATH_CMD_LINE_TO);
244        }
245
246        // Close polygon
247        let cmd = ah.vertex(&mut x, &mut y);
248        assert!(is_end_poly(cmd));
249
250        // Stop
251        let cmd = ah.vertex(&mut x, &mut y);
252        assert!(is_stop(cmd));
253    }
254
255    #[test]
256    fn test_arrowhead_invalid_path_id() {
257        let mut ah = Arrowhead::new();
258        ah.rewind(5); // Invalid path ID
259        let mut x = 0.0;
260        let mut y = 0.0;
261        let cmd = ah.vertex(&mut x, &mut y);
262        assert!(is_stop(cmd));
263    }
264
265    #[test]
266    fn test_arrowhead_enable_disable() {
267        let mut ah = Arrowhead::new();
268        ah.head(5.0, 5.0, 3.0, 1.0);
269        assert!(ah.head_flag);
270
271        ah.no_head();
272        assert!(!ah.head_flag);
273
274        ah.enable_head();
275        assert!(ah.head_flag);
276
277        ah.tail(5.0, 5.0, 3.0, 1.0);
278        assert!(ah.tail_flag);
279
280        ah.no_tail();
281        assert!(!ah.tail_flag);
282
283        ah.enable_tail();
284        assert!(ah.tail_flag);
285    }
286
287    #[test]
288    fn test_arrowhead_tail_vertex_coords() {
289        let mut ah = Arrowhead::new();
290        ah.tail(5.0, 5.0, 3.0, 1.0);
291        ah.rewind(0);
292        let mut x = 0.0;
293        let mut y = 0.0;
294
295        // Vertex 0: (d1, 0) = (5, 0)
296        ah.vertex(&mut x, &mut y);
297        assert!((x - 5.0).abs() < 1e-10);
298        assert!(y.abs() < 1e-10);
299
300        // Vertex 1: (d1 - d4, d3) = (4, 3)
301        ah.vertex(&mut x, &mut y);
302        assert!((x - 4.0).abs() < 1e-10);
303        assert!((y - 3.0).abs() < 1e-10);
304
305        // Vertex 2: (-d2 - d4, d3) = (-6, 3)
306        ah.vertex(&mut x, &mut y);
307        assert!((x + 6.0).abs() < 1e-10);
308        assert!((y - 3.0).abs() < 1e-10);
309
310        // Vertex 3: (-d2, 0) = (-5, 0)
311        ah.vertex(&mut x, &mut y);
312        assert!((x + 5.0).abs() < 1e-10);
313        assert!(y.abs() < 1e-10);
314
315        // Vertex 4: (-d2 - d4, -d3) = (-6, -3)
316        ah.vertex(&mut x, &mut y);
317        assert!((x + 6.0).abs() < 1e-10);
318        assert!((y + 3.0).abs() < 1e-10);
319
320        // Vertex 5: (d1 - d4, -d3) = (4, -3)
321        ah.vertex(&mut x, &mut y);
322        assert!((x - 4.0).abs() < 1e-10);
323        assert!((y + 3.0).abs() < 1e-10);
324    }
325}