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
16pub enum PathStyle {
18 Stroke(StrokeOptions),
20 Fill(FillOptions),
22}
23
24pub struct PathBuilder {
26 raw: lyon::path::builder::WithSvg<lyon::path::BuilderImpl>,
27 transform: Option<lyon::math::Transform>,
28 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 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 pub fn stroke_rounded(width: Pixels) -> Self {
99 Self {
100 style: PathStyle::Stroke(
101 StrokeOptions::default()
102 .with_line_width(width.0)
103 .with_line_cap(lyon::tessellation::LineCap::Round)
104 .with_line_join(lyon::tessellation::LineJoin::Round),
105 ),
106 ..Self::default()
107 }
108 }
109
110 pub fn fill() -> Self {
112 Self::default()
113 }
114
115 pub fn with_style(self, style: PathStyle) -> Self {
117 Self { style, ..self }
118 }
119
120 pub fn dash_array(mut self, dash_array: &[Pixels]) -> Self {
124 let array = if dash_array.len() % 2 == 1 {
127 let mut new_dash_array = dash_array.to_vec();
128 new_dash_array.extend_from_slice(dash_array);
129 new_dash_array
130 } else {
131 dash_array.to_vec()
132 };
133
134 self.dash_array = Some(array);
135 self
136 }
137
138 #[inline]
140 pub fn move_to(&mut self, to: Point<Pixels>) {
141 self.raw.move_to(to.into());
142 }
143
144 #[inline]
146 pub fn line_to(&mut self, to: Point<Pixels>) {
147 self.raw.line_to(to.into());
148 }
149
150 #[inline]
152 pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
153 self.raw.quadratic_bezier_to(ctrl.into(), to.into());
154 }
155
156 #[inline]
159 pub fn cubic_bezier_to(
160 &mut self,
161 to: Point<Pixels>,
162 control_a: Point<Pixels>,
163 control_b: Point<Pixels>,
164 ) {
165 self.raw
166 .cubic_bezier_to(control_a.into(), control_b.into(), to.into());
167 }
168
169 pub fn arc_to(
171 &mut self,
172 radii: Point<Pixels>,
173 x_rotation: Pixels,
174 large_arc: bool,
175 sweep: bool,
176 to: Point<Pixels>,
177 ) {
178 self.raw.arc_to(
179 radii.into(),
180 Angle::degrees(x_rotation.into()),
181 ArcFlags { large_arc, sweep },
182 to.into(),
183 );
184 }
185
186 pub fn relative_arc_to(
188 &mut self,
189 radii: Point<Pixels>,
190 x_rotation: Pixels,
191 large_arc: bool,
192 sweep: bool,
193 to: Point<Pixels>,
194 ) {
195 self.raw.relative_arc_to(
196 radii.into(),
197 Angle::degrees(x_rotation.into()),
198 ArcFlags { large_arc, sweep },
199 to.into(),
200 );
201 }
202
203 pub fn add_polygon(&mut self, points: &[Point<Pixels>], closed: bool) {
205 let points = points.iter().copied().map(|p| p.into()).collect::<Vec<_>>();
206 self.raw.add_polygon(Polygon {
207 points: points.as_ref(),
208 closed,
209 });
210 }
211
212 #[inline]
214 pub fn close(&mut self) {
215 self.raw.close();
216 }
217
218 #[inline]
220 pub fn transform(&mut self, transform: Transform) {
221 self.transform = Some(transform);
222 }
223
224 #[inline]
226 pub fn translate(&mut self, to: Point<Pixels>) {
227 if let Some(transform) = self.transform {
228 self.transform = Some(transform.then_translate(Vector2D::new(to.x.0, to.y.0)));
229 } else {
230 self.transform = Some(Transform::translation(to.x.0, to.y.0))
231 }
232 }
233
234 #[inline]
236 pub fn scale(&mut self, scale: f32) {
237 if let Some(transform) = self.transform {
238 self.transform = Some(transform.then_scale(scale, scale));
239 } else {
240 self.transform = Some(Transform::scale(scale, scale));
241 }
242 }
243
244 #[inline]
248 pub fn rotate(&mut self, angle: f32) {
249 let radians = angle.to_radians();
250 if let Some(transform) = self.transform {
251 self.transform = Some(transform.then_rotate(Angle::radians(radians)));
252 } else {
253 self.transform = Some(Transform::rotation(Angle::radians(radians)));
254 }
255 }
256
257 #[inline]
259 pub fn build(self) -> Result<Path<Pixels>, Error> {
260 let path = if let Some(transform) = self.transform {
261 self.raw.build().transformed(&transform)
262 } else {
263 self.raw.build()
264 };
265
266 match self.style {
267 PathStyle::Stroke(options) => Self::tessellate_stroke(self.dash_array, &path, &options),
268 PathStyle::Fill(options) => Self::tessellate_fill(&path, &options),
269 }
270 }
271
272 fn tessellate_fill(
273 path: &lyon::path::Path,
274 options: &FillOptions,
275 ) -> Result<Path<Pixels>, Error> {
276 let mut buf: VertexBuffers<lyon::math::Point, u16> = VertexBuffers::new();
278 let mut tessellator = FillTessellator::new();
279
280 tessellator.tessellate_path(
282 path,
283 options,
284 &mut BuffersBuilder::new(&mut buf, |vertex: FillVertex| vertex.position()),
285 )?;
286
287 Ok(Self::build_path(buf))
288 }
289
290 fn tessellate_stroke(
291 dash_array: Option<Vec<Pixels>>,
292 path: &lyon::path::Path,
293 options: &StrokeOptions,
294 ) -> Result<Path<Pixels>, Error> {
295 let path = if let Some(dash_array) = dash_array {
296 let measurements = lyon::algorithms::measure::PathMeasurements::from_path(path, 0.01);
297 let mut sampler = measurements
298 .create_sampler(path, lyon::algorithms::measure::SampleType::Normalized);
299 let mut builder = lyon::path::Path::builder();
300
301 let total_length = sampler.length();
302 let dash_array_len = dash_array.len();
303 let mut pos = 0.;
304 let mut dash_index = 0;
305 while pos < total_length {
306 let dash_length = dash_array[dash_index % dash_array_len].0;
307 let next_pos = (pos + dash_length).min(total_length);
308 if dash_index % 2 == 0 {
309 let start = pos / total_length;
310 let end = next_pos / total_length;
311 sampler.split_range(start..end, &mut builder);
312 }
313 pos = next_pos;
314 dash_index += 1;
315 }
316
317 &builder.build()
318 } else {
319 path
320 };
321
322 let mut buf: VertexBuffers<lyon::math::Point, u16> = VertexBuffers::new();
324 let mut tessellator = StrokeTessellator::new();
325
326 tessellator.tessellate_path(
328 path,
329 options,
330 &mut BuffersBuilder::new(&mut buf, |vertex: StrokeVertex| vertex.position()),
331 )?;
332
333 Ok(Self::build_path(buf))
334 }
335
336 pub fn build_path(buf: VertexBuffers<lyon::math::Point, u16>) -> Path<Pixels> {
338 if buf.vertices.is_empty() {
339 return Path::new(Point::default());
340 }
341
342 let first_point = buf.vertices[0];
343
344 let mut path = Path::new(first_point.into());
345 for i in 0..buf.indices.len() / 3 {
346 let i0 = buf.indices[i * 3] as usize;
347 let i1 = buf.indices[i * 3 + 1] as usize;
348 let i2 = buf.indices[i * 3 + 2] as usize;
349
350 let v0 = buf.vertices[i0];
351 let v1 = buf.vertices[i1];
352 let v2 = buf.vertices[i2];
353
354 path.push_triangle(
355 (v0.into(), v1.into(), v2.into()),
356 (point(0., 1.), point(0., 1.), point(0., 1.)),
357 );
358 }
359
360 path
361 }
362}