1 | //! Approximate the area of a path. |
2 | |
3 | use crate::geom::vector; |
4 | use crate::path::{iterator::PathIterator, PathEvent}; |
5 | |
6 | /// Compute the signed area of a path by summing the signed areas of its sub-paths. |
7 | pub fn approximate_signed_area<Iter>(tolerance: f32, path: Iter) -> f32 |
8 | where |
9 | Iter: IntoIterator<Item = PathEvent>, |
10 | { |
11 | let mut path: ::IntoIter = path.into_iter(); |
12 | let mut area: f32 = 0.0; |
13 | while let Some(sp_area: f32) = approximate_sub_path_signed_area(tolerance, &mut path) { |
14 | area += sp_area; |
15 | } |
16 | |
17 | area |
18 | } |
19 | |
20 | /// Compute the signed area of the next sub-path. |
21 | /// |
22 | /// The iterator is advanced so that `approximate_sub_path_signed_area` can be called multiple times |
23 | /// to process the successive sub-paths of a path. |
24 | /// |
25 | /// Returns `None` if there is no more sub-path or if the the iterator is malformed. |
26 | pub fn approximate_sub_path_signed_area<Iter>(tolerance: f32, path: &mut Iter) -> Option<f32> |
27 | where |
28 | Iter: Iterator<Item = PathEvent>, |
29 | { |
30 | let first = if let Some(PathEvent::Begin { at }) = path.next() { |
31 | at |
32 | } else { |
33 | return None; |
34 | }; |
35 | let mut double_area = 0.0; |
36 | let mut v0 = vector(0.0, 0.0); |
37 | |
38 | for evt in path.flattened(tolerance) { |
39 | match evt { |
40 | PathEvent::Begin { .. } => { |
41 | return None; |
42 | } |
43 | PathEvent::End { last, first, .. } => { |
44 | let v1 = last - first; |
45 | double_area += v0.cross(v1); |
46 | |
47 | return Some(double_area * 0.5); |
48 | } |
49 | PathEvent::Line { to, .. } => { |
50 | let v1 = to - first; |
51 | double_area += v0.cross(v1); |
52 | v0 = v1; |
53 | } |
54 | PathEvent::Quadratic { .. } | PathEvent::Cubic { .. } => { |
55 | debug_assert!(false, "Unexpected curve in a flattened path" ); |
56 | } |
57 | }; |
58 | } |
59 | |
60 | None |
61 | } |
62 | |
63 | /// Iterator over the sub-path areas of a path. |
64 | pub struct SignedAreas<Iter = PathEvent>(pub Iter, f32); |
65 | |
66 | impl<Iter: Iterator<Item = PathEvent>> Iterator for SignedAreas<Iter> { |
67 | type Item = f32; |
68 | fn next(&mut self) -> Option<f32> { |
69 | approximate_sub_path_signed_area(self.1, &mut self.0) |
70 | } |
71 | } |
72 | |
73 | #[test ] |
74 | fn sub_path_signed_area() { |
75 | use crate::geom::point; |
76 | let mut path = crate::path::Path::builder(); |
77 | |
78 | path.begin(point(0.0, 0.0)); |
79 | path.line_to(point(1.0, 0.0)); |
80 | path.line_to(point(1.0, 1.0)); |
81 | path.line_to(point(0.0, 1.0)); |
82 | path.close(); |
83 | |
84 | path.begin(point(0.0, 0.0)); |
85 | path.line_to(point(0.0, 1.0)); |
86 | path.line_to(point(1.0, 1.0)); |
87 | path.line_to(point(1.0, 0.0)); |
88 | path.close(); |
89 | |
90 | let path = path.build(); |
91 | |
92 | let mut iter = path.iter(); |
93 | |
94 | assert_eq!(approximate_sub_path_signed_area(0.01, &mut iter), Some(1.0)); |
95 | assert_eq!( |
96 | approximate_sub_path_signed_area(0.01, &mut iter), |
97 | Some(-1.0) |
98 | ); |
99 | assert_eq!(approximate_sub_path_signed_area(0.01, &mut iter), None); |
100 | |
101 | let mut path = crate::path::Path::builder(); |
102 | |
103 | path.begin(point(0.0, 1.0)); |
104 | path.line_to(point(1.0, 1.0)); |
105 | path.line_to(point(1.0, 0.0)); |
106 | path.line_to(point(2.0, 0.0)); |
107 | path.line_to(point(2.0, 1.0)); |
108 | path.line_to(point(3.0, 1.0)); |
109 | path.line_to(point(3.0, 2.0)); |
110 | path.line_to(point(2.0, 2.0)); |
111 | path.line_to(point(2.0, 3.0)); |
112 | path.line_to(point(1.0, 3.0)); |
113 | path.line_to(point(1.0, 2.0)); |
114 | path.line_to(point(0.0, 2.0)); |
115 | path.close(); |
116 | |
117 | assert_eq!(approximate_signed_area(0.01, path.build().iter()), 5.0); |
118 | } |
119 | |