lyon_algorithms/
area.rs

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 = path.into_iter();
12    let mut area = 0.0;
13    while let Some(sp_area) = 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}