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