Skip to main content

lyon_algorithms/
measure.rs

1//! Perform cached measurements and split operations on a path.
2//!
3use crate::geom::{CubicBezierSegment, LineSegment, QuadraticBezierSegment, Segment};
4use crate::math::*;
5use crate::path::{
6    builder::PathBuilder, AttributeStore, Attributes, EndpointId, IdEvent, Path, PathSlice,
7    PositionStore,
8};
9use core::ops::Range;
10
11use alloc::vec::Vec;
12
13/// Whether to measure real or normalized (between 0 and 1) distances.
14#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
15pub enum SampleType {
16    Distance,
17    Normalized,
18}
19
20type EndpointPair = (EndpointId, EndpointId);
21
22enum SegmentWrapper {
23    Empty,
24    Line(LineSegment<f32>, EndpointPair),
25    Quadratic(QuadraticBezierSegment<f32>, EndpointPair),
26    Cubic(CubicBezierSegment<f32>, EndpointPair),
27}
28
29impl SegmentWrapper {
30    fn split(&self, range: Range<f32>) -> Self {
31        match self {
32            Self::Empty => Self::Empty,
33            Self::Line(segment, pair) => Self::Line(segment.split_range(range), *pair),
34            Self::Quadratic(segment, pair) => Self::Quadratic(segment.split_range(range), *pair),
35            Self::Cubic(segment, pair) => Self::Cubic(segment.split_range(range), *pair),
36        }
37    }
38}
39
40struct Edge {
41    // distance from the beginning of the path
42    distance: f32,
43    // which segment this edge is on
44    index: usize,
45    // t-value of the endpoint on the segment
46    t: f32,
47}
48
49/// The result of sampling a path.
50#[derive(PartialEq, Debug)]
51pub struct PathSample<'l> {
52    position: Point,
53    tangent: Vector,
54    attributes: Attributes<'l>,
55}
56
57impl<'l> PathSample<'l> {
58    #[inline]
59    pub fn position(&self) -> Point {
60        self.position
61    }
62
63    #[inline]
64    pub fn tangent(&self) -> Vector {
65        self.tangent
66    }
67
68    // Takes &mut self to allow interpolating attributes lazily (like the stroke tessellator) without changing
69    // the API.
70    #[inline]
71    pub fn attributes(&mut self) -> Attributes<'l> {
72        self.attributes
73    }
74}
75
76/// An acceleration structure for sampling distances along a specific path.
77///
78/// Building the path measurements can be an expensive operation depending on the complexity of the
79/// measured path, so it is usually a good idea to cache and reuse it whenever possible.
80///
81/// Queries on path measurements are made via a sampler object (see `PathSampler`) which can be configured
82/// to measure real distance or normalized ones (values between 0 and 1 with zero indicating the start
83/// of the path and 1 indicating the end).
84///
85/// ## Differences with the `PathWalker`
86///
87/// The `walker` module provides a similar functionality via the `PathWalker`. The main differences are:
88///  - The path walker does all of its computation on the fly without storing any information for later use.
89///  - `PathMeasurements` stores potentially large amounts of data to speed up sample queries.
90///  - The cost of creating `PathMeasurements` is similar to that of walking the entire path once.
91///  - Once the `PathMeasurements` have been created, random samples on the path are much faster than path walking.
92///  - The PathWalker does not handle normalized distances since the length of the path cannot be known without
93///    traversing the entire path at least once.
94///
95/// Prefer `PathMeasurements` over `PathWalker` if the measurements can be cached and reused for a large number of
96/// queries.
97///
98/// ## Example
99///
100/// ```
101/// use lyon_algorithms::{
102///     math::point,
103///     path::Path,
104///     length::approximate_length,
105///     measure::{PathMeasurements, SampleType},
106/// };
107///
108/// let mut path = Path::builder();
109/// path.begin(point(0.0, 0.0));
110/// path.quadratic_bezier_to(point(1.0, 1.0), point(2.0, 0.0));
111/// path.end(false);
112/// let path = path.build();
113///
114/// // Build the acceleration structure.
115/// let measurements = PathMeasurements::from_path(&path, 1e-3);
116/// let mut sampler = measurements.create_sampler(&path, SampleType::Normalized);
117///
118/// let sample  = sampler.sample(0.5);
119/// println!("Mid-point position: {:?}, tangent: {:?}", sample.position(), sample.tangent());
120///
121/// let mut second_half = Path::builder();
122/// sampler.split_range(0.5..1.0, &mut second_half);
123/// let second_half = second_half.build();
124/// assert!((sampler.length() / 2.0 - approximate_length(&second_half, 1e-3)).abs() < 1e-3);
125/// ```
126///
127pub struct PathMeasurements {
128    events: Vec<IdEvent>,
129    edges: Vec<Edge>,
130}
131
132impl PathMeasurements {
133    /// Create empty path measurements.
134    ///
135    /// The measurements cannot be used until it has been initialized.
136    pub fn empty() -> Self {
137        PathMeasurements {
138            events: Vec::new(),
139            edges: Vec::new(),
140        }
141    }
142
143    /// Create path measurements initialized with a `Path`.
144    pub fn from_path(path: &Path, tolerance: f32) -> Self {
145        let mut m = Self::empty();
146        m.initialize(path.id_iter(), path, tolerance);
147
148        m
149    }
150
151    /// Create path measurements initialized with a `PathSlice`.
152    pub fn from_path_slice(path: &PathSlice, tolerance: f32) -> Self {
153        let mut m = Self::empty();
154        m.initialize(path.id_iter(), path, tolerance);
155
156        m
157    }
158
159    /// Create path measurements initialized with a generic iterator and position store.
160    pub fn from_iter<Iter, PS>(path: Iter, positions: &PS, tolerance: f32) -> Self
161    where
162        Iter: IntoIterator<Item = IdEvent>,
163        PS: PositionStore,
164    {
165        let mut m = Self::empty();
166        m.initialize(path, positions, tolerance);
167
168        m
169    }
170
171    /// Initialize the path measurements with a path.
172    pub fn initialize<Iter, PS>(&mut self, path: Iter, position_store: &PS, tolerance: f32)
173    where
174        Iter: IntoIterator<Item = IdEvent>,
175        PS: PositionStore,
176    {
177        let tolerance = tolerance.max(1e-4);
178        let mut events = core::mem::take(&mut self.events);
179        events.clear();
180        events.extend(path);
181        let mut edges = core::mem::take(&mut self.edges);
182        edges.clear();
183
184        let mut distance = 0.0;
185        for (index, event) in events.iter().cloned().enumerate() {
186            match event {
187                IdEvent::Begin { .. } => {
188                    edges.push(Edge {
189                        distance,
190                        index,
191                        t: 1.0,
192                    });
193                }
194                IdEvent::Line { from, to } => {
195                    let from = position_store.get_endpoint(from);
196                    let to = position_store.get_endpoint(to);
197                    distance += (from - to).length();
198                    edges.push(Edge {
199                        distance,
200                        index,
201                        t: 1.0,
202                    })
203                }
204                IdEvent::Quadratic { from, ctrl, to } => {
205                    let from = position_store.get_endpoint(from);
206                    let to = position_store.get_endpoint(to);
207                    let ctrl = position_store.get_control_point(ctrl);
208                    let segment = QuadraticBezierSegment { from, ctrl, to };
209                    segment.for_each_flattened_with_t(tolerance, &mut |line, t| {
210                        distance += line.length();
211                        edges.push(Edge {
212                            distance,
213                            index,
214                            t: t.end,
215                        });
216                    });
217                }
218                IdEvent::Cubic {
219                    from,
220                    ctrl1,
221                    ctrl2,
222                    to,
223                } => {
224                    let from = position_store.get_endpoint(from);
225                    let to = position_store.get_endpoint(to);
226                    let ctrl1 = position_store.get_control_point(ctrl1);
227                    let ctrl2 = position_store.get_control_point(ctrl2);
228                    let segment = CubicBezierSegment {
229                        from,
230                        ctrl1,
231                        ctrl2,
232                        to,
233                    };
234                    segment.for_each_flattened_with_t(tolerance, &mut |line, t| {
235                        distance += line.length();
236                        edges.push(Edge {
237                            distance,
238                            index,
239                            t: t.end,
240                        });
241                    });
242                }
243                IdEvent::End {
244                    last,
245                    first,
246                    close: true,
247                } => {
248                    let last = position_store.get_endpoint(last);
249                    let first = position_store.get_endpoint(first);
250                    distance += (last - first).length();
251                    edges.push(Edge {
252                        distance,
253                        index,
254                        t: 1.0,
255                    })
256                }
257                _ => {}
258            }
259        }
260
261        if !edges.is_empty() {
262            debug_assert_eq!(edges.first().unwrap().distance, 0.0);
263        }
264
265        self.events = events;
266        self.edges = edges;
267    }
268
269    /// Initialize the path measurements with a path.
270    pub fn initialize_with_path(&mut self, path: &Path, tolerance: f32) {
271        self.initialize_with_path_slice(path.as_slice(), tolerance)
272    }
273
274    /// Initialize the path measurements with a path.
275    pub fn initialize_with_path_slice(&mut self, path: PathSlice, tolerance: f32) {
276        self.initialize(path.id_iter(), &path, tolerance)
277    }
278
279    /// Returns the approximate length of the path.
280    pub fn length(&self) -> f32 {
281        if self.edges.is_empty() {
282            0.0
283        } else {
284            self.edges.last().unwrap().distance
285        }
286    }
287
288    /// Create an object that can perform fast sample queries on a path using the cached measurements.
289    ///
290    /// The returned sampler does not compute interpolated attributes.
291    pub fn create_sampler<'l, PS: PositionStore>(
292        &'l self,
293        positions: &'l PS,
294        ty: SampleType,
295    ) -> PathSampler<'l, PS, ()> {
296        let attr: &'static () = &();
297        PathSampler::new(self, positions, attr, ty)
298    }
299
300    /// Create an object that can perform fast sample queries on a path using the cached measurements.
301    ///
302    /// The returned sampler computes interpolated attributes.
303    pub fn create_sampler_with_attributes<'l, PS, AS>(
304        &'l self,
305        positions: &'l PS,
306        attributes: &'l AS,
307        ty: SampleType,
308    ) -> PathSampler<'l, PS, AS>
309    where
310        PS: PositionStore,
311        AS: AttributeStore,
312    {
313        PathSampler::new(self, positions, attributes, ty)
314    }
315}
316
317/// Performs fast sample queries on a path with cached measurements.
318///
319/// This object contains the mutable state necessary for speeding up the queries, this allows the
320/// path measurements to be immutable and accessible concurrently from multiple threads if needed.
321///
322/// Reusing a sampler over multiple queries saves a memory allocation if there are custom attributes,
323/// And speeds up queries if they are sequentially ordered along the path.
324pub struct PathSampler<'l, PS, AS> {
325    events: &'l [IdEvent],
326    edges: &'l [Edge],
327    positions: &'l PS,
328    attributes: &'l AS,
329    attribute_buffer: Vec<f32>,
330    cursor: usize,
331    sample_type: SampleType,
332}
333
334impl<'l, PS: PositionStore, AS: AttributeStore> PathSampler<'l, PS, AS> {
335    /// Create a sampler.
336    ///
337    /// The provided positions must be the ones used when initializing the path measurements.
338    pub fn new(
339        measurements: &'l PathMeasurements,
340        positions: &'l PS,
341        attributes: &'l AS,
342        sample_type: SampleType,
343    ) -> Self {
344        PathSampler {
345            events: &measurements.events,
346            edges: &measurements.edges,
347            positions,
348            attributes,
349            attribute_buffer: alloc::vec![0.0; attributes.num_attributes()],
350            cursor: 0,
351            sample_type,
352        }
353    }
354
355    /// Sample at a given distance along the path.
356    ///
357    /// If the path is empty, the produced sample will contain NaNs.
358    pub fn sample(&mut self, dist: f32) -> PathSample {
359        self.sample_impl(dist, self.sample_type)
360    }
361
362    /// Construct a path for a specific sub-range of the measured path.
363    ///
364    /// The path measurements must have been initialized with the same path.
365    /// The distance is clamped to the beginning and end of the path.
366    /// Panics if the path is empty.
367    pub fn split_range(&mut self, mut range: Range<f32>, output: &mut dyn PathBuilder) {
368        let length = self.length();
369        if self.sample_type == SampleType::Normalized {
370            range.start *= length;
371            range.end *= length;
372        }
373        range.start = range.start.max(0.0);
374        range.end = range.end.max(range.start);
375        range.start = range.start.min(length);
376        range.end = range.end.min(length);
377
378        if range.is_empty() {
379            return;
380        }
381
382        let result = self.sample_impl(range.start, SampleType::Distance);
383        output.begin(result.position, result.attributes);
384        let (ptr1, seg1) = (self.cursor, self.edges[self.cursor].index);
385        self.move_cursor(range.end);
386        let (ptr2, seg2) = (self.cursor, self.edges[self.cursor].index);
387
388        let mut is_in_subpath = true;
389        if seg1 == seg2 {
390            self.cursor = ptr1;
391            let t_begin = self.t(range.start);
392            self.cursor = ptr2;
393            let t_end = self.t(range.end);
394            self.add_segment(seg1, Some(t_begin..t_end), output, &mut is_in_subpath);
395        } else {
396            self.cursor = ptr1;
397            self.add_segment(
398                seg1,
399                Some(self.t(range.start)..1.0),
400                output,
401                &mut is_in_subpath,
402            );
403            for seg in (seg1 + 1)..seg2 {
404                self.add_segment(seg, None, output, &mut is_in_subpath);
405            }
406            self.cursor = ptr2;
407            self.add_segment(
408                seg2,
409                Some(0.0..self.t(range.end)),
410                output,
411                &mut is_in_subpath,
412            );
413        }
414
415        output.end(false);
416    }
417
418    /// Returns the approximate length of the path.
419    pub fn length(&self) -> f32 {
420        if self.edges.is_empty() {
421            0.0
422        } else {
423            self.edges.last().unwrap().distance
424        }
425    }
426
427    fn to_segment(&self, event: IdEvent) -> SegmentWrapper {
428        match event {
429            IdEvent::Line { from, to } => SegmentWrapper::Line(
430                LineSegment {
431                    from: self.positions.get_endpoint(from),
432                    to: self.positions.get_endpoint(to),
433                },
434                (from, to),
435            ),
436            IdEvent::Quadratic { from, ctrl, to } => SegmentWrapper::Quadratic(
437                QuadraticBezierSegment {
438                    from: self.positions.get_endpoint(from),
439                    to: self.positions.get_endpoint(to),
440                    ctrl: self.positions.get_control_point(ctrl),
441                },
442                (from, to),
443            ),
444            IdEvent::Cubic {
445                from,
446                ctrl1,
447                ctrl2,
448                to,
449            } => SegmentWrapper::Cubic(
450                CubicBezierSegment {
451                    from: self.positions.get_endpoint(from),
452                    to: self.positions.get_endpoint(to),
453                    ctrl1: self.positions.get_control_point(ctrl1),
454                    ctrl2: self.positions.get_control_point(ctrl2),
455                },
456                (from, to),
457            ),
458            IdEvent::End {
459                last,
460                first,
461                close: true,
462            } => SegmentWrapper::Line(
463                LineSegment {
464                    from: self.positions.get_endpoint(last),
465                    to: self.positions.get_endpoint(first),
466                },
467                (last, first),
468            ),
469            _ => SegmentWrapper::Empty,
470        }
471    }
472
473    fn in_bounds(&self, dist: f32) -> bool {
474        self.cursor != 0
475            && self.edges[self.cursor - 1].distance <= dist
476            && dist <= self.edges[self.cursor].distance
477    }
478
479    /// Move the pointer so the given point is on the current segment.
480    fn move_cursor(&mut self, dist: f32) {
481        if dist == 0.0 {
482            self.cursor = 1;
483            return;
484        }
485        if self.in_bounds(dist) {
486            // No need to move
487            return;
488        }
489
490        // Performs on [first, last)
491        // ...TTFFF...
492        //      ^
493        //      sample this point
494        fn partition_point(first: usize, last: usize, pred: impl Fn(usize) -> bool) -> usize {
495            let mut l = first;
496            let mut r = last;
497            while l < r {
498                let mid = (l + r) / 2;
499                if pred(mid) {
500                    l = mid + 1;
501                } else {
502                    r = mid;
503                }
504            }
505            debug_assert_eq!(l, r);
506            debug_assert_ne!(l, last);
507            l
508        }
509
510        fn floor_log2(num: usize) -> u32 {
511            core::mem::size_of::<usize>() as u32 * 8 - num.leading_zeros() - 1
512        }
513
514        // Here we use a heuristic method combining method 1 & 2
515        // Method 1:        Move step by step until we found the corresponding segment, works well on short paths and near queries
516        // Time complexity: (expected) (dist - start).abs() / len * num
517        // Method 2.        Binary search on lengths, works well on long paths and random calls
518        // Time complexity: (exact) floor(log2(num))
519        // where `len` and `num` are given as follow
520        //
521        // According to the benchmark, this method works well in both cases and has low overhead in relative to Method 1 & 2.
522        // Benchmark code: https://gist.github.com/Mivik/5f67ae5a72eae3884b2f386370554966
523        let start = self.edges[self.cursor].distance;
524        if start < dist {
525            let (len, num) = (self.length() - start, self.edges.len() - self.cursor - 1);
526            debug_assert_ne!(num, 0);
527            if (dist - start) / len * (num as f32) < floor_log2(num) as f32 {
528                loop {
529                    self.cursor += 1;
530                    if dist <= self.edges[self.cursor].distance {
531                        break;
532                    }
533                }
534            } else {
535                self.cursor = partition_point(self.cursor + 1, self.edges.len(), |p| {
536                    self.edges[p].distance < dist
537                });
538            }
539        } else {
540            let (len, num) = (start, self.cursor + 1);
541            debug_assert_ne!(num, 0);
542            if (start - dist) / len * (num as f32) < floor_log2(num) as f32 {
543                loop {
544                    self.cursor -= 1;
545                    if self.cursor == 0 || self.edges[self.cursor - 1].distance < dist {
546                        break;
547                    }
548                }
549            } else {
550                self.cursor = partition_point(0, self.cursor, |p| self.edges[p].distance < dist);
551            }
552        }
553
554        debug_assert!(self.in_bounds(dist));
555    }
556
557    /// Interpolate the custom attributes.
558    fn interpolate_attributes(&mut self, from: EndpointId, to: EndpointId, t: f32) {
559        let from = self.attributes.get(from);
560        let to = self.attributes.get(to);
561        for i in 0..self.attribute_buffer.len() {
562            self.attribute_buffer[i] = from[i] * (1.0 - t) + to[i] * t;
563        }
564    }
565
566    /// Returns the relative position (0 ~ 1) of the given point on the current segment.
567    fn t(&self, dist: f32) -> f32 {
568        debug_assert!(self.in_bounds(dist));
569        let prev = &self.edges[self.cursor - 1];
570        let cur = &self.edges[self.cursor];
571
572        let length = cur.distance - prev.distance;
573        if length == 0.0 {
574            return 0.0;
575        }
576
577        let t_begin = if prev.index == cur.index { prev.t } else { 0.0 };
578        let t_end = cur.t;
579        t_begin + (t_end - t_begin) * ((dist - prev.distance) / length)
580    }
581
582    fn sample_impl(&mut self, mut dist: f32, sample_type: SampleType) -> PathSample {
583        let length = self.length();
584        if length == 0.0 {
585            return self.sample_zero_length();
586        }
587        if sample_type == SampleType::Normalized {
588            dist *= length;
589        }
590        dist = dist.max(0.0);
591        dist = dist.min(length);
592
593        self.move_cursor(dist);
594        let t = self.t(dist);
595        macro_rules! dispatched_call {
596            ([$v:expr] ($seg:ident, $pair:ident) => $code:block) => {
597                #[allow(unused_parens)]
598                match $v {
599                    SegmentWrapper::Line($seg, $pair) => $code,
600                    SegmentWrapper::Quadratic($seg, $pair) => $code,
601                    SegmentWrapper::Cubic($seg, $pair) => $code,
602                    _ => {}
603                }
604            };
605        }
606
607        dispatched_call!([self.to_segment(self.events[self.edges[self.cursor].index])] (segment, pair) => {
608            self.interpolate_attributes(pair.0, pair.1, t);
609            return PathSample {
610                position: segment.sample(t),
611                tangent: segment.derivative(t).normalize(),
612                attributes: &self.attribute_buffer,
613            }
614        });
615
616        unreachable!();
617    }
618
619    #[cold]
620    fn sample_zero_length(&mut self) -> PathSample {
621        if let Some(IdEvent::Begin { at }) = self.events.first() {
622            return PathSample {
623                position: self.positions.get_endpoint(*at),
624                tangent: vector(0.0, 0.0),
625                attributes: self.attributes.get(*at),
626            };
627        }
628
629        for value in &mut self.attribute_buffer {
630            *value = f32::NAN;
631        }
632
633        PathSample {
634            position: point(f32::NAN, f32::NAN),
635            tangent: vector(f32::NAN, f32::NAN),
636            attributes: &self.attribute_buffer,
637        }
638    }
639
640    /// Caller needs to hold a parameter to keep track of whether we're in a subpath or not, as this would be determined
641    /// by prior segments. This function will update `is_in_subpath` based on the segment it adds.
642    fn add_segment(
643        &mut self,
644        ptr: usize,
645        range: Option<Range<f32>>,
646        dest: &mut dyn PathBuilder,
647        is_in_subpath: &mut bool,
648    ) {
649        let segment = self.to_segment(self.events[ptr]);
650        let segment = match range.clone() {
651            Some(range) => segment.split(range),
652            None => segment,
653        };
654        macro_rules! obtain_attrs {
655            ($p:ident, $index:tt) => {
656                match range.clone() {
657                    Some(range) => {
658                        if range.end == 1.0 {
659                            self.attributes.get($p.$index)
660                        } else {
661                            self.interpolate_attributes($p.0, $p.1, range.end);
662                            &mut self.attribute_buffer
663                        }
664                    }
665                    None => self.attributes.get($p.$index),
666                }
667            };
668        }
669
670        match segment {
671            SegmentWrapper::Line(LineSegment { from, to }, pair) => {
672                if !*is_in_subpath {
673                    dest.end(false);
674                    dest.begin(from, obtain_attrs!(pair, 0));
675                }
676                dest.line_to(to, obtain_attrs!(pair, 1));
677            }
678            SegmentWrapper::Quadratic(QuadraticBezierSegment { from, ctrl, to }, pair) => {
679                if !*is_in_subpath {
680                    dest.end(false);
681                    dest.begin(from, obtain_attrs!(pair, 0));
682                }
683                dest.quadratic_bezier_to(ctrl, to, obtain_attrs!(pair, 1));
684            }
685            SegmentWrapper::Cubic(
686                CubicBezierSegment {
687                    from,
688                    ctrl1,
689                    ctrl2,
690                    to,
691                },
692                pair,
693            ) => {
694                if !*is_in_subpath {
695                    dest.end(false);
696                    dest.begin(from, obtain_attrs!(pair, 0));
697                }
698                dest.cubic_bezier_to(ctrl1, ctrl2, to, obtain_attrs!(pair, 1));
699            }
700            _ => {}
701        }
702
703        *is_in_subpath = !matches!(
704            self.events[ptr],
705            IdEvent::End { .. } | IdEvent::Begin { .. }
706        );
707    }
708}
709
710#[cfg(test)]
711use path::geom::euclid::approxeq::ApproxEq;
712
713#[cfg(test)]
714fn slice(a: &[f32]) -> &[f32] {
715    a
716}
717
718#[test]
719fn measure_line() {
720    let mut path = Path::builder();
721    path.begin(point(1.0, 1.0));
722    path.line_to(point(0.0, 0.0));
723    path.end(false);
724    let path = path.build();
725    let measure = PathMeasurements::from_path(&path, 0.01);
726    let mut sampler = measure.create_sampler(&path, SampleType::Normalized);
727    for t in [0.0, 0.2, 0.3, 0.5, 1.0] {
728        let result = sampler.sample(t);
729        assert!((result.position - point(1.0 - t, 1.0 - t)).length() < 1e-5);
730        assert_eq!(result.tangent, vector(-1.0, -1.0).normalize());
731    }
732}
733
734#[test]
735fn measure_square() {
736    let mut path = Path::builder();
737    path.begin(point(0.0, 0.0));
738    path.line_to(point(1.0, 0.0));
739    path.line_to(point(1.0, 1.0));
740    path.line_to(point(0.0, 1.0));
741    path.close();
742    let path = path.build();
743    let measure = PathMeasurements::from_path(&path, 0.01);
744    let mut sampler = measure.create_sampler(&path, SampleType::Normalized);
745    for (t, position, tangent) in [
746        (0.125, point(0.5, 0.0), vector(1.0, 0.0)),
747        (0.375, point(1.0, 0.5), vector(0.0, 1.0)),
748        (0.625, point(0.5, 1.0), vector(-1.0, 0.0)),
749        (0.875, point(0.0, 0.5), vector(0.0, -1.0)),
750    ] {
751        let result = sampler.sample(t);
752        assert!((result.position - position).length() < 1e-5);
753        assert_eq!(result.tangent, tangent);
754    }
755}
756
757#[test]
758fn measure_attributes() {
759    let mut path = Path::builder_with_attributes(2);
760    path.begin(point(0.0, 0.0), &[1.0, 2.0]);
761    path.line_to(point(1.0, 0.0), &[2.0, 3.0]);
762    path.line_to(point(1.0, 1.0), &[0.0, 0.0]);
763    path.end(false);
764    let path = path.build();
765    let measure = PathMeasurements::from_path(&path, 0.01);
766    let mut sampler = measure.create_sampler_with_attributes(&path, &path, SampleType::Normalized);
767
768    for (t, position, attrs) in [
769        (0.25, point(0.5, 0.0), &[1.5, 2.5]),
770        (0.5, point(1.0, 0.0), &[2.0, 3.0]),
771        (0.75, point(1.0, 0.5), &[1.0, 1.5]),
772    ] {
773        let result = sampler.sample(t);
774        assert!((result.position - position).length() < 1e-5);
775        for i in 0..2 {
776            assert!((result.attributes[i] - attrs[i]).abs() < 1e-5);
777        }
778    }
779}
780
781#[test]
782fn measure_bezier_curve() {
783    let mut path = Path::builder();
784    path.begin(point(0.0, 0.0));
785    path.quadratic_bezier_to(point(0.5, 0.7), point(1.0, 0.0));
786    path.quadratic_bezier_to(point(1.5, -0.7), point(2.0, 0.0));
787    path.end(false);
788    let path = path.build();
789
790    let measure = PathMeasurements::from_path(&path, 0.01);
791    let mut sampler = measure.create_sampler(&path, SampleType::Normalized);
792
793    for t in [0.25, 0.75] {
794        let result = sampler.sample(t);
795        assert!(result.tangent.approx_eq(&vector(1.0, 0.0)));
796    }
797    for (t, position) in [
798        (0.0, point(0.0, 0.0)),
799        (0.5, point(1.0, 0.0)),
800        (1.0, point(2.0, 0.0)),
801    ] {
802        let result = sampler.sample(t);
803        assert!(result.position.approx_eq(&position));
804    }
805}
806
807#[test]
808fn split_square() {
809    use crate::path::Event;
810
811    let mut path = Path::builder();
812    path.begin(point(0.0, 0.0));
813    path.line_to(point(1.0, 0.0));
814    path.line_to(point(1.0, 1.0));
815    path.line_to(point(0.0, 1.0));
816    path.close();
817    let path = path.build();
818    let measure = PathMeasurements::from_path(&path, 0.01);
819    let mut sampler = measure.create_sampler(&path, SampleType::Normalized);
820    let mut path2 = Path::builder();
821    sampler.split_range(0.125..0.625, &mut path2);
822    let path2 = path2.build();
823    assert_eq!(
824        path2.iter().collect::<Vec<_>>(),
825        alloc::vec![
826            Event::Begin {
827                at: point(0.5, 0.0)
828            },
829            Event::Line {
830                from: point(0.5, 0.0),
831                to: point(1.0, 0.0)
832            },
833            Event::Line {
834                from: point(1.0, 0.0),
835                to: point(1.0, 1.0)
836            },
837            Event::Line {
838                from: point(1.0, 1.0),
839                to: point(0.5, 1.0)
840            },
841            Event::End {
842                last: point(0.5, 1.0),
843                first: point(0.5, 0.0),
844                close: false
845            },
846        ]
847    );
848}
849
850#[test]
851fn split_bezier_curve() {
852    use crate::path::Event;
853
854    let mut path = Path::builder();
855    path.begin(point(0.0, 0.0));
856    path.quadratic_bezier_to(point(1.0, 1.0), point(2.0, 0.0));
857    path.end(false);
858    let path = path.build();
859    let measure = PathMeasurements::from_path(&path, 0.01);
860    let mut sampler = measure.create_sampler(&path, SampleType::Normalized);
861
862    let mut path2 = Path::builder();
863    sampler.split_range(0.5..1.0, &mut path2);
864    let path2 = path2.build();
865
866    assert_eq!(
867        path2.iter().collect::<Vec<_>>(),
868        alloc::vec![
869            Event::Begin {
870                at: point(1.0, 0.5)
871            },
872            Event::Quadratic {
873                from: point(1.0, 0.5),
874                ctrl: point(1.5, 0.5),
875                to: point(2.0, 0.0),
876            },
877            Event::End {
878                last: point(2.0, 0.0),
879                first: point(1.0, 0.5),
880                close: false
881            }
882        ]
883    );
884}
885
886#[test]
887fn split_attributes() {
888    use crate::path::Event;
889
890    let mut path = Path::builder_with_attributes(2);
891    path.begin(point(0.0, 0.0), &[1.0, 2.0]);
892    path.line_to(point(1.0, 0.0), &[2.0, 3.0]);
893    path.line_to(point(1.0, 1.0), &[0.0, 0.0]);
894    path.end(false);
895    let path = path.build();
896    let measure = PathMeasurements::from_path(&path, 0.01);
897    let mut sampler = measure.create_sampler_with_attributes(&path, &path, SampleType::Normalized);
898
899    let mut path2 = Path::builder_with_attributes(2);
900    sampler.split_range(0.0..1.0, &mut path2);
901    let path2 = path2.build();
902
903    assert_eq!(
904        path2.iter_with_attributes().collect::<Vec<_>>(),
905        path.iter_with_attributes().collect::<Vec<_>>()
906    );
907
908    let mut path3 = Path::builder_with_attributes(2);
909    sampler.split_range(0.25..0.75, &mut path3);
910    let path3 = path3.build();
911
912    assert_eq!(
913        path3.iter_with_attributes().collect::<Vec<_>>(),
914        alloc::vec![
915            Event::Begin {
916                at: (point(0.5, 0.0), slice(&[1.5, 2.5]))
917            },
918            Event::Line {
919                from: (point(0.5, 0.0), slice(&[1.5, 2.5])),
920                to: (point(1.0, 0.0), slice(&[2.0, 3.0])),
921            },
922            Event::Line {
923                from: (point(1.0, 0.0), slice(&[2.0, 3.0])),
924                to: (point(1.0, 0.5), slice(&[1.0, 1.5])),
925            },
926            Event::End {
927                last: (point(1.0, 0.5), slice(&[1.0, 1.5])),
928                first: (point(0.5, 0.0), slice(&[1.5, 2.5])),
929                close: false
930            }
931        ]
932    );
933}
934
935#[test]
936fn zero_length() {
937    fn expect_nans(sample: PathSample, num_attribs: usize) {
938        assert!(sample.position.x.is_nan());
939        assert!(sample.position.y.is_nan());
940        assert!(sample.tangent.x.is_nan());
941        assert!(sample.tangent.y.is_nan());
942        for attr in sample.attributes {
943            assert!(attr.is_nan());
944        }
945        assert_eq!(sample.attributes.len(), num_attribs);
946    }
947
948    let mut path = Path::builder_with_attributes(2);
949    path.begin(point(1.0, 2.0), &[3.0, 4.0]);
950    path.end(false);
951    let path = path.build();
952    let measure = PathMeasurements::from_path(&path, 0.01);
953    let mut sampler = measure.create_sampler_with_attributes(&path, &path, SampleType::Normalized);
954    let expected = PathSample {
955        position: point(1.0, 2.0),
956        tangent: vector(0.0, 0.0),
957        attributes: &[3.0, 4.0],
958    };
959    assert_eq!(sampler.sample(0.0), expected);
960    assert_eq!(sampler.sample(0.5), expected);
961    assert_eq!(sampler.sample(1.0), expected);
962
963    let mut path = Path::builder_with_attributes(2);
964    path.begin(point(1.0, 2.0), &[3.0, 4.0]);
965    path.end(false);
966    let path = path.build();
967    let measure = PathMeasurements::from_path(&path, 0.01);
968    let mut sampler = measure.create_sampler_with_attributes(&path, &path, SampleType::Distance);
969    let expected = PathSample {
970        position: point(1.0, 2.0),
971        tangent: vector(0.0, 0.0),
972        attributes: &[3.0, 4.0],
973    };
974    assert_eq!(sampler.sample(0.0), expected);
975    assert_eq!(sampler.sample(0.5), expected);
976    assert_eq!(sampler.sample(1.0), expected);
977
978    let path = Path::builder_with_attributes(2).build();
979    let measure = PathMeasurements::from_path(&path, 0.01);
980    let mut sampler = measure.create_sampler_with_attributes(&path, &path, SampleType::Normalized);
981    expect_nans(sampler.sample(0.0), 2);
982    expect_nans(sampler.sample(0.5), 2);
983    expect_nans(sampler.sample(1.0), 2);
984
985    let path = Path::builder_with_attributes(2).build();
986    let measure = PathMeasurements::from_path(&path, 0.01);
987    let mut sampler = measure.create_sampler_with_attributes(&path, &path, SampleType::Distance);
988    expect_nans(sampler.sample(0.0), 2);
989    expect_nans(sampler.sample(0.5), 2);
990    expect_nans(sampler.sample(1.0), 2);
991}
992
993#[test]
994fn multiple_sub_paths() {
995    let mut path = Path::builder();
996
997    path.begin(point(0.0, 0.0));
998    path.line_to(point(10.0, 0.0));
999    path.end(false);
1000
1001    path.begin(point(10.0, 10.0));
1002    path.line_to(point(20.0, 10.0));
1003    path.end(false);
1004
1005    let path = path.build();
1006    let measure = PathMeasurements::from_path(&path, 0.01);
1007    let mut sampler = measure.create_sampler(&path, SampleType::Normalized);
1008
1009    let mut dashes = Path::builder();
1010    sampler.split_range(0.0..0.25, &mut dashes);
1011    sampler.split_range(0.25..0.5, &mut dashes);
1012    // Avoid starting subpaths exactly on the join as we may begin with a zero-length subpath
1013    sampler.split_range(0.6..0.75, &mut dashes);
1014    sampler.split_range(0.75..1.0, &mut dashes);
1015    let dashes = dashes.build();
1016
1017    let mut iter = dashes.iter();
1018
1019    use crate::path::geom::euclid::approxeq::ApproxEq;
1020    fn expect_begin(event: Option<path::PathEvent>, pos: Point) {
1021        std::eprintln!("- {:?}", event);
1022        if let Some(path::PathEvent::Begin { at }) = event {
1023            assert!(at.approx_eq(&pos), "Expected Begin {:?}, got {:?}", pos, at);
1024        } else {
1025            panic!("Expected begin, got {:?}", event);
1026        }
1027    }
1028
1029    fn expect_end(event: Option<path::PathEvent>, pos: Point) {
1030        std::eprintln!("- {:?}", event);
1031        if let Some(path::PathEvent::End { last, .. }) = event {
1032            assert!(
1033                last.approx_eq(&pos),
1034                "Expected End {:?}, got {:?}",
1035                pos,
1036                last
1037            );
1038        } else {
1039            panic!("Expected end, got {:?}", event);
1040        }
1041    }
1042    fn expect_line(event: Option<path::PathEvent>, expect_from: Point, expect_to: Point) {
1043        std::eprintln!("- {:?}", event);
1044        if let Some(path::PathEvent::Line { from, to }) = event {
1045            assert!(
1046                from.approx_eq(&expect_from),
1047                "Expected line {:?} {:?}, got {:?} {:?}",
1048                expect_from,
1049                expect_to,
1050                from,
1051                to
1052            );
1053            assert!(
1054                to.approx_eq(&expect_to),
1055                "Expected line {:?} {:?}, got {:?} {:?}",
1056                expect_from,
1057                expect_to,
1058                from,
1059                to
1060            );
1061        } else {
1062            panic!(
1063                "Expected a line {:?} {:?}, got {:?}",
1064                expect_from, expect_to, event
1065            );
1066        }
1067    }
1068
1069    expect_begin(iter.next(), point(0.0, 0.0));
1070    expect_line(iter.next(), point(0.0, 0.0), point(5.0, 0.0));
1071    expect_end(iter.next(), point(5.0, 0.0));
1072
1073    expect_begin(iter.next(), point(5.0, 0.0));
1074    expect_line(iter.next(), point(5.0, 0.0), point(10.0, 0.0));
1075    expect_end(iter.next(), point(10.0, 0.0));
1076
1077    expect_begin(iter.next(), point(12.0, 10.0));
1078    expect_line(iter.next(), point(12.0, 10.0), point(15.0, 10.0));
1079    expect_end(iter.next(), point(15.0, 10.0));
1080
1081    expect_begin(iter.next(), point(15.0, 10.0));
1082    expect_line(iter.next(), point(15.0, 10.0), point(20.0, 10.0));
1083    expect_end(iter.next(), point(20.0, 10.0));
1084}
1085
1086#[test]
1087fn zero_length_when_first_2_points_are_same() {
1088    use crate::path::Event;
1089
1090    let mut path = Path::builder();
1091
1092    path.begin(point(0.0, 0.0));
1093    path.line_to(point(0.0, 0.0));
1094    path.line_to(point(1.0, 0.0));
1095    path.end(false);
1096
1097    let path = path.build();
1098    let measure = PathMeasurements::from_path(&path, 0.01);
1099    let mut sampler = measure.create_sampler(&path, SampleType::Normalized);
1100
1101    let mut dashes = Path::builder();
1102
1103    sampler.split_range(0.0..1.0, &mut dashes);
1104    let dashes = dashes.build();
1105
1106    assert_eq!(
1107        dashes.iter_with_attributes().collect::<Vec<_>>(),
1108        alloc::vec![
1109            Event::Begin {
1110                at: (point(0.0, 0.0), slice(&[]))
1111            },
1112            Event::Line {
1113                from: (point(0.0, 0.0), slice(&[])),
1114                to: (point(0.0, 0.0), slice(&[])),
1115            },
1116            Event::Line {
1117                from: (point(0.0, 0.0), slice(&[])),
1118                to: (point(1.0, 0.0), slice(&[])),
1119            },
1120            Event::End {
1121                last: (point(1.0, 0.0), slice(&[])),
1122                first: (point(0.0, 0.0), slice(&[])),
1123                close: false
1124            }
1125        ]
1126    );
1127}