1extern crate alloc;
24use alloc::vec::Vec;
25
26#[derive(Debug, Clone, Copy, PartialEq)]
28pub struct Matrix {
29 pub a: f32,
30 pub b: f32,
31 pub c: f32,
32 pub d: f32,
33 pub e: f32,
34 pub f: f32,
35}
36
37impl Default for Matrix {
38 fn default() -> Self {
39 Self::identity()
40 }
41}
42
43impl Matrix {
44 pub fn identity() -> Self {
45 Matrix { a: 1.0, b: 0.0, c: 0.0, d: 1.0, e: 0.0, f: 0.0 }
46 }
47
48 pub fn translate(tx: f32, ty: f32) -> Self {
49 Matrix { a: 1.0, b: 0.0, c: 0.0, d: 1.0, e: tx, f: ty }
50 }
51
52 pub fn scale(sx: f32, sy: f32) -> Self {
53 Matrix { a: sx, b: 0.0, c: 0.0, d: sy, e: 0.0, f: 0.0 }
54 }
55
56 pub fn rotate(rad: f32) -> Self {
59 let (s, c) = (libm::sinf(rad), libm::cosf(rad));
60 Matrix { a: c, b: s, c: -s, d: c, e: 0.0, f: 0.0 }
61 }
62
63 pub fn multiply(&self, m: &Matrix) -> Matrix {
68 Matrix {
69 a: self.a * m.a + self.c * m.b,
70 b: self.b * m.a + self.d * m.b,
71 c: self.a * m.c + self.c * m.d,
72 d: self.b * m.c + self.d * m.d,
73 e: self.a * m.e + self.c * m.f + self.e,
74 f: self.b * m.e + self.d * m.f + self.f,
75 }
76 }
77
78 pub fn apply(&self, x: f32, y: f32) -> (f32, f32) {
80 (
81 self.a * x + self.c * y + self.e,
82 self.b * x + self.d * y + self.f,
83 )
84 }
85
86 pub fn determinant(&self) -> f32 {
88 self.a * self.d - self.b * self.c
89 }
90
91 pub fn invert(&self) -> Option<Matrix> {
95 let det = self.determinant();
96 if !det.is_finite() || libm::fabsf(det) < 1e-12 {
97 return None;
98 }
99 let inv = 1.0 / det;
100 Some(Matrix {
101 a: self.d * inv,
102 b: -self.b * inv,
103 c: -self.c * inv,
104 d: self.a * inv,
105 e: (self.c * self.f - self.d * self.e) * inv,
106 f: (self.b * self.e - self.a * self.f) * inv,
107 })
108 }
109}
110
111#[derive(Debug, Clone, PartialEq)]
113pub struct SubPath {
114 pub points: Vec<(f32, f32)>,
116 pub closed: bool,
118}
119
120#[derive(Debug, Clone, Default)]
125pub struct PathBuilder {
126 subpaths: Vec<SubPath>,
127 current: Option<SubPath>,
128 last_user: Option<(f32, f32)>,
130}
131
132const ARC_SEGMENTS_PER_QUARTER: usize = 8;
134const BEZIER_SEGMENTS: usize = 16;
136
137impl PathBuilder {
138 pub fn new() -> Self {
139 Self::default()
140 }
141
142 fn flush(&mut self) {
147 if let Some(sp) = self.current.take() {
148 if sp.points.len() >= 2 {
149 self.subpaths.push(sp);
150 }
151 }
152 }
153
154 pub fn begin_path(&mut self) {
155 self.subpaths.clear();
156 self.current = None;
157 self.last_user = None;
158 }
159
160 pub fn move_to(&mut self, m: &Matrix, x: f32, y: f32) {
161 self.flush();
162 self.current = Some(SubPath {
163 points: alloc::vec![m.apply(x, y)],
164 closed: false,
165 });
166 self.last_user = Some((x, y));
167 }
168
169 pub fn line_to(&mut self, m: &Matrix, x: f32, y: f32) {
170 if self.current.is_none() {
172 self.move_to(m, x, y);
173 return;
174 }
175 if let Some(sp) = self.current.as_mut() {
176 sp.points.push(m.apply(x, y));
177 }
178 self.last_user = Some((x, y));
179 }
180
181 pub fn close_path(&mut self) {
182 if let Some(sp) = self.current.as_mut() {
183 sp.closed = true;
184 }
185 self.flush();
186 }
187
188 pub fn add_path(&mut self, other: &PathBuilder, transform: Option<&Matrix>) {
189 self.flush();
190 let id_mat = Matrix::identity();
191 let m = transform.unwrap_or(&id_mat);
192 for sp in &other.subpaths {
193 let pts = sp.points.iter().map(|p| m.apply(p.0, p.1)).collect();
194 self.subpaths.push(SubPath {
195 points: pts,
196 closed: sp.closed,
197 });
198 }
199 if let Some(ref sp) = other.current {
200 if sp.points.len() >= 2 {
201 let pts = sp.points.iter().map(|p| m.apply(p.0, p.1)).collect();
202 self.subpaths.push(SubPath {
203 points: pts,
204 closed: sp.closed,
205 });
206 }
207 }
208 if let Some((lx, ly)) = other.last_user {
209 let lp = m.apply(lx, ly);
210 self.last_user = Some((lp.0, lp.1));
211 }
212 }
213
214 pub fn bezier_curve_to(
216 &mut self,
217 m: &Matrix,
218 c1x: f32,
219 c1y: f32,
220 c2x: f32,
221 c2y: f32,
222 x: f32,
223 y: f32,
224 ) {
225 let (x0, y0) = match self.last_user {
226 Some(p) => p,
227 None => {
228 self.move_to(m, c1x, c1y);
229 (c1x, c1y)
230 }
231 };
232 for i in 1..=BEZIER_SEGMENTS {
233 let t = i as f32 / BEZIER_SEGMENTS as f32;
234 let mt = 1.0 - t;
235 let px = mt * mt * mt * x0
236 + 3.0 * mt * mt * t * c1x
237 + 3.0 * mt * t * t * c2x
238 + t * t * t * x;
239 let py = mt * mt * mt * y0
240 + 3.0 * mt * mt * t * c1y
241 + 3.0 * mt * t * t * c2y
242 + t * t * t * y;
243 self.line_to(m, px, py);
244 }
245 self.last_user = Some((x, y));
246 }
247
248 pub fn quadratic_curve_to(&mut self, m: &Matrix, cx: f32, cy: f32, x: f32, y: f32) {
250 let (x0, y0) = match self.last_user {
251 Some(p) => p,
252 None => {
253 self.move_to(m, cx, cy);
254 (cx, cy)
255 }
256 };
257 let c1x = x0 + 2.0 / 3.0 * (cx - x0);
259 let c1y = y0 + 2.0 / 3.0 * (cy - y0);
260 let c2x = x + 2.0 / 3.0 * (cx - x);
261 let c2y = y + 2.0 / 3.0 * (cy - y);
262 self.bezier_curve_to(m, c1x, c1y, c2x, c2y, x, y);
263 }
264
265 pub fn arc(
267 &mut self,
268 m: &Matrix,
269 cx: f32,
270 cy: f32,
271 r: f32,
272 start: f32,
273 end: f32,
274 anticlockwise: bool,
275 ) {
276 if !r.is_finite() || r < 0.0 || !start.is_finite() || !end.is_finite() {
279 return;
280 }
281 let sweep = normalize_sweep(start, end, anticlockwise);
282 let steps = arc_steps(sweep);
283 for i in 0..=steps {
284 let t = i as f32 / steps as f32;
285 let ang = start + sweep * t;
286 let px = cx + r * libm::cosf(ang);
287 let py = cy + r * libm::sinf(ang);
288 if i == 0 && self.current.is_none() {
289 self.move_to(m, px, py);
290 } else {
291 self.line_to(m, px, py);
292 }
293 }
294 }
295
296 pub fn rect(&mut self, m: &Matrix, x: f32, y: f32, w: f32, h: f32) {
298 self.flush();
299 self.current = Some(SubPath {
300 points: alloc::vec![
301 m.apply(x, y),
302 m.apply(x + w, y),
303 m.apply(x + w, y + h),
304 m.apply(x, y + h),
305 ],
306 closed: true,
307 });
308 self.flush();
309 self.last_user = Some((x, y));
310 }
311
312 pub fn finish(&self) -> Vec<SubPath> {
314 let mut out = self.subpaths.clone();
315 if let Some(sp) = &self.current {
316 if sp.points.len() >= 2 {
317 out.push(sp.clone());
318 }
319 }
320 out
321 }
322}
323
324pub fn normalize_sweep(start: f32, end: f32, anticlockwise: bool) -> f32 {
330 let tau = core::f32::consts::PI * 2.0;
331 let mut sweep = end - start;
332 if anticlockwise {
333 if sweep > 0.0 {
334 sweep -= tau;
335 }
336 if sweep < -tau {
337 sweep = -tau;
338 }
339 } else {
340 if sweep < 0.0 {
341 sweep += tau;
342 }
343 if sweep > tau {
344 sweep = tau;
345 }
346 }
347 sweep
348}
349
350pub fn arc_steps(sweep: f32) -> usize {
352 let quarters = libm::fabsf(sweep) / (core::f32::consts::PI / 2.0);
353 let n = (quarters * ARC_SEGMENTS_PER_QUARTER as f32) as usize;
354 n.max(1)
355}
356
357#[derive(Debug, Clone, Copy, PartialEq, Eq)]
361pub enum FillRule {
362 NonZero,
364 EvenOdd,
366}
367
368pub fn scanline_spans(subpaths: &[SubPath], y: f32, rule: FillRule) -> Vec<(f32, f32)> {
382 let mut hits: Vec<(f32, i32)> = Vec::new();
384 for sp in subpaths {
385 let n = sp.points.len();
386 if n < 2 {
387 continue;
388 }
389 for i in 0..n {
391 let (x0, y0) = sp.points[i];
392 let (x1, y1) = sp.points[(i + 1) % n];
393 if !y0.is_finite() || !y1.is_finite() || !x0.is_finite() || !x1.is_finite() {
394 continue;
395 }
396 if (y0 - y1).abs() < f32::EPSILON {
398 continue;
399 }
400 let (ylo, yhi) = if y0 < y1 { (y0, y1) } else { (y1, y0) };
406 if y < ylo || y >= yhi {
407 continue;
408 }
409 let t = (y - y0) / (y1 - y0);
410 let x = x0 + t * (x1 - x0);
411 let dir = if y1 > y0 { 1 } else { -1 };
413 hits.push((x, dir));
414 }
415 }
416 if hits.is_empty() {
417 return Vec::new();
418 }
419 hits.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(core::cmp::Ordering::Equal));
421
422 let mut spans = Vec::new();
423 match rule {
424 FillRule::EvenOdd => {
425 let mut i = 0;
426 while i + 1 < hits.len() {
427 spans.push((hits[i].0, hits[i + 1].0));
428 i += 2;
429 }
430 }
431 FillRule::NonZero => {
432 let mut winding = 0i32;
433 let mut start = 0.0f32;
434 for &(x, dir) in &hits {
435 let was_inside = winding != 0;
436 winding += dir;
437 let now_inside = winding != 0;
438 if !was_inside && now_inside {
439 start = x;
440 } else if was_inside && !now_inside {
441 spans.push((start, x));
442 }
443 }
444 }
445 }
446 spans
447}
448
449
450#[derive(Debug, Clone, Copy, PartialEq)]
454pub struct ColorStop {
455 pub offset: f32, pub color: u32, }
458
459#[derive(Debug, Clone, PartialEq)]
461pub enum GradientKind {
462 Linear {
463 x0: f32,
464 y0: f32,
465 x1: f32,
466 y1: f32,
467 },
468 Radial {
469 x0: f32,
470 y0: f32,
471 r0: f32,
472 x1: f32,
473 y1: f32,
474 r1: f32,
475 },
476}
477
478pub fn sample_gradient_stops(stops: &[ColorStop], t: f32) -> u32 {
480 if stops.is_empty() {
481 return 0; }
483 let t = t.clamp(0.0, 1.0);
484 if t <= stops[0].offset {
485 return stops[0].color;
486 }
487 let last = &stops[stops.len() - 1];
488 if t >= last.offset {
489 return last.color;
490 }
491 for i in 0..stops.len() - 1 {
492 let s0 = &stops[i];
493 let s1 = &stops[i + 1];
494 if t >= s0.offset && t <= s1.offset {
495 let span = s1.offset - s0.offset;
496 let ratio = if span > 1e-6 { (t - s0.offset) / span } else { 0.0 };
497 let a0 = ((s0.color >> 24) & 0xFF) as f32;
498 let r0 = ((s0.color >> 16) & 0xFF) as f32;
499 let g0 = ((s0.color >> 8) & 0xFF) as f32;
500 let b0 = (s0.color & 0xFF) as f32;
501
502 let a1 = ((s1.color >> 24) & 0xFF) as f32;
503 let r1 = ((s1.color >> 16) & 0xFF) as f32;
504 let g1 = ((s1.color >> 8) & 0xFF) as f32;
505 let b1 = (s1.color & 0xFF) as f32;
506
507 let a = ((1.0 - ratio) * a0 + ratio * a1 + 0.5) as u32;
508 let r = ((1.0 - ratio) * r0 + ratio * r1 + 0.5) as u32;
509 let g = ((1.0 - ratio) * g0 + ratio * g1 + 0.5) as u32;
510 let b = ((1.0 - ratio) * b0 + ratio * b1 + 0.5) as u32;
511
512 return (a.min(255) << 24) | (r.min(255) << 16) | (g.min(255) << 8) | b.min(255);
513 }
514 }
515 last.color
516}
517
518#[derive(Debug, Clone, PartialEq)]
520pub struct CanvasGradient {
521 pub kind: GradientKind,
522 pub stops: Vec<ColorStop>,
523}
524
525impl CanvasGradient {
526 pub fn new_linear(x0: f32, y0: f32, x1: f32, y1: f32) -> Self {
527 CanvasGradient {
528 kind: GradientKind::Linear { x0, y0, x1, y1 },
529 stops: Vec::new(),
530 }
531 }
532
533 pub fn new_radial(x0: f32, y0: f32, r0: f32, x1: f32, y1: f32, r1: f32) -> Self {
534 CanvasGradient {
535 kind: GradientKind::Radial { x0, y0, r0, x1, y1, r1 },
536 stops: Vec::new(),
537 }
538 }
539
540 pub fn add_color_stop(&mut self, offset: f32, color: u32) {
541 let offset = offset.clamp(0.0, 1.0);
542 let stop = ColorStop { offset, color };
543 let idx = self.stops.partition_point(|s| s.offset <= offset);
544 self.stops.insert(idx, stop);
545 }
546
547 pub fn build_lut(&self) -> [u32; 256] {
549 let mut lut = [0u32; 256];
550 if self.stops.is_empty() {
551 return lut;
552 }
553 for i in 0..256 {
554 let t = i as f32 / 255.0;
555 lut[i] = sample_gradient_stops(&self.stops, t);
556 }
557 lut
558 }
559
560 pub fn calculate_t(&self, x: f32, y: f32) -> f32 {
562 match self.kind {
563 GradientKind::Linear { x0, y0, x1, y1 } => {
564 let dx = x1 - x0;
565 let dy = y1 - y0;
566 let len_sq = dx * dx + dy * dy;
567 if len_sq < 1e-6 {
568 return 0.0;
569 }
570 let proj = (x - x0) * dx + (y - y0) * dy;
571 (proj / len_sq).clamp(0.0, 1.0)
572 }
573 GradientKind::Radial { x0: _, y0: _, r0, x1, y1, r1 } => {
574 let dx = x - x1;
575 let dy = y - y1;
576 let dist = libm::sqrtf(dx * dx + dy * dy);
577 let r_span = r1 - r0;
578 if libm::fabsf(r_span) < 1e-6 {
579 return 0.0;
580 }
581 ((dist - r0) / r_span).clamp(0.0, 1.0)
582 }
583 }
584 }
585}
586
587
588#[derive(Debug, Clone, Copy, PartialEq, Eq)]
592pub enum Repetition {
593 Repeat,
594 RepeatX,
595 RepeatY,
596 NoRepeat,
597}
598
599pub fn parse_repetition(s: &str) -> Repetition {
601 let s = s.trim();
602 if s.is_empty() {
603 return Repetition::Repeat;
604 }
605 if s.eq_ignore_ascii_case("repeat") {
606 Repetition::Repeat
607 } else if s.eq_ignore_ascii_case("repeat-x") {
608 Repetition::RepeatX
609 } else if s.eq_ignore_ascii_case("repeat-y") {
610 Repetition::RepeatY
611 } else if s.eq_ignore_ascii_case("no-repeat") {
612 Repetition::NoRepeat
613 } else {
614 Repetition::Repeat
615 }
616}
617
618#[derive(Debug, Clone, PartialEq)]
620pub struct CanvasPattern {
621 pub width: u32,
622 pub height: u32,
623 pub pixels: Vec<u32>,
624 pub repetition: Repetition,
625 pub transform: Matrix,
626}
627
628impl CanvasPattern {
629 pub fn new(width: u32, height: u32, pixels: Vec<u32>, repetition: Repetition) -> Self {
630 CanvasPattern {
631 width,
632 height,
633 pixels,
634 repetition,
635 transform: Matrix::identity(),
636 }
637 }
638
639 pub fn set_transform(&mut self, m: Matrix) {
640 self.transform = m;
641 }
642
643 pub fn sample(&self, x: f32, y: f32) -> u32 {
646 if self.width == 0 || self.height == 0 || self.pixels.is_empty() {
647 return 0;
648 }
649 let (u, v) = if let Some(inv) = self.transform.invert() {
650 inv.apply(x, y)
651 } else {
652 (x, y)
653 };
654 let w = self.width as i32;
655 let h = self.height as i32;
656
657 let (ix, iy) = match self.repetition {
658 Repetition::Repeat => (
659 (libm::floorf(u) as i32).rem_euclid(w),
660 (libm::floorf(v) as i32).rem_euclid(h),
661 ),
662 Repetition::RepeatX => {
663 let y_int = libm::floorf(v) as i32;
664 if y_int < 0 || y_int >= h {
665 return 0;
666 }
667 ((libm::floorf(u) as i32).rem_euclid(w), y_int)
668 }
669 Repetition::RepeatY => {
670 let x_int = libm::floorf(u) as i32;
671 if x_int < 0 || x_int >= w {
672 return 0;
673 }
674 (x_int, (libm::floorf(v) as i32).rem_euclid(h))
675 }
676 Repetition::NoRepeat => {
677 let x_int = libm::floorf(u) as i32;
678 let y_int = libm::floorf(v) as i32;
679 if x_int < 0 || x_int >= w || y_int < 0 || y_int >= h {
680 return 0;
681 }
682 (x_int, y_int)
683 }
684 };
685
686 let idx = (iy as usize) * (self.width as usize) + (ix as usize);
687 self.pixels.get(idx).copied().unwrap_or(0)
688 }
689}
690
691
692#[derive(Debug, Clone)]
700pub struct Surface {
701 pub width: u32,
702 pub height: u32,
703 pub pixels: Vec<u32>,
705 pub clip: Option<alloc::sync::Arc<ClipMask>>,
711}
712
713impl Surface {
714 pub fn new(width: u32, height: u32) -> Option<Self> {
720 const MAX_SIDE: u32 = 4096;
721 if width == 0 || height == 0 || width > MAX_SIDE || height > MAX_SIDE {
722 return None;
723 }
724 let n = (width as usize).checked_mul(height as usize)?;
725 Some(Surface {
726 width,
727 height,
728 pixels: alloc::vec![0u32; n],
729 clip: None,
730 })
731 }
732
733 pub fn get_pixel(&self, x: i32, y: i32) -> u32 {
735 if x < 0 || y < 0 || x as u32 >= self.width || y as u32 >= self.height {
736 return 0;
737 }
738 let idx = y as usize * self.width as usize + x as usize;
739 self.pixels[idx]
740 }
741
742 pub fn blend_pixel(&mut self, x: i32, y: i32, argb: u32) {
744 if x < 0 || y < 0 || x as u32 >= self.width || y as u32 >= self.height {
745 return;
746 }
747 if let Some(c) = &self.clip {
748 if !c.allows(x, y) {
749 return;
750 }
751 }
752 let a = (argb >> 24) & 0xFF;
753 if a == 0 {
754 return;
755 }
756 let idx = y as usize * self.width as usize + x as usize;
757 if a == 255 {
758 self.pixels[idx] = argb;
759 return;
760 }
761 let dst = self.pixels[idx];
762 let inv = 255 - a;
763 let da = (dst >> 24) & 0xFF;
765 let out_a = a + da * inv / 255;
766 let mix = |shift: u32| -> u32 {
767 let s = (argb >> shift) & 0xFF;
768 let d = (dst >> shift) & 0xFF;
769 (s * a + d * inv) / 255
770 };
771 self.pixels[idx] =
772 (out_a.min(255) << 24) | (mix(16) << 16) | (mix(8) << 8) | mix(0);
773 }
774
775 pub fn fill_rect(&mut self, x: i32, y: i32, w: i32, h: i32, argb: u32, replace: bool) {
779 mark_canvas_dirty();
780 if w <= 0 || h <= 0 {
781 return;
782 }
783 let x0 = x.max(0);
784 let y0 = y.max(0);
785 let x1 = (x.saturating_add(w)).min(self.width as i32);
786 let y1 = (y.saturating_add(h)).min(self.height as i32);
787 for py in y0..y1 {
788 for px in x0..x1 {
789 if replace {
790 if let Some(c) = &self.clip {
793 if !c.allows(px, py) {
794 continue;
795 }
796 }
797 let idx = py as usize * self.width as usize + px as usize;
798 self.pixels[idx] = argb;
799 } else {
800 self.blend_pixel(px, py, argb);
801 }
802 }
803 }
804 }
805
806 pub fn fill_path(&mut self, subpaths: &[SubPath], rule: FillRule, argb: u32) {
811 mark_canvas_dirty();
812 if subpaths.is_empty() {
813 return;
814 }
815 let (mut ymin, mut ymax) = (f32::MAX, f32::MIN);
817 for sp in subpaths {
818 for &(_, py) in &sp.points {
819 if py.is_finite() {
820 ymin = ymin.min(py);
821 ymax = ymax.max(py);
822 }
823 }
824 }
825 if ymin > ymax {
826 return;
827 }
828 let y_start = (libm::floorf(ymin) as i32).max(0);
829 let y_end = (libm::ceilf(ymax) as i32).min(self.height as i32);
830 for py in y_start..y_end {
831 let spans = scanline_spans(subpaths, py as f32 + 0.5, rule);
832 for (sx, ex) in spans {
833 let x0 = (libm::roundf(sx) as i32).max(0);
835 let x1 = (libm::roundf(ex) as i32).min(self.width as i32);
836 for px in x0..x1 {
837 self.blend_pixel(px, py, argb);
838 }
839 }
840 }
841 }
842
843 pub fn fill_path_gradient(
845 &mut self,
846 subpaths: &[SubPath],
847 rule: FillRule,
848 gradient: &CanvasGradient,
849 alpha_multiplier: f32,
850 ) {
851 mark_canvas_dirty();
852 if subpaths.is_empty() || gradient.stops.is_empty() {
853 return;
854 }
855 let lut = gradient.build_lut();
856 let (mut ymin, mut ymax) = (f32::MAX, f32::MIN);
857 for sp in subpaths {
858 for &(_, py) in &sp.points {
859 if py.is_finite() {
860 ymin = ymin.min(py);
861 ymax = ymax.max(py);
862 }
863 }
864 }
865 if ymin > ymax {
866 return;
867 }
868 let y_start = (libm::floorf(ymin) as i32).max(0);
869 let y_end = (libm::ceilf(ymax) as i32).min(self.height as i32);
870
871 match gradient.kind {
872 GradientKind::Linear { x0, y0, x1, y1 } => {
873 let dx = x1 - x0;
874 let dy = y1 - y0;
875 let len_sq = dx * dx + dy * dy;
876 let (cx, cy, c_base) = if len_sq >= 1e-6 {
877 let inv = 1.0 / len_sq;
878 (dx * inv, dy * inv, -(x0 * dx + y0 * dy) * inv)
879 } else {
880 (0.0, 0.0, 0.0)
881 };
882
883 for py in y_start..y_end {
884 let y_mid = py as f32 + 0.5;
885 let t_row_base = c_base + cy * y_mid;
886 let spans = scanline_spans(subpaths, y_mid, rule);
887 for (sx, ex) in spans {
888 let px0 = (libm::roundf(sx) as i32).max(0);
889 let px1 = (libm::roundf(ex) as i32).min(self.width as i32);
890 for px in px0..px1 {
891 let x_mid = px as f32 + 0.5;
892 let t = (t_row_base + cx * x_mid).clamp(0.0, 1.0);
893 let lut_idx = (t * 255.0 + 0.5) as usize;
894 let mut color = lut[lut_idx.min(255)];
895 if alpha_multiplier < 1.0 {
896 let a = (((color >> 24) & 0xFF) as f32 * alpha_multiplier + 0.5) as u32;
897 color = (a.min(255) << 24) | (color & 0x00FF_FFFF);
898 }
899 self.blend_pixel(px, py, color);
900 }
901 }
902 }
903 }
904 GradientKind::Radial { .. } => {
905 for py in y_start..y_end {
906 let y_mid = py as f32 + 0.5;
907 let spans = scanline_spans(subpaths, y_mid, rule);
908 for (sx, ex) in spans {
909 let px0 = (libm::roundf(sx) as i32).max(0);
910 let px1 = (libm::roundf(ex) as i32).min(self.width as i32);
911 for px in px0..px1 {
912 let x_mid = px as f32 + 0.5;
913 let t = gradient.calculate_t(x_mid, y_mid);
914 let lut_idx = (t * 255.0 + 0.5) as usize;
915 let mut color = lut[lut_idx.min(255)];
916 if alpha_multiplier < 1.0 {
917 let a = (((color >> 24) & 0xFF) as f32 * alpha_multiplier + 0.5) as u32;
918 color = (a.min(255) << 24) | (color & 0x00FF_FFFF);
919 }
920 self.blend_pixel(px, py, color);
921 }
922 }
923 }
924 }
925 }
926 }
927
928 pub fn fill_path_pattern(
930 &mut self,
931 subpaths: &[SubPath],
932 rule: FillRule,
933 pattern: &CanvasPattern,
934 alpha_multiplier: f32,
935 ) {
936 mark_canvas_dirty();
937 if subpaths.is_empty() || pattern.width == 0 || pattern.height == 0 || pattern.pixels.is_empty() {
938 return;
939 }
940 let (mut ymin, mut ymax) = (f32::MAX, f32::MIN);
941 for sp in subpaths {
942 for &(_, py) in &sp.points {
943 if py.is_finite() {
944 ymin = ymin.min(py);
945 ymax = ymax.max(py);
946 }
947 }
948 }
949 if ymin > ymax {
950 return;
951 }
952 let y_start = (libm::floorf(ymin) as i32).max(0);
953 let y_end = (libm::ceilf(ymax) as i32).min(self.height as i32);
954
955 for py in y_start..y_end {
956 let y_mid = py as f32 + 0.5;
957 let spans = scanline_spans(subpaths, y_mid, rule);
958 for (sx, ex) in spans {
959 let px0 = (libm::roundf(sx) as i32).max(0);
960 let px1 = (libm::roundf(ex) as i32).min(self.width as i32);
961 for px in px0..px1 {
962 let x_mid = px as f32 + 0.5;
963 let color = pattern.sample(x_mid, y_mid);
964 let a = (color >> 24) & 0xFF;
965 if a == 0 {
966 continue;
967 }
968 let final_color = if alpha_multiplier < 1.0 {
969 let final_a = libm::roundf(a as f32 * alpha_multiplier).clamp(0.0, 255.0) as u32;
970 (final_a << 24) | (color & 0x00FF_FFFF)
971 } else {
972 color
973 };
974 self.blend_pixel(px, py, final_color);
975 }
976 }
977 }
978 }
979
980 pub fn stroke_path(&mut self, subpaths: &[SubPath], argb: u32) {
984 mark_canvas_dirty();
985 for sp in subpaths {
986 let n = sp.points.len();
987 if n < 2 {
988 continue;
989 }
990 let last = if sp.closed { n } else { n - 1 };
991 for i in 0..last {
992 let (x0, y0) = sp.points[i];
993 let (x1, y1) = sp.points[(i + 1) % n];
994 self.draw_line(x0, y0, x1, y1, argb);
995 }
996 }
997 }
998
999 fn draw_line(&mut self, x0: f32, y0: f32, x1: f32, y1: f32, argb: u32) {
1001 if !x0.is_finite() || !y0.is_finite() || !x1.is_finite() || !y1.is_finite() {
1002 return;
1003 }
1004 let mut x = libm::roundf(x0) as i32;
1005 let mut y = libm::roundf(y0) as i32;
1006 let xe = libm::roundf(x1) as i32;
1007 let ye = libm::roundf(y1) as i32;
1008 let dx = (xe - x).abs();
1009 let dy = -(ye - y).abs();
1010 let sx = if x < xe { 1 } else { -1 };
1011 let sy = if y < ye { 1 } else { -1 };
1012 let mut err = dx + dy;
1013 let mut guard = 0i32;
1015 let limit = (self.width as i32 + self.height as i32) * 4 + 16;
1016 loop {
1017 self.blend_pixel(x, y, argb);
1018 if (x == xe && y == ye) || guard > limit {
1019 break;
1020 }
1021 guard += 1;
1022 let e2 = err * 2;
1023 if e2 >= dy {
1024 err += dy;
1025 x += sx;
1026 }
1027 if e2 <= dx {
1028 err += dx;
1029 y += sy;
1030 }
1031 }
1032 }
1033}
1034
1035
1036#[derive(Debug, Clone)]
1040pub struct DrawState {
1041 pub transform: Matrix,
1042 pub fill: u32,
1044 pub fill_str: alloc::string::String,
1046 pub stroke: u32,
1048 pub stroke_str: alloc::string::String,
1050 pub line_width: f32,
1051 pub line_cap: LineCap,
1053 pub line_join: LineJoin,
1055 pub miter_limit: f32,
1057 pub line_dash: Vec<f32>,
1059 pub line_dash_offset: f32,
1061 pub clip: Option<alloc::sync::Arc<ClipMask>>,
1063 pub global_alpha: f32,
1065 pub font_str: alloc::string::String,
1067 pub text_align_str: alloc::string::String,
1069 pub text_baseline_str: alloc::string::String,
1071 pub image_smoothing_enabled: bool,
1073 pub fill_gradient: Option<CanvasGradient>,
1075 pub stroke_gradient: Option<CanvasGradient>,
1077 pub fill_pattern: Option<CanvasPattern>,
1079 pub stroke_pattern: Option<CanvasPattern>,
1081}
1082
1083impl Default for DrawState {
1084 fn default() -> Self {
1085 DrawState {
1086 transform: Matrix::identity(),
1087 fill: 0xFF00_0000,
1089 fill_str: alloc::string::String::from("#000000"),
1090 stroke: 0xFF00_0000,
1091 stroke_str: alloc::string::String::from("#000000"),
1092 line_width: 1.0,
1093 line_cap: LineCap::Butt,
1094 line_join: LineJoin::Miter,
1095 miter_limit: DEFAULT_MITER_LIMIT,
1096 line_dash: Vec::new(),
1097 line_dash_offset: 0.0,
1098 clip: None,
1099 global_alpha: 1.0,
1100 font_str: alloc::string::String::from("10px sans-serif"),
1101 text_align_str: alloc::string::String::from("start"),
1102 text_baseline_str: alloc::string::String::from("alphabetic"),
1103 image_smoothing_enabled: true,
1104 fill_gradient: None,
1105 stroke_gradient: None,
1106 fill_pattern: None,
1107 stroke_pattern: None,
1108 }
1109 }
1110}
1111
1112#[derive(Debug)]
1118pub struct Canvas2dContext {
1119 pub surface: Surface,
1120 pub path: PathBuilder,
1121 pub state: DrawState,
1122 stack: Vec<DrawState>,
1124}
1125
1126const MAX_SAVE_DEPTH: usize = 32;
1131
1132impl Canvas2dContext {
1133 pub fn new(width: u32, height: u32) -> Option<Self> {
1134 Some(Canvas2dContext {
1135 surface: Surface::new(width, height)?,
1136 path: PathBuilder::new(),
1137 state: DrawState::default(),
1138 stack: Vec::new(),
1139 })
1140 }
1141
1142 pub fn save(&mut self) {
1145 if self.stack.len() >= MAX_SAVE_DEPTH {
1146 return;
1147 }
1148 self.stack.push(self.state.clone());
1149 }
1150
1151 pub fn restore(&mut self) {
1154 if let Some(s) = self.stack.pop() {
1155 self.state = s;
1156 }
1157 }
1158
1159 pub fn apply_alpha(&self, argb: u32) -> u32 {
1164 let ga = self.state.global_alpha.clamp(0.0, 1.0);
1165 if ga >= 1.0 {
1166 return argb;
1167 }
1168 let a = ((argb >> 24) & 0xFF) as f32 * ga;
1169 let a = libm::roundf(a).clamp(0.0, 255.0) as u32;
1170 (a << 24) | (argb & 0x00FF_FFFF)
1171 }
1172
1173 pub fn fill_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
1179 self.sync_clip();
1180 let mut p = PathBuilder::new();
1181 p.rect(&self.state.transform, x, y, w, h);
1182 let sub = p.finish();
1183 if let Some(ref grad) = self.state.fill_gradient {
1184 self.surface.fill_path_gradient(&sub, FillRule::NonZero, grad, self.state.global_alpha.clamp(0.0, 1.0));
1185 } else if let Some(ref pat) = self.state.fill_pattern {
1186 self.surface.fill_path_pattern(&sub, FillRule::NonZero, pat, self.state.global_alpha.clamp(0.0, 1.0));
1187 } else {
1188 let color = self.apply_alpha(self.state.fill);
1189 self.surface.fill_path(&sub, FillRule::NonZero, color);
1190 }
1191 }
1192
1193 pub fn clear_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
1198 self.sync_clip();
1199 let m = &self.state.transform;
1200 let pts = [
1201 m.apply(x, y),
1202 m.apply(x + w, y),
1203 m.apply(x + w, y + h),
1204 m.apply(x, y + h),
1205 ];
1206 let (mut x0, mut y0, mut x1, mut y1) = (f32::MAX, f32::MAX, f32::MIN, f32::MIN);
1207 for (px, py) in pts {
1208 if !px.is_finite() || !py.is_finite() {
1209 return;
1210 }
1211 x0 = x0.min(px);
1212 y0 = y0.min(py);
1213 x1 = x1.max(px);
1214 y1 = y1.max(py);
1215 }
1216 self.surface.fill_rect(
1217 libm::floorf(x0) as i32,
1218 libm::floorf(y0) as i32,
1219 libm::ceilf(x1 - x0) as i32,
1220 libm::ceilf(y1 - y0) as i32,
1221 0,
1222 true,
1223 );
1224 }
1225
1226 pub fn fill(&mut self, rule: FillRule) {
1228 self.sync_clip();
1229 let sub = self.path.finish();
1230 if let Some(ref grad) = self.state.fill_gradient {
1231 self.surface.fill_path_gradient(&sub, rule, grad, self.state.global_alpha.clamp(0.0, 1.0));
1232 } else if let Some(ref pat) = self.state.fill_pattern {
1233 self.surface.fill_path_pattern(&sub, rule, pat, self.state.global_alpha.clamp(0.0, 1.0));
1234 } else {
1235 let color = self.apply_alpha(self.state.fill);
1236 self.surface.fill_path(&sub, rule, color);
1237 }
1238 }
1239
1240 pub fn stroke(&mut self) {
1242 self.sync_clip();
1243 let color = self.apply_alpha(self.state.stroke);
1244 let sub = self.path.finish();
1245 self.surface.stroke_path(&sub, color);
1246 }
1247
1248 pub fn translate(&mut self, tx: f32, ty: f32) {
1250 self.state.transform = self.state.transform.multiply(&Matrix::translate(tx, ty));
1251 }
1252
1253 pub fn scale(&mut self, sx: f32, sy: f32) {
1255 self.state.transform = self.state.transform.multiply(&Matrix::scale(sx, sy));
1256 }
1257
1258 pub fn rotate(&mut self, rad: f32) {
1260 self.state.transform = self.state.transform.multiply(&Matrix::rotate(rad));
1261 }
1262
1263 pub fn set_transform(&mut self, m: Matrix) {
1265 self.state.transform = m;
1266 }
1267
1268 pub fn reset_transform(&mut self) {
1270 self.state.transform = Matrix::identity();
1271 }
1272}
1273
1274
1275static CONTEXTS: spin::Mutex<alloc::collections::BTreeMap<u32, Canvas2dContext>> =
1284 spin::Mutex::new(alloc::collections::BTreeMap::new());
1285
1286static NEXT_ID: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(1);
1288
1289const MAX_CONTEXTS: usize = 8;
1294
1295pub fn create_context(width: u32, height: u32) -> Option<u32> {
1299 let ctx = Canvas2dContext::new(width, height)?;
1300 let id = NEXT_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
1301 let mut map = CONTEXTS.lock();
1302 while map.len() >= MAX_CONTEXTS {
1304 let Some(&oldest) = map.keys().next() else {
1305 break;
1306 };
1307 map.remove(&oldest);
1308 }
1309 map.insert(id, ctx);
1310 Some(id)
1311}
1312
1313pub fn with_context<R>(id: u32, f: impl FnOnce(&mut Canvas2dContext) -> R) -> Option<R> {
1318 let mut map = CONTEXTS.lock();
1319 map.get_mut(&id).map(f)
1320}
1321
1322pub fn clear_contexts() {
1324 CONTEXTS.lock().clear();
1325}
1326
1327pub fn id_from_tag(tag: &str) -> Option<u32> {
1331 tag.strip_prefix("canvas2d:")?.parse::<u32>().ok()
1332}
1333
1334pub fn tag_for_id(id: u32) -> alloc::string::String {
1336 alloc::format!("canvas2d:{}", id)
1337}
1338
1339impl PathBuilder {
1340 #[allow(clippy::too_many_arguments)]
1346 pub fn ellipse(
1347 &mut self,
1348 m: &Matrix,
1349 cx: f32,
1350 cy: f32,
1351 rx: f32,
1352 ry: f32,
1353 rotation: f32,
1354 start: f32,
1355 end: f32,
1356 anticlockwise: bool,
1357 ) {
1358 if !rx.is_finite()
1359 || !ry.is_finite()
1360 || rx < 0.0
1361 || ry < 0.0
1362 || !start.is_finite()
1363 || !end.is_finite()
1364 || !rotation.is_finite()
1365 {
1366 return;
1367 }
1368 let sweep = normalize_sweep(start, end, anticlockwise);
1369 let steps = arc_steps(sweep);
1370 let (rs, rc) = (libm::sinf(rotation), libm::cosf(rotation));
1371 for i in 0..=steps {
1372 let t = i as f32 / steps as f32;
1373 let ang = start + sweep * t;
1374 let ux = rx * libm::cosf(ang);
1376 let uy = ry * libm::sinf(ang);
1377 let px = cx + ux * rc - uy * rs;
1379 let py = cy + ux * rs + uy * rc;
1380 if i == 0 && self.current.is_none() {
1381 self.move_to(m, px, py);
1382 } else {
1383 self.line_to(m, px, py);
1384 }
1385 }
1386 }
1387
1388 #[allow(clippy::too_many_arguments)]
1394 pub fn round_rect(
1395 &mut self,
1396 m: &Matrix,
1397 x: f32,
1398 y: f32,
1399 w: f32,
1400 h: f32,
1401 radii: [f32; 4],
1402 ) {
1403 if !w.is_finite() || !h.is_finite() || w == 0.0 || h == 0.0 {
1404 return;
1405 }
1406 let (x, w) = if w < 0.0 { (x + w, -w) } else { (x, w) };
1408 let (y, h) = if h < 0.0 { (y + h, -h) } else { (y, h) };
1409
1410 let mut r = [0.0f32; 4];
1411 for (i, v) in radii.iter().enumerate() {
1412 r[i] = if v.is_finite() && *v > 0.0 { *v } else { 0.0 };
1413 }
1414 let mut scale = 1.0f32;
1416 let pairs = [
1417 (r[0] + r[1], w), (r[2] + r[3], w), (r[1] + r[2], h), (r[3] + r[0], h), ];
1422 for (sum, len) in pairs {
1423 if sum > 0.0 && sum > len {
1424 scale = scale.min(len / sum);
1425 }
1426 }
1427 for v in r.iter_mut() {
1428 *v *= scale;
1429 }
1430
1431 let half_pi = core::f32::consts::PI / 2.0;
1432 let pi = core::f32::consts::PI;
1433 self.flush();
1434 self.move_to(m, x + r[0], y);
1436 self.line_to(m, x + w - r[1], y);
1437 if r[1] > 0.0 {
1438 self.arc(m, x + w - r[1], y + r[1], r[1], -half_pi, 0.0, false);
1439 }
1440 self.line_to(m, x + w, y + h - r[2]);
1441 if r[2] > 0.0 {
1442 self.arc(m, x + w - r[2], y + h - r[2], r[2], 0.0, half_pi, false);
1443 }
1444 self.line_to(m, x + r[3], y + h);
1445 if r[3] > 0.0 {
1446 self.arc(m, x + r[3], y + h - r[3], r[3], half_pi, pi, false);
1447 }
1448 self.line_to(m, x, y + r[0]);
1449 if r[0] > 0.0 {
1450 self.arc(m, x + r[0], y + r[0], r[0], pi, pi + half_pi, false);
1451 }
1452 self.close_path();
1453 }
1454}
1455
1456impl Surface {
1457 pub fn stroke_path_width(&mut self, subpaths: &[SubPath], argb: u32, width: f32) {
1472 self.stroke_path_styled(
1473 subpaths,
1474 argb,
1475 width,
1476 LineCap::Butt,
1477 LineJoin::Round,
1478 DEFAULT_MITER_LIMIT,
1479 );
1480 }
1481
1482 fn fill_disc(&mut self, cx: f32, cy: f32, r: f32, argb: u32) {
1484 if !cx.is_finite() || !cy.is_finite() || !r.is_finite() || r <= 0.0 {
1485 return;
1486 }
1487 let y0 = (libm::floorf(cy - r) as i32).max(0);
1488 let y1 = (libm::ceilf(cy + r) as i32).min(self.height as i32);
1489 let r2 = r * r;
1490 for py in y0..y1 {
1491 let dy = py as f32 + 0.5 - cy;
1492 let d = r2 - dy * dy;
1493 if d < 0.0 {
1494 continue;
1495 }
1496 let dx = libm::sqrtf(d);
1497 let x0 = (libm::roundf(cx - dx) as i32).max(0);
1498 let x1 = (libm::roundf(cx + dx) as i32).min(self.width as i32);
1499 for px in x0..x1 {
1500 self.blend_pixel(px, py, argb);
1501 }
1502 }
1503 }
1504}
1505
1506impl Canvas2dContext {
1507 pub fn stroke_with_width(&mut self) {
1509 self.sync_clip();
1510 let color = self.apply_alpha(self.state.stroke);
1511 let w = self.state.line_width;
1512 let (cap, join, limit) = (
1513 self.state.line_cap,
1514 self.state.line_join,
1515 self.state.miter_limit,
1516 );
1517 let sub = self.path.finish();
1518 let sub = self.apply_dash(&sub);
1519 self.surface
1520 .stroke_path_styled(&sub, color, w, cap, join, limit);
1521 }
1522}
1523
1524#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1526pub enum LineCap {
1527 Butt,
1529 Round,
1531 Square,
1533}
1534
1535#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1537pub enum LineJoin {
1538 Miter,
1540 Round,
1542 Bevel,
1544}
1545
1546pub const DEFAULT_MITER_LIMIT: f32 = 10.0;
1548
1549pub fn parse_line_cap(s: &str) -> Option<LineCap> {
1550 match s.trim() {
1551 "butt" => Some(LineCap::Butt),
1552 "round" => Some(LineCap::Round),
1553 "square" => Some(LineCap::Square),
1554 _ => None,
1555 }
1556}
1557
1558pub fn parse_line_join(s: &str) -> Option<LineJoin> {
1559 match s.trim() {
1560 "miter" => Some(LineJoin::Miter),
1561 "round" => Some(LineJoin::Round),
1562 "bevel" => Some(LineJoin::Bevel),
1563 _ => None,
1564 }
1565}
1566
1567pub fn line_intersection(
1572 p1: (f32, f32),
1573 d1: (f32, f32),
1574 p2: (f32, f32),
1575 d2: (f32, f32),
1576) -> Option<(f32, f32)> {
1577 let den = d1.0 * d2.1 - d1.1 * d2.0;
1578 if libm::fabsf(den) < 1e-6 {
1580 return None;
1581 }
1582 let t = ((p2.0 - p1.0) * d2.1 - (p2.1 - p1.1) * d2.0) / den;
1583 let x = p1.0 + d1.0 * t;
1584 let y = p1.1 + d1.1 * t;
1585 if !x.is_finite() || !y.is_finite() {
1586 return None;
1587 }
1588 Some((x, y))
1589}
1590
1591impl Surface {
1592 pub fn stroke_path_styled(
1596 &mut self,
1597 subpaths: &[SubPath],
1598 argb: u32,
1599 width: f32,
1600 cap: LineCap,
1601 join: LineJoin,
1602 miter_limit: f32,
1603 ) {
1604 mark_canvas_dirty();
1605 if !width.is_finite() || width <= 1.0 {
1607 self.stroke_path(subpaths, argb);
1608 return;
1609 }
1610 let half = width / 2.0;
1611 let limit = if miter_limit.is_finite() && miter_limit > 0.0 {
1612 miter_limit
1613 } else {
1614 DEFAULT_MITER_LIMIT
1615 };
1616 for sp in subpaths {
1617 let n = sp.points.len();
1618 if n < 2 {
1619 continue;
1620 }
1621 let seg_end = if sp.closed { n } else { n - 1 };
1622
1623 for i in 0..seg_end {
1625 let a = sp.points[i];
1626 let b = sp.points[(i + 1) % n];
1627 let Some((dx, dy, len)) = unit_delta(a, b) else {
1628 continue;
1629 };
1630 let (mut ax, mut ay) = a;
1632 let (mut bx, mut by) = b;
1633 if cap == LineCap::Square && !sp.closed {
1634 if i == 0 {
1635 ax -= dx * half;
1636 ay -= dy * half;
1637 }
1638 if i == seg_end - 1 {
1639 bx += dx * half;
1640 by += dy * half;
1641 }
1642 }
1643 let _ = len;
1644 let (nx, ny) = (-dy * half, dx * half);
1645 self.fill_quad(
1646 (ax + nx, ay + ny),
1647 (bx + nx, by + ny),
1648 (bx - nx, by - ny),
1649 (ax - nx, ay - ny),
1650 argb,
1651 );
1652 }
1653
1654 let joint_from = if sp.closed { 0 } else { 1 };
1656 let joint_to = if sp.closed { n } else { n - 1 };
1657 for j in joint_from..joint_to {
1658 let prev = sp.points[(j + n - 1) % n];
1659 let cur = sp.points[j];
1660 let next = sp.points[(j + 1) % n];
1661 self.fill_joint(prev, cur, next, half, argb, join, limit);
1662 }
1663
1664 if cap == LineCap::Round && !sp.closed {
1666 let first = sp.points[0];
1667 let last = sp.points[n - 1];
1668 self.fill_disc(first.0, first.1, half, argb);
1669 self.fill_disc(last.0, last.1, half, argb);
1670 }
1671 }
1672 }
1673
1674 #[allow(clippy::too_many_arguments)]
1676 fn fill_joint(
1677 &mut self,
1678 prev: (f32, f32),
1679 cur: (f32, f32),
1680 next: (f32, f32),
1681 half: f32,
1682 argb: u32,
1683 join: LineJoin,
1684 limit: f32,
1685 ) {
1686 if join == LineJoin::Round {
1687 self.fill_disc(cur.0, cur.1, half, argb);
1688 return;
1689 }
1690 let (Some((d1x, d1y, _)), Some((d2x, d2y, _))) =
1691 (unit_delta(prev, cur), unit_delta(cur, next))
1692 else {
1693 return;
1694 };
1695 let cross = d1x * d2y - d1y * d2x;
1697 if libm::fabsf(cross) < 1e-6 {
1698 return;
1700 }
1701 let s = if cross > 0.0 { -1.0f32 } else { 1.0f32 };
1703 let p1 = (cur.0 + s * -d1y * half, cur.1 + s * d1x * half);
1704 let p2 = (cur.0 + s * -d2y * half, cur.1 + s * d2x * half);
1705
1706 if join == LineJoin::Miter {
1707 if let Some(mp) = line_intersection(p1, (d1x, d1y), p2, (d2x, d2y)) {
1708 let (mx, my) = (mp.0 - cur.0, mp.1 - cur.1);
1709 let miter_len = libm::sqrtf(mx * mx + my * my);
1710 if miter_len / half <= limit {
1713 self.fill_quad(cur, p1, mp, p2, argb);
1714 return;
1715 }
1716 }
1717 }
1718 self.fill_tri(cur, p1, p2, argb);
1720 }
1721
1722 fn fill_quad(
1723 &mut self,
1724 a: (f32, f32),
1725 b: (f32, f32),
1726 c: (f32, f32),
1727 d: (f32, f32),
1728 argb: u32,
1729 ) {
1730 for p in [a, b, c, d] {
1731 if !p.0.is_finite() || !p.1.is_finite() {
1732 return;
1733 }
1734 }
1735 let poly = alloc::vec![SubPath {
1736 points: alloc::vec![a, b, c, d],
1737 closed: true,
1738 }];
1739 self.fill_path(&poly, FillRule::NonZero, argb);
1740 }
1741
1742 fn fill_tri(&mut self, a: (f32, f32), b: (f32, f32), c: (f32, f32), argb: u32) {
1743 for p in [a, b, c] {
1744 if !p.0.is_finite() || !p.1.is_finite() {
1745 return;
1746 }
1747 }
1748 let poly = alloc::vec![SubPath {
1749 points: alloc::vec![a, b, c],
1750 closed: true,
1751 }];
1752 self.fill_path(&poly, FillRule::NonZero, argb);
1753 }
1754}
1755
1756pub fn unit_delta(a: (f32, f32), b: (f32, f32)) -> Option<(f32, f32, f32)> {
1758 let (dx, dy) = (b.0 - a.0, b.1 - a.1);
1759 if !dx.is_finite() || !dy.is_finite() {
1760 return None;
1761 }
1762 let len = libm::sqrtf(dx * dx + dy * dy);
1763 if len < 1e-6 {
1764 return None;
1765 }
1766 Some((dx / len, dy / len, len))
1767}
1768
1769static CANVAS_DIRTY: core::sync::atomic::AtomicBool =
1779 core::sync::atomic::AtomicBool::new(false);
1780
1781pub fn mark_canvas_dirty() {
1783 CANVAS_DIRTY.store(true, core::sync::atomic::Ordering::Relaxed);
1784}
1785
1786pub fn canvas_dirty() -> bool {
1788 CANVAS_DIRTY.load(core::sync::atomic::Ordering::Relaxed)
1789}
1790
1791pub fn take_canvas_dirty() -> bool {
1793 CANVAS_DIRTY.swap(false, core::sync::atomic::Ordering::Relaxed)
1794}
1795
1796pub fn normalize_dash(pattern: &[f32]) -> Option<Vec<f32>> {
1802 if pattern.is_empty() {
1803 return None;
1804 }
1805 for v in pattern {
1806 if !v.is_finite() || *v < 0.0 {
1807 return None;
1808 }
1809 }
1810 if pattern.iter().all(|v| *v == 0.0) {
1811 return None;
1812 }
1813 let mut out: Vec<f32> = pattern.to_vec();
1814 if out.len() % 2 == 1 {
1816 let dup = out.clone();
1817 out.extend(dup);
1818 }
1819 Some(out)
1820}
1821
1822pub fn dash_subpaths(subpaths: &[SubPath], pattern: &[f32], offset: f32) -> Vec<SubPath> {
1830 let Some(pat) = normalize_dash(pattern) else {
1831 return subpaths.to_vec();
1832 };
1833 let total: f32 = pat.iter().sum();
1834 if total <= 0.0 {
1835 return subpaths.to_vec();
1836 }
1837 let mut out: Vec<SubPath> = Vec::new();
1838 for sp in subpaths {
1839 let n = sp.points.len();
1840 if n < 2 {
1841 continue;
1842 }
1843 let mut phase = if offset.is_finite() {
1845 let m = libm::fmodf(offset, total);
1846 if m < 0.0 {
1847 m + total
1848 } else {
1849 m
1850 }
1851 } else {
1852 0.0
1853 };
1854 let mut idx = 0usize;
1856 while phase >= pat[idx] {
1857 phase -= pat[idx];
1858 idx = (idx + 1) % pat.len();
1859 }
1860 let mut drawing = idx % 2 == 0;
1861 let mut cur: Vec<(f32, f32)> = Vec::new();
1862 if drawing {
1863 cur.push(sp.points[0]);
1864 }
1865
1866 let seg_end = if sp.closed { n } else { n - 1 };
1867 for i in 0..seg_end {
1868 let a = sp.points[i];
1869 let b = sp.points[(i + 1) % n];
1870 let Some((dx, dy, len)) = unit_delta(a, b) else {
1871 continue;
1872 };
1873 let mut travelled = 0.0f32;
1874 while travelled < len {
1875 let remain = pat[idx] - phase;
1877 let step = remain.min(len - travelled);
1878 travelled += step;
1879 phase += step;
1880 let p = (a.0 + dx * travelled, a.1 + dy * travelled);
1881 if phase >= pat[idx] - 1e-6 {
1882 if drawing {
1884 cur.push(p);
1885 if cur.len() >= 2 {
1886 out.push(SubPath {
1887 points: core::mem::take(&mut cur),
1888 closed: false,
1889 });
1890 } else {
1891 cur.clear();
1892 }
1893 } else {
1894 cur.clear();
1895 cur.push(p);
1896 }
1897 phase = 0.0;
1898 idx = (idx + 1) % pat.len();
1899 drawing = !drawing;
1900 } else if drawing {
1901 cur.push(p);
1903 }
1904 }
1905 }
1906 if drawing && cur.len() >= 2 {
1907 out.push(SubPath {
1908 points: cur,
1909 closed: false,
1910 });
1911 }
1912 }
1913 out
1914}
1915
1916impl Canvas2dContext {
1917 pub fn apply_dash(&self, subpaths: &[SubPath]) -> Vec<SubPath> {
1919 if self.state.line_dash.is_empty() {
1920 return subpaths.to_vec();
1921 }
1922 dash_subpaths(subpaths, &self.state.line_dash, self.state.line_dash_offset)
1923 }
1924}
1925
1926#[derive(Debug, Clone)]
1933pub struct ClipMask {
1934 pub width: u32,
1935 pub height: u32,
1936 pub bits: Vec<bool>,
1938}
1939
1940impl ClipMask {
1941 pub fn from_path(width: u32, height: u32, subpaths: &[SubPath], rule: FillRule) -> ClipMask {
1945 let mut bits = alloc::vec![false; (width as usize) * (height as usize)];
1946 for y in 0..height {
1947 let spans = scanline_spans(subpaths, y as f32 + 0.5, rule);
1949 for (x0, x1) in spans {
1950 let a = (libm::ceilf(x0 - 0.5) as i32).max(0);
1951 let b = (libm::ceilf(x1 - 0.5) as i32).min(width as i32);
1952 for x in a..b {
1953 bits[y as usize * width as usize + x as usize] = true;
1954 }
1955 }
1956 }
1957 ClipMask {
1958 width,
1959 height,
1960 bits,
1961 }
1962 }
1963
1964 pub fn intersect(&self, other: &ClipMask) -> ClipMask {
1967 if self.width != other.width || self.height != other.height {
1968 return self.clone();
1970 }
1971 let bits = self
1972 .bits
1973 .iter()
1974 .zip(other.bits.iter())
1975 .map(|(a, b)| *a && *b)
1976 .collect();
1977 ClipMask {
1978 width: self.width,
1979 height: self.height,
1980 bits,
1981 }
1982 }
1983
1984 pub fn allows(&self, x: i32, y: i32) -> bool {
1986 if x < 0 || y < 0 || x as u32 >= self.width || y as u32 >= self.height {
1987 return false;
1988 }
1989 self.bits[y as usize * self.width as usize + x as usize]
1990 }
1991}
1992
1993impl Canvas2dContext {
1994 pub fn clip(&mut self, rule: FillRule) {
1999 let sub = self.path.finish();
2000 if sub.is_empty() {
2001 return;
2002 }
2003 let mask = ClipMask::from_path(self.surface.width, self.surface.height, &sub, rule);
2004 let merged = match &self.state.clip {
2005 Some(prev) => prev.intersect(&mask),
2006 None => mask,
2007 };
2008 self.state.clip = Some(alloc::sync::Arc::new(merged));
2009 }
2010
2011 pub fn sync_clip(&mut self) {
2013 self.surface.clip = self.state.clip.clone();
2014 }
2015}
2016
2017pub fn get_image_data(s: &Surface, x: i32, y: i32, w: i32, h: i32) -> Vec<u8> {
2024 if w <= 0 || h <= 0 {
2025 return Vec::new();
2026 }
2027 let mut out = alloc::vec![0u8; (w as usize) * (h as usize) * 4];
2028 for row in 0..h {
2029 for col in 0..w {
2030 let (sx, sy) = (x + col, y + row);
2031 if sx < 0 || sy < 0 || sx as u32 >= s.width || sy as u32 >= s.height {
2032 continue;
2033 }
2034 let argb = s.pixels[sy as usize * s.width as usize + sx as usize];
2035 let o = ((row as usize) * (w as usize) + col as usize) * 4;
2036 out[o] = ((argb >> 16) & 0xFF) as u8;
2037 out[o + 1] = ((argb >> 8) & 0xFF) as u8;
2038 out[o + 2] = (argb & 0xFF) as u8;
2039 out[o + 3] = ((argb >> 24) & 0xFF) as u8;
2040 }
2041 }
2042 out
2043}
2044
2045pub fn put_image_data(s: &mut Surface, data: &[u8], w: i32, h: i32, x: i32, y: i32) {
2050 if w <= 0 || h <= 0 {
2051 return;
2052 }
2053 mark_canvas_dirty();
2055 for row in 0..h {
2056 for col in 0..w {
2057 let o = ((row as usize) * (w as usize) + col as usize) * 4;
2058 if o + 3 >= data.len() {
2059 return;
2060 }
2061 let (dx, dy) = (x + col, y + row);
2062 if dx < 0 || dy < 0 || dx as u32 >= s.width || dy as u32 >= s.height {
2063 continue;
2064 }
2065 let argb = ((data[o + 3] as u32) << 24)
2066 | ((data[o] as u32) << 16)
2067 | ((data[o + 1] as u32) << 8)
2068 | (data[o + 2] as u32);
2069 s.pixels[dy as usize * s.width as usize + dx as usize] = argb;
2070 }
2071 }
2072}
2073
2074pub fn resize_context(id: u32, width: u32, height: u32) -> bool {
2082 let Some(fresh) = Surface::new(width, height) else {
2083 return false;
2084 };
2085 mark_canvas_dirty();
2086 with_context(id, |c| {
2087 c.surface = fresh;
2088 c.state = DrawState::default();
2089 c.path = PathBuilder::new();
2090 c.stack.clear();
2091 })
2092 .is_some()
2093}
2094
2095pub struct ImageSource<'a> {
2099 pub width: u32,
2100 pub height: u32,
2101 pub rgba: &'a [u8],
2102}
2103
2104impl ImageSource<'_> {
2105 pub fn sample(&self, x: i32, y: i32) -> u32 {
2107 if x < 0 || y < 0 || x as u32 >= self.width || y as u32 >= self.height {
2108 return 0;
2109 }
2110 let o = (y as usize * self.width as usize + x as usize) * 4;
2111 if o + 3 >= self.rgba.len() {
2112 return 0;
2113 }
2114 ((self.rgba[o + 3] as u32) << 24)
2115 | ((self.rgba[o] as u32) << 16)
2116 | ((self.rgba[o + 1] as u32) << 8)
2117 | (self.rgba[o + 2] as u32)
2118 }
2119
2120 pub fn sample_bilinear(&self, sxf: f32, syf: f32) -> u32 {
2122 let u = sxf - 0.5;
2123 let v = syf - 0.5;
2124 let x0 = libm::floorf(u) as i32;
2125 let y0 = libm::floorf(v) as i32;
2126 let x1 = x0 + 1;
2127 let y1 = y0 + 1;
2128
2129 let fx = ((u - x0 as f32) * 256.0).clamp(0.0, 256.0) as u32;
2130 let fy = ((v - y0 as f32) * 256.0).clamp(0.0, 256.0) as u32;
2131
2132 let c00 = self.sample(x0, y0);
2133 let c10 = self.sample(x1, y0);
2134 let c01 = self.sample(x0, y1);
2135 let c11 = self.sample(x1, y1);
2136
2137 if c00 == c10 && c10 == c01 && c01 == c11 {
2138 return c00;
2139 }
2140
2141 let w00 = (256 - fx) * (256 - fy);
2142 let w10 = fx * (256 - fy);
2143 let w01 = (256 - fx) * fy;
2144 let w11 = fx * fy;
2145
2146 let a = (((c00 >> 24) & 0xFF) * w00
2147 + ((c10 >> 24) & 0xFF) * w10
2148 + ((c01 >> 24) & 0xFF) * w01
2149 + ((c11 >> 24) & 0xFF) * w11 + 32768) >> 16;
2150
2151 let r = (((c00 >> 16) & 0xFF) * w00
2152 + ((c10 >> 16) & 0xFF) * w10
2153 + ((c01 >> 16) & 0xFF) * w01
2154 + ((c11 >> 16) & 0xFF) * w11 + 32768) >> 16;
2155
2156 let g = (((c00 >> 8) & 0xFF) * w00
2157 + ((c10 >> 8) & 0xFF) * w10
2158 + ((c01 >> 8) & 0xFF) * w01
2159 + ((c11 >> 8) & 0xFF) * w11 + 32768) >> 16;
2160
2161 let b = ((c00 & 0xFF) * w00
2162 + (c10 & 0xFF) * w10
2163 + (c01 & 0xFF) * w01
2164 + (c11 & 0xFF) * w11 + 32768) >> 16;
2165
2166 (a.min(255) << 24) | (r.min(255) << 16) | (g.min(255) << 8) | b.min(255)
2167 }
2168}
2169
2170#[derive(Debug, Clone, Copy)]
2172pub struct BlitSpec {
2173 pub sx: f32,
2175 pub sy: f32,
2176 pub sw: f32,
2177 pub sh: f32,
2178 pub dx: f32,
2180 pub dy: f32,
2181 pub dw: f32,
2182 pub dh: f32,
2183}
2184
2185impl Surface {
2186 pub fn draw_image(&mut self, src: &ImageSource, spec: &BlitSpec, m: &Matrix, alpha: f32, smooth: bool) {
2196 if spec.dw == 0.0 || spec.dh == 0.0 || spec.sw == 0.0 || spec.sh == 0.0 {
2197 return;
2198 }
2199 for v in [
2200 spec.sx, spec.sy, spec.sw, spec.sh, spec.dx, spec.dy, spec.dw, spec.dh,
2201 ] {
2202 if !v.is_finite() {
2203 return;
2204 }
2205 }
2206 let Some(inv) = m.invert() else {
2207 return;
2208 };
2209 mark_canvas_dirty();
2210
2211 let corners = [
2213 m.apply(spec.dx, spec.dy),
2214 m.apply(spec.dx + spec.dw, spec.dy),
2215 m.apply(spec.dx + spec.dw, spec.dy + spec.dh),
2216 m.apply(spec.dx, spec.dy + spec.dh),
2217 ];
2218 let mut x0 = corners[0].0;
2219 let mut x1 = corners[0].0;
2220 let mut y0 = corners[0].1;
2221 let mut y1 = corners[0].1;
2222 for (cx, cy) in corners.iter().skip(1) {
2223 x0 = x0.min(*cx);
2224 x1 = x1.max(*cx);
2225 y0 = y0.min(*cy);
2226 y1 = y1.max(*cy);
2227 }
2228 let px0 = (libm::floorf(x0) as i32).max(0);
2229 let px1 = (libm::ceilf(x1) as i32).min(self.width as i32);
2230 let py0 = (libm::floorf(y0) as i32).max(0);
2231 let py1 = (libm::ceilf(y1) as i32).min(self.height as i32);
2232
2233 let a = alpha.clamp(0.0, 1.0);
2234 for py in py0..py1 {
2235 for px in px0..px1 {
2236 let (ux, uy) = inv.apply(px as f32 + 0.5, py as f32 + 0.5);
2238 let tx = (ux - spec.dx) / spec.dw;
2240 let ty = (uy - spec.dy) / spec.dh;
2241 if !(0.0..1.0).contains(&tx) || !(0.0..1.0).contains(&ty) {
2242 continue;
2243 }
2244 let sxf = spec.sx + tx * spec.sw;
2246 let syf = spec.sy + ty * spec.sh;
2247 let argb = if smooth {
2248 src.sample_bilinear(sxf, syf)
2249 } else {
2250 src.sample(libm::floorf(sxf) as i32, libm::floorf(syf) as i32)
2251 };
2252 if argb >> 24 == 0 {
2253 continue;
2254 }
2255 let argb = if a >= 1.0 {
2256 argb
2257 } else {
2258 let na = (((argb >> 24) & 0xFF) as f32 * a) as u32;
2259 (na << 24) | (argb & 0x00FF_FFFF)
2260 };
2261 self.blend_pixel(px, py, argb);
2262 }
2263 }
2264 }
2265}
2266
2267#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2271pub enum TextAlign {
2272 Start,
2273 End,
2274 Left,
2275 Right,
2276 Center,
2277}
2278
2279#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2281pub enum TextBaseline {
2282 Top,
2283 Hanging,
2284 Middle,
2285 Alphabetic,
2286 Ideographic,
2287 Bottom,
2288}
2289
2290pub fn parse_text_align(s: &str) -> Option<TextAlign> {
2291 match s.trim() {
2292 "start" => Some(TextAlign::Start),
2293 "end" => Some(TextAlign::End),
2294 "left" => Some(TextAlign::Left),
2295 "right" => Some(TextAlign::Right),
2296 "center" => Some(TextAlign::Center),
2297 _ => None,
2298 }
2299}
2300
2301pub fn parse_text_baseline(s: &str) -> Option<TextBaseline> {
2302 match s.trim() {
2303 "top" => Some(TextBaseline::Top),
2304 "hanging" => Some(TextBaseline::Hanging),
2305 "middle" => Some(TextBaseline::Middle),
2306 "alphabetic" => Some(TextBaseline::Alphabetic),
2307 "ideographic" => Some(TextBaseline::Ideographic),
2308 "bottom" => Some(TextBaseline::Bottom),
2309 _ => None,
2310 }
2311}
2312
2313pub fn align_offset(align: TextAlign, total_advance: f32) -> f32 {
2318 match align {
2319 TextAlign::Start | TextAlign::Left => 0.0,
2320 TextAlign::End | TextAlign::Right => -total_advance,
2321 TextAlign::Center => -total_advance / 2.0,
2322 }
2323}
2324
2325pub fn baseline_offset(baseline: TextBaseline, size_px: f32) -> f32 {
2332 match baseline {
2333 TextBaseline::Alphabetic => 0.0,
2334 TextBaseline::Top => size_px * 0.85,
2335 TextBaseline::Hanging => size_px * 0.8,
2336 TextBaseline::Middle => size_px * 0.35,
2337 TextBaseline::Ideographic | TextBaseline::Bottom => -size_px * 0.15,
2338 }
2339}
2340
2341pub fn parse_font_size(font: &str) -> Option<f32> {
2347 for tok in font.split(|c: char| c.is_whitespace() || c == '/') {
2348 let t = tok.trim();
2349 let Some(num) = t.strip_suffix("px") else {
2350 continue;
2351 };
2352 if let Ok(v) = num.parse::<f32>() {
2353 if v.is_finite() && v > 0.0 {
2354 return Some(v);
2355 }
2356 }
2357 }
2358 None
2359}
2360
2361pub struct GlyphBitmap<'a> {
2366 pub width: u32,
2367 pub height: u32,
2368 pub x_offset: i32,
2370 pub y_offset: i32,
2371 pub advance: f32,
2373 pub alpha: &'a [u8],
2375}
2376
2377impl Surface {
2378 pub fn draw_glyph(&mut self, g: &GlyphBitmap, pen_x: f32, pen_y: f32, argb: u32, m: &Matrix) {
2384 if g.width == 0 || g.height == 0 {
2385 return;
2386 }
2387 let n = (g.width as usize) * (g.height as usize);
2388 if g.alpha.len() < n {
2389 return;
2390 }
2391 let (r, gg, b) = ((argb >> 16) & 0xFF, (argb >> 8) & 0xFF, argb & 0xFF);
2392 let base_a = (argb >> 24) & 0xFF;
2393 let mut rgba = alloc::vec![0u8; n * 4];
2394 for i in 0..n {
2395 let a = (g.alpha[i] as u32 * base_a / 255) as u8;
2396 rgba[i * 4] = r as u8;
2397 rgba[i * 4 + 1] = gg as u8;
2398 rgba[i * 4 + 2] = b as u8;
2399 rgba[i * 4 + 3] = a;
2400 }
2401 let src = ImageSource {
2402 width: g.width,
2403 height: g.height,
2404 rgba: &rgba,
2405 };
2406 let spec = BlitSpec {
2407 sx: 0.0,
2408 sy: 0.0,
2409 sw: g.width as f32,
2410 sh: g.height as f32,
2411 dx: pen_x + g.x_offset as f32,
2412 dy: pen_y + g.y_offset as f32,
2413 dw: g.width as f32,
2414 dh: g.height as f32,
2415 };
2416 self.draw_image(&src, &spec, m, 1.0, false);
2417 }
2418
2419 pub fn draw_glyph_stroke(
2423 &mut self,
2424 g: &GlyphBitmap,
2425 pen_x: f32,
2426 pen_y: f32,
2427 stroke_argb: u32,
2428 line_width: f32,
2429 m: &Matrix,
2430 ) {
2431 if g.width == 0 || g.height == 0 || line_width <= 0.0 {
2432 return;
2433 }
2434 let n = (g.width as usize) * (g.height as usize);
2435 if g.alpha.len() < n {
2436 return;
2437 }
2438 let r = (line_width / 2.0).clamp(0.5, 4.0);
2439 let ir = libm::ceilf(r) as i32;
2440 let w = g.width as i32;
2441 let h = g.height as i32;
2442
2443 let mut stroke_alpha = alloc::vec![0u8; n];
2444 for y in 0..h {
2445 for x in 0..w {
2446 let idx = (y * w + x) as usize;
2447 let a = g.alpha[idx];
2448 if a > 0 {
2449 for dy in -ir..=ir {
2450 for dx in -ir..=ir {
2451 let dist = libm::sqrtf((dx * dx + dy * dy) as f32);
2452 if dist <= r {
2453 let nx = x + dx;
2454 let ny = y + dy;
2455 if nx >= 0 && nx < w && ny >= 0 && ny < h {
2456 let nidx = (ny * w + nx) as usize;
2457 let falloff = if r > 0.0 {
2458 (1.0 - (dist / (r + 0.5))).clamp(0.0, 1.0)
2459 } else {
2460 1.0
2461 };
2462 let strk_val = (a as f32 * falloff) as u8;
2463 if strk_val > stroke_alpha[nidx] {
2464 stroke_alpha[nidx] = strk_val;
2465 }
2466 }
2467 }
2468 }
2469 }
2470 }
2471 }
2472 }
2473 let (r_col, g_col, b_col) = (
2474 (stroke_argb >> 16) & 0xFF,
2475 (stroke_argb >> 8) & 0xFF,
2476 stroke_argb & 0xFF,
2477 );
2478 let base_a = (stroke_argb >> 24) & 0xFF;
2479 let mut rgba = alloc::vec![0u8; n * 4];
2480 for i in 0..n {
2481 let a = (stroke_alpha[i] as u32 * base_a / 255) as u8;
2482 rgba[i * 4] = r_col as u8;
2483 rgba[i * 4 + 1] = g_col as u8;
2484 rgba[i * 4 + 2] = b_col as u8;
2485 rgba[i * 4 + 3] = a;
2486 }
2487 let src = ImageSource {
2488 width: g.width,
2489 height: g.height,
2490 rgba: &rgba,
2491 };
2492 let spec = BlitSpec {
2493 sx: 0.0,
2494 sy: 0.0,
2495 sw: g.width as f32,
2496 sh: g.height as f32,
2497 dx: pen_x + g.x_offset as f32,
2498 dy: pen_y + g.y_offset as f32,
2499 dw: g.width as f32,
2500 dh: g.height as f32,
2501 };
2502 self.draw_image(&src, &spec, m, 1.0, false);
2503 }
2504}
2505
2506
2507struct SvgPathTokenizer<'a> {
2511 data: &'a [u8],
2512 pos: usize,
2513}
2514
2515impl<'a> SvgPathTokenizer<'a> {
2516 fn new(data: &'a str) -> Self {
2517 Self {
2518 data: data.as_bytes(),
2519 pos: 0,
2520 }
2521 }
2522
2523 fn skip_whitespace_and_comma(&mut self) {
2524 while self.pos < self.data.len() {
2525 let c = self.data[self.pos];
2526 if c == b' ' || c == b'\t' || c == b'\n' || c == b'\r' || c == b',' {
2527 self.pos += 1;
2528 } else {
2529 break;
2530 }
2531 }
2532 }
2533
2534 fn next_cmd(&mut self) -> Option<char> {
2535 self.skip_whitespace_and_comma();
2536 if self.pos >= self.data.len() {
2537 return None;
2538 }
2539 let c = self.data[self.pos] as char;
2540 if c.is_ascii_alphabetic() {
2541 self.pos += 1;
2542 Some(c)
2543 } else {
2544 None
2545 }
2546 }
2547
2548 fn next_f32(&mut self) -> Option<f32> {
2549 self.skip_whitespace_and_comma();
2550 if self.pos >= self.data.len() {
2551 return None;
2552 }
2553 let start = self.pos;
2554 if self.pos < self.data.len() && (self.data[self.pos] == b'-' || self.data[self.pos] == b'+') {
2555 self.pos += 1;
2556 }
2557 let mut has_digit = false;
2558 while self.pos < self.data.len() && self.data[self.pos].is_ascii_digit() {
2559 self.pos += 1;
2560 has_digit = true;
2561 }
2562 if self.pos < self.data.len() && self.data[self.pos] == b'.' {
2563 self.pos += 1;
2564 while self.pos < self.data.len() && self.data[self.pos].is_ascii_digit() {
2565 self.pos += 1;
2566 has_digit = true;
2567 }
2568 }
2569 if has_digit && self.pos < self.data.len() && (self.data[self.pos] == b'e' || self.data[self.pos] == b'E') {
2570 self.pos += 1;
2571 if self.pos < self.data.len() && (self.data[self.pos] == b'-' || self.data[self.pos] == b'+') {
2572 self.pos += 1;
2573 }
2574 while self.pos < self.data.len() && self.data[self.pos].is_ascii_digit() {
2575 self.pos += 1;
2576 }
2577 }
2578 if !has_digit {
2579 self.pos = start;
2580 return None;
2581 }
2582 let s = core::str::from_utf8(&self.data[start..self.pos]).ok()?;
2583 s.parse::<f32>().ok()
2584 }
2585}
2586
2587pub fn parse_svg_path_into(d: &str, p: &mut PathBuilder, m: &Matrix) {
2589 let mut tok = SvgPathTokenizer::new(d);
2590 let mut curr_x = 0.0f32;
2591 let mut curr_y = 0.0f32;
2592 let mut start_x = 0.0f32;
2593 let mut start_y = 0.0f32;
2594 let mut last_cmd = ' ';
2595
2596 while tok.pos < tok.data.len() {
2597 tok.skip_whitespace_and_comma();
2598 if tok.pos >= tok.data.len() {
2599 break;
2600 }
2601 let cmd = if let Some(c) = tok.next_cmd() {
2602 last_cmd = c;
2603 c
2604 } else if last_cmd != ' ' {
2605 if last_cmd == 'M' {
2606 'L'
2607 } else if last_cmd == 'm' {
2608 'l'
2609 } else {
2610 last_cmd
2611 }
2612 } else {
2613 break;
2614 };
2615
2616 match cmd {
2617 'M' => {
2618 if let (Some(x), Some(y)) = (tok.next_f32(), tok.next_f32()) {
2619 curr_x = x;
2620 curr_y = y;
2621 start_x = x;
2622 start_y = y;
2623 p.move_to(m, x, y);
2624 }
2625 }
2626 'm' => {
2627 if let (Some(dx), Some(dy)) = (tok.next_f32(), tok.next_f32()) {
2628 curr_x += dx;
2629 curr_y += dy;
2630 start_x = curr_x;
2631 start_y = curr_y;
2632 p.move_to(m, curr_x, curr_y);
2633 }
2634 }
2635 'L' => {
2636 if let (Some(x), Some(y)) = (tok.next_f32(), tok.next_f32()) {
2637 curr_x = x;
2638 curr_y = y;
2639 p.line_to(m, x, y);
2640 }
2641 }
2642 'l' => {
2643 if let (Some(dx), Some(dy)) = (tok.next_f32(), tok.next_f32()) {
2644 curr_x += dx;
2645 curr_y += dy;
2646 p.line_to(m, curr_x, curr_y);
2647 }
2648 }
2649 'H' => {
2650 if let Some(x) = tok.next_f32() {
2651 curr_x = x;
2652 p.line_to(m, curr_x, curr_y);
2653 }
2654 }
2655 'h' => {
2656 if let Some(dx) = tok.next_f32() {
2657 curr_x += dx;
2658 p.line_to(m, curr_x, curr_y);
2659 }
2660 }
2661 'V' => {
2662 if let Some(y) = tok.next_f32() {
2663 curr_y = y;
2664 p.line_to(m, curr_x, curr_y);
2665 }
2666 }
2667 'v' => {
2668 if let Some(dy) = tok.next_f32() {
2669 curr_y += dy;
2670 p.line_to(m, curr_x, curr_y);
2671 }
2672 }
2673 'C' => {
2674 if let (Some(x1), Some(y1), Some(x2), Some(y2), Some(x), Some(y)) = (
2675 tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32()
2676 ) {
2677 p.bezier_curve_to(m, x1, y1, x2, y2, x, y);
2678 curr_x = x;
2679 curr_y = y;
2680 }
2681 }
2682 'c' => {
2683 if let (Some(dx1), Some(dy1), Some(dx2), Some(dy2), Some(dx), Some(dy)) = (
2684 tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32()
2685 ) {
2686 p.bezier_curve_to(m, curr_x + dx1, curr_y + dy1, curr_x + dx2, curr_y + dy2, curr_x + dx, curr_y + dy);
2687 curr_x += dx;
2688 curr_y += dy;
2689 }
2690 }
2691 'S' | 's' => {
2692 if cmd == 'S' {
2693 if let (Some(x2), Some(y2), Some(x), Some(y)) = (
2694 tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32()
2695 ) {
2696 p.bezier_curve_to(m, curr_x, curr_y, x2, y2, x, y);
2697 curr_x = x;
2698 curr_y = y;
2699 }
2700 } else {
2701 if let (Some(dx2), Some(dy2), Some(dx), Some(dy)) = (
2702 tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32()
2703 ) {
2704 p.bezier_curve_to(m, curr_x, curr_y, curr_x + dx2, curr_y + dy2, curr_x + dx, curr_y + dy);
2705 curr_x += dx;
2706 curr_y += dy;
2707 }
2708 }
2709 }
2710 'Q' => {
2711 if let (Some(x1), Some(y1), Some(x), Some(y)) = (
2712 tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32()
2713 ) {
2714 p.quadratic_curve_to(m, x1, y1, x, y);
2715 curr_x = x;
2716 curr_y = y;
2717 }
2718 }
2719 'q' => {
2720 if let (Some(dx1), Some(dy1), Some(dx), Some(dy)) = (
2721 tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32()
2722 ) {
2723 p.quadratic_curve_to(m, curr_x + dx1, curr_y + dy1, curr_x + dx, curr_y + dy);
2724 curr_x += dx;
2725 curr_y += dy;
2726 }
2727 }
2728 'Z' | 'z' => {
2729 p.close_path();
2730 curr_x = start_x;
2731 curr_y = start_y;
2732 }
2733 _ => {
2734 tok.pos += 1;
2735 }
2736 }
2737 }
2738}
2739
2740static PATH2D_MAP: spin::Mutex<alloc::collections::BTreeMap<u32, PathBuilder>> =
2743 spin::Mutex::new(alloc::collections::BTreeMap::new());
2744static NEXT_PATH2D_ID: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(1);
2745const MAX_PATH2D: usize = 64;
2746
2747pub fn create_path2d(init_svg: Option<&str>) -> u32 {
2748 let id = NEXT_PATH2D_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
2749 let mut builder = PathBuilder::new();
2750 if let Some(svg) = init_svg {
2751 parse_svg_path_into(svg, &mut builder, &Matrix::identity());
2752 }
2753 let mut map = PATH2D_MAP.lock();
2754 while map.len() >= MAX_PATH2D {
2755 if let Some(&oldest) = map.keys().next() {
2756 map.remove(&oldest);
2757 } else {
2758 break;
2759 }
2760 }
2761 map.insert(id, builder);
2762 id
2763}
2764
2765pub fn clone_path2d(src_id: u32) -> u32 {
2766 let new_id = NEXT_PATH2D_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
2767 let mut map = PATH2D_MAP.lock();
2768 while map.len() >= MAX_PATH2D {
2769 if let Some(&oldest) = map.keys().next() {
2770 map.remove(&oldest);
2771 } else {
2772 break;
2773 }
2774 }
2775 let cloned = map.get(&src_id).cloned().unwrap_or_else(PathBuilder::new);
2776 map.insert(new_id, cloned);
2777 new_id
2778}
2779
2780pub fn with_path2d<R>(id: u32, f: impl FnOnce(&mut PathBuilder) -> R) -> Option<R> {
2781 let mut map = PATH2D_MAP.lock();
2782 map.get_mut(&id).map(f)
2783}
2784
2785pub fn get_path2d_subpaths(id: u32) -> Option<Vec<SubPath>> {
2786 let map = PATH2D_MAP.lock();
2787 map.get(&id).map(|p| p.finish())
2788}
2789
2790pub fn add_path_to_path2d(dst_id: u32, src_id: u32, transform: Option<&Matrix>) {
2791 let mut map = PATH2D_MAP.lock();
2792 if let Some(src) = map.get(&src_id).cloned() {
2793 if let Some(dst) = map.get_mut(&dst_id) {
2794 dst.add_path(&src, transform);
2795 }
2796 }
2797}
2798
2799impl Canvas2dContext {
2800 pub fn fill_subpaths(&mut self, subpaths: &[SubPath], rule: FillRule) {
2802 self.sync_clip();
2803 if let Some(ref grad) = self.state.fill_gradient {
2804 self.surface.fill_path_gradient(subpaths, rule, grad, self.state.global_alpha.clamp(0.0, 1.0));
2805 } else {
2806 let color = self.apply_alpha(self.state.fill);
2807 self.surface.fill_path(subpaths, rule, color);
2808 }
2809 }
2810
2811 pub fn stroke_subpaths(&mut self, subpaths: &[SubPath]) {
2813 self.sync_clip();
2814 let color = self.apply_alpha(self.state.stroke);
2815 let w = self.state.line_width;
2816 let (cap, join, limit) = (
2817 self.state.line_cap,
2818 self.state.line_join,
2819 self.state.miter_limit,
2820 );
2821 let sub = self.apply_dash(subpaths);
2822 self.surface
2823 .stroke_path_styled(&sub, color, w, cap, join, limit);
2824 }
2825
2826 pub fn clip_subpaths(&mut self, subpaths: &[SubPath], rule: FillRule) {
2828 if subpaths.is_empty() {
2829 return;
2830 }
2831 let mask = ClipMask::from_path(self.surface.width, self.surface.height, subpaths, rule);
2832 let merged = match &self.state.clip {
2833 Some(prev) => prev.intersect(&mask),
2834 None => mask,
2835 };
2836 self.state.clip = Some(alloc::sync::Arc::new(merged));
2837 }
2838}
2839
2840
2841static GRADIENT_MAP: spin::Mutex<alloc::collections::BTreeMap<u32, CanvasGradient>> =
2844 spin::Mutex::new(alloc::collections::BTreeMap::new());
2845static NEXT_GRADIENT_ID: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(1);
2846const MAX_GRADIENTS: usize = 64;
2847
2848pub fn create_linear_gradient(x0: f32, y0: f32, x1: f32, y1: f32) -> u32 {
2849 let id = NEXT_GRADIENT_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
2850 let grad = CanvasGradient::new_linear(x0, y0, x1, y1);
2851 let mut map = GRADIENT_MAP.lock();
2852 while map.len() >= MAX_GRADIENTS {
2853 if let Some(&oldest) = map.keys().next() {
2854 map.remove(&oldest);
2855 } else {
2856 break;
2857 }
2858 }
2859 map.insert(id, grad);
2860 id
2861}
2862
2863pub fn create_radial_gradient(x0: f32, y0: f32, r0: f32, x1: f32, y1: f32, r1: f32) -> u32 {
2864 let id = NEXT_GRADIENT_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
2865 let grad = CanvasGradient::new_radial(x0, y0, r0, x1, y1, r1);
2866 let mut map = GRADIENT_MAP.lock();
2867 while map.len() >= MAX_GRADIENTS {
2868 if let Some(&oldest) = map.keys().next() {
2869 map.remove(&oldest);
2870 } else {
2871 break;
2872 }
2873 }
2874 map.insert(id, grad);
2875 id
2876}
2877
2878pub fn add_color_stop_to_gradient(id: u32, offset: f32, color: u32) {
2879 let mut map = GRADIENT_MAP.lock();
2880 if let Some(grad) = map.get_mut(&id) {
2881 grad.add_color_stop(offset, color);
2882 }
2883}
2884
2885pub fn get_gradient(id: u32) -> Option<CanvasGradient> {
2886 let map = GRADIENT_MAP.lock();
2887 map.get(&id).cloned()
2888}