1//! Approximate the area of a path.
2
3use crate::geom::vector;
4use crate::path::{iterator::PathIterator, PathEvent};
5
6/// Compute the signed area of a path by summing the signed areas of its sub-paths.
7pub fn approximate_signed_area<Iter>(tolerance: f32, path: Iter) -> f32
8where
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.
26pub fn approximate_sub_path_signed_area<Iter>(tolerance: f32, path: &mut Iter) -> Option<f32>
27where
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.
64pub struct SignedAreas<Iter = PathEvent>(pub Iter, f32);
65
66impl<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]
74fn 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