Coverage Report

Created: 2025-09-27 07:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/kurbo-0.12.0/src/shape.rs
Line
Count
Source
1
// Copyright 2019 the Kurbo Authors
2
// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4
//! A generic trait for shapes.
5
6
use crate::{segments, BezPath, Circle, Line, PathEl, Point, Rect, RoundedRect, Segments};
7
8
/// A generic trait for open and closed shapes.
9
///
10
/// This trait provides conversion from shapes to [`BezPath`]s, as well as
11
/// general geometry functionality like computing [`area`], [`bounding_box`]es,
12
/// and [`winding`] number.
13
///
14
/// [`area`]: Shape::area
15
/// [`bounding_box`]: Shape::bounding_box
16
/// [`winding`]: Shape::winding
17
pub trait Shape {
18
    /// The iterator returned by the [`path_elements`] method.
19
    ///
20
    /// [`path_elements`]: Shape::path_elements
21
    type PathElementsIter<'iter>: Iterator<Item = PathEl> + 'iter
22
    where
23
        Self: 'iter;
24
25
    /// Returns an iterator over this shape expressed as [`PathEl`]s;
26
    /// that is, as Bézier path _elements_.
27
    ///
28
    /// All shapes can be represented as Béziers, but in many situations
29
    /// (such as when interfacing with a platform drawing API) there are more
30
    /// efficient native types for specific concrete shapes. In this case,
31
    /// the user should exhaust the `as_` methods ([`as_rect`], [`as_line`], etc)
32
    /// before converting to a [`BezPath`], as those are likely to be more
33
    /// efficient.
34
    ///
35
    /// In many cases, shapes are able to iterate their elements without
36
    /// allocating; however creating a [`BezPath`] object always allocates.
37
    /// If you need an owned [`BezPath`] you can use [`to_path`] instead.
38
    ///
39
    /// # Tolerance
40
    ///
41
    /// The `tolerance` parameter controls the accuracy of
42
    /// conversion of geometric primitives to Bézier curves, as
43
    /// curves such as circles cannot be represented exactly but
44
    /// only approximated. For drawing as in UI elements, a value
45
    /// of 0.1 is appropriate, as it is unlikely to be visible to
46
    /// the eye. For scientific applications, a smaller value
47
    /// might be appropriate. Note that in general the number of
48
    /// cubic Bézier segments scales as `tolerance ^ (-1/6)`.
49
    ///
50
    /// [`as_rect`]: Shape::as_rect
51
    /// [`as_line`]: Shape::as_line
52
    /// [`to_path`]: Shape::to_path
53
    fn path_elements(&self, tolerance: f64) -> Self::PathElementsIter<'_>;
54
55
    /// Convert to a Bézier path.
56
    ///
57
    /// This always allocates. It is appropriate when both the source
58
    /// shape and the resulting path are to be retained.
59
    ///
60
    /// If you only need to iterate the elements (such as to convert them to
61
    /// drawing commands for a given 2D graphics API) you should prefer
62
    /// [`path_elements`], which can avoid allocating where possible.
63
    ///
64
    /// The `tolerance` parameter is the same as for [`path_elements`].
65
    ///
66
    /// [`path_elements`]: Shape::path_elements
67
0
    fn to_path(&self, tolerance: f64) -> BezPath {
68
0
        self.path_elements(tolerance).collect()
69
0
    }
70
71
    /// Convert into a Bézier path.
72
    ///
73
    /// This allocates in the general case, but is zero-cost if the
74
    /// shape is already a [`BezPath`].
75
    ///
76
    /// The `tolerance` parameter is the same as for [`path_elements()`].
77
    ///
78
    /// [`path_elements()`]: Shape::path_elements
79
0
    fn into_path(self, tolerance: f64) -> BezPath
80
0
    where
81
0
        Self: Sized,
82
    {
83
0
        self.to_path(tolerance)
84
0
    }
85
86
    /// Returns an iterator over this shape expressed as Bézier path
87
    /// _segments_ ([`PathSeg`]s).
88
    ///
89
    /// The allocation behaviour and `tolerance` parameter are the
90
    /// same as for [`path_elements()`]
91
    ///
92
    /// [`PathSeg`]: crate::PathSeg
93
    /// [`path_elements()`]: Shape::path_elements
94
0
    fn path_segments(&self, tolerance: f64) -> Segments<Self::PathElementsIter<'_>> {
95
0
        segments(self.path_elements(tolerance))
96
0
    }
97
98
    /// Signed area.
99
    ///
100
    /// This method only produces meaningful results with closed shapes.
101
    ///
102
    /// The convention for positive area is that y increases when x is
103
    /// positive. Thus, it is clockwise when down is increasing y (the
