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