1use kurbo::{BezPath, PathEl, Point, Rect, Vec2};
9
10use crate::preset::{FillMode, Geometry, SubPath};
11
12#[derive(Clone, Debug, PartialEq)]
15pub struct Contour {
16 pub closed: bool,
17 pub pts: Vec<Point>,
18}
19
20impl Contour {
21 pub fn curves(&self) -> usize {
22 self.pts.len().saturating_sub(1) / 3
23 }
24}
25
26fn line_to(p: &mut Vec<Point>, to: Point) {
27 let Some(&from) = p.last() else { return };
28 p.extend([from.lerp(to, 1.0 / 3.0), from.lerp(to, 2.0 / 3.0), to]);
29}
30
31fn close(c: &mut Contour) {
32 if let (Some(&first), Some(&last)) = (c.pts.first(), c.pts.last())
33 && first != last
34 {
35 line_to(&mut c.pts, first);
36 }
37 c.closed = true;
38}
39
40fn pen(cur: &mut Option<Contour>, start: Point) -> &mut Contour {
41 cur.get_or_insert_with(|| Contour {
42 closed: false,
43 pts: vec![start],
44 })
45}
46
47fn keep(c: Contour, out: &mut Vec<Contour>) {
48 if c.curves() > 0 && c.pts.iter().all(|p| p.is_finite()) {
49 out.push(c);
50 }
51}
52
53pub fn contours(path: &BezPath) -> Vec<Contour> {
56 let mut out = Vec::new();
57 let mut cur: Option<Contour> = None;
58 let mut start = Point::ZERO;
59 for el in path.elements() {
60 match *el {
61 PathEl::MoveTo(p) => {
62 if let Some(c) = cur.take() {
63 keep(c, &mut out);
64 }
65 start = p;
66 cur = Some(Contour {
67 closed: false,
68 pts: vec![p],
69 });
70 }
71 PathEl::LineTo(p) => line_to(&mut pen(&mut cur, start).pts, p),
72 PathEl::QuadTo(q, p) => {
73 let c = pen(&mut cur, start);
74 if let Some(&a) = c.pts.last() {
75 c.pts
76 .extend([a.lerp(q, 2.0 / 3.0), p.lerp(q, 2.0 / 3.0), p]);
77 }
78 }
79 PathEl::CurveTo(a, b, p) => pen(&mut cur, start).pts.extend([a, b, p]),
80 PathEl::ClosePath => {
81 if let Some(mut c) = cur.take()
82 && c.curves() > 0
83 {
84 close(&mut c);
85 keep(c, &mut out);
86 }
87 }
88 }
89 }
90 if let Some(c) = cur {
91 keep(c, &mut out);
92 }
93 out
94}
95
96pub fn to_path(cs: &[Contour]) -> BezPath {
98 let mut bp = BezPath::new();
99 for c in cs {
100 let Some(&s) = c.pts.first() else { continue };
101 bp.move_to(s);
102 for [a, b, p] in c.pts.get(1..).unwrap_or(&[]).as_chunks::<3>().0 {
103 bp.curve_to(*a, *b, *p);
104 }
105 if c.closed {
106 bp.close_path();
107 }
108 }
109 bp
110}
111
112fn curve_length(c: &[Point]) -> f64 {
116 let [p0, p1, p2, p3] = c else { return 0.0 };
117 (p0.distance(*p3) + p0.distance(*p1) + p1.distance(*p2) + p2.distance(*p3)) / 2.0
118}
119
120fn split_curve(c: &[Point], n: usize, out: &mut Vec<Point>) {
123 let [a, b, d, e] = c else { return };
124 let (mut p0, mut p1, mut p2, p3) = (*a, *b, *d, *e);
125 for j in (2..=n).rev() {
126 let t = 1.0 / j as f64;
127 let (ab, bc, cd) = (p0.lerp(p1, t), p1.lerp(p2, t), p2.lerp(p3, t));
128 let (abc, bcd) = (ab.lerp(bc, t), bc.lerp(cd, t));
129 let m = abc.lerp(bcd, t);
130 out.extend([ab, abc, m]);
131 (p0, p1, p2) = (m, bcd, cd);
132 }
133 out.extend([p1, p2, p3]);
134}
135
136fn refine(c: &Contour, n: usize) -> Contour {
139 let have = c.curves();
140 if have >= n || have == 0 {
141 return c.clone();
142 }
143 let curves: Vec<&[Point]> = (0..have)
144 .filter_map(|k| c.pts.get(3 * k..3 * k + 4))
145 .collect();
146 let len: Vec<f64> = curves.iter().map(|q| curve_length(q) + 1e-9).collect();
147 let mut parts = vec![1usize; curves.len()];
148 for _ in 0..n - have {
149 let share = |k: usize| {
150 len.get(k).copied().unwrap_or(0.0) / parts.get(k).copied().unwrap_or(1) as f64
151 };
152 let best = (1..curves.len()).fold(0, |b, k| if share(k) > share(b) { k } else { b });
153 if let Some(p) = parts.get_mut(best) {
154 *p += 1;
155 }
156 }
157 let mut out = Vec::with_capacity(1 + 3 * n);
158 out.extend(c.pts.first().copied());
159 for (q, p) in curves.iter().zip(&parts) {
160 split_curve(q, *p, &mut out);
161 }
162 Contour {
163 closed: c.closed,
164 pts: out,
165 }
166}
167
168const MIN_CURVES: usize = 12;
169const MAX_CURVES: usize = 60;
170const MAX_TRIES: usize = 128;
172
173fn curves_for(na: usize, nb: usize) -> usize {
177 fn gcd(x: usize, y: usize) -> usize {
178 if y == 0 { x } else { gcd(y, x % y) }
179 }
180 let hi = na.max(nb).max(1);
181 let mut n = if na > 0 && nb > 0 {
182 (na / gcd(na, nb)).saturating_mul(nb)
183 } else {
184 hi
185 };
186 if n > MAX_CURVES {
187 n = hi;
188 }
189 n.saturating_mul(MIN_CURVES.div_ceil(n).max(1))
190}
191
192fn bounds<'a>(pts: impl IntoIterator<Item = &'a Point>) -> Option<Rect> {
194 pts.into_iter().fold(None, |r: Option<Rect>, p| {
195 Some(r.map_or(Rect::from_points(*p, *p), |r| r.union_pt(*p)))
196 })
197}
198
199fn center(pts: &[Point]) -> Point {
200 bounds(pts).map(|r| r.center()).unwrap_or(Point::ZERO)
201}
202
203fn turn(pts: &[Point], s: usize, back: bool) -> Vec<Point> {
205 let q: Vec<Point> = if back {
206 pts.iter().rev().copied().collect()
207 } else {
208 pts.to_vec()
209 };
210 if s == 0 {
211 return q;
212 }
213 let body = q.get(1..).unwrap_or(&[]);
214 let cut = (3 * s).min(body.len());
215 let mut out = Vec::with_capacity(q.len());
216 out.extend(cut.checked_sub(1).and_then(|i| body.get(i)).copied());
217 out.extend_from_slice(body.get(cut..).unwrap_or(&[]));
218 out.extend_from_slice(body.get(..cut).unwrap_or(&[]));
219 out
220}
221
222fn match_pair(a: &Contour, b: &Contour, back: bool) -> (Contour, Contour, f64) {
226 let bp = if back {
227 turn(&b.pts, 0, true)
228 } else {
229 b.pts.clone()
230 };
231 let turn_a = !b.closed && a.closed;
232 let (fixed, free) = if turn_a { (&bp, &a.pts) } else { (&a.pts, &bp) };
233 let (fc, gc) = (center(fixed), center(free));
234 let tries = if turn_a || b.closed {
235 free.len().saturating_sub(1) / 3
236 } else {
237 1
238 };
239 let mut best = free.clone();
240 let mut best_cost = f64::INFINITY;
241 for s in (0..tries.max(1)).step_by(tries.div_ceil(MAX_TRIES).max(1)) {
242 let q = turn(free, s, false);
243 let mut cost = 0.0;
244 for (p, f) in q.iter().zip(fixed.iter()) {
245 cost += ((*p - gc) - (*f - fc)).hypot2();
246 if cost >= best_cost {
247 break;
248 }
249 }
250 if cost < best_cost {
251 best_cost = cost;
252 best = q;
253 }
254 }
255 let (pa, pb) = if turn_a {
256 (best, bp)
257 } else {
258 (a.pts.clone(), best)
259 };
260 (
261 Contour {
262 closed: a.closed,
263 pts: pa,
264 },
265 Contour {
266 closed: b.closed,
267 pts: pb,
268 },
269 best_cost,
270 )
271}
272
273fn place(c: &Contour, from: Option<Rect>, to: Option<Rect>) -> Point {
276 let p = center(&c.pts);
277 let (Some(from), Some(to)) = (from, to) else {
278 return p;
279 };
280 let fx = if from.width() > 0.0 {
281 (p.x - from.x0) / from.width()
282 } else {
283 0.5
284 };
285 let fy = if from.height() > 0.0 {
286 (p.y - from.y0) / from.height()
287 } else {
288 0.5
289 };
290 Point::new(to.x0 + to.width() * fx, to.y0 + to.height() * fy)
291}
292
293pub fn align(
298 a: &[Contour],
299 b: &[Contour],
300 ba: Option<Rect>,
301 bb: Option<Rect>,
302) -> (Vec<Contour>, Vec<Contour>) {
303 let mut pairs: Vec<(Contour, Contour, bool)> = Vec::new();
305 for k in 0..a.len().max(b.len()) {
306 let (ca, cb) = (a.get(k), b.get(k));
307 let n = curves_for(ca.map_or(0, Contour::curves), cb.map_or(0, Contour::curves));
308 let point = |c: &Contour, from, to, closed| Contour {
309 closed,
310 pts: vec![place(c, from, to); 1 + 3 * n],
311 };
312 match (ca, cb) {
313 (Some(x), Some(y)) => pairs.push((refine(x, n), refine(y, n), false)),
314 (Some(x), None) => pairs.push((refine(x, n), point(x, ba, bb, x.closed), true)),
315 (None, Some(y)) => pairs.push((point(y, bb, ba, y.closed), refine(y, n), true)),
316 (None, None) => {}
317 }
318 }
319 let mut best: Option<(Vec<Contour>, Vec<Contour>, f64)> = None;
320 for back in [false, true] {
321 let (mut xs, mut ys, mut cost) = (Vec::new(), Vec::new(), 0.0);
322 for (x, y, point) in &pairs {
323 if *point {
324 xs.push(x.clone());
325 ys.push(if back {
326 Contour {
327 closed: y.closed,
328 pts: turn(&y.pts, 0, true),
329 }
330 } else {
331 y.clone()
332 });
333 } else {
334 let (mx, my, c) = match_pair(x, y, back);
335 xs.push(mx);
336 ys.push(my);
337 cost += c;
338 }
339 }
340 if best.as_ref().is_none_or(|b| cost < b.2) {
341 best = Some((xs, ys, cost));
342 }
343 }
344 best.map(|(x, y, _)| (x, y)).unwrap_or_default()
345}
346
347#[derive(Clone, Copy, Debug, PartialEq)]
350struct Look {
351 fill: FillMode,
352 stroke: bool,
353 even_odd: bool,
354}
355
356#[derive(Clone, Debug)]
357struct Part {
358 a: Option<Look>,
359 b: Option<Look>,
360 ca: Vec<Contour>,
361 cb: Vec<Contour>,
362}
363
364#[derive(Clone, Debug)]
369pub struct GeometryMorph {
370 parts: Vec<Part>,
371 text: (Rect, Rect),
372}
373
374fn look(s: &SubPath) -> Look {
375 Look {
376 fill: s.fill,
377 stroke: s.stroke,
378 even_odd: s.even_odd,
379 }
380}
381
382#[derive(Clone, Copy, Debug, PartialEq)]
384pub struct Fade {
385 pub fill: f64,
386 pub stroke: f64,
387}
388
389fn layers(a: Option<Look>, b: Option<Look>, t: f64) -> Vec<(Look, Fade)> {
394 let (a, b) = match (a, b) {
395 (Some(a), Some(b)) if a != b => (a, b),
396 (Some(x), _) | (None, Some(x)) => {
397 return vec![(
398 x,
399 Fade {
400 fill: 1.0,
401 stroke: 1.0,
402 },
403 )];
404 }
405 (None, None) => return vec![],
406 };
407 let side = |on_a: bool, on_b: bool| match (on_a, on_b) {
408 (true, true) => 1.0,
409 (true, false) => 1.0 - t,
410 (false, true) => t,
411 (false, false) => 0.0,
412 };
413 let (stroke, line) = (a.stroke || b.stroke, side(a.stroke, b.stroke));
414 let (fa, fb) = (a.fill != FillMode::None, b.fill != FillMode::None);
415 if fa && fb {
416 let (under, over, alpha) = if a.even_odd || !b.even_odd {
417 (a, b, t)
418 } else {
419 (b, a, 1.0 - t)
420 };
421 vec![
422 (
423 Look { stroke, ..under },
424 Fade {
425 fill: 1.0,
426 stroke: line,
427 },
428 ),
429 (
430 Look {
431 stroke: false,
432 ..over
433 },
434 Fade {
435 fill: alpha,
436 stroke: 0.0,
437 },
438 ),
439 ]
440 } else {
441 let filled = if fa { a } else { b };
442 vec![(
443 Look { stroke, ..filled },
444 Fade {
445 fill: side(fa, fb),
446 stroke: line,
447 },
448 )]
449 }
450}
451
452impl GeometryMorph {
453 pub fn new(a: &Geometry, b: &Geometry) -> Self {
455 let ca: Vec<Vec<Contour>> = a.paths.iter().map(|s| contours(&s.path)).collect();
456 let cb: Vec<Vec<Contour>> = b.paths.iter().map(|s| contours(&s.path)).collect();
457 let ba = bounds(ca.iter().flatten().flat_map(|c| &c.pts));
458 let bb = bounds(cb.iter().flatten().flat_map(|c| &c.pts));
459 let mut parts = Vec::new();
460 for k in 0..a.paths.len().max(b.paths.len()) {
461 let (xa, xb) = (
462 ca.get(k).map(Vec::as_slice).unwrap_or(&[]),
463 cb.get(k).map(Vec::as_slice).unwrap_or(&[]),
464 );
465 let (pa, pb) = align(xa, xb, ba, bb);
466 parts.push(Part {
467 a: a.paths.get(k).map(look),
468 b: b.paths.get(k).map(look),
469 ca: pa,
470 cb: pb,
471 });
472 }
473 GeometryMorph {
474 parts,
475 text: (a.text_rect, b.text_rect),
476 }
477 }
478
479 pub fn at(&self, t: f64, sa: Vec2, sb: Vec2) -> (Geometry, Vec<Fade>) {
482 let t = if t.is_finite() {
483 t.clamp(0.0, 1.0)
484 } else {
485 0.0
486 };
487 let scale = |p: Point, s: Vec2| Point::new(p.x * s.x, p.y * s.y);
488 let (mut paths, mut fades) = (Vec::new(), Vec::new());
489 for part in &self.parts {
490 let cs: Vec<Contour> = part
491 .ca
492 .iter()
493 .zip(&part.cb)
494 .map(|(x, y)| Contour {
495 closed: x.closed && y.closed,
496 pts: x
497 .pts
498 .iter()
499 .zip(&y.pts)
500 .map(|(p, q)| scale(*p, sa).lerp(scale(*q, sb), t))
501 .collect(),
502 })
503 .collect();
504 let path = to_path(&cs);
505 for (lk, fade) in layers(part.a, part.b, t) {
506 paths.push(SubPath {
507 path: path.clone(),
508 fill: lk.fill,
509 stroke: lk.stroke,
510 even_odd: lk.even_odd,
511 });
512 fades.push(fade);
513 }
514 }
515 let r = |r: Rect, s: Vec2| Rect::new(r.x0 * s.x, r.y0 * s.y, r.x1 * s.x, r.y1 * s.y);
516 let (ta, tb) = (r(self.text.0, sa), r(self.text.1, sb));
517 let text_rect = Rect::from_points(
518 ta.origin().lerp(tb.origin(), t),
519 Point::new(ta.x1, ta.y1).lerp(Point::new(tb.x1, tb.y1), t),
520 );
521 (
522 Geometry {
523 paths,
524 text_rect,
525 handles: vec![],
526 sites: vec![],
527 },
528 fades,
529 )
530 }
531}
532
533#[cfg(test)]
534mod tests {
535 use super::*;
536 use crate::preset::build;
537 use kurbo::Shape as _;
538
539 fn bbox(g: &Geometry) -> Rect {
540 g.outline().bounding_box()
541 }
542
543 fn near(a: Rect, b: Rect) -> bool {
544 (a.x0 - b.x0).abs() < 1e-6
545 && (a.y0 - b.y0).abs() < 1e-6
546 && (a.x1 - b.x1).abs() < 1e-6
547 && (a.y1 - b.y1).abs() < 1e-6
548 }
549
550 #[test]
551 fn contours_turn_lines_and_quads_into_cubics() {
552 let mut p = BezPath::new();
553 p.move_to((0.0, 0.0));
554 p.line_to((3.0, 0.0));
555 p.quad_to((3.0, 3.0), (0.0, 3.0));
556 p.close_path();
557 p.line_to((5.0, 5.0));
559 p.move_to((9.0, 9.0));
560 let cs = contours(&p);
561 assert_eq!(cs.len(), 2, "{cs:?}");
562 assert!(cs[0].closed && !cs[1].closed);
563 assert_eq!(cs[0].curves(), 3);
565 assert_eq!(cs[0].pts[1], Point::new(1.0, 0.0));
566 assert_eq!(cs[0].pts.last(), cs[0].pts.first());
567 let end = Point::new(5.0, 5.0);
568 assert_eq!(
569 cs[1].pts,
570 vec![
571 Point::ZERO,
572 Point::ZERO.lerp(end, 1.0 / 3.0),
573 Point::ZERO.lerp(end, 2.0 / 3.0),
574 end
575 ]
576 );
577 assert_eq!(contours(&to_path(&cs)), cs);
579 }
580
581 #[test]
582 fn refine_splits_the_longest_curves_and_keeps_the_shape() {
583 let c = Contour {
584 closed: false,
585 pts: vec![
586 Point::ZERO,
587 Point::new(1.0, 0.0),
588 Point::new(2.0, 0.0),
589 Point::new(3.0, 0.0),
590 ],
591 };
592 let mut long = c.clone();
593 line_to(&mut long.pts, Point::new(3.0, 30.0));
594 let r = refine(&long, 5);
595 assert_eq!(r.curves(), 5);
596 assert_eq!(r.pts[3], Point::new(3.0, 0.0));
598 assert_eq!(r.pts.last(), Some(&Point::new(3.0, 30.0)));
599 assert!(r.pts.iter().all(|p| (p.x - 3.0).abs() < 1e-9 || p.y == 0.0));
600 assert_eq!(refine(&c, 1), c);
601 assert_eq!(curves_for(3, 4), 12);
602 assert_eq!(curves_for(4, 4), 12);
603 assert_eq!(curves_for(7, 11), 22);
605 assert_eq!(curves_for(0, 0), 12);
606 }
607
608 #[test]
609 fn ellipse_turns_into_a_star_and_lands_exactly() {
610 let (a, b) = (
611 build("ellipse", 100.0, 100.0, &[]).unwrap(),
612 build("star5", 200.0, 100.0, &[]).unwrap(),
613 );
614 let m = GeometryMorph::new(&a, &b);
615 let one = Vec2::new(1.0, 1.0);
616 assert!(near(bbox(&m.at(0.0, one, one).0), bbox(&a)));
617 assert!(near(bbox(&m.at(1.0, one, one).0), bbox(&b)));
618 let mid = m.at(0.5, one, one).0;
619 assert_eq!(mid.paths.len(), 1);
620 assert!(
621 mid.paths[0]
622 .path
623 .elements()
624 .iter()
625 .all(|e| e.end_point().is_none_or(|p| p.is_finite()))
626 );
627 assert!(near(bbox(&m.at(f64::NAN, one, one).0), bbox(&a)));
629 }
630
631 #[test]
632 fn triangle_does_not_twist_into_a_circle() {
633 let (a, b) = (
634 build("triangle", 100.0, 100.0, &[]).unwrap(),
635 build("ellipse", 100.0, 100.0, &[]).unwrap(),
636 );
637 let ca = contours(&a.paths[0].path);
638 let cb = contours(&b.paths[0].path);
639 let (x, y) = align(&ca, &cb, None, None);
640 let far = x[0]
642 .pts
643 .iter()
644 .zip(&y[0].pts)
645 .map(|(p, q)| p.distance(*q))
646 .fold(0.0, f64::max);
647 assert!(far < 60.0, "{far}");
648 }
649
650 #[test]
651 fn extra_sub_paths_grow_out_of_a_point() {
652 let (a, b) = (
653 build("rect", 100.0, 100.0, &[]).unwrap(),
654 build("cube", 100.0, 100.0, &[]).unwrap(),
655 );
656 assert!(b.paths.len() > 1);
657 let m = GeometryMorph::new(&a, &b);
658 let one = Vec2::new(1.0, 1.0);
659 let start = m.at(0.0, one, one).0;
660 assert_eq!(start.paths.len(), b.paths.len());
661 for sp in start.paths.iter().skip(1) {
662 let r = sp.path.bounding_box();
663 assert!(r.width() < 1e-9 && r.height() < 1e-9, "{r:?}");
664 }
665 assert_eq!(
667 m.at(1.0, one, one)
668 .0
669 .paths
670 .iter()
671 .map(|s| s.fill)
672 .collect::<Vec<_>>(),
673 b.paths.iter().map(|s| s.fill).collect::<Vec<_>>()
674 );
675 let back = GeometryMorph::new(&b, &a).at(1.0, one, one).0;
677 assert!(
678 back.paths
679 .iter()
680 .skip(1)
681 .all(|sp| sp.path.bounding_box().area() < 1e-9)
682 );
683 }
684
685 #[test]
686 fn differing_looks_cross_fade() {
687 let one = Vec2::new(1.0, 1.0);
688 let fades = |a: &Geometry, b: &Geometry, t: f64| {
689 let (g, f) = GeometryMorph::new(a, b).at(t, one, one);
690 g.paths
691 .iter()
692 .zip(f)
693 .map(|(s, f)| (s.fill, s.even_odd, s.stroke, f.fill, f.stroke))
694 .collect::<Vec<_>>()
695 };
696 let (r, line) = (
698 build("rect", 100.0, 100.0, &[]).unwrap(),
699 build("line", 100.0, 0.0, &[]).unwrap(),
700 );
701 assert_eq!(
702 fades(&r, &line, 0.25),
703 [(FillMode::Norm, false, true, 0.75, 1.0)]
704 );
705 assert_eq!(
706 fades(&line, &r, 0.25),
707 [(FillMode::Norm, false, true, 0.25, 1.0)]
708 );
709 let (d, e) = (
711 build("donut", 100.0, 100.0, &[]).unwrap(),
712 build("ellipse", 100.0, 100.0, &[]).unwrap(),
713 );
714 assert!(d.paths[0].even_odd && !e.paths[0].even_odd);
715 assert_eq!(
716 fades(&d, &e, 0.25),
717 [
718 (FillMode::Norm, true, true, 1.0, 1.0),
719 (FillMode::Norm, false, false, 0.25, 0.0)
720 ]
721 );
722 assert_eq!(
723 fades(&e, &d, 0.25),
724 [
725 (FillMode::Norm, true, true, 1.0, 1.0),
726 (FillMode::Norm, false, false, 0.75, 0.0)
727 ]
728 );
729 assert_eq!(
731 fades(&e, &r, 0.5),
732 [(FillMode::Norm, false, true, 1.0, 1.0)]
733 );
734 }
735
736 #[test]
737 fn scales_bring_both_sides_into_the_current_box() {
738 let (a, b) = (
739 build("rect", 10.0, 10.0, &[]).unwrap(),
740 build("ellipse", 40.0, 20.0, &[]).unwrap(),
741 );
742 let m = GeometryMorph::new(&a, &b);
743 let g = m.at(0.0, Vec2::new(2.0, 2.0), Vec2::new(0.5, 1.0)).0;
745 assert!(near(bbox(&g), Rect::new(0.0, 0.0, 20.0, 20.0)));
746 assert!(near(
747 g.text_rect,
748 Rect::new(
749 a.text_rect.x0 * 2.0,
750 a.text_rect.y0 * 2.0,
751 a.text_rect.x1 * 2.0,
752 a.text_rect.y1 * 2.0
753 )
754 ));
755 let g = m.at(1.0, Vec2::new(2.0, 2.0), Vec2::new(0.5, 1.0)).0;
756 assert!(near(bbox(&g), Rect::new(0.0, 0.0, 20.0, 20.0)));
757 }
758
759 #[test]
760 fn empty_and_degenerate_geometries_do_not_crash() {
761 let empty = Geometry {
762 paths: vec![],
763 text_rect: Rect::ZERO,
764 handles: vec![],
765 sites: vec![],
766 };
767 let r = build("rect", 10.0, 10.0, &[]).unwrap();
768 let line = build("line", 10.0, 0.0, &[]).unwrap();
769 let mut nan = BezPath::new();
770 nan.move_to((f64::NAN, 0.0));
771 nan.line_to((1.0, 1.0));
772 let bad = Geometry {
773 paths: vec![SubPath {
774 path: nan,
775 fill: FillMode::Norm,
776 stroke: true,
777 even_odd: false,
778 }],
779 ..empty.clone()
780 };
781 for (x, y) in [
782 (&empty, &empty),
783 (&empty, &r),
784 (&r, &empty),
785 (&r, &line),
786 (&line, &r),
787 (&bad, &r),
788 (&r, &bad),
789 ] {
790 for t in [0.0, 0.5, 1.0, f64::INFINITY] {
791 let g = GeometryMorph::new(x, y)
792 .at(t, Vec2::new(1.0, 1.0), Vec2::new(0.0, 1e300))
793 .0;
794 assert!(g.paths.len() <= 2 * x.paths.len().max(y.paths.len()));
796 }
797 }
798 let g = GeometryMorph::new(&r, &line)
800 .at(1.0, Vec2::new(1.0, 1.0), Vec2::new(1.0, 1.0))
801 .0;
802 assert!(
803 !g.paths[0]
804 .path
805 .elements()
806 .iter()
807 .any(|e| matches!(e, PathEl::ClosePath))
808 );
809 }
810
811 #[test]
812 fn long_contours_sample_their_start_points() {
813 let mut p = BezPath::new();
815 for k in 0..2000 {
816 let a = k as f64 / 2000.0 * std::f64::consts::TAU;
817 let q = (50.0 + 50.0 * a.cos(), 50.0 + 50.0 * a.sin());
818 if k == 0 { p.move_to(q) } else { p.line_to(q) }
819 }
820 p.close_path();
821 let many = contours(&p);
822 let e = contours(&build("ellipse", 100.0, 100.0, &[]).unwrap().paths[0].path);
823 let (x, y) = align(&many, &e, None, None);
824 assert_eq!(x[0].curves(), y[0].curves());
825 assert_eq!(x[0].curves(), 2000);
826 }
827}