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