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