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