1use skia_rs_core::{Point, Rect, Scalar};
4use smallvec::SmallVec;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
8#[repr(u8)]
9pub enum FillType {
10 #[default]
12 Winding = 0,
13 EvenOdd,
15 InverseWinding,
17 InverseEvenOdd,
19}
20
21impl FillType {
22 #[inline]
24 pub const fn is_inverse(&self) -> bool {
25 matches!(self, FillType::InverseWinding | FillType::InverseEvenOdd)
26 }
27
28 #[inline]
30 pub const fn inverse(&self) -> Self {
31 match self {
32 FillType::Winding => FillType::InverseWinding,
33 FillType::EvenOdd => FillType::InverseEvenOdd,
34 FillType::InverseWinding => FillType::Winding,
35 FillType::InverseEvenOdd => FillType::EvenOdd,
36 }
37 }
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42#[repr(u8)]
43pub enum Verb {
44 Move = 0,
46 Line,
48 Quad,
50 Conic,
52 Cubic,
54 Close,
56}
57
58impl Verb {
59 #[inline]
61 pub const fn point_count(&self) -> usize {
62 match self {
63 Verb::Move | Verb::Line => 1,
64 Verb::Quad | Verb::Conic => 2,
65 Verb::Cubic => 3,
66 Verb::Close => 0,
67 }
68 }
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
73#[repr(u8)]
74pub enum PathDirection {
75 #[default]
77 CW = 0,
78 CCW,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
84#[repr(u8)]
85pub enum PathConvexity {
86 #[default]
88 Unknown = 0,
89 Convex,
91 Concave,
93}
94
95#[derive(Debug, Clone, Default)]
97pub struct Path {
98 pub(crate) verbs: SmallVec<[Verb; 16]>,
100 pub(crate) points: SmallVec<[Point; 32]>,
102 pub(crate) conic_weights: SmallVec<[Scalar; 4]>,
104 pub(crate) fill_type: FillType,
106 pub(crate) bounds: Option<Rect>,
108 pub(crate) convexity: PathConvexity,
110}
111
112impl Path {
113 #[inline]
115 pub fn new() -> Self {
116 Self::default()
117 }
118
119 #[inline]
121 pub fn fill_type(&self) -> FillType {
122 self.fill_type
123 }
124
125 #[inline]
127 pub fn set_fill_type(&mut self, fill_type: FillType) {
128 self.fill_type = fill_type;
129 }
130
131 #[inline]
133 pub fn is_empty(&self) -> bool {
134 self.verbs.is_empty()
135 }
136
137 #[inline]
139 pub fn verb_count(&self) -> usize {
140 self.verbs.len()
141 }
142
143 #[inline]
145 pub fn point_count(&self) -> usize {
146 self.points.len()
147 }
148
149 pub fn bounds(&self) -> Rect {
151 if let Some(bounds) = self.bounds {
152 return bounds;
153 }
154
155 if self.points.is_empty() {
156 return Rect::EMPTY;
157 }
158
159 let mut min_x = self.points[0].x;
160 let mut min_y = self.points[0].y;
161 let mut max_x = min_x;
162 let mut max_y = min_y;
163
164 for p in &self.points[1..] {
165 min_x = min_x.min(p.x);
166 min_y = min_y.min(p.y);
167 max_x = max_x.max(p.x);
168 max_y = max_y.max(p.y);
169 }
170
171 Rect::new(min_x, min_y, max_x, max_y)
172 }
173
174 #[inline]
176 pub fn reset(&mut self) {
177 self.verbs.clear();
178 self.points.clear();
179 self.conic_weights.clear();
180 self.bounds = None;
181 }
182
183 pub fn iter(&self) -> PathIter<'_> {
185 PathIter {
186 path: self,
187 verb_index: 0,
188 point_index: 0,
189 weight_index: 0,
190 }
191 }
192
193 #[inline]
195 pub fn verbs(&self) -> &[Verb] {
196 &self.verbs
197 }
198
199 #[inline]
201 pub fn points(&self) -> &[Point] {
202 &self.points
203 }
204
205 #[inline]
207 pub fn last_point(&self) -> Option<Point> {
208 self.points.last().copied()
209 }
210
211 pub fn contour_count(&self) -> usize {
213 self.verbs.iter().filter(|v| **v == Verb::Move).count()
214 }
215
216 pub fn is_closed(&self) -> bool {
218 self.verbs.last() == Some(&Verb::Close)
219 }
220
221 pub fn is_line(&self) -> bool {
223 self.verbs.len() == 2 && self.verbs[0] == Verb::Move && self.verbs[1] == Verb::Line
224 }
225
226 pub fn is_rect(&self) -> Option<Rect> {
228 if self.verbs.len() < 5 {
230 return None;
231 }
232
233 let mut line_count = 0;
234 let mut has_close = false;
235
236 for verb in &self.verbs {
237 match verb {
238 Verb::Move => {}
239 Verb::Line => line_count += 1,
240 Verb::Close => has_close = true,
241 _ => return None,
242 }
243 }
244
245 if line_count != 4 || !has_close {
246 return None;
247 }
248
249 if self.points.len() < 5 {
251 return None;
252 }
253
254 let p0 = self.points[0];
255 let p1 = self.points[1];
256 let p2 = self.points[2];
257 let p3 = self.points[3];
258 let p4 = self.points[4];
259
260 let is_horizontal_1 = (p0.y - p1.y).abs() < 0.001;
262 let is_vertical_2 = (p1.x - p2.x).abs() < 0.001;
263 let is_horizontal_3 = (p2.y - p3.y).abs() < 0.001;
264 let is_vertical_4 = (p3.x - p4.x).abs() < 0.001;
265
266 if is_horizontal_1 && is_vertical_2 && is_horizontal_3 && is_vertical_4 {
267 let left = p0.x.min(p1.x).min(p2.x).min(p3.x);
268 let top = p0.y.min(p1.y).min(p2.y).min(p3.y);
269 let right = p0.x.max(p1.x).max(p2.x).max(p3.x);
270 let bottom = p0.y.max(p1.y).max(p2.y).max(p3.y);
271 return Some(Rect::new(left, top, right, bottom));
272 }
273
274 None
275 }
276
277 pub fn is_oval(&self) -> bool {
279 let cubic_count = self.verbs.iter().filter(|v| **v == Verb::Cubic).count();
281 let conic_count = self.verbs.iter().filter(|v| **v == Verb::Conic).count();
282
283 (cubic_count == 4 && self.verbs.len() == 6) || (conic_count == 4 && self.verbs.len() == 6) }
286
287 pub fn convexity(&self) -> PathConvexity {
289 if self.convexity != PathConvexity::Unknown {
290 return self.convexity;
291 }
292
293 if self.points.len() < 3 {
295 return PathConvexity::Convex;
296 }
297
298 let mut sign = 0i32;
299 let n = self.points.len();
300
301 for i in 0..n {
302 let p0 = self.points[i];
303 let p1 = self.points[(i + 1) % n];
304 let p2 = self.points[(i + 2) % n];
305
306 let cross = (p1.x - p0.x) * (p2.y - p1.y) - (p1.y - p0.y) * (p2.x - p1.x);
307
308 if cross.abs() > 0.001 {
309 let current_sign = if cross > 0.0 { 1 } else { -1 };
310 if sign == 0 {
311 sign = current_sign;
312 } else if sign != current_sign {
313 return PathConvexity::Concave;
314 }
315 }
316 }
317
318 PathConvexity::Convex
319 }
320
321 #[inline]
323 pub fn is_convex(&self) -> bool {
324 self.convexity() == PathConvexity::Convex
325 }
326
327 pub fn direction(&self) -> Option<PathDirection> {
329 if self.points.len() < 3 {
330 return None;
331 }
332
333 let mut signed_area = 0.0;
335 let n = self.points.len();
336
337 for i in 0..n {
338 let p0 = self.points[i];
339 let p1 = self.points[(i + 1) % n];
340 signed_area += (p1.x - p0.x) * (p1.y + p0.y);
341 }
342
343 if signed_area.abs() < 0.001 {
344 return None;
345 }
346
347 Some(if signed_area > 0.0 {
348 PathDirection::CW
349 } else {
350 PathDirection::CCW
351 })
352 }
353
354 pub fn reverse(&mut self) {
356 if self.verbs.is_empty() {
357 return;
358 }
359
360 self.points.reverse();
362
363 self.conic_weights.reverse();
365
366 let mut new_verbs = SmallVec::new();
369 let mut i = self.verbs.len();
370
371 while i > 0 {
372 i -= 1;
373 match self.verbs[i] {
374 Verb::Move => {
375 if !new_verbs.is_empty() {
376 new_verbs.push(Verb::Close);
377 }
378 new_verbs.push(Verb::Move);
379 }
380 Verb::Close => {
381 }
383 v => new_verbs.push(v),
384 }
385 }
386
387 if !new_verbs.is_empty() && self.is_closed() {
388 new_verbs.push(Verb::Close);
389 }
390
391 self.verbs = new_verbs;
392 self.bounds = None;
393 self.convexity = PathConvexity::Unknown;
394 }
395
396 pub fn transform(&mut self, matrix: &skia_rs_core::Matrix) {
398 for point in &mut self.points {
399 *point = matrix.map_point(*point);
400 }
401 self.bounds = None;
402 self.convexity = PathConvexity::Unknown;
403 }
404
405 pub fn transformed(&self, matrix: &skia_rs_core::Matrix) -> Self {
407 let mut result = self.clone();
408 result.transform(matrix);
409 result
410 }
411
412 pub fn offset(&mut self, dx: Scalar, dy: Scalar) {
414 for point in &mut self.points {
415 point.x += dx;
416 point.y += dy;
417 }
418 if let Some(ref mut bounds) = self.bounds {
419 bounds.left += dx;
420 bounds.right += dx;
421 bounds.top += dy;
422 bounds.bottom += dy;
423 }
424 }
425
426 pub fn contains(&self, point: Point) -> bool {
428 if !self.bounds().contains(point) {
430 return false;
431 }
432
433 let mut crossings = 0;
434 let mut current = Point::zero();
435
436 for element in self.iter() {
437 match element {
438 PathElement::Move(p) => current = p,
439 PathElement::Line(end) => {
440 if ray_crosses_segment(point, current, end) {
441 crossings += 1;
442 }
443 current = end;
444 }
445 PathElement::Quad(ctrl, end) => {
446 for i in 1..=8 {
448 let t = i as f32 / 8.0;
449 let mt = 1.0 - t;
450 let p = Point::new(
451 mt * mt * current.x + 2.0 * mt * t * ctrl.x + t * t * end.x,
452 mt * mt * current.y + 2.0 * mt * t * ctrl.y + t * t * end.y,
453 );
454 if ray_crosses_segment(point, current, p) {
455 crossings += 1;
456 }
457 current = p;
458 }
459 current = end;
460 }
461 PathElement::Conic(ctrl, end, _w) => {
462 for i in 1..=8 {
464 let t = i as f32 / 8.0;
465 let mt = 1.0 - t;
466 let p = Point::new(
467 mt * mt * current.x + 2.0 * mt * t * ctrl.x + t * t * end.x,
468 mt * mt * current.y + 2.0 * mt * t * ctrl.y + t * t * end.y,
469 );
470 if ray_crosses_segment(point, current, p) {
471 crossings += 1;
472 }
473 current = p;
474 }
475 current = end;
476 }
477 PathElement::Cubic(c1, c2, end) => {
478 for i in 1..=12 {
480 let t = i as f32 / 12.0;
481 let mt = 1.0 - t;
482 let mt2 = mt * mt;
483 let t2 = t * t;
484 let p = Point::new(
485 mt2 * mt * current.x
486 + 3.0 * mt2 * t * c1.x
487 + 3.0 * mt * t2 * c2.x
488 + t2 * t * end.x,
489 mt2 * mt * current.y
490 + 3.0 * mt2 * t * c1.y
491 + 3.0 * mt * t2 * c2.y
492 + t2 * t * end.y,
493 );
494 if ray_crosses_segment(point, current, p) {
495 crossings += 1;
496 }
497 current = p;
498 }
499 current = end;
500 }
501 PathElement::Close => {}
502 }
503 }
504
505 match self.fill_type {
506 FillType::Winding => crossings != 0,
507 FillType::EvenOdd => crossings % 2 != 0,
508 FillType::InverseWinding => crossings == 0,
509 FillType::InverseEvenOdd => crossings % 2 == 0,
510 }
511 }
512
513 pub fn tight_bounds(&self) -> Rect {
515 self.bounds()
517 }
518
519 pub fn length(&self) -> Scalar {
521 let mut total = 0.0;
522 let mut current = Point::zero();
523
524 for element in self.iter() {
525 match element {
526 PathElement::Move(p) => current = p,
527 PathElement::Line(end) => {
528 total += current.distance(&end);
529 current = end;
530 }
531 PathElement::Quad(ctrl, end) => {
532 total += current.distance(&ctrl) + ctrl.distance(&end);
534 current = end;
535 }
536 PathElement::Conic(ctrl, end, _) => {
537 total += current.distance(&ctrl) + ctrl.distance(&end);
538 current = end;
539 }
540 PathElement::Cubic(c1, c2, end) => {
541 total += current.distance(&c1) + c1.distance(&c2) + c2.distance(&end);
542 current = end;
543 }
544 PathElement::Close => {}
545 }
546 }
547
548 total
549 }
550}
551
552fn ray_crosses_segment(point: Point, p0: Point, p1: Point) -> bool {
554 let (p0, p1) = if p0.y <= p1.y { (p0, p1) } else { (p1, p0) };
556
557 if point.y < p0.y || point.y >= p1.y {
559 return false;
560 }
561
562 let t = (point.y - p0.y) / (p1.y - p0.y);
564 let x_intersect = p0.x + t * (p1.x - p0.x);
565
566 x_intersect > point.x
567}
568
569#[derive(Debug, Clone, Copy, PartialEq)]
571pub enum PathElement {
572 Move(Point),
574 Line(Point),
576 Quad(Point, Point),
578 Conic(Point, Point, Scalar),
580 Cubic(Point, Point, Point),
582 Close,
584}
585
586pub struct PathIter<'a> {
588 path: &'a Path,
589 verb_index: usize,
590 point_index: usize,
591 weight_index: usize,
592}
593
594impl<'a> Iterator for PathIter<'a> {
595 type Item = PathElement;
596
597 fn next(&mut self) -> Option<Self::Item> {
598 if self.verb_index >= self.path.verbs.len() {
599 return None;
600 }
601
602 let verb = self.path.verbs[self.verb_index];
603 self.verb_index += 1;
604
605 let element = match verb {
606 Verb::Move => {
607 let p = self.path.points[self.point_index];
608 self.point_index += 1;
609 PathElement::Move(p)
610 }
611 Verb::Line => {
612 let p = self.path.points[self.point_index];
613 self.point_index += 1;
614 PathElement::Line(p)
615 }
616 Verb::Quad => {
617 let p1 = self.path.points[self.point_index];
618 let p2 = self.path.points[self.point_index + 1];
619 self.point_index += 2;
620 PathElement::Quad(p1, p2)
621 }
622 Verb::Conic => {
623 let p1 = self.path.points[self.point_index];
624 let p2 = self.path.points[self.point_index + 1];
625 let w = self.path.conic_weights[self.weight_index];
626 self.point_index += 2;
627 self.weight_index += 1;
628 PathElement::Conic(p1, p2, w)
629 }
630 Verb::Cubic => {
631 let p1 = self.path.points[self.point_index];
632 let p2 = self.path.points[self.point_index + 1];
633 let p3 = self.path.points[self.point_index + 2];
634 self.point_index += 3;
635 PathElement::Cubic(p1, p2, p3)
636 }
637 Verb::Close => PathElement::Close,
638 };
639
640 Some(element)
641 }
642}