Skip to main content

agg_rust/
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    ///
331    /// Useful when you need to iterate or cache path data without going through
332    /// the mutable `VertexSource` iterator protocol. The slice is valid for the
333    /// lifetime of the `PathStorage`.
334    pub fn vertices(&self) -> &[VertexD] {
335        &self.vertices
336    }
337
338    /// Convert relative coordinates to absolute by adding last vertex position.
339    pub fn rel_to_abs(&self, x: &mut f64, y: &mut f64) {
340        if !self.vertices.is_empty() {
341            let last = &self.vertices[self.vertices.len() - 1];
342            if is_vertex(last.cmd) {
343                *x += last.x;
344                *y += last.y;
345            }
346        }
347    }
348
349    /// Get the last vertex's (x, y) and command. Returns `PATH_CMD_STOP` if empty.
350    pub fn last_vertex_xy(&self, x: &mut f64, y: &mut f64) -> u32 {
351        if self.vertices.is_empty() {
352            *x = 0.0;
353            *y = 0.0;
354            return PATH_CMD_STOP;
355        }
356        let v = &self.vertices[self.vertices.len() - 1];
357        *x = v.x;
358        *y = v.y;
359        v.cmd
360    }
361
362    /// Get the second-to-last vertex's (x, y) and command.
363    pub fn prev_vertex_xy(&self, x: &mut f64, y: &mut f64) -> u32 {
364        if self.vertices.len() < 2 {
365            *x = 0.0;
366            *y = 0.0;
367            return PATH_CMD_STOP;
368        }
369        let v = &self.vertices[self.vertices.len() - 2];
370        *x = v.x;
371        *y = v.y;
372        v.cmd
373    }
374
375    /// Get the last command (or `PATH_CMD_STOP` if empty).
376    pub fn last_command(&self) -> u32 {
377        if self.vertices.is_empty() {
378            PATH_CMD_STOP
379        } else {
380            self.vertices[self.vertices.len() - 1].cmd
381        }
382    }
383
384    /// Get the X coordinate of the last vertex (or 0.0 if empty).
385    pub fn last_x(&self) -> f64 {
386        if self.vertices.is_empty() {
387            0.0
388        } else {
389            self.vertices[self.vertices.len() - 1].x
390        }
391    }
392
393    /// Get the Y coordinate of the last vertex (or 0.0 if empty).
394    pub fn last_y(&self) -> f64 {
395        if self.vertices.is_empty() {
396            0.0
397        } else {
398            self.vertices[self.vertices.len() - 1].y
399        }
400    }
401
402    /// Get a vertex by index. Returns the command.
403    pub fn vertex_idx(&self, idx: usize, x: &mut f64, y: &mut f64) -> u32 {
404        let v = &self.vertices[idx];
405        *x = v.x;
406        *y = v.y;
407        v.cmd
408    }
409
410    /// Get a command by index.
411    pub fn command(&self, idx: usize) -> u32 {
412        self.vertices[idx].cmd
413    }
414
415    /// Modify a vertex's coordinates.
416    pub fn modify_vertex(&mut self, idx: usize, x: f64, y: f64) {
417        self.vertices[idx].x = x;
418        self.vertices[idx].y = y;
419    }
420
421    /// Modify a vertex's coordinates and command.
422    pub fn modify_vertex_cmd(&mut self, idx: usize, x: f64, y: f64, cmd: u32) {
423        self.vertices[idx].x = x;
424        self.vertices[idx].y = y;
425        self.vertices[idx].cmd = cmd;
426    }
427
428    /// Modify only a vertex's command.
429    pub fn modify_command(&mut self, idx: usize, cmd: u32) {
430        self.vertices[idx].cmd = cmd;
431    }
432
433    /// Swap two vertices (coordinates and commands).
434    pub fn swap_vertices(&mut self, v1: usize, v2: usize) {
435        self.vertices.swap(v1, v2);
436    }
437
438    // ---------------------------------------------------------------
439    // Concatenation and joining
440    // ---------------------------------------------------------------
441
442    /// Concatenate all vertices from a vertex source as-is.
443    pub fn concat_path(&mut self, vs: &mut dyn VertexSource, path_id: u32) {
444        let mut x = 0.0;
445        let mut y = 0.0;
446        vs.rewind(path_id);
447        loop {
448            let cmd = vs.vertex(&mut x, &mut y);
449            if is_stop(cmd) {
450                break;
451            }
452            self.vertices.push(VertexD::new(x, y, cmd));
453        }
454    }
455
456    /// Join a vertex source to the existing path (pen stays down).
457    ///
458    /// The first move_to of the joined path is converted to line_to
459    /// if the current path already has a vertex endpoint.
460    pub fn join_path(&mut self, vs: &mut dyn VertexSource, path_id: u32) {
461        let mut x = 0.0;
462        let mut y = 0.0;
463        vs.rewind(path_id);
464        let mut cmd = vs.vertex(&mut x, &mut y);
465        if !is_stop(cmd) {
466            if is_vertex(cmd) {
467                let mut x0 = 0.0;
468                let mut y0 = 0.0;
469                let cmd0 = self.last_vertex_xy(&mut x0, &mut y0);
470                if is_vertex(cmd0) {
471                    if calc_distance(x, y, x0, y0) > VERTEX_DIST_EPSILON {
472                        if is_move_to(cmd) {
473                            cmd = PATH_CMD_LINE_TO;
474                        }
475                        self.vertices.push(VertexD::new(x, y, cmd));
476                    }
477                } else {
478                    if is_stop(cmd0) {
479                        cmd = PATH_CMD_MOVE_TO;
480                    } else if is_move_to(cmd) {
481                        cmd = PATH_CMD_LINE_TO;
482                    }
483                    self.vertices.push(VertexD::new(x, y, cmd));
484                }
485            }
486            loop {
487                cmd = vs.vertex(&mut x, &mut y);
488                if is_stop(cmd) {
489                    break;
490                }
491                let actual_cmd = if is_move_to(cmd) {
492                    PATH_CMD_LINE_TO
493                } else {
494                    cmd
495                };
496                self.vertices.push(VertexD::new(x, y, actual_cmd));
497            }
498        }
499    }
500
501    /// Concatenate a polygon from flat coordinate data.
502    pub fn concat_poly(&mut self, data: &[f64], closed: bool) {
503        let mut adaptor = PolyPlainAdaptor::new(data, closed);
504        self.concat_path(&mut adaptor, 0);
505    }
506
507    /// Join a polygon from flat coordinate data.
508    pub fn join_poly(&mut self, data: &[f64], closed: bool) {
509        let mut adaptor = PolyPlainAdaptor::new(data, closed);
510        self.join_path(&mut adaptor, 0);
511    }
512
513    // ---------------------------------------------------------------
514    // Polygon manipulation
515    // ---------------------------------------------------------------
516
517    /// Detect the orientation of a polygon between `start` and `end` (exclusive).
518    fn perceive_polygon_orientation(&self, start: usize, end: usize) -> u32 {
519        let np = end - start;
520        let mut area = 0.0;
521        for i in 0..np {
522            let v1 = &self.vertices[start + i];
523            let v2 = &self.vertices[start + (i + 1) % np];
524            area += v1.x * v2.y - v1.y * v2.x;
525        }
526        if area < 0.0 {
527            PATH_FLAGS_CW
528        } else {
529            PATH_FLAGS_CCW
530        }
531    }
532
533    /// Invert a polygon between `start` and `end` (exclusive).
534    fn invert_polygon_range(&mut self, start: usize, end: usize) {
535        let tmp_cmd = self.vertices[start].cmd;
536        let end = end - 1; // Make end inclusive
537
538        // Shift all commands one position
539        for i in start..end {
540            let next_cmd = self.vertices[i + 1].cmd;
541            self.vertices[i].cmd = next_cmd;
542        }
543
544        // Assign starting command to the ending command
545        self.vertices[end].cmd = tmp_cmd;
546
547        // Reverse the polygon vertices
548        let (mut lo, mut hi) = (start, end);
549        while hi > lo {
550            self.vertices.swap(lo, hi);
551            lo += 1;
552            hi -= 1;
553        }
554    }
555
556    /// Invert the polygon starting at `start`.
557    pub fn invert_polygon(&mut self, start: usize) {
558        let mut start = start;
559        let total = self.vertices.len();
560
561        // Skip all non-vertices at the beginning
562        while start < total && !is_vertex(self.vertices[start].cmd) {
563            start += 1;
564        }
565
566        // Skip all insignificant move_to
567        while start + 1 < total
568            && is_move_to(self.vertices[start].cmd)
569            && is_move_to(self.vertices[start + 1].cmd)
570        {
571            start += 1;
572        }
573
574        // Find the last vertex
575        let mut end = start + 1;
576        while end < total && !is_next_poly(self.vertices[end].cmd) {
577            end += 1;
578        }
579
580        self.invert_polygon_range(start, end);
581    }
582
583    /// Arrange polygon orientation for a single polygon starting at `start`.
584    /// Returns the index past the end of the polygon.
585    pub fn arrange_polygon_orientation(&mut self, start: usize, orientation: u32) -> usize {
586        if orientation == PATH_FLAGS_NONE {
587            return start;
588        }
589
590        let mut start = start;
591        let total = self.vertices.len();
592
593        // Skip non-vertices
594        while start < total && !is_vertex(self.vertices[start].cmd) {
595            start += 1;
596        }
597
598        // Skip insignificant move_to
599        while start + 1 < total
600            && is_move_to(self.vertices[start].cmd)
601            && is_move_to(self.vertices[start + 1].cmd)
602        {
603            start += 1;
604        }
605
606        // Find end
607        let mut end = start + 1;
608        while end < total && !is_next_poly(self.vertices[end].cmd) {
609            end += 1;
610        }
611
612        if end - start > 2 && self.perceive_polygon_orientation(start, end) != orientation {
613            self.invert_polygon_range(start, end);
614            let mut idx = end;
615            while idx < total && is_end_poly(self.vertices[idx].cmd) {
616                let cmd = self.vertices[idx].cmd;
617                self.vertices[idx].cmd = set_orientation(cmd, orientation);
618                idx += 1;
619            }
620            return idx;
621        }
622        end
623    }
624
625    /// Arrange orientations of all polygons in a sub-path.
626    pub fn arrange_orientations(&mut self, start: usize, orientation: u32) -> usize {
627        let mut start = start;
628        if orientation != PATH_FLAGS_NONE {
629            while start < self.vertices.len() {
630                start = self.arrange_polygon_orientation(start, orientation);
631                if is_stop(self.vertices.get(start).map_or(PATH_CMD_STOP, |v| v.cmd)) {
632                    start += 1;
633                    break;
634                }
635            }
636        }
637        start
638    }
639
640    /// Arrange orientations of all polygons in all paths.
641    pub fn arrange_orientations_all_paths(&mut self, orientation: u32) {
642        if orientation != PATH_FLAGS_NONE {
643            let mut start = 0;
644            while start < self.vertices.len() {
645                start = self.arrange_orientations(start, orientation);
646            }
647        }
648    }
649
650    /// Flip all vertices horizontally between x1 and x2.
651    pub fn flip_x(&mut self, x1: f64, x2: f64) {
652        for v in &mut self.vertices {
653            if is_vertex(v.cmd) {
654                v.x = x2 - v.x + x1;
655            }
656        }
657    }
658
659    /// Flip all vertices vertically between y1 and y2.
660    pub fn flip_y(&mut self, y1: f64, y2: f64) {
661        for v in &mut self.vertices {
662            if is_vertex(v.cmd) {
663                v.y = y2 - v.y + y1;
664            }
665        }
666    }
667
668    /// Translate vertices starting from `path_id` until a stop command.
669    pub fn translate(&mut self, dx: f64, dy: f64, path_id: usize) {
670        let total = self.vertices.len();
671        let mut idx = path_id;
672        while idx < total {
673            let cmd = self.vertices[idx].cmd;
674            if is_stop(cmd) {
675                break;
676            }
677            if is_vertex(cmd) {
678                self.vertices[idx].x += dx;
679                self.vertices[idx].y += dy;
680            }
681            idx += 1;
682        }
683    }
684
685    /// Translate all vertices in all paths.
686    pub fn translate_all_paths(&mut self, dx: f64, dy: f64) {
687        for v in &mut self.vertices {
688            if is_vertex(v.cmd) {
689                v.x += dx;
690                v.y += dy;
691            }
692        }
693    }
694
695    /// Transform vertices starting from `path_id` using a closure.
696    pub fn transform<F: Fn(f64, f64) -> (f64, f64)>(&mut self, trans: &F, path_id: usize) {
697        let total = self.vertices.len();
698        let mut idx = path_id;
699        while idx < total {
700            let cmd = self.vertices[idx].cmd;
701            if is_stop(cmd) {
702                break;
703            }
704            if is_vertex(cmd) {
705                let (nx, ny) = trans(self.vertices[idx].x, self.vertices[idx].y);
706                self.vertices[idx].x = nx;
707                self.vertices[idx].y = ny;
708            }
709            idx += 1;
710        }
711    }
712
713    /// Transform all vertices in all paths using a closure.
714    pub fn transform_all_paths<F: Fn(f64, f64) -> (f64, f64)>(&mut self, trans: &F) {
715        for v in &mut self.vertices {
716            if is_vertex(v.cmd) {
717                let (nx, ny) = trans(v.x, v.y);
718                v.x = nx;
719                v.y = ny;
720            }
721        }
722    }
723
724    /// Align a single path so that nearly-equal start/end points become exact.
725    /// Returns the index past the end of this path.
726    pub fn align_path(&mut self, idx: usize) -> usize {
727        let total = self.total_vertices();
728        let mut idx = idx;
729
730        if idx >= total || !is_move_to(self.command(idx)) {
731            return total;
732        }
733
734        let mut start_x = 0.0;
735        let mut start_y = 0.0;
736        while idx < total && is_move_to(self.command(idx)) {
737            self.vertex_idx(idx, &mut start_x, &mut start_y);
738            idx += 1;
739        }
740        while idx < total && is_drawing(self.command(idx)) {
741            idx += 1;
742        }
743
744        let mut x = 0.0;
745        let mut y = 0.0;
746        if is_drawing(self.vertex_idx(idx - 1, &mut x, &mut y))
747            && is_equal_eps(x, start_x, 1e-8)
748            && is_equal_eps(y, start_y, 1e-8)
749        {
750            self.modify_vertex(idx - 1, start_x, start_y);
751        }
752
753        while idx < total && !is_move_to(self.command(idx)) {
754            idx += 1;
755        }
756        idx
757    }
758
759    /// Align all paths.
760    pub fn align_all_paths(&mut self) {
761        let mut i = 0;
762        while i < self.total_vertices() {
763            i = self.align_path(i);
764        }
765    }
766}
767
768impl Default for PathStorage {
769    fn default() -> Self {
770        Self::new()
771    }
772}
773
774impl VertexSource for PathStorage {
775    fn rewind(&mut self, path_id: u32) {
776        self.iterator = path_id as usize;
777    }
778
779    fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
780        if self.iterator >= self.vertices.len() {
781            return PATH_CMD_STOP;
782        }
783        let v = &self.vertices[self.iterator];
784        *x = v.x;
785        *y = v.y;
786        self.iterator += 1;
787        v.cmd
788    }
789}
790
791// ===================================================================
792// Adaptors
793// ===================================================================
794
795/// Adaptor that wraps a flat slice of coordinates `[x0, y0, x1, y1, ...]`
796/// as a `VertexSource`.
797///
798/// Port of C++ `agg::poly_plain_adaptor<double>`.
799pub struct PolyPlainAdaptor<'a> {
800    data: &'a [f64],
801    index: usize,
802    closed: bool,
803    stop: bool,
804}
805
806impl<'a> PolyPlainAdaptor<'a> {
807    /// Create a new adaptor. `data` must contain pairs of (x, y) coordinates.
808    pub fn new(data: &'a [f64], closed: bool) -> Self {
809        Self {
810            data,
811            index: 0,
812            closed,
813            stop: false,
814        }
815    }
816}
817
818impl VertexSource for PolyPlainAdaptor<'_> {
819    fn rewind(&mut self, _path_id: u32) {
820        self.index = 0;
821        self.stop = false;
822    }
823
824    fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
825        if self.index + 1 < self.data.len() {
826            let first = self.index == 0;
827            *x = self.data[self.index];
828            *y = self.data[self.index + 1];
829            self.index += 2;
830            return if first {
831                PATH_CMD_MOVE_TO
832            } else {
833                PATH_CMD_LINE_TO
834            };
835        }
836        *x = 0.0;
837        *y = 0.0;
838        if self.closed && !self.stop {
839            self.stop = true;
840            return PATH_CMD_END_POLY | PATH_FLAGS_CLOSE;
841        }
842        PATH_CMD_STOP
843    }
844}
845
846/// Adaptor for a single line segment as a `VertexSource`.
847///
848/// Port of C++ `agg::line_adaptor`.
849pub struct LineAdaptor {
850    coords: [f64; 4],
851    index: usize,
852}
853
854impl LineAdaptor {
855    /// Create a new line adaptor.
856    pub fn new(x1: f64, y1: f64, x2: f64, y2: f64) -> Self {
857        Self {
858            coords: [x1, y1, x2, y2],
859            index: 0,
860        }
861    }
862
863    /// Re-initialize with new coordinates.
864    pub fn init(&mut self, x1: f64, y1: f64, x2: f64, y2: f64) {
865        self.coords = [x1, y1, x2, y2];
866        self.index = 0;
867    }
868}
869
870impl VertexSource for LineAdaptor {
871    fn rewind(&mut self, _path_id: u32) {
872        self.index = 0;
873    }
874
875    fn vertex(&mut self, x: &mut f64, y: &mut f64) -> u32 {
876        if self.index < 4 {
877            let first = self.index == 0;
878            *x = self.coords[self.index];
879            *y = self.coords[self.index + 1];
880            self.index += 2;
881            return if first {
882                PATH_CMD_MOVE_TO
883            } else {
884                PATH_CMD_LINE_TO
885            };
886        }
887        *x = 0.0;
888        *y = 0.0;
889        PATH_CMD_STOP
890    }
891}
892
893#[cfg(test)]
894mod tests {
895    use super::*;
896    use crate::basics::is_close;
897
898    #[test]
899    fn test_new_empty() {
900        let ps = PathStorage::new();
901        assert_eq!(ps.total_vertices(), 0);
902        assert_eq!(ps.last_command(), PATH_CMD_STOP);
903        assert_eq!(ps.last_x(), 0.0);
904        assert_eq!(ps.last_y(), 0.0);
905    }
906
907    #[test]
908    fn test_move_to_line_to() {
909        let mut ps = PathStorage::new();
910        ps.move_to(10.0, 20.0);
911        ps.line_to(30.0, 40.0);
912        ps.line_to(50.0, 60.0);
913
914        assert_eq!(ps.total_vertices(), 3);
915        assert_eq!(ps.command(0), PATH_CMD_MOVE_TO);
916        assert_eq!(ps.command(1), PATH_CMD_LINE_TO);
917        assert_eq!(ps.command(2), PATH_CMD_LINE_TO);
918
919        let (mut x, mut y) = (0.0, 0.0);
920        ps.vertex_idx(1, &mut x, &mut y);
921        assert!((x - 30.0).abs() < 1e-10);
922        assert!((y - 40.0).abs() < 1e-10);
923    }
924
925    #[test]
926    fn test_relative_commands() {
927        let mut ps = PathStorage::new();
928        ps.move_to(10.0, 20.0);
929        ps.line_rel(5.0, 5.0);
930
931        let (mut x, mut y) = (0.0, 0.0);
932        ps.vertex_idx(1, &mut x, &mut y);
933        assert!((x - 15.0).abs() < 1e-10);
934        assert!((y - 25.0).abs() < 1e-10);
935    }
936
937    #[test]
938    fn test_hline_vline() {
939        let mut ps = PathStorage::new();
940        ps.move_to(10.0, 20.0);
941        ps.hline_to(50.0);
942        ps.vline_to(80.0);
943
944        let (mut x, mut y) = (0.0, 0.0);
945        ps.vertex_idx(1, &mut x, &mut y);
946        assert!((x - 50.0).abs() < 1e-10);
947        assert!((y - 20.0).abs() < 1e-10);
948
949        ps.vertex_idx(2, &mut x, &mut y);
950        assert!((x - 50.0).abs() < 1e-10);
951        assert!((y - 80.0).abs() < 1e-10);
952    }
953
954    #[test]
955    fn test_hline_rel_vline_rel() {
956        let mut ps = PathStorage::new();
957        ps.move_to(10.0, 20.0);
958        ps.hline_rel(5.0);
959        ps.vline_rel(10.0);
960
961        let (mut x, mut y) = (0.0, 0.0);
962        ps.vertex_idx(1, &mut x, &mut y);
963        assert!((x - 15.0).abs() < 1e-10);
964        assert!((y - 20.0).abs() < 1e-10);
965
966        ps.vertex_idx(2, &mut x, &mut y);
967        assert!((x - 15.0).abs() < 1e-10);
968        assert!((y - 30.0).abs() < 1e-10);
969    }
970
971    #[test]
972    fn test_curve3() {
973        let mut ps = PathStorage::new();
974        ps.move_to(0.0, 0.0);
975        ps.curve3(50.0, 100.0, 100.0, 0.0);
976
977        assert_eq!(ps.total_vertices(), 3);
978        assert_eq!(ps.command(1), PATH_CMD_CURVE3);
979        assert_eq!(ps.command(2), PATH_CMD_CURVE3);
980    }
981
982    #[test]
983    fn test_curve4() {
984        let mut ps = PathStorage::new();
985        ps.move_to(0.0, 0.0);
986        ps.curve4(25.0, 100.0, 75.0, 100.0, 100.0, 0.0);
987
988        assert_eq!(ps.total_vertices(), 4);
989        assert_eq!(ps.command(1), PATH_CMD_CURVE4);
990        assert_eq!(ps.command(2), PATH_CMD_CURVE4);
991        assert_eq!(ps.command(3), PATH_CMD_CURVE4);
992    }
993
994    #[test]
995    fn test_close_polygon() {
996        let mut ps = PathStorage::new();
997        ps.move_to(0.0, 0.0);
998        ps.line_to(100.0, 0.0);
999        ps.line_to(100.0, 100.0);
1000        ps.close_polygon(PATH_FLAGS_NONE);
1001
1002        assert_eq!(ps.total_vertices(), 4);
1003        assert!(is_end_poly(ps.command(3)));
1004        assert!(is_close(ps.command(3)));
1005    }
1006
1007    #[test]
1008    fn test_vertex_source_iteration() {
1009        let mut ps = PathStorage::new();
1010        ps.move_to(10.0, 20.0);
1011        ps.line_to(30.0, 40.0);
1012
1013        ps.rewind(0);
1014        let (mut x, mut y) = (0.0, 0.0);
1015
1016        let cmd = ps.vertex(&mut x, &mut y);
1017        assert_eq!(cmd, PATH_CMD_MOVE_TO);
1018        assert!((x - 10.0).abs() < 1e-10);
1019
1020        let cmd = ps.vertex(&mut x, &mut y);
1021        assert_eq!(cmd, PATH_CMD_LINE_TO);
1022        assert!((x - 30.0).abs() < 1e-10);
1023
1024        let cmd = ps.vertex(&mut x, &mut y);
1025        assert_eq!(cmd, PATH_CMD_STOP);
1026    }
1027
1028    #[test]
1029    fn test_start_new_path() {
1030        let mut ps = PathStorage::new();
1031        ps.move_to(0.0, 0.0);
1032        ps.line_to(10.0, 10.0);
1033
1034        let id = ps.start_new_path();
1035        assert_eq!(id, 3); // 2 vertices + 1 stop
1036
1037        ps.move_to(50.0, 50.0);
1038        ps.line_to(60.0, 60.0);
1039
1040        // Iterate second path
1041        ps.rewind(id as u32);
1042        let (mut x, mut y) = (0.0, 0.0);
1043        let cmd = ps.vertex(&mut x, &mut y);
1044        assert_eq!(cmd, PATH_CMD_MOVE_TO);
1045        assert!((x - 50.0).abs() < 1e-10);
1046    }
1047
1048    #[test]
1049    fn test_modify_vertex() {
1050        let mut ps = PathStorage::new();
1051        ps.move_to(10.0, 20.0);
1052        ps.modify_vertex(0, 30.0, 40.0);
1053
1054        let (mut x, mut y) = (0.0, 0.0);
1055        ps.vertex_idx(0, &mut x, &mut y);
1056        assert!((x - 30.0).abs() < 1e-10);
1057        assert!((y - 40.0).abs() < 1e-10);
1058    }
1059
1060    #[test]
1061    fn test_swap_vertices() {
1062        let mut ps = PathStorage::new();
1063        ps.move_to(10.0, 20.0);
1064        ps.line_to(30.0, 40.0);
1065
1066        ps.swap_vertices(0, 1);
1067
1068        let (mut x, mut y) = (0.0, 0.0);
1069        ps.vertex_idx(0, &mut x, &mut y);
1070        assert!((x - 30.0).abs() < 1e-10);
1071        assert_eq!(ps.command(0), PATH_CMD_LINE_TO);
1072
1073        ps.vertex_idx(1, &mut x, &mut y);
1074        assert!((x - 10.0).abs() < 1e-10);
1075        assert_eq!(ps.command(1), PATH_CMD_MOVE_TO);
1076    }
1077
1078    #[test]
1079    fn test_flip_x() {
1080        let mut ps = PathStorage::new();
1081        ps.move_to(10.0, 20.0);
1082        ps.line_to(30.0, 40.0);
1083        ps.flip_x(0.0, 100.0);
1084
1085        let (mut x, mut y) = (0.0, 0.0);
1086        ps.vertex_idx(0, &mut x, &mut y);
1087        assert!((x - 90.0).abs() < 1e-10);
1088        assert!((y - 20.0).abs() < 1e-10);
1089
1090        ps.vertex_idx(1, &mut x, &mut y);
1091        assert!((x - 70.0).abs() < 1e-10);
1092    }
1093
1094    #[test]
1095    fn test_flip_y() {
1096        let mut ps = PathStorage::new();
1097        ps.move_to(10.0, 20.0);
1098        ps.line_to(30.0, 40.0);
1099        ps.flip_y(0.0, 100.0);
1100
1101        let (mut x, mut y) = (0.0, 0.0);
1102        ps.vertex_idx(0, &mut x, &mut y);
1103        assert!((x - 10.0).abs() < 1e-10);
1104        assert!((y - 80.0).abs() < 1e-10);
1105    }
1106
1107    #[test]
1108    fn test_translate() {
1109        let mut ps = PathStorage::new();
1110        ps.move_to(10.0, 20.0);
1111        ps.line_to(30.0, 40.0);
1112        ps.translate(5.0, 10.0, 0);
1113
1114        let (mut x, mut y) = (0.0, 0.0);
1115        ps.vertex_idx(0, &mut x, &mut y);
1116        assert!((x - 15.0).abs() < 1e-10);
1117        assert!((y - 30.0).abs() < 1e-10);
1118    }
1119
1120    #[test]
1121    fn test_translate_all_paths() {
1122        let mut ps = PathStorage::new();
1123        ps.move_to(10.0, 20.0);
1124        ps.line_to(30.0, 40.0);
1125        ps.translate_all_paths(100.0, 200.0);
1126
1127        let (mut x, mut y) = (0.0, 0.0);
1128        ps.vertex_idx(0, &mut x, &mut y);
1129        assert!((x - 110.0).abs() < 1e-10);
1130        assert!((y - 220.0).abs() < 1e-10);
1131    }
1132
1133    #[test]
1134    fn test_concat_path() {
1135        let mut ps = PathStorage::new();
1136        ps.move_to(0.0, 0.0);
1137
1138        let mut ps2 = PathStorage::new();
1139        ps2.move_to(10.0, 20.0);
1140        ps2.line_to(30.0, 40.0);
1141
1142        ps.concat_path(&mut ps2, 0);
1143
1144        assert_eq!(ps.total_vertices(), 3);
1145        assert_eq!(ps.command(1), PATH_CMD_MOVE_TO);
1146    }
1147
1148    #[test]
1149    fn test_join_path() {
1150        let mut ps = PathStorage::new();
1151        ps.move_to(0.0, 0.0);
1152        ps.line_to(10.0, 10.0);
1153
1154        let mut ps2 = PathStorage::new();
1155        ps2.move_to(20.0, 20.0);
1156        ps2.line_to(30.0, 30.0);
1157
1158        ps.join_path(&mut ps2, 0);
1159
1160        // move_to(20,20) should become line_to(20,20)
1161        assert_eq!(ps.command(2), PATH_CMD_LINE_TO);
1162        let (mut x, mut y) = (0.0, 0.0);
1163        ps.vertex_idx(2, &mut x, &mut y);
1164        assert!((x - 20.0).abs() < 1e-10);
1165    }
1166
1167    #[test]
1168    fn test_remove_all() {
1169        let mut ps = PathStorage::new();
1170        ps.move_to(10.0, 20.0);
1171        ps.line_to(30.0, 40.0);
1172        ps.remove_all();
1173
1174        assert_eq!(ps.total_vertices(), 0);
1175    }
1176
1177    #[test]
1178    fn test_poly_plain_adaptor() {
1179        let data = [10.0, 20.0, 30.0, 40.0, 50.0, 60.0];
1180        let mut adaptor = PolyPlainAdaptor::new(&data, true);
1181
1182        let (mut x, mut y) = (0.0, 0.0);
1183        let cmd = adaptor.vertex(&mut x, &mut y);
1184        assert_eq!(cmd, PATH_CMD_MOVE_TO);
1185        assert!((x - 10.0).abs() < 1e-10);
1186
1187        let cmd = adaptor.vertex(&mut x, &mut y);
1188        assert_eq!(cmd, PATH_CMD_LINE_TO);
1189        assert!((x - 30.0).abs() < 1e-10);
1190
1191        let cmd = adaptor.vertex(&mut x, &mut y);
1192        assert_eq!(cmd, PATH_CMD_LINE_TO);
1193        assert!((x - 50.0).abs() < 1e-10);
1194
1195        let cmd = adaptor.vertex(&mut x, &mut y);
1196        assert!(is_end_poly(cmd));
1197        assert!(is_close(cmd));
1198
1199        let cmd = adaptor.vertex(&mut x, &mut y);
1200        assert_eq!(cmd, PATH_CMD_STOP);
1201    }
1202
1203    #[test]
1204    fn test_poly_plain_adaptor_open() {
1205        let data = [10.0, 20.0, 30.0, 40.0];
1206        let mut adaptor = PolyPlainAdaptor::new(&data, false);
1207
1208        let (mut x, mut y) = (0.0, 0.0);
1209        adaptor.vertex(&mut x, &mut y); // move_to
1210        adaptor.vertex(&mut x, &mut y); // line_to
1211
1212        let cmd = adaptor.vertex(&mut x, &mut y);
1213        assert_eq!(cmd, PATH_CMD_STOP); // no close
1214    }
1215
1216    #[test]
1217    fn test_line_adaptor() {
1218        let mut la = LineAdaptor::new(10.0, 20.0, 30.0, 40.0);
1219        let (mut x, mut y) = (0.0, 0.0);
1220
1221        let cmd = la.vertex(&mut x, &mut y);
1222        assert_eq!(cmd, PATH_CMD_MOVE_TO);
1223        assert!((x - 10.0).abs() < 1e-10);
1224        assert!((y - 20.0).abs() < 1e-10);
1225
1226        let cmd = la.vertex(&mut x, &mut y);
1227        assert_eq!(cmd, PATH_CMD_LINE_TO);
1228        assert!((x - 30.0).abs() < 1e-10);
1229        assert!((y - 40.0).abs() < 1e-10);
1230
1231        let cmd = la.vertex(&mut x, &mut y);
1232        assert_eq!(cmd, PATH_CMD_STOP);
1233    }
1234
1235    #[test]
1236    fn test_line_adaptor_rewind() {
1237        let mut la = LineAdaptor::new(10.0, 20.0, 30.0, 40.0);
1238        let (mut x, mut y) = (0.0, 0.0);
1239
1240        la.vertex(&mut x, &mut y);
1241        la.vertex(&mut x, &mut y);
1242        la.rewind(0);
1243
1244        let cmd = la.vertex(&mut x, &mut y);
1245        assert_eq!(cmd, PATH_CMD_MOVE_TO);
1246    }
1247
1248    #[test]
1249    fn test_curve3_smooth() {
1250        let mut ps = PathStorage::new();
1251        ps.move_to(0.0, 0.0);
1252        ps.curve3(50.0, 100.0, 100.0, 0.0);
1253        ps.curve3_smooth(200.0, 0.0);
1254
1255        // Reflected control point: (100,0) + (100,0) - (50,100) = (150, -100)
1256        assert_eq!(ps.total_vertices(), 5);
1257        let (mut x, mut y) = (0.0, 0.0);
1258        ps.vertex_idx(3, &mut x, &mut y);
1259        assert!((x - 150.0).abs() < 1e-10);
1260        assert!((y - (-100.0)).abs() < 1e-10);
1261    }
1262
1263    #[test]
1264    fn test_curve4_smooth() {
1265        let mut ps = PathStorage::new();
1266        ps.move_to(0.0, 0.0);
1267        ps.curve4(25.0, 100.0, 75.0, 100.0, 100.0, 0.0);
1268        ps.curve4_smooth(175.0, 100.0, 200.0, 0.0);
1269
1270        // Reflected ctrl1: (100,0) + (100,0) - (75,100) = (125, -100)
1271        assert_eq!(ps.total_vertices(), 7);
1272        let (mut x, mut y) = (0.0, 0.0);
1273        ps.vertex_idx(4, &mut x, &mut y);
1274        assert!((x - 125.0).abs() < 1e-10);
1275        assert!((y - (-100.0)).abs() < 1e-10);
1276    }
1277
1278    #[test]
1279    fn test_invert_polygon() {
1280        let mut ps = PathStorage::new();
1281        ps.move_to(0.0, 0.0);
1282        ps.line_to(100.0, 0.0);
1283        ps.line_to(100.0, 100.0);
1284
1285        ps.invert_polygon(0);
1286
1287        // After inversion, vertices are reversed with commands shifted
1288        let (mut x, mut y) = (0.0, 0.0);
1289        ps.vertex_idx(0, &mut x, &mut y);
1290        assert!((x - 100.0).abs() < 1e-10);
1291        assert!((y - 100.0).abs() < 1e-10);
1292    }
1293
1294    #[test]
1295    fn test_perceive_orientation_ccw() {
1296        let mut ps = PathStorage::new();
1297        // CCW triangle
1298        ps.move_to(0.0, 0.0);
1299        ps.line_to(100.0, 0.0);
1300        ps.line_to(100.0, 100.0);
1301
1302        let ori = ps.perceive_polygon_orientation(0, 3);
1303        assert_eq!(ori, PATH_FLAGS_CCW);
1304    }
1305
1306    #[test]
1307    fn test_perceive_orientation_cw() {
1308        let mut ps = PathStorage::new();
1309        // CW triangle
1310        ps.move_to(0.0, 0.0);
1311        ps.line_to(100.0, 100.0);
1312        ps.line_to(100.0, 0.0);
1313
1314        let ori = ps.perceive_polygon_orientation(0, 3);
1315        assert_eq!(ori, PATH_FLAGS_CW);
1316    }
1317
1318    #[test]
1319    fn test_arrange_polygon_orientation() {
1320        let mut ps = PathStorage::new();
1321        // CW triangle
1322        ps.move_to(0.0, 0.0);
1323        ps.line_to(100.0, 100.0);
1324        ps.line_to(100.0, 0.0);
1325
1326        // Force CCW
1327        ps.arrange_polygon_orientation(0, PATH_FLAGS_CCW);
1328
1329        let ori = ps.perceive_polygon_orientation(0, 3);
1330        assert_eq!(ori, PATH_FLAGS_CCW);
1331    }
1332
1333    #[test]
1334    fn test_concat_poly() {
1335        let mut ps = PathStorage::new();
1336        let coords = [0.0, 0.0, 100.0, 0.0, 100.0, 100.0];
1337        ps.concat_poly(&coords, true);
1338
1339        assert_eq!(ps.total_vertices(), 4); // 3 vertices + end_poly
1340        assert_eq!(ps.command(0), PATH_CMD_MOVE_TO);
1341        assert_eq!(ps.command(1), PATH_CMD_LINE_TO);
1342        assert_eq!(ps.command(2), PATH_CMD_LINE_TO);
1343        assert!(is_end_poly(ps.command(3)));
1344    }
1345
1346    #[test]
1347    fn test_transform_all_paths() {
1348        let mut ps = PathStorage::new();
1349        ps.move_to(10.0, 20.0);
1350        ps.line_to(30.0, 40.0);
1351
1352        ps.transform_all_paths(&|x, y| (x * 2.0, y * 3.0));
1353
1354        let (mut x, mut y) = (0.0, 0.0);
1355        ps.vertex_idx(0, &mut x, &mut y);
1356        assert!((x - 20.0).abs() < 1e-10);
1357        assert!((y - 60.0).abs() < 1e-10);
1358
1359        ps.vertex_idx(1, &mut x, &mut y);
1360        assert!((x - 60.0).abs() < 1e-10);
1361        assert!((y - 120.0).abs() < 1e-10);
1362    }
1363
1364    #[test]
1365    fn test_default() {
1366        let ps = PathStorage::default();
1367        assert_eq!(ps.total_vertices(), 0);
1368    }
1369
1370    #[test]
1371    fn test_move_rel_from_empty() {
1372        let mut ps = PathStorage::new();
1373        // When empty, rel_to_abs doesn't add anything (no prior vertex)
1374        ps.move_rel(10.0, 20.0);
1375        let (mut x, mut y) = (0.0, 0.0);
1376        ps.vertex_idx(0, &mut x, &mut y);
1377        assert!((x - 10.0).abs() < 1e-10);
1378        assert!((y - 20.0).abs() < 1e-10);
1379    }
1380
1381    #[test]
1382    fn test_end_poly_only_after_vertex() {
1383        let mut ps = PathStorage::new();
1384        // end_poly on empty should do nothing
1385        ps.end_poly(PATH_FLAGS_CLOSE);
1386        assert_eq!(ps.total_vertices(), 0);
1387
1388        ps.move_to(0.0, 0.0);
1389        ps.end_poly(PATH_FLAGS_CLOSE);
1390        assert_eq!(ps.total_vertices(), 2);
1391    }
1392
1393    #[test]
1394    fn test_arc_to() {
1395        let mut ps = PathStorage::new();
1396        ps.move_to(0.0, 0.0);
1397        // Small arc — should add several vertices via BezierArcSvg
1398        ps.arc_to(50.0, 50.0, 0.0, false, true, 100.0, 0.0);
1399
1400        assert!(ps.total_vertices() > 2);
1401    }
1402
1403    #[test]
1404    fn test_arc_to_no_prior_vertex() {
1405        let mut ps = PathStorage::new();
1406        // No prior vertex → should become move_to
1407        ps.arc_to(50.0, 50.0, 0.0, false, true, 100.0, 0.0);
1408        assert_eq!(ps.command(0), PATH_CMD_MOVE_TO);
1409    }
1410
1411    #[test]
1412    fn test_last_prev_vertex() {
1413        let mut ps = PathStorage::new();
1414        let (mut x, mut y) = (0.0, 0.0);
1415        assert_eq!(ps.last_vertex_xy(&mut x, &mut y), PATH_CMD_STOP);
1416        assert_eq!(ps.prev_vertex_xy(&mut x, &mut y), PATH_CMD_STOP);
1417
1418        ps.move_to(10.0, 20.0);
1419        ps.line_to(30.0, 40.0);
1420
1421        let cmd = ps.last_vertex_xy(&mut x, &mut y);
1422        assert_eq!(cmd, PATH_CMD_LINE_TO);
1423        assert!((x - 30.0).abs() < 1e-10);
1424
1425        let cmd = ps.prev_vertex_xy(&mut x, &mut y);
1426        assert_eq!(cmd, PATH_CMD_MOVE_TO);
1427        assert!((x - 10.0).abs() < 1e-10);
1428    }
1429
1430    #[test]
1431    fn test_vertices_accessor() {
1432        let mut ps = PathStorage::new();
1433        ps.move_to(1.0, 2.0);
1434        ps.line_to(3.0, 4.0);
1435        ps.line_to(5.0, 6.0);
1436
1437        let verts = ps.vertices();
1438        assert_eq!(verts.len(), 3);
1439        assert!((verts[0].x - 1.0).abs() < 1e-10);
1440        assert!((verts[0].y - 2.0).abs() < 1e-10);
1441        assert_eq!(verts[0].cmd, PATH_CMD_MOVE_TO);
1442        assert!((verts[1].x - 3.0).abs() < 1e-10);
1443        assert!((verts[2].x - 5.0).abs() < 1e-10);
1444        assert_eq!(verts[2].cmd, PATH_CMD_LINE_TO);
1445    }
1446}