surpass/
path.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// The path division algorithm is mostly based on Raph Levien's curvature approximation[1] for
6// quadratic Bézier division and Adrian Colomitchi's cubic-to-quadratic approximation[2] with a few
7// additions to improve robustness.
8//
9// The algorithm converts all possible types of curves into primitives (lines and quadratic
10// Béziers) sequentially, while these are pushed onto the `PathBuilder`. Afterwards, each `Path`
11// is converted into lines in parallel. This part of the algorithms tries its best not to create
12// new lines if two neighboring lines are close enough to forming a line together according to some
13// threshold.
14//
15// [1]: https://raphlinus.github.io/graphics/curves/2019/12/23/flatten-quadbez.html
16// [2]: http://www.caffeineowl.com/graphics/2d/vectorial/bezierintro.html
17
18use std::cell::RefCell;
19use std::f32;
20use std::rc::Rc;
21
22use rayon::prelude::*;
23
24use crate::extend::{ExtendTuple3, ExtendVec};
25use crate::lines::GeomId;
26use crate::transform::GeomPresTransform;
27use crate::{Point, PIXEL_WIDTH};
28
29// Pixel accuracy should be within 0.5 of a sub-pixel.
30pub const MAX_ERROR: f32 = 1.0 / PIXEL_WIDTH as f32;
31const MAX_ANGLE_ERROR: f32 = 0.001;
32const MIN_LEN: usize = 256;
33
34fn lerp(t: f32, a: f32, b: f32) -> f32 {
35    t.mul_add(b, (-t).mul_add(a, a))
36}
37
38fn curvature(x: f32) -> f32 {
39    const C: f32 = 0.67;
40    x / (1.0 - C + ((x * x).mul_add(0.25, C * C * C * C)).sqrt().sqrt())
41}
42
43fn inv_curvature(k: f32) -> f32 {
44    const C: f32 = 0.39;
45    k * (1.0 - C + ((k * k).mul_add(0.25, C * C)).sqrt())
46}
47
48#[derive(Clone, Copy, Debug)]
49pub struct WeightedPoint {
50    pub point: Point,
51    pub weight: f32,
52}
53
54impl WeightedPoint {
55    pub fn applied(self) -> Point {
56        let w_recip = self.weight.recip();
57
58        Point { x: self.point.x * w_recip, y: self.point.y * w_recip }
59    }
60}
61
62fn eval_cubic(t: f32, points: &[WeightedPoint; 4]) -> WeightedPoint {
63    let x = lerp(
64        t,
65        lerp(
66            t,
67            lerp(t, points[0].point.x, points[1].point.x),
68            lerp(t, points[1].point.x, points[2].point.x),
69        ),
70        lerp(
71            t,
72            lerp(t, points[1].point.x, points[2].point.x),
73            lerp(t, points[2].point.x, points[3].point.x),
74        ),
75    );
76    let y = lerp(
77        t,
78        lerp(
79            t,
80            lerp(t, points[0].point.y, points[1].point.y),
81            lerp(t, points[1].point.y, points[2].point.y),
82        ),
83        lerp(
84            t,
85            lerp(t, points[1].point.y, points[2].point.y),
86            lerp(t, points[2].point.y, points[3].point.y),
87        ),
88    );
89    let weight = lerp(
90        t,
91        lerp(
92            t,
93            lerp(t, points[0].weight, points[1].weight),
94            lerp(t, points[1].weight, points[2].weight),
95        ),
96        lerp(
97            t,
98            lerp(t, points[1].weight, points[2].weight),
99            lerp(t, points[2].weight, points[3].weight),
100        ),
101    );
102
103    WeightedPoint { point: Point { x, y }, weight }
104}
105
106#[derive(Debug, Default)]
107pub struct ScratchBuffers {
108    point_indices: Vec<usize>,
109    quad_indices: Vec<usize>,
110    point_commands: Vec<u32>,
111}
112
113impl ScratchBuffers {
114    pub fn clear(&mut self) {
115        self.point_indices.clear();
116        self.quad_indices.clear();
117        self.point_commands.clear();
118    }
119}
120
121#[derive(Clone, Copy, Debug)]
122enum PointCommand {
123    Start(usize),
124    Incr(f32),
125    End(usize, bool),
126}
127
128impl From<u32> for PointCommand {
129    fn from(val: u32) -> Self {
130        if val & 0x7F80_0000 == 0x7F80_0000 {
131            if val & 0x8000_0000 == 0 {
132                Self::Start((val & 0x3F_FFFF) as usize)
133            } else {
134                Self::End((val & 0x3F_FFFF) as usize, val & 0x40_0000 != 0)
135            }
136        } else {
137            Self::Incr(f32::from_bits(val))
138        }
139    }
140}
141
142impl From<PointCommand> for u32 {
143    fn from(command: PointCommand) -> Self {
144        match command {
145            PointCommand::Start(i) => 0x7F80_0000 | (i as u32 & 0x3F_FFFF),
146            PointCommand::Incr(point_command) => point_command.to_bits(),
147            PointCommand::End(i, new_contour) => {
148                0xFF80_0000 | (i as u32 & 0x3F_FFFF) | (u32::from(new_contour) << 22)
149            }
150        }
151    }
152}
153
154#[derive(Clone, Debug)]
155struct Contour;
156
157#[derive(Clone, Debug)]
158struct Spline {
159    curvature: f32,
160    p0: Point,
161    p2: Point,
162    contour: Option<Contour>,
163}
164
165impl Spline {
166    #[inline]
167    pub fn new_spline_needed(&mut self, angle_changed: bool, point: Point) -> Option<Contour> {
168        let needed = angle_changed || (point - self.p2).len() >= MAX_ERROR;
169
170        needed.then(|| self.contour.take()).flatten()
171    }
172}
173
174#[derive(Clone, Debug)]
175struct Primitives {
176    last_angle: Option<f32>,
177    contour: Option<Contour>,
178    splines: Vec<Spline>,
179    x: Vec<f32>,
180    y: Vec<f32>,
181    weight: Vec<f32>,
182    x0: Vec<f32>,
183    dx_recip: Vec<f32>,
184    k0: Vec<f32>,
185    dk: Vec<f32>,
186    curvatures_recip: Vec<f32>,
187    partial_curvatures: Vec<(u32, f32)>,
188}
189
190impl Primitives {
191    #[inline]
192    fn last_spline_or_insert_with<F>(
193        &mut self,
194        angle: Option<f32>,
195        point: Point,
196        f: F,
197    ) -> &mut Spline
198    where
199        F: FnOnce(Contour) -> Spline,
200    {
201        if let Some(contour) = self.contour.take().or_else(|| {
202            fn diff(a0: f32, a1: f32) -> f32 {
203                let mut diff = (a1 - a0).abs();
204
205                if diff > f32::consts::PI {
206                    diff -= f32::consts::PI;
207                }
208
209                if diff > f32::consts::FRAC_PI_2 {
210                    diff = f32::consts::PI - diff;
211                }
212
213                diff
214            }
215
216            let angle_changed = if let (Some(a0), Some(a1)) = (self.last_angle, angle) {
217                diff(a0, a1) > MAX_ANGLE_ERROR
218            } else {
219                false
220            };
221
222            self.splines
223                .last_mut()
224                .and_then(|spline| spline.new_spline_needed(angle_changed, point))
225        }) {
226            self.splines.push(f(contour));
227        }
228
229        self.splines.last_mut().unwrap()
230    }
231
232    pub fn push_contour(&mut self, contour: Contour) {
233        self.contour = Some(contour);
234    }
235
236    pub fn push_line(&mut self, points: [WeightedPoint; 2]) {
237        let p0 = points[0].applied();
238        let p1 = points[1].applied();
239
240        let d = p1 - p0;
241        let angle = d.angle();
242
243        let spline = self.last_spline_or_insert_with(angle, p0, |contour| Spline {
244            curvature: 0.0,
245            p0,
246            p2: p1,
247            contour: Some(contour),
248        });
249
250        spline.p2 = p1;
251
252        self.last_angle = angle;
253    }
254
255    pub fn push_quad(&mut self, points: [WeightedPoint; 3]) {
256        const PIXEL_ACCURACY_RECIP: f32 = 1.0 / MAX_ERROR;
257
258        let p0 = points[0].applied();
259        let p1 = points[1].applied();
260        let p2 = points[2].applied();
261
262        let a = p1 - p0;
263        let b = p2 - p1;
264
265        let in_angle = a.angle();
266        let out_angle = b.angle();
267
268        if in_angle.is_none() && out_angle.is_none() {
269            return;
270        }
271
272        if in_angle.is_none() || out_angle.is_none() {
273            return self.push_line([points[0], points[2]]);
274        }
275
276        self.x.extend(points.iter().map(|p| p.point.x));
277        self.y.extend(points.iter().map(|p| p.point.y));
278        self.weight.extend(points.iter().map(|p| p.weight));
279
280        let spline = self.last_spline_or_insert_with(in_angle, p0, |contour| Spline {
281            curvature: 0.0,
282            p0,
283            p2,
284            contour: Some(contour),
285        });
286
287        spline.p2 = p2;
288
289        let h = a - b;
290
291        let cross = (p2.x - p0.x).mul_add(h.y, -(p2.y - p0.y) * h.x);
292        let cross_recip = cross.recip();
293
294        let mut x0 = a.x.mul_add(h.x, a.y * h.y) * cross_recip;
295        let x2 = b.x.mul_add(h.x, b.y * h.y) * cross_recip;
296        let mut dx_recip = (x2 - x0).recip();
297
298        let scale = (cross / (h.len() * (x2 - x0))).abs();
299
300        let mut k0 = curvature(x0);
301        let k2 = curvature(x2);
302
303        let mut dk = k2 - k0;
304        let mut current_curvature = 0.5 * dk.abs() * (scale * PIXEL_ACCURACY_RECIP).sqrt();
305
306        // Points are collinear.
307        if !current_curvature.is_finite() || current_curvature <= 1.0 {
308            // These values are chosen such that the resulting points will be found at t = 0.5 and
309            // t = 1.0.
310            x0 = 0.036_624_67;
311            dx_recip = 1.0;
312            k0 = 0.0;
313            dk = 1.0;
314
315            current_curvature = 2.0;
316        }
317
318        let total_curvature = spline.curvature + current_curvature;
319
320        spline.curvature = total_curvature;
321
322        self.last_angle = out_angle;
323
324        self.x0.push(x0);
325        self.dx_recip.push(dx_recip);
326        self.k0.push(k0);
327        self.dk.push(dk);
328        self.curvatures_recip.push(current_curvature.recip());
329        self.partial_curvatures.push((self.splines.len() as u32 - 1, total_curvature));
330    }
331
332    pub fn push_cubic(&mut self, points: [WeightedPoint; 4]) {
333        const MAX_CUBIC_ERROR_SQUARED: f32 = (36.0 * 36.0 / 3.0) * MAX_ERROR * MAX_ERROR;
334
335        let p0 = points[0].applied();
336        let p1 = points[1].applied();
337        let p2 = points[2].applied();
338
339        let dx = p2.x.mul_add(3.0, -p0.x) - p1.x.mul_add(3.0, -p1.x);
340        let dy = p2.y.mul_add(3.0, -p0.y) - p1.y.mul_add(3.0, -p1.y);
341
342        let err = dx.mul_add(dx, dy * dy);
343
344        let mult = points[1].weight.max(points[2].weight).max(1.0);
345
346        let subdivisions = (((err * MAX_CUBIC_ERROR_SQUARED.recip()).powf(1.0 / 6.0) * mult).ceil()
347            as usize)
348            .max(1);
349        let incr = (subdivisions as f32).recip();
350
351        let mut quad_p0 = p0;
352        for i in 1..=subdivisions {
353            let t = i as f32 * incr;
354
355            let quad_p2 = eval_cubic(t, &points).applied();
356
357            let mid_point = eval_cubic(t - 0.5 * incr, &points).applied();
358
359            let quad_p1 = Point {
360                x: mid_point.x.mul_add(2.0, -0.5 * (quad_p0.x + quad_p2.x)),
361                y: mid_point.y.mul_add(2.0, -0.5 * (quad_p0.y + quad_p2.y)),
362            };
363
364            self.push_quad([
365                WeightedPoint { point: quad_p0, weight: 1.0 },
366                WeightedPoint { point: quad_p1, weight: 1.0 },
367                WeightedPoint { point: quad_p2, weight: 1.0 },
368            ]);
369
370            quad_p0 = quad_p2;
371        }
372    }
373
374    pub fn populate_buffers(&self, buffers: &mut ScratchBuffers) {
375        buffers.clear();
376
377        let mut i = 0;
378        let mut last_spline = None;
379        for (spline_i, spline) in self.splines.iter().enumerate() {
380            let subdivisions = spline.curvature.ceil() as usize;
381            let point_command = spline.curvature / subdivisions as f32;
382
383            let needs_start_point = last_spline.map_or(true, |last_spline: &Spline| {
384                last_spline.contour.is_some() || (last_spline.p2 - spline.p0).len() > MAX_ERROR
385            });
386
387            if needs_start_point {
388                buffers.point_indices.push(Default::default());
389                buffers.quad_indices.push(Default::default());
390                buffers.point_commands.push(PointCommand::Start(spline_i).into());
391            }
392
393            for pi in 1..subdivisions {
394                if pi as f32 > self.partial_curvatures[i].1 {
395                    i += 1;
396                }
397
398                buffers.point_indices.push(pi);
399                buffers.quad_indices.push(i);
400                buffers.point_commands.push(PointCommand::Incr(point_command).into());
401            }
402
403            buffers.point_indices.push(Default::default());
404            buffers.quad_indices.push(Default::default());
405            buffers
406                .point_commands
407                .push(PointCommand::End(spline_i, spline.contour.is_some()).into());
408
409            last_spline = Some(spline);
410
411            if subdivisions > 0 {
412                i += 1;
413            }
414        }
415    }
416
417    pub fn eval_quad(&self, quad_index: usize, t: f32) -> Point {
418        let i0 = 3 * quad_index;
419        let i1 = i0 + 1;
420        let i2 = i0 + 2;
421
422        let weight = lerp(
423            t,
424            lerp(t, self.weight[i0], self.weight[i1]),
425            lerp(t, self.weight[i1], self.weight[i2]),
426        );
427        let w_recip = weight.recip();
428
429        let x = lerp(t, lerp(t, self.x[i0], self.x[i1]), lerp(t, self.x[i1], self.x[i2])) * w_recip;
430        let y = lerp(t, lerp(t, self.y[i0], self.y[i1]), lerp(t, self.y[i1], self.y[i2])) * w_recip;
431
432        Point::new(x, y)
433    }
434
435    pub fn into_lines(self) -> Lines {
436        let mut lines = Lines::default();
437
438        thread_local!(static BUFFERS: RefCell<ScratchBuffers> = const { RefCell::new(ScratchBuffers {
439            point_indices: Vec::new(),
440            quad_indices: Vec::new(),
441            point_commands: Vec::new(),
442        }) });
443
444        BUFFERS.with(|buffers| {
445            let mut buffers = buffers.borrow_mut();
446
447            self.populate_buffers(&mut buffers);
448
449            let points = buffers
450                .point_indices
451                .par_iter()
452                .with_min_len(MIN_LEN)
453                .zip(buffers.quad_indices.par_iter().with_min_len(MIN_LEN))
454                .zip(buffers.point_commands.par_iter().with_min_len(MIN_LEN))
455                .map(|((&pi, &qi), &point_command)| {
456                    let incr = match PointCommand::from(point_command) {
457                        PointCommand::Start(spline_i) => {
458                            let point = self.splines[spline_i as usize].p0;
459                            return ((point.x, point.y), false);
460                        }
461                        PointCommand::End(spline_i, new_contour) => {
462                            let point = self.splines[spline_i as usize].p2;
463                            return ((point.x, point.y), new_contour);
464                        }
465                        PointCommand::Incr(incr) => incr,
466                    };
467
468                    let spline_i = self.partial_curvatures[qi].0;
469
470                    let previous_curvature = qi
471                        .checked_sub(1)
472                        .and_then(|i| {
473                            let partial_curvature = self.partial_curvatures[i];
474                            (partial_curvature.0 == spline_i).then_some(partial_curvature.1)
475                        })
476                        .unwrap_or_default();
477                    let ratio =
478                        incr.mul_add(pi as f32, -previous_curvature) * self.curvatures_recip[qi];
479
480                    let x = inv_curvature(ratio.mul_add(self.dk[qi], self.k0[qi]));
481
482                    let t = ((x - self.x0[qi]) * self.dx_recip[qi]).clamp(0.0, 1.0);
483
484                    let point = self.eval_quad(qi, t);
485
486                    ((point.x, point.y), false)
487                });
488
489            (
490                (ExtendVec::new(&mut lines.x), ExtendVec::new(&mut lines.y)),
491                ExtendVec::new(&mut lines.start_new_contour),
492            )
493                .par_extend(points);
494        });
495
496        lines
497    }
498}
499
500impl Default for Primitives {
501    fn default() -> Self {
502        Self {
503            last_angle: None,
504            contour: Some(Contour),
505            splines: Default::default(),
506            x: Default::default(),
507            y: Default::default(),
508            weight: Default::default(),
509            x0: Default::default(),
510            dx_recip: Default::default(),
511            k0: Default::default(),
512            dk: Default::default(),
513            curvatures_recip: Default::default(),
514            partial_curvatures: Default::default(),
515        }
516    }
517}
518
519#[derive(Clone, Copy, Debug, PartialEq)]
520enum PathCommand {
521    Move,
522    Line,
523    Quad,
524    Cubic,
525}
526#[derive(Debug, Default)]
527struct Lines {
528    x: Vec<f32>,
529    y: Vec<f32>,
530    start_new_contour: Vec<bool>,
531}
532
533#[derive(Debug)]
534struct PathData {
535    x: Vec<f32>,
536    y: Vec<f32>,
537    weight: Vec<f32>,
538    commands: Vec<PathCommand>,
539    open_point_index: usize,
540    lines: Option<Lines>,
541}
542
543macro_rules! points {
544    ( $inner:expr , $i:expr , $( $d:expr ),+ $( , )? ) => {[
545        $(
546            WeightedPoint {
547                point: Point::new($inner.x[$i - $d], $inner.y[$i - $d]),
548                weight: $inner.weight[$i - $d],
549            },
550        )*
551    ]};
552}
553
554impl PathData {
555    pub fn close(&mut self) {
556        let len = self.x.len();
557
558        let last_point = WeightedPoint {
559            point: Point::new(self.x[len - 1], self.y[len - 1]),
560            weight: self.weight[len - 1],
561        };
562        let open_point = WeightedPoint {
563            point: Point::new(self.x[self.open_point_index], self.y[self.open_point_index]),
564            weight: self.weight[self.open_point_index],
565        };
566
567        if last_point.applied() != open_point.applied() {
568            self.x.push(open_point.point.x);
569            self.y.push(open_point.point.y);
570            self.weight.push(open_point.weight);
571
572            self.commands.push(PathCommand::Line);
573        }
574    }
575
576    pub fn lines(&mut self) -> &Lines {
577        if self.lines.is_none() {
578            let mut primitives = Primitives::default();
579
580            let mut i = 0;
581            for command in &self.commands {
582                match command {
583                    PathCommand::Move => {
584                        i += 1;
585
586                        primitives.push_contour(Contour);
587                    }
588                    PathCommand::Line => {
589                        i += 1;
590
591                        let points = points!(self, i, 2, 1);
592                        primitives.push_line(points);
593                    }
594                    PathCommand::Quad => {
595                        i += 2;
596
597                        let points = points!(self, i, 3, 2, 1);
598                        primitives.push_quad(points);
599                    }
600                    PathCommand::Cubic => {
601                        i += 3;
602
603                        let points = points!(self, i, 4, 3, 2, 1);
604                        primitives.push_cubic(points);
605                    }
606                }
607            }
608
609            self.lines = Some(primitives.into_lines());
610        }
611
612        self.lines.as_ref().unwrap()
613    }
614}
615
616impl Default for PathData {
617    fn default() -> Self {
618        Self {
619            x: vec![0.0],
620            y: vec![0.0],
621            weight: vec![1.0],
622            commands: vec![PathCommand::Move],
623            open_point_index: 0,
624            lines: None,
625        }
626    }
627}
628
629#[derive(Clone, Debug, Default)]
630pub struct Path {
631    inner: Rc<RefCell<PathData>>,
632    transform: Option<GeomPresTransform>,
633}
634
635impl Path {
636    pub(crate) fn push_lines_to(
637        &self,
638        x: &mut Vec<f32>,
639        y: &mut Vec<f32>,
640        id: GeomId,
641        ids: &mut Vec<Option<GeomId>>,
642    ) {
643        let mut inner = self.inner.borrow_mut();
644
645        let lines = inner.lines();
646        let transform = &self.transform;
647
648        if let Some(transform) = &transform {
649            let iter = lines
650                .x
651                .par_iter()
652                .with_min_len(MIN_LEN)
653                .zip(
654                    lines
655                        .y
656                        .par_iter()
657                        .with_min_len(MIN_LEN)
658                        .zip(lines.start_new_contour.par_iter().with_min_len(MIN_LEN)),
659                )
660                .map(|(&x, (&y, start_new_contour))| {
661                    let point = transform.transform(Point::new(x, y));
662                    (point.x, point.y, (!start_new_contour).then_some(id))
663                });
664
665            ExtendTuple3::new((x, y, ids)).par_extend(iter);
666        } else {
667            let iter = lines
668                .x
669                .par_iter()
670                .with_min_len(MIN_LEN)
671                .zip(
672                    lines
673                        .y
674                        .par_iter()
675                        .with_min_len(MIN_LEN)
676                        .zip(lines.start_new_contour.par_iter().with_min_len(MIN_LEN)),
677                )
678                .map(|(&x, (&y, start_new_contour))| (x, y, (!start_new_contour).then_some(id)));
679
680            ExtendTuple3::new((x, y, ids)).par_extend(iter);
681        }
682    }
683
684    #[inline]
685    pub fn transform(&self, transform: &[f32; 9]) -> Self {
686        if let Some(transform) = GeomPresTransform::new(*transform) {
687            return Self { inner: Rc::clone(&self.inner), transform: Some(transform) };
688        }
689
690        let inner = self.inner.borrow();
691        let mut data = PathData {
692            x: inner.x.clone(),
693            y: inner.y.clone(),
694            weight: inner.weight.clone(),
695            commands: inner.commands.clone(),
696            open_point_index: inner.open_point_index,
697            lines: None,
698        };
699
700        data.x
701            .par_iter_mut()
702            .with_min_len(MIN_LEN)
703            .zip(
704                data.y
705                    .par_iter_mut()
706                    .with_min_len(MIN_LEN)
707                    .zip(data.weight.par_iter_mut().with_min_len(MIN_LEN)),
708            )
709            .for_each(|(x, (y, weight))| {
710                (*x, *y, *weight) = (
711                    transform[0].mul_add(*x, transform[1].mul_add(*y, transform[2] * *weight)),
712                    transform[3].mul_add(*x, transform[4].mul_add(*y, transform[5] * *weight)),
713                    transform[6].mul_add(*x, transform[7].mul_add(*y, transform[8] * *weight)),
714                );
715            });
716
717        Self { inner: Rc::new(RefCell::new(data)), transform: None }
718    }
719}
720
721impl Eq for Path {}
722
723impl PartialEq for Path {
724    fn eq(&self, other: &Self) -> bool {
725        Rc::ptr_eq(&self.inner, &other.inner)
726    }
727}
728
729#[derive(Clone, Debug, Default)]
730pub struct PathBuilder {
731    inner: Rc<RefCell<PathData>>,
732}
733
734impl PathBuilder {
735    #[inline]
736    pub fn new() -> Self {
737        Self::default()
738    }
739
740    #[inline]
741    pub fn move_to(&mut self, p: Point) -> &mut Self {
742        {
743            let mut inner = self.inner.borrow_mut();
744            let len = inner.x.len();
745
746            if matches!(inner.commands[inner.commands.len() - 1], PathCommand::Move) {
747                inner.x[len - 1] = p.x;
748                inner.y[len - 1] = p.y;
749                inner.weight[len - 1] = 1.0;
750            } else {
751                inner.close();
752
753                let open_point_index = inner.x.len();
754
755                inner.x.push(p.x);
756                inner.y.push(p.y);
757                inner.weight.push(1.0);
758
759                inner.commands.push(PathCommand::Move);
760
761                inner.open_point_index = open_point_index;
762            }
763        }
764
765        self
766    }
767
768    #[inline]
769    pub fn line_to(&mut self, p: Point) -> &mut Self {
770        {
771            let mut inner = self.inner.borrow_mut();
772
773            inner.x.push(p.x);
774            inner.y.push(p.y);
775            inner.weight.push(1.0);
776
777            inner.commands.push(PathCommand::Line);
778        }
779
780        self
781    }
782
783    #[inline]
784    pub fn quad_to(&mut self, p1: Point, p2: Point) -> &mut Self {
785        {
786            let mut inner = self.inner.borrow_mut();
787
788            inner.x.push(p1.x);
789            inner.y.push(p1.y);
790            inner.weight.push(1.0);
791
792            inner.x.push(p2.x);
793            inner.y.push(p2.y);
794            inner.weight.push(1.0);
795
796            inner.commands.push(PathCommand::Quad);
797        }
798
799        self
800    }
801
802    #[inline]
803    pub fn cubic_to(&mut self, p1: Point, p2: Point, p3: Point) -> &mut Self {
804        {
805            let mut inner = self.inner.borrow_mut();
806
807            inner.x.push(p1.x);
808            inner.y.push(p1.y);
809            inner.weight.push(1.0);
810
811            inner.x.push(p2.x);
812            inner.y.push(p2.y);
813            inner.weight.push(1.0);
814
815            inner.x.push(p3.x);
816            inner.y.push(p3.y);
817            inner.weight.push(1.0);
818
819            inner.commands.push(PathCommand::Cubic);
820        }
821
822        self
823    }
824
825    #[inline]
826    pub fn rat_quad_to(&mut self, p1: Point, p2: Point, weight: f32) -> &mut Self {
827        {
828            let mut inner = self.inner.borrow_mut();
829
830            inner.x.push(p1.x * weight);
831            inner.y.push(p1.y * weight);
832            inner.weight.push(weight);
833
834            inner.x.push(p2.x);
835            inner.y.push(p2.y);
836            inner.weight.push(1.0);
837
838            inner.commands.push(PathCommand::Quad);
839        }
840
841        self
842    }
843
844    #[inline]
845    pub fn rat_cubic_to(&mut self, p1: Point, p2: Point, p3: Point, w1: f32, w2: f32) -> &mut Self {
846        {
847            let mut inner = self.inner.borrow_mut();
848
849            inner.x.push(p1.x * w1);
850            inner.y.push(p1.y * w1);
851            inner.weight.push(w1);
852
853            inner.x.push(p2.x * w2);
854            inner.y.push(p2.y * w2);
855            inner.weight.push(w2);
856
857            inner.x.push(p3.x);
858            inner.y.push(p3.y);
859            inner.weight.push(1.0);
860
861            inner.commands.push(PathCommand::Cubic);
862        }
863
864        self
865    }
866
867    #[inline]
868    pub fn build(&mut self) -> Path {
869        let mut inner = self.inner.borrow_mut();
870
871        inner.close();
872
873        Path { inner: Rc::clone(&self.inner), transform: None }
874    }
875}
876
877#[cfg(test)]
878mod tests {
879    use super::*;
880
881    fn dist(p0: Point, p1: Point, p2: Point) -> f32 {
882        let d10 = p1 - p0;
883        let d21 = p2 - p1;
884
885        (d21.x * d10.y - d10.x * d21.y).abs() / d21.len()
886    }
887
888    fn min_dist(p: Point, points: &[Point]) -> f32 {
889        points
890            .windows(2)
891            .map(|window| dist(p, window[0], window[1]))
892            .min_by(|a, b| a.partial_cmp(b).unwrap())
893            .unwrap()
894    }
895
896    fn eval_quad(t: f32, points: &[WeightedPoint; 3]) -> Point {
897        let x = lerp(
898            t,
899            lerp(t, points[0].point.x, points[1].point.x),
900            lerp(t, points[1].point.x, points[2].point.x),
901        );
902        let y = lerp(
903            t,
904            lerp(t, points[0].point.y, points[1].point.y),
905            lerp(t, points[1].point.y, points[2].point.y),
906        );
907
908        Point { x, y }
909    }
910
911    macro_rules! curve {
912        (
913            ( $p0x:expr , $p0y:expr ) ,
914            ( $p1x:expr , $p1y:expr ) ,
915            ( $p2x:expr , $p2y:expr ) ,
916            ( $p3x:expr , $p3y:expr ) $( , )?
917        ) => {
918            [
919                WeightedPoint { point: Point::new($p0x, $p0y), weight: 1.0 },
920                WeightedPoint { point: Point::new($p1x, $p1y), weight: 1.0 },
921                WeightedPoint { point: Point::new($p2x, $p2y), weight: 1.0 },
922                WeightedPoint { point: Point::new($p3x, $p3y), weight: 1.0 },
923            ]
924        };
925        (
926            ( $p0x:expr , $p0y:expr ) ,
927            ( $p1x:expr , $p1y:expr ) ,
928            ( $p2x:expr , $p2y:expr ) $( , )?
929        ) => {
930            [
931                WeightedPoint { point: Point::new($p0x, $p0y), weight: 1.0 },
932                WeightedPoint { point: Point::new($p1x, $p1y), weight: 1.0 },
933                WeightedPoint { point: Point::new($p2x, $p2y), weight: 1.0 },
934            ]
935        };
936        ( ( $p0x:expr , $p0y:expr ) , ( $p1x:expr , $p1y:expr ) $( , )? ) => {
937            [
938                WeightedPoint { point: Point::new($p0x, $p0y), weight: 1.0 },
939                WeightedPoint { point: Point::new($p1x, $p1y), weight: 1.0 },
940            ]
941        };
942    }
943
944    #[test]
945    fn quads() {
946        let mut primitives = Primitives::default();
947
948        let c0 = curve![(2.0, 0.0), (0.0, 1.0), (10.0, 1.0)];
949        let c1 = curve![(10.0, 1.0), (20.0, 1.0), (18.0, 0.0)];
950
951        primitives.push_quad(c0);
952        primitives.push_quad(c1);
953
954        let lines = primitives.into_lines();
955
956        assert_eq!(lines.x.len(), 9);
957
958        assert_eq!(lines.x[0], 2.0);
959        assert_eq!(lines.y[0], 0.0);
960        assert_eq!(lines.x[8], 18.0);
961        assert_eq!(lines.y[8], 0.0);
962
963        let a = Point::new(lines.x[3], lines.y[3]);
964        let b = Point::new(lines.x[5], lines.y[5]);
965
966        assert!((a - b).len() > 10.0);
967
968        let points: Vec<_> =
969            lines.x.iter().zip(lines.y.iter()).map(|(&x, &y)| Point::new(x, y)).collect();
970
971        let min_c0 = (0..=50)
972            .into_iter()
973            .map(|i| {
974                let t = i as f32 / 50.0;
975
976                min_dist(eval_quad(t, &c0), &points)
977            })
978            .max_by(|a, b| a.partial_cmp(b).unwrap())
979            .unwrap();
980        let min_c1 = (0..=50)
981            .into_iter()
982            .map(|i| {
983                let t = i as f32 / 50.0;
984
985                min_dist(eval_quad(t, &c1), &points)
986            })
987            .max_by(|a, b| a.partial_cmp(b).unwrap())
988            .unwrap();
989
990        assert!(min_c0 < MAX_ERROR);
991        assert!(min_c1 < MAX_ERROR);
992    }
993
994    #[test]
995    fn two_splines() {
996        let mut primitives = Primitives::default();
997
998        primitives.push_quad(curve![(0.0, 0.0), (1.0, 2.0), (2.0, 0.0)]);
999        primitives.push_quad(curve![(3.0, 0.0), (4.0, 4.0), (5.0, 0.0)]);
1000
1001        let lines = primitives.into_lines();
1002
1003        assert_eq!(lines.x.len(), 11);
1004
1005        assert_eq!(lines.x[0], 0.0);
1006        assert_eq!(lines.y[0], 0.0);
1007        assert_eq!(lines.x[4], 2.0);
1008        assert_eq!(lines.y[4], 0.0);
1009        assert_eq!(lines.x[5], 3.0);
1010        assert_eq!(lines.y[5], 0.0);
1011        assert_eq!(lines.x[10], 5.0);
1012        assert_eq!(lines.y[10], 0.0);
1013    }
1014
1015    #[test]
1016    fn collinear_quad() {
1017        let mut primitives = Primitives::default();
1018
1019        primitives.push_quad(curve![(0.0, 0.0), (2.0, 0.0001), (1.0, 0.0)]);
1020
1021        let lines = primitives.into_lines();
1022
1023        assert_eq!(lines.x.len(), 3);
1024
1025        assert!((lines.x[1] - 1.25).abs() < 0.01);
1026        assert!((lines.y[1] - 0.0).abs() < 0.01);
1027    }
1028
1029    #[test]
1030    fn overlapping_control_point_quad() {
1031        let mut primitives = Primitives::default();
1032
1033        primitives.push_quad(curve![(0.0, 0.0), (0.0, 0.0), (1.0, 1.0)]);
1034        primitives.push_quad(curve![(1.0, 1.0), (1.0, 1.0), (1.0, 1.0)]);
1035        primitives.push_quad(curve![(1.0, 1.0), (2.0, 2.0), (2.0, 2.0)]);
1036
1037        let lines = primitives.into_lines();
1038
1039        assert_eq!(lines.x.len(), 2);
1040
1041        assert!((lines.x[0] - 0.0).abs() < 0.01);
1042        assert!((lines.y[0] - 0.0).abs() < 0.01);
1043        assert!((lines.x[1] - 2.0).abs() < 0.01);
1044        assert!((lines.y[1] - 2.0).abs() < 0.01);
1045    }
1046
1047    #[test]
1048    fn rat_quad() {
1049        let mut primitives = Primitives::default();
1050        let weight = 10.0;
1051
1052        primitives.push_quad([
1053            WeightedPoint { point: Point::new(0.0, 0.0), weight: 1.0 },
1054            WeightedPoint { point: Point::new(1.0 * weight, 2.0 * weight), weight },
1055            WeightedPoint { point: Point::new(2.0, 0.0), weight: 1.0 },
1056        ]);
1057
1058        let lines = primitives.into_lines();
1059
1060        assert_eq!(lines.x.len(), 5);
1061
1062        let points: Vec<_> =
1063            lines.x.iter().zip(lines.y.iter()).map(|(&x, &y)| Point::new(x, y)).collect();
1064
1065        assert!((points[2].x - 1.0).abs() <= 0.001);
1066
1067        let distances: Vec<_> =
1068            points.windows(2).map(|window| (window[1] - window[0]).len()).collect();
1069
1070        assert!(distances[0] > 1.5);
1071        assert!(distances[1] < 0.2);
1072        assert!(distances[2] < 0.2);
1073        assert!(distances[3] > 1.5);
1074    }
1075
1076    #[test]
1077    fn lines_and_quads() {
1078        let mut primitives = Primitives::default();
1079
1080        primitives.push_line(curve![(-1.0, -2.0), (0.0, 0.0)]);
1081        primitives.push_quad(curve![(0.0, 0.0), (1.0, 2.0), (2.0, 0.0)]);
1082        primitives.push_line(curve![(2.0, 0.0), (3.0, -2.0)]);
1083        primitives.push_line(curve![(3.0, -2.0), (4.0, 2.0)]);
1084        primitives.push_line(curve![(4.0, 2.0), (5.0, -4.0)]);
1085        primitives.push_line(curve![(5.0, -4.0), (6.0, 0.0)]);
1086        primitives.push_quad(curve![(6.0, 0.0), (7.0, 4.0), (8.0, 0.0)]);
1087        primitives.push_line(curve![(8.0, 0.0), (9.0, -4.0)]);
1088
1089        let lines = primitives.into_lines();
1090
1091        assert_eq!(lines.x.len(), 12);
1092
1093        assert_eq!(lines.x[0], -1.0);
1094        assert_eq!(lines.y[0], -2.0);
1095        assert_eq!(lines.x[4], 3.0);
1096        assert_eq!(lines.y[4], -2.0);
1097        assert_eq!(lines.x[5], 4.0);
1098        assert_eq!(lines.y[5], 2.0);
1099        assert_eq!(lines.x[6], 5.0);
1100        assert_eq!(lines.y[6], -4.0);
1101        assert_eq!(lines.x[11], 9.0);
1102        assert_eq!(lines.y[11], -4.0);
1103    }
1104
1105    #[test]
1106    fn cubic() {
1107        let mut primitives = Primitives::default();
1108
1109        primitives.push_cubic(curve![(0.0, 0.0), (10.0, 6.0), (-2.0, 6.0), (8.0, 0.0)]);
1110
1111        let lines = primitives.into_lines();
1112
1113        assert_eq!(lines.x.len(), 10);
1114
1115        assert!(lines.x[2] > lines.x[7]);
1116        assert!(lines.x[3] > lines.x[6]);
1117        assert!(lines.x[4] > lines.x[5]);
1118
1119        assert!(lines.y[0] < lines.y[1]);
1120        assert!(lines.y[1] < lines.y[2]);
1121        assert!(lines.y[2] < lines.y[3]);
1122        assert!(lines.y[3] < lines.y[4]);
1123
1124        assert!(lines.y[5] > lines.y[6]);
1125        assert!(lines.y[6] > lines.y[7]);
1126        assert!(lines.y[7] > lines.y[8]);
1127        assert!(lines.y[8] > lines.y[9]);
1128    }
1129
1130    #[test]
1131    fn rat_cubic_high() {
1132        let mut primitives = Primitives::default();
1133        let weight = 10.0;
1134
1135        primitives.push_cubic([
1136            WeightedPoint { point: Point::new(0.0, 0.0), weight: 1.0 },
1137            WeightedPoint { point: Point::new(5.0 * weight, 3.0 * weight), weight },
1138            WeightedPoint { point: Point::new(-1.0 * weight, 3.0 * weight), weight },
1139            WeightedPoint { point: Point::new(4.0, 0.0), weight: 1.0 },
1140        ]);
1141
1142        let lines = primitives.into_lines();
1143
1144        assert_eq!(lines.x.len(), 45);
1145    }
1146
1147    #[test]
1148    fn rat_cubic_low() {
1149        let mut primitives = Primitives::default();
1150        let weight = 0.5;
1151
1152        primitives.push_cubic([
1153            WeightedPoint { point: Point::new(0.0, 0.0), weight: 1.0 },
1154            WeightedPoint { point: Point::new(5.0 * weight, 3.0 * weight), weight },
1155            WeightedPoint { point: Point::new(-1.0 * weight, 3.0 * weight), weight },
1156            WeightedPoint { point: Point::new(4.0, 0.0), weight: 1.0 },
1157        ]);
1158
1159        let lines = primitives.into_lines();
1160
1161        assert_eq!(lines.x.len(), 7);
1162    }
1163
1164    #[test]
1165    fn collinear_cubic() {
1166        let mut primitives = Primitives::default();
1167
1168        primitives.push_cubic(curve![(1.0, 0.0), (0.0, 0.0), (3.0, 0.0), (2.0, 0.0)]);
1169
1170        let lines = primitives.into_lines();
1171
1172        assert_eq!(lines.x.len(), 5);
1173
1174        assert_eq!(lines.x[0], 1.0);
1175        assert_eq!(lines.y[0], 0.0);
1176
1177        assert!(lines.x[1] > 0.5);
1178        assert!(lines.x[1] < 1.0);
1179        assert_eq!(lines.y[1], 0.0);
1180
1181        assert!(lines.x[2] > 1.0);
1182        assert!(lines.x[2] < 2.0);
1183        assert_eq!(lines.y[2], 0.0);
1184
1185        assert!(lines.x[3] > 2.0);
1186        assert!(lines.x[3] < 2.5);
1187        assert_eq!(lines.y[3], 0.0);
1188
1189        assert_eq!(lines.x[4], 2.0);
1190        assert_eq!(lines.y[4], 0.0);
1191    }
1192
1193    #[test]
1194    fn overlapping_control_point_cubic_line() {
1195        let mut primitives = Primitives::default();
1196
1197        primitives.push_cubic(curve![(0.0, 0.0), (0.0, 0.0), (1.0, 1.0), (1.0, 1.0)]);
1198        primitives.push_cubic(curve![(1.0, 1.0), (1.0, 1.0), (1.0, 1.0), (1.0, 1.0)]);
1199        primitives.push_cubic(curve![(1.0, 1.0), (1.0, 1.0), (2.0, 2.0), (2.0, 2.0)]);
1200
1201        let lines = primitives.into_lines();
1202
1203        assert_eq!(lines.x.len(), 9);
1204
1205        for x in lines.x.windows(2) {
1206            assert!(x[0] < x[1]);
1207        }
1208
1209        for y in lines.y.windows(2) {
1210            assert!(y[0] < y[1]);
1211        }
1212
1213        for (&x, &y) in lines.x.iter().zip(lines.y.iter()) {
1214            assert_eq!(x, y);
1215        }
1216
1217        assert!((lines.x[0] - 0.0).abs() < 0.01);
1218        assert!((lines.y[0] - 0.0).abs() < 0.01);
1219        assert!((lines.x[8] - 2.0).abs() < 0.01);
1220        assert!((lines.y[8] - 2.0).abs() < 0.01);
1221    }
1222
1223    #[test]
1224    fn ring() {
1225        let mut primitives = Primitives::default();
1226
1227        primitives.push_cubic(curve![(0.0, 2.0), (2.0, 2.0), (2.0, 2.0), (2.0, 0.0)]);
1228        primitives.push_cubic(curve![(2.0, 0.0), (2.0, -2.0), (2.0, -2.0), (0.0, -2.0)]);
1229        primitives.push_cubic(curve![(0.0, -2.0), (-2.0, -2.0), (-2.0, -2.0), (-2.0, 0.0)]);
1230        primitives.push_cubic(curve![(-2.0, 0.0), (-2.0, 2.0), (-2.0, 2.0), (0.0, 2.0)]);
1231
1232        primitives.push_contour(Contour);
1233
1234        primitives.push_cubic(curve![(0.0, 1.0), (-1.0, 1.0), (-1.0, 1.0), (-1.0, 0.0)]);
1235        primitives.push_cubic(curve![(-1.0, 0.0), (-1.0, -1.0), (-1.0, -1.0), (0.0, -1.0)]);
1236        primitives.push_cubic(curve![(0.0, -1.0), (1.0, -1.0), (1.0, -1.0), (1.0, 0.0)]);
1237        primitives.push_cubic(curve![(1.0, 0.0), (1.0, 1.0), (1.0, 1.0), (0.0, 1.0)]);
1238
1239        let lines = primitives.into_lines();
1240
1241        assert_eq!(lines.start_new_contour.len(), 30);
1242
1243        assert_eq!(
1244            lines.start_new_contour.iter().filter(|&&start_new_contour| start_new_contour).count(),
1245            2
1246        );
1247
1248        assert!(lines.start_new_contour[16]);
1249        assert!(lines.start_new_contour[29]);
1250    }
1251
1252    #[test]
1253    fn ring_overlapping_start() {
1254        let mut primitives = Primitives::default();
1255
1256        primitives.push_cubic(curve![(0.0, 1.0), (-1.0, 1.0), (-1.0, 1.0), (-1.0, 0.0)]);
1257        primitives.push_cubic(curve![(-1.0, 0.0), (-1.0, -1.0), (-1.0, -1.0), (0.0, -1.0)]);
1258        primitives.push_cubic(curve![(0.0, -1.0), (1.0, -1.0), (1.0, -1.0), (1.0, 0.0)]);
1259        primitives.push_cubic(curve![(1.0, 0.0), (1.0, 1.0), (1.0, 1.0), (0.0, 1.0)]);
1260
1261        primitives.push_contour(Contour);
1262
1263        primitives.push_cubic(curve![(0.0, 1.0), (1.0, 1.0), (1.0, 1.0), (1.0, 2.0)]);
1264        primitives.push_cubic(curve![(1.0, 2.0), (1.0, 3.0), (1.0, 3.0), (0.0, 3.0)]);
1265        primitives.push_cubic(curve![(0.0, 3.0), (-1.0, 3.0), (-1.0, 3.0), (-1.0, 2.0)]);
1266        primitives.push_cubic(curve![(-1.0, 2.0), (-1.0, 1.0), (-1.0, 1.0), (0.0, 1.0)]);
1267
1268        let lines = primitives.into_lines();
1269
1270        assert_eq!(lines.start_new_contour.len(), 26);
1271
1272        assert_eq!(
1273            lines.start_new_contour.iter().filter(|&&start_new_contour| start_new_contour).count(),
1274            2
1275        );
1276
1277        assert!(lines.start_new_contour[12]);
1278        assert!(lines.start_new_contour[25]);
1279    }
1280
1281    #[test]
1282    fn circle() {
1283        let mut primitives = Primitives::default();
1284        let radius = 50.0;
1285        let weight = 2.0f32.sqrt() / 2.0;
1286
1287        primitives.push_quad([
1288            WeightedPoint { point: Point::new(radius, 0.0), weight: 1.0 },
1289            WeightedPoint { point: Point::new(0.0, 0.0), weight },
1290            WeightedPoint { point: Point::new(0.0, radius), weight: 1.0 },
1291        ]);
1292        primitives.push_quad([
1293            WeightedPoint { point: Point::new(0.0, radius), weight: 1.0 },
1294            WeightedPoint { point: Point::new(0.0, 2.0 * radius * weight), weight },
1295            WeightedPoint { point: Point::new(radius, 2.0 * radius), weight: 1.0 },
1296        ]);
1297        primitives.push_quad([
1298            WeightedPoint { point: Point::new(radius, 2.0 * radius), weight: 1.0 },
1299            WeightedPoint {
1300                point: Point::new(2.0 * radius * weight, 2.0 * radius * weight),
1301                weight,
1302            },
1303            WeightedPoint { point: Point::new(2.0 * radius, radius), weight: 1.0 },
1304        ]);
1305        primitives.push_quad([
1306            WeightedPoint { point: Point::new(2.0 * radius, radius), weight: 1.0 },
1307            WeightedPoint { point: Point::new(2.0 * radius * weight, 0.0), weight },
1308            WeightedPoint { point: Point::new(radius, 0.0), weight: 1.0 },
1309        ]);
1310
1311        let lines = primitives.into_lines();
1312
1313        assert_eq!(lines.x.len(), 66);
1314
1315        let points: Vec<_> =
1316            lines.x.iter().zip(lines.y.iter()).map(|(&x, &y)| Point::new(x, y)).collect();
1317
1318        let max_distance = points
1319            .windows(2)
1320            .map(|window| (window[1] - window[0]).len())
1321            .max_by(|a, b| a.partial_cmp(b).unwrap())
1322            .unwrap();
1323
1324        assert!(max_distance < 5.0);
1325    }
1326
1327    #[test]
1328    fn transform_path() {
1329        let weight = 2.0f32.sqrt() / 2.0;
1330        let radius = 10.0;
1331
1332        let mut builder = PathBuilder::new();
1333
1334        builder.move_to(Point::new(radius, 0.0));
1335        builder.rat_quad_to(Point::new(radius, -radius), Point::new(0.0, -radius), weight);
1336        builder.rat_quad_to(Point::new(-radius, -radius), Point::new(-radius, 0.0), weight);
1337        builder.rat_quad_to(Point::new(-radius, radius), Point::new(0.0, radius), weight);
1338        builder.rat_quad_to(Point::new(radius, radius), Point::new(radius, 0.0), weight);
1339
1340        let path = builder.build();
1341
1342        let mut x = Vec::new();
1343        let mut y = Vec::new();
1344        let mut ids = Vec::new();
1345
1346        let id0 = GeomId::default();
1347        let id1 = id0.next();
1348
1349        path.push_lines_to(&mut x, &mut y, id1, &mut ids);
1350
1351        let orig_len = x.len();
1352
1353        assert_eq!(ids[..ids.len() - 1], vec![Some(id1); ids.len() - 1]);
1354        assert_eq!(ids[ids.len() - 1], None);
1355
1356        for (&x, &y) in x.iter().zip(y.iter()) {
1357            assert!((Point::new(x, y).len() - radius).abs() <= 0.1);
1358        }
1359
1360        let dx = 5.0;
1361        let dy = 20.0;
1362        let translated_path = path.transform(&[1.0, 0.0, dx, 0.0, 1.0, dy, 0.0, 0.0, 1.0]);
1363
1364        x.clear();
1365        y.clear();
1366
1367        translated_path.push_lines_to(&mut x, &mut y, id0, &mut ids);
1368
1369        for (&x, &y) in x.iter().zip(y.iter()) {
1370            assert!((Point::new(x - dx, y - dy).len() - radius).abs() <= 0.1);
1371        }
1372
1373        let s = 2.0;
1374        let scaled_path = path.transform(&[s, 0.0, 0.0, 0.0, s, 0.0, 0.0, 0.0, 1.0]);
1375
1376        x.clear();
1377        y.clear();
1378
1379        scaled_path.push_lines_to(&mut x, &mut y, id0, &mut ids);
1380
1381        for (&x, &y) in x.iter().zip(y.iter()) {
1382            assert!((Point::new(x, y).len() - s * radius).abs() <= 0.1);
1383        }
1384
1385        let scaled_len = x.len();
1386
1387        assert!(scaled_len > orig_len);
1388    }
1389
1390    #[test]
1391    fn perspective_transform_path() {
1392        let weight = 2.0f32.sqrt() / 2.0;
1393        let radius = 10.0;
1394        let translation = 1000.0;
1395
1396        let mut builder = PathBuilder::new();
1397
1398        builder.move_to(Point::new(radius + translation, 0.0));
1399        builder.rat_quad_to(
1400            Point::new(radius + translation, -radius),
1401            Point::new(translation, -radius),
1402            weight,
1403        );
1404        builder.rat_quad_to(
1405            Point::new(-radius + translation, -radius),
1406            Point::new(-radius + translation, 0.0),
1407            weight,
1408        );
1409        builder.rat_quad_to(
1410            Point::new(-radius + translation, radius),
1411            Point::new(translation, radius),
1412            weight,
1413        );
1414        builder.rat_quad_to(
1415            Point::new(radius + translation, radius),
1416            Point::new(radius + translation, 0.0),
1417            weight,
1418        );
1419
1420        let path = builder.build().transform(&[1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.001, 0.0, 1.0]);
1421
1422        let mut x = Vec::new();
1423        let mut y = Vec::new();
1424        let mut ids = Vec::new();
1425
1426        path.push_lines_to(&mut x, &mut y, GeomId::default(), &mut ids);
1427
1428        let mut points: Vec<_> = x.iter().zip(y.iter()).map(|(&x, &y)| Point::new(x, y)).collect();
1429        points.pop(); // Remove duplicate point.
1430
1431        let half_len = points.len() / 2;
1432
1433        let min = (0..half_len)
1434            .into_iter()
1435            .map(|i| (points[i] - points[(i + half_len) % points.len()]).len())
1436            .min_by(|a, b| a.partial_cmp(b).unwrap())
1437            .unwrap();
1438        let max = (0..half_len)
1439            .into_iter()
1440            .map(|i| (points[i] - points[(i + half_len) % points.len()]).len())
1441            .max_by(|a, b| a.partial_cmp(b).unwrap())
1442            .unwrap();
1443
1444        assert!((min - radius / 2.0).abs() <= 0.2);
1445        assert!((max - radius).abs() <= 0.2);
1446    }
1447}