Skip to main content

smooth_frame/output/
path.rs

1use crate::types::Point;
2
3use super::format::{PathFormatter, SvgPathFormat};
4
5/// 一段 cubic Bezier,包含起点,便于直接映射到底层渲染 API。
6#[derive(Debug, Clone, Copy, PartialEq)]
7pub struct CubicSegment {
8    /// cubic 起点。
9    pub from: Point,
10    /// 第一个控制点。
11    pub ctrl1: Point,
12    /// 第二个控制点。
13    pub ctrl2: Point,
14    /// cubic 终点。
15    pub to: Point,
16}
17
18/// 可直接映射到 SVG Canvas Skia 等 API 的路径命令。
19#[derive(Debug, Clone, Copy, PartialEq)]
20pub enum PathCommand {
21    /// 移动当前点。
22    MoveTo(Point),
23    /// 从当前点绘制直线。
24    LineTo(Point),
25    /// 从当前点绘制 cubic Bezier。
26    CubicTo {
27        /// 第一个控制点。
28        ctrl1: Point,
29        /// 第二个控制点。
30        ctrl2: Point,
31        /// cubic 终点。
32        to: Point,
33    },
34    /// 闭合当前子路径。
35    Close,
36}
37
38/// 平滑路径。
39#[derive(Debug, Clone, PartialEq, Default)]
40pub struct SmoothPath {
41    commands: Vec<PathCommand>,
42}
43
44impl SmoothPath {
45    /// 创建空路径。
46    #[must_use]
47    pub fn new() -> Self {
48        Self::default()
49    }
50
51    /// 返回底层路径命令。
52    #[must_use]
53    pub fn commands(&self) -> &[PathCommand] {
54        &self.commands
55    }
56
57    /// 提取路径中的所有 cubic 段。
58    #[must_use]
59    pub fn cubics(&self) -> Vec<CubicSegment> {
60        let mut cubics = Vec::new();
61        let mut current = None;
62        let mut subpath_start = None;
63
64        for command in &self.commands {
65            match *command {
66                PathCommand::MoveTo(point) => {
67                    current = Some(point);
68                    subpath_start = Some(point);
69                }
70                PathCommand::LineTo(point) => {
71                    current = Some(point);
72                }
73                PathCommand::CubicTo { ctrl1, ctrl2, to } => {
74                    if let Some(from) = current {
75                        cubics.push(CubicSegment {
76                            from,
77                            ctrl1,
78                            ctrl2,
79                            to,
80                        });
81                    }
82                    current = Some(to);
83                }
84                PathCommand::Close => {
85                    current = subpath_start;
86                }
87            }
88        }
89
90        cubics
91    }
92
93    /// 追加 `MoveTo` 命令。
94    pub fn move_to(&mut self, point: Point) {
95        self.commands.push(PathCommand::MoveTo(point));
96    }
97
98    /// 追加 `LineTo` 命令。
99    pub fn line_to(&mut self, point: Point) {
100        self.commands.push(PathCommand::LineTo(point));
101    }
102
103    /// 追加 `CubicTo` 命令。
104    pub fn cubic_to(&mut self, ctrl1: Point, ctrl2: Point, to: Point) {
105        self.commands
106            .push(PathCommand::CubicTo { ctrl1, ctrl2, to });
107    }
108
109    /// 追加闭合路径命令。
110    pub fn close(&mut self) {
111        self.commands.push(PathCommand::Close);
112    }
113
114    /// 使用指定 formatter 导出路径。
115    ///
116    /// 这是输出层的主要扩展点:调用方可以实现 [`PathFormatter`],把同一组
117    /// [`PathCommand`] 转换成 SVG、Godot、Canvas 或自定义函数调用格式。
118    pub fn export_with<F>(&self, formatter: &F) -> F::Output
119    where
120        F: PathFormatter + ?Sized,
121    {
122        formatter.format(self.commands())
123    }
124
125    /// 以默认 6 位小数输出 SVG path data。
126    #[must_use]
127    pub fn to_svg_path(&self) -> String {
128        self.export_with(&SvgPathFormat::default())
129    }
130
131    /// 按指定小数位数输出 SVG path data,最多保留 12 位。
132    #[must_use]
133    pub fn to_svg_path_with_precision(&self, precision: usize) -> String {
134        self.export_with(&SvgPathFormat::new(precision))
135    }
136}