1 | // Compute the winding of a path. |
2 | |
3 | use crate::geom::vector; |
4 | use crate::path::{PathEvent, Winding}; |
5 | |
6 | /// Compute the winding of the next sub-path. |
7 | /// |
8 | /// The sub-path is expected to have a non-null area and no self-intersections, otherwise |
9 | /// the result is unspecified. |
10 | /// |
11 | /// The iterator is advanced so that `compute_winding` can be called multiple times |
12 | /// to process the successive sub-paths of a path. |
13 | /// |
14 | /// Returns `None` if there is no more sub-path or if the the iterator is malformed. |
15 | pub fn compute_winding<Iter>(path: &mut Iter) -> Option<Winding> |
16 | where |
17 | Iter: Iterator<Item = PathEvent>, |
18 | { |
19 | let first = if let Some(PathEvent::Begin { at }) = path.next() { |
20 | at |
21 | } else { |
22 | return None; |
23 | }; |
24 | let mut area = 0.0; |
25 | let mut v0 = vector(0.0, 0.0); |
26 | |
27 | for evt in path { |
28 | match evt { |
29 | PathEvent::Begin { .. } => { |
30 | return None; |
31 | } |
32 | PathEvent::End { last, first, .. } => { |
33 | let v1 = last - first; |
34 | area += v0.cross(v1); |
35 | |
36 | return if area > 0.0 { |
37 | Some(Winding::Positive) |
38 | } else { |
39 | Some(Winding::Negative) |
40 | }; |
41 | } |
42 | PathEvent::Line { to, .. } => { |
43 | let v1 = to - first; |
44 | area += v0.cross(v1); |
45 | v0 = v1; |
46 | } |
47 | PathEvent::Quadratic { ctrl, to, .. } => { |
48 | let v1 = ctrl - first; |
49 | let v2 = to - first; |
50 | area += v0.cross(v1) + v1.cross(v2); |
51 | v0 = v2; |
52 | } |
53 | PathEvent::Cubic { |
54 | ctrl1, ctrl2, to, .. |
55 | } => { |
56 | let v1 = ctrl1 - first; |
57 | let v2 = ctrl2 - first; |
58 | let v3 = to - first; |
59 | area += v0.cross(v1) + v1.cross(v2) + v2.cross(v3); |
60 | v0 = v3; |
61 | } |
62 | }; |
63 | } |
64 | |
65 | None |
66 | } |
67 | |
68 | /// Iterator over the sub-path windings of a path. |
69 | pub struct Windings<Iter = PathEvent>(pub Iter); |
70 | |
71 | impl<Iter: Iterator<Item = PathEvent>> Iterator for Windings<Iter> { |
72 | type Item = Winding; |
73 | fn next(&mut self) -> Option<Winding> { |
74 | compute_winding(&mut self.0) |
75 | } |
76 | } |
77 | |
78 | #[test ] |
79 | fn path_winding() { |
80 | use crate::geom::point; |
81 | let mut path: NoAttributes = crate::path::Path::builder(); |
82 | |
83 | path.begin(at:point(x:0.0, y:0.0)); |
84 | path.line_to(point(x:1.0, y:0.0)); |
85 | path.line_to(point(x:1.0, y:1.0)); |
86 | path.line_to(point(x:0.0, y:1.0)); |
87 | path.close(); |
88 | |
89 | path.begin(at:point(x:0.0, y:0.0)); |
90 | path.line_to(point(x:0.0, y:1.0)); |
91 | path.line_to(point(x:1.0, y:1.0)); |
92 | path.line_to(point(x:1.0, y:0.0)); |
93 | path.close(); |
94 | |
95 | let path: Path = path.build(); |
96 | |
97 | let mut iter: Iter<'_> = path.iter(); |
98 | |
99 | assert_eq!(compute_winding(&mut iter), Some(Winding::Positive)); |
100 | assert_eq!(compute_winding(&mut iter), Some(Winding::Negative)); |
101 | assert_eq!(compute_winding(&mut iter), None); |
102 | } |
103 | |