1use 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
29pub 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 if !current_curvature.is_finite() || current_curvature <= 1.0 {
308 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(); 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}