kurbo/
bezpath.rs

1// Copyright 2018 the Kurbo Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Bézier paths (up to cubic).
5
6#![allow(clippy::many_single_char_names)]
7
8use core::iter::{Extend, FromIterator};
9use core::mem;
10use core::ops::{Mul, Range};
11
12use alloc::vec::Vec;
13
14use arrayvec::ArrayVec;
15
16use crate::common::{solve_cubic, solve_quadratic};
17use crate::MAX_EXTREMA;
18use crate::{
19    Affine, CubicBez, Line, Nearest, ParamCurve, ParamCurveArclen, ParamCurveArea,
20    ParamCurveExtrema, ParamCurveNearest, Point, QuadBez, Rect, Shape, TranslateScale, Vec2,
21};
22
23#[cfg(not(feature = "std"))]
24use crate::common::FloatFuncs;
25
26/// A Bézier path.
27///
28/// These docs assume basic familiarity with Bézier curves; for an introduction,
29/// see Pomax's wonderful [A Primer on Bézier Curves].
30///
31/// This path can contain lines, quadratics ([`QuadBez`]) and cubics
32/// ([`CubicBez`]), and may contain multiple subpaths.
33///
34/// # Elements and Segments
35///
36/// A Bézier path can be represented in terms of either 'elements' ([`PathEl`])
37/// or 'segments' ([`PathSeg`]). Elements map closely to how Béziers are
38/// generally used in PostScript-style drawing APIs; they can be thought of as
39/// instructions for drawing the path. Segments more directly describe the
40/// path itself, with each segment being an independent line or curve.
41///
42/// These different representations are useful in different contexts.
43/// For tasks like drawing, elements are a natural fit, but when doing
44/// hit-testing or subdividing, we need to have access to the segments.
45///
46/// Conceptually, a `BezPath` contains zero or more subpaths. Each subpath
47/// *always* begins with a `MoveTo`, then has zero or more `LineTo`, `QuadTo`,
48/// and `CurveTo` elements, and optionally ends with a `ClosePath`.
49///
50/// Internally, a `BezPath` is a list of [`PathEl`]s; as such it implements
51/// [`FromIterator<PathEl>`] and [`Extend<PathEl>`]:
52///
53/// ```
54/// use kurbo::{BezPath, Rect, Shape, Vec2};
55/// let accuracy = 0.1;
56/// let rect = Rect::from_origin_size((0., 0.,), (10., 10.));
57/// // these are equivalent
58/// let path1 = rect.to_path(accuracy);
59/// let path2: BezPath = rect.path_elements(accuracy).collect();
60///
61/// // extend a path with another path:
62/// let mut path = rect.to_path(accuracy);
63/// let shifted_rect = rect + Vec2::new(5.0, 10.0);
64/// path.extend(shifted_rect.to_path(accuracy));
65/// ```
66///
67/// You can iterate the elements of a `BezPath` with the [`iter`] method,
68/// and the segments with the [`segments`] method:
69///
70/// ```
71/// use kurbo::{BezPath, Line, PathEl, PathSeg, Point, Rect, Shape};
72/// let accuracy = 0.1;
73/// let rect = Rect::from_origin_size((0., 0.,), (10., 10.));
74/// // these are equivalent
75/// let path = rect.to_path(accuracy);
76/// let first_el = PathEl::MoveTo(Point::ZERO);
77/// let first_seg = PathSeg::Line(Line::new((0., 0.), (10., 0.)));
78/// assert_eq!(path.iter().next(), Some(first_el));
79/// assert_eq!(path.segments().next(), Some(first_seg));
80/// ```
81/// In addition, if you have some other type that implements
82/// `Iterator<Item=PathEl>`, you can adapt that to an iterator of segments with
83/// the [`segments` free function].
84///
85/// # Advanced functionality
86///
87/// In addition to the basic API, there are several useful pieces of advanced
88/// functionality available on `BezPath`:
89///
90/// - [`flatten`] does Bézier flattening, converting a curve to a series of
91///   line segments
92/// - [`intersect_line`] computes intersections of a path with a line, useful
93///   for things like subdividing
94///
95/// [A Primer on Bézier Curves]: https://pomax.github.io/bezierinfo/
96/// [`iter`]: BezPath::iter
97/// [`segments`]: BezPath::segments
98/// [`flatten`]: flatten
99/// [`intersect_line`]: PathSeg::intersect_line
100/// [`segments` free function]: segments
101/// [`FromIterator<PathEl>`]: std::iter::FromIterator
102/// [`Extend<PathEl>`]: std::iter::Extend
103#[derive(Clone, Default, Debug, PartialEq)]
104#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
105#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
106pub struct BezPath(Vec<PathEl>);
107
108/// The element of a Bézier path.
109///
110/// A valid path has `MoveTo` at the beginning of each subpath.
111#[derive(Clone, Copy, Debug, PartialEq)]
112#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
113#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
114pub enum PathEl {
115    /// Move directly to the point without drawing anything, starting a new
116    /// subpath.
117    MoveTo(Point),
118    /// Draw a line from the current location to the point.
119    LineTo(Point),
120    /// Draw a quadratic bezier using the current location and the two points.
121    QuadTo(Point, Point),
122    /// Draw a cubic bezier using the current location and the three points.
123    CurveTo(Point, Point, Point),
124    /// Close off the path.
125    ClosePath,
126}
127
128/// A segment of a Bézier path.
129#[derive(Clone, Copy, Debug, PartialEq)]
130#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
131#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
132pub enum PathSeg {
133    /// A line segment.
134    Line(Line),
135    /// A quadratic bezier segment.
136    Quad(QuadBez),
137    /// A cubic bezier segment.
138    Cubic(CubicBez),
139}
140
141/// An intersection of a [`Line`] and a [`PathSeg`].
142///
143/// This can be generated with the [`PathSeg::intersect_line`] method.
144#[derive(Debug, Clone, Copy)]
145pub struct LineIntersection {
146    /// The 'time' that the intersection occurs, on the line.
147    ///
148    /// This value is in the range 0..1.
149    pub line_t: f64,
150
151    /// The 'time' that the intersection occurs, on the path segment.
152    ///
153    /// This value is nominally in the range 0..1, although it may slightly exceed
154    /// that range at the boundaries of segments.
155    pub segment_t: f64,
156}
157
158/// The minimum distance between two Bézier curves.
159pub struct MinDistance {
160    /// The shortest distance between any two points on the two curves.
161    pub distance: f64,
162    /// The position of the nearest point on the first curve, as a parameter.
163    ///
164    /// To resolve this to a [`Point`], use [`ParamCurve::eval`].
165    ///
166    /// [`ParamCurve::eval`]: crate::ParamCurve::eval
167    pub t1: f64,
168    /// The position of the nearest point on the second curve, as a parameter.
169    ///
170    /// To resolve this to a [`Point`], use [`ParamCurve::eval`].
171    ///
172    /// [`ParamCurve::eval`]: crate::ParamCurve::eval
173    pub t2: f64,
174}
175
176impl BezPath {
177    /// Create a new path.
178    #[inline(always)]
179    pub fn new() -> BezPath {
180        BezPath::default()
181    }
182
183    /// Create a new path with the specified capacity.
184    ///
185    /// This can be useful if you already know how many path elements the path
186    /// will consist of, to prevent reallocations.
187    pub fn with_capacity(capacity: usize) -> BezPath {
188        BezPath(Vec::with_capacity(capacity))
189    }
190
191    /// Create a path from a vector of path elements.
192    ///
193    /// `BezPath` also implements `FromIterator<PathEl>`, so it works with `collect`:
194    ///
195    /// ```
196    /// // a very contrived example:
197    /// use kurbo::{BezPath, PathEl};
198    ///
199    /// let path = BezPath::new();
200    /// let as_vec: Vec<PathEl> = path.into_iter().collect();
201    /// let back_to_path: BezPath = as_vec.into_iter().collect();
202    /// ```
203    pub fn from_vec(v: Vec<PathEl>) -> BezPath {
204        debug_assert!(
205            v.is_empty() || matches!(v.first(), Some(PathEl::MoveTo(_))),
206            "BezPath must begin with MoveTo"
207        );
208        BezPath(v)
209    }
210
211    /// Removes the last [`PathEl`] from the path and returns it, or `None` if the path is empty.
212    pub fn pop(&mut self) -> Option<PathEl> {
213        self.0.pop()
214    }
215
216    /// Push a generic path element onto the path.
217    pub fn push(&mut self, el: PathEl) {
218        self.0.push(el);
219        debug_assert!(
220            matches!(self.0.first(), Some(PathEl::MoveTo(_))),
221            "BezPath must begin with MoveTo"
222        );
223    }
224
225    /// Push a "move to" element onto the path.
226    pub fn move_to<P: Into<Point>>(&mut self, p: P) {
227        self.push(PathEl::MoveTo(p.into()));
228    }
229
230    /// Push a "line to" element onto the path.
231    ///
232    /// Will panic with a debug assert when the path is empty and there is no
233    /// "move to" element on the path.
234    ///
235    /// If `line_to` is called immediately after `close_path` then the current
236    /// subpath starts at the initial point of the previous subpath.
237    pub fn line_to<P: Into<Point>>(&mut self, p: P) {
238        debug_assert!(!self.0.is_empty(), "uninitialized subpath (missing MoveTo)");
239        self.push(PathEl::LineTo(p.into()));
240    }
241
242    /// Push a "quad to" element onto the path.
243    ///
244    /// Will panic with a debug assert when the path is empty and there is no
245    /// "move to" element on the path.
246    ///
247    /// If `quad_to` is called immediately after `close_path` then the current
248    /// subpath starts at the initial point of the previous subpath.
249    pub fn quad_to<P: Into<Point>>(&mut self, p1: P, p2: P) {
250        debug_assert!(!self.0.is_empty(), "uninitialized subpath (missing MoveTo)");
251        self.push(PathEl::QuadTo(p1.into(), p2.into()));
252    }
253
254    /// Push a "curve to" element onto the path.
255    ///
256    /// Will panic with a debug assert when the path is empty and there is no
257    /// "move to" element on the path.
258    ///
259    /// If `curve_to` is called immediately after `close_path` then the current
260    /// subpath starts at the initial point of the previous subpath.
261    pub fn curve_to<P: Into<Point>>(&mut self, p1: P, p2: P, p3: P) {
262        debug_assert!(!self.0.is_empty(), "uninitialized subpath (missing MoveTo)");
263        self.push(PathEl::CurveTo(p1.into(), p2.into(), p3.into()));
264    }
265
266    /// Push a "close path" element onto the path.
267    ///
268    /// Will panic with a debug assert when the path is empty and there is no
269    /// "move to" element on the path.
270    pub fn close_path(&mut self) {
271        debug_assert!(!self.0.is_empty(), "uninitialized subpath (missing MoveTo)");
272        self.push(PathEl::ClosePath);
273    }
274
275    /// Get the path elements.
276    #[inline(always)]
277    pub fn elements(&self) -> &[PathEl] {
278        &self.0
279    }
280
281    /// Get the path elements (mut version).
282    #[inline(always)]
283    pub fn elements_mut(&mut self) -> &mut [PathEl] {
284        &mut self.0
285    }
286
287    /// Returns an iterator over the path's elements.
288    pub fn iter(&self) -> impl Iterator<Item = PathEl> + Clone + '_ {
289        self.0.iter().copied()
290    }
291
292    /// Iterate over the path segments.
293    pub fn segments(&self) -> impl Iterator<Item = PathSeg> + Clone + '_ {
294        segments(self.iter())
295    }
296
297    /// Shorten the path, keeping the first `len` elements.
298    pub fn truncate(&mut self, len: usize) {
299        self.0.truncate(len);
300    }
301
302    /// Flatten the path, invoking the callback repeatedly.
303    ///
304    /// See [`flatten`] for more discussion.
305    #[deprecated(since = "0.11.1", note = "use the free function flatten instead")]
306    pub fn flatten(&self, tolerance: f64, callback: impl FnMut(PathEl)) {
307        flatten(self, tolerance, callback);
308    }
309
310    /// Get the segment at the given element index.
311    ///
312    /// If you need to access all segments, [`segments`] provides a better
313    /// API. This is intended for random access of specific elements, for clients
314    /// that require this specifically.
315    ///
316    /// **note**: This returns the segment that ends at the provided element
317    /// index. In effect this means it is *1-indexed*: since no segment ends at
318    /// the first element (which is presumed to be a `MoveTo`) `get_seg(0)` will
319    /// always return `None`.
320    pub fn get_seg(&self, ix: usize) -> Option<PathSeg> {
321        if ix == 0 || ix >= self.0.len() {
322            return None;
323        }
324        let last = match self.0[ix - 1] {
325            PathEl::MoveTo(p) => p,
326            PathEl::LineTo(p) => p,
327            PathEl::QuadTo(_, p2) => p2,
328            PathEl::CurveTo(_, _, p3) => p3,
329            PathEl::ClosePath => return None,
330        };
331        match self.0[ix] {
332            PathEl::LineTo(p) => Some(PathSeg::Line(Line::new(last, p))),
333            PathEl::QuadTo(p1, p2) => Some(PathSeg::Quad(QuadBez::new(last, p1, p2))),
334            PathEl::CurveTo(p1, p2, p3) => Some(PathSeg::Cubic(CubicBez::new(last, p1, p2, p3))),
335            PathEl::ClosePath => self.0[..ix].iter().rev().find_map(|el| match *el {
336                PathEl::MoveTo(start) if start != last => {
337                    Some(PathSeg::Line(Line::new(last, start)))
338                }
339                _ => None,
340            }),
341            PathEl::MoveTo(_) => None,
342        }
343    }
344
345    /// Returns `true` if the path contains no segments.
346    pub fn is_empty(&self) -> bool {
347        self.0
348            .iter()
349            .all(|el| matches!(el, PathEl::MoveTo(..) | PathEl::ClosePath))
350    }
351
352    /// Apply an affine transform to the path.
353    pub fn apply_affine(&mut self, affine: Affine) {
354        for el in self.0.iter_mut() {
355            *el = affine * (*el);
356        }
357    }
358
359    /// Is this path finite?
360    #[inline]
361    pub fn is_finite(&self) -> bool {
362        self.0.iter().all(|v| v.is_finite())
363    }
364
365    /// Is this path NaN?
366    #[inline]
367    pub fn is_nan(&self) -> bool {
368        self.0.iter().any(|v| v.is_nan())
369    }
370
371    /// Returns a rectangle that conservatively encloses the path.
372    ///
373    /// Unlike the `bounding_box` method, this uses control points directly
374    /// rather than computing tight bounds for curve elements.
375    pub fn control_box(&self) -> Rect {
376        let mut cbox: Option<Rect> = None;
377        let mut add_pts = |pts: &[Point]| {
378            for pt in pts {
379                cbox = match cbox {
380                    Some(cbox) => Some(cbox.union_pt(*pt)),
381                    _ => Some(Rect::from_points(*pt, *pt)),
382                };
383            }
384        };
385        for &el in self.elements() {
386            match el {
387                PathEl::MoveTo(p0) | PathEl::LineTo(p0) => add_pts(&[p0]),
388                PathEl::QuadTo(p0, p1) => add_pts(&[p0, p1]),
389                PathEl::CurveTo(p0, p1, p2) => add_pts(&[p0, p1, p2]),
390                PathEl::ClosePath => {}
391            }
392        }
393        cbox.unwrap_or_default()
394    }
395
396    /// Returns current position in the path, if path is not empty.
397    ///
398    /// This is different from calling [`PathEl::end_point`] on the last entry of [`BezPath::elements`]:
399    /// this method handles [`PathEl::ClosePath`],
400    /// by finding the first point of our last subpath, hence the time complexity is O(n).
401    pub fn current_position(&self) -> Option<Point> {
402        match self.0.last()? {
403            PathEl::MoveTo(p) => Some(*p),
404            PathEl::LineTo(p1) => Some(*p1),
405            PathEl::QuadTo(_, p2) => Some(*p2),
406            PathEl::CurveTo(_, _, p3) => Some(*p3),
407            PathEl::ClosePath => self
408                .elements()
409                .iter()
410                .rev()
411                .skip(1)
412                .take_while(|el| !matches!(el, PathEl::ClosePath))
413                .last()
414                .and_then(|el| el.end_point()),
415        }
416    }
417
418    /// Returns a new path with the winding direction of all subpaths reversed.
419    pub fn reverse_subpaths(&self) -> BezPath {
420        let elements = self.elements();
421        let mut start_ix = 1;
422        let mut start_pt = Point::default();
423        let mut reversed = BezPath(Vec::with_capacity(elements.len()));
424        // Pending move is used to capture degenerate subpaths that should
425        // remain in the reversed output.
426        let mut pending_move = false;
427        for (ix, el) in elements.iter().enumerate() {
428            match el {
429                PathEl::MoveTo(pt) => {
430                    if pending_move {
431                        reversed.push(PathEl::MoveTo(start_pt));
432                    }
433                    if start_ix < ix {
434                        reverse_subpath(start_pt, &elements[start_ix..ix], &mut reversed);
435                    }
436                    pending_move = true;
437                    start_pt = *pt;
438                    start_ix = ix + 1;
439                }
440                PathEl::ClosePath => {
441                    if start_ix <= ix {
442                        reverse_subpath(start_pt, &elements[start_ix..ix], &mut reversed);
443                    }
444                    reversed.push(PathEl::ClosePath);
445                    start_ix = ix + 1;
446                    pending_move = false;
447                }
448                _ => {
449                    pending_move = false;
450                }
451            }
452        }
453        if start_ix < elements.len() {
454            reverse_subpath(start_pt, &elements[start_ix..], &mut reversed);
455        } else if pending_move {
456            reversed.push(PathEl::MoveTo(start_pt));
457        }
458        reversed
459    }
460}
461
462/// Helper for reversing a subpath.
463///
464/// The `els` parameter must not contain any `MoveTo` or `ClosePath` elements.
465fn reverse_subpath(start_pt: Point, els: &[PathEl], reversed: &mut BezPath) {
466    let end_pt = els.last().and_then(|el| el.end_point()).unwrap_or(start_pt);
467    reversed.push(PathEl::MoveTo(end_pt));
468    for (ix, el) in els.iter().enumerate().rev() {
469        let end_pt = if ix > 0 {
470            els[ix - 1].end_point().unwrap()
471        } else {
472            start_pt
473        };
474        match el {
475            PathEl::LineTo(_) => reversed.push(PathEl::LineTo(end_pt)),
476            PathEl::QuadTo(c0, _) => reversed.push(PathEl::QuadTo(*c0, end_pt)),
477            PathEl::CurveTo(c0, c1, _) => reversed.push(PathEl::CurveTo(*c1, *c0, end_pt)),
478            _ => panic!("reverse_subpath expects MoveTo and ClosePath to be removed"),
479        }
480    }
481}
482
483impl FromIterator<PathEl> for BezPath {
484    fn from_iter<T: IntoIterator<Item = PathEl>>(iter: T) -> Self {
485        let el_vec: Vec<_> = iter.into_iter().collect();
486        BezPath::from_vec(el_vec)
487    }
488}
489
490/// Allow iteration over references to `BezPath`.
491///
492/// Note: the semantics are slightly different from simply iterating over the
493/// slice, as it returns `PathEl` items, rather than references.
494impl<'a> IntoIterator for &'a BezPath {
495    type Item = PathEl;
496    type IntoIter = core::iter::Cloned<core::slice::Iter<'a, PathEl>>;
497
498    fn into_iter(self) -> Self::IntoIter {
499        self.elements().iter().cloned()
500    }
501}
502
503impl IntoIterator for BezPath {
504    type Item = PathEl;
505    type IntoIter = alloc::vec::IntoIter<PathEl>;
506
507    fn into_iter(self) -> Self::IntoIter {
508        self.0.into_iter()
509    }
510}
511
512impl Extend<PathEl> for BezPath {
513    fn extend<I: IntoIterator<Item = PathEl>>(&mut self, iter: I) {
514        self.0.extend(iter);
515    }
516}
517
518/// Proportion of tolerance budget that goes to cubic to quadratic conversion.
519const TO_QUAD_TOL: f64 = 0.1;
520
521/// Flatten the path, invoking the callback repeatedly.
522///
523/// Flattening is the action of approximating a curve with a succession of line segments.
524///
525/// <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 30" height="30mm" width="120mm">
526///   <path d="M26.7 24.94l.82-11.15M44.46 5.1L33.8 7.34" fill="none" stroke="#55d400" stroke-width=".5"/>
527///   <path d="M26.7 24.94c.97-11.13 7.17-17.6 17.76-19.84M75.27 24.94l1.13-5.5 2.67-5.48 4-4.42L88 6.7l5.02-1.6" fill="none" stroke="#000"/>
528///   <path d="M77.57 19.37a1.1 1.1 0 0 1-1.08 1.08 1.1 1.1 0 0 1-1.1-1.08 1.1 1.1 0 0 1 1.08-1.1 1.1 1.1 0 0 1 1.1 1.1" color="#000" fill="none" stroke="#030303" stroke-linecap="round" stroke-opacity=".5"/>
529///   <path d="M77.57 19.37a1.1 1.1 0 0 1-1.08 1.08 1.1 1.1 0 0 1-1.1-1.08 1.1 1.1 0 0 1 1.08-1.1 1.1 1.1 0 0 1 1.1 1.1" color="#000" fill="#fff"/>
530///   <path d="M80.22 13.93a1.1 1.1 0 0 1-1.1 1.1 1.1 1.1 0 0 1-1.08-1.1 1.1 1.1 0 0 1 1.1-1.08 1.1 1.1 0 0 1 1.08 1.08" color="#000" fill="none" stroke="#030303" stroke-linecap="round" stroke-opacity=".5"/>
531///   <path d="M80.22 13.93a1.1 1.1 0 0 1-1.1 1.1 1.1 1.1 0 0 1-1.08-1.1 1.1 1.1 0 0 1 1.1-1.08 1.1 1.1 0 0 1 1.08 1.08" color="#000" fill="#fff"/>
532///   <path d="M84.08 9.55a1.1 1.1 0 0 1-1.08 1.1 1.1 1.1 0 0 1-1.1-1.1 1.1 1.1 0 0 1 1.1-1.1 1.1 1.1 0 0 1 1.08 1.1" color="#000" fill="none" stroke="#030303" stroke-linecap="round" stroke-opacity=".5"/>
533///   <path d="M84.08 9.55a1.1 1.1 0 0 1-1.08 1.1 1.1 1.1 0 0 1-1.1-1.1 1.1 1.1 0 0 1 1.1-1.1 1.1 1.1 0 0 1 1.08 1.1" color="#000" fill="#fff"/>
534///   <path d="M89.1 6.66a1.1 1.1 0 0 1-1.08 1.1 1.1 1.1 0 0 1-1.08-1.1 1.1 1.1 0 0 1 1.08-1.08 1.1 1.1 0 0 1 1.1 1.08" color="#000" fill="none" stroke="#030303" stroke-linecap="round" stroke-opacity=".5"/>
535///   <path d="M89.1 6.66a1.1 1.1 0 0 1-1.08 1.1 1.1 1.1 0 0 1-1.08-1.1 1.1 1.1 0 0 1 1.08-1.08 1.1 1.1 0 0 1 1.1 1.08" color="#000" fill="#fff"/>
536///   <path d="M94.4 5a1.1 1.1 0 0 1-1.1 1.1A1.1 1.1 0 0 1 92.23 5a1.1 1.1 0 0 1 1.08-1.08A1.1 1.1 0 0 1 94.4 5" color="#000" fill="none" stroke="#030303" stroke-linecap="round" stroke-opacity=".5"/>
537///   <path d="M94.4 5a1.1 1.1 0 0 1-1.1 1.1A1.1 1.1 0 0 1 92.23 5a1.1 1.1 0 0 1 1.08-1.08A1.1 1.1 0 0 1 94.4 5" color="#000" fill="#fff"/>
538///   <path d="M76.44 25.13a1.1 1.1 0 0 1-1.1 1.1 1.1 1.1 0 0 1-1.08-1.1 1.1 1.1 0 0 1 1.1-1.1 1.1 1.1 0 0 1 1.08 1.1" color="#000" fill="none" stroke="#030303" stroke-linecap="round" stroke-opacity=".5"/>
539///   <path d="M76.44 25.13a1.1 1.1 0 0 1-1.1 1.1 1.1 1.1 0 0 1-1.08-1.1 1.1 1.1 0 0 1 1.1-1.1 1.1 1.1 0 0 1 1.08 1.1" color="#000" fill="#fff"/>
540///   <path d="M27.78 24.9a1.1 1.1 0 0 1-1.08 1.08 1.1 1.1 0 0 1-1.1-1.08 1.1 1.1 0 0 1 1.1-1.1 1.1 1.1 0 0 1 1.08 1.1" color="#000" fill="none" stroke="#030303" stroke-linecap="round" stroke-opacity=".5"/>
541///   <path d="M27.78 24.9a1.1 1.1 0 0 1-1.08 1.08 1.1 1.1 0 0 1-1.1-1.08 1.1 1.1 0 0 1 1.1-1.1 1.1 1.1 0 0 1 1.08 1.1" color="#000" fill="#fff"/>
542///   <path d="M45.4 5.14a1.1 1.1 0 0 1-1.08 1.1 1.1 1.1 0 0 1-1.1-1.1 1.1 1.1 0 0 1 1.1-1.08 1.1 1.1 0 0 1 1.1 1.08" color="#000" fill="none" stroke="#030303" stroke-linecap="round" stroke-opacity=".5"/>
543///   <path d="M45.4 5.14a1.1 1.1 0 0 1-1.08 1.1 1.1 1.1 0 0 1-1.1-1.1 1.1 1.1 0 0 1 1.1-1.08 1.1 1.1 0 0 1 1.1 1.08" color="#000" fill="#fff"/>
544///   <path d="M28.67 13.8a1.1 1.1 0 0 1-1.1 1.08 1.1 1.1 0 0 1-1.08-1.08 1.1 1.1 0 0 1 1.08-1.1 1.1 1.1 0 0 1 1.1 1.1" color="#000" fill="none" stroke="#030303" stroke-linecap="round" stroke-opacity=".5"/>
545///   <path d="M28.67 13.8a1.1 1.1 0 0 1-1.1 1.08 1.1 1.1 0 0 1-1.08-1.08 1.1 1.1 0 0 1 1.08-1.1 1.1 1.1 0 0 1 1.1 1.1" color="#000" fill="#fff"/>
546///   <path d="M35 7.32a1.1 1.1 0 0 1-1.1 1.1 1.1 1.1 0 0 1-1.08-1.1 1.1 1.1 0 0 1 1.1-1.1A1.1 1.1 0 0 1 35 7.33" color="#000" fill="none" stroke="#030303" stroke-linecap="round" stroke-opacity=".5"/>
547///   <path d="M35 7.32a1.1 1.1 0 0 1-1.1 1.1 1.1 1.1 0 0 1-1.08-1.1 1.1 1.1 0 0 1 1.1-1.1A1.1 1.1 0 0 1 35 7.33" color="#000" fill="#fff"/>
548///   <text style="line-height:6.61458302px" x="35.74" y="284.49" font-size="5.29" font-family="Sans" letter-spacing="0" word-spacing="0" fill="#b3b3b3" stroke-width=".26" transform="translate(19.595 -267)">
549///     <tspan x="35.74" y="284.49" font-size="10.58">→</tspan>
550///   </text>
551/// </svg>
552///
553/// The tolerance value controls the maximum distance between the curved input
554/// segments and their polyline approximations. (In technical terms, this is the
555/// Hausdorff distance). The algorithm attempts to bound this distance between
556/// by `tolerance` but this is not absolutely guaranteed. The appropriate value
557/// depends on the use, but for antialiased rendering, a value of 0.25 has been
558/// determined to give good results. The number of segments tends to scale as the
559/// inverse square root of tolerance.
560///
561/// <svg viewBox="0 0 47.5 13.2" height="100" width="350" xmlns="http://www.w3.org/2000/svg">
562///   <path d="M-2.44 9.53c16.27-8.5 39.68-7.93 52.13 1.9" fill="none" stroke="#dde9af" stroke-width="4.6"/>
563///   <path d="M-1.97 9.3C14.28 1.03 37.36 1.7 49.7 11.4" fill="none" stroke="#00d400" stroke-width=".57" stroke-linecap="round" stroke-dasharray="4.6, 2.291434"/>
564///   <path d="M-1.94 10.46L6.2 6.08l28.32-1.4 15.17 6.74" fill="none" stroke="#000" stroke-width=".6"/>
565///   <path d="M6.83 6.57a.9.9 0 0 1-1.25.15.9.9 0 0 1-.15-1.25.9.9 0 0 1 1.25-.15.9.9 0 0 1 .15 1.25" color="#000" stroke="#000" stroke-width=".57" stroke-linecap="round" stroke-opacity=".5"/>
566///   <path d="M35.35 5.3a.9.9 0 0 1-1.25.15.9.9 0 0 1-.15-1.25.9.9 0 0 1 1.25-.15.9.9 0 0 1 .15 1.24" color="#000" stroke="#000" stroke-width=".6" stroke-opacity=".5"/>
567///   <g fill="none" stroke="#ff7f2a" stroke-width=".26">
568///     <path d="M20.4 3.8l.1 1.83M19.9 4.28l.48-.56.57.52M21.02 5.18l-.5.56-.6-.53" stroke-width=".2978872"/>
569///   </g>
570/// </svg>
571///
572/// The callback will be called in order with each element of the generated
573/// path. Because the result is made of polylines, these will be straight-line
574/// path elements only, no curves.
575///
576/// This algorithm is based on the blog post [Flattening quadratic Béziers]
577/// but with some refinements. For one, there is a more careful approximation
578/// at cusps. For two, the algorithm is extended to work with cubic Béziers
579/// as well, by first subdividing into quadratics and then computing the
580/// subdivision of each quadratic. However, as a clever trick, these quadratics
581/// are subdivided fractionally, and their endpoints are not included.
582///
583/// TODO: write a paper explaining this in more detail.
584///
585/// [Flattening quadratic Béziers]: https://raphlinus.github.io/graphics/curves/2019/12/23/flatten-quadbez.html
586pub fn flatten(
587    path: impl IntoIterator<Item = PathEl>,
588    tolerance: f64,
589    mut callback: impl FnMut(PathEl),
590) {
591    let sqrt_tol = tolerance.sqrt();
592    let mut last_pt = None;
593    let mut quad_buf = Vec::new();
594    for el in path {
595        match el {
596            PathEl::MoveTo(p) => {
597                last_pt = Some(p);
598                callback(PathEl::MoveTo(p));
599            }
600            PathEl::LineTo(p) => {
601                last_pt = Some(p);
602                callback(PathEl::LineTo(p));
603            }
604            PathEl::QuadTo(p1, p2) => {
605                if let Some(p0) = last_pt {
606                    let q = QuadBez::new(p0, p1, p2);
607                    let params = q.estimate_subdiv(sqrt_tol);
608                    let n = ((0.5 * params.val / sqrt_tol).ceil() as usize).max(1);
609                    let step = 1.0 / (n as f64);
610                    for i in 1..n {
611                        let u = (i as f64) * step;
612                        let t = q.determine_subdiv_t(&params, u);
613                        let p = q.eval(t);
614                        callback(PathEl::LineTo(p));
615                    }
616                    callback(PathEl::LineTo(p2));
617                }
618                last_pt = Some(p2);
619            }
620            PathEl::CurveTo(p1, p2, p3) => {
621                if let Some(p0) = last_pt {
622                    let c = CubicBez::new(p0, p1, p2, p3);
623
624                    // Subdivide into quadratics, and estimate the number of
625                    // subdivisions required for each, summing to arrive at an
626                    // estimate for the number of subdivisions for the cubic.
627                    // Also retain these parameters for later.
628                    let iter = c.to_quads(tolerance * TO_QUAD_TOL);
629                    quad_buf.clear();
630                    quad_buf.reserve(iter.size_hint().0);
631                    let sqrt_remain_tol = sqrt_tol * (1.0 - TO_QUAD_TOL).sqrt();
632                    let mut sum = 0.0;
633                    for (_, _, q) in iter {
634                        let params = q.estimate_subdiv(sqrt_remain_tol);
635                        sum += params.val;
636                        quad_buf.push((q, params));
637                    }
638                    let n = ((0.5 * sum / sqrt_remain_tol).ceil() as usize).max(1);
639
640                    // Iterate through the quadratics, outputting the points of
641                    // subdivisions that fall within that quadratic.
642                    let step = sum / (n as f64);
643                    let mut i = 1;
644                    let mut val_sum = 0.0;
645                    for (q, params) in &quad_buf {
646                        let mut target = (i as f64) * step;
647                        let recip_val = params.val.recip();
648                        while target < val_sum + params.val {
649                            let u = (target - val_sum) * recip_val;
650                            let t = q.determine_subdiv_t(params, u);
651                            let p = q.eval(t);
652                            callback(PathEl::LineTo(p));
653                            i += 1;
654                            if i == n + 1 {
655                                break;
656                            }
657                            target = (i as f64) * step;
658                        }
659                        val_sum += params.val;
660                    }
661                    callback(PathEl::LineTo(p3));
662                }
663                last_pt = Some(p3);
664            }
665            PathEl::ClosePath => {
666                last_pt = None;
667                callback(PathEl::ClosePath);
668            }
669        }
670    }
671}
672
673impl Mul<PathEl> for Affine {
674    type Output = PathEl;
675
676    // TODO: Inlining this leads to a huge performance benefit, perhaps the same should be done for
677    // other methods.
678    #[inline(always)]
679    fn mul(self, other: PathEl) -> PathEl {
680        match other {
681            PathEl::MoveTo(p) => PathEl::MoveTo(self * p),
682            PathEl::LineTo(p) => PathEl::LineTo(self * p),
683            PathEl::QuadTo(p1, p2) => PathEl::QuadTo(self * p1, self * p2),
684            PathEl::CurveTo(p1, p2, p3) => PathEl::CurveTo(self * p1, self * p2, self * p3),
685            PathEl::ClosePath => PathEl::ClosePath,
686        }
687    }
688}
689
690impl Mul<PathSeg> for Affine {
691    type Output = PathSeg;
692
693    fn mul(self, other: PathSeg) -> PathSeg {
694        match other {
695            PathSeg::Line(line) => PathSeg::Line(self * line),
696            PathSeg::Quad(quad) => PathSeg::Quad(self * quad),
697            PathSeg::Cubic(cubic) => PathSeg::Cubic(self * cubic),
698        }
699    }
700}
701
702impl Mul<BezPath> for Affine {
703    type Output = BezPath;
704
705    fn mul(self, other: BezPath) -> BezPath {
706        BezPath(other.0.iter().map(|&el| self * el).collect())
707    }
708}
709
710impl Mul<&BezPath> for Affine {
711    type Output = BezPath;
712
713    fn mul(self, other: &BezPath) -> BezPath {
714        BezPath(other.0.iter().map(|&el| self * el).collect())
715    }
716}
717
718impl Mul<PathEl> for TranslateScale {
719    type Output = PathEl;
720
721    fn mul(self, other: PathEl) -> PathEl {
722        match other {
723            PathEl::MoveTo(p) => PathEl::MoveTo(self * p),
724            PathEl::LineTo(p) => PathEl::LineTo(self * p),
725            PathEl::QuadTo(p1, p2) => PathEl::QuadTo(self * p1, self * p2),
726            PathEl::CurveTo(p1, p2, p3) => PathEl::CurveTo(self * p1, self * p2, self * p3),
727            PathEl::ClosePath => PathEl::ClosePath,
728        }
729    }
730}
731
732impl Mul<PathSeg> for TranslateScale {
733    type Output = PathSeg;
734
735    fn mul(self, other: PathSeg) -> PathSeg {
736        match other {
737            PathSeg::Line(line) => PathSeg::Line(self * line),
738            PathSeg::Quad(quad) => PathSeg::Quad(self * quad),
739            PathSeg::Cubic(cubic) => PathSeg::Cubic(self * cubic),
740        }
741    }
742}
743
744impl Mul<BezPath> for TranslateScale {
745    type Output = BezPath;
746
747    fn mul(self, other: BezPath) -> BezPath {
748        BezPath(other.0.iter().map(|&el| self * el).collect())
749    }
750}
751
752impl Mul<&BezPath> for TranslateScale {
753    type Output = BezPath;
754
755    fn mul(self, other: &BezPath) -> BezPath {
756        BezPath(other.0.iter().map(|&el| self * el).collect())
757    }
758}
759
760/// Transform an iterator over path elements into one over path
761/// segments.
762///
763/// See also [`BezPath::segments`].
764/// This signature is a bit more general, allowing `&[PathEl]` slices
765/// and other iterators yielding `PathEl`.
766pub fn segments<I>(elements: I) -> Segments<I::IntoIter>
767where
768    I: IntoIterator<Item = PathEl>,
769{
770    Segments {
771        elements: elements.into_iter(),
772        start_last: None,
773    }
774}
775
776/// An iterator that transforms path elements to path segments.
777///
778/// This struct is created by the [`segments`] function.
779#[derive(Clone)]
780pub struct Segments<I: Iterator<Item = PathEl>> {
781    elements: I,
782    start_last: Option<(Point, Point)>,
783}
784
785impl<I: Iterator<Item = PathEl>> Iterator for Segments<I> {
786    type Item = PathSeg;
787
788    fn next(&mut self) -> Option<PathSeg> {
789        for el in &mut self.elements {
790            // We first need to check whether this is the first
791            // path element we see to fill in the start position.
792            let (start, last) = self.start_last.get_or_insert_with(|| {
793                let point = match el {
794                    PathEl::MoveTo(p) => p,
795                    PathEl::LineTo(p) => p,
796                    PathEl::QuadTo(_, p2) => p2,
797                    PathEl::CurveTo(_, _, p3) => p3,
798                    PathEl::ClosePath => panic!("Can't start a segment on a ClosePath"),
799                };
800                (point, point)
801            });
802
803            return Some(match el {
804                PathEl::MoveTo(p) => {
805                    *start = p;
806                    *last = p;
807                    continue;
808                }
809                PathEl::LineTo(p) => PathSeg::Line(Line::new(mem::replace(last, p), p)),
810                PathEl::QuadTo(p1, p2) => {
811                    PathSeg::Quad(QuadBez::new(mem::replace(last, p2), p1, p2))
812                }
813                PathEl::CurveTo(p1, p2, p3) => {
814                    PathSeg::Cubic(CubicBez::new(mem::replace(last, p3), p1, p2, p3))
815                }
816                PathEl::ClosePath => {
817                    if *last != *start {
818                        PathSeg::Line(Line::new(mem::replace(last, *start), *start))
819                    } else {
820                        continue;
821                    }
822                }
823            });
824        }
825
826        None
827    }
828}
829
830impl<I: Iterator<Item = PathEl>> Segments<I> {
831    /// Here, `accuracy` specifies the accuracy for each Bézier segment. At worst,
832    /// the total error is `accuracy` times the number of Bézier segments.
833    // TODO: pub? Or is this subsumed by method of &[PathEl]?
834    pub(crate) fn perimeter(self, accuracy: f64) -> f64 {
835        self.map(|seg| seg.arclen(accuracy)).sum()
836    }
837
838    // Same
839    pub(crate) fn area(self) -> f64 {
840        self.map(|seg| seg.signed_area()).sum()
841    }
842
843    // Same
844    pub(crate) fn winding(self, p: Point) -> i32 {
845        self.map(|seg| seg.winding(p)).sum()
846    }
847
848    // Same
849    pub(crate) fn bounding_box(self) -> Rect {
850        let mut bbox: Option<Rect> = None;
851        for seg in self {
852            let seg_bb = ParamCurveExtrema::bounding_box(&seg);
853            if let Some(bb) = bbox {
854                bbox = Some(bb.union(seg_bb));
855            } else {
856                bbox = Some(seg_bb);
857            }
858        }
859        bbox.unwrap_or_default()
860    }
861}
862
863impl ParamCurve for PathSeg {
864    fn eval(&self, t: f64) -> Point {
865        match *self {
866            PathSeg::Line(line) => line.eval(t),
867            PathSeg::Quad(quad) => quad.eval(t),
868            PathSeg::Cubic(cubic) => cubic.eval(t),
869        }
870    }
871
872    fn subsegment(&self, range: Range<f64>) -> PathSeg {
873        match *self {
874            PathSeg::Line(line) => PathSeg::Line(line.subsegment(range)),
875            PathSeg::Quad(quad) => PathSeg::Quad(quad.subsegment(range)),
876            PathSeg::Cubic(cubic) => PathSeg::Cubic(cubic.subsegment(range)),
877        }
878    }
879
880    fn start(&self) -> Point {
881        match *self {
882            PathSeg::Line(line) => line.start(),
883            PathSeg::Quad(quad) => quad.start(),
884            PathSeg::Cubic(cubic) => cubic.start(),
885        }
886    }
887
888    fn end(&self) -> Point {
889        match *self {
890            PathSeg::Line(line) => line.end(),
891            PathSeg::Quad(quad) => quad.end(),
892            PathSeg::Cubic(cubic) => cubic.end(),
893        }
894    }
895}
896
897impl ParamCurveArclen for PathSeg {
898    fn arclen(&self, accuracy: f64) -> f64 {
899        match *self {
900            PathSeg::Line(line) => line.arclen(accuracy),
901            PathSeg::Quad(quad) => quad.arclen(accuracy),
902            PathSeg::Cubic(cubic) => cubic.arclen(accuracy),
903        }
904    }
905
906    fn inv_arclen(&self, arclen: f64, accuracy: f64) -> f64 {
907        match *self {
908            PathSeg::Line(line) => line.inv_arclen(arclen, accuracy),
909            PathSeg::Quad(quad) => quad.inv_arclen(arclen, accuracy),
910            PathSeg::Cubic(cubic) => cubic.inv_arclen(arclen, accuracy),
911        }
912    }
913}
914
915impl ParamCurveArea for PathSeg {
916    fn signed_area(&self) -> f64 {
917        match *self {
918            PathSeg::Line(line) => line.signed_area(),
919            PathSeg::Quad(quad) => quad.signed_area(),
920            PathSeg::Cubic(cubic) => cubic.signed_area(),
921        }
922    }
923}
924
925impl ParamCurveNearest for PathSeg {
926    fn nearest(&self, p: Point, accuracy: f64) -> Nearest {
927        match *self {
928            PathSeg::Line(line) => line.nearest(p, accuracy),
929            PathSeg::Quad(quad) => quad.nearest(p, accuracy),
930            PathSeg::Cubic(cubic) => cubic.nearest(p, accuracy),
931        }
932    }
933}
934
935impl ParamCurveExtrema for PathSeg {
936    fn extrema(&self) -> ArrayVec<f64, MAX_EXTREMA> {
937        match *self {
938            PathSeg::Line(line) => line.extrema(),
939            PathSeg::Quad(quad) => quad.extrema(),
940            PathSeg::Cubic(cubic) => cubic.extrema(),
941        }
942    }
943}
944
945impl PathSeg {
946    /// Get the [`PathEl`] that is equivalent to discarding the segment start point.
947    pub fn as_path_el(&self) -> PathEl {
948        match self {
949            PathSeg::Line(line) => PathEl::LineTo(line.p1),
950            PathSeg::Quad(q) => PathEl::QuadTo(q.p1, q.p2),
951            PathSeg::Cubic(c) => PathEl::CurveTo(c.p1, c.p2, c.p3),
952        }
953    }
954
955    /// Returns a new `PathSeg` describing the same path as `self`, but with
956    /// the points reversed.
957    pub fn reverse(&self) -> PathSeg {
958        match self {
959            PathSeg::Line(Line { p0, p1 }) => PathSeg::Line(Line::new(*p1, *p0)),
960            PathSeg::Quad(q) => PathSeg::Quad(QuadBez::new(q.p2, q.p1, q.p0)),
961            PathSeg::Cubic(c) => PathSeg::Cubic(CubicBez::new(c.p3, c.p2, c.p1, c.p0)),
962        }
963    }
964
965    /// Convert this segment to a cubic bezier.
966    pub fn to_cubic(&self) -> CubicBez {
967        match *self {
968            PathSeg::Line(Line { p0, p1 }) => CubicBez::new(p0, p0, p1, p1),
969            PathSeg::Cubic(c) => c,
970            PathSeg::Quad(q) => q.raise(),
971        }
972    }
973
974    // Assumes split at extrema.
975    fn winding_inner(&self, p: Point) -> i32 {
976        let start = self.start();
977        let end = self.end();
978        let sign = if end.y > start.y {
979            if p.y < start.y || p.y >= end.y {
980                return 0;
981            }
982            -1
983        } else if end.y < start.y {
984            if p.y < end.y || p.y >= start.y {
985                return 0;
986            }
987            1
988        } else {
989            return 0;
990        };
991        match *self {
992            PathSeg::Line(_line) => {
993                if p.x < start.x.min(end.x) {
994                    return 0;
995                }
996                if p.x >= start.x.max(end.x) {
997                    return sign;
998                }
999                // line equation ax + by = c
1000                let a = end.y - start.y;
1001                let b = start.x - end.x;
1002                let c = a * start.x + b * start.y;
1003                if (a * p.x + b * p.y - c) * (sign as f64) <= 0.0 {
1004                    sign
1005                } else {
1006                    0
1007                }
1008            }
1009            PathSeg::Quad(quad) => {
1010                let p1 = quad.p1;
1011                if p.x < start.x.min(end.x).min(p1.x) {
1012                    return 0;
1013                }
1014                if p.x >= start.x.max(end.x).max(p1.x) {
1015                    return sign;
1016                }
1017                let a = end.y - 2.0 * p1.y + start.y;
1018                let b = 2.0 * (p1.y - start.y);
1019                let c = start.y - p.y;
1020                for t in solve_quadratic(c, b, a) {
1021                    if (0.0..=1.0).contains(&t) {
1022                        let x = quad.eval(t).x;
1023                        if p.x >= x {
1024                            return sign;
1025                        } else {
1026                            return 0;
1027                        }
1028                    }
1029                }
1030                0
1031            }
1032            PathSeg::Cubic(cubic) => {
1033                let p1 = cubic.p1;
1034                let p2 = cubic.p2;
1035                if p.x < start.x.min(end.x).min(p1.x).min(p2.x) {
1036                    return 0;
1037                }
1038                if p.x >= start.x.max(end.x).max(p1.x).max(p2.x) {
1039                    return sign;
1040                }
1041                let a = end.y - 3.0 * p2.y + 3.0 * p1.y - start.y;
1042                let b = 3.0 * (p2.y - 2.0 * p1.y + start.y);
1043                let c = 3.0 * (p1.y - start.y);
1044                let d = start.y - p.y;
1045                for t in solve_cubic(d, c, b, a) {
1046                    if (0.0..=1.0).contains(&t) {
1047                        let x = cubic.eval(t).x;
1048                        if p.x >= x {
1049                            return sign;
1050                        } else {
1051                            return 0;
1052                        }
1053                    }
1054                }
1055                0
1056            }
1057        }
1058    }
1059
1060    /// Compute the winding number contribution of a single segment.
1061    ///
1062    /// Cast a ray to the left and count intersections.
1063    fn winding(&self, p: Point) -> i32 {
1064        self.extrema_ranges()
1065            .into_iter()
1066            .map(|range| self.subsegment(range).winding_inner(p))
1067            .sum()
1068    }
1069
1070    /// Compute intersections against a line.
1071    ///
1072    /// Returns a vector of the intersections. For each intersection,
1073    /// the `t` value of the segment and line are given.
1074    ///
1075    /// Note: This test is designed to be inclusive of points near the endpoints
1076    /// of the segment. This is so that testing a line against multiple
1077    /// contiguous segments of a path will be guaranteed to catch at least one
1078    /// of them. In such cases, use higher level logic to coalesce the hits
1079    /// (the `t` value may be slightly outside the range of 0..1).
1080    ///
1081    /// # Examples
1082    ///
1083    /// ```
1084    /// # use kurbo::*;
1085    /// let seg = PathSeg::Line(Line::new((0.0, 0.0), (2.0, 0.0)));
1086    /// let line = Line::new((1.0, 2.0), (1.0, -2.0));
1087    /// let intersection = seg.intersect_line(line);
1088    /// assert_eq!(intersection.len(), 1);
1089    /// let intersection = intersection[0];
1090    /// assert_eq!(intersection.segment_t, 0.5);
1091    /// assert_eq!(intersection.line_t, 0.5);
1092    ///
1093    /// let point = seg.eval(intersection.segment_t);
1094    /// assert_eq!(point, Point::new(1.0, 0.0));
1095    /// ```
1096    pub fn intersect_line(&self, line: Line) -> ArrayVec<LineIntersection, 3> {
1097        const EPSILON: f64 = 1e-9;
1098        let p0 = line.p0;
1099        let p1 = line.p1;
1100        let dx = p1.x - p0.x;
1101        let dy = p1.y - p0.y;
1102        let mut result = ArrayVec::new();
1103        match self {
1104            PathSeg::Line(l) => {
1105                let det = dx * (l.p1.y - l.p0.y) - dy * (l.p1.x - l.p0.x);
1106                if det.abs() < EPSILON {
1107                    // Lines are coincident (or nearly so).
1108                    return result;
1109                }
1110                let t = dx * (p0.y - l.p0.y) - dy * (p0.x - l.p0.x);
1111                // t = position on self
1112                let t = t / det;
1113                if (-EPSILON..=(1.0 + EPSILON)).contains(&t) {
1114                    // u = position on probe line
1115                    let u =
1116                        (l.p0.x - p0.x) * (l.p1.y - l.p0.y) - (l.p0.y - p0.y) * (l.p1.x - l.p0.x);
1117                    let u = u / det;
1118                    if (0.0..=1.0).contains(&u) {
1119                        result.push(LineIntersection::new(u, t));
1120                    }
1121                }
1122            }
1123            PathSeg::Quad(q) => {
1124                // The basic technique here is to determine x and y as a quadratic polynomial
1125                // as a function of t. Then plug those values into the line equation for the
1126                // probe line (giving a sort of signed distance from the probe line) and solve
1127                // that for t.
1128                let (px0, px1, px2) = quadratic_bez_coefs(q.p0.x, q.p1.x, q.p2.x);
1129                let (py0, py1, py2) = quadratic_bez_coefs(q.p0.y, q.p1.y, q.p2.y);
1130                let c0 = dy * (px0 - p0.x) - dx * (py0 - p0.y);
1131                let c1 = dy * px1 - dx * py1;
1132                let c2 = dy * px2 - dx * py2;
1133                let invlen2 = (dx * dx + dy * dy).recip();
1134                for t in solve_quadratic(c0, c1, c2) {
1135                    if (-EPSILON..=(1.0 + EPSILON)).contains(&t) {
1136                        let x = px0 + t * px1 + t * t * px2;
1137                        let y = py0 + t * py1 + t * t * py2;
1138                        let u = ((x - p0.x) * dx + (y - p0.y) * dy) * invlen2;
1139                        if (0.0..=1.0).contains(&u) {
1140                            result.push(LineIntersection::new(u, t));
1141                        }
1142                    }
1143                }
1144            }
1145            PathSeg::Cubic(c) => {
1146                // Same technique as above, but cubic polynomial.
1147                let (px0, px1, px2, px3) = cubic_bez_coefs(c.p0.x, c.p1.x, c.p2.x, c.p3.x);
1148                let (py0, py1, py2, py3) = cubic_bez_coefs(c.p0.y, c.p1.y, c.p2.y, c.p3.y);
1149                let c0 = dy * (px0 - p0.x) - dx * (py0 - p0.y);
1150                let c1 = dy * px1 - dx * py1;
1151                let c2 = dy * px2 - dx * py2;
1152                let c3 = dy * px3 - dx * py3;
1153                let invlen2 = (dx * dx + dy * dy).recip();
1154                for t in solve_cubic(c0, c1, c2, c3) {
1155                    if (-EPSILON..=(1.0 + EPSILON)).contains(&t) {
1156                        let x = px0 + t * px1 + t * t * px2 + t * t * t * px3;
1157                        let y = py0 + t * py1 + t * t * py2 + t * t * t * py3;
1158                        let u = ((x - p0.x) * dx + (y - p0.y) * dy) * invlen2;
1159                        if (0.0..=1.0).contains(&u) {
1160                            result.push(LineIntersection::new(u, t));
1161                        }
1162                    }
1163                }
1164            }
1165        }
1166        result
1167    }
1168
1169    /// Is this Bezier path finite?
1170    #[inline]
1171    pub fn is_finite(&self) -> bool {
1172        match self {
1173            PathSeg::Line(line) => line.is_finite(),
1174            PathSeg::Quad(quad_bez) => quad_bez.is_finite(),
1175            PathSeg::Cubic(cubic_bez) => cubic_bez.is_finite(),
1176        }
1177    }
1178
1179    /// Is this Bezier path NaN?
1180    #[inline]
1181    pub fn is_nan(&self) -> bool {
1182        match self {
1183            PathSeg::Line(line) => line.is_nan(),
1184            PathSeg::Quad(quad_bez) => quad_bez.is_nan(),
1185            PathSeg::Cubic(cubic_bez) => cubic_bez.is_nan(),
1186        }
1187    }
1188
1189    #[inline]
1190    fn as_vec2_vec(&self) -> ArrayVec<Vec2, 4> {
1191        let mut a = ArrayVec::new();
1192        match self {
1193            PathSeg::Line(l) => {
1194                a.push(l.p0.to_vec2());
1195                a.push(l.p1.to_vec2());
1196            }
1197            PathSeg::Quad(q) => {
1198                a.push(q.p0.to_vec2());
1199                a.push(q.p1.to_vec2());
1200                a.push(q.p2.to_vec2());
1201            }
1202            PathSeg::Cubic(c) => {
1203                a.push(c.p0.to_vec2());
1204                a.push(c.p1.to_vec2());
1205                a.push(c.p2.to_vec2());
1206                a.push(c.p3.to_vec2());
1207            }
1208        }
1209        a
1210    }
1211
1212    /// Minimum distance between two [`PathSeg`]s.
1213    ///
1214    /// Returns a tuple of the distance, the path time `t1` of the closest point
1215    /// on the first `PathSeg`, and the path time `t2` of the closest point on the
1216    /// second `PathSeg`.
1217    pub fn min_dist(&self, other: PathSeg, accuracy: f64) -> MinDistance {
1218        let (distance, t1, t2) = crate::mindist::min_dist_param(
1219            &self.as_vec2_vec(),
1220            &other.as_vec2_vec(),
1221            (0.0, 1.0),
1222            (0.0, 1.0),
1223            accuracy,
1224            None,
1225        );
1226        MinDistance {
1227            distance: distance.sqrt(),
1228            t1,
1229            t2,
1230        }
1231    }
1232
1233    /// Compute endpoint tangents of a path segment.
1234    ///
1235    /// This version is robust to the path segment not being a regular curve.
1236    pub(crate) fn tangents(&self) -> (Vec2, Vec2) {
1237        const EPS: f64 = 1e-12;
1238        match self {
1239            PathSeg::Line(l) => {
1240                let d = l.p1 - l.p0;
1241                (d, d)
1242            }
1243            PathSeg::Quad(q) => {
1244                let d01 = q.p1 - q.p0;
1245                let d0 = if d01.hypot2() > EPS { d01 } else { q.p2 - q.p0 };
1246                let d12 = q.p2 - q.p1;
1247                let d1 = if d12.hypot2() > EPS { d12 } else { q.p2 - q.p0 };
1248                (d0, d1)
1249            }
1250            PathSeg::Cubic(c) => {
1251                let d01 = c.p1 - c.p0;
1252                let d0 = if d01.hypot2() > EPS {
1253                    d01
1254                } else {
1255                    let d02 = c.p2 - c.p0;
1256                    if d02.hypot2() > EPS {
1257                        d02
1258                    } else {
1259                        c.p3 - c.p0
1260                    }
1261                };
1262                let d23 = c.p3 - c.p2;
1263                let d1 = if d23.hypot2() > EPS {
1264                    d23
1265                } else {
1266                    let d13 = c.p3 - c.p1;
1267                    if d13.hypot2() > EPS {
1268                        d13
1269                    } else {
1270                        c.p3 - c.p0
1271                    }
1272                };
1273                (d0, d1)
1274            }
1275        }
1276    }
1277}
1278
1279impl LineIntersection {
1280    #[inline(always)]
1281    fn new(line_t: f64, segment_t: f64) -> Self {
1282        LineIntersection { line_t, segment_t }
1283    }
1284
1285    /// Is this line intersection finite?
1286    #[inline]
1287    pub fn is_finite(self) -> bool {
1288        self.line_t.is_finite() && self.segment_t.is_finite()
1289    }
1290
1291    /// Is this line intersection NaN?
1292    #[inline]
1293    pub fn is_nan(self) -> bool {
1294        self.line_t.is_nan() || self.segment_t.is_nan()
1295    }
1296}
1297
1298// Return polynomial coefficients given cubic bezier coordinates.
1299fn quadratic_bez_coefs(x0: f64, x1: f64, x2: f64) -> (f64, f64, f64) {
1300    let p0 = x0;
1301    let p1 = 2.0 * x1 - 2.0 * x0;
1302    let p2 = x2 - 2.0 * x1 + x0;
1303    (p0, p1, p2)
1304}
1305
1306// Return polynomial coefficients given cubic bezier coordinates.
1307fn cubic_bez_coefs(x0: f64, x1: f64, x2: f64, x3: f64) -> (f64, f64, f64, f64) {
1308    let p0 = x0;
1309    let p1 = 3.0 * x1 - 3.0 * x0;
1310    let p2 = 3.0 * x2 - 6.0 * x1 + 3.0 * x0;
1311    let p3 = x3 - 3.0 * x2 + 3.0 * x1 - x0;
1312    (p0, p1, p2, p3)
1313}
1314
1315impl From<CubicBez> for PathSeg {
1316    #[inline(always)]
1317    fn from(cubic_bez: CubicBez) -> PathSeg {
1318        PathSeg::Cubic(cubic_bez)
1319    }
1320}
1321
1322impl From<Line> for PathSeg {
1323    #[inline(always)]
1324    fn from(line: Line) -> PathSeg {
1325        PathSeg::Line(line)
1326    }
1327}
1328
1329impl From<QuadBez> for PathSeg {
1330    #[inline(always)]
1331    fn from(quad_bez: QuadBez) -> PathSeg {
1332        PathSeg::Quad(quad_bez)
1333    }
1334}
1335
1336impl Shape for BezPath {
1337    type PathElementsIter<'iter> = core::iter::Copied<core::slice::Iter<'iter, PathEl>>;
1338
1339    fn path_elements(&self, _tolerance: f64) -> Self::PathElementsIter<'_> {
1340        self.0.iter().copied()
1341    }
1342
1343    fn to_path(&self, _tolerance: f64) -> BezPath {
1344        self.clone()
1345    }
1346
1347    #[inline(always)]
1348    fn into_path(self, _tolerance: f64) -> BezPath {
1349        self
1350    }
1351
1352    /// Signed area.
1353    fn area(&self) -> f64 {
1354        self.elements().area()
1355    }
1356
1357    fn perimeter(&self, accuracy: f64) -> f64 {
1358        self.elements().perimeter(accuracy)
1359    }
1360
1361    /// Winding number of point.
1362    fn winding(&self, pt: Point) -> i32 {
1363        self.elements().winding(pt)
1364    }
1365
1366    fn bounding_box(&self) -> Rect {
1367        self.elements().bounding_box()
1368    }
1369
1370    #[inline(always)]
1371    fn as_path_slice(&self) -> Option<&[PathEl]> {
1372        Some(&self.0)
1373    }
1374}
1375
1376impl PathEl {
1377    /// Is this path element finite?
1378    #[inline]
1379    pub fn is_finite(&self) -> bool {
1380        match self {
1381            PathEl::MoveTo(p) => p.is_finite(),
1382            PathEl::LineTo(p) => p.is_finite(),
1383            PathEl::QuadTo(p, p2) => p.is_finite() && p2.is_finite(),
1384            PathEl::CurveTo(p, p2, p3) => p.is_finite() && p2.is_finite() && p3.is_finite(),
1385            PathEl::ClosePath => true,
1386        }
1387    }
1388
1389    /// Is this path element NaN?
1390    #[inline]
1391    pub fn is_nan(&self) -> bool {
1392        match self {
1393            PathEl::MoveTo(p) => p.is_nan(),
1394            PathEl::LineTo(p) => p.is_nan(),
1395            PathEl::QuadTo(p, p2) => p.is_nan() || p2.is_nan(),
1396            PathEl::CurveTo(p, p2, p3) => p.is_nan() || p2.is_nan() || p3.is_nan(),
1397            PathEl::ClosePath => false,
1398        }
1399    }
1400
1401    /// Get the end point of the path element, if it exists.
1402    pub fn end_point(&self) -> Option<Point> {
1403        match self {
1404            PathEl::MoveTo(p) => Some(*p),
1405            PathEl::LineTo(p1) => Some(*p1),
1406            PathEl::QuadTo(_, p2) => Some(*p2),
1407            PathEl::CurveTo(_, _, p3) => Some(*p3),
1408            PathEl::ClosePath => None,
1409        }
1410    }
1411}
1412
1413/// Implements [`Shape`] for a slice of [`PathEl`], provided that the first element of the slice is
1414/// not a `PathEl::ClosePath`. If it is, several of these functions will panic.
1415///
1416/// If the slice starts with `LineTo`, `QuadTo`, or `CurveTo`, it will be treated as a `MoveTo`.
1417impl<'a> Shape for &'a [PathEl] {
1418    type PathElementsIter<'iter>
1419        = core::iter::Copied<core::slice::Iter<'a, PathEl>>
1420    where
1421        'a: 'iter;
1422
1423    #[inline]
1424    fn path_elements(&self, _tolerance: f64) -> Self::PathElementsIter<'_> {
1425        self.iter().copied()
1426    }
1427
1428    fn to_path(&self, _tolerance: f64) -> BezPath {
1429        BezPath::from_vec(self.to_vec())
1430    }
1431
1432    /// Signed area.
1433    fn area(&self) -> f64 {
1434        segments(self.iter().copied()).area()
1435    }
1436
1437    fn perimeter(&self, accuracy: f64) -> f64 {
1438        segments(self.iter().copied()).perimeter(accuracy)
1439    }
1440
1441    /// Winding number of point.
1442    fn winding(&self, pt: Point) -> i32 {
1443        segments(self.iter().copied()).winding(pt)
1444    }
1445
1446    fn bounding_box(&self) -> Rect {
1447        segments(self.iter().copied()).bounding_box()
1448    }
1449
1450    #[inline(always)]
1451    fn as_path_slice(&self) -> Option<&[PathEl]> {
1452        Some(self)
1453    }
1454}
1455
1456/// Implements [`Shape`] for an array of [`PathEl`], provided that the first element of the array is
1457/// not a `PathEl::ClosePath`. If it is, several of these functions will panic.
1458///
1459/// If the array starts with `LineTo`, `QuadTo`, or `CurveTo`, it will be treated as a `MoveTo`.
1460impl<const N: usize> Shape for [PathEl; N] {
1461    type PathElementsIter<'iter> = core::iter::Copied<core::slice::Iter<'iter, PathEl>>;
1462
1463    #[inline]
1464    fn path_elements(&self, _tolerance: f64) -> Self::PathElementsIter<'_> {
1465        self.iter().copied()
1466    }
1467
1468    fn to_path(&self, _tolerance: f64) -> BezPath {
1469        BezPath::from_vec(self.to_vec())
1470    }
1471
1472    /// Signed area.
1473    fn area(&self) -> f64 {
1474        segments(self.iter().copied()).area()
1475    }
1476
1477    fn perimeter(&self, accuracy: f64) -> f64 {
1478        segments(self.iter().copied()).perimeter(accuracy)
1479    }
1480
1481    /// Winding number of point.
1482    fn winding(&self, pt: Point) -> i32 {
1483        segments(self.iter().copied()).winding(pt)
1484    }
1485
1486    fn bounding_box(&self) -> Rect {
1487        segments(self.iter().copied()).bounding_box()
1488    }
1489
1490    #[inline(always)]
1491    fn as_path_slice(&self) -> Option<&[PathEl]> {
1492        Some(self)
1493    }
1494}
1495
1496/// An iterator for path segments.
1497pub struct PathSegIter {
1498    seg: PathSeg,
1499    ix: usize,
1500}
1501
1502impl Shape for PathSeg {
1503    type PathElementsIter<'iter> = PathSegIter;
1504
1505    #[inline(always)]
1506    fn path_elements(&self, _tolerance: f64) -> PathSegIter {
1507        PathSegIter { seg: *self, ix: 0 }
1508    }
1509
1510    /// The area under the curve.
1511    ///
1512    /// We could just return `0`, but this seems more useful.
1513    fn area(&self) -> f64 {
1514        self.signed_area()
1515    }
1516
1517    #[inline]
1518    fn perimeter(&self, accuracy: f64) -> f64 {
1519        self.arclen(accuracy)
1520    }
1521
1522    #[inline(always)]
1523    fn winding(&self, _pt: Point) -> i32 {
1524        0
1525    }
1526
1527    #[inline]
1528    fn bounding_box(&self) -> Rect {
1529        ParamCurveExtrema::bounding_box(self)
1530    }
1531
1532    fn as_line(&self) -> Option<Line> {
1533        if let PathSeg::Line(line) = self {
1534            Some(*line)
1535        } else {
1536            None
1537        }
1538    }
1539}
1540
1541impl Iterator for PathSegIter {
1542    type Item = PathEl;
1543
1544    fn next(&mut self) -> Option<PathEl> {
1545        self.ix += 1;
1546        match (self.ix, self.seg) {
1547            // yes I could do some fancy bindings thing here but... :shrug:
1548            (1, PathSeg::Line(seg)) => Some(PathEl::MoveTo(seg.p0)),
1549            (1, PathSeg::Quad(seg)) => Some(PathEl::MoveTo(seg.p0)),
1550            (1, PathSeg::Cubic(seg)) => Some(PathEl::MoveTo(seg.p0)),
1551            (2, PathSeg::Line(seg)) => Some(PathEl::LineTo(seg.p1)),
1552            (2, PathSeg::Quad(seg)) => Some(PathEl::QuadTo(seg.p1, seg.p2)),
1553            (2, PathSeg::Cubic(seg)) => Some(PathEl::CurveTo(seg.p1, seg.p2, seg.p3)),
1554            _ => None,
1555        }
1556    }
1557}
1558
1559#[cfg(test)]
1560mod tests {
1561    use crate::{Circle, DEFAULT_ACCURACY};
1562
1563    use super::*;
1564
1565    fn assert_approx_eq(x: f64, y: f64) {
1566        assert!((x - y).abs() < 1e-8, "{x} != {y}");
1567    }
1568
1569    #[test]
1570    #[should_panic(expected = "uninitialized subpath")]
1571    fn test_elements_to_segments_starts_on_closepath() {
1572        let mut path = BezPath::new();
1573        path.close_path();
1574        path.segments().next();
1575    }
1576
1577    #[test]
1578    fn test_elements_to_segments_closepath_refers_to_last_moveto() {
1579        let mut path = BezPath::new();
1580        path.move_to((5.0, 5.0));
1581        path.line_to((15.0, 15.0));
1582        path.move_to((10.0, 10.0));
1583        path.line_to((15.0, 15.0));
1584        path.close_path();
1585        assert_eq!(
1586            path.segments().collect::<Vec<_>>().last(),
1587            Some(&Line::new((15.0, 15.0), (10.0, 10.0)).into()),
1588        );
1589    }
1590
1591    #[test]
1592    #[should_panic(expected = "uninitialized subpath")]
1593    fn test_must_not_start_on_quad() {
1594        let mut path = BezPath::new();
1595        path.quad_to((5.0, 5.0), (10.0, 10.0));
1596        path.line_to((15.0, 15.0));
1597        path.close_path();
1598    }
1599
1600    #[test]
1601    fn test_intersect_line() {
1602        let h_line = Line::new((0.0, 0.0), (100.0, 0.0));
1603        let v_line = Line::new((10.0, -10.0), (10.0, 10.0));
1604        let intersection = PathSeg::Line(h_line).intersect_line(v_line)[0];
1605        assert_approx_eq(intersection.segment_t, 0.1);
1606        assert_approx_eq(intersection.line_t, 0.5);
1607
1608        let v_line = Line::new((-10.0, -10.0), (-10.0, 10.0));
1609        assert!(PathSeg::Line(h_line).intersect_line(v_line).is_empty());
1610
1611        let v_line = Line::new((10.0, 10.0), (10.0, 20.0));
1612        assert!(PathSeg::Line(h_line).intersect_line(v_line).is_empty());
1613    }
1614
1615    #[test]
1616    fn test_intersect_qad() {
1617        let q = QuadBez::new((0.0, -10.0), (10.0, 20.0), (20.0, -10.0));
1618        let v_line = Line::new((10.0, -10.0), (10.0, 10.0));
1619        assert_eq!(PathSeg::Quad(q).intersect_line(v_line).len(), 1);
1620        let intersection = PathSeg::Quad(q).intersect_line(v_line)[0];
1621        assert_approx_eq(intersection.segment_t, 0.5);
1622        assert_approx_eq(intersection.line_t, 0.75);
1623
1624        let h_line = Line::new((0.0, 0.0), (100.0, 0.0));
1625        assert_eq!(PathSeg::Quad(q).intersect_line(h_line).len(), 2);
1626    }
1627
1628    #[test]
1629    fn test_intersect_cubic() {
1630        let c = CubicBez::new((0.0, -10.0), (10.0, 20.0), (20.0, -20.0), (30.0, 10.0));
1631        let v_line = Line::new((10.0, -10.0), (10.0, 10.0));
1632        assert_eq!(PathSeg::Cubic(c).intersect_line(v_line).len(), 1);
1633        let intersection = PathSeg::Cubic(c).intersect_line(v_line)[0];
1634        assert_approx_eq(intersection.segment_t, 0.333333333);
1635        assert_approx_eq(intersection.line_t, 0.592592592);
1636
1637        let h_line = Line::new((0.0, 0.0), (100.0, 0.0));
1638        assert_eq!(PathSeg::Cubic(c).intersect_line(h_line).len(), 3);
1639    }
1640
1641    #[test]
1642    fn test_contains() {
1643        let mut path = BezPath::new();
1644        path.move_to((0.0, 0.0));
1645        path.line_to((1.0, 1.0));
1646        path.line_to((2.0, 0.0));
1647        path.close_path();
1648        assert_eq!(path.winding(Point::new(1.0, 0.5)), -1);
1649        assert!(path.contains(Point::new(1.0, 0.5)));
1650    }
1651
1652    // get_seg(i) should produce the same results as path_segments().nth(i - 1).
1653    #[test]
1654    fn test_get_seg() {
1655        let circle = Circle::new((10.0, 10.0), 2.0).to_path(DEFAULT_ACCURACY);
1656        let segments = circle.path_segments(DEFAULT_ACCURACY).collect::<Vec<_>>();
1657        let get_segs = (1..usize::MAX)
1658            .map_while(|i| circle.get_seg(i))
1659            .collect::<Vec<_>>();
1660        assert_eq!(segments, get_segs);
1661    }
1662
1663    #[test]
1664    fn test_control_box() {
1665        // a sort of map ping looking thing drawn with a single cubic
1666        // cbox is wildly different than tight box
1667        let path = BezPath::from_svg("M200,300 C50,50 350,50 200,300").unwrap();
1668        assert_eq!(Rect::new(50.0, 50.0, 350.0, 300.0), path.control_box());
1669        assert!(path.control_box().area() > path.bounding_box().area());
1670    }
1671
1672    #[test]
1673    fn test_reverse_unclosed() {
1674        let path = BezPath::from_svg("M10,10 Q40,40 60,10 L100,10 C125,10 150,50 125,60").unwrap();
1675        let reversed = path.reverse_subpaths();
1676        assert_eq!(
1677            "M125,60 C150,50 125,10 100,10 L60,10 Q40,40 10,10",
1678            reversed.to_svg()
1679        );
1680    }
1681
1682    #[test]
1683    fn test_reverse_closed_triangle() {
1684        let path = BezPath::from_svg("M100,100 L150,200 L50,200 Z").unwrap();
1685        let reversed = path.reverse_subpaths();
1686        assert_eq!("M50,200 L150,200 L100,100 Z", reversed.to_svg());
1687    }
1688
1689    #[test]
1690    fn test_reverse_closed_shape() {
1691        let path = BezPath::from_svg(
1692            "M125,100 Q200,150 175,300 C150,150 50,150 25,300 Q0,150 75,100 L100,50 Z",
1693        )
1694        .unwrap();
1695        let reversed = path.reverse_subpaths();
1696        assert_eq!(
1697            "M100,50 L75,100 Q0,150 25,300 C50,150 150,150 175,300 Q200,150 125,100 Z",
1698            reversed.to_svg()
1699        );
1700    }
1701
1702    #[test]
1703    fn test_reverse_multiple_subpaths() {
1704        let svg = "M10,10 Q40,40 60,10 L100,10 C125,10 150,50 125,60 M100,100 L150,200 L50,200 Z M125,100 Q200,150 175,300 C150,150 50,150 25,300 Q0,150 75,100 L100,50 Z";
1705        let expected_svg = "M125,60 C150,50 125,10 100,10 L60,10 Q40,40 10,10 M50,200 L150,200 L100,100 Z M100,50 L75,100 Q0,150 25,300 C50,150 150,150 175,300 Q200,150 125,100 Z";
1706        let path = BezPath::from_svg(svg).unwrap();
1707        let reversed = path.reverse_subpaths();
1708        assert_eq!(expected_svg, reversed.to_svg());
1709    }
1710
1711    // https://github.com/fonttools/fonttools/blob/bf265ce49e0cae6f032420a4c80c31d8e16285b8/Tests/pens/reverseContourPen_test.py#L7
1712    #[test]
1713    fn test_reverse_lines() {
1714        let mut path = BezPath::new();
1715        path.move_to((0.0, 0.0));
1716        path.line_to((1.0, 1.0));
1717        path.line_to((2.0, 2.0));
1718        path.line_to((3.0, 3.0));
1719        path.close_path();
1720        let rev = path.reverse_subpaths();
1721        assert_eq!("M3,3 L2,2 L1,1 L0,0 Z", rev.to_svg());
1722    }
1723
1724    #[test]
1725    fn test_reverse_multiple_moves() {
1726        reverse_test_helper(
1727            vec![
1728                PathEl::MoveTo((2.0, 2.0).into()),
1729                PathEl::MoveTo((3.0, 3.0).into()),
1730                PathEl::ClosePath,
1731                PathEl::MoveTo((4.0, 4.0).into()),
1732            ],
1733            vec![
1734                PathEl::MoveTo((2.0, 2.0).into()),
1735                PathEl::MoveTo((3.0, 3.0).into()),
1736                PathEl::ClosePath,
1737                PathEl::MoveTo((4.0, 4.0).into()),
1738            ],
1739        );
1740    }
1741
1742    // The following are direct port of fonttools'
1743    // reverseContourPen_test.py::test_reverse_pen, adapted to rust, excluding
1744    // test cases that don't apply because we don't implement
1745    // outputImpliedClosingLine=False.
1746    // https://github.com/fonttools/fonttools/blob/85c80be/Tests/pens/reverseContourPen_test.py#L6-L467
1747
1748    #[test]
1749    fn test_reverse_closed_last_line_not_on_move() {
1750        reverse_test_helper(
1751            vec![
1752                PathEl::MoveTo((0.0, 0.0).into()),
1753                PathEl::LineTo((1.0, 1.0).into()),
1754                PathEl::LineTo((2.0, 2.0).into()),
1755                PathEl::LineTo((3.0, 3.0).into()),
1756                PathEl::ClosePath,
1757            ],
1758            vec![
1759                PathEl::MoveTo((3.0, 3.0).into()),
1760                PathEl::LineTo((2.0, 2.0).into()),
1761                PathEl::LineTo((1.0, 1.0).into()),
1762                PathEl::LineTo((0.0, 0.0).into()), // closing line NOT implied
1763                PathEl::ClosePath,
1764            ],
1765        );
1766    }
1767
1768    #[test]
1769    fn test_reverse_closed_last_line_overlaps_move() {
1770        reverse_test_helper(
1771            vec![
1772                PathEl::MoveTo((0.0, 0.0).into()),
1773                PathEl::LineTo((1.0, 1.0).into()),
1774                PathEl::LineTo((2.0, 2.0).into()),
1775                PathEl::LineTo((0.0, 0.0).into()),
1776                PathEl::ClosePath,
1777            ],
1778            vec![
1779                PathEl::MoveTo((0.0, 0.0).into()),
1780                PathEl::LineTo((2.0, 2.0).into()),
1781                PathEl::LineTo((1.0, 1.0).into()),
1782                PathEl::LineTo((0.0, 0.0).into()), // closing line NOT implied
1783                PathEl::ClosePath,
1784            ],
1785        );
1786    }
1787
1788    #[test]
1789    fn test_reverse_closed_duplicate_line_following_move() {
1790        reverse_test_helper(
1791            vec![
1792                PathEl::MoveTo((0.0, 0.0).into()),
1793                PathEl::LineTo((0.0, 0.0).into()),
1794                PathEl::LineTo((1.0, 1.0).into()),
1795                PathEl::LineTo((2.0, 2.0).into()),
1796                PathEl::ClosePath,
1797            ],
1798            vec![
1799                PathEl::MoveTo((2.0, 2.0).into()),
1800                PathEl::LineTo((1.0, 1.0).into()),
1801                PathEl::LineTo((0.0, 0.0).into()), // duplicate line retained
1802                PathEl::LineTo((0.0, 0.0).into()),
1803                PathEl::ClosePath,
1804            ],
1805        );
1806    }
1807
1808    #[test]
1809    fn test_reverse_closed_two_lines() {
1810        reverse_test_helper(
1811            vec![
1812                PathEl::MoveTo((0.0, 0.0).into()),
1813                PathEl::LineTo((1.0, 1.0).into()),
1814                PathEl::ClosePath,
1815            ],
1816            vec![
1817                PathEl::MoveTo((1.0, 1.0).into()),
1818                PathEl::LineTo((0.0, 0.0).into()), // closing line NOT implied
1819                PathEl::ClosePath,
1820            ],
1821        );
1822    }
1823
1824    #[test]
1825    fn test_reverse_closed_last_curve_overlaps_move() {
1826        reverse_test_helper(
1827            vec![
1828                PathEl::MoveTo((0.0, 0.0).into()),
1829                PathEl::CurveTo((1.0, 1.0).into(), (2.0, 2.0).into(), (3.0, 3.0).into()),
1830                PathEl::CurveTo((4.0, 4.0).into(), (5.0, 5.0).into(), (0.0, 0.0).into()),
1831                PathEl::ClosePath,
1832            ],
1833            vec![
1834                PathEl::MoveTo((0.0, 0.0).into()), // no extra lineTo added here
1835                PathEl::CurveTo((5.0, 5.0).into(), (4.0, 4.0).into(), (3.0, 3.0).into()),
1836                PathEl::CurveTo((2.0, 2.0).into(), (1.0, 1.0).into(), (0.0, 0.0).into()),
1837                PathEl::ClosePath,
1838            ],
1839        );
1840    }
1841
1842    #[test]
1843    fn test_reverse_closed_last_curve_not_on_move() {
1844        reverse_test_helper(
1845            vec![
1846                PathEl::MoveTo((0.0, 0.0).into()),
1847                PathEl::CurveTo((1.0, 1.0).into(), (2.0, 2.0).into(), (3.0, 3.0).into()),
1848                PathEl::CurveTo((4.0, 4.0).into(), (5.0, 5.0).into(), (6.0, 6.0).into()),
1849                PathEl::ClosePath,
1850            ],
1851            vec![
1852                PathEl::MoveTo((6.0, 6.0).into()), // the previously implied line
1853                PathEl::CurveTo((5.0, 5.0).into(), (4.0, 4.0).into(), (3.0, 3.0).into()),
1854                PathEl::CurveTo((2.0, 2.0).into(), (1.0, 1.0).into(), (0.0, 0.0).into()),
1855                PathEl::ClosePath,
1856            ],
1857        );
1858    }
1859
1860    #[test]
1861    fn test_reverse_closed_line_curve_line() {
1862        reverse_test_helper(
1863            vec![
1864                PathEl::MoveTo((0.0, 0.0).into()),
1865                PathEl::LineTo((1.0, 1.0).into()), // this line...
1866                PathEl::CurveTo((2.0, 2.0).into(), (3.0, 3.0).into(), (4.0, 4.0).into()),
1867                PathEl::CurveTo((5.0, 5.0).into(), (6.0, 6.0).into(), (7.0, 7.0).into()),
1868                PathEl::ClosePath,
1869            ],
1870            vec![
1871                PathEl::MoveTo((7.0, 7.0).into()),
1872                PathEl::CurveTo((6.0, 6.0).into(), (5.0, 5.0).into(), (4.0, 4.0).into()),
1873                PathEl::CurveTo((3.0, 3.0).into(), (2.0, 2.0).into(), (1.0, 1.0).into()),
1874                PathEl::LineTo((0.0, 0.0).into()), // ... does NOT become implied
1875                PathEl::ClosePath,
1876            ],
1877        );
1878    }
1879
1880    #[test]
1881    fn test_reverse_closed_last_quad_overlaps_move() {
1882        reverse_test_helper(
1883            vec![
1884                PathEl::MoveTo((0.0, 0.0).into()),
1885                PathEl::QuadTo((1.0, 1.0).into(), (2.0, 2.0).into()),
1886                PathEl::QuadTo((3.0, 3.0).into(), (0.0, 0.0).into()),
1887                PathEl::ClosePath,
1888            ],
1889            vec![
1890                PathEl::MoveTo((0.0, 0.0).into()), // no extra lineTo added here
1891                PathEl::QuadTo((3.0, 3.0).into(), (2.0, 2.0).into()),
1892                PathEl::QuadTo((1.0, 1.0).into(), (0.0, 0.0).into()),
1893                PathEl::ClosePath,
1894            ],
1895        );
1896    }
1897
1898    #[test]
1899    fn test_reverse_closed_last_quad_not_on_move() {
1900        reverse_test_helper(
1901            vec![
1902                PathEl::MoveTo((0.0, 0.0).into()),
1903                PathEl::QuadTo((1.0, 1.0).into(), (2.0, 2.0).into()),
1904                PathEl::QuadTo((3.0, 3.0).into(), (4.0, 4.0).into()),
1905                PathEl::ClosePath,
1906            ],
1907            vec![
1908                PathEl::MoveTo((4.0, 4.0).into()), // the previously implied line
1909                PathEl::QuadTo((3.0, 3.0).into(), (2.0, 2.0).into()),
1910                PathEl::QuadTo((1.0, 1.0).into(), (0.0, 0.0).into()),
1911                PathEl::ClosePath,
1912            ],
1913        );
1914    }
1915
1916    #[test]
1917    fn test_reverse_closed_line_quad_line() {
1918        reverse_test_helper(
1919            vec![
1920                PathEl::MoveTo((0.0, 0.0).into()),
1921                PathEl::LineTo((1.0, 1.0).into()), // this line...
1922                PathEl::QuadTo((2.0, 2.0).into(), (3.0, 3.0).into()),
1923                PathEl::ClosePath,
1924            ],
1925            vec![
1926                PathEl::MoveTo((3.0, 3.0).into()),
1927                PathEl::QuadTo((2.0, 2.0).into(), (1.0, 1.0).into()),
1928                PathEl::LineTo((0.0, 0.0).into()), // ... does NOT become implied
1929                PathEl::ClosePath,
1930            ],
1931        );
1932    }
1933
1934    #[test]
1935    fn test_reverse_empty() {
1936        reverse_test_helper(vec![], vec![]);
1937    }
1938
1939    #[test]
1940    fn test_reverse_single_point() {
1941        reverse_test_helper(
1942            vec![PathEl::MoveTo((0.0, 0.0).into())],
1943            vec![PathEl::MoveTo((0.0, 0.0).into())],
1944        );
1945    }
1946
1947    #[test]
1948    fn test_reverse_single_point_closed() {
1949        reverse_test_helper(
1950            vec![PathEl::MoveTo((0.0, 0.0).into()), PathEl::ClosePath],
1951            vec![PathEl::MoveTo((0.0, 0.0).into()), PathEl::ClosePath],
1952        );
1953    }
1954
1955    #[test]
1956    fn test_reverse_single_line_open() {
1957        reverse_test_helper(
1958            vec![
1959                PathEl::MoveTo((0.0, 0.0).into()),
1960                PathEl::LineTo((1.0, 1.0).into()),
1961            ],
1962            vec![
1963                PathEl::MoveTo((1.0, 1.0).into()),
1964                PathEl::LineTo((0.0, 0.0).into()),
1965            ],
1966        );
1967    }
1968
1969    #[test]
1970    fn test_reverse_single_curve_open() {
1971        reverse_test_helper(
1972            vec![
1973                PathEl::MoveTo((0.0, 0.0).into()),
1974                PathEl::CurveTo((1.0, 1.0).into(), (2.0, 2.0).into(), (3.0, 3.0).into()),
1975            ],
1976            vec![
1977                PathEl::MoveTo((3.0, 3.0).into()),
1978                PathEl::CurveTo((2.0, 2.0).into(), (1.0, 1.0).into(), (0.0, 0.0).into()),
1979            ],
1980        );
1981    }
1982
1983    #[test]
1984    fn test_reverse_curve_line_open() {
1985        reverse_test_helper(
1986            vec![
1987                PathEl::MoveTo((0.0, 0.0).into()),
1988                PathEl::CurveTo((1.0, 1.0).into(), (2.0, 2.0).into(), (3.0, 3.0).into()),
1989                PathEl::LineTo((4.0, 4.0).into()),
1990            ],
1991            vec![
1992                PathEl::MoveTo((4.0, 4.0).into()),
1993                PathEl::LineTo((3.0, 3.0).into()),
1994                PathEl::CurveTo((2.0, 2.0).into(), (1.0, 1.0).into(), (0.0, 0.0).into()),
1995            ],
1996        );
1997    }
1998
1999    #[test]
2000    fn test_reverse_line_curve_open() {
2001        reverse_test_helper(
2002            vec![
2003                PathEl::MoveTo((0.0, 0.0).into()),
2004                PathEl::LineTo((1.0, 1.0).into()),
2005                PathEl::CurveTo((2.0, 2.0).into(), (3.0, 3.0).into(), (4.0, 4.0).into()),
2006            ],
2007            vec![
2008                PathEl::MoveTo((4.0, 4.0).into()),
2009                PathEl::CurveTo((3.0, 3.0).into(), (2.0, 2.0).into(), (1.0, 1.0).into()),
2010                PathEl::LineTo((0.0, 0.0).into()),
2011            ],
2012        );
2013    }
2014
2015    #[test]
2016    fn test_reverse_duplicate_point_after_move() {
2017        // Test case from: https://github.com/googlei18n/cu2qu/issues/51#issue-179370514
2018        // Simplified to only use atomic PathEl::QuadTo (no QuadSplines).
2019        reverse_test_helper(
2020            vec![
2021                PathEl::MoveTo((848.0, 348.0).into()),
2022                PathEl::LineTo((848.0, 348.0).into()),
2023                PathEl::QuadTo((848.0, 526.0).into(), (449.0, 704.0).into()),
2024                PathEl::QuadTo((848.0, 171.0).into(), (848.0, 348.0).into()),
2025                PathEl::ClosePath,
2026            ],
2027            vec![
2028                PathEl::MoveTo((848.0, 348.0).into()),
2029                PathEl::QuadTo((848.0, 171.0).into(), (449.0, 704.0).into()),
2030                PathEl::QuadTo((848.0, 526.0).into(), (848.0, 348.0).into()),
2031                PathEl::LineTo((848.0, 348.0).into()),
2032                PathEl::ClosePath,
2033            ],
2034        );
2035    }
2036
2037    #[test]
2038    fn test_reverse_duplicate_point_at_end() {
2039        // Test case from: https://github.com/googlefonts/fontmake/issues/572
2040        reverse_test_helper(
2041            vec![
2042                PathEl::MoveTo((0.0, 651.0).into()),
2043                PathEl::LineTo((0.0, 101.0).into()),
2044                PathEl::LineTo((0.0, 101.0).into()),
2045                PathEl::LineTo((0.0, 651.0).into()),
2046                PathEl::LineTo((0.0, 651.0).into()),
2047                PathEl::ClosePath,
2048            ],
2049            vec![
2050                PathEl::MoveTo((0.0, 651.0).into()),
2051                PathEl::LineTo((0.0, 651.0).into()),
2052                PathEl::LineTo((0.0, 101.0).into()),
2053                PathEl::LineTo((0.0, 101.0).into()),
2054                PathEl::LineTo((0.0, 651.0).into()),
2055                PathEl::ClosePath,
2056            ],
2057        );
2058    }
2059
2060    fn reverse_test_helper(contour: Vec<PathEl>, expected: Vec<PathEl>) {
2061        assert_eq!(BezPath(contour).reverse_subpaths().0, expected);
2062    }
2063
2064    #[test]
2065    fn test_rect_segments() {
2066        // Ensure that `from_path_segments` does not insert spurious MoveTos in
2067        // the middle of a path.
2068        let x0 = 25.189500810000002;
2069        let x1 = 568.18950081;
2070        let y0 = -105.0;
2071        let y1 = 176.0;
2072        let r = Rect::from_points((x0, y0), (x1, y1));
2073
2074        let path0 = r.into_path(0.0);
2075        assert!(path0
2076            .elements()
2077            .iter()
2078            .skip(1)
2079            .all(|el| !matches!(el, PathEl::MoveTo(_))));
2080
2081        let path1 = BezPath::from_path_segments(path0.segments());
2082        assert!(path1
2083            .elements()
2084            .iter()
2085            .skip(1)
2086            .all(|el| !matches!(el, PathEl::MoveTo(_))));
2087    }
2088
2089    #[test]
2090    fn test_current_position() {
2091        let mut path = BezPath::new();
2092        assert_eq!(path.current_position(), None);
2093        path.move_to((0., 0.));
2094        assert_eq!(path.current_position(), Some(Point::new(0., 0.)));
2095        path.line_to((10., 10.));
2096        assert_eq!(path.current_position(), Some(Point::new(10., 10.)));
2097        path.line_to((10., 0.));
2098        assert_eq!(path.current_position(), Some(Point::new(10., 0.)));
2099        path.close_path();
2100        assert_eq!(path.current_position(), Some(Point::new(0., 0.)));
2101
2102        path.close_path();
2103        assert_eq!(path.current_position(), None);
2104
2105        path.move_to((0., 10.));
2106        assert_eq!(path.current_position(), Some(Point::new(0., 10.)));
2107        path.close_path();
2108        assert_eq!(path.current_position(), Some(Point::new(0., 10.)));
2109        path.close_path();
2110        assert_eq!(path.current_position(), None);
2111    }
2112}