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 | |