Skip to main content

agg_rust_azul/
path_storage.rs

1//! Path storage — the primary vertex container for AGG.
2//!
3//! Port of `agg_path_storage.h` — stores vertices with path commands.
4//! Uses `Vec<VertexD>` instead of C++'s block-based `vertex_block_storage`
5//! since Rust's `Vec` already provides amortized O(1) push.
6
7use crate::basics::{
8    is_curve, is_drawing, is_end_poly, is_equal_eps, is_move_to, is_next_poly, is_stop, is_vertex,
9    set_orientation, VertexD, VertexSource, PATH_CMD_CURVE3, PATH_CMD_CURVE4, PATH_CMD_END_POLY,
10    PATH_CMD_LINE_TO, PATH_CMD_MOVE_TO, PATH_CMD_STOP, PATH_FLAGS_CCW, PATH_FLAGS_CLOSE,
11    PATH_FLAGS_CW, PATH_FLAGS_NONE,
12};
13use crate::bezier_arc::BezierArcSvg;
14use crate::math::{calc_distance, VERTEX_DIST_EPSILON};
15
16/// Path storage — the main vertex container.
17///
18/// Stores an ordered sequence of vertices, each with an (x, y) coordinate and
19/// a path command. Supports multiple sub-paths separated by `move_to` or `stop`
20/// commands. Implements `VertexSource` for use in the rendering pipeline.
21///
22/// Port of C++ `agg::path_storage` (typedef for `path_base<vertex_block_storage<double>>`).
23#[derive(Clone)]
24pub struct PathStorage {
25    vertices: Vec<VertexD>,
26    iterator: usize,
27}
28
29impl PathStorage {
30    /// Create an empty path storage.
31    pub fn new() -> Self {
32        Self {
33            vertices: Vec::new(),
34            iterator: 0,
35        }
36    }
37
38    /// Remove all vertices (keeps allocated memory).
39    pub fn remove_all(&mut self) {
40        self.vertices.clear();
41        self.iterator = 0;
42    }
43
44    /// Remove all vertices and free memory.
45    pub fn free_all(&mut self) {
46        self.vertices = Vec::new();
47        self.iterator = 0;
48    }
49
50    // ---------------------------------------------------------------
51    // Path construction
52    // ---------------------------------------------------------------
53
54    /// Begin a new sub-path. If the last command is not `stop`,
55    /// inserts a stop command first. Returns the index where the
56    /// new path will start.
57    pub fn start_new_path(&mut self) -> usize {
58        if !is_stop(self.last_command()) {
59            self.vertices.push(VertexD::new(0.0, 0.0, PATH_CMD_STOP));
60        }
61        self.vertices.len()
62    }
63
64    /// Add a vertex with an explicit command.
65    ///
66    /// Port of C++ `path_storage::add_vertex(x, y, cmd)`.
67    pub fn add_vertex(&mut self, x: f64, y: f64, cmd: u32) {
68        self.vertices.push(VertexD::new(x, y, cmd));
69    }
70
71    /// Add a move_to command.
72    pub fn move_to(&mut self, x: f64, y: f64) {
73        self.vertices.push(VertexD::new(x, y, PATH_CMD_MOVE_TO));
74    }
75
76    /// Add a relative move_to command.
77    pub fn move_rel(&mut self, dx: f64, dy: f64) {
78        let (mut x, mut y) = (dx, dy);
79        self.rel_to_abs(&mut x, &mut y);
80        self.vertices.push(VertexD::new(x, y, PATH_CMD_MOVE_TO));
81    }
82
83    /// Add a line_to command.
84    pub fn line_to(&mut self, x: f64, y: f64) {
85        self.vertices.push(VertexD::new(x, y, PATH_CMD_LINE_TO));
86    }
87
88    /// Add a relative line_to command.
89    pub fn line_rel(&mut self, dx: f64, dy: f64) {
90        let (mut x, mut y) = (dx, dy);
91        self.rel_to_abs(&mut x, &mut y);
92        self.vertices.push(VertexD::new(x, y, PATH_CMD_LINE_TO));
93    }
94
95    /// Add a horizontal line_to command.
96    pub fn hline_to(&mut self, x: f64) {
97        self.vertices
98            .push(VertexD::new(x, self.last_y(), PATH_CMD_LINE_TO));
99    }
100
101    /// Add a relative horizontal line_to command.
102    pub fn hline_rel(&mut self, dx: f64) {
103        let (mut x, mut y) = (dx, 0.0);
104        self.rel_to_abs(&mut x, &mut y);
105        self.vertices.push(VertexD::new(x, y, PATH_CMD_LINE_TO));
106    }
107
108    /// Add a vertical line_to command.
109    pub fn vline_to(&mut self, y: f64) {
110        self.vertices
111            .push(VertexD::new(self.last_x(), y, PATH_CMD_LINE_TO));
112    }
113
114    /// Add a relative vertical line_to command.
115    pub fn vline_rel(&mut self, dy: f64) {
116        let (mut x, mut y) = (0.0, dy);
117        self.rel_to_abs(&mut x, &mut y);
118        self.vertices.push(VertexD::new(x, y, PATH_CMD_LINE_TO));
119    }
120
121    /// Add an SVG-style arc_to command.
122    #[allow(clippy::too_many_arguments)]
123    pub fn arc_to(
124        &mut self,
125        rx: f64,
126        ry: f64,
127        angle: f64,
128        large_arc_flag: bool,
129        sweep_flag: bool,
130        x: f64,
131        y: f64,
132    ) {
133        if self.total_vertices() > 0 && is_vertex(self.last_command()) {
134            let epsilon = 1e-30;
135            let mut x0 = 0.0;
136            let mut y0 = 0.0;
137            self.last_vertex_xy(&mut x0, &mut y0);
138
139            let rx = rx.abs();
140            let ry = ry.abs();
141
142            if rx < epsilon || ry < epsilon {
143                self.line_to(x, y);
144                return;
145            }
146
147            if calc_distance(x0, y0, x, y) < epsilon {
148                return;
149            }
150
151            let mut a = BezierArcSvg::new_with_params(
152                x0,
153                y0,
154                rx,
155                ry,
156                angle,
157                large_arc_flag,
158                sweep_flag,
159                x,
160                y,
161            );
162            if a.radii_ok() {
163                self.join_path(&mut a, 0);
164            } else {
165                self.line_to(x, y);
166            }
167        } else {
168            self.move_to(x, y);
169        }
170    }
171
172    /// Add a relative SVG-style arc_to command.
173    #[allow(clippy::too_many_arguments)]
174    pub fn arc_rel(
175        &mut self,
176        rx: f64,
177        ry: f64,
178        angle: f64,
179        large_arc_flag: bool,
180        sweep_flag: bool,
181        dx: f64,
182        dy: f64,
183    ) {
184        let (mut x, mut y) = (dx, dy);
185        self.rel_to_abs(&mut x, &mut y);
186        self.arc_to(rx, ry, angle, large_arc_flag, sweep_flag, x, y);
187    }
188
189    /// Add a quadratic Bezier curve (curve3) with explicit control point.
190    pub fn curve3(&mut self, x_ctrl: f64, y_ctrl: f64, x_to: f64, y_to: f64) {
191        self.vertices
192            .push(VertexD::new(x_ctrl, y_ctrl, PATH_CMD_CURVE3));
193        self.vertices
194            .push(VertexD::new(x_to, y_to, PATH_CMD_CURVE3));
195    }
196
197    /// Add a relative quadratic Bezier curve with explicit control point.
198    pub fn curve3_rel(&mut self, dx_ctrl: f64, dy_ctrl: f64, dx_to: f64, dy_to: f64) {
199        let (mut x_ctrl, mut y_ctrl) = (dx_ctrl, dy_ctrl);
200        self.rel_to_abs(&mut x_ctrl, &mut y_ctrl);
201        let (mut x_to, mut y_to) = (dx_to, dy_to);
202        self.rel_to_abs(&mut x_to, &mut y_to);
203        self.vertices
204            .push(VertexD::new(x_ctrl, y_ctrl, PATH_CMD_CURVE3));
205        self.vertices
206            .push(VertexD::new(x_to, y_to, PATH_CMD_CURVE3));
207    }
208
209    /// Add a smooth quadratic Bezier curve (reflected control point).
210    pub fn curve3_smooth(&mut self, x_to: f64, y_to: f64) {
211        let mut x0 = 0.0;
212        let mut y0 = 0.0;
213        if is_vertex(self.last_vertex_xy(&mut x0, &mut y0)) {
214            let mut x_ctrl = 0.0;
215            let mut y_ctrl = 0.0;
216            let cmd = self.prev_vertex_xy(&mut x_ctrl, &mut y_ctrl);
217            if is_curve(cmd) {
218                x_ctrl = x0 + x0 - x_ctrl;
219                y_ctrl = y0 + y0 - y_ctrl;
220            } else {
221                x_ctrl = x0;
222                y_ctrl = y0;
223            }
224            self.curve3(x_ctrl, y_ctrl, x_to, y_to);
225        }
226    }
227
228    /// Add a relative smooth quadratic Bezier curve.
229    pub fn curve3_smooth_rel(&mut self, dx_to: f64, dy_to: f64) {
230        let (mut x_to, mut y_to) = (dx_to, dy_to);
231        self.rel_to_abs(&mut x_to, &mut y_to);
232        self.curve3_smooth(x_to, y_to);
233    }
234
235    /// Add a cubic Bezier curve (curve4) with two explicit control points.
236    #[allow(clippy::too_many_arguments)]
237    pub fn curve4(
238        &mut self,
239        x_ctrl1: f64,
240        y_ctrl1: f64,
241        x_ctrl2: f64,
242        y_ctrl2: f64,
243        x_to: f64,
244        y_to: f64,
245    ) {
246        self.vertices
247            .push(VertexD::new(x_ctrl1, y_ctrl1, PATH_CMD_CURVE4));
248        self.vertices
249            .push(VertexD::new(x_ctrl2, y_ctrl2, PATH_CMD_CURVE4));
250        self.vertices
251            .push(VertexD::new(x_to, y_to, PATH_CMD_CURVE4));
252    }
253
254    /// Add a relative cubic Bezier curve with two explicit control points.
255    #[allow(clippy::too_many_arguments)]
256    pub fn curve4_rel(
257        &mut self,
258        dx_ctrl1: f64,
259        dy_ctrl1: f64,
260        dx_ctrl2: f64,
261        dy_ctrl2: f64,
262        dx_to: f64,
263        dy_to: f64,
264    ) {
265        let (mut x_ctrl1, mut y_ctrl1) = (dx_ctrl1, dy_ctrl1);
266        self.rel_to_abs(&mut x_ctrl1, &mut y_ctrl1);
267        let (mut x_ctrl2, mut y_ctrl2) = (dx_ctrl2, dy_ctrl2);
268        self.rel_to_abs(&mut x_ctrl2, &mut y_ctrl2);
269        let (mut x_to, mut y_to) = (dx_to, dy_to);
270        self.rel_to_abs(&mut x_to, &mut y_to);
271        self.vertices
272            .push(VertexD::new(x_ctrl1, y_ctrl1, PATH_CMD_CURVE4));
273        self.vertices
274            .push(VertexD::new(x_ctrl2, y_ctrl2, PATH_CMD_CURVE4));
275        self.vertices
276            .push(VertexD::new(x_to, y_to, PATH_CMD_CURVE4));
277    }
278
279    /// Add a smooth cubic Bezier curve (reflected first control point).
280    pub fn curve4_smooth(&mut self, x_ctrl2: f64, y_ctrl2: f64, x_to: f64, y_to: f64) {
281        let mut x0 = 0.0;
282        let mut y0 = 0.0;
283        if is_vertex(self.last_vertex_xy(&mut x0, &mut y0)) {
284            let mut x_ctrl1 = 0.0;
285            let mut y_ctrl1 = 0.0;
286            let cmd = self.prev_vertex_xy(&mut x_ctrl1, &mut y_ctrl1);
287            if is_curve(cmd) {
288                x_ctrl1 = x0 + x0 - x_ctrl1;
289                y_ctrl1 = y0 + y0 - y_ctrl1;
290            } else {
291                x_ctrl1 = x0;
292                y_ctrl1 = y0;
293            }
294            self.curve4(x_ctrl1, y_ctrl1, x_ctrl2, y_ctrl2, x_to, y_to);
295        }
296    }
297
298    /// Add a relative smooth cubic Bezier curve.
299    pub fn curve4_smooth_rel(&mut self, dx_ctrl2: f64, dy_ctrl2: f64, dx_to: f64, dy_to: f64) {
300        let (mut x_ctrl2, mut y_ctrl2) = (dx_ctrl2, dy_ctrl2);
301        self.rel_to_abs(&mut x_ctrl2, &mut y_ctrl2);
302        let (mut x_to, mut y_to) = (dx_to, dy_to);
303        self.rel_to_abs(&mut x_to, &mut y_to);
304        self.curve4_smooth(x_ctrl2, y_ctrl2, x_to, y_to);
305    }
306
307    /// Add an end_poly command with optional flags.
308    pub fn end_poly(&mut self, flags: u32) {
309        if is_vertex(self.last_command()) {
310            self.vertices
311                .push(VertexD::new(0.0, 0.0, PATH_CMD_END_POLY | flags));
312        }
313    }
314
315    /// Close the current polygon.
316    pub fn close_polygon(&mut self, flags: u32) {
317        self.end_poly(PATH_FLAGS_CLOSE | flags);
318    }
319
320    // ---------------------------------------------------------------
321    // Accessors
322    // ---------------------------------------------------------------
323
324    /// Total number of vertices stored.
325    pub fn total_vertices(&self) -> usize {
326        self.vertices.len()
327    }
328
329    /// Immutable access to the raw vertex slice.
330    /// Use with `RasterizerScanlineAa::add_path_vertices` to avoid cloning.
331    pub fn vertices(&self) -> &[VertexD] {
332        &self.vertices
333    }
334
335    /// Convert relative coordinates to absolute by adding last vertex position.
336    pub fn rel_to_abs(&self, x: &mut f64, y: &mut f64) {
337        if !self.vertices.is_empty() {
338            let last = &self.vertices[self.vertices.len() - 1];
339            if is_vertex(last.cmd) {
340                *x += last.x;
341                *y += last.y;
342            }
343        }
344    }
345
346    /// Get the last vertex's (x, y) and command. Returns `PATH_CMD_STOP` if empty.
347    pub fn last_vertex_xy(&self, x: &mut f64, y: &mut f64) -> u32 {
348        if self.vertices.is_empty() {
349            *x = 0.0;
350            *y = 0.0;
351            return PATH_CMD_STOP;
352        }
353        let v = &self.vertices[self.vertices.len() - 1];
354        *x = v.x;
355        *y = v.y;
356        v.cmd
357    }
358
359    /// Get the second-to-last vertex's (x, y) and command.
360    pub fn prev_vertex_xy(&self, x: &mut f64, y: &mut f64) -> u32 {
361        if self.vertices.len() < 2 {
362            *x = 0.0;
363            *y = 0.0;
364            return PATH_CMD_STOP;
365        }
366        let v = &self.vertices[self.vertices.len() - 2];
367        *x = v.x;
368        *y = v.y;
369        v.cmd
370    }
371
372    /// Get the last command (or `PATH_CMD_STOP` if empty).
373    pub fn last_command(&self) -> u32 {
374        if self.vertices.is_empty() {
375            PATH_CMD_STOP
376        } else {
377            self.vertices[self.vertices.len() - 1].cmd
378        }
379    }
380
381    /// Get the X coordinate of the last vertex (or 0.0 if empty).
382    pub fn last_x(&self) -> f64 {
383        if self.vertices.is_empty() {
384            0.0
385        } else {
386            self.vertices[self.vertices.len() - 1].x
387        }
388    }
389
390    /// Get the Y coordinate of the last vertex (or 0.0 if empty).
391    pub fn last_y(&self) -> f64 {
392        if self.vertices.is_empty() {
393            0.0
394        } else {
395            self.vertices[self.vertices.len() - 1].y
396        }
397    }
398
399    /// Get a vertex by index. Returns the command.
400    pub fn vertex_idx(&self, idx: usize, x: &mut f64, y: &mut f64) -> u32 {
401        let v = &self.vertices[idx];
402        *x = v.x;
403        *y = v.y;
404        v.cmd
405    }
406
407    /// Get a command by index.
408    pub fn command(&self, idx: usize) -> u32 {
409        self.vertices[idx].cmd
410    }
411
412    /// Modify a vertex's coordinates.
413    pub fn modify_vertex(&mut self, idx: usize, x: f64, y: f64) {
414        self.vertices[idx].x = x;
415        self.vertices[idx].y = y;
416    }
417
418    /// Modify a vertex's coordinates and command.
419    pub fn modify_vertex_cmd(&mut self, idx: usize, x: f64, y: f64, cmd: u32) {
420        self.vertices[idx].x = x;
421        self.vertices[idx].y = y;
422        self.vertices[idx].cmd = cmd;
423    }
424
425    /// Modify only a vertex's command.
426    pub fn modify_command(&mut self, idx: usize, cmd: u32) {
427        self.vertices[idx].cmd = cmd;
428    }
429
430    /// Swap two vertices (coordinates and commands).
431    pub fn swap_vertices(&mut self, v1: usize, v2: usize) {
432        self.vertices.swap(v1, v2);
433    }
434
435    // ---------------------------------------------------------------
436    // Concatenation and joining
437    // ---------------------------------------------------------------
438
439    /// Concatenate all vertices from a vertex source as-is.
440    pub fn concat_path(&mut self, vs: &mut dyn VertexSource, path_id: u32) {
441        let mut x = 0.0;
442        let mut y = 0.0;
443        vs.rewind(path_id);
444        loop {
445            let cmd = vs.vertex(&mut x, &mut y);
446            if is_stop(cmd) {
447                break;
448            }
449            self.vertices.push(VertexD::new(x, y, cmd));
450        }
451    }
452
453    /// Join a vertex source to the existing path (pen stays down).
454    ///
455    /// The first move_to of the joined path is converted to line_to
456    /// if the current path already has a vertex endpoint.
457    pub fn join_path(&mut self, vs: &mut dyn VertexSource, path_id: u32) {
458        let mut x = 0.0;
459        let mut y = 0.0;
460        vs.rewind(path_id);
461        let mut cmd = vs.vertex(&mut x, &mut y);
462        if !is_stop(cmd) {
463            if is_vertex(cmd) {
464                let mut x0 = 0.0;
465                let mut y0 = 0.0;
466                let cmd0 = self.last_vertex_xy(&mut x0, &mut y0);
467                if is_vertex(cmd0) {
468                    if calc_distance(x, y, x0, y0) > VERTEX_DIST_EPSILON {
469                        if is_move_to(cmd) {
470                            cmd = PATH_CMD_LINE_TO;
471                        }
472                        self.vertices.push(VertexD::new(x, y, cmd));
473                    }
474                } else {
475                    if is_stop(cmd0) {
476                        cmd = PATH_CMD_MOVE_TO;
477                    } else if is_move_to(cmd) {
478                        cmd = PATH_CMD_LINE_TO;
479                    }
480                    self.vertices.push(VertexD::new(x, y, cmd));
481                }
482            }
483            loop {
484                cmd = vs.vertex(&mut x, &mut y);
485                if is_stop(cmd) {
486                    break;
487                }
488                let actual_cmd = if is_move_to(cmd) {
489                    PATH_CMD_LINE_TO
490                } else {
491                    cmd
492                };
493                self.vertices.push(VertexD::new(x, y, actual_cmd));
494            }
495        }
496    }
497
498    /// Concatenate a polygon from flat coordinate data.
499    pub fn concat_poly(&mut self, data: &[f64], closed: bool) {
500        let mut adaptor = PolyPlainAdaptor::new(data, closed);
501        self.concat_path(&mut adaptor, 0);
502    }
503
504    /// Join a polygon from flat coordinate data.
505    pub fn join_poly(&mut self, data: &[f64], closed: bool) {
506        let mut adaptor = PolyPlainAdaptor::new(data, closed);
507        self.join_path(&mut adaptor, 0);
508    }
509
510    // ---------------------------------------------------------------
511    // Polygon manipulation
512    // ---------------------------------------------------------------
513
514    /// Detect the orientation of a polygon between `start` and `end` (exclusive).
515    fn perceive_polygon_orientation(&self, start: usize, end: usize) -> u32 {
516        let np = end - start;
517        let mut area = 0.0;
518        for i in 0..np {
519            let v1 = &self.vertices[start + i];
520            let v2 = &self.vertices[start + (i + 1) % np];
521            area += v1.x * v2.y - v1.y * v2.x;
522        }
523        if area < 0.0 {
524            PATH_FLAGS_CW
525        } else {
526            PATH_FLAGS_CCW
527        }
528    }
529
530    /// Invert a polygon between `start` and `end` (exclusive).
531    fn invert_polygon_range(&mut self, start: usize, end: usize) {
532        let tmp_cmd = self.vertices[start].cmd;
533        let end = end - 1; // Make end inclusive
534
535        // Shift all commands one position
536        for i in start..end {
537            let next_cmd = self.vertices[i + 1].cmd;
538            self.vertices[i].cmd = next_cmd;
539        }
540
541        // Assign starting command to the ending command
542        self.vertices[end].cmd = tmp_cmd;
543
544        // Reverse the polygon vertices
545        let (mut lo, mut hi) = (start, end);
546        while hi > lo {
547            self.vertices.swap(lo, hi);
548            lo += 1;
549            hi -= 1;
550        }
551    }
552
553    /// Invert the polygon starting at `start`.
554    pub fn invert_polygon(&mut self, start: usize) {
555        let mut start = start;
556        let total = self.vertices.len();
557
558        // Skip all non-vertices at the beginning
559        while start < total && !is_vertex(self.vertices[start].cmd) {
560            start += 1;
561        }
562
563        // Skip all insignificant move_to
564        while start + 1 < total
565            && is_move_to(self.vertices[start].cmd)
566            && is_move_to(self.vertices[start + 1].cmd)
567        {
568            start += 1;
569        }
570
571        // Find the last vertex
572        let mut end = start + 1;
573        while end < total && !is_next_poly(self.vertices[end].cmd) {
574            end += 1;
575        }
576
577        self.invert_polygon_range(start, end);
578    }
579
580    /// Arrange polygon orientation for a single polygon starting at `start`.
581    /// Returns the index past the end of the polygon.
582    pub fn arrange_polygon_orientation(&mut self, start: usize, orientation: u32) -> usize {
583        if orientation == PATH_FLAGS_NONE {
584            return start;
585        }
586
587        let mut start = start;
588        let total = self.vertices.len();
589
590        // Skip non-vertices
591        while start < total && !is_vertex(self.vertices[start].cmd) {
592            start += 1;
593        }
594
595        // Skip insignificant move_to
596        while start + 1 < total
597            && is_move_to(self.vertices[start].cmd)
598            && is_move_to(self.vertices[start + 1].cmd)
599        {
600            start += 1;
601        }
602
603        // Find end
604        let mut end = start + 1;
605        while end < total && !is_next_poly(self.vertices[end].cmd) {
606            end += 1;
607        }
608
609        if end - start > 2 && self.perceive_polygon_orientation(start, end) != orientation {
610            self.invert_polygon_range(start, end);
611            let mut idx = end;
612            while idx < total && is_end_poly(self.vertices[idx].cmd) {
613                let cmd = self.vertices[idx].cmd;
614                self.vertices[idx].cmd = set_orientation(cmd, orientation);
615                idx += 1;
616            }
617            return idx;
618        }
619        end
620    }
621
622    /// Arrange orientations of all polygons in a sub-path.
623    pub fn arrange_orientations(&mut self, start: usize, orientation: u32) -> usize {
624        let mut start = start;
625        if orientation != PATH_FLAGS_NONE {
626            while start < self.vertices.len() {
627                start = self.arrange_polygon_orientation(start, orientation);
628                if is_stop(self.vertices.get(start).map_or(PATH_CMD_STOP, |v| v.cmd)) {
629                    start += 1;
630                    break;
631                }
632            }
633        }
634        start
635    }
636
637    /// Arrange orientations of all polygons in all paths.
638    pub fn arrange_orientations_all_paths(&mut self, orientation: u32) {
639        if orientation != PATH_FLAGS_NONE {
640            let mut start = 0;
641            while start < self.vertices.len() {
642                start = self.arrange_orientations(start, orientation);
643            }
644        }
645    }
646
647    /// Flip all vertices horizontally between x1 and x2.
648    pub fn flip_x(&mut self, x1: f64, x2: f64) {
649        for v in &mut self.vertices {
650            if is_vertex(v.cmd) {
651                v.x = x2 - v.x + x1;
652            }
653        }
654    }
655
656    /// Flip all vertices vertically between y1 and y2.
657    pub fn flip_y(&mut self, y1: f64, y2: f64) {
658        for v in &mut self.vertices {
659            if is_vertex(v.cmd) {
660                v.y = y2 - v.y + y1;
661            }
662        }
663    }
664
665    /// Translate vertices starting from `path_id` until a stop command.
666    pub fn translate(&mut self, dx: f64, dy: f64, path_id: usize) {
667        let total = self.vertices.len();
668        let mut idx = path_id;
669        while idx < total {
670            let cmd = self.vertices[idx].cmd;
671            if is_stop(cmd) {
672                break;
673            }
674            if is_vertex(cmd) {
675                self.vertices[idx].x += dx;
676                self.vertices[idx].y += dy;
677            }
678            idx += 1;
679        }
680    }
681
682    /// Translate all vertices in all paths.
683    pub fn translate_all_paths(&mut self, dx: f64, dy: f64) {
684        for v in &mut self.vertices {
685            if is_vertex(v.cmd) {
686                v.x += dx;
687                v.y += dy;
688            }
689        }
690    }
691
692    /// Transform vertices starting from `path_id` using a closure.
693    pub fn transform<F: Fn(f64, f64) -> (f64, f64)>(&mut self, trans: &F, path_id: usize) {
694        let total = self.vertices.len();
695        let mut idx = path_id;
696        while idx < total {
697            let cmd = self.vertices[idx].cmd;
698            if is_stop(cmd) {
699                break;
700            }
701            if is_vertex(cmd) {
702                let (nx, ny) = trans(self.vertices[idx].x, self.vertices[idx].y);
703                self.vertices[idx].x = nx;
704                self.vertices[idx].y = ny;
705            }
706            idx += 1;
707        }
708    }
709
710    /// Transform all vertices in all paths using a closure.
711    pub fn transform_all_paths<F: Fn(f64, f64) -> (f64, f64)>(&mut self, trans: &F) {
712        for v in &mut self.vertices {
713            if is_vertex(v.cmd) {
714                let (nx, ny) = trans(v.x, v.y);
715                v.x = nx;
716                v.y = ny;
717            }
718        }
719    }
720
721    /// Align a single path so that nearly-equal start/end points become exact.
722    /// Returns the index past the end of this path.
723    pub fn align_path(&mut self, idx: usize) -> usize {
724        let total = self.total_vertices();
725        let mut idx = idx;
726
727        if idx >= total || !is_move_to(self.command(idx)) {
728            return total;
729        }
730
731        let mut start_x = 0.0;
732        let mut start_y = 0.0;
733        while idx < total && is_move_to(self.command(idx)) {
734            self.vertex_idx(idx, &mut start_x, &mut start_y);
735            idx += 1;
736        }
737        while idx < total && is_drawing(self.command(idx)) {
738            idx += 1;
739        }
740
741        let mut x = 0.0;
742        let mut y = 0.0;
743        if is_drawing(self.vertex_idx(idx - 1, &mut x, &mut y))
744            && is_equal_eps(x, start_x, 1e-8)
745            && is_equal_eps(y, start_y, 1e-8)
746        {
747            self.modify_vertex(idx - 1, start_x, start_y);
748        }
749
750        while idx < total && !is_move_to(self.command(idx)) {
751            idx += 1;
752        }
753        idx
754    }
755
756    /// Align all paths.
757    pub fn align_all_paths(&mut self) {
758        let mut i = 0;
759        while i < self.total_vertices() {
760            i = self.align_path(i);
761        }
762    }
763}
764
765impl Default for PathStorage {
766    fn default() -> Self {
767        Self::new()
768    }
769}
770
771impl VertexSource for PathStorage {
772    fn rewind(&mut self, path_id: u32) {
773        self.iterator = path_id as usize;
774    }
775
776    fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
777        if self.iterator >= self.vertices.len() {
778            return PATH_CMD_STOP;
779        }
780        let v = &self.vertices[self.iterator];
781        *x = v.x;
782        *y = v.y;
783        self.iterator += 1;
784        v.cmd
785    }
786}
787
788// ===================================================================
789// Adaptors
790// ===================================================================
791
792/// Adaptor that wraps a flat slice of coordinates `[x0, y0, x1, y1, ...]`
793/// as a `VertexSource`.
794///
795/// Port of C++ `agg::poly_plain_adaptor<double>`.
796pub struct PolyPlainAdaptor<'a> {
797    data: &'a [f64],
798    index: usize,
799    closed: bool,
800    stop: bool,
801}
802
803impl<'a> PolyPlainAdaptor<'a> {
804    /// Create a new adaptor. `data` must contain pairs of (x, y) coordinates.
805    pub fn new(data: &'a [f64], closed: bool) -> Self {
806        Self {
807            data,
808            index: 0,
809            closed,
810            stop: false,
811        }
812    }
813}
814
815impl VertexSource for PolyPlainAdaptor<'_> {
816    fn rewind(&mut self, _path_id: u32) {
817        self.index = 0;
818        self.stop = false;
819    }
820
821    fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
822        if self.index + 1 < self.data.len() {
823            let first = self.index == 0;
824            *x = self.data[self.index];
825            *y = self.data[self.index + 1];
826            self.index += 2;
827            return if first {
828                PATH_CMD_MOVE_TO
829            } else {
830                PATH_CMD_LINE_TO
831            };
832        }
833        *x = 0.0;
834        *y = 0.0;
835        if self.closed && !self.stop {
836            self.stop = true;
837            return PATH_CMD_END_POLY | PATH_FLAGS_CLOSE;
838        }
839        PATH_CMD_STOP
840    }
841}
842
843/// Adaptor for a single line segment as a `VertexSource`.
844///
845/// Port of C++ `agg::line_adaptor`.
846pub struct LineAdaptor {
847    coords: [f64; 4],
848    index: usize,
849}
850
851impl LineAdaptor {
852    /// Create a new line adaptor.
853    pub fn new(x1: f64, y1: f64, x2: f64, y2: f64) -> Self {
854        Self {
855            coords: [x1, y1, x2, y2],
856            index: 0,
857        }
858    }
859
860    /// Re-initialize with new coordinates.
861    pub fn init(&mut self, x1: f64, y1: f64, x2: f64, y2: f64) {
862        self.coords = [x1, y1, x2, y2];
863        self.index = 0;
864    }
865}
866
867impl VertexSource for LineAdaptor {
868    fn rewind(&mut self, _path_id: u32) {
869        self.index = 0;
870    }
871
872    fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
873        if self.index < 4 {
874            let first = self.index == 0;
875            *x = self.coords[self.index];
876            *y = self.coords[self.index + 1];
877            self.index += 2;
878            return if first {
879                PATH_CMD_MOVE_TO
880            } else {
881                PATH_CMD_LINE_TO
882            };
883        }
884        *x = 0.0;
885        *y = 0.0;
886        PATH_CMD_STOP
887    }
888}
889
890#[cfg(test)]
891mod tests {
892    use super::*;
893    use crate::basics::is_close;
894
895    #[test]
896    fn test_new_empty() {
897        let ps = PathStorage::new();
898        assert_eq!(ps.total_vertices(), 0);
899        assert_eq!(ps.last_command(), PATH_CMD_STOP);
900        assert_eq!(ps.last_x(), 0.0);
901        assert_eq!(ps.last_y(), 0.0);
902    }
903
904    #[test]
905    fn test_move_to_line_to() {
906        let mut ps = PathStorage::new();
907        ps.move_to(10.0, 20.0);
908        ps.line_to(30.0, 40.0);
909        ps.line_to(50.0, 60.0);
910
911        assert_eq!(ps.total_vertices(), 3);
912        assert_eq!(ps.command(0), PATH_CMD_MOVE_TO);
913        assert_eq!(ps.command(1), PATH_CMD_LINE_TO);
914        assert_eq!(ps.command(2), PATH_CMD_LINE_TO);
915
916        let (mut x, mut y) = (0.0, 0.0);
917        ps.vertex_idx(1, &mut x, &mut y);
918        assert!((x - 30.0).abs() < 1e-10);
919        assert!((y - 40.0).abs() < 1e-10);
920    }
921
922    #[test]
923    fn test_relative_commands() {
924        let mut ps = PathStorage::new();
925        ps.move_to(10.0, 20.0);
926        ps.line_rel(5.0, 5.0);
927
928        let (mut x, mut y) = (0.0, 0.0);
929        ps.vertex_idx(1, &mut x, &mut y);
930        assert!((x - 15.0).abs() < 1e-10);
931        assert!((y - 25.0).abs() < 1e-10);
932    }
933
934    #[test]
935    fn test_hline_vline() {
936        let mut ps = PathStorage::new();
937        ps.move_to(10.0, 20.0);
938        ps.hline_to(50.0);
939        ps.vline_to(80.0);
940
941        let (mut x, mut y) = (0.0, 0.0);
942        ps.vertex_idx(1, &mut x, &mut y);
943        assert!((x - 50.0).abs() < 1e-10);
944        assert!((y - 20.0).abs() < 1e-10);
945
946        ps.vertex_idx(2, &mut x, &mut y);
947        assert!((x - 50.0).abs() < 1e-10);
948        assert!((y - 80.0).abs() < 1e-10);
949    }
950
951    #[test]
952    fn test_hline_rel_vline_rel() {
953        let mut ps = PathStorage::new();
954        ps.move_to(10.0, 20.0);
955        ps.hline_rel(5.0);
956        ps.vline_rel(10.0);
957
958        let (mut x, mut y) = (0.0, 0.0);
959        ps.vertex_idx(1, &mut x, &mut y);
960        assert!((x - 15.0).abs() < 1e-10);
961        assert!((y - 20.0).abs() < 1e-10);
962
963        ps.vertex_idx(2, &mut x, &mut y);
964        assert!((x - 15.0).abs() < 1e-10);
965        assert!((y - 30.0).abs() < 1e-10);
966    }
967
968    #[test]
969    fn test_curve3() {
970        let mut ps = PathStorage::new();
971        ps.move_to(0.0, 0.0);
972        ps.curve3(50.0, 100.0, 100.0, 0.0);
973
974        assert_eq!(ps.total_vertices(), 3);
975        assert_eq!(ps.command(1), PATH_CMD_CURVE3);
976        assert_eq!(ps.command(2), PATH_CMD_CURVE3);
977    }
978
979    #[test]
980    fn test_curve4() {
981        let mut ps = PathStorage::new();
982        ps.move_to(0.0, 0.0);
983        ps.curve4(25.0, 100.0, 75.0, 100.0, 100.0, 0.0);
984
985        assert_eq!(ps.total_vertices(), 4);
986        assert_eq!(ps.command(1), PATH_CMD_CURVE4);
987        assert_eq!(ps.command(2), PATH_CMD_CURVE4);
988        assert_eq!(ps.command(3), PATH_CMD_CURVE4);
989    }
990
991    #[test]
992    fn test_close_polygon() {
993        let mut ps = PathStorage::new();
994        ps.move_to(0.0, 0.0);
995        ps.line_to(100.0, 0.0);
996        ps.line_to(100.0, 100.0);
997        ps.close_polygon(PATH_FLAGS_NONE);
998
999        assert_eq!(ps.total_vertices(), 4);
1000        assert!(is_end_poly(ps.command(3)));
1001        assert!(is_close(ps.command(3)));
1002    }
1003
1004    #[test]
1005    fn test_vertex_source_iteration() {
1006        let mut ps = PathStorage::new();
1007        ps.move_to(10.0, 20.0);
1008        ps.line_to(30.0, 40.0);
1009
1010        ps.rewind(0);
1011        let (mut x, mut y) = (0.0, 0.0);
1012
1013        let cmd = ps.vertex(&mut x, &mut y);
1014        assert_eq!(cmd, PATH_CMD_MOVE_TO);
1015        assert!((x - 10.0).abs() < 1e-10);
1016
1017        let cmd = ps.vertex(&mut x, &mut y);
1018        assert_eq!(cmd, PATH_CMD_LINE_TO);
1019        assert!((x - 30.0).abs() < 1e-10);
1020
1021        let cmd = ps.vertex(&mut x, &mut y);
1022        assert_eq!(cmd, PATH_CMD_STOP);
1023    }
1024
1025    #[test]
1026    fn test_start_new_path() {
1027        let mut ps = PathStorage::new();
1028        ps.move_to(0.0, 0.0);
1029        ps.line_to(10.0, 10.0);
1030
1031        let id = ps.start_new_path();
1032        assert_eq!(id, 3); // 2 vertices + 1 stop
1033
1034        ps.move_to(50.0, 50.0);
1035        ps.line_to(60.0, 60.0);
1036
1037        // Iterate second path
1038        ps.rewind(id as u32);
1039        let (mut x, mut y) = (0.0, 0.0);
1040        let cmd = ps.vertex(&mut x, &mut y);
1041        assert_eq!(cmd, PATH_CMD_MOVE_TO);
1042        assert!((x - 50.0).abs() < 1e-10);
1043    }
1044
1045    #[test]
1046    fn test_modify_vertex() {
1047        let mut ps = PathStorage::new();
1048        ps.move_to(10.0, 20.0);
1049        ps.modify_vertex(0, 30.0, 40.0);
1050
1051        let (mut x, mut y) = (0.0, 0.0);
1052        ps.vertex_idx(0, &mut x, &mut y);
1053        assert!((x - 30.0).abs() < 1e-10);
1054        assert!((y - 40.0).abs() < 1e-10);
1055    }
1056
1057    #[test]
1058    fn test_swap_vertices() {
1059        let mut ps = PathStorage::new();
1060        ps.move_to(10.0, 20.0);
1061        ps.line_to(30.0, 40.0);
1062
1063        ps.swap_vertices(0, 1);
1064
1065        let (mut x, mut y) = (0.0, 0.0);
1066        ps.vertex_idx(0, &mut x, &mut y);
1067        assert!((x - 30.0).abs() < 1e-10);
1068        assert_eq!(ps.command(0), PATH_CMD_LINE_TO);
1069
1070        ps.vertex_idx(1, &mut x, &mut y);
1071        assert!((x - 10.0).abs() < 1e-10);
1072        assert_eq!(ps.command(1), PATH_CMD_MOVE_TO);
1073    }
1074
1075    #[test]
1076    fn test_flip_x() {
1077        let mut ps = PathStorage::new();
1078        ps.move_to(10.0, 20.0);
1079        ps.line_to(30.0, 40.0);
1080        ps.flip_x(0.0, 100.0);
1081
1082        let (mut x, mut y) = (0.0, 0.0);
1083        ps.vertex_idx(0, &mut x, &mut y);
1084        assert!((x - 90.0).abs() < 1e-10);
1085        assert!((y - 20.0).abs() < 1e-10);
1086
1087        ps.vertex_idx(1, &mut x, &mut y);
1088        assert!((x - 70.0).abs() < 1e-10);
1089    }
1090
1091    #[test]
1092    fn test_flip_y() {
1093        let mut ps = PathStorage::new();
1094        ps.move_to(10.0, 20.0);
1095        ps.line_to(30.0, 40.0);
1096        ps.flip_y(0.0, 100.0);
1097
1098        let (mut x, mut y) = (0.0, 0.0);
1099        ps.vertex_idx(0, &mut x, &mut y);
1100        assert!((x - 10.0).abs() < 1e-10);
1101        assert!((y - 80.0).abs() < 1e-10);
1102    }
1103
1104    #[test]
1105    fn test_translate() {
1106        let mut ps = PathStorage::new();
1107        ps.move_to(10.0, 20.0);
1108        ps.line_to(30.0, 40.0);
1109        ps.translate(5.0, 10.0, 0);
1110
1111        let (mut x, mut y) = (0.0, 0.0);
1112        ps.vertex_idx(0, &mut x, &mut y);
1113        assert!((x - 15.0).abs() < 1e-10);
1114        assert!((y - 30.0).abs() < 1e-10);
1115    }
1116
1117    #[test]
1118    fn test_translate_all_paths() {
1119        let mut ps = PathStorage::new();
1120        ps.move_to(10.0, 20.0);
1121        ps.line_to(30.0, 40.0);
1122        ps.translate_all_paths(100.0, 200.0);
1123
1124        let (mut x, mut y) = (0.0, 0.0);
1125        ps.vertex_idx(0, &mut x, &mut y);
1126        assert!((x - 110.0).abs() < 1e-10);
1127        assert!((y - 220.0).abs() < 1e-10);
1128    }
1129
1130    #[test]
1131    fn test_concat_path() {
1132        let mut ps = PathStorage::new();
1133        ps.move_to(0.0, 0.0);
1134
1135        let mut ps2 = PathStorage::new();
1136        ps2.move_to(10.0, 20.0);
1137        ps2.line_to(30.0, 40.0);
1138
1139        ps.concat_path(&mut ps2, 0);
1140
1141        assert_eq!(ps.total_vertices(), 3);
1142        assert_eq!(ps.command(1), PATH_CMD_MOVE_TO);
1143    }
1144
1145    #[test]
1146    fn test_join_path() {
1147        let mut ps = PathStorage::new();
1148        ps.move_to(0.0, 0.0);
1149        ps.line_to(10.0, 10.0);
1150
1151        let mut ps2 = PathStorage::new();
1152        ps2.move_to(20.0, 20.0);
1153        ps2.line_to(30.0, 30.0);
1154
1155        ps.join_path(&mut ps2, 0);
1156
1157        // move_to(20,20) should become line_to(20,20)
1158        assert_eq!(ps.command(2), PATH_CMD_LINE_TO);
1159        let (mut x, mut y) = (0.0, 0.0);
1160        ps.vertex_idx(2, &mut x, &mut y);
1161        assert!((x - 20.0).abs() < 1e-10);
1162    }
1163
1164    #[test]
1165    fn test_remove_all() {
1166        let mut ps = PathStorage::new();
1167        ps.move_to(10.0, 20.0);
1168        ps.line_to(30.0, 40.0);
1169        ps.remove_all();
1170
1171        assert_eq!(ps.total_vertices(), 0);
1172    }
1173
1174    #[test]
1175    fn test_poly_plain_adaptor() {
1176        let data = [10.0, 20.0, 30.0, 40.0, 50.0, 60.0];
1177        let mut adaptor = PolyPlainAdaptor::new(&data, true);
1178
1179        let (mut x, mut y) = (0.0, 0.0);
1180        let cmd = adaptor.vertex(&mut x, &mut y);
1181        assert_eq!(cmd, PATH_CMD_MOVE_TO);
1182        assert!((x - 10.0).abs() < 1e-10);
1183
1184        let cmd = adaptor.vertex(&mut x, &mut y);
1185        assert_eq!(cmd, PATH_CMD_LINE_TO);
1186        assert!((x - 30.0).abs() < 1e-10);
1187
1188        let cmd = adaptor.vertex(&mut x, &mut y);
1189        assert_eq!(cmd, PATH_CMD_LINE_TO);
1190        assert!((x - 50.0).abs() < 1e-10);
1191
1192        let cmd = adaptor.vertex(&mut x, &mut y);
1193        assert!(is_end_poly(cmd));
1194        assert!(is_close(cmd));
1195
1196        let cmd = adaptor.vertex(&mut x, &mut y);
1197        assert_eq!(cmd, PATH_CMD_STOP);
1198    }
1199
1200    #[test]
1201    fn test_poly_plain_adaptor_open() {
1202        let data = [10.0, 20.0, 30.0, 40.0];
1203        let mut adaptor = PolyPlainAdaptor::new(&data, false);
1204
1205        let (mut x, mut y) = (0.0, 0.0);
1206        adaptor.vertex(&mut x, &mut y); // move_to
1207        adaptor.vertex(&mut x, &mut y); // line_to
1208
1209        let cmd = adaptor.vertex(&mut x, &mut y);
1210        assert_eq!(cmd, PATH_CMD_STOP); // no close
1211    }
1212
1213    #[test]
1214    fn test_line_adaptor() {
1215        let mut la = LineAdaptor::new(10.0, 20.0, 30.0, 40.0);
1216        let (mut x, mut y) = (0.0, 0.0);
1217
1218        let cmd = la.vertex(&mut x, &mut y);
1219        assert_eq!(cmd, PATH_CMD_MOVE_TO);
1220        assert!((x - 10.0).abs() < 1e-10);
1221        assert!((y - 20.0).abs() < 1e-10);
1222
1223        let cmd = la.vertex(&mut x, &mut y);
1224        assert_eq!(cmd, PATH_CMD_LINE_TO);
1225        assert!((x - 30.0).abs() < 1e-10);
1226        assert!((y - 40.0).abs() < 1e-10);
1227
1228        let cmd = la.vertex(&mut x, &mut y);
1229        assert_eq!(cmd, PATH_CMD_STOP);
1230    }
1231
1232    #[test]
1233    fn test_line_adaptor_rewind() {
1234        let mut la = LineAdaptor::new(10.0, 20.0, 30.0, 40.0);
1235        let (mut x, mut y) = (0.0, 0.0);
1236
1237        la.vertex(&mut x, &mut y);
1238        la.vertex(&mut x, &mut y);
1239        la.rewind(0);
1240
1241        let cmd = la.vertex(&mut x, &mut y);
1242        assert_eq!(cmd, PATH_CMD_MOVE_TO);
1243    }
1244
1245    #[test]
1246    fn test_curve3_smooth() {
1247        let mut ps = PathStorage::new();
1248        ps.move_to(0.0, 0.0);
1249        ps.curve3(50.0, 100.0, 100.0, 0.0);
1250        ps.curve3_smooth(200.0, 0.0);
1251
1252        // Reflected control point: (100,0) + (100,0) - (50,100) = (150, -100)
1253        assert_eq!(ps.total_vertices(), 5);
1254        let (mut x, mut y) = (0.0, 0.0);
1255        ps.vertex_idx(3, &mut x, &mut y);
1256        assert!((x - 150.0).abs() < 1e-10);
1257        assert!((y - (-100.0)).abs() < 1e-10);
1258    }
1259
1260    #[test]
1261    fn test_curve4_smooth() {
1262        let mut ps = PathStorage::new();
1263        ps.move_to(0.0, 0.0);
1264        ps.curve4(25.0, 100.0, 75.0, 100.0, 100.0, 0.0);
1265        ps.curve4_smooth(175.0, 100.0, 200.0, 0.0);
1266
1267        // Reflected ctrl1: (100,0) + (100,0) - (75,100) = (125, -100)
1268        assert_eq!(ps.total_vertices(), 7);
1269        let (mut x, mut y) = (0.0, 0.0);
1270        ps.vertex_idx(4, &mut x, &mut y);
1271        assert!((x - 125.0).abs() < 1e-10);
1272        assert!((y - (-100.0)).abs() < 1e-10);
1273    }
1274
1275    #[test]
1276    fn test_invert_polygon() {
1277        let mut ps = PathStorage::new();
1278        ps.move_to(0.0, 0.0);
1279        ps.line_to(100.0, 0.0);
1280        ps.line_to(100.0, 100.0);
1281
1282        ps.invert_polygon(0);
1283
1284        // After inversion, vertices are reversed with commands shifted
1285        let (mut x, mut y) = (0.0, 0.0);
1286        ps.vertex_idx(0, &mut x, &mut y);
1287        assert!((x - 100.0).abs() < 1e-10);
1288        assert!((y - 100.0).abs() < 1e-10);
1289    }
1290
1291    #[test]
1292    fn test_perceive_orientation_ccw() {
1293        let mut ps = PathStorage::new();
1294        // CCW triangle
1295        ps.move_to(0.0, 0.0);
1296        ps.line_to(100.0, 0.0);
1297        ps.line_to(100.0, 100.0);
1298
1299        let ori = ps.perceive_polygon_orientation(0, 3);
1300        assert_eq!(ori, PATH_FLAGS_CCW);
1301    }
1302
1303    #[test]
1304    fn test_perceive_orientation_cw() {
1305        let mut ps = PathStorage::new();
1306        // CW triangle
1307        ps.move_to(0.0, 0.0);
1308        ps.line_to(100.0, 100.0);
1309        ps.line_to(100.0, 0.0);
1310
1311        let ori = ps.perceive_polygon_orientation(0, 3);
1312        assert_eq!(ori, PATH_FLAGS_CW);
1313    }
1314
1315    #[test]
1316    fn test_arrange_polygon_orientation() {
1317        let mut ps = PathStorage::new();
1318        // CW triangle
1319        ps.move_to(0.0, 0.0);
1320        ps.line_to(100.0, 100.0);
1321        ps.line_to(100.0, 0.0);
1322
1323        // Force CCW
1324        ps.arrange_polygon_orientation(0, PATH_FLAGS_CCW);
1325
1326        let ori = ps.perceive_polygon_orientation(0, 3);
1327        assert_eq!(ori, PATH_FLAGS_CCW);
1328    }
1329
1330    #[test]
1331    fn test_concat_poly() {
1332        let mut ps = PathStorage::new();
1333        let coords = [0.0, 0.0, 100.0, 0.0, 100.0, 100.0];
1334        ps.concat_poly(&coords, true);
1335
1336        assert_eq!(ps.total_vertices(), 4); // 3 vertices + end_poly
1337        assert_eq!(ps.command(0), PATH_CMD_MOVE_TO);
1338        assert_eq!(ps.command(1), PATH_CMD_LINE_TO);
1339        assert_eq!(ps.command(2), PATH_CMD_LINE_TO);
1340        assert!(is_end_poly(ps.command(3)));
1341    }
1342
1343    #[test]
1344    fn test_transform_all_paths() {
1345        let mut ps = PathStorage::new();
1346        ps.move_to(10.0, 20.0);
1347        ps.line_to(30.0, 40.0);
1348
1349        ps.transform_all_paths(&|x, y| (x * 2.0, y * 3.0));
1350
1351        let (mut x, mut y) = (0.0, 0.0);
1352        ps.vertex_idx(0, &mut x, &mut y);
1353        assert!((x - 20.0).abs() < 1e-10);
1354        assert!((y - 60.0).abs() < 1e-10);
1355
1356        ps.vertex_idx(1, &mut x, &mut y);
1357        assert!((x - 60.0).abs() < 1e-10);
1358        assert!((y - 120.0).abs() < 1e-10);
1359    }
1360
1361    #[test]
1362    fn test_default() {
1363        let ps = PathStorage::default();
1364        assert_eq!(ps.total_vertices(), 0);
1365    }
1366
1367    #[test]
1368    fn test_move_rel_from_empty() {
1369        let mut ps = PathStorage::new();
1370        // When empty, rel_to_abs doesn't add anything (no prior vertex)
1371        ps.move_rel(10.0, 20.0);
1372        let (mut x, mut y) = (0.0, 0.0);
1373        ps.vertex_idx(0, &mut x, &mut y);
1374        assert!((x - 10.0).abs() < 1e-10);
1375        assert!((y - 20.0).abs() < 1e-10);
1376    }
1377
1378    #[test]
1379    fn test_end_poly_only_after_vertex() {
1380        let mut ps = PathStorage::new();
1381        // end_poly on empty should do nothing
1382        ps.end_poly(PATH_FLAGS_CLOSE);
1383        assert_eq!(ps.total_vertices(), 0);
1384
1385        ps.move_to(0.0, 0.0);
1386        ps.end_poly(PATH_FLAGS_CLOSE);
1387        assert_eq!(ps.total_vertices(), 2);
1388    }
1389
1390    #[test]
1391    fn test_arc_to() {
1392        let mut ps = PathStorage::new();
1393        ps.move_to(0.0, 0.0);
1394        // Small arc — should add several vertices via BezierArcSvg
1395        ps.arc_to(50.0, 50.0, 0.0, false, true, 100.0, 0.0);
1396
1397        assert!(ps.total_vertices() > 2);
1398    }
1399
1400    #[test]
1401    fn test_arc_to_no_prior_vertex() {
1402        let mut ps = PathStorage::new();
1403        // No prior vertex → should become move_to
1404        ps.arc_to(50.0, 50.0, 0.0, false, true, 100.0, 0.0);
1405        assert_eq!(ps.command(0), PATH_CMD_MOVE_TO);
1406    }
1407
1408    #[test]
1409    fn test_last_prev_vertex() {
1410        let mut ps = PathStorage::new();
1411        let (mut x, mut y) = (0.0, 0.0);
1412        assert_eq!(ps.last_vertex_xy(&mut x, &mut y), PATH_CMD_STOP);
1413        assert_eq!(ps.prev_vertex_xy(&mut x, &mut y), PATH_CMD_STOP);
1414
1415        ps.move_to(10.0, 20.0);
1416        ps.line_to(30.0, 40.0);
1417
1418        let cmd = ps.last_vertex_xy(&mut x, &mut y);
1419        assert_eq!(cmd, PATH_CMD_LINE_TO);
1420        assert!((x - 30.0).abs() < 1e-10);
1421
1422        let cmd = ps.prev_vertex_xy(&mut x, &mut y);
1423        assert_eq!(cmd, PATH_CMD_MOVE_TO);
1424        assert!((x - 10.0).abs() < 1e-10);
1425    }
1426}