1 | //! Path building utilities. |
2 | //! |
3 | //! ## `PathBuilder` or `SvgPathBuilder` |
4 | //! |
5 | //! Path can be built via either of two abstractions: |
6 | //! |
7 | //! - [PathBuilder](trait.PathBuilder.html) is a simple and efficient interface which |
8 | //! does not deal with any ambiguous cases. |
9 | //! - [SvgPathBuilder](trait.SvgPathBuilder.html) is a higher-level interface that |
10 | //! follows SVG's specification, removing the the burden of dealing with special cases |
11 | //! from the user at a run-time cost. |
12 | //! |
13 | //! `SvgPathBuilder` may be a better choice when interactive with SVG, or dealing with arbitrary |
14 | //! input. `PathBuilder`. `PathBuilder` is probably a more useful trait to implement when creating |
15 | //! a new path data structure since all `PathBuilder` implementations automatically get an |
16 | //! `SvgPathBuilder` adapter (see the `with_svg` method). It may also make sense to use the |
17 | //! `PathBuilder` API when following a specification that behaves like SVG paths or when no |
18 | //! performance can be traded for convenience. |
19 | //! |
20 | //! ## Examples |
21 | //! |
22 | //! The following example shows how to create a simple path using the |
23 | //! [PathBuilder](trait.PathBuilder.html) interface. |
24 | //! |
25 | //! ``` |
26 | //! use lyon_path::{Path, geom::point}; |
27 | //! |
28 | //! let mut builder = Path::builder(); |
29 | //! |
30 | //! // All sub-paths *must* have be contained in a begin/end pair. |
31 | //! builder.begin(point(0.0, 0.0)); |
32 | //! builder.line_to(point(1.0, 0.0)); |
33 | //! builder.quadratic_bezier_to(point(2.0, 0.0), point(2.0, 1.0)); |
34 | //! builder.end(false); |
35 | //! |
36 | //! builder.begin(point(10.0, 0.0)); |
37 | //! builder.cubic_bezier_to(point(12.0, 2.0), point(11.0, 2.0), point(5.0, 0.0)); |
38 | //! builder.close(); // close() is equivalent to end(true). |
39 | //! |
40 | //! let path = builder.build(); |
41 | //! ``` |
42 | //! |
43 | //! The same path can be built using the `SvgPathBuilder` API: |
44 | //! |
45 | //! ``` |
46 | //! use lyon_path::{Path, geom::{point, vector}, builder::SvgPathBuilder}; |
47 | //! |
48 | //! // Use the SVG adapter. |
49 | //! let mut builder = Path::builder().with_svg(); |
50 | //! |
51 | //! // All sub-paths *must* have be contained in a begin/end pair. |
52 | //! builder.move_to(point(0.0, 0.0)); |
53 | //! builder.line_to(point(1.0, 0.0)); |
54 | //! builder.quadratic_bezier_to(point(2.0, 0.0), point(2.0, 1.0)); |
55 | //! // No need to explicitly end a sub-path. |
56 | //! |
57 | //! builder.move_to(point(10.0, 0.0)); |
58 | //! builder.relative_cubic_bezier_to(vector(2.0, 2.0), vector(1.0, 2.0), vector(-5.0, 0.0)); |
59 | //! builder.close(); |
60 | //! |
61 | //! let path = builder.build(); |
62 | //! ``` |
63 | //! |
64 | //! Implementors of the `PathBuilder` trait automatically gain access to a few other adapters. |
65 | //! For example a builder that approximates curves with a sequence of line segments: |
66 | //! |
67 | //! ``` |
68 | //! use lyon_path::{Path, geom::point}; |
69 | //! |
70 | //! let tolerance = 0.05;// maximum distance between a curve and its approximation. |
71 | //! let mut builder = Path::builder().flattened(tolerance); |
72 | //! |
73 | //! builder.begin(point(0.0, 0.0)); |
74 | //! builder.quadratic_bezier_to(point(1.0, 0.0), point(1.0, 1.0)); |
75 | //! builder.end(true); |
76 | //! |
77 | //! // The resulting path contains only Begin, Line and End events. |
78 | //! let path = builder.build(); |
79 | //! ``` |
80 | //! |
81 | |
82 | use crate::events::{Event, PathEvent}; |
83 | use crate::geom::{traits::Transformation, Arc, ArcFlags, LineSegment, SvgArc}; |
84 | use crate::math::*; |
85 | use crate::path::Verb; |
86 | use crate::polygon::Polygon; |
87 | use crate::{Attributes, EndpointId, Winding, NO_ATTRIBUTES}; |
88 | |
89 | use core::f32::consts::PI; |
90 | use core::marker::Sized; |
91 | |
92 | use alloc::vec; |
93 | use alloc::vec::Vec; |
94 | |
95 | #[cfg (not(feature = "std" ))] |
96 | use num_traits::Float; |
97 | |
98 | /// The radius of each corner of a rounded rectangle. |
99 | #[derive (Copy, Clone, PartialEq, PartialOrd, Debug, Default)] |
100 | pub struct BorderRadii { |
101 | pub top_left: f32, |
102 | pub top_right: f32, |
103 | pub bottom_left: f32, |
104 | pub bottom_right: f32, |
105 | } |
106 | |
107 | impl BorderRadii { |
108 | pub fn new(radius: f32) -> Self { |
109 | let r: f32 = radius.abs(); |
110 | BorderRadii { |
111 | top_left: r, |
112 | top_right: r, |
113 | bottom_left: r, |
114 | bottom_right: r, |
115 | } |
116 | } |
117 | } |
118 | |
119 | impl core::fmt::Display for BorderRadii { |
120 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { |
121 | // In the order of a well known convention (CSS) clockwise from top left |
122 | write!( |
123 | f, |
124 | "BorderRadii( {}, {}, {}, {})" , |
125 | self.top_left, self.top_right, self.bottom_left, self.bottom_right |
126 | ) |
127 | } |
128 | } |
129 | |
130 | /// A convenience wrapper for `PathBuilder` without custom attributes. |
131 | /// |
132 | /// See the [PathBuilder] trait. |
133 | /// |
134 | /// This simply forwards to an underlying `PathBuilder` implementation, |
135 | /// using no attributes. |
136 | #[derive (Clone, Debug, PartialEq, Hash)] |
137 | #[cfg_attr (feature = "serialization" , derive(Serialize, Deserialize))] |
138 | pub struct NoAttributes<B: PathBuilder> { |
139 | pub(crate) inner: B, |
140 | } |
141 | |
142 | impl<B: PathBuilder> NoAttributes<B> { |
143 | #[inline ] |
144 | pub fn wrap(inner: B) -> Self { |
145 | assert_eq!(inner.num_attributes(), 0); |
146 | NoAttributes { inner } |
147 | } |
148 | |
149 | pub fn new() -> Self |
150 | where |
151 | B: Default, |
152 | { |
153 | NoAttributes::wrap(B::default()) |
154 | } |
155 | |
156 | pub fn with_capacity(endpoints: usize, ctrl_points: usize) -> Self |
157 | where |
158 | B: Default, |
159 | { |
160 | let mut builder = B::default(); |
161 | builder.reserve(endpoints, ctrl_points); |
162 | NoAttributes::wrap(builder) |
163 | } |
164 | |
165 | /// Starts a new sub-path at a given position. |
166 | /// |
167 | /// There must be no sub-path in progress when this method is called. |
168 | /// `at` becomes the current position of the sub-path. |
169 | #[inline ] |
170 | pub fn begin(&mut self, at: Point) -> EndpointId { |
171 | self.inner.begin(at, NO_ATTRIBUTES) |
172 | } |
173 | |
174 | /// Ends the current sub path. |
175 | /// |
176 | /// A sub-path must be in progress when this method is called. |
177 | /// After this method is called, there is no sub-path in progress until |
178 | /// `begin` is called again. |
179 | #[inline ] |
180 | pub fn end(&mut self, close: bool) { |
181 | self.inner.end(close); |
182 | } |
183 | |
184 | /// Closes the current sub path. |
185 | /// |
186 | /// Shorthand for `builder.end(true)`. |
187 | #[inline ] |
188 | pub fn close(&mut self) { |
189 | self.inner.close(); |
190 | } |
191 | |
192 | /// Adds a line segment to the current sub-path. |
193 | /// |
194 | /// A sub-path must be in progress when this method is called. |
195 | #[inline ] |
196 | pub fn line_to(&mut self, to: Point) -> EndpointId { |
197 | self.inner.line_to(to, NO_ATTRIBUTES) |
198 | } |
199 | |
200 | /// Adds a quadratic bézier curve to the current sub-path. |
201 | /// |
202 | /// A sub-path must be in progress when this method is called. |
203 | #[inline ] |
204 | pub fn quadratic_bezier_to(&mut self, ctrl: Point, to: Point) -> EndpointId { |
205 | self.inner.quadratic_bezier_to(ctrl, to, NO_ATTRIBUTES) |
206 | } |
207 | |
208 | /// Adds a cubic bézier curve to the current sub-path. |
209 | /// |
210 | /// A sub-path must be in progress when this method is called. |
211 | #[inline ] |
212 | pub fn cubic_bezier_to(&mut self, ctrl1: Point, ctrl2: Point, to: Point) -> EndpointId { |
213 | self.inner.cubic_bezier_to(ctrl1, ctrl2, to, NO_ATTRIBUTES) |
214 | } |
215 | |
216 | /// Hints at the builder that a certain number of endpoints and control |
217 | /// points will be added. |
218 | /// |
219 | /// The Builder implementation may use this information to pre-allocate |
220 | /// memory as an optimization. |
221 | #[inline ] |
222 | pub fn reserve(&mut self, endpoints: usize, ctrl_points: usize) { |
223 | self.inner.reserve(endpoints, ctrl_points); |
224 | } |
225 | |
226 | /// Applies the provided path event. |
227 | /// |
228 | /// By default this calls one of `begin`, `end`, `line`, `quadratic_bezier_segment`, |
229 | /// or `cubic_bezier_segment` according to the path event. |
230 | /// |
231 | /// The requirements for each method apply to the corresponding event. |
232 | #[inline ] |
233 | pub fn path_event(&mut self, event: PathEvent) { |
234 | self.inner.path_event(event, NO_ATTRIBUTES); |
235 | } |
236 | |
237 | /// Adds a sub-path from a polygon. |
238 | /// |
239 | /// There must be no sub-path in progress when this method is called. |
240 | /// No sub-path is in progress after the method is called. |
241 | #[inline ] |
242 | pub fn add_polygon(&mut self, polygon: Polygon<Point>) { |
243 | self.inner.add_polygon(polygon, NO_ATTRIBUTES); |
244 | } |
245 | |
246 | /// Adds a sub-path containing a single point. |
247 | /// |
248 | /// There must be no sub-path in progress when this method is called. |
249 | /// No sub-path is in progress after the method is called. |
250 | #[inline ] |
251 | pub fn add_point(&mut self, at: Point) -> EndpointId { |
252 | self.inner.add_point(at, NO_ATTRIBUTES) |
253 | } |
254 | |
255 | /// Adds a sub-path containing a single line segment. |
256 | /// |
257 | /// There must be no sub-path in progress when this method is called. |
258 | /// No sub-path is in progress after the method is called. |
259 | #[inline ] |
260 | pub fn add_line_segment(&mut self, line: &LineSegment<f32>) -> (EndpointId, EndpointId) { |
261 | self.inner.add_line_segment(line, NO_ATTRIBUTES) |
262 | } |
263 | |
264 | /// Adds a sub-path containing an ellipse. |
265 | /// |
266 | /// There must be no sub-path in progress when this method is called. |
267 | /// No sub-path is in progress after the method is called. |
268 | #[inline ] |
269 | pub fn add_ellipse( |
270 | &mut self, |
271 | center: Point, |
272 | radii: Vector, |
273 | x_rotation: Angle, |
274 | winding: Winding, |
275 | ) { |
276 | self.inner |
277 | .add_ellipse(center, radii, x_rotation, winding, NO_ATTRIBUTES); |
278 | } |
279 | |
280 | /// Adds a sub-path containing a circle. |
281 | /// |
282 | /// There must be no sub-path in progress when this method is called. |
283 | /// No sub-path is in progress after the method is called. |
284 | #[inline ] |
285 | pub fn add_circle(&mut self, center: Point, radius: f32, winding: Winding) |
286 | where |
287 | B: Sized, |
288 | { |
289 | self.inner |
290 | .add_circle(center, radius, winding, NO_ATTRIBUTES); |
291 | } |
292 | |
293 | /// Adds a sub-path containing a rectangle. |
294 | /// |
295 | /// There must be no sub-path in progress when this method is called. |
296 | /// No sub-path is in progress after the method is called. |
297 | #[inline ] |
298 | pub fn add_rectangle(&mut self, rect: &Box2D, winding: Winding) { |
299 | self.inner.add_rectangle(rect, winding, NO_ATTRIBUTES); |
300 | } |
301 | |
302 | /// Adds a sub-path containing a rectangle. |
303 | /// |
304 | /// There must be no sub-path in progress when this method is called. |
305 | /// No sub-path is in progress after the method is called. |
306 | #[inline ] |
307 | pub fn add_rounded_rectangle(&mut self, rect: &Box2D, radii: &BorderRadii, winding: Winding) |
308 | where |
309 | B: Sized, |
310 | { |
311 | self.inner |
312 | .add_rounded_rectangle(rect, radii, winding, NO_ATTRIBUTES); |
313 | } |
314 | |
315 | /// Returns a builder that approximates all curves with sequences of line segments. |
316 | #[inline ] |
317 | pub fn flattened(self, tolerance: f32) -> NoAttributes<Flattened<B>> |
318 | where |
319 | B: Sized, |
320 | { |
321 | NoAttributes { |
322 | inner: Flattened::new(self.inner, tolerance), |
323 | } |
324 | } |
325 | |
326 | /// Returns a builder that applies the given transformation to all positions. |
327 | #[inline ] |
328 | pub fn transformed<Transform>( |
329 | self, |
330 | transform: Transform, |
331 | ) -> NoAttributes<Transformed<B, Transform>> |
332 | where |
333 | B: Sized, |
334 | Transform: Transformation<f32>, |
335 | { |
336 | NoAttributes { |
337 | inner: Transformed::new(self.inner, transform), |
338 | } |
339 | } |
340 | |
341 | /// Returns a builder that support SVG commands. |
342 | /// |
343 | /// This must be called before starting to add any sub-path. |
344 | #[inline ] |
345 | pub fn with_svg(self) -> WithSvg<B> |
346 | where |
347 | B: Sized, |
348 | { |
349 | WithSvg::new(self.inner) |
350 | } |
351 | |
352 | /// Builds a path object, consuming the builder. |
353 | #[inline ] |
354 | pub fn build<P>(self) -> P |
355 | where |
356 | B: Build<PathType = P>, |
357 | { |
358 | self.inner.build() |
359 | } |
360 | |
361 | #[inline ] |
362 | pub fn inner(&self) -> &B { |
363 | &self.inner |
364 | } |
365 | |
366 | #[inline ] |
367 | pub fn inner_mut(&mut self) -> &mut B { |
368 | &mut self.inner |
369 | } |
370 | |
371 | #[inline ] |
372 | pub fn into_inner(self) -> B { |
373 | self.inner |
374 | } |
375 | } |
376 | |
377 | impl<B: PathBuilder> PathBuilder for NoAttributes<B> { |
378 | #[inline ] |
379 | fn num_attributes(&self) -> usize { |
380 | 0 |
381 | } |
382 | |
383 | #[inline ] |
384 | fn begin(&mut self, at: Point, _attributes: Attributes) -> EndpointId { |
385 | self.inner.begin(at, NO_ATTRIBUTES) |
386 | } |
387 | |
388 | #[inline ] |
389 | fn end(&mut self, close: bool) { |
390 | self.inner.end(close); |
391 | } |
392 | |
393 | #[inline ] |
394 | fn line_to(&mut self, to: Point, _attributes: Attributes) -> EndpointId { |
395 | self.inner.line_to(to, NO_ATTRIBUTES) |
396 | } |
397 | |
398 | #[inline ] |
399 | fn quadratic_bezier_to( |
400 | &mut self, |
401 | ctrl: Point, |
402 | to: Point, |
403 | _attributes: Attributes, |
404 | ) -> EndpointId { |
405 | self.inner.quadratic_bezier_to(ctrl, to, NO_ATTRIBUTES) |
406 | } |
407 | |
408 | #[inline ] |
409 | fn cubic_bezier_to( |
410 | &mut self, |
411 | ctrl1: Point, |
412 | ctrl2: Point, |
413 | to: Point, |
414 | _attributes: Attributes, |
415 | ) -> EndpointId { |
416 | self.inner.cubic_bezier_to(ctrl1, ctrl2, to, NO_ATTRIBUTES) |
417 | } |
418 | |
419 | #[inline ] |
420 | fn reserve(&mut self, endpoints: usize, ctrl_points: usize) { |
421 | self.inner.reserve(endpoints, ctrl_points) |
422 | } |
423 | } |
424 | |
425 | impl<B: PathBuilder + Build> Build for NoAttributes<B> { |
426 | type PathType = B::PathType; |
427 | |
428 | fn build(self) -> B::PathType { |
429 | self.inner.build() |
430 | } |
431 | } |
432 | |
433 | impl<B: PathBuilder + Default> Default for NoAttributes<B> { |
434 | fn default() -> Self { |
435 | Self::new() |
436 | } |
437 | } |
438 | |
439 | /// The base path building interface. |
440 | /// |
441 | /// Unlike `SvgPathBuilder`, this interface strictly requires sub-paths to be manually |
442 | /// started and ended (See the `begin` and `end` methods). |
443 | /// All positions are provided in absolute coordinates. |
444 | /// |
445 | /// The goal of this interface is to abstract over simple and fast implementations that |
446 | /// do not deal with corner cases such as adding segments without starting a sub-path. |
447 | /// |
448 | /// More elaborate interfaces are built on top of the provided primitives. In particular, |
449 | /// the `SvgPathBuilder` trait providing more permissive and richer interface is |
450 | /// automatically implemented via the `WithSvg` adapter (See the `with_svg` method). |
451 | pub trait PathBuilder { |
452 | fn num_attributes(&self) -> usize; |
453 | /// Starts a new sub-path at a given position. |
454 | /// |
455 | /// There must be no sub-path in progress when this method is called. |
456 | /// `at` becomes the current position of the sub-path. |
457 | fn begin(&mut self, at: Point, custom_attributes: Attributes) -> EndpointId; |
458 | |
459 | /// Ends the current sub path. |
460 | /// |
461 | /// A sub-path must be in progress when this method is called. |
462 | /// After this method is called, there is no sub-path in progress until |
463 | /// `begin` is called again. |
464 | fn end(&mut self, close: bool); |
465 | |
466 | /// Closes the current sub path. |
467 | /// |
468 | /// Shorthand for `builder.end(true)`. |
469 | fn close(&mut self) { |
470 | self.end(true) |
471 | } |
472 | |
473 | /// Adds a line segment to the current sub-path. |
474 | /// |
475 | /// A sub-path must be in progress when this method is called. |
476 | fn line_to(&mut self, to: Point, custom_attributes: Attributes) -> EndpointId; |
477 | |
478 | /// Adds a quadratic bézier curve to the current sub-path. |
479 | /// |
480 | /// A sub-path must be in progress when this method is called. |
481 | fn quadratic_bezier_to( |
482 | &mut self, |
483 | ctrl: Point, |
484 | to: Point, |
485 | custom_attributes: Attributes, |
486 | ) -> EndpointId; |
487 | |
488 | /// Adds a cubic bézier curve to the current sub-path. |
489 | /// |
490 | /// A sub-path must be in progress when this method is called. |
491 | fn cubic_bezier_to( |
492 | &mut self, |
493 | ctrl1: Point, |
494 | ctrl2: Point, |
495 | to: Point, |
496 | custom_attributes: Attributes, |
497 | ) -> EndpointId; |
498 | |
499 | /// Hints at the builder that a certain number of endpoints and control |
500 | /// points will be added. |
501 | /// |
502 | /// The Builder implementation may use this information to pre-allocate |
503 | /// memory as an optimization. |
504 | fn reserve(&mut self, _endpoints: usize, _ctrl_points: usize) {} |
505 | |
506 | /// Applies the provided path event. |
507 | /// |
508 | /// By default this calls one of `begin`, `end`, `line`, `quadratic_bezier_segment`, |
509 | /// or `cubic_bezier_segment` according to the path event. |
510 | /// |
511 | /// The requirements for each method apply to the corresponding event. |
512 | fn path_event(&mut self, event: PathEvent, attributes: Attributes) { |
513 | match event { |
514 | PathEvent::Begin { at } => { |
515 | self.begin(at, attributes); |
516 | } |
517 | PathEvent::Line { to, .. } => { |
518 | self.line_to(to, attributes); |
519 | } |
520 | PathEvent::Quadratic { ctrl, to, .. } => { |
521 | self.quadratic_bezier_to(ctrl, to, attributes); |
522 | } |
523 | PathEvent::Cubic { |
524 | ctrl1, ctrl2, to, .. |
525 | } => { |
526 | self.cubic_bezier_to(ctrl1, ctrl2, to, attributes); |
527 | } |
528 | PathEvent::End { close, .. } => { |
529 | self.end(close); |
530 | } |
531 | } |
532 | } |
533 | |
534 | fn event(&mut self, event: Event<(Point, Attributes), Point>) { |
535 | match event { |
536 | Event::Begin { at } => { |
537 | self.begin(at.0, at.1); |
538 | } |
539 | Event::Line { to, .. } => { |
540 | self.line_to(to.0, to.1); |
541 | } |
542 | Event::Quadratic { ctrl, to, .. } => { |
543 | self.quadratic_bezier_to(ctrl, to.0, to.1); |
544 | } |
545 | Event::Cubic { |
546 | ctrl1, ctrl2, to, .. |
547 | } => { |
548 | self.cubic_bezier_to(ctrl1, ctrl2, to.0, to.1); |
549 | } |
550 | Event::End { close, .. } => { |
551 | self.end(close); |
552 | } |
553 | } |
554 | } |
555 | |
556 | /// Adds a sub-path from a polygon. |
557 | /// |
558 | /// There must be no sub-path in progress when this method is called. |
559 | /// No sub-path is in progress after the method is called. |
560 | fn add_polygon(&mut self, polygon: Polygon<Point>, attributes: Attributes) { |
561 | if polygon.points.is_empty() { |
562 | return; |
563 | } |
564 | |
565 | self.reserve(polygon.points.len(), 0); |
566 | |
567 | self.begin(polygon.points[0], attributes); |
568 | for p in &polygon.points[1..] { |
569 | self.line_to(*p, attributes); |
570 | } |
571 | |
572 | self.end(polygon.closed); |
573 | } |
574 | |
575 | /// Adds a sub-path containing a single point. |
576 | /// |
577 | /// There must be no sub-path in progress when this method is called. |
578 | /// No sub-path is in progress after the method is called. |
579 | fn add_point(&mut self, at: Point, attributes: Attributes) -> EndpointId { |
580 | let id = self.begin(at, attributes); |
581 | self.end(false); |
582 | |
583 | id |
584 | } |
585 | |
586 | /// Adds a sub-path containing a single line segment. |
587 | /// |
588 | /// There must be no sub-path in progress when this method is called. |
589 | /// No sub-path is in progress after the method is called. |
590 | fn add_line_segment( |
591 | &mut self, |
592 | line: &LineSegment<f32>, |
593 | attributes: Attributes, |
594 | ) -> (EndpointId, EndpointId) { |
595 | let a = self.begin(line.from, attributes); |
596 | let b = self.line_to(line.to, attributes); |
597 | self.end(false); |
598 | |
599 | (a, b) |
600 | } |
601 | |
602 | /// Adds a sub-path containing an ellipse. |
603 | /// |
604 | /// There must be no sub-path in progress when this method is called. |
605 | /// No sub-path is in progress after the method is called. |
606 | fn add_ellipse( |
607 | &mut self, |
608 | center: Point, |
609 | radii: Vector, |
610 | x_rotation: Angle, |
611 | winding: Winding, |
612 | attributes: Attributes, |
613 | ) { |
614 | let dir = match winding { |
615 | Winding::Positive => 1.0, |
616 | Winding::Negative => -1.0, |
617 | }; |
618 | |
619 | let arc = Arc { |
620 | center, |
621 | radii, |
622 | x_rotation, |
623 | start_angle: Angle::radians(0.0), |
624 | sweep_angle: Angle::radians(2.0 * PI) * dir, |
625 | }; |
626 | |
627 | self.begin(arc.sample(0.0), attributes); |
628 | arc.for_each_quadratic_bezier(&mut |curve| { |
629 | self.quadratic_bezier_to(curve.ctrl, curve.to, attributes); |
630 | }); |
631 | self.end(true); |
632 | } |
633 | |
634 | /// Adds a sub-path containing a circle. |
635 | /// |
636 | /// There must be no sub-path in progress when this method is called. |
637 | /// No sub-path is in progress after the method is called. |
638 | fn add_circle(&mut self, center: Point, radius: f32, winding: Winding, attributes: Attributes) |
639 | where |
640 | Self: Sized, |
641 | { |
642 | add_circle(self, center, radius, winding, attributes); |
643 | } |
644 | |
645 | /// Adds a sub-path containing a rectangle. |
646 | /// |
647 | /// There must be no sub-path in progress when this method is called. |
648 | /// No sub-path is in progress after the method is called. |
649 | fn add_rectangle(&mut self, rect: &Box2D, winding: Winding, attributes: Attributes) { |
650 | match winding { |
651 | Winding::Positive => self.add_polygon( |
652 | Polygon { |
653 | points: &[ |
654 | rect.min, |
655 | point(rect.max.x, rect.min.y), |
656 | rect.max, |
657 | point(rect.min.x, rect.max.y), |
658 | ], |
659 | closed: true, |
660 | }, |
661 | attributes, |
662 | ), |
663 | Winding::Negative => self.add_polygon( |
664 | Polygon { |
665 | points: &[ |
666 | rect.min, |
667 | point(rect.min.x, rect.max.y), |
668 | rect.max, |
669 | point(rect.max.x, rect.min.y), |
670 | ], |
671 | closed: true, |
672 | }, |
673 | attributes, |
674 | ), |
675 | }; |
676 | } |
677 | |
678 | /// Adds a sub-path containing a rectangle. |
679 | /// |
680 | /// There must be no sub-path in progress when this method is called. |
681 | /// No sub-path is in progress after the method is called. |
682 | fn add_rounded_rectangle( |
683 | &mut self, |
684 | rect: &Box2D, |
685 | radii: &BorderRadii, |
686 | winding: Winding, |
687 | custom_attributes: Attributes, |
688 | ) where |
689 | Self: Sized, |
690 | { |
691 | add_rounded_rectangle(self, rect, radii, winding, custom_attributes); |
692 | } |
693 | |
694 | /// Returns a builder that approximates all curves with sequences of line segments. |
695 | fn flattened(self, tolerance: f32) -> Flattened<Self> |
696 | where |
697 | Self: Sized, |
698 | { |
699 | Flattened::new(self, tolerance) |
700 | } |
701 | |
702 | /// Returns a builder that applies the given transformation to all positions. |
703 | fn transformed<Transform>(self, transform: Transform) -> Transformed<Self, Transform> |
704 | where |
705 | Self: Sized, |
706 | Transform: Transformation<f32>, |
707 | { |
708 | Transformed::new(self, transform) |
709 | } |
710 | |
711 | /// Returns a builder that support SVG commands. |
712 | /// |
713 | /// This must be called before starting to add any sub-path. |
714 | fn with_svg(self) -> WithSvg<Self> |
715 | where |
716 | Self: Sized, |
717 | { |
718 | WithSvg::new(self) |
719 | } |
720 | } |
721 | |
722 | /// A path building interface that tries to stay close to SVG's path specification. |
723 | /// <https://svgwg.org/specs/paths/> |
724 | /// |
725 | /// Some of the wording in the documentation of this trait is borrowed from the SVG |
726 | /// specification. |
727 | /// |
728 | /// Unlike `PathBuilder`, implementations of this trait are expected to deal with |
729 | /// various corners cases such as adding segments without starting a sub-path. |
730 | pub trait SvgPathBuilder { |
731 | /// Start a new sub-path at the given position. |
732 | /// |
733 | /// Corresponding SVG command: `M`. |
734 | /// |
735 | /// This command establishes a new initial point and a new current point. The effect |
736 | /// is as if the "pen" were lifted and moved to a new location. |
737 | /// If a sub-path is in progress, it is ended without being closed. |
738 | fn move_to(&mut self, to: Point); |
739 | |
740 | /// Ends the current sub-path by connecting it back to its initial point. |
741 | /// |
742 | /// Corresponding SVG command: `Z`. |
743 | /// |
744 | /// A straight line is drawn from the current point to the initial point of the |
745 | /// current sub-path. |
746 | /// The current position is set to the initial position of the sub-path that was |
747 | /// closed. |
748 | fn close(&mut self); |
749 | |
750 | /// Adds a line segment to the current sub-path. |
751 | /// |
752 | /// Corresponding SVG command: `L`. |
753 | /// |
754 | /// The segment starts at the builder's current position. |
755 | /// If this is the very first command of the path (the builder therefore does not |
756 | /// have a current position), the `line_to` command is replaced with a `move_to(to)`. |
757 | fn line_to(&mut self, to: Point); |
758 | |
759 | /// Adds a quadratic bézier segment to the current sub-path. |
760 | /// |
761 | /// Corresponding SVG command: `Q`. |
762 | /// |
763 | /// The segment starts at the builder's current position. |
764 | /// If this is the very first command of the path (the builder therefore does not |
765 | /// have a current position), the `quadratic_bezier_to` command is replaced with |
766 | /// a `move_to(to)`. |
767 | fn quadratic_bezier_to(&mut self, ctrl: Point, to: Point); |
768 | |
769 | /// Adds a cubic bézier segment to the current sub-path. |
770 | /// |
771 | /// Corresponding SVG command: `C`. |
772 | /// |
773 | /// The segment starts at the builder's current position. |
774 | /// If this is the very first command of the path (the builder therefore does not |
775 | /// have a current position), the `cubic_bezier_to` command is replaced with |
776 | /// a `move_to(to)`. |
777 | fn cubic_bezier_to(&mut self, ctrl1: Point, ctrl2: Point, to: Point); |
778 | |
779 | /// Equivalent to `move_to` in relative coordinates. |
780 | /// |
781 | /// Corresponding SVG command: `m`. |
782 | /// |
783 | /// The provided coordinates are offsets relative to the current position of |
784 | /// the builder. |
785 | fn relative_move_to(&mut self, to: Vector); |
786 | |
787 | /// Equivalent to `line_to` in relative coordinates. |
788 | /// |
789 | /// Corresponding SVG command: `l`. |
790 | /// |
791 | /// The provided coordinates are offsets relative to the current position of |
792 | /// the builder. |
793 | fn relative_line_to(&mut self, to: Vector); |
794 | |
795 | /// Equivalent to `quadratic_bezier_to` in relative coordinates. |
796 | /// |
797 | /// Corresponding SVG command: `q`. |
798 | /// |
799 | /// the provided coordinates are offsets relative to the current position of |
800 | /// the builder. |
801 | fn relative_quadratic_bezier_to(&mut self, ctrl: Vector, to: Vector); |
802 | |
803 | /// Equivalent to `cubic_bezier_to` in relative coordinates. |
804 | /// |
805 | /// Corresponding SVG command: `c`. |
806 | /// |
807 | /// The provided coordinates are offsets relative to the current position of |
808 | /// the builder. |
809 | fn relative_cubic_bezier_to(&mut self, ctrl1: Vector, ctrl2: Vector, to: Vector); |
810 | |
811 | /// Equivalent to `cubic_bezier_to` with implicit first control point. |
812 | /// |
813 | /// Corresponding SVG command: `S`. |
814 | /// |
815 | /// The first control point is assumed to be the reflection of the second |
816 | /// control point on the previous command relative to the current point. |
817 | /// If there is no previous command or if the previous command was not a |
818 | /// cubic bézier segment, the first control point is coincident with |
819 | /// the current position. |
820 | fn smooth_cubic_bezier_to(&mut self, ctrl2: Point, to: Point); |
821 | |
822 | /// Equivalent to `smooth_cubic_bezier_to` in relative coordinates. |
823 | /// |
824 | /// Corresponding SVG command: `s`. |
825 | /// |
826 | /// The provided coordinates are offsets relative to the current position of |
827 | /// the builder. |
828 | fn smooth_relative_cubic_bezier_to(&mut self, ctrl2: Vector, to: Vector); |
829 | |
830 | /// Equivalent to `quadratic_bezier_to` with implicit control point. |
831 | /// |
832 | /// Corresponding SVG command: `T`. |
833 | /// |
834 | /// The control point is assumed to be the reflection of the control |
835 | /// point on the previous command relative to the current point. |
836 | /// If there is no previous command or if the previous command was not a |
837 | /// quadratic bézier segment, a line segment is added instead. |
838 | fn smooth_quadratic_bezier_to(&mut self, to: Point); |
839 | |
840 | /// Equivalent to `smooth_quadratic_bezier_to` in relative coordinates. |
841 | /// |
842 | /// Corresponding SVG command: `t`. |
843 | /// |
844 | /// The provided coordinates are offsets relative to the current position of |
845 | /// the builder. |
846 | fn smooth_relative_quadratic_bezier_to(&mut self, to: Vector); |
847 | |
848 | /// Adds an horizontal line segment. |
849 | /// |
850 | /// Corresponding SVG command: `H`. |
851 | /// |
852 | /// Equivalent to `line_to`, using the y coordinate of the current position. |
853 | fn horizontal_line_to(&mut self, x: f32); |
854 | |
855 | /// Adds an horizontal line segment in relative coordinates. |
856 | /// |
857 | /// Corresponding SVG command: `l`. |
858 | /// |
859 | /// Equivalent to `line_to`, using the y coordinate of the current position. |
860 | /// `dx` is the horizontal offset relative to the current position. |
861 | fn relative_horizontal_line_to(&mut self, dx: f32); |
862 | |
863 | /// Adds a vertical line segment. |
864 | /// |
865 | /// Corresponding SVG command: `V`. |
866 | /// |
867 | /// Equivalent to `line_to`, using the x coordinate of the current position. |
868 | fn vertical_line_to(&mut self, y: f32); |
869 | |
870 | /// Adds a vertical line segment in relative coordinates. |
871 | /// |
872 | /// Corresponding SVG command: `v`. |
873 | /// |
874 | /// Equivalent to `line_to`, using the y coordinate of the current position. |
875 | /// `dy` is the horizontal offset relative to the current position. |
876 | fn relative_vertical_line_to(&mut self, dy: f32); |
877 | |
878 | /// Adds an elliptical arc. |
879 | /// |
880 | /// Corresponding SVG command: `A`. |
881 | /// |
882 | /// The arc starts at the current point and ends at `to`. |
883 | /// The size and orientation of the ellipse are defined by `radii` and an `x_rotation`, |
884 | /// which indicates how the ellipse as a whole is rotated relative to the current coordinate |
885 | /// system. The center of the ellipse is calculated automatically to satisfy the constraints |
886 | /// imposed by the other parameters. the arc `flags` contribute to the automatic calculations |
887 | /// and help determine how the arc is built. |
888 | fn arc_to(&mut self, radii: Vector, x_rotation: Angle, flags: ArcFlags, to: Point); |
889 | |
890 | /// Equivalent to `arc_to` in relative coordinates. |
891 | /// |
892 | /// Corresponding SVG command: `a`. |
893 | /// |
894 | /// The provided `to` coordinates are offsets relative to the current position of |
895 | /// the builder. |
896 | fn relative_arc_to(&mut self, radii: Vector, x_rotation: Angle, flags: ArcFlags, to: Vector); |
897 | |
898 | /// Hints at the builder that a certain number of endpoints and control |
899 | /// points will be added. |
900 | /// |
901 | /// The Builder implementation may use this information to pre-allocate |
902 | /// memory as an optimization. |
903 | fn reserve(&mut self, _endpoints: usize, _ctrl_points: usize) {} |
904 | |
905 | /// Adds a sub-path from a polygon. |
906 | /// |
907 | /// There must be no sub-path in progress when this method is called. |
908 | /// No sub-path is in progress after the method is called. |
909 | fn add_polygon(&mut self, polygon: Polygon<Point>) { |
910 | if polygon.points.is_empty() { |
911 | return; |
912 | } |
913 | |
914 | self.reserve(polygon.points.len(), 0); |
915 | |
916 | self.move_to(polygon.points[0]); |
917 | for p in &polygon.points[1..] { |
918 | self.line_to(*p); |
919 | } |
920 | |
921 | if polygon.closed { |
922 | self.close(); |
923 | } |
924 | } |
925 | } |
926 | |
927 | /// Builds a path. |
928 | /// |
929 | /// This trait is separate from `PathBuilder` and `SvgPathBuilder` to allow them to |
930 | /// be used as trait object (which isn't possible when a method returns an associated |
931 | /// type). |
932 | pub trait Build { |
933 | /// The type of object that is created by this builder. |
934 | type PathType; |
935 | |
936 | /// Builds a path object, consuming the builder. |
937 | fn build(self) -> Self::PathType; |
938 | } |
939 | |
940 | /// A Builder that approximates curves with successions of line segments. |
941 | pub struct Flattened<Builder> { |
942 | builder: Builder, |
943 | current_position: Point, |
944 | tolerance: f32, |
945 | prev_attributes: Vec<f32>, |
946 | attribute_buffer: Vec<f32>, |
947 | } |
948 | |
949 | impl<Builder: Build> Build for Flattened<Builder> { |
950 | type PathType = Builder::PathType; |
951 | |
952 | fn build(self) -> Builder::PathType { |
953 | self.builder.build() |
954 | } |
955 | } |
956 | |
957 | impl<Builder: PathBuilder> PathBuilder for Flattened<Builder> { |
958 | fn num_attributes(&self) -> usize { |
959 | self.builder.num_attributes() |
960 | } |
961 | |
962 | fn begin(&mut self, at: Point, attributes: Attributes) -> EndpointId { |
963 | self.current_position = at; |
964 | self.builder.begin(at, attributes) |
965 | } |
966 | |
967 | fn end(&mut self, close: bool) { |
968 | self.builder.end(close) |
969 | } |
970 | |
971 | fn line_to(&mut self, to: Point, attributes: Attributes) -> EndpointId { |
972 | let id = self.builder.line_to(to, attributes); |
973 | self.current_position = to; |
974 | self.prev_attributes.copy_from_slice(attributes); |
975 | id |
976 | } |
977 | |
978 | fn quadratic_bezier_to( |
979 | &mut self, |
980 | ctrl: Point, |
981 | to: Point, |
982 | attributes: Attributes, |
983 | ) -> EndpointId { |
984 | let id = crate::private::flatten_quadratic_bezier( |
985 | self.tolerance, |
986 | self.current_position, |
987 | ctrl, |
988 | to, |
989 | attributes, |
990 | &self.prev_attributes, |
991 | &mut self.builder, |
992 | &mut self.attribute_buffer, |
993 | ); |
994 | self.current_position = to; |
995 | self.prev_attributes.copy_from_slice(attributes); |
996 | |
997 | id |
998 | } |
999 | |
1000 | fn cubic_bezier_to( |
1001 | &mut self, |
1002 | ctrl1: Point, |
1003 | ctrl2: Point, |
1004 | to: Point, |
1005 | attributes: Attributes, |
1006 | ) -> EndpointId { |
1007 | let id = crate::private::flatten_cubic_bezier( |
1008 | self.tolerance, |
1009 | self.current_position, |
1010 | ctrl1, |
1011 | ctrl2, |
1012 | to, |
1013 | attributes, |
1014 | &self.prev_attributes, |
1015 | &mut self.builder, |
1016 | &mut self.attribute_buffer, |
1017 | ); |
1018 | self.current_position = to; |
1019 | self.prev_attributes.copy_from_slice(attributes); |
1020 | |
1021 | id |
1022 | } |
1023 | |
1024 | fn reserve(&mut self, endpoints: usize, ctrl_points: usize) { |
1025 | self.builder.reserve(endpoints + ctrl_points * 4, 0); |
1026 | } |
1027 | } |
1028 | |
1029 | impl<Builder: PathBuilder> Flattened<Builder> { |
1030 | pub fn new(builder: Builder, tolerance: f32) -> Flattened<Builder> { |
1031 | let n: usize = builder.num_attributes(); |
1032 | Flattened { |
1033 | builder, |
1034 | current_position: point(x:0.0, y:0.0), |
1035 | tolerance, |
1036 | prev_attributes: vec![0.0; n], |
1037 | attribute_buffer: vec![0.0; n], |
1038 | } |
1039 | } |
1040 | |
1041 | pub fn build(self) -> Builder::PathType |
1042 | where |
1043 | Builder: Build, |
1044 | { |
1045 | self.builder.build() |
1046 | } |
1047 | |
1048 | pub fn set_tolerance(&mut self, tolerance: f32) { |
1049 | self.tolerance = tolerance |
1050 | } |
1051 | } |
1052 | |
1053 | /// Builds a path with a transformation applied. |
1054 | pub struct Transformed<Builder, Transform> { |
1055 | builder: Builder, |
1056 | transform: Transform, |
1057 | } |
1058 | |
1059 | impl<Builder, Transform> Transformed<Builder, Transform> { |
1060 | #[inline ] |
1061 | pub fn new(builder: Builder, transform: Transform) -> Self { |
1062 | Transformed { builder, transform } |
1063 | } |
1064 | |
1065 | #[inline ] |
1066 | pub fn set_transform(&mut self, transform: Transform) { |
1067 | self.transform = transform; |
1068 | } |
1069 | } |
1070 | |
1071 | impl<Builder: Build, Transform> Build for Transformed<Builder, Transform> { |
1072 | type PathType = Builder::PathType; |
1073 | |
1074 | #[inline ] |
1075 | fn build(self) -> Builder::PathType { |
1076 | self.builder.build() |
1077 | } |
1078 | } |
1079 | |
1080 | impl<Builder, Transform> PathBuilder for Transformed<Builder, Transform> |
1081 | where |
1082 | Builder: PathBuilder, |
1083 | Transform: Transformation<f32>, |
1084 | { |
1085 | fn num_attributes(&self) -> usize { |
1086 | self.builder.num_attributes() |
1087 | } |
1088 | |
1089 | #[inline ] |
1090 | fn begin(&mut self, at: Point, attributes: Attributes) -> EndpointId { |
1091 | self.builder |
1092 | .begin(self.transform.transform_point(at), attributes) |
1093 | } |
1094 | |
1095 | #[inline ] |
1096 | fn end(&mut self, close: bool) { |
1097 | self.builder.end(close) |
1098 | } |
1099 | |
1100 | #[inline ] |
1101 | fn line_to(&mut self, to: Point, attributes: Attributes) -> EndpointId { |
1102 | self.builder |
1103 | .line_to(self.transform.transform_point(to), attributes) |
1104 | } |
1105 | |
1106 | #[inline ] |
1107 | fn quadratic_bezier_to( |
1108 | &mut self, |
1109 | ctrl: Point, |
1110 | to: Point, |
1111 | attributes: Attributes, |
1112 | ) -> EndpointId { |
1113 | self.builder.quadratic_bezier_to( |
1114 | self.transform.transform_point(ctrl), |
1115 | self.transform.transform_point(to), |
1116 | attributes, |
1117 | ) |
1118 | } |
1119 | |
1120 | #[inline ] |
1121 | fn cubic_bezier_to( |
1122 | &mut self, |
1123 | ctrl1: Point, |
1124 | ctrl2: Point, |
1125 | to: Point, |
1126 | attributes: Attributes, |
1127 | ) -> EndpointId { |
1128 | self.builder.cubic_bezier_to( |
1129 | self.transform.transform_point(ctrl1), |
1130 | self.transform.transform_point(ctrl2), |
1131 | self.transform.transform_point(to), |
1132 | attributes, |
1133 | ) |
1134 | } |
1135 | |
1136 | #[inline ] |
1137 | fn reserve(&mut self, endpoints: usize, ctrl_points: usize) { |
1138 | self.builder.reserve(endpoints, ctrl_points); |
1139 | } |
1140 | } |
1141 | |
1142 | /// Implements an SVG-like building interface on top of a PathBuilder. |
1143 | pub struct WithSvg<Builder: PathBuilder> { |
1144 | builder: Builder, |
1145 | |
1146 | first_position: Point, |
1147 | current_position: Point, |
1148 | last_ctrl: Point, |
1149 | last_cmd: Verb, |
1150 | need_moveto: bool, |
1151 | is_empty: bool, |
1152 | attribute_buffer: Vec<f32>, |
1153 | } |
1154 | |
1155 | impl<Builder: PathBuilder> WithSvg<Builder> { |
1156 | pub fn new(builder: Builder) -> Self { |
1157 | let attribute_buffer = vec![0.0; builder.num_attributes()]; |
1158 | WithSvg { |
1159 | builder, |
1160 | first_position: point(0.0, 0.0), |
1161 | current_position: point(0.0, 0.0), |
1162 | last_ctrl: point(0.0, 0.0), |
1163 | need_moveto: true, |
1164 | is_empty: true, |
1165 | last_cmd: Verb::End, |
1166 | attribute_buffer, |
1167 | } |
1168 | } |
1169 | |
1170 | pub fn build(mut self) -> Builder::PathType |
1171 | where |
1172 | Builder: Build, |
1173 | { |
1174 | self.end_if_needed(); |
1175 | self.builder.build() |
1176 | } |
1177 | |
1178 | pub fn flattened(self, tolerance: f32) -> WithSvg<Flattened<Builder>> { |
1179 | WithSvg::new(Flattened::new(self.builder, tolerance)) |
1180 | } |
1181 | |
1182 | pub fn transformed<Transform>( |
1183 | self, |
1184 | transform: Transform, |
1185 | ) -> WithSvg<Transformed<Builder, Transform>> |
1186 | where |
1187 | Transform: Transformation<f32>, |
1188 | { |
1189 | WithSvg::new(Transformed::new(self.builder, transform)) |
1190 | } |
1191 | |
1192 | pub fn move_to(&mut self, to: Point) -> EndpointId { |
1193 | self.end_if_needed(); |
1194 | |
1195 | let id = self.builder.begin(to, &self.attribute_buffer); |
1196 | |
1197 | self.is_empty = false; |
1198 | self.need_moveto = false; |
1199 | self.first_position = to; |
1200 | self.current_position = to; |
1201 | self.last_cmd = Verb::Begin; |
1202 | |
1203 | id |
1204 | } |
1205 | |
1206 | pub fn line_to(&mut self, to: Point) -> EndpointId { |
1207 | if let Some(id) = self.begin_if_needed(&to) { |
1208 | return id; |
1209 | } |
1210 | |
1211 | self.current_position = to; |
1212 | self.last_cmd = Verb::LineTo; |
1213 | |
1214 | self.builder.line_to(to, &self.attribute_buffer) |
1215 | } |
1216 | |
1217 | pub fn close(&mut self) { |
1218 | if self.need_moveto { |
1219 | return; |
1220 | } |
1221 | |
1222 | // Relative path ops tend to accumulate small floating point error, |
1223 | // which results in the last segment ending almost but not quite at the |
1224 | // start of the sub-path, causing a new edge to be inserted which often |
1225 | // intersects with the first or last edge. This can affect algorithms that |
1226 | // Don't handle self-intersecting paths. |
1227 | // Deal with this by snapping the last point if it is very close to the |
1228 | // start of the sub path. |
1229 | // |
1230 | // TODO |
1231 | // if let Some(p) = self.builder.points.last_mut() { |
1232 | // let d = (*p - self.first_position).abs(); |
1233 | // if d.x + d.y < 0.0001 { |
1234 | // *p = self.first_position; |
1235 | // } |
1236 | // } |
1237 | |
1238 | self.current_position = self.first_position; |
1239 | self.need_moveto = true; |
1240 | self.last_cmd = Verb::Close; |
1241 | |
1242 | self.builder.close(); |
1243 | } |
1244 | |
1245 | pub fn quadratic_bezier_to(&mut self, ctrl: Point, to: Point) -> EndpointId { |
1246 | if let Some(id) = self.begin_if_needed(&to) { |
1247 | return id; |
1248 | } |
1249 | |
1250 | self.current_position = to; |
1251 | self.last_cmd = Verb::QuadraticTo; |
1252 | self.last_ctrl = ctrl; |
1253 | |
1254 | self.builder |
1255 | .quadratic_bezier_to(ctrl, to, &self.attribute_buffer) |
1256 | } |
1257 | |
1258 | pub fn cubic_bezier_to(&mut self, ctrl1: Point, ctrl2: Point, to: Point) -> EndpointId { |
1259 | if let Some(id) = self.begin_if_needed(&to) { |
1260 | return id; |
1261 | } |
1262 | |
1263 | self.current_position = to; |
1264 | self.last_cmd = Verb::CubicTo; |
1265 | self.last_ctrl = ctrl2; |
1266 | |
1267 | self.builder |
1268 | .cubic_bezier_to(ctrl1, ctrl2, to, &self.attribute_buffer) |
1269 | } |
1270 | |
1271 | pub fn arc(&mut self, center: Point, radii: Vector, sweep_angle: Angle, x_rotation: Angle) { |
1272 | nan_check(center); |
1273 | nan_check(radii.to_point()); |
1274 | debug_assert!(!sweep_angle.get().is_nan()); |
1275 | debug_assert!(!x_rotation.get().is_nan()); |
1276 | |
1277 | self.last_ctrl = self.current_position; |
1278 | |
1279 | // If the center is equal to the current position, the start and end angles aren't |
1280 | // defined, so we just skip the arc to avoid generating NaNs that will cause issues |
1281 | // later. |
1282 | use lyon_geom::euclid::approxeq::ApproxEq; |
1283 | if self.current_position.approx_eq(¢er) { |
1284 | return; |
1285 | } |
1286 | |
1287 | let start_angle = (self.current_position - center).angle_from_x_axis() - x_rotation; |
1288 | |
1289 | let arc = Arc { |
1290 | center, |
1291 | radii, |
1292 | start_angle, |
1293 | sweep_angle, |
1294 | x_rotation, |
1295 | }; |
1296 | |
1297 | // If the current position is not on the arc, move or line to the beginning of the |
1298 | // arc. |
1299 | let arc_start = arc.from(); |
1300 | if self.need_moveto { |
1301 | self.move_to(arc_start); |
1302 | } else if (arc_start - self.current_position).square_length() < 0.01 { |
1303 | self.builder.line_to(arc_start, &self.attribute_buffer); |
1304 | } |
1305 | |
1306 | arc.for_each_quadratic_bezier(&mut |curve| { |
1307 | self.builder |
1308 | .quadratic_bezier_to(curve.ctrl, curve.to, &self.attribute_buffer); |
1309 | self.current_position = curve.to; |
1310 | }); |
1311 | } |
1312 | |
1313 | /// Ensures the current sub-path has a moveto command. |
1314 | /// |
1315 | /// Returns an ID if the command should be skipped and the ID returned instead. |
1316 | #[inline (always)] |
1317 | fn begin_if_needed(&mut self, default: &Point) -> Option<EndpointId> { |
1318 | if self.need_moveto { |
1319 | return self.insert_move_to(default); |
1320 | } |
1321 | |
1322 | None |
1323 | } |
1324 | |
1325 | #[inline (never)] |
1326 | fn insert_move_to(&mut self, default: &Point) -> Option<EndpointId> { |
1327 | if self.is_empty { |
1328 | return Some(self.move_to(*default)); |
1329 | } |
1330 | |
1331 | self.move_to(self.first_position); |
1332 | |
1333 | None |
1334 | } |
1335 | |
1336 | fn end_if_needed(&mut self) { |
1337 | if (self.last_cmd as u8) <= (Verb::Begin as u8) { |
1338 | self.builder.end(false); |
1339 | } |
1340 | } |
1341 | |
1342 | pub fn current_position(&self) -> Point { |
1343 | self.current_position |
1344 | } |
1345 | |
1346 | pub fn reserve(&mut self, endpoints: usize, ctrl_points: usize) { |
1347 | self.builder.reserve(endpoints, ctrl_points); |
1348 | } |
1349 | |
1350 | fn get_smooth_cubic_ctrl(&self) -> Point { |
1351 | match self.last_cmd { |
1352 | Verb::CubicTo => self.current_position + (self.current_position - self.last_ctrl), |
1353 | _ => self.current_position, |
1354 | } |
1355 | } |
1356 | |
1357 | fn get_smooth_quadratic_ctrl(&self) -> Point { |
1358 | match self.last_cmd { |
1359 | Verb::QuadraticTo => self.current_position + (self.current_position - self.last_ctrl), |
1360 | _ => self.current_position, |
1361 | } |
1362 | } |
1363 | |
1364 | fn relative_to_absolute(&self, v: Vector) -> Point { |
1365 | self.current_position + v |
1366 | } |
1367 | } |
1368 | |
1369 | impl<Builder, Transform> WithSvg<Transformed<Builder, Transform>> |
1370 | where |
1371 | Builder: PathBuilder, |
1372 | Transform: Transformation<f32>, |
1373 | { |
1374 | #[inline ] |
1375 | pub fn set_transform(&mut self, transform: Transform) { |
1376 | self.builder.set_transform(transform); |
1377 | } |
1378 | } |
1379 | |
1380 | impl<Builder: PathBuilder + Build> Build for WithSvg<Builder> { |
1381 | type PathType = Builder::PathType; |
1382 | |
1383 | fn build(mut self) -> Builder::PathType { |
1384 | self.end_if_needed(); |
1385 | self.builder.build() |
1386 | } |
1387 | } |
1388 | |
1389 | impl<Builder: PathBuilder> SvgPathBuilder for WithSvg<Builder> { |
1390 | fn move_to(&mut self, to: Point) { |
1391 | self.move_to(to); |
1392 | } |
1393 | |
1394 | fn close(&mut self) { |
1395 | self.close(); |
1396 | } |
1397 | |
1398 | fn line_to(&mut self, to: Point) { |
1399 | self.line_to(to); |
1400 | } |
1401 | |
1402 | fn quadratic_bezier_to(&mut self, ctrl: Point, to: Point) { |
1403 | self.quadratic_bezier_to(ctrl, to); |
1404 | } |
1405 | |
1406 | fn cubic_bezier_to(&mut self, ctrl1: Point, ctrl2: Point, to: Point) { |
1407 | self.cubic_bezier_to(ctrl1, ctrl2, to); |
1408 | } |
1409 | |
1410 | fn relative_move_to(&mut self, to: Vector) { |
1411 | let to = self.relative_to_absolute(to); |
1412 | self.move_to(to); |
1413 | } |
1414 | |
1415 | fn relative_line_to(&mut self, to: Vector) { |
1416 | let to = self.relative_to_absolute(to); |
1417 | self.line_to(to); |
1418 | } |
1419 | |
1420 | fn relative_quadratic_bezier_to(&mut self, ctrl: Vector, to: Vector) { |
1421 | let ctrl = self.relative_to_absolute(ctrl); |
1422 | let to = self.relative_to_absolute(to); |
1423 | self.quadratic_bezier_to(ctrl, to); |
1424 | } |
1425 | |
1426 | fn relative_cubic_bezier_to(&mut self, ctrl1: Vector, ctrl2: Vector, to: Vector) { |
1427 | let to = self.relative_to_absolute(to); |
1428 | let ctrl1 = self.relative_to_absolute(ctrl1); |
1429 | let ctrl2 = self.relative_to_absolute(ctrl2); |
1430 | self.cubic_bezier_to(ctrl1, ctrl2, to); |
1431 | } |
1432 | |
1433 | fn smooth_cubic_bezier_to(&mut self, ctrl2: Point, to: Point) { |
1434 | let ctrl1 = self.get_smooth_cubic_ctrl(); |
1435 | self.cubic_bezier_to(ctrl1, ctrl2, to); |
1436 | } |
1437 | |
1438 | fn smooth_relative_cubic_bezier_to(&mut self, ctrl2: Vector, to: Vector) { |
1439 | let ctrl1 = self.get_smooth_cubic_ctrl(); |
1440 | let ctrl2 = self.relative_to_absolute(ctrl2); |
1441 | let to = self.relative_to_absolute(to); |
1442 | self.cubic_bezier_to(ctrl1, ctrl2, to); |
1443 | } |
1444 | |
1445 | fn smooth_quadratic_bezier_to(&mut self, to: Point) { |
1446 | let ctrl = self.get_smooth_quadratic_ctrl(); |
1447 | self.quadratic_bezier_to(ctrl, to); |
1448 | } |
1449 | |
1450 | fn smooth_relative_quadratic_bezier_to(&mut self, to: Vector) { |
1451 | let ctrl = self.get_smooth_quadratic_ctrl(); |
1452 | let to = self.relative_to_absolute(to); |
1453 | self.quadratic_bezier_to(ctrl, to); |
1454 | } |
1455 | |
1456 | fn horizontal_line_to(&mut self, x: f32) { |
1457 | let y = self.current_position.y; |
1458 | self.line_to(point(x, y)); |
1459 | } |
1460 | |
1461 | fn relative_horizontal_line_to(&mut self, dx: f32) { |
1462 | let p = self.current_position; |
1463 | self.line_to(point(p.x + dx, p.y)); |
1464 | } |
1465 | |
1466 | fn vertical_line_to(&mut self, y: f32) { |
1467 | let x = self.current_position.x; |
1468 | self.line_to(point(x, y)); |
1469 | } |
1470 | |
1471 | fn relative_vertical_line_to(&mut self, dy: f32) { |
1472 | let p = self.current_position; |
1473 | self.line_to(point(p.x, p.y + dy)); |
1474 | } |
1475 | |
1476 | fn arc_to(&mut self, radii: Vector, x_rotation: Angle, flags: ArcFlags, to: Point) { |
1477 | let svg_arc = SvgArc { |
1478 | from: self.current_position, |
1479 | to, |
1480 | radii, |
1481 | x_rotation, |
1482 | flags: ArcFlags { |
1483 | large_arc: flags.large_arc, |
1484 | sweep: flags.sweep, |
1485 | }, |
1486 | }; |
1487 | |
1488 | if svg_arc.is_straight_line() { |
1489 | self.line_to(to); |
1490 | } else { |
1491 | let arc = svg_arc.to_arc(); |
1492 | self.arc(arc.center, arc.radii, arc.sweep_angle, arc.x_rotation); |
1493 | } |
1494 | } |
1495 | |
1496 | fn relative_arc_to(&mut self, radii: Vector, x_rotation: Angle, flags: ArcFlags, to: Vector) { |
1497 | let to = self.relative_to_absolute(to); |
1498 | self.arc_to(radii, x_rotation, flags, to); |
1499 | } |
1500 | |
1501 | fn reserve(&mut self, endpoints: usize, ctrl_points: usize) { |
1502 | self.builder.reserve(endpoints, ctrl_points); |
1503 | } |
1504 | } |
1505 | |
1506 | /// Tessellate the stroke for an axis-aligned rounded rectangle. |
1507 | fn add_circle<Builder: PathBuilder>( |
1508 | builder: &mut Builder, |
1509 | center: Point, |
1510 | radius: f32, |
1511 | winding: Winding, |
1512 | attributes: Attributes, |
1513 | ) { |
1514 | let radius = radius.abs(); |
1515 | let dir = match winding { |
1516 | Winding::Positive => 1.0, |
1517 | Winding::Negative => -1.0, |
1518 | }; |
1519 | |
1520 | // https://spencermortensen.com/articles/bezier-circle/ |
1521 | const CONSTANT_FACTOR: f32 = 0.55191505; |
1522 | let d = radius * CONSTANT_FACTOR; |
1523 | |
1524 | builder.begin(center + vector(-radius, 0.0), attributes); |
1525 | |
1526 | let ctrl_0 = center + vector(-radius, -d * dir); |
1527 | let ctrl_1 = center + vector(-d, -radius * dir); |
1528 | let mid = center + vector(0.0, -radius * dir); |
1529 | builder.cubic_bezier_to(ctrl_0, ctrl_1, mid, attributes); |
1530 | |
1531 | let ctrl_0 = center + vector(d, -radius * dir); |
1532 | let ctrl_1 = center + vector(radius, -d * dir); |
1533 | let mid = center + vector(radius, 0.0); |
1534 | builder.cubic_bezier_to(ctrl_0, ctrl_1, mid, attributes); |
1535 | |
1536 | let ctrl_0 = center + vector(radius, d * dir); |
1537 | let ctrl_1 = center + vector(d, radius * dir); |
1538 | let mid = center + vector(0.0, radius * dir); |
1539 | builder.cubic_bezier_to(ctrl_0, ctrl_1, mid, attributes); |
1540 | |
1541 | let ctrl_0 = center + vector(-d, radius * dir); |
1542 | let ctrl_1 = center + vector(-radius, d * dir); |
1543 | let mid = center + vector(-radius, 0.0); |
1544 | builder.cubic_bezier_to(ctrl_0, ctrl_1, mid, attributes); |
1545 | |
1546 | builder.close(); |
1547 | } |
1548 | |
1549 | /// Tessellate the stroke for an axis-aligned rounded rectangle. |
1550 | fn add_rounded_rectangle<Builder: PathBuilder>( |
1551 | builder: &mut Builder, |
1552 | rect: &Box2D, |
1553 | radii: &BorderRadii, |
1554 | winding: Winding, |
1555 | attributes: Attributes, |
1556 | ) { |
1557 | let w = rect.width(); |
1558 | let h = rect.height(); |
1559 | let x_min = rect.min.x; |
1560 | let y_min = rect.min.y; |
1561 | let x_max = rect.max.x; |
1562 | let y_max = rect.max.y; |
1563 | let min_wh = w.min(h); |
1564 | let mut tl = radii.top_left.abs().min(min_wh); |
1565 | let mut tr = radii.top_right.abs().min(min_wh); |
1566 | let mut bl = radii.bottom_left.abs().min(min_wh); |
1567 | let mut br = radii.bottom_right.abs().min(min_wh); |
1568 | |
1569 | // clamp border radii if they don't fit in the rectangle. |
1570 | if tl + tr > w { |
1571 | let x = (tl + tr - w) * 0.5; |
1572 | tl -= x; |
1573 | tr -= x; |
1574 | } |
1575 | if bl + br > w { |
1576 | let x = (bl + br - w) * 0.5; |
1577 | bl -= x; |
1578 | br -= x; |
1579 | } |
1580 | if tr + br > h { |
1581 | let x = (tr + br - h) * 0.5; |
1582 | tr -= x; |
1583 | br -= x; |
1584 | } |
1585 | if tl + bl > h { |
1586 | let x = (tl + bl - h) * 0.5; |
1587 | tl -= x; |
1588 | bl -= x; |
1589 | } |
1590 | |
1591 | // https://spencermortensen.com/articles/bezier-circle/ |
1592 | const CONSTANT_FACTOR: f32 = 0.55191505; |
1593 | |
1594 | let tl_d = tl * CONSTANT_FACTOR; |
1595 | let tl_corner = point(x_min, y_min); |
1596 | |
1597 | let tr_d = tr * CONSTANT_FACTOR; |
1598 | let tr_corner = point(x_max, y_min); |
1599 | |
1600 | let br_d = br * CONSTANT_FACTOR; |
1601 | let br_corner = point(x_max, y_max); |
1602 | |
1603 | let bl_d = bl * CONSTANT_FACTOR; |
1604 | let bl_corner = point(x_min, y_max); |
1605 | |
1606 | let points = [ |
1607 | point(x_min, y_min + tl), // begin |
1608 | tl_corner + vector(0.0, tl - tl_d), // control |
1609 | tl_corner + vector(tl - tl_d, 0.0), // control |
1610 | tl_corner + vector(tl, 0.0), // end |
1611 | point(x_max - tr, y_min), |
1612 | tr_corner + vector(-tr + tr_d, 0.0), |
1613 | tr_corner + vector(0.0, tr - tr_d), |
1614 | tr_corner + vector(0.0, tr), |
1615 | point(x_max, y_max - br), |
1616 | br_corner + vector(0.0, -br + br_d), |
1617 | br_corner + vector(-br + br_d, 0.0), |
1618 | br_corner + vector(-br, 0.0), |
1619 | point(x_min + bl, y_max), |
1620 | bl_corner + vector(bl - bl_d, 0.0), |
1621 | bl_corner + vector(0.0, -bl + bl_d), |
1622 | bl_corner + vector(0.0, -bl), |
1623 | ]; |
1624 | |
1625 | if winding == Winding::Positive { |
1626 | builder.begin(points[0], attributes); |
1627 | if tl > 0.0 { |
1628 | builder.cubic_bezier_to(points[1], points[2], points[3], attributes); |
1629 | } |
1630 | builder.line_to(points[4], attributes); |
1631 | if tl > 0.0 { |
1632 | builder.cubic_bezier_to(points[5], points[6], points[7], attributes); |
1633 | } |
1634 | builder.line_to(points[8], attributes); |
1635 | if br > 0.0 { |
1636 | builder.cubic_bezier_to(points[9], points[10], points[11], attributes); |
1637 | } |
1638 | builder.line_to(points[12], attributes); |
1639 | if bl > 0.0 { |
1640 | builder.cubic_bezier_to(points[13], points[14], points[15], attributes); |
1641 | } |
1642 | } else { |
1643 | builder.begin(points[15], attributes); |
1644 | if bl > 0.0 { |
1645 | builder.cubic_bezier_to(points[14], points[13], points[12], attributes); |
1646 | } |
1647 | builder.line_to(points[11], attributes); |
1648 | if br > 0.0 { |
1649 | builder.cubic_bezier_to(points[10], points[9], points[8], attributes); |
1650 | } |
1651 | builder.line_to(points[7], attributes); |
1652 | if tl > 0.0 { |
1653 | builder.cubic_bezier_to(points[6], points[5], points[4], attributes); |
1654 | } |
1655 | builder.line_to(points[3], attributes); |
1656 | if tl > 0.0 { |
1657 | builder.cubic_bezier_to(points[2], points[1], points[0], attributes); |
1658 | } |
1659 | } |
1660 | builder.end(true); |
1661 | } |
1662 | |
1663 | #[inline ] |
1664 | fn nan_check(p: Point) { |
1665 | debug_assert!(p.x.is_finite()); |
1666 | debug_assert!(p.y.is_finite()); |
1667 | } |
1668 | |
1669 | #[test ] |
1670 | fn svg_builder_line_to_after_close() { |
1671 | use crate::Path; |
1672 | use crate::PathEvent; |
1673 | |
1674 | let mut p = Path::svg_builder(); |
1675 | p.line_to(point(1.0, 0.0)); |
1676 | p.close(); |
1677 | p.line_to(point(2.0, 0.0)); |
1678 | |
1679 | let path = p.build(); |
1680 | let mut it = path.iter(); |
1681 | assert_eq!( |
1682 | it.next(), |
1683 | Some(PathEvent::Begin { |
1684 | at: point(1.0, 0.0) |
1685 | }) |
1686 | ); |
1687 | assert_eq!( |
1688 | it.next(), |
1689 | Some(PathEvent::End { |
1690 | last: point(1.0, 0.0), |
1691 | first: point(1.0, 0.0), |
1692 | close: true |
1693 | }) |
1694 | ); |
1695 | assert_eq!( |
1696 | it.next(), |
1697 | Some(PathEvent::Begin { |
1698 | at: point(1.0, 0.0) |
1699 | }) |
1700 | ); |
1701 | assert_eq!( |
1702 | it.next(), |
1703 | Some(PathEvent::Line { |
1704 | from: point(1.0, 0.0), |
1705 | to: point(2.0, 0.0) |
1706 | }) |
1707 | ); |
1708 | assert_eq!( |
1709 | it.next(), |
1710 | Some(PathEvent::End { |
1711 | last: point(2.0, 0.0), |
1712 | first: point(1.0, 0.0), |
1713 | close: false |
1714 | }) |
1715 | ); |
1716 | assert_eq!(it.next(), None); |
1717 | } |
1718 | |
1719 | #[test ] |
1720 | fn svg_builder_relative_curves() { |
1721 | use crate::Path; |
1722 | use crate::PathEvent; |
1723 | |
1724 | let mut p = Path::svg_builder(); |
1725 | p.move_to(point(0.0, 0.0)); |
1726 | p.relative_quadratic_bezier_to(vector(0., 100.), vector(-100., 100.)); |
1727 | p.relative_line_to(vector(-50., 0.)); |
1728 | |
1729 | let path = p.build(); |
1730 | let mut it = path.iter(); |
1731 | assert_eq!( |
1732 | it.next(), |
1733 | Some(PathEvent::Begin { |
1734 | at: point(0.0, 0.0) |
1735 | }) |
1736 | ); |
1737 | assert_eq!( |
1738 | it.next(), |
1739 | Some(PathEvent::Quadratic { |
1740 | from: point(0.0, 0.0), |
1741 | ctrl: point(0.0, 100.0), |
1742 | to: point(-100., 100.), |
1743 | }) |
1744 | ); |
1745 | assert_eq!( |
1746 | it.next(), |
1747 | Some(PathEvent::Line { |
1748 | from: point(-100.0, 100.0), |
1749 | to: point(-150., 100.) |
1750 | }) |
1751 | ); |
1752 | assert_eq!( |
1753 | it.next(), |
1754 | Some(PathEvent::End { |
1755 | first: point(0.0, 0.0), |
1756 | last: point(-150., 100.), |
1757 | close: false, |
1758 | }) |
1759 | ); |
1760 | assert_eq!(it.next(), None); |
1761 | } |
1762 | |
1763 | #[test ] |
1764 | fn svg_builder_arc_to_update_position() { |
1765 | use crate::Path; |
1766 | |
1767 | let mut p: WithSvg = Path::svg_builder(); |
1768 | p.move_to(point(x:0.0, y:0.0)); |
1769 | assert_eq!(p.current_position(), point(0.0, 0.0)); |
1770 | p.arc_to( |
1771 | radii:vector(100., 100.), |
1772 | x_rotation:Angle::degrees(0.), |
1773 | flags:ArcFlags::default(), |
1774 | to:point(x:0.0, y:100.0), |
1775 | ); |
1776 | assert_ne!(p.current_position(), point(0.0, 0.0)); |
1777 | } |
1778 | |
1779 | #[test ] |
1780 | fn issue_650() { |
1781 | let mut builder: WithSvg = crate::path::Path::builder().with_svg(); |
1782 | builder.arc( |
1783 | center:point(0.0, 0.0), |
1784 | radii:vector(50.0, 50.0), |
1785 | sweep_angle:Angle::radians(PI), |
1786 | x_rotation:Angle::radians(0.0), |
1787 | ); |
1788 | builder.build(); |
1789 | } |
1790 | |
1791 | #[test ] |
1792 | fn straight_line_arc() { |
1793 | use crate::Path; |
1794 | |
1795 | let mut p: WithSvg = Path::svg_builder(); |
1796 | p.move_to(point(x:100.0, y:0.0)); |
1797 | // Don't assert on a "false" arc that's a straight line |
1798 | p.arc_to( |
1799 | radii:vector(100., 100.), |
1800 | x_rotation:Angle::degrees(0.), |
1801 | flags:ArcFlags::default(), |
1802 | to:point(x:100.0, y:0.0), |
1803 | ); |
1804 | } |
1805 | |