1// Copyright 2019 the Kurbo Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! A generic trait for shapes.
5
6use 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
17pub 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.
182impl<'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