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 dash_array: Option<Vec<Pixels>>,
31}
32
33impl From<lyon::path::Builder> for PathBuilder {
34 fn from(builder: lyon::path::Builder) -> Self {
35 Self {
36 raw: builder.with_svg(),
37 ..Default::default()
38 }
39 }
40}
41
42impl From<lyon::path::builder::WithSvg<lyon::path::BuilderImpl>> for PathBuilder {
43 fn from(raw: lyon::path::builder::WithSvg<lyon::path::BuilderImpl>) -> Self {
44 Self {
45 raw,
46 ..Default::default()
47 }
48 }
49}
50
51impl From<lyon::math::Point> for Point<Pixels> {
52 fn from(p: lyon::math::Point) -> Self {
53 point(px(p.x), px(p.y))
54 }
55}
56
57impl From<Point<Pixels>> for lyon::math::Point {
58 fn from(p: Point<Pixels>) -> Self {
59 lyon::math::point(p.x.0, p.y.0)
60 }
61}
62
63impl From<Point<Pixels>> for Vector {
64 fn from(p: Point<Pixels>) -> Self {
65 vector(p.x.0, p.y.0)
66 }
67}
68
69impl From<Point<Pixels>> for Point2D<f32, Pixels> {
70 fn from(p: Point<Pixels>) -> Self {
71 Point2D::new(p.x.0, p.y.0)
72 }
73}
74
75impl Default for PathBuilder {
76 fn default() -> Self {
77 Self {
78 raw: lyon::path::Path::builder().with_svg(),
79 style: PathStyle::Fill(FillOptions::default()),
80 transform: None,
81 dash_array: None,
82 }
83 }
84}
85
86impl PathBuilder {
87 /// Creates a new [`PathBuilder`] to build a Stroke path.
88 pub fn stroke(width: Pixels) -> Self {
89 Self {
90 style: PathStyle::Stroke(StrokeOptions::default().with_line_width(width.0)),
91 ..Self::default()
92 }
93 }
94
95 /// Creates a new [`PathBuilder`] to build a Fill path.
96 pub fn fill() -> Self {
97 Self::default()
98 }
99
100 /// Sets the style of the [`PathBuilder`].
101 pub fn with_style(self, style: PathStyle) -> Self {
102 Self { style, ..self }
103 }
104
105 /// Sets the dash array of the [`PathBuilder`].
106 ///
107 /// [MDN](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Attribute/stroke-dasharray)
108 pub fn dash_array(mut self, dash_array: &[Pixels]) -> Self {
109 // If an odd number of values is provided, then the list of values is repeated to yield an even number of values.
110 // Thus, 5,3,2 is equivalent to 5,3,2,5,3,2.
111 let array = if dash_array.len() % 2 == 1 {
112 let mut new_dash_array = dash_array.to_vec();
113 new_dash_array.extend_from_slice(dash_array);
114 new_dash_array
115 } else {
116 dash_array.to_vec()
117 };
118
119 self.dash_array = Some(array);
120 self
121 }
122
123 /// Move the current point to the given point.
124 #[inline]
125 pub fn move_to(&mut self, to: Point<Pixels>) {
126 self.raw.move_to(to.into());
127 }
128
129 /// Draw a straight line from the current point to the given point.
130 #[inline]
131 pub fn line_to(&mut self, to: Point<Pixels>) {
132 self.raw.line_to(to.into());
133 }
134
135 /// Draw a curve from the current point to the given point, using the given control point.
136 #[inline]
137 pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
138 self.raw.quadratic_bezier_to(ctrl.into(), to.into());
139 }
140
141 /// Adds a cubic Bézier to the [`Path`] given its two control points
142 /// and its end point.
143 #[inline]
144 pub fn cubic_bezier_to(
145 &mut self,
146 to: Point<Pixels>,
147 control_a: Point<Pixels>,
148 control_b: Point<Pixels>,
149 ) {
150 self.raw
151 .cubic_bezier_to(control_a.into(), control_b.into(), to.into());
152 }
153
154 /// Adds an elliptical arc.
155 pub fn arc_to(
156 &mut self,
157 radii: Point<Pixels>,
158 x_rotation: Pixels,
159 large_arc: bool,
160 sweep: bool,
161 to: Point<Pixels>,
162 ) {
163 self.raw.arc_to(
164 radii.into(),
165 Angle::degrees(x_rotation.into()),
166 ArcFlags { large_arc, sweep },
167 to.into(),
168 );
169 }
170
171 /// Equivalent to `arc_to` in relative coordinates.
172 pub fn relative_arc_to(
173 &mut self,
174 radii: Point<Pixels>,
175 x_rotation: Pixels,
176 large_arc: bool,
177 sweep: bool,
178 to: Point<Pixels>,
179 ) {
180 self.raw.relative_arc_to(
181 radii.into(),
182 Angle::degrees(x_rotation.into()),
183 ArcFlags { large_arc, sweep },
184 to.into(),
185 );
186 }
187
188 /// Adds a polygon.
189 pub fn add_polygon(&mut self, points: &[Point<Pixels>], closed: bool) {
190 let points = points.iter().copied().map(|p| p.into()).collect::<Vec<_>>();
191 self.raw.add_polygon(Polygon {
192 points: points.as_ref(),
193 closed,
194 });
195 }
196
197 /// Close the current sub-path.
198 #[inline]
199 pub fn close(&mut self) {
200 self.raw.close();
201 }
202
203 /// Applies a transform to the path.
204 #[inline]
205 pub const fn transform(&mut self, transform: Transform) {
206 self.transform = Some(transform);
207 }
208
209 /// Applies a translation to the path.
210 #[inline]
211 pub fn translate(&mut self, to: Point<Pixels>) {
212 if let Some(transform) = self.transform {
213 self.transform = Some(transform.then_translate(Vector2D::new(to.x.0, to.y.0)));
214 } else {
215 self.transform = Some(Transform::translation(to.x.0, to.y.0))
216 }
217 }
218
219 /// Applies a scale to the path.
220 #[inline]
221 pub fn scale(&mut self, scale: f32) {
222 if let Some(transform) = self.transform {
223 self.transform = Some(transform.then_scale(scale, scale));
224 } else {
225 self.transform = Some(Transform::scale(scale, scale));
226 }
227 }
228
229 /// Applies a rotation to the path.
230 ///
231 /// The `angle` is in degrees value in the range 0.0 to 360.0.
232 #[inline]
233 pub fn rotate(&mut self, angle: f32) {
234 let radians = angle.to_radians();
235 if let Some(transform) = self.transform {
236 self.transform = Some(transform.then_rotate(Angle::radians(radians)));
237 } else {
238 self.transform = Some(Transform::rotation(Angle::radians(radians)));
239 }
240 }
241
242 /// Builds into a [`Path`].
243 #[inline]
244 pub fn build(self) -> Result<Path<Pixels>, Error> {
245 let path = if let Some(transform) = self.transform {
246 self.raw.build().transformed(&transform)
247 } else {
248 self.raw.build()
249 };
250
251 match self.style {
252 PathStyle::Stroke(options) => Self::tessellate_stroke(self.dash_array, &path, &options),
253 PathStyle::Fill(options) => Self::tessellate_fill(&path, &options),
254 }
255 }
256
257 fn tessellate_fill(
258 path: &lyon::path::Path,
259 options: &FillOptions,
260 ) -> Result<Path<Pixels>, Error> {
261 // Will contain the result of the tessellation.
262 let mut buf: VertexBuffers<lyon::math::Point, u16> = VertexBuffers::new();
263 let mut tessellator = FillTessellator::new();
264
265 // Compute the tessellation.
266 tessellator.tessellate_path(
267 path,
268 options,
269 &mut BuffersBuilder::new(&mut buf, |vertex: FillVertex| vertex.position()),
270 )?;
271
272 Ok(Self::build_path(buf))
273 }
274
275 fn tessellate_stroke(
276 dash_array: Option<Vec<Pixels>>,
277 path: &lyon::path::Path,
278 options: &StrokeOptions,
279 ) -> Result<Path<Pixels>, Error> {
280 let path = if let Some(dash_array) = dash_array {
281 let measurements = lyon::algorithms::measure::PathMeasurements::from_path(path, 0.01);
282 let mut sampler = measurements
283 .create_sampler(path, lyon::algorithms::measure::SampleType::Normalized);
284 let mut builder = lyon::path::Path::builder();
285
286 let total_length = sampler.length();
287 let dash_array_len = dash_array.len();
288 let mut pos = 0.;
289 let mut dash_index = 0;
290 while pos < total_length {
291 let dash_length = dash_array[dash_index % dash_array_len].0;
292 let next_pos = (pos + dash_length).min(total_length);
293 if dash_index % 2 == 0 {
294 let start = pos / total_length;
295 let end = next_pos / total_length;
296 sampler.split_range(start..end, &mut builder);
297 }
298 pos = next_pos;
299 dash_index += 1;
300 }
301
302 &builder.build()
303 } else {
304 path
305 };
306
307 // Will contain the result of the tessellation.
308 let mut buf: VertexBuffers<lyon::math::Point, u16> = VertexBuffers::new();
309 let mut tessellator = StrokeTessellator::new();
310
311 // Compute the tessellation.
312 tessellator.tessellate_path(
313 path,
314 options,
315 &mut BuffersBuilder::new(&mut buf, |vertex: StrokeVertex| vertex.position()),
316 )?;
317
318 Ok(Self::build_path(buf))
319 }
320
321 /// Builds a [`Path`] from a [`lyon::tessellation::VertexBuffers`].
322 pub fn build_path(buf: VertexBuffers<lyon::math::Point, u16>) -> Path<Pixels> {
323 if buf.vertices.is_empty() {
324 return Path::new(Point::default());
325 }
326
327 let first_point = buf.vertices[0];
328
329 let mut path = Path::new(first_point.into());
330 for i in 0..buf.indices.len() / 3 {
331 let i0 = buf.indices[i * 3] as usize;
332 let i1 = buf.indices[i * 3 + 1] as usize;
333 let i2 = buf.indices[i * 3 + 2] as usize;
334
335 let v0 = buf.vertices[i0];
336 let v1 = buf.vertices[i1];
337 let v2 = buf.vertices[i2];
338
339 path.push_triangle(
340 (v0.into(), v1.into(), v2.into()),
341 (point(0., 1.), point(0., 1.), point(0., 1.)),
342 );
343 }
344
345 path
346 }
347}