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