1use anyhow::Error;
2use etagere::euclid::Vector2D;
3use lyon::geom::Angle;
4use lyon::path::traits::SvgPathBuilder;
5use lyon::path::{ArcFlags, Polygon};
6use lyon::tessellation::{
7 BuffersBuilder, FillTessellator, FillVertex, StrokeTessellator, StrokeVertex, VertexBuffers,
8};
9
10pub use lyon::math::Transform;
11pub use lyon::tessellation::{FillOptions, FillRule, StrokeOptions};
12
13use crate::{Path, Pixels, Point, point};
14
15pub enum PathStyle {
17 Stroke(StrokeOptions),
19 Fill(FillOptions),
21}
22
23pub struct PathBuilder {
25 raw: lyon::path::builder::WithSvg<lyon::path::BuilderImpl>,
26 transform: Option<lyon::math::Transform>,
27 pub style: PathStyle,
29 dash_array: Option<Vec<Pixels>>,
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 Default for PathBuilder {
51 fn default() -> Self {
52 Self {
53 raw: lyon::path::Path::builder().with_svg(),
54 style: PathStyle::Fill(FillOptions::default()),
55 transform: None,
56 dash_array: None,
57 }
58 }
59}
60
61impl PathBuilder {
62 pub fn stroke(width: Pixels) -> Self {
64 Self {
65 style: PathStyle::Stroke(StrokeOptions::default().with_line_width(width.0)),
66 ..Self::default()
67 }
68 }
69
70 pub fn fill() -> Self {
72 Self::default()
73 }
74
75 pub fn with_style(self, style: PathStyle) -> Self {
77 Self { style, ..self }
78 }
79
80 pub fn dash_array(mut self, dash_array: &[Pixels]) -> Self {
84 let array = if dash_array.len() % 2 == 1 {
87 let mut new_dash_array = dash_array.to_vec();
88 new_dash_array.extend_from_slice(dash_array);
89 new_dash_array
90 } else {
91 dash_array.to_vec()
92 };
93
94 self.dash_array = Some(array);
95 self
96 }
97
98 #[inline]
100 pub fn move_to(&mut self, to: Point<Pixels>) {
101 self.raw.move_to(to.into());
102 }
103
104 #[inline]
106 pub fn line_to(&mut self, to: Point<Pixels>) {
107 self.raw.line_to(to.into());
108 }
109
110 #[inline]
112 pub fn curve_to(&mut self, to: Point<Pixels>, ctrl: Point<Pixels>) {
113 self.raw.quadratic_bezier_to(ctrl.into(), to.into());
114 }
115
116 #[inline]
119 pub fn cubic_bezier_to(
120 &mut self,
121 to: Point<Pixels>,
122 control_a: Point<Pixels>,
123 control_b: Point<Pixels>,
124 ) {
125 self.raw
126 .cubic_bezier_to(control_a.into(), control_b.into(), to.into());
127 }
128
129 pub fn arc_to(
131 &mut self,
132 radii: Point<Pixels>,
133 x_rotation: Pixels,
134 large_arc: bool,
135 sweep: bool,
136 to: Point<Pixels>,
137 ) {
138 self.raw.arc_to(
139 radii.into(),
140 Angle::degrees(x_rotation.into()),
141 ArcFlags { large_arc, sweep },
142 to.into(),
143 );
144 }
145
146 pub fn relative_arc_to(
148 &mut self,
149 radii: Point<Pixels>,
150 x_rotation: Pixels,
151 large_arc: bool,
152 sweep: bool,
153 to: Point<Pixels>,
154 ) {
155 self.raw.relative_arc_to(
156 radii.into(),
157 Angle::degrees(x_rotation.into()),
158 ArcFlags { large_arc, sweep },
159 to.into(),
160 );
161 }
162
163 pub fn add_polygon(&mut self, points: &[Point<Pixels>], closed: bool) {
165 let points = points.iter().copied().map(|p| p.into()).collect::<Vec<_>>();
166 self.raw.add_polygon(Polygon {
167 points: points.as_ref(),
168 closed,
169 });
170 }
171
172 #[inline]
174 pub fn close(&mut self) {
175 self.raw.close();
176 }
177
178 #[inline]
180 pub fn transform(&mut self, transform: Transform) {
181 self.transform = Some(transform);
182 }
183
184 #[inline]
186 pub fn translate(&mut self, to: Point<Pixels>) {
187 if let Some(transform) = self.transform {
188 self.transform = Some(transform.then_translate(Vector2D::new(to.x.0, to.y.0)));
189 } else {
190 self.transform = Some(Transform::translation(to.x.0, to.y.0))
191 }
192 }
193
194 #[inline]
196 pub fn scale(&mut self, scale: f32) {
197 if let Some(transform) = self.transform {
198 self.transform = Some(transform.then_scale(scale, scale));
199 } else {
200 self.transform = Some(Transform::scale(scale, scale));
201 }
202 }
203
204 #[inline]
208 pub fn rotate(&mut self, angle: f32) {
209 let radians = angle.to_radians();
210 if let Some(transform) = self.transform {
211 self.transform = Some(transform.then_rotate(Angle::radians(radians)));
212 } else {
213 self.transform = Some(Transform::rotation(Angle::radians(radians)));
214 }
215 }
216
217 #[inline]
219 pub fn build(self) -> Result<Path<Pixels>, Error> {
220 let path = if let Some(transform) = self.transform {
221 self.raw.build().transformed(&transform)
222 } else {
223 self.raw.build()
224 };
225
226 match self.style {
227 PathStyle::Stroke(options) => Self::tessellate_stroke(self.dash_array, &path, &options),
228 PathStyle::Fill(options) => Self::tessellate_fill(&path, &options),
229 }
230 }
231
232 fn tessellate_fill(
233 path: &lyon::path::Path,
234 options: &FillOptions,
235 ) -> Result<Path<Pixels>, Error> {
236 let mut buf: VertexBuffers<lyon::math::Point, u16> = VertexBuffers::new();
238 let mut tessellator = FillTessellator::new();
239
240 tessellator.tessellate_path(
242 path,
243 options,
244 &mut BuffersBuilder::new(&mut buf, |vertex: FillVertex| vertex.position()),
245 )?;
246
247 Ok(Self::build_path(buf))
248 }
249
250 fn tessellate_stroke(
251 dash_array: Option<Vec<Pixels>>,
252 path: &lyon::path::Path,
253 options: &StrokeOptions,
254 ) -> Result<Path<Pixels>, Error> {
255 let path = if let Some(dash_array) = dash_array {
256 let measurements = lyon::algorithms::measure::PathMeasurements::from_path(path, 0.01);
257 let mut sampler = measurements
258 .create_sampler(path, lyon::algorithms::measure::SampleType::Normalized);
259 let mut builder = lyon::path::Path::builder();
260
261 let total_length = sampler.length();
262 let dash_array_len = dash_array.len();
263 let mut pos = 0.;
264 let mut dash_index = 0;
265 while pos < total_length {
266 let dash_length = dash_array[dash_index % dash_array_len].0;
267 let next_pos = (pos + dash_length).min(total_length);
268 if dash_index % 2 == 0 {
269 let start = pos / total_length;
270 let end = next_pos / total_length;
271 sampler.split_range(start..end, &mut builder);
272 }
273 pos = next_pos;
274 dash_index += 1;
275 }
276
277 &builder.build()
278 } else {
279 path
280 };
281
282 let mut buf: VertexBuffers<lyon::math::Point, u16> = VertexBuffers::new();
284 let mut tessellator = StrokeTessellator::new();
285
286 tessellator.tessellate_path(
288 path,
289 options,
290 &mut BuffersBuilder::new(&mut buf, |vertex: StrokeVertex| vertex.position()),
291 )?;
292
293 Ok(Self::build_path(buf))
294 }
295
296 pub fn build_path(buf: VertexBuffers<lyon::math::Point, u16>) -> Path<Pixels> {
298 if buf.vertices.is_empty() {
299 return Path::new(Point::default());
300 }
301
302 let first_point = buf.vertices[0];
303
304 let mut path = Path::new(first_point.into());
305 for i in 0..buf.indices.len() / 3 {
306 let i0 = buf.indices[i * 3] as usize;
307 let i1 = buf.indices[i * 3 + 1] as usize;
308 let i2 = buf.indices[i * 3 + 2] as usize;
309
310 let v0 = buf.vertices[i0];
311 let v1 = buf.vertices[i1];
312 let v2 = buf.vertices[i2];
313
314 path.push_triangle(
315 (v0.into(), v1.into(), v2.into()),
316 (point(0., 1.), point(0., 1.), point(0., 1.)),
317 );
318 }
319
320 path
321 }
322}