path_offset/path/
mod.rs

1use std::{fmt::Display, str::FromStr};
2
3use lyon::path::Event;
4
5use crate::error::PathError;
6
7pub mod conversions;
8pub mod point;
9pub mod subpath;
10
11#[derive(Debug, Clone)]
12pub struct Path {
13    inner: lyon::path::Path,
14}
15
16impl Path {
17    pub fn iter(&self) -> impl Iterator<Item = Path> + '_ {
18        self.into_iter()
19    }
20
21    pub fn is_closed(&self) -> bool {
22        self.inner
23            .iter()
24            .any(|e| matches!(e, Event::End { close: true, .. }))
25    }
26
27    /// 智能地查找并返回代表最外层轮廓的子路径。
28    ///
29    /// 这个方法会优先使用快速的“面积最大”启发式算法。
30    /// 如果该算法无法找到结果,则会回退到更精确但更慢的“几何包含”算法。
31    pub fn find_outer_shell(&self) -> Option<Path> {
32        let subpaths: Vec<Path> = self.iter().collect();
33
34        match subpaths.len() {
35            // 情况一:没有子路径
36            0 => None,
37
38            // 情况二:只有一个子路径,那它自身就是外壳
39            // 我们用 .into_iter().next() 来消耗 Vec 并取出唯一的元素
40            // 这样可以避免克隆(.clone())。
41            1 => subpaths.into_iter().next(),
42
43            // 情况三:有多个子路径,执行我们的“智能”查找逻辑
44            _ => {
45                // 首先尝试快速的面积启发式算法
46                find_shell_by_area(&subpaths)
47                    // 如果面积法没有返回任何结果,则回退到精确的几何包含算法
48                    .or_else(|| find_shell_by_containment(&subpaths))
49            }
50        }
51    }
52
53    fn intersect_with(&self, other: &Path) -> bool {
54        let bbox_a = lyon::algorithms::aabb::bounding_box(self.inner.iter());
55        let bbox_b = lyon::algorithms::aabb::bounding_box(other.inner.iter());
56        bbox_a.intersects(&bbox_b)
57    }
58
59    fn contained_by(&self, other_path: &Path) -> bool {
60        !std::ptr::eq(self, other_path)
61            && self.is_closed()
62            && other_path.is_closed()
63            && self.inner.first_endpoint().map_or(false, |(pt, _)| {
64                lyon::algorithms::hit_test::hit_test_path(
65                    &pt,
66                    &other_path.inner,
67                    lyon::path::FillRule::EvenOdd,
68                    0.1,
69                )
70            })
71    }
72}
73
74impl FromStr for Path {
75    type Err = PathError;
76
77    fn from_str(s: &str) -> Result<Self, Self::Err> {
78        let mut parser = lyon::extra::parser::PathParser::new();
79        let mut builder = lyon::path::Path::builder();
80        let mut src = lyon::extra::parser::Source::new(s.chars());
81
82        parser.parse(
83            &lyon::extra::parser::ParserOptions::DEFAULT,
84            &mut src,
85            &mut builder,
86        )?;
87
88        let path = builder.build();
89        Ok(Path::from(path))
90    }
91}
92
93impl Display for Path {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        let path_slice = self.inner.as_slice();
96
97        for event in path_slice.iter_with_attributes() {
98            match event {
99                Event::Begin { at: (at, _) } => {
100                    write!(f, "M{},{}", at.x, at.y)?;
101                }
102                Event::Line { to: (to, _), .. } => {
103                    write!(f, "L{},{}", to.x, to.y)?;
104                }
105                Event::Quadratic {
106                    ctrl, to: (to, _), ..
107                } => {
108                    write!(f, "Q{},{} {},{}", ctrl.x, ctrl.y, to.x, to.y)?;
109                }
110                Event::Cubic {
111                    ctrl1,
112                    ctrl2,
113                    to: (to, _),
114                    ..
115                } => {
116                    write!(
117                        f,
118                        "C{},{} {},{} {},{}",
119                        ctrl1.x, ctrl1.y, ctrl2.x, ctrl2.y, to.x, to.y
120                    )?;
121                }
122                Event::End { close, .. } => {
123                    if close {
124                        write!(f, "Z")?;
125                    }
126                }
127            }
128        }
129
130        Ok(())
131    }
132}
133
134/// 策略一:通过计算有向面积找到最外层轮廓。
135/// 这是一个快速的启发式算法。
136fn find_shell_by_area(paths: &[Path]) -> Option<Path> {
137    paths
138        .iter()
139        // 只考虑闭合路径,因为只有闭合路径能定义内外
140        .filter(|p| p.is_closed())
141        .max_by(|a, b| {
142            let area_a = lyon::algorithms::area::approximate_signed_area(0.01, a.inner.iter());
143            let area_b = lyon::algorithms::area::approximate_signed_area(0.01, b.inner.iter());
144            // total_cmp 可以处理 f32 的 NaN 和无穷大等特殊情况
145            area_a.total_cmp(&area_b)
146        })
147        .cloned() // 从 &Path 得到 Path
148}
149
150/// 策略二:通过检查几何包含关系找到最外层轮廓。
151/// 这是一个精确但计算成本较高的算法。
152fn find_shell_by_containment(paths: &[Path]) -> Option<Path> {
153    paths
154        .iter()
155        .find(|this_path| {
156            // 寻找一个不被任何其他路径包含的路径
157            !paths.iter().any(|other_path| {
158                // 使用我们之前设计好的辅助方法
159                this_path.intersect_with(other_path) && this_path.contained_by(other_path)
160            })
161        })
162        .cloned()
163}