104
    /// usual convention for graphics), and anticlockwise when
105
    /// up is increasing y (the usual convention for math).
106
    fn area(&self) -> f64;
107
108
    /// Total length of perimeter.
109
    //FIXME: document the accuracy param
110
    fn perimeter(&self, accuracy: f64) -> f64;
111
112
    /// The [winding number] of a point.
113
    ///
114
    /// This method only produces meaningful results with closed shapes.
115
    ///
116
    /// The sign of the winding number is consistent with that of [`area`],
117
    /// meaning it is +1 when the point is inside a positive area shape
118
    /// and -1 when it is inside a negative area shape. Of course, greater
119
    /// magnitude values are also possible when the shape is more complex.
120
    ///
121
    /// [`area`]: Shape::area
122
    /// [winding number]: https://mathworld.wolfram.com/ContourWindingNumber.html
123
    fn winding(&self, pt: Point) -> i32;
124
125
    /// Returns `true` if the [`Point`] is inside this shape.
126
    ///
127
    /// This is only meaningful for closed shapes. Some shapes may have specialized
128
    /// implementations of this function or of [`winding`] determination.
129
    ///
130
    /// The default implementation uses the non-zero winding rule.
131
    ///
132
    /// To determine containment using the even-odd winding rule, check the
133
    /// [`winding`] directly.
134
    ///
135
    /// [`winding`]: Self::winding
136
0
    fn contains(&self, pt: Point) -> bool {
137
0
        self.winding(pt) != 0
138
0
    }
139
140
    /// The smallest rectangle that encloses the shape.
141
    fn bounding_box(&self) -> Rect;
142
143
    /// If the shape is a line, make it available.
144
0
    fn as_line(&self) -> Option<Line> {
145
0
        None
146
0
    }
147
148
    /// If the shape is a rectangle, make it available.
149
0
    fn as_rect(&self) -> Option<Rect> {
150
0
        None
151
0
    }
152
153
    /// If the shape is a rounded rectangle, make it available.
154
0
    fn as_rounded_rect(&self) -> Option<RoundedRect> {
155
0
        None
156
0
    }
157
158
    /// If the shape is a circle, make it available.
159
0
    fn as_circle(&self) -> Option<Circle> {
160
0
        None
161
0
    }
162
163
    /// If the shape is stored as a slice of path elements, make
164
    /// that available.
165
    ///
166
    /// Note: when GAT's land, a method like `path_elements` would be
167
    /// able to iterate through the slice with no extra allocation,
168
    /// without making any assumption that storage is contiguous.
169
0
    fn as_path_slice(&self) -> Option<&[PathEl]> {
170
0
        None
171
0
    }
172
}
173
174
/// Blanket implementation so `impl Shape` will accept owned or reference.
175
impl<'a, T: Shape> Shape for &'a T {
176
    type PathElementsIter<'iter>
177
        = T::PathElementsIter<'iter>
178
    where
179
        T: 'iter,
180
        'a: 'iter;
181
182
0
    fn path_elements(&self, tolerance: f64) -> Self::PathElementsIter<'_> {
183
0
        (*self).path_elements(tolerance)
184
0
    }
185
186
0
    fn to_path(&self, tolerance: f64) -> BezPath {
187
0
        (*self).to_path(tolerance)
188
0
    }
189
190
0
    fn path_segments(&self, tolerance: f64) -> Segments<Self::PathElementsIter<'_>> {
191
0
        (*self).path_segments(tolerance)
192
0
    }
193
194
0
    fn area(&self) -> f64 {
195
0
        (*self).area()
196
0
    }
197
198
0
    fn perimeter(&self, accuracy: f64) -> f64 {
199
0
        (*self).perimeter(accuracy)
200
0
    }
201
202
0
    fn winding(&self, pt: Point) -> i32 {
203
0
        (*self).winding(pt)
204
0
    }
205
206
0
    fn bounding_box(&self) -> Rect {
207
0
        (*self).bounding_box()
208
0
    }
209
210
0
    fn as_line(&self) -> Option<Line> {
211
0
        (*self).as_line()
212
0
    }
213
214
0
    fn as_rect(&self) -> Option<Rect> {
215
0
        (*self).as_rect()
216
0
    }
217
218
0
    fn as_rounded_rect(&self) -> Option<RoundedRect> {
219
0
        (*self).as_rounded_rect()
220
0
    }
221
222
0
    fn as_circle(&self) -> Option<Circle> {
223
0
        (*self).as_circle()
224
0
    }
225
226
0
    fn as_path_slice(&self) -> Option<&[PathEl]> {
227
0
        (*self).as_path_slice()
228
0
    }
229
}