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 shifted(&self, d: Vec2) -> PathOp {
124 let s = |p: Vec2| Vec2::new(p.x + d.x, p.y + d.y);
125 match *self {
126 PathOp::MoveTo(p) => PathOp::MoveTo(s(p)),
127 PathOp::LineTo(p) => PathOp::LineTo(s(p)),
128 PathOp::QuadTo(c, p) => PathOp::QuadTo(s(c), s(p)),
129 PathOp::CubicTo(a, b, p) => PathOp::CubicTo(s(a), s(b), s(p)),
130 PathOp::ArcTo {
131 rx,
132 ry,
133 rotation,
134 large,
135 sweep,
136 to,
137 } => PathOp::ArcTo {
138 rx,
139 ry,
140 rotation,
141 large,
142 sweep,
143 to: s(to),
144 },
145 PathOp::Close => PathOp::Close,
146 }
147 }
148}
149
150#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
152pub enum FillRule {
153 #[default]
156 NonZero,
157 EvenOdd,
160}
161
162impl FillRule {
163 pub const ALL: &[&str] = &["nonzero", "evenodd"];
165
166 pub fn from_index(i: usize) -> Self {
167 match i {
168 1 => FillRule::EvenOdd,
169 _ => FillRule::NonZero,
170 }
171 }
172
173 pub fn index(self) -> usize {
174 match self {
175 FillRule::NonZero => 0,
176 FillRule::EvenOdd => 1,
177 }
178 }
179
180 pub fn parse(s: &str) -> Option<Self> {
181 match s {
182 "nonzero" => Some(FillRule::NonZero),
183 "evenodd" => Some(FillRule::EvenOdd),
184 _ => None,
185 }
186 }
187}
188
189#[derive(Clone, Copy, Debug, Default, PartialEq)]
197pub struct Turn {
198 pub turns: f32,
199 pub pivot: Option<Vec2>,
200}
201
202impl Turn {
203 pub fn radians(self) -> f32 {
205 self.turns * std::f32::consts::TAU
206 }
207}
208
209pub fn turned(p: Vec2, c: Vec2, angle: f32) -> Vec2 {
211 let (sin, cos) = angle.sin_cos();
212 let (x, y) = (p.x - c.x, p.y - c.y);
213 Vec2::new(c.x + x * cos - y * sin, c.y + x * sin + y * cos)
214}
215
216#[derive(Clone, Debug, PartialEq, Eq)]
218pub struct PathError {
219 pub at: usize,
220 pub what: &'static str,
221}
222
223impl std::fmt::Display for PathError {
224 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
225 write!(f, "{} at byte {}", self.what, self.at)
226 }
227}
228
229impl std::error::Error for PathError {}
230
231#[derive(Clone, Debug, PartialEq)]
235pub struct Path {
236 ops: Vec<PathOp>,
237 rule: FillRule,
238 stroke: Option<crate::line::Stroke>,
239 turn: Option<Turn>,
240}
241
242impl Default for Path {
243 fn default() -> Self {
244 Self::new()
245 }
246}
247
248impl Path {
249 pub fn new() -> Self {
250 Self {
251 ops: Vec::new(),
252 rule: FillRule::NonZero,
253 stroke: None,
254 turn: None,
255 }
256 }
257
258 pub fn from_ops(ops: Vec<PathOp>) -> Self {
260 Self {
261 ops,
262 rule: FillRule::NonZero,
263 stroke: None,
264 turn: None,
265 }
266 }
267
268 pub fn from_floats(f: &[f32]) -> Result<Self, PathError> {
270 let mut ops = Vec::new();
271 let mut i = 0;
272 while i < f.len() {
273 let code = f[i];
274 let Some(n) = PathOp::operands(code) else {
275 return Err(PathError {
276 at: i,
277 what: "an op code 0..=5",
278 });
279 };
280 let Some(a) = f.get(i + 1..i + 1 + n) else {
281 return Err(PathError {
282 at: i,
283 what: "the op's operands",
284 });
285 };
286 ops.push(match code as i32 {
287 0 => PathOp::MoveTo(Vec2::new(a[0], a[1])),
288 1 => PathOp::LineTo(Vec2::new(a[0], a[1])),
289 2 => PathOp::QuadTo(Vec2::new(a[0], a[1]), Vec2::new(a[2], a[3])),
290 3 => PathOp::CubicTo(
291 Vec2::new(a[0], a[1]),
292 Vec2::new(a[2], a[3]),
293 Vec2::new(a[4], a[5]),
294 ),
295 4 => PathOp::ArcTo {
296 rx: a[0],
297 ry: a[1],
298 rotation: a[2],
299 large: a[3] != 0.0,
300 sweep: a[4] != 0.0,
301 to: Vec2::new(a[5], a[6]),
302 },
303 _ => PathOp::Close,
304 });
305 i += 1 + n;
306 }
307 Ok(Self::from_ops(ops))
308 }
309
310 pub fn to_floats(&self) -> Vec<f32> {
312 let mut out = Vec::with_capacity(self.ops.len() * 3);
313 for op in &self.ops {
314 op.write(&mut out);
315 }
316 out
317 }
318
319 pub fn ops(&self) -> &[PathOp] {
320 &self.ops
321 }
322
323 pub fn into_ops(self) -> Vec<PathOp> {
324 self.ops
325 }
326
327 pub fn rule(&self) -> FillRule {
328 self.rule
329 }
330
331 pub fn stroke(&self) -> Option<crate::line::Stroke> {
332 self.stroke
333 }
334
335 pub fn turn(&self) -> Option<Turn> {
336 self.turn
337 }
338
339 pub fn rotated(mut self, turns: f32) -> Self {
344 self.turn.get_or_insert_default().turns = turns;
345 self
346 }
347
348 pub fn pivot(mut self, x: f32, y: f32) -> Self {
352 self.turn.get_or_insert_default().pivot = Some(Vec2::new(x, y));
353 self
354 }
355
356 pub fn fill_rule(mut self, rule: FillRule) -> Self {
357 self.rule = rule;
358 self
359 }
360
361 pub fn stroked(mut self, stroke: crate::line::Stroke) -> Self {
364 self.stroke = Some(stroke);
365 self
366 }
367
368 pub fn move_to(mut self, x: f32, y: f32) -> Self {
369 self.ops.push(PathOp::MoveTo(Vec2::new(x, y)));
370 self
371 }
372
373 pub fn line_to(mut self, x: f32, y: f32) -> Self {
374 self.ops.push(PathOp::LineTo(Vec2::new(x, y)));
375 self
376 }
377
378 pub fn quad_to(mut self, cx: f32, cy: f32, x: f32, y: f32) -> Self {
379 self.ops
380 .push(PathOp::QuadTo(Vec2::new(cx, cy), Vec2::new(x, y)));
381 self
382 }
383
384 pub fn cubic_to(mut self, c1x: f32, c1y: f32, c2x: f32, c2y: f32, x: f32, y: f32) -> Self {
385 self.ops.push(PathOp::CubicTo(
386 Vec2::new(c1x, c1y),
387 Vec2::new(c2x, c2y),
388 Vec2::new(x, y),
389 ));
390 self
391 }
392
393 #[allow(clippy::too_many_arguments)]
397 pub fn arc_to(
398 mut self,
399 rx: f32,
400 ry: f32,
401 rotation: f32,
402 large: bool,
403 sweep: bool,
404 x: f32,
405 y: f32,
406 ) -> Self {
407 self.ops.push(PathOp::ArcTo {
408 rx,
409 ry,
410 rotation,
411 large,
412 sweep,
413 to: Vec2::new(x, y),
414 });
415 self
416 }
417
418 pub fn close(mut self) -> Self {
419 self.ops.push(PathOp::Close);
420 self
421 }
422
423 pub fn sector(cx: f32, cy: f32, outer: f32, inner: f32, from: f32, sweep: f32) -> Self {
428 let tau = std::f32::consts::TAU;
429 let sweep = sweep.clamp(-1.0, 1.0);
430 let (a0, a1) = (from * tau, (from + sweep) * tau);
431 let at = |r: f32, a: f32| (cx + r * a.cos(), cy + r * a.sin());
432 let large = sweep.abs() > 0.5;
433 let cw = sweep >= 0.0;
434 if sweep.abs() >= 1.0 {
435 let mid = a0 + tau * 0.5;
438 let (ox, oy) = at(outer, a0);
439 let (mx, my) = at(outer, mid);
440 let mut p = Path::new()
441 .move_to(ox, oy)
442 .arc_to(outer, outer, 0.0, false, cw, mx, my)
443 .arc_to(outer, outer, 0.0, false, cw, ox, oy)
444 .close();
445 if inner > 0.0 {
446 let (ix, iy) = at(inner, a0);
447 let (jx, jy) = at(inner, mid);
448 p = p
449 .move_to(ix, iy)
450 .arc_to(inner, inner, 0.0, false, !cw, jx, jy)
451 .arc_to(inner, inner, 0.0, false, !cw, ix, iy)
452 .close();
453 }
454 return p;
455 }
456 let (ox0, oy0) = at(outer, a0);
457 let (ox1, oy1) = at(outer, a1);
458 let mut p = Path::new()
459 .move_to(ox0, oy0)
460 .arc_to(outer, outer, 0.0, large, cw, ox1, oy1);
461 if inner > 0.0 {
462 let (ix1, iy1) = at(inner, a1);
463 let (ix0, iy0) = at(inner, a0);
464 p = p
465 .line_to(ix1, iy1)
466 .arc_to(inner, inner, 0.0, large, !cw, ix0, iy0);
467 } else {
468 p = p.line_to(cx, cy);
469 }
470 p.close()
471 }
472
473 pub fn parse(d: &str) -> Result<Self, PathError> {
478 let b = d.as_bytes();
479 let mut i = 0;
480 let mut ops = Vec::new();
481 let mut cur = Vec2::ZERO;
482 let mut start = Vec2::ZERO;
483 let mut last_cubic: Option<Vec2> = None;
486 let mut last_quad: Option<Vec2> = None;
487 let mut cmd: Option<u8> = None;
488
489 fn skip_ws(b: &[u8], i: &mut usize) {
490 while *i < b.len() && (b[*i].is_ascii_whitespace() || b[*i] == b',') {
491 *i += 1;
492 }
493 }
494 fn number(b: &[u8], i: &mut usize) -> Result<f32, PathError> {
495 skip_ws(b, i);
496 let at = *i;
497 let mut j = at;
498 if j < b.len() && (b[j] == b'-' || b[j] == b'+') {
499 j += 1;
500 }
501 let digits = j;
502 while j < b.len() && b[j].is_ascii_digit() {
503 j += 1;
504 }
505 if j < b.len() && b[j] == b'.' {
506 j += 1;
507 while j < b.len() && b[j].is_ascii_digit() {
508 j += 1;
509 }
510 }
511 if j == digits || (j == digits + 1 && b[digits] == b'.') {
512 return Err(PathError {
513 at,
514 what: "a number",
515 });
516 }
517 if j < b.len() && (b[j] == b'e' || b[j] == b'E') {
518 let mut k = j + 1;
519 if k < b.len() && (b[k] == b'-' || b[k] == b'+') {
520 k += 1;
521 }
522 let e = k;
523 while k < b.len() && b[k].is_ascii_digit() {
524 k += 1;
525 }
526 if k > e {
527 j = k;
528 }
529 }
530 let s = std::str::from_utf8(&b[at..j]).map_err(|_| PathError {
531 at,
532 what: "a number",
533 })?;
534 *i = j;
535 s.parse::<f32>().map_err(|_| PathError {
536 at,
537 what: "a number",
538 })
539 }
540 fn flag(b: &[u8], i: &mut usize) -> Result<bool, PathError> {
541 skip_ws(b, i);
542 match b.get(*i) {
543 Some(b'0') => {
544 *i += 1;
545 Ok(false)
546 }
547 Some(b'1') => {
548 *i += 1;
549 Ok(true)
550 }
551 _ => Err(PathError {
552 at: *i,
553 what: "an arc flag (0 or 1)",
554 }),
555 }
556 }
557
558 loop {
559 skip_ws(b, &mut i);
560 if i >= b.len() {
561 break;
562 }
563 let c = b[i];
564 if c.is_ascii_alphabetic() {
565 cmd = Some(c);
566 i += 1;
567 if c == b'Z' || c == b'z' {
568 ops.push(PathOp::Close);
569 cur = start;
570 last_cubic = None;
571 last_quad = None;
572 continue;
573 }
574 } else if cmd.is_none() {
575 return Err(PathError {
576 at: i,
577 what: "a command letter",
578 });
579 }
580 let Some(c) = cmd else { break };
581 let rel = c.is_ascii_lowercase();
582 let base = if rel { cur } else { Vec2::ZERO };
583 let point = |b: &[u8], i: &mut usize| -> Result<Vec2, PathError> {
584 let x = number(b, i)?;
585 let y = number(b, i)?;
586 Ok(Vec2::new(base.x + x, base.y + y))
587 };
588 match c.to_ascii_uppercase() {
589 b'M' => {
590 let p = point(b, &mut i)?;
591 ops.push(PathOp::MoveTo(p));
592 cur = p;
593 start = p;
594 cmd = Some(if rel { b'l' } else { b'L' });
596 last_cubic = None;
597 last_quad = None;
598 }
599 b'L' => {
600 let p = point(b, &mut i)?;
601 ops.push(PathOp::LineTo(p));
602 cur = p;
603 last_cubic = None;
604 last_quad = None;
605 }
606 b'H' => {
607 let x = number(b, &mut i)?;
608 let p = Vec2::new(base.x + x, cur.y);
609 ops.push(PathOp::LineTo(p));
610 cur = p;
611 last_cubic = None;
612 last_quad = None;
613 }
614 b'V' => {
615 let y = number(b, &mut i)?;
616 let p = Vec2::new(cur.x, base.y + y);
617 ops.push(PathOp::LineTo(p));
618 cur = p;
619 last_cubic = None;
620 last_quad = None;
621 }
622 b'C' => {
623 let c1 = point(b, &mut i)?;
624 let c2 = point(b, &mut i)?;
625 let p = point(b, &mut i)?;
626 ops.push(PathOp::CubicTo(c1, c2, p));
627 last_cubic = Some(c2);
628 last_quad = None;
629 cur = p;
630 }
631 b'S' => {
632 let c2 = point(b, &mut i)?;
633 let p = point(b, &mut i)?;
634 let c1 = match last_cubic {
635 Some(l) => Vec2::new(2.0 * cur.x - l.x, 2.0 * cur.y - l.y),
636 None => cur,
637 };
638 ops.push(PathOp::CubicTo(c1, c2, p));
639 last_cubic = Some(c2);
640 last_quad = None;
641 cur = p;
642 }
643 b'Q' => {
644 let c1 = point(b, &mut i)?;
645 let p = point(b, &mut i)?;
646 ops.push(PathOp::QuadTo(c1, p));
647 last_quad = Some(c1);
648 last_cubic = None;
649 cur = p;
650 }
651 b'T' => {
652 let p = point(b, &mut i)?;
653 let c1 = match last_quad {
654 Some(l) => Vec2::new(2.0 * cur.x - l.x, 2.0 * cur.y - l.y),
655 None => cur,
656 };
657 ops.push(PathOp::QuadTo(c1, p));
658 last_quad = Some(c1);
659 last_cubic = None;
660 cur = p;
661 }
662 b'A' => {
663 let rx = number(b, &mut i)?;
664 let ry = number(b, &mut i)?;
665 let rotation = number(b, &mut i)?;
666 let large = flag(b, &mut i)?;
667 let sweep = flag(b, &mut i)?;
668 let p = point(b, &mut i)?;
669 ops.push(PathOp::ArcTo {
670 rx: rx.abs(),
671 ry: ry.abs(),
672 rotation,
673 large,
674 sweep,
675 to: p,
676 });
677 last_cubic = None;
678 last_quad = None;
679 cur = p;
680 }
681 _ => {
682 return Err(PathError {
683 at: i - 1,
684 what: "one of M L H V C S Q T A Z",
685 });
686 }
687 }
688 }
689 Ok(Self::from_ops(ops))
690 }
691}
692
693pub const CONTOUR_BREAK: Vec2 = Vec2 {
697 x: f32::NAN,
698 y: f32::NAN,
699};
700
701pub const FLATTEN_TOLERANCE: f32 = 0.2;
704
705const MAX_PIECES: usize = 128;
707
708pub fn flatten(ops: &[PathOp], out: &mut Vec<Vec2>) {
713 flatten_as(ops, out, false);
714}
715
716pub fn flatten_stroke(ops: &[PathOp], out: &mut Vec<Vec2>) {
721 flatten_as(ops, out, true);
722}
723
724fn flatten_as(ops: &[PathOp], out: &mut Vec<Vec2>, stroke: bool) {
725 let mut cur = Vec2::ZERO;
726 let mut open = false;
727 let mut contour_start = out.len();
728 let close = |out: &mut Vec<Vec2>, open: &mut bool, contour_start: usize, closed: bool| {
729 if *open {
730 if out.len() - contour_start >= 2 {
732 if stroke && closed {
733 out.push(out[contour_start]);
734 }
735 out.push(CONTOUR_BREAK);
736 } else {
737 out.truncate(contour_start);
738 }
739 *open = false;
740 }
741 };
742 for op in ops {
743 match *op {
744 PathOp::MoveTo(p) => {
745 close(out, &mut open, contour_start, false);
746 contour_start = out.len();
747 out.push(p);
748 cur = p;
749 open = true;
750 }
751 PathOp::Close => {
752 close(out, &mut open, contour_start, true);
753 if let Some(&s) = out.get(contour_start) {
755 cur = s;
756 }
757 contour_start = out.len();
758 }
759 _ => {
760 if !open {
761 contour_start = out.len();
762 out.push(cur);
763 open = true;
764 }
765 match *op {
766 PathOp::LineTo(p) => {
767 out.push(p);
768 cur = p;
769 }
770 PathOp::QuadTo(c, p) => {
771 flatten_quad(cur, c, p, out);
772 cur = p;
773 }
774 PathOp::CubicTo(a, b, p) => {
775 flatten_cubic(cur, a, b, p, out);
776 cur = p;
777 }
778 PathOp::ArcTo {
779 rx,
780 ry,
781 rotation,
782 large,
783 sweep,
784 to,
785 } => {
786 flatten_arc(cur, rx, ry, rotation, large, sweep, to, out);
787 cur = to;
788 }
789 PathOp::MoveTo(_) | PathOp::Close => unreachable!(),
790 }
791 }
792 }
793 }
794 close(out, &mut open, contour_start, false);
795}
796
797fn pieces(dd: f32, k: f32) -> usize {
798 ((dd * k / FLATTEN_TOLERANCE).sqrt().ceil() as usize).clamp(1, MAX_PIECES)
799}
800
801fn flatten_quad(p0: Vec2, c: Vec2, p1: Vec2, out: &mut Vec<Vec2>) {
802 let dd = Vec2::new(p0.x - 2.0 * c.x + p1.x, p0.y - 2.0 * c.y + p1.y);
803 let n = pieces((dd.x * dd.x + dd.y * dd.y).sqrt(), 0.25);
804 for i in 1..=n {
805 let t = i as f32 / n as f32;
806 let u = 1.0 - t;
807 out.push(Vec2::new(
808 u * u * p0.x + 2.0 * u * t * c.x + t * t * p1.x,
809 u * u * p0.y + 2.0 * u * t * c.y + t * t * p1.y,
810 ));
811 }
812}
813
814fn flatten_cubic(p0: Vec2, a: Vec2, b: Vec2, p1: Vec2, out: &mut Vec<Vec2>) {
815 let d1 = Vec2::new(p0.x - 2.0 * a.x + b.x, p0.y - 2.0 * a.y + b.y);
816 let d2 = Vec2::new(a.x - 2.0 * b.x + p1.x, a.y - 2.0 * b.y + p1.y);
817 let dd = (d1.x * d1.x + d1.y * d1.y)
818 .max(d2.x * d2.x + d2.y * d2.y)
819 .sqrt();
820 let n = pieces(dd, 0.75);
821 for i in 1..=n {
822 let t = i as f32 / n as f32;
823 let u = 1.0 - t;
824 let (uu, tt) = (u * u, t * t);
825 out.push(Vec2::new(
826 uu * u * p0.x + 3.0 * uu * t * a.x + 3.0 * u * tt * b.x + tt * t * p1.x,
827 uu * u * p0.y + 3.0 * uu * t * a.y + 3.0 * u * tt * b.y + tt * t * p1.y,
828 ));
829 }
830}
831
832#[allow(clippy::too_many_arguments)]
837fn arc_center(
838 from: Vec2,
839 rx: f32,
840 ry: f32,
841 rotation: f32,
842 large: bool,
843 sweep: bool,
844 to: Vec2,
845) -> Option<(Vec2, f32, f32, f32, f32, f32)> {
846 if (from.x - to.x).abs() < 1e-6 && (from.y - to.y).abs() < 1e-6 {
847 return None;
848 }
849 let (mut rx, mut ry) = (rx.abs(), ry.abs());
850 if rx < 1e-6 || ry < 1e-6 {
851 return None;
852 }
853 let phi = rotation.to_radians();
854 let (sin_phi, cos_phi) = phi.sin_cos();
855 let dx = (from.x - to.x) * 0.5;
856 let dy = (from.y - to.y) * 0.5;
857 let x1 = cos_phi * dx + sin_phi * dy;
858 let y1 = -sin_phi * dx + cos_phi * dy;
859 let lambda = (x1 * x1) / (rx * rx) + (y1 * y1) / (ry * ry);
860 if lambda > 1.0 {
861 let s = lambda.sqrt();
862 rx *= s;
863 ry *= s;
864 }
865 let num = (rx * rx * ry * ry - rx * rx * y1 * y1 - ry * ry * x1 * x1).max(0.0);
866 let den = rx * rx * y1 * y1 + ry * ry * x1 * x1;
867 let mut coef = if den > 0.0 { (num / den).sqrt() } else { 0.0 };
868 if large == sweep {
869 coef = -coef;
870 }
871 let cx1 = coef * rx * y1 / ry;
872 let cy1 = -coef * ry * x1 / rx;
873 let cx = cos_phi * cx1 - sin_phi * cy1 + (from.x + to.x) * 0.5;
874 let cy = sin_phi * cx1 + cos_phi * cy1 + (from.y + to.y) * 0.5;
875 let ux = (x1 - cx1) / rx;
876 let uy = (y1 - cy1) / ry;
877 let vx = (-x1 - cx1) / rx;
878 let vy = (-y1 - cy1) / ry;
879 let angle = |ax: f32, ay: f32, bx: f32, by: f32| -> f32 {
880 let dot = ax * bx + ay * by;
881 let len = ((ax * ax + ay * ay) * (bx * bx + by * by)).sqrt();
882 let mut a = (dot / len).clamp(-1.0, 1.0).acos();
883 if ax * by - ay * bx < 0.0 {
884 a = -a;
885 }
886 a
887 };
888 let theta = angle(1.0, 0.0, ux, uy);
889 let mut delta = angle(ux, uy, vx, vy);
890 let tau = std::f32::consts::TAU;
891 if !sweep && delta > 0.0 {
892 delta -= tau;
893 } else if sweep && delta < 0.0 {
894 delta += tau;
895 }
896 Some((Vec2::new(cx, cy), rx, ry, phi, theta, delta))
897}
898
899#[allow(clippy::too_many_arguments)]
900fn flatten_arc(
901 from: Vec2,
902 rx: f32,
903 ry: f32,
904 rotation: f32,
905 large: bool,
906 sweep: bool,
907 to: Vec2,
908 out: &mut Vec<Vec2>,
909) {
910 let Some((c, rx, ry, phi, theta, delta)) = arc_center(from, rx, ry, rotation, large, sweep, to)
911 else {
912 out.push(to);
913 return;
914 };
915 let r = rx.max(ry);
916 let step = 2.0 * (1.0 - FLATTEN_TOLERANCE / r).clamp(-1.0, 1.0).acos();
918 let n = if step > 0.0 {
919 ((delta.abs() / step).ceil() as usize).clamp(1, MAX_PIECES)
920 } else {
921 1
922 };
923 let (sin_phi, cos_phi) = phi.sin_cos();
924 for i in 1..=n {
925 let a = theta + delta * i as f32 / n as f32;
926 let (sa, ca) = a.sin_cos();
927 let x = rx * ca;
928 let y = ry * sa;
929 out.push(Vec2::new(
930 c.x + cos_phi * x - sin_phi * y,
931 c.y + sin_phi * x + cos_phi * y,
932 ));
933 }
934 if let Some(last) = out.last_mut() {
936 *last = to;
937 }
938}
939
940pub fn in_path(p: Vec2, pts: &[Vec2], rule: FillRule) -> bool {
945 let mut winding = 0i32;
946 let mut crossings = 0u32;
947 for contour in pts.split(|v| v.x.is_nan()) {
948 let n = contour.len();
949 if n < 3 {
950 continue;
951 }
952 let mut j = n - 1;
953 for i in 0..n {
954 let (a, b) = (contour[i], contour[j]);
955 if (a.y > p.y) != (b.y > p.y) {
956 let x = a.x + (p.y - a.y) / (b.y - a.y) * (b.x - a.x);
957 if p.x < x {
958 crossings += 1;
959 winding += if b.y > a.y { 1 } else { -1 };
960 }
961 }
962 j = i;
963 }
964 }
965 match rule {
966 FillRule::NonZero => winding != 0,
967 FillRule::EvenOdd => crossings % 2 == 1,
968 }
969}
970
971pub fn bounds(pts: &[Vec2]) -> Option<Rect> {
974 let mut it = pts.iter().filter(|p| !p.x.is_nan());
975 let first = *it.next()?;
976 let (mut min, mut max) = (first, first);
977 for p in it {
978 min.x = min.x.min(p.x);
979 min.y = min.y.min(p.y);
980 max.x = max.x.max(p.x);
981 max.y = max.y.max(p.y);
982 }
983 Some(Rect::new(min.x, min.y, max.x - min.x, max.y - min.y))
984}
985
986pub fn hash_ops(ops: &[PathOp]) -> u64 {
989 const OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
990 const PRIME: u64 = 0x0000_0100_0000_01b3;
991 let mut h = OFFSET;
992 let mut mix = |v: u32| {
993 for byte in v.to_le_bytes() {
994 h ^= u64::from(byte);
995 h = h.wrapping_mul(PRIME);
996 }
997 };
998 let mut scratch = Vec::with_capacity(8);
999 for op in ops {
1000 scratch.clear();
1001 op.write(&mut scratch);
1002 for v in &scratch {
1003 mix(v.to_bits());
1004 }
1005 }
1006 h
1007}
1008
1009#[derive(Clone, Copy, Debug, PartialEq)]
1014pub enum MaskPaint {
1015 Fill(FillRule),
1016 Stroke(f32),
1017 Dashed(f32, DashCut),
1018}
1019
1020pub type DashCut = crate::line::Cut;
1024
1025pub fn rasterize(
1030 ops: &[PathOp],
1031 scale: f32,
1032 bin: (u8, u8),
1033 w: u32,
1034 h: u32,
1035 paint: MaskPaint,
1036) -> Vec<u8> {
1037 let off = Vec2::new(f32::from(bin.0) * 0.25, f32::from(bin.1) * 0.25);
1038 rasterize_at(ops, scale, off, w, h, paint)
1039}
1040
1041pub fn rasterize_at(
1045 ops: &[PathOp],
1046 scale: f32,
1047 off: Vec2,
1048 w: u32,
1049 h: u32,
1050 paint: MaskPaint,
1051) -> Vec<u8> {
1052 use swash::zeno::{Cap, Command, Fill, Join, Mask, PathBuilder, Point, Stroke};
1053 let len = (w as usize) * (h as usize);
1054 let mut buf = vec![0u8; len];
1055 if len == 0 || ops.is_empty() {
1056 return buf;
1057 }
1058 let at = |p: Vec2| Point::new(p.x * scale + off.x, p.y * scale + off.y);
1059 let mut cmds: Vec<Command> = Vec::with_capacity(ops.len() + 1);
1060 let mut cur = Vec2::ZERO;
1061 let mut start = Vec2::ZERO;
1062 let mut open = false;
1063 for op in ops {
1064 match *op {
1065 PathOp::MoveTo(p) => {
1066 cmds.move_to(at(p));
1067 cur = p;
1068 start = p;
1069 open = true;
1070 }
1071 PathOp::Close => {
1072 if open {
1073 cmds.close();
1074 }
1075 cur = start;
1078 open = false;
1079 }
1080 _ => {
1081 if !open {
1082 cmds.move_to(at(cur));
1083 start = cur;
1084 open = true;
1085 }
1086 match *op {
1087 PathOp::LineTo(p) => {
1088 cmds.line_to(at(p));
1089 cur = p;
1090 }
1091 PathOp::QuadTo(c, p) => {
1092 cmds.quad_to(at(c), at(p));
1093 cur = p;
1094 }
1095 PathOp::CubicTo(a, b, p) => {
1096 cmds.curve_to(at(a), at(b), at(p));
1097 cur = p;
1098 }
1099 PathOp::ArcTo {
1100 rx,
1101 ry,
1102 rotation,
1103 large,
1104 sweep,
1105 to,
1106 } => {
1107 use swash::zeno::{Angle, ArcSize, ArcSweep};
1108 cmds.arc_to(
1109 rx * scale,
1110 ry * scale,
1111 Angle::from_degrees(rotation),
1112 if large {
1113 ArcSize::Large
1114 } else {
1115 ArcSize::Small
1116 },
1117 if sweep {
1118 ArcSweep::Positive
1119 } else {
1120 ArcSweep::Negative
1121 },
1122 at(to),
1123 );
1124 cur = to;
1125 }
1126 PathOp::MoveTo(_) | PathOp::Close => unreachable!(),
1127 }
1128 }
1129 }
1130 }
1131 match paint {
1132 MaskPaint::Fill(rule) => {
1133 let fill = match rule {
1134 FillRule::NonZero => Fill::NonZero,
1135 FillRule::EvenOdd => Fill::EvenOdd,
1136 };
1137 Mask::new(&cmds[..])
1138 .style(fill)
1139 .size(w, h)
1140 .render_into(&mut buf, None);
1141 if buf.iter().all(|&a| a == 0) {
1145 return buf;
1146 }
1147 let mut edge = vec![0u8; len];
1150 let mut stroke = Stroke::new(1.0);
1151 stroke.join(Join::Round).cap(Cap::Round);
1152 Mask::new(&cmds[..])
1153 .style(stroke)
1154 .size(w, h)
1155 .render_into(&mut edge, None);
1156 for (a, e) in buf.iter_mut().zip(edge) {
1157 *a = (*a).max(e);
1158 }
1159 }
1160 MaskPaint::Stroke(width) => {
1161 let mut stroke = Stroke::new(width.max(0.0));
1162 stroke.join(Join::Round).cap(Cap::Round);
1163 Mask::new(&cmds[..])
1164 .style(stroke)
1165 .size(w, h)
1166 .render_into(&mut buf, None);
1167 }
1168 MaskPaint::Dashed(width, cut) => {
1169 let mut stroke = Stroke::new(width.max(0.0));
1170 stroke.join(Join::Round).cap(Cap::Round);
1171 if !cut.finer_than(1.0) {
1174 stroke.dash(&cut.lens, cut.offset);
1175 }
1176 Mask::new(&cmds[..])
1177 .style(stroke)
1178 .size(w, h)
1179 .render_into(&mut buf, None);
1180 }
1181 }
1182 buf
1183}
1184
1185pub const MAX_ATLAS_MASK_TEXELS: u64 = 2048 * 2048;
1189
1190pub const MAX_MASK_SIDE: u32 = 8192;
1198
1199pub struct PathTexture {
1203 pub id: crate::resources::ImageId,
1204 pub w: u32,
1205 pub h: u32,
1206 pub rgba: std::sync::Arc<Vec<u8>>,
1207 last_used: u64,
1208}
1209
1210pub struct PathTextures {
1218 by_key: rustc_hash::FxHashMap<u64, PathTexture>,
1219 session: crate::session::Session,
1220}
1221
1222impl Drop for PathTextures {
1223 fn drop(&mut self) {
1224 for t in self.by_key.values() {
1225 crate::resources::unmint_image(t.id);
1226 }
1227 if let Some(mut sess) = self.session.try_state() {
1231 sess.dropped
1232 .images
1233 .extend(self.by_key.values().map(|t| t.id));
1234 }
1235 }
1236}
1237
1238impl PathTextures {
1239 pub(crate) fn new(session: crate::session::Session) -> Self {
1240 Self {
1241 by_key: Default::default(),
1242 session,
1243 }
1244 }
1245
1246 pub(crate) fn pixels(
1249 &self,
1250 id: crate::resources::ImageId,
1251 ) -> Option<(u32, u32, std::sync::Arc<Vec<u8>>)> {
1252 self.by_key
1253 .values()
1254 .find(|t| t.id == id)
1255 .map(|t| (t.w, t.h, t.rgba.clone()))
1256 }
1257
1258 pub(crate) fn get_or_make(
1261 &mut self,
1262 key: u64,
1263 w: u32,
1264 h: u32,
1265 frame: u64,
1266 session: crate::resources::SessionId,
1267 coverage: impl FnOnce() -> Vec<u8>,
1268 ) -> &PathTexture {
1269 if let Some(tex) = self.by_key.get(&key) {
1270 debug_assert!(tex.w == w && tex.h == h, "a mask key names one size");
1271 }
1272 let tex = self.by_key.entry(key).or_insert_with(|| {
1273 let mask = coverage();
1274 let mut rgba = Vec::with_capacity(mask.len() * 4);
1275 for &a in &mask {
1276 rgba.extend_from_slice(&[255, 255, 255, a]);
1277 }
1278 PathTexture {
1279 id: crate::resources::mint_image(session),
1280 w,
1281 h,
1282 rgba: std::sync::Arc::new(rgba),
1283 last_used: frame,
1284 }
1285 });
1286 tex.last_used = frame;
1287 tex
1288 }
1289
1290 pub(crate) fn sweep(&mut self, frame: u64) -> Vec<crate::resources::ImageId> {
1293 let mut gone = Vec::new();
1294 self.by_key.retain(|_, t| {
1295 if t.last_used + 1 < frame {
1296 crate::resources::unmint_image(t.id);
1297 gone.push(t.id);
1298 false
1299 } else {
1300 true
1301 }
1302 });
1303 gone
1304 }
1305
1306 pub fn len(&self) -> usize {
1308 self.by_key.len()
1309 }
1310
1311 pub fn is_empty(&self) -> bool {
1312 self.by_key.is_empty()
1313 }
1314}
1315
1316pub(crate) const TURNED_BIN: (u8, u8) = (0xff, 0xff);
1321
1322pub(crate) fn mask_key(hash: u64, scale: f32, bin: (u8, u8), paint: MaskPaint) -> u64 {
1323 const PRIME: u64 = 0x0000_0100_0000_01b3;
1324 let mut h = hash;
1325 let mut mix = |v: u64| {
1326 h ^= v;
1327 h = h.wrapping_mul(PRIME);
1328 };
1329 mix(u64::from(scale.to_bits()));
1330 mix(u64::from(bin.0) | (u64::from(bin.1) << 8));
1331 match paint {
1332 MaskPaint::Fill(rule) => mix(0x1000 | rule.index() as u64),
1333 MaskPaint::Stroke(w) => mix(0x2000_0000_0000 | u64::from(w.to_bits())),
1334 MaskPaint::Dashed(w, cut) => {
1335 mix(0x3000_0000_0000 | u64::from(w.to_bits()));
1336 mix(cut.hash());
1337 }
1338 }
1339 h
1340}
1341
1342pub const ANIMATING_WINDOW: u64 = 8;
1348
1349pub const SETTLED_AFTER: u64 = 120;
1354
1355#[derive(Clone, Copy, Debug)]
1358pub(crate) struct Motion {
1359 pub hash: u64,
1361 pub seen: u64,
1362 pub changed: u64,
1364 pub animating: bool,
1367}
1368
1369pub(crate) struct Parsed {
1373 pub hash: u64,
1375 pub len: usize,
1376 pub seen: u64,
1377 pub ops: Vec<PathOp>,
1378}
1379
1380#[derive(Clone, Copy, Debug, PartialEq)]
1383pub(crate) struct Run {
1384 pub first: u32,
1385 pub len: u32,
1386 pub rule: FillRule,
1387 pub stroke_w: f32,
1389 pub dash: Option<crate::line::Cut>,
1391 pub hash: u64,
1393 pub angle: Option<f32>,
1396 pub animating: bool,
1400}
1401
1402#[derive(Default)]
1404pub struct PathStore {
1405 runs: Kept<Run>,
1406 ops: Kept<PathOp>,
1407 scratch: Vec<Vec2>,
1408}
1409
1410impl PathStore {
1411 pub(crate) fn begin_frame(&mut self, keep_prev: bool) {
1414 self.runs.begin(keep_prev);
1415 self.ops.begin(keep_prev);
1416 }
1417
1418 pub(crate) fn push(
1422 &mut self,
1423 ops: &[PathOp],
1424 rule: FillRule,
1425 stroke_w: f32,
1426 dash: Option<crate::line::Cut>,
1427 turn: Option<Turn>,
1428 ) -> Option<(PathId, Rect)> {
1429 self.scratch.clear();
1430 flatten(ops, &mut self.scratch);
1431 let b = bounds(&self.scratch)?;
1432 let pad = 2.0 + stroke_w * 0.5;
1436 let rect = match turn {
1437 None => Rect::new(b.x - pad, b.y - pad, b.w + 2.0 * pad, b.h + 2.0 * pad),
1438 Some(turn) => {
1442 let c = turn
1443 .pivot
1444 .unwrap_or(Vec2::new(b.x + b.w * 0.5, b.y + b.h * 0.5));
1445 let far = self
1446 .scratch
1447 .iter()
1448 .filter(|p| !p.x.is_nan())
1449 .map(|p| (p.x - c.x).hypot(p.y - c.y))
1450 .fold(0.0f32, f32::max);
1451 let half = far + pad;
1452 Rect::new(c.x - half, c.y - half, 2.0 * half, 2.0 * half)
1453 }
1454 };
1455 let origin = Vec2::new(rect.x, rect.y);
1456 let first = self.ops.len();
1457 let shift = Vec2::new(-origin.x, -origin.y);
1458 self.ops.extend(ops.iter().map(|op| op.shifted(shift)));
1459 let hash = hash_ops(&self.ops[first..]);
1460 let id = PathId(self.runs.len() as u32);
1461 self.runs.push(Run {
1462 first: first as u32,
1463 len: (self.ops.len() - first) as u32,
1464 rule,
1465 stroke_w,
1466 dash,
1467 hash,
1468 angle: turn.map(Turn::radians),
1469 animating: false,
1470 });
1471 Some((id, rect))
1472 }
1473
1474 pub(crate) fn set_animating(&mut self, id: PathId) {
1475 if let Some(run) = self.runs.get_mut(id.0 as usize) {
1476 run.animating = true;
1477 }
1478 }
1479
1480 pub(crate) fn run(&self, id: PathId) -> (Run, &[PathOp]) {
1482 let run = self.runs[id.0 as usize];
1483 (
1484 run,
1485 &self.ops[run.first as usize..(run.first + run.len) as usize],
1486 )
1487 }
1488
1489 pub(crate) fn prev_run(&self, id: PathId) -> Option<(Run, &[PathOp])> {
1492 let run = *self.runs.prev().get(id.0 as usize)?;
1493 Some((
1494 run,
1495 &self.ops.prev()[run.first as usize..(run.first + run.len) as usize],
1496 ))
1497 }
1498
1499 pub fn len(&self) -> usize {
1500 self.runs.len()
1501 }
1502
1503 pub fn is_empty(&self) -> bool {
1504 self.runs.is_empty()
1505 }
1506}
1507
1508#[cfg(test)]
1509mod tests {
1510 use super::*;
1511
1512 fn pts(ops: &[PathOp]) -> Vec<Vec2> {
1513 let mut out = Vec::new();
1514 flatten(ops, &mut out);
1515 out
1516 }
1517
1518 #[test]
1519 fn parses_absolute_and_relative_commands_alike() {
1520 let a = Path::parse("M10 10 L20 10 L20 20 Z").unwrap();
1521 let b = Path::parse("m10,10 l10 0 l0 10 z").unwrap();
1522 assert_eq!(a.ops(), b.ops());
1523 assert_eq!(a.ops().len(), 4);
1524 let c = Path::parse("M10 10 20 10V20z").unwrap();
1526 assert_eq!(a.ops(), c.ops());
1527 let d = Path::parse("M.5.5-1-1").unwrap();
1528 assert_eq!(
1529 d.ops(),
1530 &[
1531 PathOp::MoveTo(Vec2::new(0.5, 0.5)),
1532 PathOp::LineTo(Vec2::new(-1.0, -1.0))
1533 ]
1534 );
1535 }
1536
1537 #[test]
1538 fn smooth_curves_reflect_the_last_control_point() {
1539 let p = Path::parse("M0 0 C 10 0 20 10 20 20 S 30 40 40 40").unwrap();
1540 match p.ops()[2] {
1541 PathOp::CubicTo(c1, c2, to) => {
1542 assert_eq!(c1, Vec2::new(20.0, 30.0));
1543 assert_eq!(c2, Vec2::new(30.0, 40.0));
1544 assert_eq!(to, Vec2::new(40.0, 40.0));
1545 }
1546 op => panic!("{op:?}"),
1547 }
1548 let q = Path::parse("M0 0 Q 10 10 20 0 T 40 0").unwrap();
1549 match q.ops()[2] {
1550 PathOp::QuadTo(c, to) => {
1551 assert_eq!(c, Vec2::new(30.0, -10.0));
1552 assert_eq!(to, Vec2::new(40.0, 0.0));
1553 }
1554 op => panic!("{op:?}"),
1555 }
1556 }
1557
1558 #[test]
1559 fn a_malformed_string_names_its_byte() {
1560 let e = Path::parse("M10 10 L20").unwrap_err();
1561 assert_eq!(e.what, "a number");
1562 assert_eq!(e.at, 10);
1563 let e = Path::parse("10 10").unwrap_err();
1564 assert_eq!(e.what, "a command letter");
1565 let e = Path::parse("M0 0 X").unwrap_err();
1566 assert_eq!(e.what, "one of M L H V C S Q T A Z");
1567 let e = Path::parse("M0 0 A 5 5 0 2 0 10 10").unwrap_err();
1568 assert_eq!(e.what, "an arc flag (0 or 1)");
1569 }
1570
1571 #[test]
1572 fn the_wire_form_round_trips() {
1573 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();
1574 let f = p.to_floats();
1575 assert_eq!(f.len(), 3 + 3 + 5 + 7 + 8 + 1);
1576 assert_eq!(Path::from_floats(&f).unwrap().ops(), p.ops());
1577 assert!(Path::from_floats(&[9.0]).is_err());
1578 assert!(Path::from_floats(&[OP_LINE, 1.0]).is_err());
1579 }
1580
1581 #[test]
1582 fn a_square_flattens_to_its_corners_and_hits_inside() {
1583 let p = Path::parse("M0 0 H10 V10 H0 Z").unwrap();
1584 let v = pts(p.ops());
1585 assert_eq!(v.len(), 5);
1586 assert!(v[4].x.is_nan());
1587 assert!(in_path(Vec2::new(5.0, 5.0), &v, FillRule::NonZero));
1588 assert!(in_path(Vec2::new(5.0, 5.0), &v, FillRule::EvenOdd));
1589 assert!(!in_path(Vec2::new(15.0, 5.0), &v, FillRule::NonZero));
1590 assert_eq!(bounds(&v), Some(Rect::new(0.0, 0.0, 10.0, 10.0)));
1591 }
1592
1593 #[test]
1594 fn a_ring_is_a_ring_by_either_rule_when_its_contours_oppose() {
1595 let p = Path::parse("M0 0 H20 V20 H0 Z M5 5 V15 H15 V5 Z").unwrap();
1597 let v = pts(p.ops());
1598 let hole = Vec2::new(10.0, 10.0);
1599 let band = Vec2::new(2.0, 10.0);
1600 assert!(!in_path(hole, &v, FillRule::NonZero));
1601 assert!(!in_path(hole, &v, FillRule::EvenOdd));
1602 assert!(in_path(band, &v, FillRule::NonZero));
1603 assert!(in_path(band, &v, FillRule::EvenOdd));
1604 let same = Path::parse("M0 0 H20 V20 H0 Z M5 5 H15 V15 H5 Z").unwrap();
1606 let v = pts(same.ops());
1607 assert!(in_path(hole, &v, FillRule::NonZero));
1608 assert!(!in_path(hole, &v, FillRule::EvenOdd));
1609 }
1610
1611 #[test]
1612 fn an_arc_flattens_onto_its_circle() {
1613 let p = Path::parse("M0 0 A10 10 0 0 1 20 0").unwrap();
1615 let v = pts(p.ops());
1616 assert!(v.len() >= 9, "{}", v.len());
1617 for q in v.iter().filter(|q| !q.x.is_nan()) {
1620 let r = ((q.x - 10.0).powi(2) + q.y.powi(2)).sqrt();
1621 assert!((r - 10.0).abs() < 0.3, "{q:?} is {r} from the centre");
1622 assert!(q.y <= 0.01, "{q:?} bows the wrong way");
1623 }
1624 assert_eq!(v[v.len() - 2], Vec2::new(20.0, 0.0));
1625 let down = Path::parse("M0 0 A10 10 0 0 0 20 0").unwrap();
1627 assert!(
1628 pts(down.ops())
1629 .iter()
1630 .filter(|q| !q.x.is_nan())
1631 .all(|q| q.y >= -0.01)
1632 );
1633 }
1634
1635 #[test]
1636 fn a_sector_is_a_wedge_and_a_full_turn_is_a_disc() {
1637 let w = Path::sector(50.0, 50.0, 40.0, 0.0, 0.0, 0.25);
1638 let v = pts(w.ops());
1639 assert!(in_path(Vec2::new(70.0, 70.0), &v, FillRule::NonZero));
1640 assert!(!in_path(Vec2::new(30.0, 30.0), &v, FillRule::NonZero));
1641 let d = Path::sector(50.0, 50.0, 40.0, 20.0, 0.0, 1.0);
1642 let v = pts(d.ops());
1643 assert!(in_path(Vec2::new(50.0, 20.0), &v, FillRule::NonZero));
1644 assert!(!in_path(Vec2::new(50.0, 50.0), &v, FillRule::NonZero));
1645 }
1646
1647 #[test]
1648 fn a_draw_after_a_close_starts_where_the_subpath_began() {
1649 let p = Path::parse("M10 10 H30 V30 Z L10 40 L30 40 Z").unwrap();
1652 let v = pts(p.ops());
1653 let m = rasterize(
1654 p.ops(),
1655 1.0,
1656 (0, 0),
1657 48,
1658 48,
1659 MaskPaint::Fill(FillRule::NonZero),
1660 );
1661 for (x, y) in [(12usize, 25usize), (28, 32), (28, 20), (14, 36)] {
1662 let hit = in_path(
1663 Vec2::new(x as f32 + 0.5, y as f32 + 0.5),
1664 &v,
1665 FillRule::NonZero,
1666 );
1667 assert_eq!(m[y * 48 + x] == 255, hit, "({x}, {y})");
1668 }
1669 assert_eq!(m[25 * 48 + 12], 255);
1670 assert_eq!(m[32 * 48 + 28], 0);
1671 }
1672
1673 #[test]
1674 fn a_strokes_pieces_close_only_what_its_z_closed() {
1675 let open = Path::parse("M0 0 L10 0 L10 10").unwrap();
1676 let mut v = Vec::new();
1677 flatten_stroke(open.ops(), &mut v);
1678 assert_eq!(v.len(), 4);
1679 assert_eq!(v[2], Vec2::new(10.0, 10.0));
1680 assert!(v[3].x.is_nan());
1681 let closed = Path::parse("M0 0 L10 0 L10 10 Z").unwrap();
1682 v.clear();
1683 flatten_stroke(closed.ops(), &mut v);
1684 assert_eq!(v.len(), 5);
1685 assert_eq!(v[3], Vec2::ZERO);
1686 assert!(v[4].x.is_nan());
1687 }
1688
1689 #[test]
1690 fn the_hash_follows_the_ops_and_nothing_else() {
1691 let a = Path::parse("M0 0 L10 0 L10 10 Z").unwrap();
1692 let b = Path::parse("M0 0 L10 0 L10 10 Z")
1693 .unwrap()
1694 .fill_rule(FillRule::EvenOdd);
1695 let c = Path::parse("M0 0 L10 0 L10 11 Z").unwrap();
1696 assert_eq!(hash_ops(a.ops()), hash_ops(b.ops()));
1697 assert_ne!(hash_ops(a.ops()), hash_ops(c.ops()));
1698 }
1699
1700 #[test]
1701 fn a_filled_square_is_opaque_inside_and_bleeds_past_its_edge() {
1702 let p = Path::parse("M2 2 H12 V12 H2 Z").unwrap();
1703 let m = rasterize(
1704 p.ops(),
1705 1.0,
1706 (0, 0),
1707 16,
1708 16,
1709 MaskPaint::Fill(FillRule::NonZero),
1710 );
1711 let at = |x: usize, y: usize| m[y * 16 + x];
1712 assert_eq!(at(7, 7), 255);
1713 assert_eq!(at(0, 0), 0);
1714 assert_eq!(at(14, 7), 0);
1715 assert!(at(1, 7) > 100 && at(1, 7) < 200, "{}", at(1, 7));
1718 assert_eq!(at(2, 7), 255);
1719 let q = Path::parse("M12 2 H22 V12 H12 Z").unwrap();
1722 let n = rasterize(
1723 q.ops(),
1724 1.0,
1725 (0, 0),
1726 24,
1727 16,
1728 MaskPaint::Fill(FillRule::NonZero),
1729 );
1730 let (a, b) = (
1731 f32::from(at(12, 7)) / 255.0,
1732 f32::from(n[7 * 24 + 12]) / 255.0,
1733 );
1734 assert!(a + b * (1.0 - a) > 0.99, "{a} over {b}");
1735 let (a, b) = (
1736 f32::from(at(11, 7)) / 255.0,
1737 f32::from(n[7 * 24 + 11]) / 255.0,
1738 );
1739 assert!(a + b * (1.0 - a) > 0.99, "{a} over {b}");
1740 }
1741
1742 #[test]
1746 fn a_dashed_stroke_is_marks_and_gaps() {
1747 let p = Path::parse("M4 8 H44").unwrap();
1748 let row = |dash: crate::line::Dash| {
1749 let paint = MaskPaint::Dashed(2.0, dash.cut(2.0).unwrap());
1750 let m = rasterize(p.ops(), 1.0, (0, 0), 48, 16, paint);
1751 (0..48).map(|x| m[8 * 48 + x] > 127).collect::<Vec<_>>()
1753 };
1754 let on = |r: &[bool], x: std::ops::Range<usize>| r[x].iter().all(|&b| b);
1755 let off = |r: &[bool], x: std::ops::Range<usize>| r[x].iter().all(|&b| !b);
1756 let r = row(crate::line::Dash::new(6.0, 4.0));
1758 assert!(on(&r, 3..9) && off(&r, 10..12) && on(&r, 13..19), "{r:?}");
1759 let r = row(crate::line::Dash::new(2.0, 6.0));
1761 assert!(on(&r, 3..5) && off(&r, 6..10) && on(&r, 11..13), "{r:?}");
1762 let r = row(crate::line::Dash::new(6.0, 4.0).offset(5.0));
1764 assert!(off(&r, 6..7) && on(&r, 8..14), "{r:?}");
1765 }
1766
1767 #[test]
1768 fn a_stroke_covers_the_outline_and_not_the_inside() {
1769 let p = Path::parse("M2 2 H12 V12 H2 Z").unwrap();
1770 let m = rasterize(p.ops(), 1.0, (0, 0), 16, 16, MaskPaint::Stroke(2.0));
1771 let at = |x: usize, y: usize| m[y * 16 + x];
1772 assert_eq!(at(7, 7), 0);
1773 assert!(at(2, 7) > 200, "{}", at(2, 7));
1774 assert!(at(1, 7) > 200, "{}", at(1, 7));
1775 }
1776
1777 #[test]
1778 fn the_store_boxes_a_path_and_keeps_the_ops_relative() {
1779 let mut store = PathStore::default();
1780 store.begin_frame(false);
1781 let p = Path::parse("M10 20 H30 V40 Z").unwrap();
1782 let (id, rect) = store
1783 .push(p.ops(), FillRule::NonZero, 0.0, None, None)
1784 .unwrap();
1785 assert_eq!(rect, Rect::new(8.0, 18.0, 24.0, 24.0));
1786 let (run, ops) = store.run(id);
1787 assert_eq!(ops[0], PathOp::MoveTo(Vec2::new(2.0, 2.0)));
1788 assert_eq!(run.len, 4);
1789 assert!(
1790 store
1791 .push(
1792 &[PathOp::MoveTo(Vec2::ZERO)],
1793 FillRule::NonZero,
1794 0.0,
1795 None,
1796 None
1797 )
1798 .is_none()
1799 );
1800 let (_, rect) = store
1802 .push(p.ops(), FillRule::NonZero, 4.0, None, None)
1803 .unwrap();
1804 assert_eq!(rect, Rect::new(6.0, 16.0, 28.0, 28.0));
1805 let turn = |t: f32| Turn {
1808 turns: t,
1809 pivot: Some(Vec2::new(10.0, 20.0)),
1810 };
1811 let (a, ra) = store
1812 .push(p.ops(), FillRule::NonZero, 0.0, None, Some(turn(0.0)))
1813 .unwrap();
1814 let (b, rb) = store
1815 .push(p.ops(), FillRule::NonZero, 0.0, None, Some(turn(0.3)))
1816 .unwrap();
1817 let far = (20.0f32 * 20.0 + 20.0 * 20.0).sqrt() + 2.0;
1818 assert_eq!(ra, Rect::new(10.0 - far, 20.0 - far, 2.0 * far, 2.0 * far));
1819 assert_eq!(ra, rb);
1820 assert_eq!(store.run(a).0.hash, store.run(b).0.hash);
1821 assert_eq!(store.run(a).1, store.run(b).1);
1822 assert_eq!(store.run(b).0.angle, Some(0.3 * std::f32::consts::TAU));
1823 store.begin_frame(true);
1824 assert!(store.prev_run(id).is_some());
1825 assert!(store.is_empty());
1826 }
1827
1828 #[test]
1831 fn a_fill_with_no_area_is_an_empty_mask() {
1832 for p in [
1833 Path::sector(16.0, 16.0, 12.0, 8.0, 0.0, 0.0),
1834 Path::parse("M2 2 L20 2 Z").unwrap(),
1835 ] {
1836 let m = rasterize(
1837 p.ops(),
1838 1.0,
1839 (0, 0),
1840 32,
1841 32,
1842 MaskPaint::Fill(FillRule::NonZero),
1843 );
1844 assert!(m.iter().all(|&a| a == 0));
1845 }
1846 }
1847}