1use std::f64::consts::FRAC_PI_2;
7use std::ops::Range;
8
9use kurbo::{Affine, CubicBez, ParamCurve, ParamCurveArclen, ParamCurveExtrema, Point, Rect};
10
11const ARCLEN_ACCURACY: f64 = 1e-6;
12
13#[derive(Debug, Clone, PartialEq)]
15pub struct SubPath {
16 pub segments: Vec<CubicBez>,
18 pub closed: bool,
20}
21
22#[derive(Debug, Clone, PartialEq, Default)]
24pub struct VPath {
25 pub subpaths: Vec<SubPath>,
27}
28
29pub fn line_segment(a: Point, b: Point) -> CubicBez {
31 CubicBez::new(a, a.lerp(b, 1.0 / 3.0), a.lerp(b, 2.0 / 3.0), b)
32}
33
34impl SubPath {
35 pub fn polyline(points: &[Point], closed: bool) -> Self {
37 let mut segments: Vec<_> = points
38 .windows(2)
39 .map(|w| line_segment(w[0], w[1]))
40 .collect();
41 if closed && points.len() > 2 {
42 segments.push(line_segment(points[points.len() - 1], points[0]));
43 }
44 Self { segments, closed }
45 }
46
47 pub fn centroid(&self) -> Point {
49 let n = self.segments.len().max(1) as f64;
50 let sum = self
51 .segments
52 .iter()
53 .fold(Point::ORIGIN, |acc, s| acc + s.p0.to_vec2());
54 Point::new(sum.x / n, sum.y / n)
55 }
56
57 fn collapsed(&self) -> Self {
59 let c = self.centroid();
60 let seg = CubicBez::new(c, c, c, c);
61 Self {
62 segments: vec![seg; self.segments.len()],
63 closed: self.closed,
64 }
65 }
66}
67
68impl VPath {
69 pub fn arc(radius: f64, start: f64, sweep: f64) -> Self {
71 let n = (sweep.abs() / FRAC_PI_2).ceil().max(1.0) as usize;
72 let step = sweep / n as f64;
73 let k = 4.0 / 3.0 * (step / 4.0).tan() * radius;
74 let at = |th: f64| Point::new(radius * th.cos(), radius * th.sin());
75 let segments = (0..n)
76 .map(|i| {
77 let (a, b) = (start + step * i as f64, start + step * (i + 1) as f64);
78 let (p0, p3) = (at(a), at(b));
79 let t0 = kurbo::Vec2::new(-a.sin(), a.cos()) * k;
80 let t1 = kurbo::Vec2::new(-b.sin(), b.cos()) * k;
81 CubicBez::new(p0, p0 + t0, p3 - t1, p3)
82 })
83 .collect();
84 let closed = (sweep.abs() - std::f64::consts::TAU).abs() < 1e-9;
85 Self {
86 subpaths: vec![SubPath { segments, closed }],
87 }
88 }
89
90 pub fn polyline(points: &[Point], closed: bool) -> Self {
92 Self {
93 subpaths: vec![SubPath::polyline(points, closed)],
94 }
95 }
96
97 pub fn transform(&self, a: Affine) -> Self {
99 let subpaths = self
100 .subpaths
101 .iter()
102 .map(|sp| SubPath {
103 segments: sp.segments.iter().map(|s| a * *s).collect(),
104 closed: sp.closed,
105 })
106 .collect();
107 Self { subpaths }
108 }
109
110 pub fn bbox(&self) -> Option<Rect> {
112 self.subpaths
113 .iter()
114 .flat_map(|sp| &sp.segments)
115 .map(|s| s.bounding_box())
116 .reduce(|a, b| a.union(b))
117 }
118
119 pub fn center(&self) -> Point {
121 self.bbox().map_or(Point::ORIGIN, |r| r.center())
122 }
123
124 pub fn trim(&self, range: Range<f32>) -> Self {
127 if range.start <= 0.0 && range.end >= 1.0 {
128 return self.clone();
129 }
130 let lens: Vec<Vec<f64>> = self
131 .subpaths
132 .iter()
133 .map(|sp| {
134 sp.segments
135 .iter()
136 .map(|s| s.arclen(ARCLEN_ACCURACY))
137 .collect()
138 })
139 .collect();
140 let total: f64 = lens.iter().flatten().sum();
141 let (from, to) = (f64::from(range.start) * total, f64::from(range.end) * total);
142 let mut acc = 0.0;
143 let mut subpaths = Vec::new();
144 for (sp, lens) in self.subpaths.iter().zip(&lens) {
145 let mut segments = Vec::new();
146 for (seg, &len) in sp.segments.iter().zip(lens) {
147 let (l0, l1) = (acc, acc + len);
148 acc = l1;
149 if len == 0.0 || l1 <= from || l0 >= to {
150 continue;
151 }
152 let t0 = if from > l0 {
153 seg.inv_arclen(from - l0, ARCLEN_ACCURACY)
154 } else {
155 0.0
156 };
157 let t1 = if to < l1 {
158 seg.inv_arclen(to - l0, ARCLEN_ACCURACY)
159 } else {
160 1.0
161 };
162 segments.push(seg.subsegment(t0..t1));
163 }
164 if !segments.is_empty() {
165 subpaths.push(SubPath {
166 segments,
167 closed: false,
168 });
169 }
170 }
171 Self { subpaths }
172 }
173}
174
175pub fn align(a: &VPath, b: &VPath) -> (VPath, VPath) {
182 let n = a.subpaths.len().max(b.subpaths.len());
183 let (mut out_a, mut out_b) = (VPath::default(), VPath::default());
184 for i in 0..n {
185 let (sa, sb) = match (a.subpaths.get(i), b.subpaths.get(i)) {
186 (Some(x), Some(y)) => (x.clone(), y.clone()),
187 (Some(x), None) => (x.clone(), x.collapsed()),
188 (None, Some(y)) => (y.collapsed(), y.clone()),
189 (None, None) => unreachable!(),
190 };
191 let (sa, sb) = align_subpaths(sa, sb);
192 out_a.subpaths.push(sa);
193 out_b.subpaths.push(sb);
194 }
195 (out_a, out_b)
196}
197
198fn align_subpaths(mut a: SubPath, mut b: SubPath) -> (SubPath, SubPath) {
199 if a.segments.is_empty() {
200 a = SubPath {
201 closed: a.closed,
202 ..b.collapsed()
203 };
204 } else if b.segments.is_empty() {
205 b = SubPath {
206 closed: b.closed,
207 ..a.collapsed()
208 };
209 }
210 let n = a.segments.len().max(b.segments.len());
211 subdivide_to(&mut a.segments, n);
212 subdivide_to(&mut b.segments, n);
213 if a.closed && b.closed {
214 let travel = |r: usize| -> f64 {
215 (0..n)
216 .map(|i| (a.segments[(i + r) % n].p0 - b.segments[i].p0).hypot2())
217 .sum()
218 };
219 let best = (0..n)
220 .min_by(|&x, &y| travel(x).total_cmp(&travel(y)))
221 .unwrap_or(0);
222 a.segments.rotate_left(best);
223 }
224 (a, b)
225}
226
227fn subdivide_to(segs: &mut Vec<CubicBez>, n: usize) {
229 while segs.len() < n {
230 let lens: Vec<f64> = segs.iter().map(|s| s.arclen(ARCLEN_ACCURACY)).collect();
231 let i = (0..segs.len())
232 .max_by(|&x, &y| lens[x].total_cmp(&lens[y]).then(y.cmp(&x)))
233 .unwrap();
234 let (l, r) = segs[i].subdivide();
235 segs[i] = l;
236 segs.insert(i + 1, r);
237 }
238}