1use crate::arclen;
4use kurbo::{Affine, BezPath, ParamCurve, PathEl, PathSeg, Point};
5
6#[derive(Clone, Debug, PartialEq)]
9pub struct Subpath {
10 pub segments: Vec<PathSeg>,
12 pub closed: bool,
14 pub start: Point,
16}
17
18#[must_use]
20pub fn subpaths(path: &BezPath) -> Vec<Subpath> {
21 let mut found: Vec<Subpath> = Vec::new();
22 let mut current: Option<Subpath> = None;
23 let mut last = Point::ZERO;
24 for element in path.elements() {
25 match *element {
26 PathEl::MoveTo(p) => {
27 found.extend(current.take());
28 current = Some(Subpath {
29 segments: Vec::new(),
30 closed: false,
31 start: p,
32 });
33 last = p;
34 }
35 PathEl::LineTo(p) => {
36 let subpath = current.get_or_insert_with(|| open_at(last));
37 subpath
38 .segments
39 .push(PathSeg::Line(kurbo::Line::new(last, p)));
40 last = p;
41 }
42 PathEl::QuadTo(a, b) => {
43 let subpath = current.get_or_insert_with(|| open_at(last));
44 subpath
45 .segments
46 .push(PathSeg::Quad(kurbo::QuadBez::new(last, a, b)));
47 last = b;
48 }
49 PathEl::CurveTo(a, b, c) => {
50 let subpath = current.get_or_insert_with(|| open_at(last));
51 subpath
52 .segments
53 .push(PathSeg::Cubic(kurbo::CubicBez::new(last, a, b, c)));
54 last = c;
55 }
56 PathEl::ClosePath => {
57 if let Some(mut subpath) = current.take() {
58 if last != subpath.start {
59 subpath
60 .segments
61 .push(PathSeg::Line(kurbo::Line::new(last, subpath.start)));
62 }
63 subpath.closed = true;
64 last = subpath.start;
65 found.push(subpath);
66 }
67 }
68 }
69 }
70 found.extend(current);
71 found
72}
73
74fn open_at(point: Point) -> Subpath {
75 Subpath {
76 segments: Vec::new(),
77 closed: false,
78 start: point,
79 }
80}
81
82pub fn append(path: &mut BezPath, subpath: &Subpath) {
84 let Some(first) = subpath.segments.first() else {
85 return;
86 };
87 path.move_to(first.start());
88 for segment in &subpath.segments {
89 match *segment {
90 PathSeg::Line(line) => path.line_to(line.p1),
91 PathSeg::Quad(quad) => path.quad_to(quad.p1, quad.p2),
92 PathSeg::Cubic(cubic) => path.curve_to(cubic.p1, cubic.p2, cubic.p3),
93 }
94 }
95 if subpath.closed {
96 path.close_path();
97 }
98}
99
100#[derive(Copy, Clone, Debug, PartialEq)]
102pub struct TrimSpan {
103 pub start: f64,
105 pub end: f64,
107 pub offset: f64,
109}
110
111impl TrimSpan {
112 fn ranges(self) -> Ranges {
115 if self.start.is_nan() || self.end.is_nan() || self.start >= self.end {
116 return Ranges::None;
117 }
118 if self.end - self.start >= 1.0 {
119 return Ranges::Whole;
120 }
121 let shift = self.offset - self.offset.floor();
122 let mut start = self.start + shift;
123 let mut end = self.end + shift;
124 if start >= 1.0 {
125 start -= 1.0;
126 end -= 1.0;
127 }
128 if end > 1.0 {
129 Ranges::Wrapped(start, end - 1.0)
130 } else {
131 Ranges::One(start, end)
132 }
133 }
134}
135
136enum Ranges {
137 None,
138 Whole,
139 One(f64, f64),
140 Wrapped(f64, f64),
142}
143
144#[derive(Clone, Debug)]
146pub struct Measured {
147 pub subpath: Subpath,
149 pub measure: Affine,
151}
152
153fn segment_lengths(subpath: &Subpath, measure: Affine) -> Vec<f64> {
155 subpath
156 .segments
157 .iter()
158 .map(|segment| arclen::length(&transformed(*segment, measure)))
159 .collect()
160}
161
162fn transformed(segment: PathSeg, affine: Affine) -> PathSeg {
163 affine * segment
164}
165
166const MIN_PIECE_LENGTH: f64 = 1e-6;
171
172fn cut(subpath: &Subpath, lengths: &[f64], measure: Affine, from: f64, to: f64) -> Subpath {
175 let mut segments = Vec::new();
176 let mut run = 0.0;
177 for (segment, &length) in subpath.segments.iter().zip(lengths) {
178 let (start, end) = (run, run + length);
179 run = end;
180 if to.min(end) - from.max(start) < MIN_PIECE_LENGTH {
181 continue;
182 }
183 let measured = transformed(*segment, measure);
184 let t0 = if from > start {
185 arclen::parameter_at(&measured, from - start, length)
186 } else {
187 0.0
188 };
189 let t1 = if to < end {
190 arclen::parameter_at(&measured, to - start, length)
191 } else {
192 1.0
193 };
194 if t1 > t0 {
195 let whole = t0 <= 0.0 && t1 >= 1.0;
196 segments.push(if whole {
197 *segment
198 } else {
199 segment.subsegment(t0..t1)
200 });
201 }
202 }
203 let start = segments.first().map_or(subpath.start, PathSeg::start);
204 Subpath {
205 segments,
206 closed: false,
207 start,
208 }
209}
210
211#[must_use]
216pub fn trim_together(parts: &[Measured], span: TrimSpan) -> Vec<(usize, Subpath)> {
217 let lengths: Vec<Vec<f64>> = parts
218 .iter()
219 .map(|part| segment_lengths(&part.subpath, part.measure))
220 .collect();
221 let totals: Vec<f64> = lengths.iter().map(|list| list.iter().sum()).collect();
222 let total: f64 = totals.iter().sum();
223 let pieces = |from: f64, to: f64| -> Vec<(usize, Subpath)> {
224 let mut kept = Vec::new();
225 let mut offset = 0.0;
226 for (index, part) in parts.iter().enumerate() {
227 let (start, end) = (offset, offset + totals[index]);
228 offset = end;
229 if end <= from || start >= to {
230 continue;
231 }
232 if from <= start && to >= end {
233 kept.push((index, part.subpath.clone()));
234 } else {
235 kept.push((
236 index,
237 cut(
238 &part.subpath,
239 &lengths[index],
240 part.measure,
241 from - start,
242 to - start,
243 ),
244 ));
245 }
246 }
247 kept
248 };
249 match span.ranges() {
250 Ranges::None => Vec::new(),
251 Ranges::Whole => parts
252 .iter()
253 .enumerate()
254 .map(|(index, part)| (index, part.subpath.clone()))
255 .collect(),
256 Ranges::One(start, end) => pieces(start * total, end * total),
257 Ranges::Wrapped(start, end) => {
258 let mut tail = pieces(start * total, total);
259 let head = pieces(0.0, end * total);
260 let joined = parts.len() == 1 && parts[0].subpath.closed;
261 if joined && let (Some((_, last)), Some((_, first))) = (tail.last_mut(), head.first()) {
262 last.segments.extend(first.segments.iter().copied());
263 return tail;
264 }
265 tail.into_iter().chain(head).collect()
266 }
267 }
268}
269
270#[must_use]
273pub fn trim_individually(parts: &[Measured], span: TrimSpan) -> Vec<(usize, Subpath)> {
274 parts
275 .iter()
276 .enumerate()
277 .flat_map(|(index, part)| {
278 trim_together(std::slice::from_ref(part), span)
279 .into_iter()
280 .map(move |(_, piece)| (index, piece))
281 })
282 .collect()
283}
284
285#[must_use]
287pub fn subpath_length(subpath: &Subpath, measure: Affine) -> f64 {
288 segment_lengths(subpath, measure).iter().sum()
289}
290
291#[must_use]
294pub fn point_at_length(subpath: &Subpath, measure: Affine, distance: f64) -> Point {
295 let mut run = 0.0;
296 for segment in &subpath.segments {
297 let measured = transformed(*segment, measure);
298 let length = arclen::length(&measured);
299 if distance <= run + length && length > 0.0 {
300 let t = arclen::parameter_at(&measured, distance - run, length);
301 return segment.eval(t);
302 }
303 run += length;
304 }
305 subpath.segments.last().map_or(subpath.start, PathSeg::end)
306}