1use kurbo::{Arc, BezPath, PathEl, Point, Rect, Vec2};
21
22const TOL: f64 = 0.02;
24
25#[derive(Clone, Debug, Default)]
27pub struct Guide {
28 pts: Vec<Point>,
29 cum: Vec<f64>,
30 closed: bool,
31}
32
33impl Guide {
34 fn from_points(raw: impl IntoIterator<Item = Point>, closed: bool) -> Guide {
35 let mut pts: Vec<Point> = Vec::new();
36 let mut cum: Vec<f64> = Vec::new();
37 for p in raw {
38 if !(p.x.is_finite() && p.y.is_finite()) {
39 continue;
40 }
41 match pts.last() {
42 Some(q) => {
43 let d = (p - *q).hypot();
44 if d > 1e-9 {
45 cum.push(cum.last().copied().unwrap_or(0.0) + d);
46 pts.push(p);
47 }
48 }
49 None => {
50 pts.push(p);
51 cum.push(0.0);
52 }
53 }
54 if pts.len() > 100_000 {
55 break;
56 }
57 }
58 Guide { pts, cum, closed }
59 }
60
61 pub fn from_path(path: &BezPath, closed: bool) -> Guide {
63 let mut pts = Vec::new();
64 kurbo::flatten(path.iter(), TOL, |el| match el {
65 PathEl::MoveTo(p) | PathEl::LineTo(p) => pts.push(p),
66 _ => {}
67 });
68 Guide::from_points(pts, closed)
69 }
70
71 pub fn length(&self) -> f64 {
73 self.cum.last().copied().unwrap_or(0.0)
74 }
75
76 pub fn is_closed(&self) -> bool {
78 self.closed
79 }
80
81 pub fn at(&self, s: f64) -> (Point, Vec2) {
84 let len = self.length();
85 let (Some(&first), Some(&last)) = (self.pts.first(), self.pts.last()) else {
86 return (Point::ZERO, Vec2::new(1.0, 0.0));
87 };
88 if self.pts.len() < 2 || len <= 1e-9 {
89 return (first, Vec2::new(1.0, 0.0));
90 }
91 let s = if s.is_finite() { s } else { 0.0 };
92 let s = if self.closed { s.rem_euclid(len) } else { s };
93 let seg = |i: usize| -> (Point, Point, f64) {
94 let a = self.pts.get(i).copied().unwrap_or(first);
95 let b = self.pts.get(i + 1).copied().unwrap_or(last);
96 (a, b, self.cum.get(i).copied().unwrap_or(0.0))
97 };
98 let n = self.pts.len();
99 let i = if s <= 0.0 {
100 0
101 } else if s >= len {
102 n - 2
103 } else {
104 self.cum
105 .partition_point(|c| *c <= s)
106 .saturating_sub(1)
107 .min(n - 2)
108 };
109 let (a, b, c0) = seg(i);
110 let d = b - a;
111 let l = d.hypot();
112 let t = if l > 1e-12 {
113 d / l
114 } else {
115 Vec2::new(1.0, 0.0)
116 };
117 (a + t * (s - c0), t)
118 }
119
120 pub fn offset(&self, d: f64) -> Guide {
123 let n = self.pts.len();
124 if n < 2 || !d.is_finite() || d == 0.0 {
125 return self.clone();
126 }
127 let unit = |i: usize| -> Vec2 {
128 let (a, b) = (
129 self.pts.get(i).copied().unwrap_or(Point::ZERO),
130 self.pts.get(i + 1).copied().unwrap_or(Point::ZERO),
131 );
132 let v = b - a;
133 let l = v.hypot();
134 if l > 1e-12 { v / l } else { Vec2::ZERO }
135 };
136 let pts = (0..n).map(|i| {
137 let before = if i > 0 {
138 unit(i - 1)
139 } else if self.closed {
140 unit(n - 2)
141 } else {
142 Vec2::ZERO
143 };
144 let after = if i + 1 < n {
145 unit(i)
146 } else if self.closed {
147 unit(0)
148 } else {
149 Vec2::ZERO
150 };
151 let t = before + after;
152 let l = t.hypot();
153 let t = if l > 1e-12 {
154 t / l
155 } else {
156 Vec2::new(1.0, 0.0)
157 };
158 self.pts.get(i).copied().unwrap_or(Point::ZERO) + Vec2::new(t.y, -t.x) * d
159 });
160 Guide::from_points(pts.collect::<Vec<_>>(), self.closed)
161 }
162
163 pub fn at_frac(&self, t: f64) -> Point {
165 self.at(t * self.length()).0
166 }
167}
168
169#[derive(Clone, Copy, Debug, PartialEq, Eq)]
171pub enum LineAnchor {
172 Last,
174 First,
176 Middle,
178}
179
180#[derive(Clone, Debug)]
181pub enum Band {
182 Fill { top: Guide, bottom: Guide },
184 Follow { path: Guide, anchor: LineAnchor },
186}
187
188impl Band {
189 pub fn length(&self) -> f64 {
191 match self {
192 Band::Fill { top, bottom } => (top.length() + bottom.length()) / 2.0,
193 Band::Follow { path, .. } => path.length(),
194 }
195 }
196}
197
198#[derive(Clone, Debug)]
200pub struct Warp {
201 pub bands: Vec<Band>,
202}
203
204pub fn is_follow(name: &str) -> bool {
206 matches!(
207 name,
208 "textArchUp" | "textArchDown" | "textCircle" | "textButton"
209 )
210}
211
212pub fn adjust_defaults(name: &str) -> &'static [f64] {
214 match name {
215 "textPlain" => &[50_000.0],
216 "textStop" => &[25_000.0],
217 "textTriangle" | "textTriangleInverted" => &[50_000.0],
218 "textChevron" => &[25_000.0],
219 "textChevronInverted" => &[75_000.0],
220 "textRingInside" | "textRingOutside" => &[60_000.0],
221 "textArchUp" | "textCircle" | "textButton" => &[10_800_000.0],
222 "textArchDown" => &[0.0],
223 "textArchUpPour" | "textCirclePour" | "textButtonPour" => &[10_800_000.0, 50_000.0],
224 "textArchDownPour" => &[0.0, 25_000.0],
225 "textCurveUp" | "textCurveDown" => &[45_977.0],
226 "textCanUp" => &[85_000.0],
227 "textCanDown" => &[14_286.0],
228 "textWave1" | "textWave2" => &[12_500.0, 0.0],
229 "textDoubleWave1" | "textWave4" => &[6_250.0, 0.0],
230 "textInflate" | "textDeflate" => &[18_750.0],
231 "textInflateBottom" => &[60_000.0],
232 "textDeflateBottom" | "textDeflateTop" => &[50_000.0],
233 "textInflateTop" => &[40_000.0],
234 "textDeflateInflate" => &[35_000.0],
235 "textDeflateInflateDeflate" => &[25_000.0],
236 "textFadeRight" | "textFadeLeft" | "textFadeUp" | "textFadeDown" => &[33_333.0],
237 "textSlantUp" => &[55_556.0],
238 "textSlantDown" => &[44_444.0],
239 "textCascadeUp" | "textCascadeDown" => &[44_444.0],
240 _ => &[],
241 }
242}
243
244pub fn adjust_count(name: &str) -> usize {
246 adjust_defaults(name).len()
247}
248
249pub fn is_supported(name: &str) -> bool {
251 !adjust_defaults(name).is_empty()
252}
253
254#[derive(Clone, Copy, Debug)]
256enum Seg {
257 M(f64, f64),
258 L(f64, f64),
259 Q(f64, f64, f64, f64),
260 C(f64, f64, f64, f64, f64, f64),
261 A(f64, f64, f64, f64, f64, f64),
263}
264
265enum Spec {
266 Fill(Vec<Seg>, Vec<Seg>),
267 Follow(Vec<Seg>, LineAnchor),
268}
269
270struct Adj<'a> {
271 raw: &'a [f64],
272 defaults: &'static [f64],
273}
274
275impl Adj<'_> {
276 fn raw(&self, i: usize) -> f64 {
277 self.raw
278 .get(i)
279 .copied()
280 .filter(|v| v.is_finite())
281 .or_else(|| self.defaults.get(i).copied())
282 .unwrap_or(0.0)
283 }
284 fn frac(&self, i: usize, lo: f64, hi: f64) -> f64 {
286 (self.raw(i) / 100_000.0).clamp(lo, hi)
287 }
288 fn angle(&self, i: usize) -> f64 {
290 (self.raw(i) / 60_000.0).rem_euclid(360.0)
291 }
292}
293
294fn line(pts: &[(f64, f64)]) -> Vec<Seg> {
295 pts.iter()
296 .enumerate()
297 .map(|(i, &(x, y))| if i == 0 { Seg::M(x, y) } else { Seg::L(x, y) })
298 .collect()
299}
300
301fn quad(y0: f64, c: f64) -> Vec<Seg> {
303 vec![Seg::M(0.0, y0), Seg::Q(0.5, c, 1.0, y0)]
304}
305
306fn turn(deg: f64) -> f64 {
308 let d = deg.rem_euclid(360.0);
309 if d < 1e-9 { 360.0 } else { d }
310}
311
312fn ellipse(s: f64, start: f64, sweep: f64) -> Vec<Seg> {
314 vec![Seg::A(0.5, 0.5, 0.5 * s, 0.5 * s, start, sweep)]
315}
316
317fn arch_up(a: f64) -> (f64, f64) {
319 (a, turn(2.0 * (270.0 - a)))
320}
321
322fn arch_down(a: f64) -> (f64, f64) {
324 (180.0 - a, -turn(180.0 - 2.0 * a))
325}
326
327fn circle(a: f64) -> (f64, f64) {
329 (a, turn(2.0 * (360.0 - a)))
330}
331
332fn wave(out: &mut Vec<Seg>, x0: f64, x1: f64, y: f64, k: f64) {
334 let l = x1 - x0;
335 out.push(Seg::C(
336 x0 + l / 3.0,
337 y - k,
338 x0 + 2.0 * l / 3.0,
339 y + k,
340 x1,
341 y,
342 ));
343}
344
345fn waves(a: f64, shift: f64, periods: usize, up_first: bool) -> Spec {
347 let d = 2.0 * shift;
348 let (top_x, bot_x) = if d >= 0.0 {
349 ((0.0, 1.0 - d), (d, 1.0))
350 } else {
351 ((-d, 1.0), (0.0, 1.0 + d))
352 };
353 let k = if up_first {
354 a * 10.0 / 3.0
355 } else {
356 -a * 10.0 / 3.0
357 };
358 let guide = |(x0, x1): (f64, f64), y: f64| {
359 let mut v = vec![Seg::M(x0, y)];
360 let n = periods.max(1) as f64;
361 for i in 0..periods.max(1) {
362 let f = i as f64;
363 wave(
364 &mut v,
365 x0 + (x1 - x0) * f / n,
366 x0 + (x1 - x0) * (f + 1.0) / n,
367 y,
368 k,
369 );
370 }
371 v
372 };
373 Spec::Fill(guide(top_x, a), guide(bot_x, 1.0 - a))
374}
375
376fn spec(name: &str, a: &Adj) -> Option<Vec<Spec>> {
377 use Seg::*;
378 let fill = |t: Vec<Seg>, b: Vec<Seg>| vec![Spec::Fill(t, b)];
379 let top = || line(&[(0.0, 0.0), (1.0, 0.0)]);
380 let bottom = || line(&[(0.0, 1.0), (1.0, 1.0)]);
381 Some(match name {
382 "textPlain" => {
383 let d = 2.0 * (a.frac(0, 0.3, 0.7) - 0.5);
384 if d < 0.0 {
385 fill(
386 line(&[(-d, 0.0), (1.0, 0.0)]),
387 line(&[(0.0, 1.0), (1.0 + d, 1.0)]),
388 )
389 } else {
390 fill(
391 line(&[(0.0, 0.0), (1.0 - d, 0.0)]),
392 line(&[(d, 1.0), (1.0, 1.0)]),
393 )
394 }
395 }
396 "textStop" => {
397 let y = a.frac(0, 0.14286, 0.5);
398 let t = 1.0 / 3.0;
399 fill(
400 line(&[(0.0, y), (t, 0.0), (1.0 - t, 0.0), (1.0, y)]),
401 line(&[(0.0, 1.0 - y), (t, 1.0), (1.0 - t, 1.0), (1.0, 1.0 - y)]),
402 )
403 }
404 "textTriangle" => {
405 let y = a.frac(0, 0.0, 1.0);
406 fill(line(&[(0.0, y), (0.5, 0.0), (1.0, y)]), bottom())
407 }
408 "textTriangleInverted" => {
409 let y = a.frac(0, 0.0, 1.0);
410 fill(top(), line(&[(0.0, y), (0.5, 1.0), (1.0, y)]))
411 }
412 "textChevron" => {
413 let y = a.frac(0, 0.0, 0.5);
414 fill(
415 line(&[(0.0, y), (0.5, 0.0), (1.0, y)]),
416 line(&[(0.0, 1.0), (0.5, 1.0 - y), (1.0, 1.0)]),
417 )
418 }
419 "textChevronInverted" => {
420 let y = a.frac(0, 0.5, 1.0);
421 fill(
422 line(&[(0.0, 0.0), (0.5, 1.0 - y), (1.0, 0.0)]),
423 line(&[(0.0, y), (0.5, 1.0), (1.0, y)]),
424 )
425 }
426 "textFadeRight" => {
427 let y = a.frac(0, 0.0, 0.49999);
428 fill(
429 line(&[(0.0, 0.0), (1.0, y)]),
430 line(&[(0.0, 1.0), (1.0, 1.0 - y)]),
431 )
432 }
433 "textFadeLeft" => {
434 let y = a.frac(0, 0.0, 0.49999);
435 fill(
436 line(&[(0.0, y), (1.0, 0.0)]),
437 line(&[(0.0, 1.0 - y), (1.0, 1.0)]),
438 )
439 }
440 "textFadeUp" => {
441 let x = a.frac(0, 0.0, 0.49999);
442 fill(line(&[(x, 0.0), (1.0 - x, 0.0)]), bottom())
443 }
444 "textFadeDown" => {
445 let x = a.frac(0, 0.0, 0.49999);
446 fill(top(), line(&[(x, 1.0), (1.0 - x, 1.0)]))
447 }
448 "textSlantUp" => {
449 let y = a.frac(0, 0.44444, 1.0);
450 fill(
451 line(&[(0.0, y), (1.0, 0.0)]),
452 line(&[(0.0, 1.0), (1.0, 1.0 - y)]),
453 )
454 }
455 "textSlantDown" => {
456 let y = a.frac(0, 0.0, 0.55556);
457 fill(
458 line(&[(0.0, 0.0), (1.0, 1.0 - y)]),
459 line(&[(0.0, y), (1.0, 1.0)]),
460 )
461 }
462 "textCascadeUp" => {
463 let y = a.frac(0, 0.0, 1.0);
464 fill(
465 line(&[(0.0, (1.0 - y) / 4.0), (1.0, 0.0)]),
466 line(&[(0.0, 1.0), (1.0, y)]),
467 )
468 }
469 "textCascadeDown" => {
470 let y = a.frac(0, 0.0, 1.0);
471 fill(
472 line(&[(0.0, 0.0), (1.0, (1.0 - y) / 4.0)]),
473 line(&[(0.0, y), (1.0, 1.0)]),
474 )
475 }
476 "textInflate" => {
477 let y = a.frac(0, 0.0, 0.2);
478 fill(quad(y, -y), quad(1.0 - y, 1.0 + y))
479 }
480 "textDeflate" => {
481 let y = a.frac(0, 0.0, 0.375);
482 fill(quad(0.0, 2.0 * y), quad(1.0, 1.0 - 2.0 * y))
483 }
484 "textInflateBottom" => {
485 let y = a.frac(0, 0.6, 1.0);
486 fill(top(), quad(y, 1.0))
487 }
488 "textDeflateBottom" => {
489 let y = a.frac(0, 0.0625, 1.0);
490 fill(top(), quad(1.0, 2.0 * y - 1.0))
491 }
492 "textInflateTop" => {
493 let y = a.frac(0, 0.0, 0.5);
494 fill(quad(y, 0.0), bottom())
495 }
496 "textDeflateTop" => {
497 let y = a.frac(0, 0.0, 0.9375);
498 fill(quad(0.0, 2.0 * y), bottom())
499 }
500 "textDeflateInflate" => {
501 let y = a.frac(0, 0.05, 0.95);
504 let g = 0.05;
505 vec![
506 Spec::Fill(top(), quad(0.5 - g, 2.0 * y - 0.5 - g)),
507 Spec::Fill(quad(0.5 + g, 2.0 * y - 0.5 + g), bottom()),
508 ]
509 }
510 "textDeflateInflateDeflate" => {
511 let y = a.frac(0, 0.01, 1.0 / 3.0);
512 let g = 1.0 / 30.0;
513 let (t1, t2) = (1.0 / 3.0, 2.0 / 3.0);
514 let (c1, c2) = (2.0 * y - t1, 2.0 * (1.0 - y) - t2);
515 vec![
516 Spec::Fill(top(), quad(t1 - g, c1 - g)),
517 Spec::Fill(quad(t1 + g, c1 + g), quad(t2 - g, c2 - g)),
518 Spec::Fill(quad(t2 + g, c2 + g), bottom()),
519 ]
520 }
521 "textCurveUp" | "textCurveDown" => {
522 let y = a.frac(0, 0.0, 0.56818);
523 let b = vec![
524 M(0.0, 1.0 - 3.0 * y / 8.0),
525 C(
526 1.0 / 3.0,
527 1.0 + y / 8.0,
528 2.0 / 3.0,
529 1.0 + y / 8.0,
530 1.0,
531 1.0 - 3.0 * y / 8.0,
532 ),
533 ];
534 let t = if name == "textCurveUp" {
535 vec![
536 M(0.0, y),
537 C(1.0 / 3.0, 1.25 * y, 2.0 / 3.0, 0.75 * y, 1.0, 0.0),
538 ]
539 } else {
540 vec![
541 M(0.0, 0.0),
542 C(1.0 / 3.0, 0.75 * y, 2.0 / 3.0, 1.25 * y, 1.0, y),
543 ]
544 };
545 fill(t, b)
546 }
547 "textCanUp" => {
548 let r = 1.0 - a.frac(0, 0.5, 1.0);
549 fill(
550 vec![A(0.5, r, 0.5, r, 180.0, 180.0)],
551 vec![A(0.5, 1.0, 0.5, r, 180.0, 180.0)],
552 )
553 }
554 "textCanDown" => {
555 let r = a.frac(0, 0.0, 0.5);
556 fill(
557 vec![A(0.5, 0.0, 0.5, r, 180.0, -180.0)],
558 vec![A(0.5, 1.0 - r, 0.5, r, 180.0, -180.0)],
559 )
560 }
561 "textRingInside" | "textRingOutside" => {
562 let h = a.frac(0, 0.5, 0.99);
563 let sweep = if name == "textRingInside" {
564 360.0
565 } else {
566 -360.0
567 };
568 fill(
569 vec![A(0.5, h / 2.0, 0.5, h / 2.0, 180.0, sweep)],
570 vec![A(0.5, 1.0 - h / 2.0, 0.5, h / 2.0, 180.0, sweep)],
571 )
572 }
573 "textWave1" => vec![waves(a.frac(0, 0.0, 0.2), a.frac(1, -0.1, 0.1), 1, true)],
574 "textWave2" => vec![waves(a.frac(0, 0.0, 0.2), a.frac(1, -0.1, 0.1), 1, false)],
575 "textDoubleWave1" => vec![waves(a.frac(0, 0.0, 0.125), a.frac(1, -0.1, 0.1), 2, true)],
576 "textWave4" => vec![waves(a.frac(0, 0.0, 0.125), a.frac(1, -0.1, 0.1), 2, false)],
577 "textArchUp" => {
578 let (s, w) = arch_up(a.angle(0));
579 vec![Spec::Follow(ellipse(1.0, s, w), LineAnchor::Last)]
580 }
581 "textArchDown" => {
582 let (s, w) = arch_down(a.angle(0));
583 vec![Spec::Follow(ellipse(1.0, s, w), LineAnchor::First)]
584 }
585 "textCircle" => {
586 let (s, w) = circle(a.angle(0));
587 vec![Spec::Follow(ellipse(1.0, s, w), LineAnchor::Last)]
588 }
589 "textButton" => {
590 let t = a.angle(0);
591 let (s, w) = arch_up(t);
592 let half = 0.5 * t.to_radians().cos().abs();
593 vec![
594 Spec::Follow(ellipse(1.0, s, w), LineAnchor::Last),
595 Spec::Follow(
596 line(&[(0.5 - half, 0.5), (0.5 + half, 0.5)]),
597 LineAnchor::Middle,
598 ),
599 Spec::Follow(ellipse(1.0, 360.0 - s, -w), LineAnchor::First),
600 ]
601 }
602 "textArchUpPour" => {
603 let (s, w) = arch_up(a.angle(0));
604 let r = a.frac(1, 0.0, 0.99);
605 fill(ellipse(1.0, s, w), ellipse(r, s, w))
606 }
607 "textArchDownPour" => {
608 let (s, w) = arch_down(a.angle(0));
609 let r = a.frac(1, 0.0, 0.99);
610 fill(ellipse(r, s, w), ellipse(1.0, s, w))
611 }
612 "textCirclePour" => {
613 let (s, w) = circle(a.angle(0));
614 let r = a.frac(1, 0.0, 0.99);
615 fill(ellipse(1.0, s, w), ellipse(r, s, w))
616 }
617 "textButtonPour" => {
618 let (s, w) = arch_up(a.angle(0));
619 let r = a.frac(1, 0.0, 0.99);
620 let (dy, half) = (0.25 * r, 0.25 * r * 3f64.sqrt());
623 vec![
624 Spec::Fill(ellipse(1.0, s, w), ellipse(r, s, w)),
625 Spec::Fill(
626 line(&[(0.5 - half, 0.5 - dy), (0.5 + half, 0.5 - dy)]),
627 line(&[(0.5 - half, 0.5 + dy), (0.5 + half, 0.5 + dy)]),
628 ),
629 Spec::Fill(ellipse(r, 360.0 - s, -w), ellipse(1.0, 360.0 - s, -w)),
630 ]
631 }
632 _ => return None,
633 })
634}
635
636fn guide(segs: &[Seg], r: Rect) -> Guide {
638 let mut p = BezPath::new();
639 let mut started = false;
640 let mut closed = false;
641 for s in segs {
642 match *s {
643 Seg::M(x, y) => {
644 p.move_to((x, y));
645 started = true;
646 }
647 Seg::L(x, y) => {
648 if started {
649 p.line_to((x, y))
650 } else {
651 p.move_to((x, y))
652 }
653 started = true;
654 }
655 Seg::Q(cx, cy, x, y) => {
656 if started {
657 p.quad_to((cx, cy), (x, y));
658 }
659 }
660 Seg::C(x1, y1, x2, y2, x, y) => {
661 if started {
662 p.curve_to((x1, y1), (x2, y2), (x, y));
663 }
664 }
665 Seg::A(cx, cy, rx, ry, start, sweep) => {
666 let arc = Arc::new(
667 (cx, cy),
668 (rx.max(0.0), ry.max(0.0)),
669 start.to_radians(),
670 sweep.to_radians(),
671 0.0,
672 );
673 let s0 = Point::new(
674 cx + rx * start.to_radians().cos(),
675 cy + ry * start.to_radians().sin(),
676 );
677 if started {
678 p.line_to(s0);
679 } else {
680 p.move_to(s0);
681 }
682 started = true;
683 for el in arc.append_iter(1e-5) {
684 p.push(el);
685 }
686 closed = sweep.abs() >= 359.999;
687 }
688 }
689 }
690 let m = kurbo::Affine::new([r.width(), 0.0, 0.0, r.height(), r.x0, r.y0]);
691 p.apply_affine(m);
692 Guide::from_path(&p, closed)
693}
694
695pub fn warp(name: &str, adj: &[f64], rect: Rect) -> Option<Warp> {
698 if ![rect.x0, rect.y0, rect.x1, rect.y1]
699 .iter()
700 .all(|v| v.is_finite())
701 {
702 return None;
703 }
704 let r = Rect::new(
705 rect.x0.min(rect.x1),
706 rect.y0.min(rect.y1),
707 rect.x0.max(rect.x1),
708 rect.y0.max(rect.y1),
709 );
710 let a = Adj {
711 raw: adj,
712 defaults: adjust_defaults(name),
713 };
714 let specs = spec(name, &a)?;
715 let bands = specs
716 .into_iter()
717 .map(|s| match s {
718 Spec::Fill(t, b) => Band::Fill {
719 top: guide(&t, r),
720 bottom: guide(&b, r),
721 },
722 Spec::Follow(p, anchor) => Band::Follow {
723 path: guide(&p, r),
724 anchor,
725 },
726 })
727 .collect();
728 Some(Warp { bands })
729}
730
731pub fn band_of_line(line: usize, lines: usize, bands: usize) -> usize {
734 if bands <= 1 || lines == 0 {
735 return 0;
736 }
737 (0..bands)
739 .rev()
740 .find(|&b| (b * lines).div_ceil(bands) <= line)
741 .unwrap_or(0)
742}
743
744#[derive(Clone, Copy, Debug)]
746pub struct WarpLine {
747 pub ink: Option<Rect>,
749 pub extent: (f64, f64),
751 pub ref_y: f64,
753}
754
755#[derive(Clone, Copy, Debug)]
756enum LineMap {
757 Fill {
758 band: usize,
759 cx: f64,
760 k: f64,
761 len: f64,
762 y0: f64,
763 y1: f64,
764 },
765 Follow {
767 band: usize,
768 guide: Option<usize>,
769 e0: f64,
770 s0: f64,
771 k: f64,
772 ref_y: f64,
773 },
774}
775
776#[derive(Clone, Debug)]
778pub struct Mapper<'a> {
779 warp: &'a Warp,
780 lines: Vec<LineMap>,
781 offsets: Vec<Guide>,
782 zoom: f64,
783}
784
785impl Warp {
786 pub fn mapper(&self, lines: &[WarpLine], inner: Rect) -> Mapper<'_> {
789 let nb = self.bands.len().max(1);
790 let n = lines.len();
791 let band_of: Vec<usize> = (0..n).map(|i| band_of_line(i, n, nb).min(nb - 1)).collect();
792 let ink_all = lines.iter().filter_map(|l| l.ink).reduce(|a, b| a.union(b));
794 let cx = ink_all.map(|r| r.center().x).unwrap_or(0.0);
795 let mut k_fill = f64::INFINITY;
796 let mut zoom: f64 = 1.0;
797 let mut band_y: Vec<Option<(f64, f64)>> = vec![None; nb];
798 for (i, l) in lines.iter().enumerate() {
799 let Some(r) = l.ink else { continue };
800 let b = band_of.get(i).copied().unwrap_or(0);
801 if let Some(slot) = band_y.get_mut(b) {
802 *slot = Some(match *slot {
803 Some((y0, y1)) => (y0.min(r.y0), y1.max(r.y1)),
804 None => (r.y0, r.y1),
805 });
806 }
807 }
808 let tallest = band_y
811 .iter()
812 .flatten()
813 .map(|(y0, y1)| y1 - y0)
814 .fold(0.0, f64::max);
815 let band_y: Vec<Option<(f64, f64)>> = band_y
816 .iter()
817 .map(|r| r.map(|(y0, y1)| ((y0 + y1 - tallest) / 2.0, (y0 + y1 + tallest) / 2.0)))
818 .collect();
819 for (b, band) in self.bands.iter().enumerate() {
820 if let Band::Fill { top, bottom } = band {
821 let half = lines
822 .iter()
823 .enumerate()
824 .filter(|(i, _)| band_of.get(*i) == Some(&b))
825 .filter_map(|(_, l)| l.ink)
826 .map(|r| (r.x0 - cx).abs().max((r.x1 - cx).abs()))
827 .fold(0.0, f64::max);
828 if half > 1e-9 {
829 k_fill = k_fill.min(band.length() / (2.0 * half));
830 }
831 if let Some(Some((y0, y1))) = band_y.get(b) {
832 let thick = [0.0, 0.25, 0.5, 0.75, 1.0]
833 .iter()
834 .map(|&u| (top.at_frac(u) - bottom.at_frac(u)).hypot())
835 .fold(0.0, f64::max);
836 if y1 - y0 > 1e-9 {
837 zoom = zoom.max(thick / (y1 - y0));
838 }
839 }
840 }
841 }
842 if !k_fill.is_finite() {
843 k_fill = 1.0;
844 }
845 zoom = zoom.max(k_fill);
846 let mut maps = Vec::with_capacity(n);
847 let mut offsets = Vec::new();
848 for (i, l) in lines.iter().enumerate() {
849 let b = band_of.get(i).copied().unwrap_or(0);
850 let in_band = |j: &usize| band_of.get(*j) == Some(&b);
851 match self.bands.get(b) {
852 Some(Band::Fill { .. }) | None => {
853 let (y0, y1) = band_y.get(b).copied().flatten().unwrap_or((0.0, 1.0));
854 let len = self.bands.get(b).map(Band::length).unwrap_or(0.0);
855 maps.push(LineMap::Fill {
856 band: b,
857 cx,
858 k: k_fill,
859 len,
860 y0,
861 y1,
862 });
863 }
864 Some(Band::Follow { path, anchor }) => {
865 let len = path.length();
866 let widest = (0..n)
867 .filter(in_band)
868 .filter_map(|j| lines.get(j))
869 .map(|l| l.extent.1 - l.extent.0)
870 .fold(0.0, f64::max);
871 let k = if widest > len && widest > 1e-9 {
872 len / widest
873 } else {
874 1.0
875 };
876 let members: Vec<&WarpLine> = (0..n)
877 .filter(in_band)
878 .filter_map(|j| lines.get(j))
879 .collect();
880 let ref_y = match anchor {
881 LineAnchor::Last => members.last().map(|l| l.ref_y),
882 LineAnchor::First => members.first().map(|l| l.ref_y),
883 LineAnchor::Middle => match (members.first(), members.last()) {
884 (Some(a), Some(z)) => Some((a.ref_y + z.ref_y) / 2.0),
885 _ => None,
886 },
887 }
888 .unwrap_or(l.ref_y);
889 let d = (ref_y - l.ref_y) * k;
891 let (guide, len) = if d.abs() > 1e-6 && d.is_finite() && offsets.len() < 4096 {
892 let g = path.offset(d);
893 let len = g.length();
894 offsets.push(g);
895 (Some(offsets.len() - 1), len)
896 } else {
897 (None, len)
898 };
899 let (e0, e1) = l.extent;
900 let w = (e1 - e0).max(0.0) * k;
901 let (gl, gr) = ((e0 - inner.x0).max(0.0), (inner.x1 - e1).max(0.0));
902 let f = if gl + gr > 1e-9 { gl / (gl + gr) } else { 0.5 };
903 let s0 = f * (len - w);
904 maps.push(LineMap::Follow {
905 band: b,
906 guide,
907 e0,
908 s0,
909 k,
910 ref_y: l.ref_y,
911 });
912 }
913 }
914 }
915 Mapper {
916 warp: self,
917 lines: maps,
918 offsets,
919 zoom: if zoom.is_finite() {
920 zoom.clamp(1.0, 1.0e4)
921 } else {
922 1.0
923 },
924 }
925 }
926}
927
928impl Mapper<'_> {
929 pub fn zoom(&self) -> f64 {
931 self.zoom
932 }
933
934 pub fn map(&self, line: usize, p: Point) -> Point {
936 let out = match self.lines.get(line) {
937 Some(&LineMap::Fill {
938 band,
939 cx,
940 k,
941 len,
942 y0,
943 y1,
944 }) => {
945 let Some(Band::Fill { top, bottom }) = self.warp.bands.get(band) else {
946 return p;
947 };
948 let u = if len > 1e-9 {
949 0.5 + (p.x - cx) * k / len
950 } else {
951 0.5
952 };
953 let v = if y1 - y0 > 1e-9 {
954 (p.y - y0) / (y1 - y0)
955 } else {
956 0.5
957 };
958 let (t, b) = (top.at_frac(u), bottom.at_frac(u));
959 t.lerp(b, v)
960 }
961 Some(&LineMap::Follow {
962 band,
963 guide,
964 e0,
965 s0,
966 k,
967 ref_y,
968 }) => {
969 let path = match (
970 guide.and_then(|g| self.offsets.get(g)),
971 self.warp.bands.get(band),
972 ) {
973 (Some(g), _) => g,
974 (None, Some(Band::Follow { path, .. })) => path,
975 _ => return p,
976 };
977 let s = s0 + (p.x - e0) * k;
978 let d = (ref_y - p.y) * k;
979 let (q, t) = path.at(s);
980 q + Vec2::new(t.y, -t.x) * d
981 }
982 None => p,
983 };
984 if out.x.is_finite() && out.y.is_finite() {
985 out
986 } else {
987 p
988 }
989 }
990}
991
992#[cfg(test)]
993mod tests;