1use crate::geom::{Rect, Vec2};
15use crate::retain::Kept;
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub struct PathId(pub u32);
20
21#[derive(Clone, Copy, Debug, PartialEq)]
24pub enum PathOp {
25 MoveTo(Vec2),
27 LineTo(Vec2),
29 QuadTo(Vec2, Vec2),
31 CubicTo(Vec2, Vec2, Vec2),
33 ArcTo {
36 rx: f32,
37 ry: f32,
38 rotation: f32,
39 large: bool,
40 sweep: bool,
41 to: Vec2,
42 },
43 Close,
45}
46
47pub const OP_MOVE: f32 = 0.0;
49pub const OP_LINE: f32 = 1.0;
50pub const OP_QUAD: f32 = 2.0;
51pub const OP_CUBIC: f32 = 3.0;
52pub const OP_ARC: f32 = 4.0;
53pub const OP_CLOSE: f32 = 5.0;
54
55impl PathOp {
56 pub fn operands(code: f32) -> Option<usize> {
59 if code.fract() != 0.0 {
62 return None;
63 }
64 match code as i32 {
65 0 | 1 => Some(2),
66 2 => Some(4),
67 3 => Some(6),
68 4 => Some(7),
69 5 => Some(0),
70 _ => None,
71 }
72 }
73
74 pub fn is_finite(&self) -> bool {
76 let ok = |p: Vec2| p.x.is_finite() && p.y.is_finite();
77 match *self {
78 PathOp::MoveTo(p) | PathOp::LineTo(p) => ok(p),
79 PathOp::QuadTo(c, p) => ok(c) && ok(p),
80 PathOp::CubicTo(a, b, p) => ok(a) && ok(b) && ok(p),
81 PathOp::ArcTo {
82 rx,
83 ry,
84 rotation,
85 to,
86 ..
87 } => rx.is_finite() && ry.is_finite() && rotation.is_finite() && ok(to),
88 PathOp::Close => true,
89 }
90 }
91
92 pub fn write(&self, out: &mut Vec<f32>) {
94 match *self {
95 PathOp::MoveTo(p) => out.extend_from_slice(&[OP_MOVE, p.x, p.y]),
96 PathOp::LineTo(p) => out.extend_from_slice(&[OP_LINE, p.x, p.y]),
97 PathOp::QuadTo(c, p) => out.extend_from_slice(&[OP_QUAD, c.x, c.y, p.x, p.y]),
98 PathOp::CubicTo(a, b, p) => {
99 out.extend_from_slice(&[OP_CUBIC, a.x, a.y, b.x, b.y, p.x, p.y])
100 }
101 PathOp::ArcTo {
102 rx,
103 ry,
104 rotation,
105 large,
106 sweep,
107 to,
108 } => out.extend_from_slice(&[
109 OP_ARC,
110 rx,
111 ry,
112 rotation,
113 f32::from(large),
114 f32::from(sweep),
115 to.x,
116 to.y,
117 ]),
118 PathOp::Close => out.push(OP_CLOSE),
119 }
120 }
121
122 fn placed(&self, scale: f32, off: Vec2) -> PathOp {
125 let s = |p: Vec2| Vec2::new(p.x * scale + off.x, p.y * scale + off.y);
126 match *self {
127 PathOp::MoveTo(p) => PathOp::MoveTo(s(p)),
128 PathOp::LineTo(p) => PathOp::LineTo(s(p)),
129 PathOp::QuadTo(c, p) => PathOp::QuadTo(s(c), s(p)),
130 PathOp::CubicTo(a, b, p) => PathOp::CubicTo(s(a), s(b), s(p)),
131 PathOp::ArcTo {
132 rx,
133 ry,
134 rotation,
135 large,
136 sweep,
137 to,
138 } => PathOp::ArcTo {
139 rx: rx * scale,
140 ry: ry * scale,
141 rotation,
142 large,
143 sweep,
144 to: s(to),
145 },
146 PathOp::Close => PathOp::Close,
147 }
148 }
149
150 fn shifted(&self, d: Vec2) -> PathOp {
152 let s = |p: Vec2| Vec2::new(p.x + d.x, p.y + d.y);
153 match *self {
154 PathOp::MoveTo(p) => PathOp::MoveTo(s(p)),
155 PathOp::LineTo(p) => PathOp::LineTo(s(p)),
156 PathOp::QuadTo(c, p) => PathOp::QuadTo(s(c), s(p)),
157 PathOp::CubicTo(a, b, p) => PathOp::CubicTo(s(a), s(b), s(p)),
158 PathOp::ArcTo {
159 rx,
160 ry,
161 rotation,
162 large,
163 sweep,
164 to,
165 } => PathOp::ArcTo {
166 rx,
167 ry,
168 rotation,
169 large,
170 sweep,
171 to: s(to),
172 },
173 PathOp::Close => PathOp::Close,
174 }
175 }
176}
177
178#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
180pub enum FillRule {
181 #[default]
184 NonZero,
185 EvenOdd,
188}
189
190impl FillRule {
191 pub const ALL: &[&str] = &["nonzero", "evenodd"];
193
194 pub fn from_index(i: usize) -> Self {
195 match i {
196 1 => FillRule::EvenOdd,
197 _ => FillRule::NonZero,
198 }
199 }
200
201 pub fn index(self) -> usize {
202 match self {
203 FillRule::NonZero => 0,
204 FillRule::EvenOdd => 1,
205 }
206 }
207
208 pub fn parse(s: &str) -> Option<Self> {
209 match s {
210 "nonzero" => Some(FillRule::NonZero),
211 "evenodd" => Some(FillRule::EvenOdd),
212 _ => None,
213 }
214 }
215}
216
217#[derive(Clone, Copy, Debug, Default, PartialEq)]
225pub struct Turn {
226 pub turns: f32,
227 pub pivot: Option<Vec2>,
228}
229
230impl Turn {
231 pub fn radians(self) -> f32 {
233 self.turns * std::f32::consts::TAU
234 }
235}
236
237pub fn turned(p: Vec2, c: Vec2, angle: f32) -> Vec2 {
239 let (sin, cos) = angle.sin_cos();
240 let (x, y) = (p.x - c.x, p.y - c.y);
241 Vec2::new(c.x + x * cos - y * sin, c.y + x * sin + y * cos)
242}
243
244#[derive(Clone, Debug, PartialEq, Eq)]
246pub struct PathError {
247 pub at: usize,
248 pub what: &'static str,
249}
250
251impl std::fmt::Display for PathError {
252 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
253 write!(f, "{} at byte {}", self.what, self.at)
254 }
255}
256
257impl std::error::Error for PathError {}
258
259#[derive(Clone, Debug, PartialEq)]
263pub struct Path {
264 ops: Vec<PathOp>,
265 rule: FillRule,
266 stroke: Option<crate::line::Stroke>,
267 turn: Option<Turn>,
268}
269
270impl Default for Path {
271 fn default() -> Self {
272 Self::new()
273 }
274}
275
276impl Path {
277 pub fn new() -> Self {
278 Self {
279 ops: Vec::new(),
280 rule: FillRule::NonZero,
281 stroke: None,
282 turn: None,
283 }
284 }
285
286 pub fn from_ops(ops: Vec<PathOp>) -> Self {
288 Self {
289 ops,
290 rule: FillRule::NonZero,
291 stroke: None,
292 turn: None,
293 }
294 }
295
296 pub fn from_floats(f: &[f32]) -> Result<Self, PathError> {
298 let mut ops = Vec::new();
299 let mut i = 0;
300 while i < f.len() {
301 let code = f[i];
302 let Some(n) = PathOp::operands(code) else {
303 return Err(PathError {
304 at: i,
305 what: "an op code 0..=5",
306 });
307 };
308 let Some(a) = f.get(i + 1..i + 1 + n) else {
309 return Err(PathError {
310 at: i,
311 what: "the op's operands",
312 });
313 };
314 ops.push(match code as i32 {
315 0 => PathOp::MoveTo(Vec2::new(a[0], a[1])),
316 1 => PathOp::LineTo(Vec2::new(a[0], a[1])),
317 2 => PathOp::QuadTo(Vec2::new(a[0], a[1]), Vec2::new(a[2], a[3])),
318 3 => PathOp::CubicTo(
319 Vec2::new(a[0], a[1]),
320 Vec2::new(a[2], a[3]),
321 Vec2::new(a[4], a[5]),
322 ),
323 4 => PathOp::ArcTo {
324 rx: a[0],
325 ry: a[1],
326 rotation: a[2],
327 large: a[3] != 0.0,
328 sweep: a[4] != 0.0,
329 to: Vec2::new(a[5], a[6]),
330 },
331 _ => PathOp::Close,
332 });
333 i += 1 + n;
334 }
335 Ok(Self::from_ops(ops))
336 }
337
338 pub fn to_floats(&self) -> Vec<f32> {
340 let mut out = Vec::with_capacity(self.ops.len() * 3);
341 for op in &self.ops {
342 op.write(&mut out);
343 }
344 out
345 }
346
347 pub fn ops(&self) -> &[PathOp] {
348 &self.ops
349 }
350
351 pub fn into_ops(self) -> Vec<PathOp> {
352 self.ops
353 }
354
355 pub fn rule(&self) -> FillRule {
356 self.rule
357 }
358
359 pub fn stroke(&self) -> Option<crate::line::Stroke> {
360 self.stroke
361 }
362
363 pub fn turn(&self) -> Option<Turn> {
364 self.turn
365 }
366
367 pub fn rotated(mut self, turns: f32) -> Self {
372 self.turn.get_or_insert_default().turns = turns;
373 self
374 }
375
376 pub fn pivot(mut self, x: f32, y: f32) -> Self {
380 self.turn.get_or_insert_default().pivot = Some(Vec2::new(x, y));
381 self
382 }
383
384 pub fn fill_rule(mut self, rule: FillRule) -> Self {
385 self.rule = rule;
386 self
387 }
388
389 pub fn stroked(mut self, stroke: crate::line::Stroke) -> Self {
392 self.stroke = Some(stroke);
393 self
394 }
395
396 pub fn move_to(mut self, x: f32, y: f32) -> Self {
397 self.ops.push(PathOp::MoveTo(Vec2::new(x, y)));
398 self
399 }
400
401 pub fn line_to(mut self, x: f32, y: f32) -> Self {
402 self.ops.push(PathOp::LineTo(Vec2::new(x, y)));
403 self
404 }
405
406 pub fn quad_to(mut self, cx: f32, cy: f32, x: f32, y: f32) -> Self {
407 self.ops
408 .push(PathOp::QuadTo(Vec2::new(cx, cy), Vec2::new(x, y)));
409 self
410 }
411
412 pub fn cubic_to(mut self, c1x: f32, c1y: f32, c2x: f32, c2y: f32, x: f32, y: f32) -> Self {
413 self.ops.push(PathOp::CubicTo(
414 Vec2::new(c1x, c1y),
415 Vec2::new(c2x, c2y),
416 Vec2::new(x, y),
417 ));
418 self
419 }
420
421 #[allow(clippy::too_many_arguments)]
425 pub fn arc_to(
426 mut self,
427 rx: f32,
428 ry: f32,
429 rotation: f32,
430 large: bool,
431 sweep: bool,
432 x: f32,
433 y: f32,
434 ) -> Self {
435 self.ops.push(PathOp::ArcTo {
436 rx,
437 ry,
438 rotation,
439 large,
440 sweep,
441 to: Vec2::new(x, y),
442 });
443 self
444 }
445
446 pub fn close(mut self) -> Self {
447 self.ops.push(PathOp::Close);
448 self
449 }
450
451 pub fn sector(cx: f32, cy: f32, outer: f32, inner: f32, from: f32, sweep: f32) -> Self {
456 let tau = std::f32::consts::TAU;
457 let sweep = sweep.clamp(-1.0, 1.0);
458 let (a0, a1) = (from * tau, (from + sweep) * tau);
459 let at = |r: f32, a: f32| (cx + r * a.cos(), cy + r * a.sin());
460 let large = sweep.abs() > 0.5;
461 let cw = sweep >= 0.0;
462 if sweep.abs() >= 1.0 {
463 let mid = a0 + tau * 0.5;
466 let (ox, oy) = at(outer, a0);
467 let (mx, my) = at(outer, mid);
468 let mut p = Path::new()
469 .move_to(ox, oy)
470 .arc_to(outer, outer, 0.0, false, cw, mx, my)
471 .arc_to(outer, outer, 0.0, false, cw, ox, oy)
472 .close();
473 if inner > 0.0 {
474 let (ix, iy) = at(inner, a0);
475 let (jx, jy) = at(inner, mid);
476 p = p
477 .move_to(ix, iy)
478 .arc_to(inner, inner, 0.0, false, !cw, jx, jy)
479 .arc_to(inner, inner, 0.0, false, !cw, ix, iy)
480 .close();
481 }
482 return p;
483 }
484 let (ox0, oy0) = at(outer, a0);
485 let (ox1, oy1) = at(outer, a1);
486 let mut p = Path::new()
487 .move_to(ox0, oy0)
488 .arc_to(outer, outer, 0.0, large, cw, ox1, oy1);
489 if inner > 0.0 {
490 let (ix1, iy1) = at(inner, a1);
491 let (ix0, iy0) = at(inner, a0);
492 p = p
493 .line_to(ix1, iy1)
494 .arc_to(inner, inner, 0.0, large, !cw, ix0, iy0);
495 } else {
496 p = p.line_to(cx, cy);
497 }
498 p.close()
499 }
500
501 pub fn parse(d: &str) -> Result<Self, PathError> {
506 let b = d.as_bytes();
507 let mut i = 0;
508 let mut ops = Vec::new();
509 let mut cur = Vec2::ZERO;
510 let mut start = Vec2::ZERO;
511 let mut last_cubic: Option<Vec2> = None;
514 let mut last_quad: Option<Vec2> = None;
515 let mut cmd: Option<u8> = None;
516
517 fn skip_ws(b: &[u8], i: &mut usize) {
518 while *i < b.len() && (b[*i].is_ascii_whitespace() || b[*i] == b',') {
519 *i += 1;
520 }
521 }
522 fn number(b: &[u8], i: &mut usize) -> Result<f32, PathError> {
523 skip_ws(b, i);
524 let at = *i;
525 let mut j = at;
526 if j < b.len() && (b[j] == b'-' || b[j] == b'+') {
527 j += 1;
528 }
529 let digits = j;
530 while j < b.len() && b[j].is_ascii_digit() {
531 j += 1;
532 }
533 if j < b.len() && b[j] == b'.' {
534 j += 1;
535 while j < b.len() && b[j].is_ascii_digit() {
536 j += 1;
537 }
538 }
539 if j == digits || (j == digits + 1 && b[digits] == b'.') {
540 return Err(PathError {
541 at,
542 what: "a number",
543 });
544 }
545 if j < b.len() && (b[j] == b'e' || b[j] == b'E') {
546 let mut k = j + 1;
547 if k < b.len() && (b[k] == b'-' || b[k] == b'+') {
548 k += 1;
549 }
550 let e = k;
551 while k < b.len() && b[k].is_ascii_digit() {
552 k += 1;
553 }
554 if k > e {
555 j = k;
556 }
557 }
558 let s = std::str::from_utf8(&b[at..j]).map_err(|_| PathError {
559 at,
560 what: "a number",
561 })?;
562 *i = j;
563 s.parse::<f32>().map_err(|_| PathError {
564 at,
565 what: "a number",
566 })
567 }
568 fn flag(b: &[u8], i: &mut usize) -> Result<bool, PathError> {
569 skip_ws(b, i);
570 match b.get(*i) {
571 Some(b'0') => {
572 *i += 1;
573 Ok(false)
574 }
575 Some(b'1') => {
576 *i += 1;
577 Ok(true)
578 }
579 _ => Err(PathError {
580 at: *i,
581 what: "an arc flag (0 or 1)",
582 }),
583 }
584 }
585
586 loop {
587 skip_ws(b, &mut i);
588 if i >= b.len() {
589 break;
590 }
591 let c = b[i];
592 if c.is_ascii_alphabetic() {
593 cmd = Some(c);
594 i += 1;
595 if c == b'Z' || c == b'z' {
596 ops.push(PathOp::Close);
597 cur = start;
598 last_cubic = None;
599 last_quad = None;
600 continue;
601 }
602 } else if cmd.is_none() {
603 return Err(PathError {
604 at: i,
605 what: "a command letter",
606 });
607 }
608 let Some(c) = cmd else { break };
609 let rel = c.is_ascii_lowercase();
610 let base = if rel { cur } else { Vec2::ZERO };
611 let point = |b: &[u8], i: &mut usize| -> Result<Vec2, PathError> {
612 let x = number(b, i)?;
613 let y = number(b, i)?;
614 Ok(Vec2::new(base.x + x, base.y + y))
615 };
616 match c.to_ascii_uppercase() {
617 b'M' => {
618 let p = point(b, &mut i)?;
619 ops.push(PathOp::MoveTo(p));
620 cur = p;
621 start = p;
622 cmd = Some(if rel { b'l' } else { b'L' });
624 last_cubic = None;
625 last_quad = None;
626 }
627 b'L' => {
628 let p = point(b, &mut i)?;
629 ops.push(PathOp::LineTo(p));
630 cur = p;
631 last_cubic = None;
632 last_quad = None;
633 }
634 b'H' => {
635 let x = number(b, &mut i)?;
636 let p = Vec2::new(base.x + x, cur.y);
637 ops.push(PathOp::LineTo(p));
638 cur = p;
639 last_cubic = None;
640 last_quad = None;
641 }
642 b'V' => {
643 let y = number(b, &mut i)?;
644 let p = Vec2::new(cur.x, base.y + y);
645 ops.push(PathOp::LineTo(p));
646 cur = p;
647 last_cubic = None;
648 last_quad = None;
649 }
650 b'C' => {
651 let c1 = point(b, &mut i)?;
652 let c2 = point(b, &mut i)?;
653 let p = point(b, &mut i)?;
654 ops.push(PathOp::CubicTo(c1, c2, p));
655 last_cubic = Some(c2);
656 last_quad = None;
657 cur = p;
658 }
659 b'S' => {
660 let c2 = point(b, &mut i)?;
661 let p = point(b, &mut i)?;
662 let c1 = match last_cubic {
663 Some(l) => Vec2::new(2.0 * cur.x - l.x, 2.0 * cur.y - l.y),
664 None => cur,
665 };
666 ops.push(PathOp::CubicTo(c1, c2, p));
667 last_cubic = Some(c2);
668 last_quad = None;
669 cur = p;
670 }
671 b'Q' => {
672 let c1 = point(b, &mut i)?;
673 let p = point(b, &mut i)?;
674 ops.push(PathOp::QuadTo(c1, p));
675 last_quad = Some(c1);
676 last_cubic = None;
677 cur = p;
678 }
679 b'T' => {
680 let p = point(b, &mut i)?;
681 let c1 = match last_quad {
682 Some(l) => Vec2::new(2.0 * cur.x - l.x, 2.0 * cur.y - l.y),
683 None => cur,
684 };
685 ops.push(PathOp::QuadTo(c1, p));
686 last_quad = Some(c1);
687 last_cubic = None;
688 cur = p;
689 }
690 b'A' => {
691 let rx = number(b, &mut i)?;
692 let ry = number(b, &mut i)?;
693 let rotation = number(b, &mut i)?;
694 let large = flag(b, &mut i)?;
695 let sweep = flag(b, &mut i)?;
696 let p = point(b, &mut i)?;
697 ops.push(PathOp::ArcTo {
698 rx: rx.abs(),
699 ry: ry.abs(),
700 rotation,
701 large,
702 sweep,
703 to: p,
704 });
705 last_cubic = None;
706 last_quad = None;
707 cur = p;
708 }
709 _ => {
710 return Err(PathError {
711 at: i - 1,
712 what: "one of M L H V C S Q T A Z",
713 });
714 }
715 }
716 }
717 Ok(Self::from_ops(ops))
718 }
719}
720
721pub const CONTOUR_BREAK: Vec2 = Vec2 {
725 x: f32::NAN,
726 y: f32::NAN,
727};
728
729pub const FLATTEN_TOLERANCE: f32 = 0.2;
732
733const MAX_PIECES: usize = 128;
735
736#[derive(Clone, Copy)]
739struct Fineness {
740 tolerance: f32,
741 pieces: usize,
742}
743
744const HIT: Fineness = Fineness {
746 tolerance: FLATTEN_TOLERANCE,
747 pieces: MAX_PIECES,
748};
749
750const RASTER: Fineness = Fineness {
753 tolerance: 0.1,
754 pieces: 4096,
755};
756
757pub fn flatten(ops: &[PathOp], out: &mut Vec<Vec2>) {
762 flatten_as(ops, out, false, HIT, None);
763}
764
765pub fn flatten_stroke(ops: &[PathOp], out: &mut Vec<Vec2>) {
770 flatten_as(ops, out, true, HIT, None);
771}
772
773fn flatten_as(
777 ops: &[PathOp],
778 out: &mut Vec<Vec2>,
779 stroke: bool,
780 fine: Fineness,
781 mut closes: Option<&mut Vec<bool>>,
782) {
783 let mut cur = Vec2::ZERO;
784 let mut open = false;
785 let mut contour_start = out.len();
786 let mut close = |out: &mut Vec<Vec2>, open: &mut bool, contour_start: usize, closed: bool| {
787 if *open {
788 if out.len() - contour_start >= 2 {
790 match closes.as_deref_mut() {
791 Some(closes) => closes.push(closed),
792 None if stroke && closed => out.push(out[contour_start]),
793 None => {}
794 }
795 out.push(CONTOUR_BREAK);
796 } else {
797 out.truncate(contour_start);
798 }
799 *open = false;
800 }
801 };
802 for op in ops {
803 match *op {
804 PathOp::MoveTo(p) => {
805 close(out, &mut open, contour_start, false);
806 contour_start = out.len();
807 out.push(p);
808 cur = p;
809 open = true;
810 }
811 PathOp::Close => {
812 close(out, &mut open, contour_start, true);
813 if let Some(&s) = out.get(contour_start) {
815 cur = s;
816 }
817 contour_start = out.len();
818 }
819 _ => {
820 if !open {
821 contour_start = out.len();
822 out.push(cur);
823 open = true;
824 }
825 match *op {
826 PathOp::LineTo(p) => {
827 out.push(p);
828 cur = p;
829 }
830 PathOp::QuadTo(c, p) => {
831 flatten_quad(cur, c, p, out, fine);
832 cur = p;
833 }
834 PathOp::CubicTo(a, b, p) => {
835 flatten_cubic(cur, a, b, p, out, fine);
836 cur = p;
837 }
838 PathOp::ArcTo {
839 rx,
840 ry,
841 rotation,
842 large,
843 sweep,
844 to,
845 } => {
846 flatten_arc(cur, rx, ry, rotation, large, sweep, to, out, fine);
847 cur = to;
848 }
849 PathOp::MoveTo(_) | PathOp::Close => unreachable!(),
850 }
851 }
852 }
853 }
854 close(out, &mut open, contour_start, false);
855}
856
857fn pieces(dd: f32, k: f32, fine: Fineness) -> usize {
858 ((dd * k / fine.tolerance).sqrt().ceil() as usize).clamp(1, fine.pieces)
859}
860
861fn flatten_quad(p0: Vec2, c: Vec2, p1: Vec2, out: &mut Vec<Vec2>, fine: Fineness) {
862 let dd = Vec2::new(p0.x - 2.0 * c.x + p1.x, p0.y - 2.0 * c.y + p1.y);
863 let n = pieces((dd.x * dd.x + dd.y * dd.y).sqrt(), 0.25, fine);
864 for i in 1..=n {
865 let t = i as f32 / n as f32;
866 let u = 1.0 - t;
867 out.push(Vec2::new(
868 u * u * p0.x + 2.0 * u * t * c.x + t * t * p1.x,
869 u * u * p0.y + 2.0 * u * t * c.y + t * t * p1.y,
870 ));
871 }
872}
873
874fn flatten_cubic(p0: Vec2, a: Vec2, b: Vec2, p1: Vec2, out: &mut Vec<Vec2>, fine: Fineness) {
875 let d1 = Vec2::new(p0.x - 2.0 * a.x + b.x, p0.y - 2.0 * a.y + b.y);
876 let d2 = Vec2::new(a.x - 2.0 * b.x + p1.x, a.y - 2.0 * b.y + p1.y);
877 let dd = (d1.x * d1.x + d1.y * d1.y)
878 .max(d2.x * d2.x + d2.y * d2.y)
879 .sqrt();
880 let n = pieces(dd, 0.75, fine);
881 for i in 1..=n {
882 let t = i as f32 / n as f32;
883 let u = 1.0 - t;
884 let (uu, tt) = (u * u, t * t);
885 out.push(Vec2::new(
886 uu * u * p0.x + 3.0 * uu * t * a.x + 3.0 * u * tt * b.x + tt * t * p1.x,
887 uu * u * p0.y + 3.0 * uu * t * a.y + 3.0 * u * tt * b.y + tt * t * p1.y,
888 ));
889 }
890}
891
892fn turn_of(degrees: f32) -> f32 {
896 degrees.rem_euclid(360.0)
897}
898
899#[allow(clippy::too_many_arguments)]
908fn arc_center(
909 from: Vec2,
910 rx: f32,
911 ry: f32,
912 rotation: f32,
913 large: bool,
914 sweep: bool,
915 to: Vec2,
916) -> Option<(Vec2, f32, f32, f32, f32, f32)> {
917 if (from.x - to.x).abs() < 1e-6 && (from.y - to.y).abs() < 1e-6 {
918 return None;
919 }
920 let (mut rx, mut ry) = (f64::from(rx.abs()), f64::from(ry.abs()));
921 if rx < 1e-6 || ry < 1e-6 {
922 return None;
923 }
924 let phi = f64::from(turn_of(rotation)).to_radians();
925 let (sin_phi, cos_phi) = phi.sin_cos();
926 let (fx, fy, tx, ty) = (
927 f64::from(from.x),
928 f64::from(from.y),
929 f64::from(to.x),
930 f64::from(to.y),
931 );
932 let dx = (fx - tx) * 0.5;
933 let dy = (fy - ty) * 0.5;
934 let x1 = cos_phi * dx + sin_phi * dy;
935 let y1 = -sin_phi * dx + cos_phi * dy;
936 let lambda = (x1 * x1) / (rx * rx) + (y1 * y1) / (ry * ry);
937 if lambda > 1.0 {
938 let s = lambda.sqrt();
939 rx *= s;
940 ry *= s;
941 }
942 let num = (rx * rx * ry * ry - rx * rx * y1 * y1 - ry * ry * x1 * x1).max(0.0);
943 let den = rx * rx * y1 * y1 + ry * ry * x1 * x1;
944 let mut coef = if den > 0.0 { (num / den).sqrt() } else { 0.0 };
945 if large == sweep {
946 coef = -coef;
947 }
948 let cx1 = coef * rx * y1 / ry;
949 let cy1 = -coef * ry * x1 / rx;
950 let cx = cos_phi * cx1 - sin_phi * cy1 + (fx + tx) * 0.5;
951 let cy = sin_phi * cx1 + cos_phi * cy1 + (fy + ty) * 0.5;
952 let ux = (x1 - cx1) / rx;
953 let uy = (y1 - cy1) / ry;
954 let vx = (-x1 - cx1) / rx;
955 let vy = (-y1 - cy1) / ry;
956 let angle = |ax: f64, ay: f64, bx: f64, by: f64| -> f64 {
957 let dot = ax * bx + ay * by;
958 let len = ((ax * ax + ay * ay) * (bx * bx + by * by)).sqrt();
959 let mut a = (dot / len).clamp(-1.0, 1.0).acos();
960 if ax * by - ay * bx < 0.0 {
961 a = -a;
962 }
963 a
964 };
965 let theta = angle(1.0, 0.0, ux, uy);
966 let mut delta = angle(ux, uy, vx, vy);
967 let tau = std::f64::consts::TAU;
968 if !sweep && delta > 0.0 {
969 delta -= tau;
970 } else if sweep && delta < 0.0 {
971 delta += tau;
972 }
973 Some((
974 Vec2::new(cx as f32, cy as f32),
975 rx as f32,
976 ry as f32,
977 phi as f32,
978 theta as f32,
979 delta as f32,
980 ))
981}
982
983#[allow(clippy::too_many_arguments)]
984fn flatten_arc(
985 from: Vec2,
986 rx: f32,
987 ry: f32,
988 rotation: f32,
989 large: bool,
990 sweep: bool,
991 to: Vec2,
992 out: &mut Vec<Vec2>,
993 fine: Fineness,
994) {
995 let Some((c, rx, ry, phi, theta, delta)) = arc_center(from, rx, ry, rotation, large, sweep, to)
996 else {
997 out.push(to);
998 return;
999 };
1000 let r = rx.max(ry);
1001 let x = fine.tolerance / r;
1007 let step = if x < 1e-4 {
1008 2.0 * (2.0 * x).sqrt()
1009 } else {
1010 2.0 * (1.0 - x).clamp(-1.0, 1.0).acos()
1011 };
1012 let n = if step > 0.0 {
1013 ((delta.abs() / step).ceil() as usize).clamp(1, fine.pieces)
1014 } else {
1015 1
1016 };
1017 let (sin_phi, cos_phi) = phi.sin_cos();
1018 for i in 1..=n {
1019 let a = theta + delta * i as f32 / n as f32;
1020 let (sa, ca) = a.sin_cos();
1021 let x = rx * ca;
1022 let y = ry * sa;
1023 out.push(Vec2::new(
1024 c.x + cos_phi * x - sin_phi * y,
1025 c.y + sin_phi * x + cos_phi * y,
1026 ));
1027 }
1028 if let Some(last) = out.last_mut() {
1030 *last = to;
1031 }
1032}
1033
1034pub fn in_path(p: Vec2, pts: &[Vec2], rule: FillRule) -> bool {
1039 let mut winding = 0i32;
1040 let mut crossings = 0u32;
1041 for contour in pts.split(|v| v.x.is_nan()) {
1042 let n = contour.len();
1043 if n < 3 {
1044 continue;
1045 }
1046 let mut j = n - 1;
1047 for i in 0..n {
1048 let (a, b) = (contour[i], contour[j]);
1049 if (a.y > p.y) != (b.y > p.y) {
1050 let x = a.x + (p.y - a.y) / (b.y - a.y) * (b.x - a.x);
1051 if p.x < x {
1052 crossings += 1;
1053 winding += if b.y > a.y { 1 } else { -1 };
1054 }
1055 }
1056 j = i;
1057 }
1058 }
1059 match rule {
1060 FillRule::NonZero => winding != 0,
1061 FillRule::EvenOdd => crossings % 2 == 1,
1062 }
1063}
1064
1065pub fn bounds(pts: &[Vec2]) -> Option<Rect> {
1068 let mut it = pts.iter().filter(|p| !p.x.is_nan());
1069 let first = *it.next()?;
1070 let (mut min, mut max) = (first, first);
1071 for p in it {
1072 min.x = min.x.min(p.x);
1073 min.y = min.y.min(p.y);
1074 max.x = max.x.max(p.x);
1075 max.y = max.y.max(p.y);
1076 }
1077 Some(Rect::new(min.x, min.y, max.x - min.x, max.y - min.y))
1078}
1079
1080pub fn hash_ops(ops: &[PathOp]) -> u64 {
1083 const OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
1084 const PRIME: u64 = 0x0000_0100_0000_01b3;
1085 let mut h = OFFSET;
1086 let mut mix = |v: u32| {
1087 for byte in v.to_le_bytes() {
1088 h ^= u64::from(byte);
1089 h = h.wrapping_mul(PRIME);
1090 }
1091 };
1092 let mut scratch = Vec::with_capacity(8);
1093 for op in ops {
1094 scratch.clear();
1095 op.write(&mut scratch);
1096 for v in &scratch {
1097 mix(v.to_bits());
1098 }
1099 }
1100 h
1101}
1102
1103#[derive(Clone, Copy, Debug, PartialEq)]
1108pub enum MaskPaint {
1109 Fill(FillRule),
1110 Stroke(f32),
1111 Dashed(f32, DashCut),
1112}
1113
1114pub type DashCut = crate::line::Cut;
1118
1119pub fn rasterize(
1131 ops: &[PathOp],
1132 scale: f32,
1133 bin: (u8, u8),
1134 w: u32,
1135 h: u32,
1136 paint: MaskPaint,
1137) -> Vec<u8> {
1138 let off = Vec2::new(f32::from(bin.0) * 0.25, f32::from(bin.1) * 0.25);
1139 rasterize_at(ops, scale, off, w, h, paint)
1140}
1141
1142pub fn rasterize_at(
1146 ops: &[PathOp],
1147 scale: f32,
1148 off: Vec2,
1149 w: u32,
1150 h: u32,
1151 paint: MaskPaint,
1152) -> Vec<u8> {
1153 use swash::zeno::{Cap, Command, Fill, Join, Mask, PathBuilder, Point, Stroke};
1154 let len = (w as usize) * (h as usize);
1155 let mut buf = vec![0u8; len];
1156 if len == 0 || ops.is_empty() || !drawable(ops, scale, off, w, h, paint) {
1157 return buf;
1158 }
1159 let snap = |v: f32| (v * 256.0).round() / 256.0;
1167 let place = |p: Vec2| Vec2::new(p.x * scale + off.x, p.y * scale + off.y);
1168 let at = |p: Vec2| {
1169 let q = place(p);
1170 Point::new(snap(q.x), snap(q.y))
1171 };
1172 let mut arc = Vec::new();
1173 let mut cmds: Vec<Command> = Vec::with_capacity(ops.len() + 1);
1174 let mut cur = Vec2::ZERO;
1175 let mut start = Vec2::ZERO;
1176 let mut open = false;
1177 for op in ops {
1178 match *op {
1179 PathOp::MoveTo(p) => {
1180 cmds.move_to(at(p));
1181 cur = p;
1182 start = p;
1183 open = true;
1184 }
1185 PathOp::Close => {
1186 if open {
1187 cmds.close();
1188 }
1189 cur = start;
1192 open = false;
1193 }
1194 _ => {
1195 if !open {
1196 cmds.move_to(at(cur));
1197 start = cur;
1198 open = true;
1199 }
1200 match *op {
1201 PathOp::LineTo(p) => {
1202 cmds.line_to(at(p));
1203 cur = p;
1204 }
1205 PathOp::QuadTo(c, p) => {
1206 cmds.quad_to(at(c), at(p));
1207 cur = p;
1208 }
1209 PathOp::CubicTo(a, b, p) => {
1210 cmds.curve_to(at(a), at(b), at(p));
1211 cur = p;
1212 }
1213 PathOp::ArcTo {
1214 rx,
1215 ry,
1216 rotation,
1217 large,
1218 sweep,
1219 to,
1220 } => {
1221 arc.clear();
1229 flatten_arc(
1230 place(cur),
1231 rx * scale,
1232 ry * scale,
1233 rotation,
1234 large,
1235 sweep,
1236 place(to),
1237 &mut arc,
1238 RASTER,
1239 );
1240 for p in &arc {
1241 cmds.line_to(Point::new(snap(p.x), snap(p.y)));
1242 }
1243 cur = to;
1244 }
1245 PathOp::MoveTo(_) | PathOp::Close => unreachable!(),
1246 }
1247 }
1248 }
1249 }
1250 let lines = {
1259 let mut physical: Vec<PathOp> = Vec::with_capacity(ops.len() + 1);
1260 if !matches!(ops.first(), Some(PathOp::MoveTo(_))) {
1266 physical.push(PathOp::MoveTo(off));
1267 }
1268 physical.extend(ops.iter().map(|op| op.placed(scale, off)));
1269 let (mut pts, mut closes) = (Vec::new(), Vec::new());
1270 flatten_as(&physical, &mut pts, true, RASTER, Some(&mut closes));
1271 let mut lines: Vec<Command> = Vec::with_capacity(pts.len() + closes.len());
1272 let mut closes = closes.into_iter();
1273 let mut kept: Vec<Point> = Vec::new();
1274 for contour in pts.split(|p| p.x.is_nan()).filter(|c| !c.is_empty()) {
1275 let closed = closes.next() == Some(true);
1276 kept.clear();
1282 for p in contour {
1283 let q = Point::new(snap(p.x), snap(p.y));
1284 if kept.last() != Some(&q) {
1285 kept.push(q);
1286 }
1287 }
1288 if closed && kept.len() > 1 && kept.first() == kept.last() {
1289 kept.pop();
1290 }
1291 let Some((&first, rest)) = kept.split_first() else {
1292 continue;
1293 };
1294 if rest.is_empty() {
1295 continue;
1296 }
1297 lines.move_to(first);
1298 for &q in rest {
1299 lines.line_to(q);
1300 }
1301 if closed {
1305 lines.close();
1306 }
1307 }
1308 lines
1309 };
1310 match paint {
1311 MaskPaint::Fill(rule) => {
1312 let fill = match rule {
1313 FillRule::NonZero => Fill::NonZero,
1314 FillRule::EvenOdd => Fill::EvenOdd,
1315 };
1316 Mask::new(&cmds[..])
1317 .style(fill)
1318 .size(w, h)
1319 .render_into(&mut buf, None);
1320 if buf.iter().all(|&a| a == 0) {
1324 return buf;
1325 }
1326 let mut edge = vec![0u8; len];
1329 let mut stroke = Stroke::new(1.0);
1330 stroke.join(Join::Round).cap(Cap::Round);
1331 Mask::new(&lines[..])
1332 .style(stroke)
1333 .size(w, h)
1334 .render_into(&mut edge, None);
1335 for (a, e) in buf.iter_mut().zip(edge) {
1336 *a = (*a).max(e);
1337 }
1338 }
1339 MaskPaint::Stroke(width) => {
1340 let mut stroke = Stroke::new(width.max(0.0));
1341 stroke.join(Join::Round).cap(Cap::Round);
1342 Mask::new(&lines[..])
1343 .style(stroke)
1344 .size(w, h)
1345 .render_into(&mut buf, None);
1346 }
1347 MaskPaint::Dashed(width, cut) => {
1348 let mut stroke = Stroke::new(width.max(0.0));
1349 stroke.join(Join::Round).cap(Cap::Round);
1350 if !cut.finer_than(1.0) {
1353 stroke.dash(&cut.lens, cut.offset);
1354 }
1355 Mask::new(&lines[..])
1356 .style(stroke)
1357 .size(w, h)
1358 .render_into(&mut buf, None);
1359 }
1360 }
1361 buf
1362}
1363
1364pub const RASTER_SPAN: f32 = 16384.0;
1372
1373pub const RASTER_REACH: f32 = 262_144.0;
1380
1381fn drawable(ops: &[PathOp], scale: f32, off: Vec2, w: u32, h: u32, paint: MaskPaint) -> bool {
1387 let width = match paint {
1388 MaskPaint::Fill(_) => 1.0,
1390 MaskPaint::Stroke(w) => w,
1391 MaskPaint::Dashed(w, cut) => {
1392 if !cut.offset.is_finite() || !cut.lens.iter().all(|l| l.is_finite()) {
1393 return false;
1394 }
1395 w
1396 }
1397 };
1398 if !(scale.is_finite() && off.x.is_finite() && off.y.is_finite() && width.is_finite())
1399 || w > MAX_MASK_SIDE
1400 || h > MAX_MASK_SIDE
1401 || !ops.iter().all(PathOp::is_finite)
1402 {
1403 return false;
1404 }
1405 let mut pts = Vec::new();
1406 flatten(ops, &mut pts);
1407 let Some(b) = bounds(&pts) else {
1408 return true;
1410 };
1411 let (x0, y0) = (b.x * scale + off.x, b.y * scale + off.y);
1412 let (x1, y1) = (x0 + b.w * scale, y0 + b.h * scale);
1413 let reach = [x0, y0, x1, y1].iter().all(|v| v.abs() <= RASTER_REACH);
1414 reach && (x1 - x0).abs() + (y1 - y0).abs() + 2.0 * width.abs() <= RASTER_SPAN
1415}
1416
1417pub const MAX_ATLAS_MASK_TEXELS: u64 = 2048 * 2048;
1421
1422pub const MAX_MASK_SIDE: u32 = 8192;
1430
1431pub struct PathTexture {
1435 pub id: crate::resources::ImageId,
1436 pub w: u32,
1437 pub h: u32,
1438 pub rgba: std::sync::Arc<Vec<u8>>,
1439 last_used: u64,
1440}
1441
1442pub struct PathTextures {
1450 by_key: rustc_hash::FxHashMap<u64, PathTexture>,
1451 session: crate::session::Session,
1452}
1453
1454impl Drop for PathTextures {
1455 fn drop(&mut self) {
1456 for t in self.by_key.values() {
1457 crate::resources::unmint_image(t.id);
1458 }
1459 if let Some(mut sess) = self.session.try_state() {
1463 sess.dropped
1464 .images
1465 .extend(self.by_key.values().map(|t| t.id));
1466 }
1467 }
1468}
1469
1470impl PathTextures {
1471 pub(crate) fn new(session: crate::session::Session) -> Self {
1472 Self {
1473 by_key: Default::default(),
1474 session,
1475 }
1476 }
1477
1478 pub(crate) fn pixels(
1481 &self,
1482 id: crate::resources::ImageId,
1483 ) -> Option<(u32, u32, std::sync::Arc<Vec<u8>>)> {
1484 self.by_key
1485 .values()
1486 .find(|t| t.id == id)
1487 .map(|t| (t.w, t.h, t.rgba.clone()))
1488 }
1489
1490 pub(crate) fn get_or_make(
1493 &mut self,
1494 key: u64,
1495 w: u32,
1496 h: u32,
1497 frame: u64,
1498 session: crate::resources::SessionId,
1499 coverage: impl FnOnce() -> Vec<u8>,
1500 ) -> &PathTexture {
1501 if let Some(tex) = self.by_key.get(&key) {
1502 debug_assert!(tex.w == w && tex.h == h, "a mask key names one size");
1503 }
1504 let tex = self.by_key.entry(key).or_insert_with(|| {
1505 let mask = coverage();
1506 let mut rgba = Vec::with_capacity(mask.len() * 4);
1507 for &a in &mask {
1508 rgba.extend_from_slice(&[255, 255, 255, a]);
1509 }
1510 PathTexture {
1511 id: crate::resources::mint_image(session),
1512 w,
1513 h,
1514 rgba: std::sync::Arc::new(rgba),
1515 last_used: frame,
1516 }
1517 });
1518 tex.last_used = frame;
1519 tex
1520 }
1521
1522 pub(crate) fn sweep(&mut self, frame: u64) -> Vec<crate::resources::ImageId> {
1525 let mut gone = Vec::new();
1526 self.by_key.retain(|_, t| {
1527 if t.last_used + 1 < frame {
1528 crate::resources::unmint_image(t.id);
1529 gone.push(t.id);
1530 false
1531 } else {
1532 true
1533 }
1534 });
1535 gone
1536 }
1537
1538 pub fn len(&self) -> usize {
1540 self.by_key.len()
1541 }
1542
1543 pub fn is_empty(&self) -> bool {
1544 self.by_key.is_empty()
1545 }
1546}
1547
1548pub(crate) const TURNED_BIN: (u8, u8) = (0xff, 0xff);
1553
1554pub(crate) fn mask_key(hash: u64, scale: f32, bin: (u8, u8), paint: MaskPaint) -> u64 {
1555 const PRIME: u64 = 0x0000_0100_0000_01b3;
1556 let mut h = hash;
1557 let mut mix = |v: u64| {
1558 h ^= v;
1559 h = h.wrapping_mul(PRIME);
1560 };
1561 mix(u64::from(scale.to_bits()));
1562 mix(u64::from(bin.0) | (u64::from(bin.1) << 8));
1563 match paint {
1564 MaskPaint::Fill(rule) => mix(0x1000 | rule.index() as u64),
1565 MaskPaint::Stroke(w) => mix(0x2000_0000_0000 | u64::from(w.to_bits())),
1566 MaskPaint::Dashed(w, cut) => {
1567 mix(0x3000_0000_0000 | u64::from(w.to_bits()));
1568 mix(cut.hash());
1569 }
1570 }
1571 h
1572}
1573
1574pub const ANIMATING_WINDOW: u64 = 8;
1580
1581pub const SETTLED_AFTER: u64 = 120;
1586
1587#[derive(Clone, Copy, Debug)]
1590pub(crate) struct Motion {
1591 pub hash: u64,
1593 pub seen: u64,
1594 pub changed: u64,
1596 pub animating: bool,
1599}
1600
1601pub(crate) struct Parsed {
1605 pub hash: u64,
1607 pub len: usize,
1608 pub seen: u64,
1609 pub ops: Vec<PathOp>,
1610}
1611
1612#[derive(Clone, Copy, Debug, PartialEq)]
1615pub(crate) struct Run {
1616 pub first: u32,
1617 pub len: u32,
1618 pub rule: FillRule,
1619 pub stroke_w: f32,
1621 pub dash: Option<crate::line::Cut>,
1623 pub hash: u64,
1625 pub angle: Option<f32>,
1628 pub animating: bool,
1632}
1633
1634#[derive(Default)]
1636pub struct PathStore {
1637 runs: Kept<Run>,
1638 ops: Kept<PathOp>,
1639 scratch: Vec<Vec2>,
1640}
1641
1642impl PathStore {
1643 pub(crate) fn begin_frame(&mut self, keep_prev: bool) {
1646 self.runs.begin(keep_prev);
1647 self.ops.begin(keep_prev);
1648 }
1649
1650 pub(crate) fn push(
1654 &mut self,
1655 ops: &[PathOp],
1656 rule: FillRule,
1657 stroke_w: f32,
1658 dash: Option<crate::line::Cut>,
1659 turn: Option<Turn>,
1660 ) -> Option<(PathId, Rect)> {
1661 self.scratch.clear();
1662 flatten(ops, &mut self.scratch);
1663 let b = bounds(&self.scratch)?;
1664 let pad = 2.0 + stroke_w * 0.5;
1668 let rect = match turn {
1669 None => Rect::new(b.x - pad, b.y - pad, b.w + 2.0 * pad, b.h + 2.0 * pad),
1670 Some(turn) => {
1674 let c = turn
1675 .pivot
1676 .unwrap_or(Vec2::new(b.x + b.w * 0.5, b.y + b.h * 0.5));
1677 let far = self
1678 .scratch
1679 .iter()
1680 .filter(|p| !p.x.is_nan())
1681 .map(|p| (p.x - c.x).hypot(p.y - c.y))
1682 .fold(0.0f32, f32::max);
1683 let half = far + pad;
1684 Rect::new(c.x - half, c.y - half, 2.0 * half, 2.0 * half)
1685 }
1686 };
1687 let origin = Vec2::new(rect.x, rect.y);
1688 let first = self.ops.len();
1689 let shift = Vec2::new(-origin.x, -origin.y);
1690 self.ops.extend(ops.iter().map(|op| op.shifted(shift)));
1691 let hash = hash_ops(&self.ops[first..]);
1692 let id = PathId(self.runs.len() as u32);
1693 self.runs.push(Run {
1694 first: first as u32,
1695 len: (self.ops.len() - first) as u32,
1696 rule,
1697 stroke_w,
1698 dash,
1699 hash,
1700 angle: turn.map(Turn::radians),
1701 animating: false,
1702 });
1703 Some((id, rect))
1704 }
1705
1706 pub(crate) fn set_animating(&mut self, id: PathId) {
1707 if let Some(run) = self.runs.get_mut(id.0 as usize) {
1708 run.animating = true;
1709 }
1710 }
1711
1712 pub(crate) fn run(&self, id: PathId) -> (Run, &[PathOp]) {
1714 let run = self.runs[id.0 as usize];
1715 (
1716 run,
1717 &self.ops[run.first as usize..(run.first + run.len) as usize],
1718 )
1719 }
1720
1721 pub(crate) fn prev_run(&self, id: PathId) -> Option<(Run, &[PathOp])> {
1724 let run = *self.runs.prev().get(id.0 as usize)?;
1725 Some((
1726 run,
1727 &self.ops.prev()[run.first as usize..(run.first + run.len) as usize],
1728 ))
1729 }
1730
1731 pub fn len(&self) -> usize {
1732 self.runs.len()
1733 }
1734
1735 pub fn is_empty(&self) -> bool {
1736 self.runs.is_empty()
1737 }
1738}
1739
1740#[cfg(test)]
1741mod tests {
1742 use super::*;
1743
1744 fn pts(ops: &[PathOp]) -> Vec<Vec2> {
1745 let mut out = Vec::new();
1746 flatten(ops, &mut out);
1747 out
1748 }
1749
1750 #[test]
1757 fn an_outline_past_the_rasterizer_is_left_blank() {
1758 let blank = |ops: &[PathOp], paint| {
1759 rasterize(ops, 1.0, (0, 0), 48, 32, paint)
1760 .iter()
1761 .all(|&a| a == 0)
1762 };
1763 let fill = MaskPaint::Fill(FillRule::NonZero);
1764 let long = Path::parse("M60 60 L60610 0 Z").unwrap();
1765 assert!(blank(long.ops(), fill));
1766 let far = [
1767 PathOp::MoveTo(Vec2::new(10.0, 0.0)),
1768 PathOp::CubicTo(
1769 Vec2::new(50.0, 0.0),
1770 Vec2::new(1.0, 0.0),
1771 Vec2::new(20.0, 1.44e18),
1772 ),
1773 ];
1774 assert!(blank(&far, fill));
1775 let away = Path::parse("M1e7 1e7 l10 0 l0 10 z").unwrap();
1776 assert!(blank(away.ops(), MaskPaint::Stroke(2.0)));
1777 let nan = [
1778 PathOp::MoveTo(Vec2::new(2.0, 2.0)),
1779 PathOp::LineTo(Vec2::new(f32::NAN, 20.0)),
1780 PathOp::LineTo(Vec2::new(30.0, 20.0)),
1781 ];
1782 assert!(blank(&nan, fill));
1783 let tri = Path::parse("M2 2 L40 2 L20 30 Z").unwrap();
1785 assert!(blank(tri.ops(), MaskPaint::Stroke(RASTER_SPAN)));
1786 assert!(!blank(tri.ops(), fill));
1787 assert!(!blank(tri.ops(), MaskPaint::Stroke(2.0)));
1788 let side = (MAX_MASK_SIDE - 2) as f32;
1790 let diag = [
1791 PathOp::MoveTo(Vec2::new(1.0, 1.0)),
1792 PathOp::LineTo(Vec2::new(side, side)),
1793 PathOp::LineTo(Vec2::new(1.0, side)),
1794 PathOp::Close,
1795 ];
1796 assert!(drawable(
1797 &diag,
1798 1.0,
1799 Vec2::ZERO,
1800 MAX_MASK_SIDE,
1801 MAX_MASK_SIDE,
1802 fill
1803 ));
1804 }
1805
1806 #[test]
1812 fn an_arc_turned_past_any_turn_draws_as_within_one() {
1813 let arc = |rotation: f32| {
1814 let ops = [
1815 PathOp::MoveTo(Vec2::new(4.0, 20.0)),
1816 PathOp::ArcTo {
1817 rx: 20.0,
1818 ry: 8.0,
1819 rotation,
1820 large: true,
1821 sweep: true,
1822 to: Vec2::new(40.0, 24.0),
1823 },
1824 PathOp::Close,
1825 ];
1826 let fill = rasterize(
1827 &ops,
1828 1.0,
1829 (0, 0),
1830 64,
1831 48,
1832 MaskPaint::Fill(FillRule::NonZero),
1833 );
1834 let stroke = rasterize(&ops, 1.0, (0, 0), 64, 48, MaskPaint::Stroke(1.5));
1835 (fill, stroke)
1836 };
1837 let far = -1.7e38f32;
1838 assert_eq!(arc(far), arc(far.rem_euclid(360.0)));
1839 assert!(arc(far).0.iter().any(|&a| a > 0));
1840 let huge = [
1844 PathOp::MoveTo(Vec2::new(20.0, 80.0)),
1845 PathOp::ArcTo {
1846 rx: 3.6e17,
1847 ry: 5.0,
1848 rotation: 30.0,
1849 large: true,
1850 sweep: false,
1851 to: Vec2::new(20.0, 20.0),
1852 },
1853 ];
1854 let mut pts = Vec::new();
1855 flatten(&huge, &mut pts);
1856 assert!(pts.len() > 64, "{} points", pts.len());
1857 let m = rasterize(
1858 &huge,
1859 1.0,
1860 (0, 0),
1861 64,
1862 96,
1863 MaskPaint::Fill(FillRule::NonZero),
1864 );
1865 assert!(m.iter().all(|&a| a == 0));
1866 let square = Path::parse("M10 10 L30 10 L30 30 L10 30 Z").unwrap();
1869 let m = rasterize(square.ops(), 1.0, (0, 0), 40, 40, MaskPaint::Stroke(2.0));
1870 let near = |a: u8, b: u8| a.abs_diff(b) <= 4;
1874 assert!(near(m[10 * 40 + 10], m[29 * 40 + 29]));
1875 assert!(
1876 near(m[9 * 40 + 9], m[30 * 40 + 30]),
1877 "{} {}",
1878 m[9 * 40 + 9],
1879 m[30 * 40 + 30]
1880 );
1881 }
1882
1883 #[test]
1889 fn a_path_that_draws_before_it_moves_starts_at_its_origin() {
1890 let stroke = |d: &str| {
1891 let p = Path::parse(d).unwrap();
1892 rasterize(p.ops(), 1.0, (1, 3), 40, 40, MaskPaint::Stroke(2.0))
1893 };
1894 assert_eq!(stroke("L20 10 L30 30"), stroke("M0 0 L20 10 L30 30"));
1895 let arc = [
1896 PathOp::ArcTo {
1897 rx: 16.0,
1898 ry: 1.0e25,
1899 rotation: 0.0,
1900 large: true,
1901 sweep: true,
1902 to: Vec2::new(5.7e-41, 0.0),
1903 },
1904 PathOp::Close,
1905 ];
1906 let m = rasterize(&arc, 1.0, (1, 3), 48, 32, MaskPaint::Stroke(1.5));
1907 assert!(m.iter().all(|&a| a == 0));
1908 }
1909
1910 #[test]
1911 fn parses_absolute_and_relative_commands_alike() {
1912 let a = Path::parse("M10 10 L20 10 L20 20 Z").unwrap();
1913 let b = Path::parse("m10,10 l10 0 l0 10 z").unwrap();
1914 assert_eq!(a.ops(), b.ops());
1915 assert_eq!(a.ops().len(), 4);
1916 let c = Path::parse("M10 10 20 10V20z").unwrap();
1918 assert_eq!(a.ops(), c.ops());
1919 let d = Path::parse("M.5.5-1-1").unwrap();
1920 assert_eq!(
1921 d.ops(),
1922 &[
1923 PathOp::MoveTo(Vec2::new(0.5, 0.5)),
1924 PathOp::LineTo(Vec2::new(-1.0, -1.0))
1925 ]
1926 );
1927 }
1928
1929 #[test]
1930 fn smooth_curves_reflect_the_last_control_point() {
1931 let p = Path::parse("M0 0 C 10 0 20 10 20 20 S 30 40 40 40").unwrap();
1932 match p.ops()[2] {
1933 PathOp::CubicTo(c1, c2, to) => {
1934 assert_eq!(c1, Vec2::new(20.0, 30.0));
1935 assert_eq!(c2, Vec2::new(30.0, 40.0));
1936 assert_eq!(to, Vec2::new(40.0, 40.0));
1937 }
1938 op => panic!("{op:?}"),
1939 }
1940 let q = Path::parse("M0 0 Q 10 10 20 0 T 40 0").unwrap();
1941 match q.ops()[2] {
1942 PathOp::QuadTo(c, to) => {
1943 assert_eq!(c, Vec2::new(30.0, -10.0));
1944 assert_eq!(to, Vec2::new(40.0, 0.0));
1945 }
1946 op => panic!("{op:?}"),
1947 }
1948 }
1949
1950 #[test]
1951 fn a_malformed_string_names_its_byte() {
1952 let e = Path::parse("M10 10 L20").unwrap_err();
1953 assert_eq!(e.what, "a number");
1954 assert_eq!(e.at, 10);
1955 let e = Path::parse("10 10").unwrap_err();
1956 assert_eq!(e.what, "a command letter");
1957 let e = Path::parse("M0 0 X").unwrap_err();
1958 assert_eq!(e.what, "one of M L H V C S Q T A Z");
1959 let e = Path::parse("M0 0 A 5 5 0 2 0 10 10").unwrap_err();
1960 assert_eq!(e.what, "an arc flag (0 or 1)");
1961 }
1962
1963 #[test]
1964 fn the_wire_form_round_trips() {
1965 let p = Path::parse("M1 2 L3 4 Q5 6 7 8 C9 10 11 12 13 14 A15 16 17 1 0 18 19 Z").unwrap();
1966 let f = p.to_floats();
1967 assert_eq!(f.len(), 3 + 3 + 5 + 7 + 8 + 1);
1968 assert_eq!(Path::from_floats(&f).unwrap().ops(), p.ops());
1969 assert!(Path::from_floats(&[9.0]).is_err());
1970 assert!(Path::from_floats(&[OP_LINE, 1.0]).is_err());
1971 }
1972
1973 #[test]
1974 fn a_square_flattens_to_its_corners_and_hits_inside() {
1975 let p = Path::parse("M0 0 H10 V10 H0 Z").unwrap();
1976 let v = pts(p.ops());
1977 assert_eq!(v.len(), 5);
1978 assert!(v[4].x.is_nan());
1979 assert!(in_path(Vec2::new(5.0, 5.0), &v, FillRule::NonZero));
1980 assert!(in_path(Vec2::new(5.0, 5.0), &v, FillRule::EvenOdd));
1981 assert!(!in_path(Vec2::new(15.0, 5.0), &v, FillRule::NonZero));
1982 assert_eq!(bounds(&v), Some(Rect::new(0.0, 0.0, 10.0, 10.0)));
1983 }
1984
1985 #[test]
1986 fn a_ring_is_a_ring_by_either_rule_when_its_contours_oppose() {
1987 let p = Path::parse("M0 0 H20 V20 H0 Z M5 5 V15 H15 V5 Z").unwrap();
1989 let v = pts(p.ops());
1990 let hole = Vec2::new(10.0, 10.0);
1991 let band = Vec2::new(2.0, 10.0);
1992 assert!(!in_path(hole, &v, FillRule::NonZero));
1993 assert!(!in_path(hole, &v, FillRule::EvenOdd));
1994 assert!(in_path(band, &v, FillRule::NonZero));
1995 assert!(in_path(band, &v, FillRule::EvenOdd));
1996 let same = Path::parse("M0 0 H20 V20 H0 Z M5 5 H15 V15 H5 Z").unwrap();
1998 let v = pts(same.ops());
1999 assert!(in_path(hole, &v, FillRule::NonZero));
2000 assert!(!in_path(hole, &v, FillRule::EvenOdd));
2001 }
2002
2003 #[test]
2004 fn an_arc_flattens_onto_its_circle() {
2005 let p = Path::parse("M0 0 A10 10 0 0 1 20 0").unwrap();
2007 let v = pts(p.ops());
2008 assert!(v.len() >= 9, "{}", v.len());
2009 for q in v.iter().filter(|q| !q.x.is_nan()) {
2012 let r = ((q.x - 10.0).powi(2) + q.y.powi(2)).sqrt();
2013 assert!((r - 10.0).abs() < 0.3, "{q:?} is {r} from the centre");
2014 assert!(q.y <= 0.01, "{q:?} bows the wrong way");
2015 }
2016 assert_eq!(v[v.len() - 2], Vec2::new(20.0, 0.0));
2017 let down = Path::parse("M0 0 A10 10 0 0 0 20 0").unwrap();
2019 assert!(
2020 pts(down.ops())
2021 .iter()
2022 .filter(|q| !q.x.is_nan())
2023 .all(|q| q.y >= -0.01)
2024 );
2025 }
2026
2027 #[test]
2028 fn a_sector_is_a_wedge_and_a_full_turn_is_a_disc() {
2029 let w = Path::sector(50.0, 50.0, 40.0, 0.0, 0.0, 0.25);
2030 let v = pts(w.ops());
2031 assert!(in_path(Vec2::new(70.0, 70.0), &v, FillRule::NonZero));
2032 assert!(!in_path(Vec2::new(30.0, 30.0), &v, FillRule::NonZero));
2033 let d = Path::sector(50.0, 50.0, 40.0, 20.0, 0.0, 1.0);
2034 let v = pts(d.ops());
2035 assert!(in_path(Vec2::new(50.0, 20.0), &v, FillRule::NonZero));
2036 assert!(!in_path(Vec2::new(50.0, 50.0), &v, FillRule::NonZero));
2037 }
2038
2039 #[test]
2040 fn a_draw_after_a_close_starts_where_the_subpath_began() {
2041 let p = Path::parse("M10 10 H30 V30 Z L10 40 L30 40 Z").unwrap();
2044 let v = pts(p.ops());
2045 let m = rasterize(
2046 p.ops(),
2047 1.0,
2048 (0, 0),
2049 48,
2050 48,
2051 MaskPaint::Fill(FillRule::NonZero),
2052 );
2053 for (x, y) in [(12usize, 25usize), (28, 32), (28, 20), (14, 36)] {
2054 let hit = in_path(
2055 Vec2::new(x as f32 + 0.5, y as f32 + 0.5),
2056 &v,
2057 FillRule::NonZero,
2058 );
2059 assert_eq!(m[y * 48 + x] == 255, hit, "({x}, {y})");
2060 }
2061 assert_eq!(m[25 * 48 + 12], 255);
2062 assert_eq!(m[32 * 48 + 28], 0);
2063 }
2064
2065 #[test]
2066 fn a_strokes_pieces_close_only_what_its_z_closed() {
2067 let open = Path::parse("M0 0 L10 0 L10 10").unwrap();
2068 let mut v = Vec::new();
2069 flatten_stroke(open.ops(), &mut v);
2070 assert_eq!(v.len(), 4);
2071 assert_eq!(v[2], Vec2::new(10.0, 10.0));
2072 assert!(v[3].x.is_nan());
2073 let closed = Path::parse("M0 0 L10 0 L10 10 Z").unwrap();
2074 v.clear();
2075 flatten_stroke(closed.ops(), &mut v);
2076 assert_eq!(v.len(), 5);
2077 assert_eq!(v[3], Vec2::ZERO);
2078 assert!(v[4].x.is_nan());
2079 }
2080
2081 #[test]
2082 fn the_hash_follows_the_ops_and_nothing_else() {
2083 let a = Path::parse("M0 0 L10 0 L10 10 Z").unwrap();
2084 let b = Path::parse("M0 0 L10 0 L10 10 Z")
2085 .unwrap()
2086 .fill_rule(FillRule::EvenOdd);
2087 let c = Path::parse("M0 0 L10 0 L10 11 Z").unwrap();
2088 assert_eq!(hash_ops(a.ops()), hash_ops(b.ops()));
2089 assert_ne!(hash_ops(a.ops()), hash_ops(c.ops()));
2090 }
2091
2092 #[test]
2093 fn a_filled_square_is_opaque_inside_and_bleeds_past_its_edge() {
2094 let p = Path::parse("M2 2 H12 V12 H2 Z").unwrap();
2095 let m = rasterize(
2096 p.ops(),
2097 1.0,
2098 (0, 0),
2099 16,
2100 16,
2101 MaskPaint::Fill(FillRule::NonZero),
2102 );
2103 let at = |x: usize, y: usize| m[y * 16 + x];
2104 assert_eq!(at(7, 7), 255);
2105 assert_eq!(at(0, 0), 0);
2106 assert_eq!(at(14, 7), 0);
2107 assert!(at(1, 7) > 100 && at(1, 7) < 200, "{}", at(1, 7));
2110 assert_eq!(at(2, 7), 255);
2111 let q = Path::parse("M12 2 H22 V12 H12 Z").unwrap();
2114 let n = rasterize(
2115 q.ops(),
2116 1.0,
2117 (0, 0),
2118 24,
2119 16,
2120 MaskPaint::Fill(FillRule::NonZero),
2121 );
2122 let (a, b) = (
2123 f32::from(at(12, 7)) / 255.0,
2124 f32::from(n[7 * 24 + 12]) / 255.0,
2125 );
2126 assert!(a + b * (1.0 - a) > 0.99, "{a} over {b}");
2127 let (a, b) = (
2128 f32::from(at(11, 7)) / 255.0,
2129 f32::from(n[7 * 24 + 11]) / 255.0,
2130 );
2131 assert!(a + b * (1.0 - a) > 0.99, "{a} over {b}");
2132 }
2133
2134 #[test]
2138 fn a_dashed_stroke_is_marks_and_gaps() {
2139 let p = Path::parse("M4 8 H44").unwrap();
2140 let row = |dash: crate::line::Dash| {
2141 let paint = MaskPaint::Dashed(2.0, dash.cut(2.0).unwrap());
2142 let m = rasterize(p.ops(), 1.0, (0, 0), 48, 16, paint);
2143 (0..48).map(|x| m[8 * 48 + x] > 127).collect::<Vec<_>>()
2145 };
2146 let on = |r: &[bool], x: std::ops::Range<usize>| r[x].iter().all(|&b| b);
2147 let off = |r: &[bool], x: std::ops::Range<usize>| r[x].iter().all(|&b| !b);
2148 let r = row(crate::line::Dash::new(6.0, 4.0));
2150 assert!(on(&r, 3..9) && off(&r, 10..12) && on(&r, 13..19), "{r:?}");
2151 let r = row(crate::line::Dash::new(2.0, 6.0));
2153 assert!(on(&r, 3..5) && off(&r, 6..10) && on(&r, 11..13), "{r:?}");
2154 let r = row(crate::line::Dash::new(6.0, 4.0).offset(5.0));
2156 assert!(off(&r, 6..7) && on(&r, 8..14), "{r:?}");
2157 }
2158
2159 #[test]
2160 fn a_stroke_covers_the_outline_and_not_the_inside() {
2161 let p = Path::parse("M2 2 H12 V12 H2 Z").unwrap();
2162 let m = rasterize(p.ops(), 1.0, (0, 0), 16, 16, MaskPaint::Stroke(2.0));
2163 let at = |x: usize, y: usize| m[y * 16 + x];
2164 assert_eq!(at(7, 7), 0);
2165 assert!(at(2, 7) > 200, "{}", at(2, 7));
2166 assert!(at(1, 7) > 200, "{}", at(1, 7));
2167 }
2168
2169 #[test]
2170 fn the_store_boxes_a_path_and_keeps_the_ops_relative() {
2171 let mut store = PathStore::default();
2172 store.begin_frame(false);
2173 let p = Path::parse("M10 20 H30 V40 Z").unwrap();
2174 let (id, rect) = store
2175 .push(p.ops(), FillRule::NonZero, 0.0, None, None)
2176 .unwrap();
2177 assert_eq!(rect, Rect::new(8.0, 18.0, 24.0, 24.0));
2178 let (run, ops) = store.run(id);
2179 assert_eq!(ops[0], PathOp::MoveTo(Vec2::new(2.0, 2.0)));
2180 assert_eq!(run.len, 4);
2181 assert!(
2182 store
2183 .push(
2184 &[PathOp::MoveTo(Vec2::ZERO)],
2185 FillRule::NonZero,
2186 0.0,
2187 None,
2188 None
2189 )
2190 .is_none()
2191 );
2192 let (_, rect) = store
2194 .push(p.ops(), FillRule::NonZero, 4.0, None, None)
2195 .unwrap();
2196 assert_eq!(rect, Rect::new(6.0, 16.0, 28.0, 28.0));
2197 let turn = |t: f32| Turn {
2200 turns: t,
2201 pivot: Some(Vec2::new(10.0, 20.0)),
2202 };
2203 let (a, ra) = store
2204 .push(p.ops(), FillRule::NonZero, 0.0, None, Some(turn(0.0)))
2205 .unwrap();
2206 let (b, rb) = store
2207 .push(p.ops(), FillRule::NonZero, 0.0, None, Some(turn(0.3)))
2208 .unwrap();
2209 let far = (20.0f32 * 20.0 + 20.0 * 20.0).sqrt() + 2.0;
2210 assert_eq!(ra, Rect::new(10.0 - far, 20.0 - far, 2.0 * far, 2.0 * far));
2211 assert_eq!(ra, rb);
2212 assert_eq!(store.run(a).0.hash, store.run(b).0.hash);
2213 assert_eq!(store.run(a).1, store.run(b).1);
2214 assert_eq!(store.run(b).0.angle, Some(0.3 * std::f32::consts::TAU));
2215 store.begin_frame(true);
2216 assert!(store.prev_run(id).is_some());
2217 assert!(store.is_empty());
2218 }
2219
2220 #[test]
2223 fn a_fill_with_no_area_is_an_empty_mask() {
2224 for p in [
2225 Path::sector(16.0, 16.0, 12.0, 8.0, 0.0, 0.0),
2226 Path::parse("M2 2 L20 2 Z").unwrap(),
2227 ] {
2228 let m = rasterize(
2229 p.ops(),
2230 1.0,
2231 (0, 0),
2232 32,
2233 32,
2234 MaskPaint::Fill(FillRule::NonZero),
2235 );
2236 assert!(m.iter().all(|&a| a == 0));
2237 }
2238 }
2239}