kurbo/
shape.rs

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. Some shapes may have specialized
143    /// implementations of this function or of [`winding`] determination.
144    ///
145    /// The default implementation uses the non-zero winding rule.
146    ///
147    /// To determine containment using the even-odd winding rule, check the
148    /// [`winding`] directly.
149    ///
150    /// [`winding`]: Self::winding
151    fn contains(&self, pt: Point) -> bool {
152        self.winding(pt) != 0
153    }
154
155    /// The smallest rectangle that encloses the shape.
156    fn bounding_box(&self) -> Rect;
157
158    /// If the shape is a line, make it available.
159    fn as_line(&self) -> Option<Line> {
160        None
161    }
162
163    /// If the shape is a rectangle, make it available.
164    fn as_rect(&self) -> Option<Rect> {
165        None
166    }
167
168    /// If the shape is a rounded rectangle, make it available.
169    fn as_rounded_rect(&self) -> Option<RoundedRect> {
170        None
171    }
172
173    /// If the shape is a circle, make it available.
174    fn as_circle(&self) -> Option<Circle> {
175        None
176    }
177
178    /// If the shape is stored as a slice of path elements, make
179    /// that available.
180    ///
181    /// Note: when GAT's land, a method like `path_elements` would be
182    /// able to iterate through the slice with no extra allocation,
183    /// without making any assumption that storage is contiguous.
184    fn as_path_slice(&self) -> Option<&[PathEl]> {
185        None
186    }
187}
188
189/// Blanket implementation so `impl Shape` will accept owned or reference.
190impl<'a, T: Shape> Shape for &'a T {
191    type PathElementsIter<'iter>
192        = T::PathElementsIter<'iter>
193    where
194        T: 'iter,
195        'a: 'iter;
196
197    fn path_elements(&self, tolerance: f64) -> Self::PathElementsIter<'_> {
198        (*self).path_elements(tolerance)
199    }
200
201    fn to_path(&self, tolerance: f64) -> BezPath {
202        (*self).to_path(tolerance)
203    }
204
205    fn path_segments(&self, tolerance: f64) -> Segments<Self::PathElementsIter<'_>> {
206        (*self).path_segments(tolerance)
207    }
208
209    fn area(&self) -> f64 {
210        (*self).area()
211    }
212
213    fn perimeter(&self, accuracy: f64) -> f64 {
214        (*self).perimeter(accuracy)
215    }
216
217    fn winding(&self, pt: Point) -> i32 {
218        (*self).winding(pt)
219    }
220
221    fn bounding_box(&self) -> Rect {
222        (*self).bounding_box()
223    }
224
225    fn as_line(&self) -> Option<Line> {
226        (*self).as_line()
227    }
228
229    fn as_rect(&self) -> Option<Rect> {
230        (*self).as_rect()
231    }
232
233    fn as_rounded_rect(&self) -> Option<RoundedRect> {
234        (*self).as_rounded_rect()
235    }
236
237    fn as_circle(&self) -> Option<Circle> {
238        (*self).as_circle()
239    }
240
241    fn as_path_slice(&self) -> Option<&[PathEl]> {
242        (*self).as_path_slice()
243    }
244}