path_builder.rs

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