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: Sized {
|
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 | fn to_path(&self, tolerance: f64) -> BezPath {
|
68 | self.path_elements(tolerance).collect()
|
69 | }
|
70 |
|
71 | #[deprecated (since = "0.7.0" , note = "Use path_elements instead" )]
|
72 | #[doc (hidden)]
|
73 | fn to_bez_path(&self, tolerance: f64) -> Self::PathElementsIter<'_> {
|
74 | self.path_elements(tolerance)
|
75 | }
|
76 | /// Convert into a Bézier path.
|
77 | ///
|
78 | /// This allocates in the general case, but is zero-cost if the
|
79 | /// shape is already a [`BezPath`].
|
80 | ///
|
81 | /// The `tolerance` parameter is the same as for [`path_elements()`].
|
82 | ///
|
83 | /// [`path_elements()`]: Shape::path_elements
|
84 | fn into_path(self, tolerance: f64) -> BezPath {
|
85 | self.to_path(tolerance)
|
86 | }
|
87 |
|
88 | #[deprecated (since = "0.7.0" , note = "Use into_path instead" )]
|
89 | #[doc (hidden)]
|
90 | fn into_bez_path(self, tolerance: f64) -> BezPath {
|
91 | self.into_path(tolerance)
|
92 | }
|
93 |
|
94 | /// Returns an iterator over this shape expressed as Bézier path
|
95 | /// _segments_ ([`PathSeg`]s).
|
96 | ///
|
97 | /// The allocation behaviour and `tolerance` parameter are the
|
98 | /// same as for [`path_elements()`]
|
99 | ///
|
100 | /// [`PathSeg`]: crate::PathSeg
|
101 | /// [`path_elements()`]: Shape::path_elements
|
102 | fn path_segments(&self, tolerance: f64) -> Segments<Self::PathElementsIter<'_>> {
|
103 | segments(self.path_elements(tolerance))
|
104 | }
|
105 |
|
106 | /// Signed area.
|
107 | ///
|
108 | /// This method only produces meaningful results with closed shapes.
|
109 | ///
|
110 | /// The convention for positive area is that y increases when x is
|
111 | /// positive. Thus, it is clockwise when down is increasing y (the
|
112 | /// usual convention for graphics), and anticlockwise when
|
113 | /// up is increasing y (the usual convention for math).
|
114 | fn area(&self) -> f64;
|
115 |
|
116 | /// Total length of perimeter.
|
117 | //FIXME: document the accuracy param
|
118 | fn perimeter(&self, accuracy: f64) -> f64;
|
119 |
|
120 | /// The [winding number] of a point.
|
121 | ///
|
122 | /// This method only produces meaningful results with closed shapes.
|
123 | ///
|
124 | /// The sign of the winding number is consistent with that of [`area`],
|
125 | /// meaning it is +1 when the point is inside a positive area shape
|
126 | /// and -1 when it is inside a negative area shape. Of course, greater
|
127 | /// magnitude values are also possible when the shape is more complex.
|
128 | ///
|
129 | /// [`area`]: Shape::area
|
130 | /// [winding number]: https://mathworld.wolfram.com/ContourWindingNumber.html
|
131 | fn winding(&self, pt: Point) -> i32;
|
132 |
|
133 | /// Returns `true` if the [`Point`] is inside this shape.
|
134 | ///
|
135 | /// This is only meaningful for closed shapes.
|
136 | fn contains(&self, pt: Point) -> bool {
|
137 | self.winding(pt) != 0
|
138 | }
|
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 | fn as_line(&self) -> Option<Line> {
|
145 | None
|
146 | }
|
147 |
|
148 | /// If the shape is a rectangle, make it available.
|
149 | fn as_rect(&self) -> Option<Rect> {
|
150 | None
|
151 | }
|
152 |
|
153 | /// If the shape is a rounded rectangle, make it available.
|
154 | fn as_rounded_rect(&self) -> Option<RoundedRect> {
|
155 | None
|
156 | }
|
157 |
|
158 | /// If the shape is a circle, make it available.
|
159 | fn as_circle(&self) -> Option<Circle> {
|
160 | None
|
161 | }
|
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 | fn as_path_slice(&self) -> Option<&[PathEl]> {
|
170 | None
|
171 | }
|
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 |
|
178 | = T::PathElementsIter<'iter> where T: 'iter, 'a: 'iter;
|
179 |
|
180 | fn path_elements(&self, tolerance: f64) -> Self::PathElementsIter<'_> {
|
181 | (*self).path_elements(tolerance)
|
182 | }
|
183 |
|
184 | fn to_path(&self, tolerance: f64) -> BezPath {
|
185 | (*self).to_path(tolerance)
|
186 | }
|
187 |
|
188 | fn path_segments(&self, tolerance: f64) -> Segments<Self::PathElementsIter<'_>> {
|
189 | (*self).path_segments(tolerance)
|
190 | }
|
191 |
|
192 | fn area(&self) -> f64 {
|
193 | (*self).area()
|
194 | }
|
195 |
|
196 | fn perimeter(&self, accuracy: f64) -> f64 {
|
197 | (*self).perimeter(accuracy)
|
198 | }
|
199 |
|
200 | fn winding(&self, pt: Point) -> i32 {
|
201 | (*self).winding(pt)
|
202 | }
|
203 |
|
204 | fn bounding_box(&self) -> Rect {
|
205 | (*self).bounding_box()
|
206 | }
|
207 |
|
208 | fn as_line(&self) -> Option<Line> {
|
209 | (*self).as_line()
|
210 | }
|
211 |
|
212 | fn as_rect(&self) -> Option<Rect> {
|
213 | (*self).as_rect()
|
214 | }
|
215 |
|
216 | fn as_rounded_rect(&self) -> Option<RoundedRect> {
|
217 | (*self).as_rounded_rect()
|
218 | }
|
219 |
|
220 | fn as_circle(&self) -> Option<Circle> {
|
221 | (*self).as_circle()
|
222 | }
|
223 |
|
224 | fn as_path_slice(&self) -> Option<&[PathEl]> {
|
225 | (*self).as_path_slice()
|
226 | }
|
227 | }
|
228 | |