1use anyhow::Error;
2use etagere::euclid::{Point2D, Vector2D};
3use lyon::geom::Angle;
4use lyon::math::{Vector, vector};
5use lyon::path::traits::SvgPathBuilder;
6use lyon::path::{ArcFlags, Polygon};
7use lyon::tessellation::{
8 BuffersBuilder, FillTessellator, FillVertex, StrokeTessellator, StrokeVertex, VertexBuffers,
9};
10
11pub use lyon::math::Transform;
12pub use lyon::tessellation::{FillOptions, FillRule, StrokeOptions};
13
14use crate::{Path, Pixels, Point, point, px};
15
16/// Style of the PathBuilder
17pub enum PathStyle {
18 /// Stroke style
19 Stroke(StrokeOptions),
20 /// Fill style
21 Fill(FillOptions),
22}
23
24/// A [`Path`] builder.
25pub struct PathBuilder {
26 raw: lyon::path::builder::WithSvg<lyon::path::BuilderImpl>,
27 transform: Option<lyon::math::Transform>,
28 /// PathStyle of the PathBuilder
29 pub style: PathStyle,
30}
31
32impl From<lyon::path::Builder> for PathBuilder {
33 fn from(builder: lyon::path::Builder) -> Self {
34 Self {
35 raw: builder.with_svg(),
36 ..Default::default()
37 }
38 }
39}
40
41impl From<lyon::path::builder::WithSvg<lyon::path::BuilderImpl>> for PathBuilder {
42 fn from(raw: lyon::path::builder::WithSvg<lyon::path::BuilderImpl>) -> Self {
43 Self {
44 raw,
45 ..Default::default()
46 }
47 }
48}
49
50impl From<lyon::math::Point> for Point<Pixels> {
51 fn from(p: lyon::math::Point) -> Self {
52 point(px(p.x), px(p.y))
53 }
54}
55
56impl From<Point<Pixels>> for lyon::math::Point {
57 fn from(p: Point<Pixels>) -> Self {
58 lyon::math::point(p.x.0, p.y.0)
59 }
60}
61
62impl From<Point<Pixels>> for Vector {
63 fn from(p: Point<Pixels>) -> Self {
64 vector(p.x.0, p.y.0)
65 }
66}
67
68impl From<Point<Pixels>> for Point2D<f32, Pixels> {
69 fn from(p: Point<Pixels>) -> Self {
70 Point2D::new(p.x.0, p.y.0)
71 }
72}
73
74impl Default for PathBuilder {
75 fn default() -> Self {
76 Self {
77 raw: lyon::path::Path::builder().with_svg(),
78 style: PathStyle::Fill(FillOptions::default()),
79 transform: None,
80 }
81 }
82}
83
84impl PathBuilder {
85 /// Creates a new [`PathBuilder`] to build a Stroke path.
86 pub fn stroke(width: Pixels) -> Self {
87 Self {
88 style: PathStyle::Stroke(StrokeOptions::default().with_line_width(width.0)),
89 ..Self::default()
90 }
91 }
92
93 /// Creates a new [`PathBuilder`] to build a Fill path.
94 pub fn fill() -> Self {
95 Self::default()
96 }
97
98 /// Sets the style of the [`PathBuilder`].
99 pub fn with_style(self, style: PathStyle) -> Self {
100 Self { style, ..self }
101 }
102
103 /// Move the current point to the given point.
104 #[inline]
105 pub fn move_to(&mut self, to: Point<Pixels>) {
106 self.raw.move_to(to.into());
107 }
108
109 /// Draw a straight line from the current point to the given point.
110 #[inline]
111 pub fn line_to(&mut self, to: Point<Pixels>) {
112 self.raw.line_to(to.into());
113 }
114
115 /// Draw a curve from the current point to the given point, using the given control point.
116 #[inline]
117 pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
118 self.raw.quadratic_bezier_to(ctrl.into(), to.into());
119 }
120
121 /// Adds a cubic Bézier to the [`Path`] given its two control points
122 /// and its end point.
123 #[inline]
124 pub fn cubic_bezier_to(
125 &mut self,
126 to: Point<Pixels>,
127 control_a: Point<Pixels>,
128 control_b: Point<Pixels>,
129 ) {
130 self.raw
131 .cubic_bezier_to(control_a.into(), control_b.into(), to.into());
132 }
133
134 /// Adds an elliptical arc.
135 pub fn arc_to(
136 &mut self,
137 radii: Point<Pixels>,
138 x_rotation: Pixels,
139 large_arc: bool,
140 sweep: bool,
141 to: Point<Pixels>,
142 ) {
143 self.raw.arc_to(
144 radii.into(),
145 Angle::degrees(x_rotation.into()),
146 ArcFlags { large_arc, sweep },
147 to.into(),
148 );
149 }
150
151 /// Equivalent to `arc_to` in relative coordinates.
152 pub fn relative_arc_to(
153 &mut self,
154 radii: Point<Pixels>,
155 x_rotation: Pixels,
156 large_arc: bool,
157 sweep: bool,
158 to: Point<Pixels>,
159 ) {
160 self.raw.relative_arc_to(
161 radii.into(),
162 Angle::degrees(x_rotation.into()),
163 ArcFlags { large_arc, sweep },
164 to.into(),
165 );
166 }
167
168 /// Adds a polygon.
169 pub fn add_polygon(&mut self, points: &[Point<Pixels>], closed: bool) {
170 let points = points.iter().copied().map(|p| p.into()).collect::<Vec<_>>();
171 self.raw.add_polygon(Polygon {
172 points: points.as_ref(),
173 closed,
174 });
175 }
176
177 /// Close the current sub-path.
178 #[inline]
179 pub fn close(&mut self) {
180 self.raw.close();
181 }
182
183 /// Applies a transform to the path.
184 #[inline]
185 pub fn transform(&mut self, transform: Transform) {
186 self.transform = Some(transform);
187 }
188
189 /// Applies a translation to the path.
190 #[inline]
191 pub fn translate(&mut self, to: Point<Pixels>) {
192 if let Some(transform) = self.transform {
193 self.transform = Some(transform.then_translate(Vector2D::new(to.x.0, to.y.0)));
194 } else {
195 self.transform = Some(Transform::translation(to.x.0, to.y.0))
196 }
197 }
198
199 /// Applies a scale to the path.
200 #[inline]
201 pub fn scale(&mut self, scale: f32) {
202 if let Some(transform) = self.transform {
203 self.transform = Some(transform.then_scale(scale, scale));
204 } else {
205 self.transform = Some(Transform::scale(scale, scale));
206 }
207 }
208
209 /// Applies a rotation to the path.
210 ///
211 /// The `angle` is in degrees value in the range 0.0 to 360.0.
212 #[inline]
213 pub fn rotate(&mut self, angle: f32) {
214 let radians = angle.to_radians();
215 if let Some(transform) = self.transform {
216 self.transform = Some(transform.then_rotate(Angle::radians(radians)));
217 } else {
218 self.transform = Some(Transform::rotation(Angle::radians(radians)));
219 }
220 }
221
222 /// Builds into a [`Path`].
223 #[inline]
224 pub fn build(self) -> Result<Path<Pixels>, Error> {
225 let path = if let Some(transform) = self.transform {
226 self.raw.build().transformed(&transform)
227 } else {
228 self.raw.build()
229 };
230
231 match self.style {
232 PathStyle::Stroke(options) => Self::tessellate_stroke(&path, &options),
233 PathStyle::Fill(options) => Self::tessellate_fill(&path, &options),
234 }
235 }
236
237 fn tessellate_fill(
238 path: &lyon::path::Path,
239 options: &FillOptions,
240 ) -> Result<Path<Pixels>, Error> {
241 // Will contain the result of the tessellation.
242 let mut buf: VertexBuffers<lyon::math::Point, u16> = VertexBuffers::new();
243 let mut tessellator = FillTessellator::new();
244
245 // Compute the tessellation.
246 tessellator.tessellate_path(
247 path,
248 options,
249 &mut BuffersBuilder::new(&mut buf, |vertex: FillVertex| vertex.position()),
250 )?;
251
252 Ok(Self::build_path(buf))
253 }
254
255 fn tessellate_stroke(
256 path: &lyon::path::Path,
257 options: &StrokeOptions,
258 ) -> Result<Path<Pixels>, Error> {
259 // Will contain the result of the tessellation.
260 let mut buf: VertexBuffers<lyon::math::Point, u16> = VertexBuffers::new();
261 let mut tessellator = StrokeTessellator::new();
262
263 // Compute the tessellation.
264 tessellator.tessellate_path(
265 path,
266 options,
267 &mut BuffersBuilder::new(&mut buf, |vertex: StrokeVertex| vertex.position()),
268 )?;
269
270 Ok(Self::build_path(buf))
271 }
272
273 /// Builds a [`Path`] from a [`lyon::VertexBuffers`].
274 pub fn build_path(buf: VertexBuffers<lyon::math::Point, u16>) -> Path<Pixels> {
275 if buf.vertices.is_empty() {
276 return Path::new(Point::default());
277 }
278
279 let first_point = buf.vertices[0];
280
281 let mut path = Path::new(first_point.into());
282 for i in 0..buf.indices.len() / 3 {
283 let i0 = buf.indices[i * 3] as usize;
284 let i1 = buf.indices[i * 3 + 1] as usize;
285 let i2 = buf.indices[i * 3 + 2] as usize;
286
287 let v0 = buf.vertices[i0];
288 let v1 = buf.vertices[i1];
289 let v2 = buf.vertices[i2];
290
291 path.push_triangle(
292 (v0.into(), v1.into(), v2.into()),
293 (point(0., 1.), point(0., 1.), point(0., 1.)),
294 );
295 }
296
297 path
298 }
299}