1use sequoia_core::{Point, Rect};
2
3use crate::Transform;
4use crate::flatten::{Contour, flatten};
5use crate::raster;
6use crate::svg_path::parse_svg_path;
7
8#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
9#[non_exhaustive]
10pub enum FillRule {
11 #[default]
12 NonZero,
13
14 EvenOdd,
15}
16
17#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct PathError {
19 pub position: usize,
20
21 pub message: &'static str,
22}
23
24impl std::fmt::Display for PathError {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 write!(f, "{} (position {})", self.message, self.position)
27 }
28}
29
30impl std::error::Error for PathError {}
31
32#[derive(Clone, Copy, Debug, PartialEq)]
33#[non_exhaustive]
34pub enum PathSeg {
35 Move(Point),
36
37 Line(Point),
38
39 Quad(Point, Point),
40
41 Cubic(Point, Point, Point),
42
43 Close,
44}
45
46#[derive(Clone, Copy, Debug, Default, PartialEq)]
47pub struct Radius {
48 pub tl: f32,
49
50 pub tr: f32,
51
52 pub br: f32,
53
54 pub bl: f32,
55}
56
57impl Radius {
58 pub const ZERO: Self = Self {
59 tl: 0.0,
60 tr: 0.0,
61 br: 0.0,
62 bl: 0.0,
63 };
64
65 pub const fn all(r: f32) -> Self {
66 Self {
67 tl: r,
68 tr: r,
69 br: r,
70 bl: r,
71 }
72 }
73
74 pub const fn new(tl: f32, tr: f32, br: f32, bl: f32) -> Self {
75 Self { tl, tr, br, bl }
76 }
77
78 pub fn uniform(&self) -> Option<f32> {
79 if self.tl > 0.0 && self.tl == self.tr && self.tr == self.br && self.br == self.bl {
80 Some(self.tl)
81 } else {
82 None
83 }
84 }
85}
86
87impl From<f32> for Radius {
88 fn from(r: f32) -> Self {
89 Self::all(r)
90 }
91}
92
93impl From<[f32; 4]> for Radius {
94 fn from([tl, tr, br, bl]: [f32; 4]) -> Self {
95 Self { tl, tr, br, bl }
96 }
97}
98
99#[derive(Clone, Copy, Debug, Default, PartialEq)]
100enum Hint {
101 #[default]
102 None,
103 Rect(Rect),
104 RoundRect(Rect, f32),
105 Circle(Point, f32),
106}
107
108#[derive(Clone, Debug, Default, PartialEq)]
109pub struct Path {
110 segs: Vec<PathSeg>,
111 current: Point,
112 start: Point,
113 hint: Hint,
114}
115
116impl Path {
117 pub fn new() -> Self {
118 Self {
119 segs: Vec::new(),
120 current: Point::ZERO,
121 start: Point::ZERO,
122 hint: Hint::None,
123 }
124 }
125
126 pub fn is_empty(&self) -> bool {
127 self.segs.is_empty()
128 }
129
130 pub fn segs(&self) -> &[PathSeg] {
131 &self.segs
132 }
133
134 fn push(&mut self, seg: PathSeg) -> &mut Self {
135 self.hint = Hint::None;
136 self.segs.push(seg);
137 self
138 }
139
140 pub fn move_to(&mut self, p: impl Into<Point>) -> &mut Self {
141 let p = p.into();
142 self.current = p;
143 self.start = p;
144 self.push(PathSeg::Move(p))
145 }
146
147 pub fn line_to(&mut self, p: impl Into<Point>) -> &mut Self {
148 let p = p.into();
149 self.current = p;
150 self.push(PathSeg::Line(p))
151 }
152
153 pub fn quad_to(&mut self, c: impl Into<Point>, p: impl Into<Point>) -> &mut Self {
154 let (c, p) = (c.into(), p.into());
155 self.current = p;
156 self.push(PathSeg::Quad(c, p))
157 }
158
159 pub fn cubic_to(
160 &mut self,
161 c1: impl Into<Point>,
162 c2: impl Into<Point>,
163 p: impl Into<Point>,
164 ) -> &mut Self {
165 let (c1, c2, p) = (c1.into(), c2.into(), p.into());
166 self.current = p;
167 self.push(PathSeg::Cubic(c1, c2, p))
168 }
169
170 pub fn close(&mut self) -> &mut Self {
171 self.current = self.start;
172 self.push(PathSeg::Close)
173 }
174
175 pub fn polyline(&mut self, points: &[Point]) -> &mut Self {
176 let Some((first, rest)) = points.split_first() else {
177 return self;
178 };
179 self.move_to(*first);
180 for p in rest {
181 self.line_to(*p);
182 }
183 self
184 }
185
186 pub fn spline(&mut self, points: &[Point], tension: f32) -> &mut Self {
187 let n = points.len();
188 if n < 2 {
189 return self;
190 }
191 let tension = if tension.is_finite() { tension } else { 1.0 };
192 self.move_to(points[0]);
193 for i in 0..n - 1 {
194 let p0 = if i == 0 { points[0] } else { points[i - 1] };
195 let p1 = points[i];
196 let p2 = points[i + 1];
197 let p3 = if i + 2 < n {
198 points[i + 2]
199 } else {
200 points[n - 1]
201 };
202 let c1 = Point::new(
203 p1.x + (p2.x - p0.x) / 6.0 * tension,
204 p1.y + (p2.y - p0.y) / 6.0 * tension,
205 );
206 let c2 = Point::new(
207 p2.x - (p3.x - p1.x) / 6.0 * tension,
208 p2.y - (p3.y - p1.y) / 6.0 * tension,
209 );
210 self.cubic_to(c1, c2, p2);
211 }
212 self
213 }
214
215 pub fn polygon(&mut self, points: &[Point]) -> &mut Self {
216 self.polyline(points);
217 if !points.is_empty() {
218 self.close();
219 }
220 self
221 }
222
223 fn is_first_contour(&self) -> bool {
224 !self.segs.iter().any(|seg| matches!(seg, PathSeg::Move(_)))
225 }
226
227 pub fn rect(&mut self, rect: Rect) -> &mut Self {
228 let single = self.is_first_contour();
229 let (x0, y0) = (rect.left(), rect.top());
230 let (x1, y1) = (rect.right(), rect.bottom());
231 self.move_to((x0, y0));
232 self.line_to((x1, y0));
233 self.line_to((x1, y1));
234 self.line_to((x0, y1));
235 self.close();
236 if single {
237 self.hint = Hint::Rect(rect);
238 }
239 self
240 }
241
242 pub fn rounded_rect(&mut self, rect: Rect, radius: impl Into<Radius>) -> &mut Self {
243 let single = self.is_first_contour();
244 let radius = radius.into();
245 let max = (rect.size.width.min(rect.size.height) * 0.5).max(0.0);
246 let r = Radius {
247 tl: radius.tl.clamp(0.0, max),
248 tr: radius.tr.clamp(0.0, max),
249 br: radius.br.clamp(0.0, max),
250 bl: radius.bl.clamp(0.0, max),
251 };
252 let (x0, y0) = (rect.left(), rect.top());
253 let (x1, y1) = (rect.right(), rect.bottom());
254
255 self.move_to((x0 + r.tl, y0));
256 self.line_to((x1 - r.tr, y0));
257 if r.tr > 0.0 {
258 self.arc_corner((x1 - r.tr, y0 + r.tr), r.tr, -90.0, 0.0);
259 }
260 self.line_to((x1, y1 - r.br));
261 if r.br > 0.0 {
262 self.arc_corner((x1 - r.br, y1 - r.br), r.br, 0.0, 90.0);
263 }
264 self.line_to((x0 + r.bl, y1));
265 if r.bl > 0.0 {
266 self.arc_corner((x0 + r.bl, y1 - r.bl), r.bl, 90.0, 180.0);
267 }
268 self.line_to((x0, y0 + r.tl));
269 if r.tl > 0.0 {
270 self.arc_corner((x0 + r.tl, y0 + r.tl), r.tl, 180.0, 270.0);
271 }
272 self.close();
273 if let Some(uniform) = r.uniform()
274 && single
275 {
276 self.hint = Hint::RoundRect(rect, uniform);
277 }
278 self
279 }
280
281 pub fn circle(&mut self, center: impl Into<Point>, radius: f32) -> &mut Self {
282 let single = self.is_first_contour();
283 let center = center.into();
284 if radius <= 0.0 {
285 return self;
286 }
287 self.ellipse(center, radius, radius);
288 if single {
289 self.hint = Hint::Circle(center, radius);
290 }
291 self
292 }
293
294 pub fn ellipse(&mut self, center: impl Into<Point>, rx: f32, ry: f32) -> &mut Self {
295 let c = center.into();
296 if rx <= 0.0 || ry <= 0.0 {
297 return self;
298 }
299 let k = 0.552_284_8;
300 let (kx, ky) = (rx * k, ry * k);
301 self.move_to((c.x + rx, c.y));
302 self.cubic_to((c.x + rx, c.y + ky), (c.x + kx, c.y + ry), (c.x, c.y + ry));
303 self.cubic_to((c.x - kx, c.y + ry), (c.x - rx, c.y + ky), (c.x - rx, c.y));
304 self.cubic_to((c.x - rx, c.y - ky), (c.x - kx, c.y - ry), (c.x, c.y - ry));
305 self.cubic_to((c.x + kx, c.y - ry), (c.x + rx, c.y - ky), (c.x + rx, c.y));
306 self.close();
307 self
308 }
309
310 pub fn arc(
311 &mut self,
312 center: impl Into<Point>,
313 radius: f32,
314 from_deg: f32,
315 to_deg: f32,
316 ) -> &mut Self {
317 let c = center.into();
318 if radius <= 0.0 {
319 return self;
320 }
321 let from = from_deg.to_radians();
322 let start = Point::new(c.x + from.cos() * radius, c.y + from.sin() * radius);
323 if self.segs.is_empty() {
324 self.move_to(start);
325 } else if self.current != start {
326 self.line_to(start);
327 }
328 self.arc_corner(c, radius, from_deg, to_deg);
329 self
330 }
331
332 fn arc_corner(
333 &mut self,
334 center: impl Into<Point>,
335 radius: f32,
336 from_deg: f32,
337 to_deg: f32,
338 ) -> &mut Self {
339 let center = center.into();
340 let from = from_deg.to_radians();
341 let to = to_deg.to_radians();
342 let span = to - from;
343 if span.abs() < 1e-6 {
344 return self;
345 }
346 let steps = (span.abs() / (std::f32::consts::FRAC_PI_2)).ceil().max(1.0) as usize;
347 let step = span / steps as f32;
348 let k = 4.0 / 3.0 * (step / 4.0).tan();
349 let mut a = from;
350 let mut p = Point::new(center.x + a.cos() * radius, center.y + a.sin() * radius);
351 for _ in 0..steps {
352 let b = a + step;
353 let q = Point::new(center.x + b.cos() * radius, center.y + b.sin() * radius);
354 let c1 = Point::new(p.x - a.sin() * radius * k, p.y + a.cos() * radius * k);
355 let c2 = Point::new(q.x + b.sin() * radius * k, q.y - b.cos() * radius * k);
356 self.cubic_to(c1, c2, q);
357 a = b;
358 p = q;
359 }
360 self
361 }
362
363 pub fn from_svg(data: &str) -> Result<Self, PathError> {
364 parse_svg_path(data)
365 }
366
367 pub fn as_rect(&self) -> Option<Rect> {
368 match self.hint {
369 Hint::Rect(r) => Some(r),
370 _ => None,
371 }
372 }
373
374 pub fn as_rounded_rect(&self) -> Option<(Rect, f32)> {
375 match self.hint {
376 Hint::RoundRect(r, radius) => Some((r, radius)),
377 _ => None,
378 }
379 }
380
381 pub fn as_circle(&self) -> Option<(Point, f32)> {
382 match self.hint {
383 Hint::Circle(c, r) => Some((c, r)),
384 _ => None,
385 }
386 }
387
388 pub fn to_svg(&self) -> String {
389 crate::svg_path::segs_to_debug(self)
390 }
391
392 pub fn contours(&self, tolerance: f32) -> Vec<Contour> {
393 flatten(self, tolerance)
394 }
395
396 pub fn bounds(&self) -> Rect {
397 bounds_of_contours(&self.contours(crate::TOLERANCE))
398 }
399
400 pub fn bounds_with(&self, tolerance: f32) -> Rect {
401 bounds_of_contours(&self.contours(tolerance))
402 }
403
404 pub fn contains(&self, p: Point, rule: FillRule) -> bool {
405 let contours = self.contours(crate::TOLERANCE);
406 raster::contains(&contours, p, rule)
407 }
408
409 pub fn transformed(&self, t: Transform) -> Self {
410 let mut out = Self {
411 segs: Vec::with_capacity(self.segs.len()),
412 current: t.apply(self.current),
413 start: t.apply(self.start),
414 hint: Hint::None,
415 };
416 out.segs = self
417 .segs
418 .iter()
419 .map(|seg| match *seg {
420 PathSeg::Move(p) => PathSeg::Move(t.apply(p)),
421 PathSeg::Line(p) => PathSeg::Line(t.apply(p)),
422 PathSeg::Quad(c, p) => PathSeg::Quad(t.apply(c), t.apply(p)),
423 PathSeg::Cubic(c1, c2, p) => PathSeg::Cubic(t.apply(c1), t.apply(c2), t.apply(p)),
424 PathSeg::Close => PathSeg::Close,
425 })
426 .collect();
427 out.hint = match self.hint {
428 Hint::Rect(r) if t.m[1] == 0.0 && t.m[2] == 0.0 => Hint::Rect(transform_rect(r, t)),
429 Hint::Circle(c, r) => match t.similarity_scale() {
430 Some(s) => Hint::Circle(t.apply(c), r * s),
431 None => Hint::None,
432 },
433 Hint::RoundRect(r, radius)
434 if t.m[1] == 0.0 && t.m[2] == 0.0 && (t.m[0].abs() - t.m[3].abs()).abs() < 1e-6 =>
435 {
436 Hint::RoundRect(transform_rect(r, t), radius * t.m[0].abs())
437 }
438 _ => Hint::None,
439 };
440 out
441 }
442
443 pub fn translated(&self, dx: f32, dy: f32) -> Self {
444 self.transformed(Transform::translate(dx, dy))
445 }
446
447 pub fn scaled(&self, sx: f32, sy: f32) -> Self {
448 self.transformed(Transform::scale(sx, sy))
449 }
450
451 pub fn rotated(&self, radians: f32, pivot: Point) -> Self {
452 self.transformed(Transform::rotate_around(radians, pivot))
453 }
454
455 pub(crate) fn fingerprint(&self) -> u64 {
456 let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
457 hash ^= match self.hint {
458 Hint::None => 7,
459 Hint::Rect(_) => 8,
460 Hint::RoundRect(_, _) => 9,
461 Hint::Circle(_, _) => 10,
462 };
463 for seg in &self.segs {
464 match *seg {
465 PathSeg::Move(p) => {
466 hash ^= 1;
467 hash_point(&mut hash, p);
468 }
469 PathSeg::Line(p) => {
470 hash ^= 2;
471 hash_point(&mut hash, p);
472 }
473 PathSeg::Quad(c, p) => {
474 hash ^= 3;
475 hash_point(&mut hash, c);
476 hash_point(&mut hash, p);
477 }
478 PathSeg::Cubic(c1, c2, p) => {
479 hash ^= 4;
480 hash_point(&mut hash, c1);
481 hash_point(&mut hash, c2);
482 hash_point(&mut hash, p);
483 }
484 PathSeg::Close => hash ^= 5,
485 }
486 hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
487 }
488 hash
489 }
490}
491
492impl std::fmt::Display for Path {
493 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
494 f.write_str(&self.to_svg())
495 }
496}
497
498fn hash_point(hash: &mut u64, p: Point) {
499 for v in [p.x, p.y] {
500 for byte in v.to_bits().to_le_bytes() {
501 *hash ^= u64::from(byte);
502 *hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
503 }
504 }
505}
506
507fn bounds_of_contours(contours: &[Contour]) -> Rect {
508 let mut min_x = f32::INFINITY;
509 let mut min_y = f32::INFINITY;
510 let mut max_x = f32::NEG_INFINITY;
511 let mut max_y = f32::NEG_INFINITY;
512 for c in contours {
513 for p in &c.points {
514 min_x = min_x.min(p.x);
515 min_y = min_y.min(p.y);
516 max_x = max_x.max(p.x);
517 max_y = max_y.max(p.y);
518 }
519 }
520 if !min_x.is_finite() {
521 return Rect::ZERO;
522 }
523 Rect::from_xywh(min_x, min_y, max_x - min_x, max_y - min_y)
524}
525
526fn transform_rect(r: Rect, t: Transform) -> Rect {
527 let a = t.apply(Point::new(r.left(), r.top()));
528 let b = t.apply(Point::new(r.right(), r.bottom()));
529 Rect::from_xywh(
530 a.x.min(b.x),
531 a.y.min(b.y),
532 (b.x - a.x).abs(),
533 (b.y - a.y).abs(),
534 )
535}