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
120pub(crate) fn offset_subpaths(paths: &[SubPath], dx: f32, dy: f32) -> Vec<SubPath> {
122 paths
123 .iter()
124 .map(|sp| SubPath {
125 points: sp.points.iter().map(|&(x, y)| (x + dx, y + dy)).collect(),
126 closed: sp.closed,
127 })
128 .collect()
129}
130
131#[derive(Debug, Clone, Default)]
136pub struct PathBuilder {
137 subpaths: Vec<SubPath>,
138 current: Option<SubPath>,
139 last_user: Option<(f32, f32)>,
141}
142
143const ARC_SEGMENTS_PER_QUARTER: usize = 8;
145const BEZIER_SEGMENTS: usize = 16;
147
148impl PathBuilder {
149 pub fn new() -> Self {
150 Self::default()
151 }
152
153 fn flush(&mut self) {
158 if let Some(sp) = self.current.take() {
159 if sp.points.len() >= 2 {
160 self.subpaths.push(sp);
161 }
162 }
163 }
164
165 pub fn begin_path(&mut self) {
166 self.subpaths.clear();
167 self.current = None;
168 self.last_user = None;
169 }
170
171 pub fn move_to(&mut self, m: &Matrix, x: f32, y: f32) {
172 self.flush();
173 self.current = Some(SubPath {
174 points: alloc::vec![m.apply(x, y)],
175 closed: false,
176 });
177 self.last_user = Some((x, y));
178 }
179
180 pub fn line_to(&mut self, m: &Matrix, x: f32, y: f32) {
181 if self.current.is_none() {
183 self.move_to(m, x, y);
184 return;
185 }
186 if let Some(sp) = self.current.as_mut() {
187 sp.points.push(m.apply(x, y));
188 }
189 self.last_user = Some((x, y));
190 }
191
192 pub fn close_path(&mut self) {
193 if let Some(sp) = self.current.as_mut() {
194 sp.closed = true;
195 }
196 self.flush();
197 }
198
199 pub fn add_path(&mut self, other: &PathBuilder, transform: Option<&Matrix>) {
200 self.flush();
201 let id_mat = Matrix::identity();
202 let m = transform.unwrap_or(&id_mat);
203 for sp in &other.subpaths {
204 let pts = sp.points.iter().map(|p| m.apply(p.0, p.1)).collect();
205 self.subpaths.push(SubPath {
206 points: pts,
207 closed: sp.closed,
208 });
209 }
210 if let Some(ref sp) = other.current {
211 if sp.points.len() >= 2 {
212 let pts = sp.points.iter().map(|p| m.apply(p.0, p.1)).collect();
213 self.subpaths.push(SubPath {
214 points: pts,
215 closed: sp.closed,
216 });
217 }
218 }
219 if let Some((lx, ly)) = other.last_user {
220 let lp = m.apply(lx, ly);
221 self.last_user = Some((lp.0, lp.1));
222 }
223 }
224
225 pub fn bezier_curve_to(
227 &mut self,
228 m: &Matrix,
229 c1x: f32,
230 c1y: f32,
231 c2x: f32,
232 c2y: f32,
233 x: f32,
234 y: f32,
235 ) {
236 let (x0, y0) = match self.last_user {
237 Some(p) => p,
238 None => {
239 self.move_to(m, c1x, c1y);
240 (c1x, c1y)
241 }
242 };
243 for i in 1..=BEZIER_SEGMENTS {
244 let t = i as f32 / BEZIER_SEGMENTS as f32;
245 let mt = 1.0 - t;
246 let px = mt * mt * mt * x0
247 + 3.0 * mt * mt * t * c1x
248 + 3.0 * mt * t * t * c2x
249 + t * t * t * x;
250 let py = mt * mt * mt * y0
251 + 3.0 * mt * mt * t * c1y
252 + 3.0 * mt * t * t * c2y
253 + t * t * t * y;
254 self.line_to(m, px, py);
255 }
256 self.last_user = Some((x, y));
257 }
258
259 pub fn quadratic_curve_to(&mut self, m: &Matrix, cx: f32, cy: f32, x: f32, y: f32) {
261 let (x0, y0) = match self.last_user {
262 Some(p) => p,
263 None => {
264 self.move_to(m, cx, cy);
265 (cx, cy)
266 }
267 };
268 let c1x = x0 + 2.0 / 3.0 * (cx - x0);
270 let c1y = y0 + 2.0 / 3.0 * (cy - y0);
271 let c2x = x + 2.0 / 3.0 * (cx - x);
272 let c2y = y + 2.0 / 3.0 * (cy - y);
273 self.bezier_curve_to(m, c1x, c1y, c2x, c2y, x, y);
274 }
275
276 pub fn arc(
278 &mut self,
279 m: &Matrix,
280 cx: f32,
281 cy: f32,
282 r: f32,
283 start: f32,
284 end: f32,
285 anticlockwise: bool,
286 ) {
287 if !r.is_finite() || r < 0.0 || !start.is_finite() || !end.is_finite() {
290 return;
291 }
292 let sweep = normalize_sweep(start, end, anticlockwise);
293 let steps = arc_steps(sweep);
294 for i in 0..=steps {
295 let t = i as f32 / steps as f32;
296 let ang = start + sweep * t;
297 let px = cx + r * libm::cosf(ang);
298 let py = cy + r * libm::sinf(ang);
299 if i == 0 && self.current.is_none() {
300 self.move_to(m, px, py);
301 } else {
302 self.line_to(m, px, py);
303 }
304 }
305 }
306
307 pub fn arc_to(&mut self, m: &Matrix, x1: f32, y1: f32, x2: f32, y2: f32, radius: f32) {
309 if !x1.is_finite() || !y1.is_finite() || !x2.is_finite() || !y2.is_finite() || !radius.is_finite() {
310 return;
311 }
312 if radius < 0.0 {
313 return;
314 }
315 let (x0, y0) = match self.last_user {
316 Some(p) => p,
317 None => {
318 self.move_to(m, x1, y1);
319 (x1, y1)
320 }
321 };
322
323 if radius == 0.0 || (x0 == x1 && y0 == y1) {
325 self.line_to(m, x1, y1);
326 return;
327 }
328
329 let v1x = x0 - x1;
330 let v1y = y0 - y1;
331 let v2x = x2 - x1;
332 let v2y = y2 - y1;
333
334 let len1 = libm::hypotf(v1x, v1y);
335 let len2 = libm::hypotf(v2x, v2y);
336
337 if len1 == 0.0 || len2 == 0.0 {
338 self.line_to(m, x1, y1);
339 return;
340 }
341
342 let u1x = v1x / len1;
343 let u1y = v1y / len1;
344 let u2x = v2x / len2;
345 let u2y = v2y / len2;
346
347 let cross = u1x * u2y - u1y * u2x;
348 let dot = u1x * u2x + u1y * u2y;
349
350 if libm::fabsf(cross) < 1e-6 {
352 self.line_to(m, x1, y1);
353 return;
354 }
355
356 let dist = radius * (1.0 + dot) / libm::fabsf(cross);
358
359 let t1x = x1 + dist * u1x;
360 let t1y = y1 + dist * u1y;
361 let t2x = x1 + dist * u2x;
362 let t2y = y1 + dist * u2y;
363
364 let (cx, cy) = if cross > 0.0 {
366 (t1x - radius * u1y, t1y + radius * u1x)
367 } else {
368 (t1x + radius * u1y, t1y - radius * u1x)
369 };
370
371 let start_ang = libm::atan2f(t1y - cy, t1x - cx);
372 let end_ang = libm::atan2f(t2y - cy, t2x - cx);
373 let anticlockwise = cross > 0.0;
374
375 self.line_to(m, t1x, t1y);
376 self.arc(m, cx, cy, radius, start_ang, end_ang, anticlockwise);
377 self.last_user = Some((t2x, t2y));
378 }
379
380 pub fn rect(&mut self, m: &Matrix, x: f32, y: f32, w: f32, h: f32) {
382 self.flush();
383 self.current = Some(SubPath {
384 points: alloc::vec![
385 m.apply(x, y),
386 m.apply(x + w, y),
387 m.apply(x + w, y + h),
388 m.apply(x, y + h),
389 ],
390 closed: true,
391 });
392 self.flush();
393 self.last_user = Some((x, y));
394 }
395
396 pub fn finish(&self) -> Vec<SubPath> {
398 let mut out = self.subpaths.clone();
399 if let Some(sp) = &self.current {
400 if sp.points.len() >= 2 {
401 out.push(sp.clone());
402 }
403 }
404 out
405 }
406}
407
408pub fn normalize_sweep(start: f32, end: f32, anticlockwise: bool) -> f32 {
414 let tau = core::f32::consts::PI * 2.0;
415 let mut sweep = end - start;
416 if anticlockwise {
417 if sweep > 0.0 {
418 sweep -= tau;
419 }
420 if sweep < -tau {
421 sweep = -tau;
422 }
423 } else {
424 if sweep < 0.0 {
425 sweep += tau;
426 }
427 if sweep > tau {
428 sweep = tau;
429 }
430 }
431 sweep
432}
433
434pub fn arc_steps(sweep: f32) -> usize {
436 let quarters = libm::fabsf(sweep) / (core::f32::consts::PI / 2.0);
437 let n = (quarters * ARC_SEGMENTS_PER_QUARTER as f32) as usize;
438 n.max(1)
439}
440
441#[derive(Debug, Clone, Copy, PartialEq, Eq)]
445pub enum FillRule {
446 NonZero,
448 EvenOdd,
450}
451
452pub fn scanline_spans(subpaths: &[SubPath], y: f32, rule: FillRule) -> Vec<(f32, f32)> {
466 let mut hits: Vec<(f32, i32)> = Vec::new();
468 for sp in subpaths {
469 let n = sp.points.len();
470 if n < 2 {
471 continue;
472 }
473 for i in 0..n {
475 let (x0, y0) = sp.points[i];
476 let (x1, y1) = sp.points[(i + 1) % n];
477 if !y0.is_finite() || !y1.is_finite() || !x0.is_finite() || !x1.is_finite() {
478 continue;
479 }
480 if (y0 - y1).abs() < f32::EPSILON {
482 continue;
483 }
484 let (ylo, yhi) = if y0 < y1 { (y0, y1) } else { (y1, y0) };
490 if y < ylo || y >= yhi {
491 continue;
492 }
493 let t = (y - y0) / (y1 - y0);
494 let x = x0 + t * (x1 - x0);
495 let dir = if y1 > y0 { 1 } else { -1 };
497 hits.push((x, dir));
498 }
499 }
500 if hits.is_empty() {
501 return Vec::new();
502 }
503 hits.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(core::cmp::Ordering::Equal));
505
506 let mut spans = Vec::new();
507 match rule {
508 FillRule::EvenOdd => {
509 let mut i = 0;
510 while i + 1 < hits.len() {
511 spans.push((hits[i].0, hits[i + 1].0));
512 i += 2;
513 }
514 }
515 FillRule::NonZero => {
516 let mut winding = 0i32;
517 let mut start = 0.0f32;
518 for &(x, dir) in &hits {
519 let was_inside = winding != 0;
520 winding += dir;
521 let now_inside = winding != 0;
522 if !was_inside && now_inside {
523 start = x;
524 } else if was_inside && !now_inside {
525 spans.push((start, x));
526 }
527 }
528 }
529 }
530 spans
531}
532
533pub fn is_point_in_path(subpaths: &[SubPath], x: f32, y: f32, rule: FillRule) -> bool {
535 if !x.is_finite() || !y.is_finite() || subpaths.is_empty() {
536 return false;
537 }
538 let spans = scanline_spans(subpaths, y, rule);
539 for (sx, ex) in spans {
540 if x >= sx - 1e-4 && x <= ex + 1e-4 {
541 return true;
542 }
543 }
544 false
545}
546
547pub fn is_point_in_stroke(subpaths: &[SubPath], x: f32, y: f32, line_width: f32) -> bool {
549 if !x.is_finite() || !y.is_finite() || subpaths.is_empty() || line_width <= 0.0 {
550 return false;
551 }
552 let max_dist = line_width * 0.5;
553 let max_dist_sq = max_dist * max_dist;
554
555 for sp in subpaths {
556 let n = sp.points.len();
557 if n < 2 {
558 continue;
559 }
560 for i in 0..n - 1 {
561 let (x0, y0) = sp.points[i];
562 let (x1, y1) = sp.points[i + 1];
563 if !x0.is_finite() || !y0.is_finite() || !x1.is_finite() || !y1.is_finite() {
564 continue;
565 }
566 let dx = x1 - x0;
567 let dy = y1 - y0;
568 let len_sq = dx * dx + dy * dy;
569 let dist_sq = if len_sq < 1e-6 {
570 let px = x - x0;
571 let py = y - y0;
572 px * px + py * py
573 } else {
574 let t = (((x - x0) * dx + (y - y0) * dy) / len_sq).clamp(0.0, 1.0);
575 let qx = x0 + t * dx;
576 let qy = y0 + t * dy;
577 let px = x - qx;
578 let py = y - qy;
579 px * px + py * py
580 };
581 if dist_sq <= max_dist_sq {
582 return true;
583 }
584 }
585 if sp.closed && n >= 3 {
586 let (x0, y0) = sp.points[n - 1];
587 let (x1, y1) = sp.points[0];
588 let dx = x1 - x0;
589 let dy = y1 - y0;
590 let len_sq = dx * dx + dy * dy;
591 let dist_sq = if len_sq < 1e-6 {
592 let px = x - x0;
593 let py = y - y0;
594 px * px + py * py
595 } else {
596 let t = (((x - x0) * dx + (y - y0) * dy) / len_sq).clamp(0.0, 1.0);
597 let qx = x0 + t * dx;
598 let qy = y0 + t * dy;
599 let px = x - qx;
600 let py = y - qy;
601 px * px + py * py
602 };
603 if dist_sq <= max_dist_sq {
604 return true;
605 }
606 }
607 }
608 false
609}
610
611pub fn is_point_in_stroke_styled(
613 subpaths: &[SubPath],
614 x: f32,
615 y: f32,
616 line_width: f32,
617 cap: LineCap,
618 join: LineJoin,
619 miter_limit: f32,
620 dash: &[f32],
621 dash_offset: f32,
622) -> bool {
623 if !x.is_finite() || !y.is_finite() || subpaths.is_empty() || line_width <= 0.0 {
624 return false;
625 }
626 let dashed = if dash.is_empty() {
627 subpaths.to_vec()
628 } else {
629 dash_subpaths(subpaths, dash, dash_offset)
630 };
631 let outline = stroke_to_outline(&dashed, line_width, cap, join, miter_limit);
632 if outline.is_empty() {
633 return false;
634 }
635 is_point_in_path(&outline, x, y, FillRule::NonZero)
636}
637
638
639#[derive(Debug, Clone, Copy, PartialEq)]
643pub struct ColorStop {
644 pub offset: f32, pub color: u32, }
647
648#[derive(Debug, Clone, PartialEq)]
650pub enum GradientKind {
651 Linear {
652 x0: f32,
653 y0: f32,
654 x1: f32,
655 y1: f32,
656 },
657 Radial {
658 x0: f32,
659 y0: f32,
660 r0: f32,
661 x1: f32,
662 y1: f32,
663 r1: f32,
664 },
665 Conic {
666 start_angle: f32,
667 cx: f32,
668 cy: f32,
669 },
670}
671
672pub fn sample_gradient_stops(stops: &[ColorStop], t: f32) -> u32 {
674 if stops.is_empty() {
675 return 0; }
677 let t = t.clamp(0.0, 1.0);
678 if t <= stops[0].offset {
679 return stops[0].color;
680 }
681 let last = &stops[stops.len() - 1];
682 if t >= last.offset {
683 return last.color;
684 }
685 for i in 0..stops.len() - 1 {
686 let s0 = &stops[i];
687 let s1 = &stops[i + 1];
688 if t >= s0.offset && t <= s1.offset {
689 let span = s1.offset - s0.offset;
690 let ratio = if span > 1e-6 { (t - s0.offset) / span } else { 0.0 };
691 let a0 = ((s0.color >> 24) & 0xFF) as f32;
692 let r0 = ((s0.color >> 16) & 0xFF) as f32;
693 let g0 = ((s0.color >> 8) & 0xFF) as f32;
694 let b0 = (s0.color & 0xFF) as f32;
695
696 let a1 = ((s1.color >> 24) & 0xFF) as f32;
697 let r1 = ((s1.color >> 16) & 0xFF) as f32;
698 let g1 = ((s1.color >> 8) & 0xFF) as f32;
699 let b1 = (s1.color & 0xFF) as f32;
700
701 let a = ((1.0 - ratio) * a0 + ratio * a1 + 0.5) as u32;
702 let r = ((1.0 - ratio) * r0 + ratio * r1 + 0.5) as u32;
703 let g = ((1.0 - ratio) * g0 + ratio * g1 + 0.5) as u32;
704 let b = ((1.0 - ratio) * b0 + ratio * b1 + 0.5) as u32;
705
706 return (a.min(255) << 24) | (r.min(255) << 16) | (g.min(255) << 8) | b.min(255);
707 }
708 }
709 last.color
710}
711
712#[derive(Debug, Clone, PartialEq)]
714pub struct CanvasGradient {
715 pub kind: GradientKind,
716 pub stops: Vec<ColorStop>,
717}
718
719impl CanvasGradient {
720 pub fn new_linear(x0: f32, y0: f32, x1: f32, y1: f32) -> Self {
721 CanvasGradient {
722 kind: GradientKind::Linear { x0, y0, x1, y1 },
723 stops: Vec::new(),
724 }
725 }
726
727 pub fn new_radial(x0: f32, y0: f32, r0: f32, x1: f32, y1: f32, r1: f32) -> Self {
728 CanvasGradient {
729 kind: GradientKind::Radial { x0, y0, r0, x1, y1, r1 },
730 stops: Vec::new(),
731 }
732 }
733
734 pub fn new_conic(start_angle: f32, cx: f32, cy: f32) -> Self {
735 CanvasGradient {
736 kind: GradientKind::Conic { start_angle, cx, cy },
737 stops: Vec::new(),
738 }
739 }
740
741 pub fn add_color_stop(&mut self, offset: f32, color: u32) {
742 let offset = offset.clamp(0.0, 1.0);
743 let stop = ColorStop { offset, color };
744 let idx = self.stops.partition_point(|s| s.offset <= offset);
745 self.stops.insert(idx, stop);
746 }
747
748 pub fn build_lut(&self) -> [u32; 256] {
750 let mut lut = [0u32; 256];
751 if self.stops.is_empty() {
752 return lut;
753 }
754 for i in 0..256 {
755 let t = i as f32 / 255.0;
756 lut[i] = sample_gradient_stops(&self.stops, t);
757 }
758 lut
759 }
760
761 pub fn calculate_t(&self, x: f32, y: f32) -> f32 {
763 match self.kind {
764 GradientKind::Linear { x0, y0, x1, y1 } => {
765 let dx = x1 - x0;
766 let dy = y1 - y0;
767 let len_sq = dx * dx + dy * dy;
768 if len_sq < 1e-6 {
769 return 0.0;
770 }
771 let proj = (x - x0) * dx + (y - y0) * dy;
772 (proj / len_sq).clamp(0.0, 1.0)
773 }
774 GradientKind::Radial { x0: _, y0: _, r0, x1, y1, r1 } => {
775 let dx = x - x1;
776 let dy = y - y1;
777 let dist = libm::sqrtf(dx * dx + dy * dy);
778 let r_span = r1 - r0;
779 if libm::fabsf(r_span) < 1e-6 {
780 return 0.0;
781 }
782 ((dist - r0) / r_span).clamp(0.0, 1.0)
783 }
784 GradientKind::Conic { start_angle, cx, cy } => {
785 let dx = x - cx;
786 let dy = y - cy;
787 if dx == 0.0 && dy == 0.0 {
788 return 0.0;
789 }
790 let angle = libm::atan2f(dy, dx);
791 let mut diff = angle - start_angle;
792 let two_pi = core::f32::consts::PI * 2.0;
793 while diff < 0.0 {
794 diff += two_pi;
795 }
796 while diff >= two_pi {
797 diff -= two_pi;
798 }
799 (diff / two_pi).clamp(0.0, 1.0)
800 }
801 }
802 }
803}
804
805
806#[derive(Debug, Clone, Copy, PartialEq, Eq)]
810pub enum Repetition {
811 Repeat,
812 RepeatX,
813 RepeatY,
814 NoRepeat,
815}
816
817pub fn parse_repetition(s: &str) -> Repetition {
819 let s = s.trim();
820 if s.is_empty() {
821 return Repetition::Repeat;
822 }
823 if s.eq_ignore_ascii_case("repeat") {
824 Repetition::Repeat
825 } else if s.eq_ignore_ascii_case("repeat-x") {
826 Repetition::RepeatX
827 } else if s.eq_ignore_ascii_case("repeat-y") {
828 Repetition::RepeatY
829 } else if s.eq_ignore_ascii_case("no-repeat") {
830 Repetition::NoRepeat
831 } else {
832 Repetition::Repeat
833 }
834}
835
836#[derive(Debug, Clone, PartialEq)]
838pub struct CanvasPattern {
839 pub width: u32,
840 pub height: u32,
841 pub pixels: Vec<u32>,
842 pub repetition: Repetition,
843 pub transform: Matrix,
844}
845
846impl CanvasPattern {
847 pub fn new(width: u32, height: u32, pixels: Vec<u32>, repetition: Repetition) -> Self {
848 CanvasPattern {
849 width,
850 height,
851 pixels,
852 repetition,
853 transform: Matrix::identity(),
854 }
855 }
856
857 pub fn set_transform(&mut self, m: Matrix) {
858 self.transform = m;
859 }
860
861 pub fn sample(&self, x: f32, y: f32) -> u32 {
864 if self.width == 0 || self.height == 0 || self.pixels.is_empty() {
865 return 0;
866 }
867 let (u, v) = if let Some(inv) = self.transform.invert() {
868 inv.apply(x, y)
869 } else {
870 (x, y)
871 };
872 let w = self.width as i32;
873 let h = self.height as i32;
874
875 let (ix, iy) = match self.repetition {
876 Repetition::Repeat => (
877 (libm::floorf(u) as i32).rem_euclid(w),
878 (libm::floorf(v) as i32).rem_euclid(h),
879 ),
880 Repetition::RepeatX => {
881 let y_int = libm::floorf(v) as i32;
882 if y_int < 0 || y_int >= h {
883 return 0;
884 }
885 ((libm::floorf(u) as i32).rem_euclid(w), y_int)
886 }
887 Repetition::RepeatY => {
888 let x_int = libm::floorf(u) as i32;
889 if x_int < 0 || x_int >= w {
890 return 0;
891 }
892 (x_int, (libm::floorf(v) as i32).rem_euclid(h))
893 }
894 Repetition::NoRepeat => {
895 let x_int = libm::floorf(u) as i32;
896 let y_int = libm::floorf(v) as i32;
897 if x_int < 0 || x_int >= w || y_int < 0 || y_int >= h {
898 return 0;
899 }
900 (x_int, y_int)
901 }
902 };
903
904 let idx = (iy as usize) * (self.width as usize) + (ix as usize);
905 self.pixels.get(idx).copied().unwrap_or(0)
906 }
907}
908
909
910#[derive(Debug, Clone)]
918pub struct Surface {
919 pub width: u32,
920 pub height: u32,
921 pub pixels: Vec<u32>,
923 pub clip: Option<alloc::sync::Arc<ClipMask>>,
929 pub composite_op: CompositeOperation,
931}
932
933impl Surface {
934 pub fn new(width: u32, height: u32) -> Option<Self> {
940 const MAX_SIDE: u32 = 4096;
941 if width == 0 || height == 0 || width > MAX_SIDE || height > MAX_SIDE {
942 return None;
943 }
944 let n = (width as usize).checked_mul(height as usize)?;
945 Some(Surface {
946 width,
947 height,
948 pixels: alloc::vec![0u32; n],
949 clip: None,
950 composite_op: CompositeOperation::SourceOver,
951 })
952 }
953
954 pub fn get_pixel(&self, x: i32, y: i32) -> u32 {
956 if x < 0 || y < 0 || x as u32 >= self.width || y as u32 >= self.height {
957 return 0;
958 }
959 let idx = y as usize * self.width as usize + x as usize;
960 self.pixels[idx]
961 }
962
963 pub fn set_pixel(&mut self, x: i32, y: i32, argb: u32) {
965 if x < 0 || y < 0 || x as u32 >= self.width || y as u32 >= self.height {
966 return;
967 }
968 let idx = y as usize * self.width as usize + x as usize;
969 self.pixels[idx] = argb;
970 }
971
972 pub fn blend_pixel(&mut self, x: i32, y: i32, argb: u32) {
974 self.blend_pixel_with_op(x, y, argb, self.composite_op);
975 }
976
977 pub fn blend_pixel_with_op(&mut self, x: i32, y: i32, argb: u32, op: CompositeOperation) {
979 if x < 0 || y < 0 || x as u32 >= self.width || y as u32 >= self.height {
980 return;
981 }
982 if let Some(c) = &self.clip {
983 if !c.allows(x, y) {
984 return;
985 }
986 }
987 let idx = y as usize * self.width as usize + x as usize;
988 let sa = (argb >> 24) & 0xFF;
989 let sr = (argb >> 16) & 0xFF;
990 let sg = (argb >> 8) & 0xFF;
991 let sb = argb & 0xFF;
992
993 let dst = self.pixels[idx];
994 let da = (dst >> 24) & 0xFF;
995 let dr = (dst >> 16) & 0xFF;
996 let dg = (dst >> 8) & 0xFF;
997 let db = dst & 0xFF;
998
999 let (out_a, out_r, out_g, out_b) = match op {
1000 CompositeOperation::Copy => (sa, sr, sg, sb),
1001 CompositeOperation::SourceOver => {
1002 if sa == 0 {
1003 return;
1004 }
1005 if sa == 255 {
1006 self.pixels[idx] = argb;
1007 return;
1008 }
1009 let inv = 255 - sa;
1010 let out_a = sa + da * inv / 255;
1011 if out_a == 0 {
1012 (0, 0, 0, 0)
1013 } else {
1014 let r = (sr * sa + dr * da * inv / 255) / out_a;
1015 let g = (sg * sa + dg * da * inv / 255) / out_a;
1016 let b = (sb * sa + db * da * inv / 255) / out_a;
1017 (out_a, r.min(255), g.min(255), b.min(255))
1018 }
1019 }
1020 CompositeOperation::DestinationOver => {
1021 if da == 255 {
1022 return;
1023 }
1024 let inv = 255 - da;
1025 let out_a = da + sa * inv / 255;
1026 if out_a == 0 {
1027 (0, 0, 0, 0)
1028 } else {
1029 let r = (dr * da + sr * sa * inv / 255) / out_a;
1030 let g = (dg * da + sg * sa * inv / 255) / out_a;
1031 let b = (db * da + sb * sa * inv / 255) / out_a;
1032 (out_a, r.min(255), g.min(255), b.min(255))
1033 }
1034 }
1035 CompositeOperation::SourceIn => {
1036 let out_a = (sa * da) / 255;
1037 (out_a, sr, sg, sb)
1038 }
1039 CompositeOperation::SourceOut => {
1040 let out_a = (sa * (255 - da)) / 255;
1041 (out_a, sr, sg, sb)
1042 }
1043 CompositeOperation::SourceAtop => {
1044 let out_a = da;
1045 let r = (sr * sa + dr * (255 - sa)) / 255;
1046 let g = (sg * sa + dg * (255 - sa)) / 255;
1047 let b = (sb * sa + db * (255 - sa)) / 255;
1048 (out_a, r.min(255), g.min(255), b.min(255))
1049 }
1050 CompositeOperation::DestinationIn => {
1051 let out_a = (da * sa) / 255;
1052 (out_a, dr, dg, db)
1053 }
1054 CompositeOperation::DestinationOut => {
1055 let out_a = (da * (255 - sa)) / 255;
1056 (out_a, dr, dg, db)
1057 }
1058 CompositeOperation::DestinationAtop => {
1059 let out_a = sa;
1060 let r = (dr * da + sr * (255 - da)) / 255;
1061 let g = (dg * da + sg * (255 - da)) / 255;
1062 let b = (db * da + sb * (255 - da)) / 255;
1063 (out_a, r.min(255), g.min(255), b.min(255))
1064 }
1065 CompositeOperation::Lighter => {
1066 let out_a = (sa + da).min(255);
1067 let r = (sr + dr).min(255);
1068 let g = (sg + dg).min(255);
1069 let b = (sb + db).min(255);
1070 (out_a, r, g, b)
1071 }
1072 CompositeOperation::Xor => {
1073 let inv_d = 255 - da;
1074 let inv_s = 255 - sa;
1075 let out_a = (sa * inv_d + da * inv_s) / 255;
1076 if out_a == 0 {
1077 (0, 0, 0, 0)
1078 } else {
1079 let r = (sr * sa * inv_d + dr * da * inv_s) / (out_a * 255);
1080 let g = (sg * sa * inv_d + dg * da * inv_s) / (out_a * 255);
1081 let b = (sb * sa * inv_d + db * da * inv_s) / (out_a * 255);
1082 (out_a.min(255), r.min(255), g.min(255), b.min(255))
1083 }
1084 }
1085 };
1086
1087 self.pixels[idx] = (out_a.min(255) << 24) | (out_r.min(255) << 16) | (out_g.min(255) << 8) | out_b.min(255);
1088 }
1089
1090 pub fn fill_rect(&mut self, x: i32, y: i32, w: i32, h: i32, argb: u32, replace: bool) {
1094 mark_canvas_dirty();
1095 if w <= 0 || h <= 0 {
1096 return;
1097 }
1098 let x0 = x.max(0);
1099 let y0 = y.max(0);
1100 let x1 = (x.saturating_add(w)).min(self.width as i32);
1101 let y1 = (y.saturating_add(h)).min(self.height as i32);
1102 for py in y0..y1 {
1103 for px in x0..x1 {
1104 if replace {
1105 if let Some(c) = &self.clip {
1108 if !c.allows(px, py) {
1109 continue;
1110 }
1111 }
1112 let idx = py as usize * self.width as usize + px as usize;
1113 self.pixels[idx] = argb;
1114 } else {
1115 self.blend_pixel(px, py, argb);
1116 }
1117 }
1118 }
1119 }
1120
1121 pub fn fill_path(&mut self, subpaths: &[SubPath], rule: FillRule, argb: u32) {
1126 mark_canvas_dirty();
1127 if subpaths.is_empty() {
1128 return;
1129 }
1130 let (mut ymin, mut ymax) = (f32::MAX, f32::MIN);
1132 for sp in subpaths {
1133 for &(_, py) in &sp.points {
1134 if py.is_finite() {
1135 ymin = ymin.min(py);
1136 ymax = ymax.max(py);
1137 }
1138 }
1139 }
1140 if ymin > ymax {
1141 return;
1142 }
1143 let y_start = (libm::floorf(ymin) as i32).max(0);
1144 let y_end = (libm::ceilf(ymax) as i32).min(self.height as i32);
1145 for py in y_start..y_end {
1146 let spans = scanline_spans(subpaths, py as f32 + 0.5, rule);
1147 for (sx, ex) in spans {
1148 let x0 = (libm::roundf(sx) as i32).max(0);
1150 let x1 = (libm::roundf(ex) as i32).min(self.width as i32);
1151 for px in x0..x1 {
1152 self.blend_pixel(px, py, argb);
1153 }
1154 }
1155 }
1156 }
1157
1158 pub fn fill_path_gradient(
1160 &mut self,
1161 subpaths: &[SubPath],
1162 rule: FillRule,
1163 gradient: &CanvasGradient,
1164 alpha_multiplier: f32,
1165 ) {
1166 mark_canvas_dirty();
1167 if subpaths.is_empty() || gradient.stops.is_empty() {
1168 return;
1169 }
1170 let lut = gradient.build_lut();
1171 let (mut ymin, mut ymax) = (f32::MAX, f32::MIN);
1172 for sp in subpaths {
1173 for &(_, py) in &sp.points {
1174 if py.is_finite() {
1175 ymin = ymin.min(py);
1176 ymax = ymax.max(py);
1177 }
1178 }
1179 }
1180 if ymin > ymax {
1181 return;
1182 }
1183 let y_start = (libm::floorf(ymin) as i32).max(0);
1184 let y_end = (libm::ceilf(ymax) as i32).min(self.height as i32);
1185
1186 match gradient.kind {
1187 GradientKind::Linear { x0, y0, x1, y1 } => {
1188 let dx = x1 - x0;
1189 let dy = y1 - y0;
1190 let len_sq = dx * dx + dy * dy;
1191 let (cx, cy, c_base) = if len_sq >= 1e-6 {
1192 let inv = 1.0 / len_sq;
1193 (dx * inv, dy * inv, -(x0 * dx + y0 * dy) * inv)
1194 } else {
1195 (0.0, 0.0, 0.0)
1196 };
1197
1198 for py in y_start..y_end {
1199 let y_mid = py as f32 + 0.5;
1200 let t_row_base = c_base + cy * y_mid;
1201 let spans = scanline_spans(subpaths, y_mid, rule);
1202 for (sx, ex) in spans {
1203 let px0 = (libm::roundf(sx) as i32).max(0);
1204 let px1 = (libm::roundf(ex) as i32).min(self.width as i32);
1205 for px in px0..px1 {
1206 let x_mid = px as f32 + 0.5;
1207 let t = (t_row_base + cx * x_mid).clamp(0.0, 1.0);
1208 let lut_idx = (t * 255.0 + 0.5) as usize;
1209 let mut color = lut[lut_idx.min(255)];
1210 if alpha_multiplier < 1.0 {
1211 let a = (((color >> 24) & 0xFF) as f32 * alpha_multiplier + 0.5) as u32;
1212 color = (a.min(255) << 24) | (color & 0x00FF_FFFF);
1213 }
1214 self.blend_pixel(px, py, color);
1215 }
1216 }
1217 }
1218 }
1219 GradientKind::Radial { .. } | GradientKind::Conic { .. } => {
1220 for py in y_start..y_end {
1221 let y_mid = py as f32 + 0.5;
1222 let spans = scanline_spans(subpaths, y_mid, rule);
1223 for (sx, ex) in spans {
1224 let px0 = (libm::roundf(sx) as i32).max(0);
1225 let px1 = (libm::roundf(ex) as i32).min(self.width as i32);
1226 for px in px0..px1 {
1227 let x_mid = px as f32 + 0.5;
1228 let t = gradient.calculate_t(x_mid, y_mid);
1229 let lut_idx = (t * 255.0 + 0.5) as usize;
1230 let mut color = lut[lut_idx.min(255)];
1231 if alpha_multiplier < 1.0 {
1232 let a = (((color >> 24) & 0xFF) as f32 * alpha_multiplier + 0.5) as u32;
1233 color = (a.min(255) << 24) | (color & 0x00FF_FFFF);
1234 }
1235 self.blend_pixel(px, py, color);
1236 }
1237 }
1238 }
1239 }
1240 }
1241 }
1242
1243 pub fn fill_path_pattern(
1245 &mut self,
1246 subpaths: &[SubPath],
1247 rule: FillRule,
1248 pattern: &CanvasPattern,
1249 alpha_multiplier: f32,
1250 ) {
1251 mark_canvas_dirty();
1252 if subpaths.is_empty() || pattern.width == 0 || pattern.height == 0 || pattern.pixels.is_empty() {
1253 return;
1254 }
1255 let (mut ymin, mut ymax) = (f32::MAX, f32::MIN);
1256 for sp in subpaths {
1257 for &(_, py) in &sp.points {
1258 if py.is_finite() {
1259 ymin = ymin.min(py);
1260 ymax = ymax.max(py);
1261 }
1262 }
1263 }
1264 if ymin > ymax {
1265 return;
1266 }
1267 let y_start = (libm::floorf(ymin) as i32).max(0);
1268 let y_end = (libm::ceilf(ymax) as i32).min(self.height as i32);
1269
1270 for py in y_start..y_end {
1271 let y_mid = py as f32 + 0.5;
1272 let spans = scanline_spans(subpaths, y_mid, rule);
1273 for (sx, ex) in spans {
1274 let px0 = (libm::roundf(sx) as i32).max(0);
1275 let px1 = (libm::roundf(ex) as i32).min(self.width as i32);
1276 for px in px0..px1 {
1277 let x_mid = px as f32 + 0.5;
1278 let color = pattern.sample(x_mid, y_mid);
1279 let a = (color >> 24) & 0xFF;
1280 if a == 0 {
1281 continue;
1282 }
1283 let final_color = if alpha_multiplier < 1.0 {
1284 let final_a = libm::roundf(a as f32 * alpha_multiplier).clamp(0.0, 255.0) as u32;
1285 (final_a << 24) | (color & 0x00FF_FFFF)
1286 } else {
1287 color
1288 };
1289 self.blend_pixel(px, py, final_color);
1290 }
1291 }
1292 }
1293 }
1294
1295 pub fn stroke_path(&mut self, subpaths: &[SubPath], argb: u32) {
1299 mark_canvas_dirty();
1300 for sp in subpaths {
1301 let n = sp.points.len();
1302 if n < 2 {
1303 continue;
1304 }
1305 let last = if sp.closed { n } else { n - 1 };
1306 for i in 0..last {
1307 let (x0, y0) = sp.points[i];
1308 let (x1, y1) = sp.points[(i + 1) % n];
1309 self.draw_line(x0, y0, x1, y1, argb);
1310 }
1311 }
1312 }
1313
1314 fn draw_line(&mut self, x0: f32, y0: f32, x1: f32, y1: f32, argb: u32) {
1316 if !x0.is_finite() || !y0.is_finite() || !x1.is_finite() || !y1.is_finite() {
1317 return;
1318 }
1319 let mut x = libm::roundf(x0) as i32;
1320 let mut y = libm::roundf(y0) as i32;
1321 let xe = libm::roundf(x1) as i32;
1322 let ye = libm::roundf(y1) as i32;
1323 let dx = (xe - x).abs();
1324 let dy = -(ye - y).abs();
1325 let sx = if x < xe { 1 } else { -1 };
1326 let sy = if y < ye { 1 } else { -1 };
1327 let mut err = dx + dy;
1328 let mut guard = 0i32;
1330 let limit = (self.width as i32 + self.height as i32) * 4 + 16;
1331 loop {
1332 self.blend_pixel(x, y, argb);
1333 if (x == xe && y == ye) || guard > limit {
1334 break;
1335 }
1336 guard += 1;
1337 let e2 = err * 2;
1338 if e2 >= dy {
1339 err += dy;
1340 x += sx;
1341 }
1342 if e2 <= dx {
1343 err += dx;
1344 y += sy;
1345 }
1346 }
1347 }
1348}
1349
1350
1351#[derive(Debug, Clone)]
1355pub struct DrawState {
1356 pub transform: Matrix,
1357 pub fill: u32,
1359 pub fill_str: alloc::string::String,
1361 pub stroke: u32,
1363 pub stroke_str: alloc::string::String,
1365 pub line_width: f32,
1366 pub line_cap: LineCap,
1368 pub line_join: LineJoin,
1370 pub miter_limit: f32,
1372 pub line_dash: Vec<f32>,
1374 pub line_dash_offset: f32,
1376 pub clip: Option<alloc::sync::Arc<ClipMask>>,
1378 pub global_alpha: f32,
1380 pub font_str: alloc::string::String,
1382 pub text_align_str: alloc::string::String,
1384 pub text_baseline_str: alloc::string::String,
1386 pub image_smoothing_enabled: bool,
1388 pub fill_gradient: Option<CanvasGradient>,
1390 pub stroke_gradient: Option<CanvasGradient>,
1392 pub fill_pattern: Option<CanvasPattern>,
1394 pub stroke_pattern: Option<CanvasPattern>,
1396 pub shadow_blur: f32,
1398 pub shadow_color: u32,
1400 pub shadow_color_str: alloc::string::String,
1402 pub shadow_offset_x: f32,
1404 pub shadow_offset_y: f32,
1406 pub filter: alloc::string::String,
1408 pub global_composite_operation: CompositeOperation,
1410 pub global_composite_operation_str: alloc::string::String,
1412 pub direction: CanvasDirection,
1414 pub direction_str: alloc::string::String,
1416}
1417
1418impl DrawState {
1419 pub fn has_shadow(&self) -> bool {
1421 let a = (self.shadow_color >> 24) & 0xFF;
1422 a > 0 && (self.shadow_offset_x != 0.0 || self.shadow_offset_y != 0.0 || self.shadow_blur > 0.0)
1423 }
1424
1425 pub fn effective_direction(&self) -> CanvasDirection {
1427 match self.direction {
1428 CanvasDirection::Rtl => CanvasDirection::Rtl,
1429 _ => CanvasDirection::Ltr,
1430 }
1431 }
1432}
1433
1434impl Default for DrawState {
1435 fn default() -> Self {
1436 DrawState {
1437 transform: Matrix::identity(),
1438 fill: 0xFF00_0000,
1440 fill_str: alloc::string::String::from("#000000"),
1441 stroke: 0xFF00_0000,
1442 stroke_str: alloc::string::String::from("#000000"),
1443 line_width: 1.0,
1444 line_cap: LineCap::Butt,
1445 line_join: LineJoin::Miter,
1446 miter_limit: DEFAULT_MITER_LIMIT,
1447 line_dash: Vec::new(),
1448 line_dash_offset: 0.0,
1449 clip: None,
1450 global_alpha: 1.0,
1451 font_str: alloc::string::String::from("10px sans-serif"),
1452 text_align_str: alloc::string::String::from("start"),
1453 text_baseline_str: alloc::string::String::from("alphabetic"),
1454 image_smoothing_enabled: true,
1455 fill_gradient: None,
1456 stroke_gradient: None,
1457 fill_pattern: None,
1458 stroke_pattern: None,
1459 shadow_blur: 0.0,
1460 shadow_color: 0x0000_0000,
1461 shadow_color_str: alloc::string::String::from("rgba(0, 0, 0, 0)"),
1462 shadow_offset_x: 0.0,
1463 shadow_offset_y: 0.0,
1464 filter: alloc::string::String::from("none"),
1465 global_composite_operation: CompositeOperation::SourceOver,
1466 global_composite_operation_str: alloc::string::String::from("source-over"),
1467 direction: CanvasDirection::Inherit,
1468 direction_str: alloc::string::String::from("inherit"),
1469 }
1470 }
1471}
1472
1473#[derive(Debug)]
1479pub struct Canvas2dContext {
1480 pub surface: Surface,
1481 pub path: PathBuilder,
1482 pub state: DrawState,
1483 stack: Vec<DrawState>,
1485}
1486
1487const MAX_SAVE_DEPTH: usize = 32;
1492
1493impl Canvas2dContext {
1494 pub fn new(width: u32, height: u32) -> Option<Self> {
1495 Some(Canvas2dContext {
1496 surface: Surface::new(width, height)?,
1497 path: PathBuilder::new(),
1498 state: DrawState::default(),
1499 stack: Vec::new(),
1500 })
1501 }
1502
1503 pub fn save(&mut self) {
1506 if self.stack.len() >= MAX_SAVE_DEPTH {
1507 return;
1508 }
1509 self.stack.push(self.state.clone());
1510 }
1511
1512 pub fn restore(&mut self) {
1515 if let Some(s) = self.stack.pop() {
1516 self.state = s;
1517 }
1518 }
1519
1520 pub fn apply_alpha(&self, argb: u32) -> u32 {
1525 let ga = self.state.global_alpha.clamp(0.0, 1.0);
1526 if ga >= 1.0 {
1527 return argb;
1528 }
1529 let a = ((argb >> 24) & 0xFF) as f32 * ga;
1530 let a = libm::roundf(a).clamp(0.0, 255.0) as u32;
1531 (a << 24) | (argb & 0x00FF_FFFF)
1532 }
1533
1534 pub fn shadow_color_argb(&self) -> u32 {
1536 self.apply_alpha(self.state.shadow_color)
1537 }
1538
1539 pub fn fill_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
1545 self.sync_clip();
1546 let mut p = PathBuilder::new();
1547 p.rect(&self.state.transform, x, y, w, h);
1548 let sub = p.finish();
1549 if self.state.has_shadow() {
1550 let shadow_sub = offset_subpaths(&sub, self.state.shadow_offset_x, self.state.shadow_offset_y);
1551 let s_color = self.shadow_color_argb();
1552 self.surface.fill_path(&shadow_sub, FillRule::NonZero, s_color);
1553 }
1554 if let Some(ref grad) = self.state.fill_gradient {
1555 self.surface.fill_path_gradient(&sub, FillRule::NonZero, grad, self.state.global_alpha.clamp(0.0, 1.0));
1556 } else if let Some(ref pat) = self.state.fill_pattern {
1557 self.surface.fill_path_pattern(&sub, FillRule::NonZero, pat, self.state.global_alpha.clamp(0.0, 1.0));
1558 } else {
1559 let color = self.apply_alpha(self.state.fill);
1560 self.surface.fill_path(&sub, FillRule::NonZero, color);
1561 }
1562 }
1563
1564 pub fn clear_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
1569 self.sync_clip();
1570 let m = &self.state.transform;
1571 let pts = [
1572 m.apply(x, y),
1573 m.apply(x + w, y),
1574 m.apply(x + w, y + h),
1575 m.apply(x, y + h),
1576 ];
1577 let (mut x0, mut y0, mut x1, mut y1) = (f32::MAX, f32::MAX, f32::MIN, f32::MIN);
1578 for (px, py) in pts {
1579 if !px.is_finite() || !py.is_finite() {
1580 return;
1581 }
1582 x0 = x0.min(px);
1583 y0 = y0.min(py);
1584 x1 = x1.max(px);
1585 y1 = y1.max(py);
1586 }
1587 self.surface.fill_rect(
1588 libm::floorf(x0) as i32,
1589 libm::floorf(y0) as i32,
1590 libm::ceilf(x1 - x0) as i32,
1591 libm::ceilf(y1 - y0) as i32,
1592 0,
1593 true,
1594 );
1595 }
1596
1597 pub fn fill(&mut self, rule: FillRule) {
1599 self.sync_clip();
1600 let sub = self.path.finish();
1601 if self.state.has_shadow() {
1602 let shadow_sub = offset_subpaths(&sub, self.state.shadow_offset_x, self.state.shadow_offset_y);
1603 let s_color = self.shadow_color_argb();
1604 self.surface.fill_path(&shadow_sub, rule, s_color);
1605 }
1606 if let Some(ref grad) = self.state.fill_gradient {
1607 self.surface.fill_path_gradient(&sub, rule, grad, self.state.global_alpha.clamp(0.0, 1.0));
1608 } else if let Some(ref pat) = self.state.fill_pattern {
1609 self.surface.fill_path_pattern(&sub, rule, pat, self.state.global_alpha.clamp(0.0, 1.0));
1610 } else {
1611 let color = self.apply_alpha(self.state.fill);
1612 self.surface.fill_path(&sub, rule, color);
1613 }
1614 }
1615
1616 fn stroke_subpaths_internal(&mut self, subpaths: &[SubPath]) {
1618 self.sync_clip();
1619 let dashed = self.apply_dash(subpaths);
1620 let lw = self.state.line_width;
1621 if !lw.is_finite() || lw <= 0.0 {
1622 return;
1623 }
1624 let (cap, join, limit) = (self.state.line_cap, self.state.line_join, self.state.miter_limit);
1625 let has_shadow = self.state.has_shadow();
1626 let shadow_offset_x = self.state.shadow_offset_x;
1627 let shadow_offset_y = self.state.shadow_offset_y;
1628 let s_color = if has_shadow { self.shadow_color_argb() } else { 0 };
1629 let alpha = self.state.global_alpha.clamp(0.0, 1.0);
1630 let stroke_color = self.apply_alpha(self.state.stroke);
1631 let stroke_grad = self.state.stroke_gradient.clone();
1632 let stroke_pat = self.state.stroke_pattern.clone();
1633
1634 if has_shadow {
1635 let shadow_sub = offset_subpaths(&dashed, shadow_offset_x, shadow_offset_y);
1636 self.surface.stroke_path_styled(&shadow_sub, s_color, lw, cap, join, limit);
1637 }
1638 if let Some(ref grad) = stroke_grad {
1639 let outline = stroke_to_outline(&dashed, lw, cap, join, limit);
1640 self.surface.fill_path_gradient(&outline, FillRule::NonZero, grad, alpha);
1641 } else if let Some(ref pat) = stroke_pat {
1642 let outline = stroke_to_outline(&dashed, lw, cap, join, limit);
1643 self.surface.fill_path_pattern(&outline, FillRule::NonZero, pat, alpha);
1644 } else {
1645 self.surface.stroke_path_styled(&dashed, stroke_color, lw, cap, join, limit);
1646 }
1647 }
1648
1649 pub fn stroke(&mut self) {
1651 let sub = self.path.finish();
1652 self.stroke_subpaths_internal(&sub);
1653 }
1654
1655 pub fn stroke_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
1657 let mut p = PathBuilder::new();
1658 p.rect(&self.state.transform, x, y, w, h);
1659 let sub = p.finish();
1660 self.stroke_subpaths_internal(&sub);
1661 }
1662
1663 pub fn translate(&mut self, tx: f32, ty: f32) {
1665 self.state.transform = self.state.transform.multiply(&Matrix::translate(tx, ty));
1666 }
1667
1668 pub fn scale(&mut self, sx: f32, sy: f32) {
1670 self.state.transform = self.state.transform.multiply(&Matrix::scale(sx, sy));
1671 }
1672
1673 pub fn rotate(&mut self, rad: f32) {
1675 self.state.transform = self.state.transform.multiply(&Matrix::rotate(rad));
1676 }
1677
1678 pub fn set_transform(&mut self, m: Matrix) {
1680 self.state.transform = m;
1681 }
1682
1683 pub fn reset_transform(&mut self) {
1685 self.state.transform = Matrix::identity();
1686 }
1687
1688 pub fn begin_path(&mut self) {
1689 self.path.begin_path();
1690 }
1691
1692 pub fn close_path(&mut self) {
1693 self.path.close_path();
1694 }
1695
1696 pub fn move_to(&mut self, x: f32, y: f32) {
1697 let m = self.state.transform;
1698 self.path.move_to(&m, x, y);
1699 }
1700
1701 pub fn line_to(&mut self, x: f32, y: f32) {
1702 let m = self.state.transform;
1703 self.path.line_to(&m, x, y);
1704 }
1705
1706 pub fn arc_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, radius: f32) {
1707 let m = self.state.transform;
1708 self.path.arc_to(&m, x1, y1, x2, y2, radius);
1709 }
1710}
1711
1712
1713static CONTEXTS: spin::Mutex<alloc::collections::BTreeMap<u32, Canvas2dContext>> =
1722 spin::Mutex::new(alloc::collections::BTreeMap::new());
1723
1724static NEXT_ID: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(1);
1726
1727const MAX_CONTEXTS: usize = 8;
1732
1733pub fn create_context(width: u32, height: u32) -> Option<u32> {
1737 let ctx = Canvas2dContext::new(width, height)?;
1738 let id = NEXT_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
1739 let mut map = CONTEXTS.lock();
1740 while map.len() >= MAX_CONTEXTS {
1742 let Some(&oldest) = map.keys().next() else {
1743 break;
1744 };
1745 map.remove(&oldest);
1746 }
1747 map.insert(id, ctx);
1748 Some(id)
1749}
1750
1751pub fn with_context<R>(id: u32, f: impl FnOnce(&mut Canvas2dContext) -> R) -> Option<R> {
1756 let mut map = CONTEXTS.lock();
1757 map.get_mut(&id).map(f)
1758}
1759
1760pub fn clear_contexts() {
1762 CONTEXTS.lock().clear();
1763}
1764
1765pub fn id_from_tag(tag: &str) -> Option<u32> {
1769 tag.strip_prefix("canvas2d:")?.parse::<u32>().ok()
1770}
1771
1772pub fn tag_for_id(id: u32) -> alloc::string::String {
1774 alloc::format!("canvas2d:{}", id)
1775}
1776
1777impl PathBuilder {
1778 #[allow(clippy::too_many_arguments)]
1784 pub fn ellipse(
1785 &mut self,
1786 m: &Matrix,
1787 cx: f32,
1788 cy: f32,
1789 rx: f32,
1790 ry: f32,
1791 rotation: f32,
1792 start: f32,
1793 end: f32,
1794 anticlockwise: bool,
1795 ) {
1796 if !rx.is_finite()
1797 || !ry.is_finite()
1798 || rx < 0.0
1799 || ry < 0.0
1800 || !start.is_finite()
1801 || !end.is_finite()
1802 || !rotation.is_finite()
1803 {
1804 return;
1805 }
1806 let sweep = normalize_sweep(start, end, anticlockwise);
1807 let steps = arc_steps(sweep);
1808 let (rs, rc) = (libm::sinf(rotation), libm::cosf(rotation));
1809 for i in 0..=steps {
1810 let t = i as f32 / steps as f32;
1811 let ang = start + sweep * t;
1812 let ux = rx * libm::cosf(ang);
1814 let uy = ry * libm::sinf(ang);
1815 let px = cx + ux * rc - uy * rs;
1817 let py = cy + ux * rs + uy * rc;
1818 if i == 0 && self.current.is_none() {
1819 self.move_to(m, px, py);
1820 } else {
1821 self.line_to(m, px, py);
1822 }
1823 }
1824 }
1825
1826 #[allow(clippy::too_many_arguments)]
1832 pub fn round_rect(
1833 &mut self,
1834 m: &Matrix,
1835 x: f32,
1836 y: f32,
1837 w: f32,
1838 h: f32,
1839 radii: [f32; 4],
1840 ) {
1841 if !w.is_finite() || !h.is_finite() || w == 0.0 || h == 0.0 {
1842 return;
1843 }
1844 let (x, w) = if w < 0.0 { (x + w, -w) } else { (x, w) };
1846 let (y, h) = if h < 0.0 { (y + h, -h) } else { (y, h) };
1847
1848 let mut r = [0.0f32; 4];
1849 for (i, v) in radii.iter().enumerate() {
1850 r[i] = if v.is_finite() && *v > 0.0 { *v } else { 0.0 };
1851 }
1852 let mut scale = 1.0f32;
1854 let pairs = [
1855 (r[0] + r[1], w), (r[2] + r[3], w), (r[1] + r[2], h), (r[3] + r[0], h), ];
1860 for (sum, len) in pairs {
1861 if sum > 0.0 && sum > len {
1862 scale = scale.min(len / sum);
1863 }
1864 }
1865 for v in r.iter_mut() {
1866 *v *= scale;
1867 }
1868
1869 let half_pi = core::f32::consts::PI / 2.0;
1870 let pi = core::f32::consts::PI;
1871 self.flush();
1872 self.move_to(m, x + r[0], y);
1874 self.line_to(m, x + w - r[1], y);
1875 if r[1] > 0.0 {
1876 self.arc(m, x + w - r[1], y + r[1], r[1], -half_pi, 0.0, false);
1877 }
1878 self.line_to(m, x + w, y + h - r[2]);
1879 if r[2] > 0.0 {
1880 self.arc(m, x + w - r[2], y + h - r[2], r[2], 0.0, half_pi, false);
1881 }
1882 self.line_to(m, x + r[3], y + h);
1883 if r[3] > 0.0 {
1884 self.arc(m, x + r[3], y + h - r[3], r[3], half_pi, pi, false);
1885 }
1886 self.line_to(m, x, y + r[0]);
1887 if r[0] > 0.0 {
1888 self.arc(m, x + r[0], y + r[0], r[0], pi, pi + half_pi, false);
1889 }
1890 self.close_path();
1891 }
1892}
1893
1894impl Surface {
1895 pub fn stroke_path_width(&mut self, subpaths: &[SubPath], argb: u32, width: f32) {
1910 self.stroke_path_styled(
1911 subpaths,
1912 argb,
1913 width,
1914 LineCap::Butt,
1915 LineJoin::Round,
1916 DEFAULT_MITER_LIMIT,
1917 );
1918 }
1919
1920 fn fill_disc(&mut self, cx: f32, cy: f32, r: f32, argb: u32) {
1922 if !cx.is_finite() || !cy.is_finite() || !r.is_finite() || r <= 0.0 {
1923 return;
1924 }
1925 let y0 = (libm::floorf(cy - r) as i32).max(0);
1926 let y1 = (libm::ceilf(cy + r) as i32).min(self.height as i32);
1927 let r2 = r * r;
1928 for py in y0..y1 {
1929 let dy = py as f32 + 0.5 - cy;
1930 let d = r2 - dy * dy;
1931 if d < 0.0 {
1932 continue;
1933 }
1934 let dx = libm::sqrtf(d);
1935 let x0 = (libm::roundf(cx - dx) as i32).max(0);
1936 let x1 = (libm::roundf(cx + dx) as i32).min(self.width as i32);
1937 for px in x0..x1 {
1938 self.blend_pixel(px, py, argb);
1939 }
1940 }
1941 }
1942}
1943
1944impl Canvas2dContext {
1945 pub fn stroke_with_width(&mut self) {
1947 self.stroke();
1948 }
1949}
1950
1951#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1953pub enum LineCap {
1954 Butt,
1956 Round,
1958 Square,
1960}
1961
1962#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1964pub enum LineJoin {
1965 Miter,
1967 Round,
1969 Bevel,
1971}
1972
1973pub const DEFAULT_MITER_LIMIT: f32 = 10.0;
1975
1976pub fn parse_line_cap(s: &str) -> Option<LineCap> {
1977 match s.trim() {
1978 "butt" => Some(LineCap::Butt),
1979 "round" => Some(LineCap::Round),
1980 "square" => Some(LineCap::Square),
1981 _ => None,
1982 }
1983}
1984
1985pub fn parse_line_join(s: &str) -> Option<LineJoin> {
1986 match s.trim() {
1987 "miter" => Some(LineJoin::Miter),
1988 "round" => Some(LineJoin::Round),
1989 "bevel" => Some(LineJoin::Bevel),
1990 _ => None,
1991 }
1992}
1993
1994#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1996pub enum CompositeOperation {
1997 SourceOver,
1998 SourceIn,
1999 SourceOut,
2000 SourceAtop,
2001 DestinationOver,
2002 DestinationIn,
2003 DestinationOut,
2004 DestinationAtop,
2005 Lighter,
2006 Copy,
2007 Xor,
2008}
2009
2010pub fn parse_composite_operation(s: &str) -> Option<CompositeOperation> {
2011 let t = s.trim();
2012 if t.eq_ignore_ascii_case("source-over") {
2013 Some(CompositeOperation::SourceOver)
2014 } else if t.eq_ignore_ascii_case("source-in") {
2015 Some(CompositeOperation::SourceIn)
2016 } else if t.eq_ignore_ascii_case("source-out") {
2017 Some(CompositeOperation::SourceOut)
2018 } else if t.eq_ignore_ascii_case("source-atop") {
2019 Some(CompositeOperation::SourceAtop)
2020 } else if t.eq_ignore_ascii_case("destination-over") {
2021 Some(CompositeOperation::DestinationOver)
2022 } else if t.eq_ignore_ascii_case("destination-in") {
2023 Some(CompositeOperation::DestinationIn)
2024 } else if t.eq_ignore_ascii_case("destination-out") {
2025 Some(CompositeOperation::DestinationOut)
2026 } else if t.eq_ignore_ascii_case("destination-atop") {
2027 Some(CompositeOperation::DestinationAtop)
2028 } else if t.eq_ignore_ascii_case("lighter") || t.eq_ignore_ascii_case("plus-lighter") {
2029 Some(CompositeOperation::Lighter)
2030 } else if t.eq_ignore_ascii_case("copy") {
2031 Some(CompositeOperation::Copy)
2032 } else if t.eq_ignore_ascii_case("xor") {
2033 Some(CompositeOperation::Xor)
2034 } else {
2035 None
2036 }
2037}
2038
2039pub fn line_intersection(
2044 p1: (f32, f32),
2045 d1: (f32, f32),
2046 p2: (f32, f32),
2047 d2: (f32, f32),
2048) -> Option<(f32, f32)> {
2049 let den = d1.0 * d2.1 - d1.1 * d2.0;
2050 if libm::fabsf(den) < 1e-6 {
2052 return None;
2053 }
2054 let t = ((p2.0 - p1.0) * d2.1 - (p2.1 - p1.1) * d2.0) / den;
2055 let x = p1.0 + d1.0 * t;
2056 let y = p1.1 + d1.1 * t;
2057 if !x.is_finite() || !y.is_finite() {
2058 return None;
2059 }
2060 Some((x, y))
2061}
2062
2063impl Surface {
2064 pub fn stroke_path_styled(
2068 &mut self,
2069 subpaths: &[SubPath],
2070 argb: u32,
2071 width: f32,
2072 cap: LineCap,
2073 join: LineJoin,
2074 miter_limit: f32,
2075 ) {
2076 mark_canvas_dirty();
2077 if !width.is_finite() || width <= 1.0 {
2079 self.stroke_path(subpaths, argb);
2080 return;
2081 }
2082 let half = width / 2.0;
2083 let limit = if miter_limit.is_finite() && miter_limit > 0.0 {
2084 miter_limit
2085 } else {
2086 DEFAULT_MITER_LIMIT
2087 };
2088 for sp in subpaths {
2089 let n = sp.points.len();
2090 if n < 2 {
2091 continue;
2092 }
2093 let seg_end = if sp.closed { n } else { n - 1 };
2094
2095 for i in 0..seg_end {
2097 let a = sp.points[i];
2098 let b = sp.points[(i + 1) % n];
2099 let Some((dx, dy, len)) = unit_delta(a, b) else {
2100 continue;
2101 };
2102 let (mut ax, mut ay) = a;
2104 let (mut bx, mut by) = b;
2105 if cap == LineCap::Square && !sp.closed {
2106 if i == 0 {
2107 ax -= dx * half;
2108 ay -= dy * half;
2109 }
2110 if i == seg_end - 1 {
2111 bx += dx * half;
2112 by += dy * half;
2113 }
2114 }
2115 let _ = len;
2116 let (nx, ny) = (-dy * half, dx * half);
2117 self.fill_quad(
2118 (ax + nx, ay + ny),
2119 (bx + nx, by + ny),
2120 (bx - nx, by - ny),
2121 (ax - nx, ay - ny),
2122 argb,
2123 );
2124 }
2125
2126 let joint_from = if sp.closed { 0 } else { 1 };
2128 let joint_to = if sp.closed { n } else { n - 1 };
2129 for j in joint_from..joint_to {
2130 let prev = sp.points[(j + n - 1) % n];
2131 let cur = sp.points[j];
2132 let next = sp.points[(j + 1) % n];
2133 self.fill_joint(prev, cur, next, half, argb, join, limit);
2134 }
2135
2136 if cap == LineCap::Round && !sp.closed {
2138 let first = sp.points[0];
2139 let last = sp.points[n - 1];
2140 self.fill_disc(first.0, first.1, half, argb);
2141 self.fill_disc(last.0, last.1, half, argb);
2142 }
2143 }
2144 }
2145
2146 #[allow(clippy::too_many_arguments)]
2148 fn fill_joint(
2149 &mut self,
2150 prev: (f32, f32),
2151 cur: (f32, f32),
2152 next: (f32, f32),
2153 half: f32,
2154 argb: u32,
2155 join: LineJoin,
2156 limit: f32,
2157 ) {
2158 if join == LineJoin::Round {
2159 self.fill_disc(cur.0, cur.1, half, argb);
2160 return;
2161 }
2162 let (Some((d1x, d1y, _)), Some((d2x, d2y, _))) =
2163 (unit_delta(prev, cur), unit_delta(cur, next))
2164 else {
2165 return;
2166 };
2167 let cross = d1x * d2y - d1y * d2x;
2169 if libm::fabsf(cross) < 1e-6 {
2170 return;
2172 }
2173 let s = if cross > 0.0 { -1.0f32 } else { 1.0f32 };
2175 let p1 = (cur.0 + s * -d1y * half, cur.1 + s * d1x * half);
2176 let p2 = (cur.0 + s * -d2y * half, cur.1 + s * d2x * half);
2177
2178 if join == LineJoin::Miter {
2179 if let Some(mp) = line_intersection(p1, (d1x, d1y), p2, (d2x, d2y)) {
2180 let (mx, my) = (mp.0 - cur.0, mp.1 - cur.1);
2181 let miter_len = libm::sqrtf(mx * mx + my * my);
2182 if miter_len / half <= limit {
2185 self.fill_quad(cur, p1, mp, p2, argb);
2186 return;
2187 }
2188 }
2189 }
2190 self.fill_tri(cur, p1, p2, argb);
2192 }
2193
2194 fn fill_quad(
2195 &mut self,
2196 a: (f32, f32),
2197 b: (f32, f32),
2198 c: (f32, f32),
2199 d: (f32, f32),
2200 argb: u32,
2201 ) {
2202 for p in [a, b, c, d] {
2203 if !p.0.is_finite() || !p.1.is_finite() {
2204 return;
2205 }
2206 }
2207 let poly = alloc::vec![SubPath {
2208 points: alloc::vec![a, b, c, d],
2209 closed: true,
2210 }];
2211 self.fill_path(&poly, FillRule::NonZero, argb);
2212 }
2213
2214 fn fill_tri(&mut self, a: (f32, f32), b: (f32, f32), c: (f32, f32), argb: u32) {
2215 for p in [a, b, c] {
2216 if !p.0.is_finite() || !p.1.is_finite() {
2217 return;
2218 }
2219 }
2220 let poly = alloc::vec![SubPath {
2221 points: alloc::vec![a, b, c],
2222 closed: true,
2223 }];
2224 self.fill_path(&poly, FillRule::NonZero, argb);
2225 }
2226}
2227
2228pub fn polygon_signed_area(pts: &[(f32, f32)]) -> f32 {
2230 let n = pts.len();
2231 if n < 3 {
2232 return 0.0;
2233 }
2234 let mut area = 0.0f32;
2235 for i in 0..n {
2236 let (x0, y0) = pts[i];
2237 let (x1, y1) = pts[(i + 1) % n];
2238 area += x0 * y1 - x1 * y0;
2239 }
2240 area * 0.5
2241}
2242
2243fn normalize_convex_poly(mut pts: Vec<(f32, f32)>) -> Option<SubPath> {
2244 for p in &pts {
2245 if !p.0.is_finite() || !p.1.is_finite() {
2246 return None;
2247 }
2248 }
2249 if pts.len() < 3 {
2250 return None;
2251 }
2252 let area = polygon_signed_area(&pts);
2253 if libm::fabsf(area) < 1e-5 {
2254 return None;
2255 }
2256 if area < 0.0 {
2257 pts.reverse();
2258 }
2259 Some(SubPath {
2260 points: pts,
2261 closed: true,
2262 })
2263}
2264
2265fn circle_poly(cx: f32, cy: f32, r: f32) -> Option<SubPath> {
2266 if !cx.is_finite() || !cy.is_finite() || !r.is_finite() || r <= 0.0 {
2267 return None;
2268 }
2269 const SEGMENTS: usize = 16;
2270 let mut pts = Vec::with_capacity(SEGMENTS);
2271 for k in 0..SEGMENTS {
2272 let theta = (k as f32) * (2.0 * core::f32::consts::PI / (SEGMENTS as f32));
2273 let px = cx + r * libm::cosf(theta);
2274 let py = cy + r * libm::sinf(theta);
2275 pts.push((px, py));
2276 }
2277 normalize_convex_poly(pts)
2278}
2279
2280pub fn stroke_to_outline(
2287 subpaths: &[SubPath],
2288 width: f32,
2289 cap: LineCap,
2290 join: LineJoin,
2291 miter_limit: f32,
2292) -> Vec<SubPath> {
2293 if !width.is_finite() || width <= 0.0 || subpaths.is_empty() {
2294 return Vec::new();
2295 }
2296 let half = width / 2.0;
2297 let limit = if miter_limit.is_finite() && miter_limit > 0.0 {
2298 miter_limit
2299 } else {
2300 DEFAULT_MITER_LIMIT
2301 };
2302
2303 let mut result = Vec::new();
2304
2305 for sp in subpaths {
2306 let n = sp.points.len();
2307 if n < 2 {
2308 continue;
2309 }
2310 let seg_end = if sp.closed { n } else { n - 1 };
2311
2312 for i in 0..seg_end {
2314 let a = sp.points[i];
2315 let b = sp.points[(i + 1) % n];
2316 let Some((dx, dy, _)) = unit_delta(a, b) else {
2317 continue;
2318 };
2319 let (mut ax, mut ay) = a;
2320 let (mut bx, mut by) = b;
2321 if cap == LineCap::Square && !sp.closed {
2322 if i == 0 {
2323 ax -= dx * half;
2324 ay -= dy * half;
2325 }
2326 if i == seg_end - 1 {
2327 bx += dx * half;
2328 by += dy * half;
2329 }
2330 }
2331 let (nx, ny) = (-dy * half, dx * half);
2332 let p0 = (ax + nx, ay + ny);
2333 let p1 = (bx + nx, by + ny);
2334 let p2 = (bx - nx, by - ny);
2335 let p3 = (ax - nx, ay - ny);
2336 if let Some(poly) = normalize_convex_poly(alloc::vec![p0, p1, p2, p3]) {
2337 result.push(poly);
2338 }
2339 }
2340
2341 let joint_from = if sp.closed { 0 } else { 1 };
2343 let joint_to = if sp.closed { n } else { n - 1 };
2344 for j in joint_from..joint_to {
2345 let prev = sp.points[(j + n - 1) % n];
2346 let cur = sp.points[j];
2347 let next = sp.points[(j + 1) % n];
2348
2349 if join == LineJoin::Round {
2350 if let Some(circle) = circle_poly(cur.0, cur.1, half) {
2351 result.push(circle);
2352 }
2353 continue;
2354 }
2355
2356 let (Some((d1x, d1y, _)), Some((d2x, d2y, _))) =
2357 (unit_delta(prev, cur), unit_delta(cur, next))
2358 else {
2359 continue;
2360 };
2361
2362 let cross = d1x * d2y - d1y * d2x;
2363 if libm::fabsf(cross) < 1e-6 {
2364 continue;
2365 }
2366 let s = if cross > 0.0 { -1.0f32 } else { 1.0f32 };
2367 let p1 = (cur.0 + s * -d1y * half, cur.1 + s * d1x * half);
2368 let p2 = (cur.0 + s * -d2y * half, cur.1 + s * d2x * half);
2369
2370 if join == LineJoin::Miter {
2371 if let Some(mp) = line_intersection(p1, (d1x, d1y), p2, (d2x, d2y)) {
2372 let (mx, my) = (mp.0 - cur.0, mp.1 - cur.1);
2373 let miter_len = libm::sqrtf(mx * mx + my * my);
2374 if miter_len / half <= limit {
2375 if let Some(poly) = normalize_convex_poly(alloc::vec![cur, p1, mp, p2]) {
2376 result.push(poly);
2377 continue;
2378 }
2379 }
2380 }
2381 }
2382 if let Some(poly) = normalize_convex_poly(alloc::vec![cur, p1, p2]) {
2384 result.push(poly);
2385 }
2386 }
2387
2388 if cap == LineCap::Round && !sp.closed {
2390 let first = sp.points[0];
2391 let last = sp.points[n - 1];
2392 if let Some(circle) = circle_poly(first.0, first.1, half) {
2393 result.push(circle);
2394 }
2395 if let Some(circle) = circle_poly(last.0, last.1, half) {
2396 result.push(circle);
2397 }
2398 }
2399 }
2400
2401 result
2402}
2403
2404pub fn unit_delta(a: (f32, f32), b: (f32, f32)) -> Option<(f32, f32, f32)> {
2406 let (dx, dy) = (b.0 - a.0, b.1 - a.1);
2407 if !dx.is_finite() || !dy.is_finite() {
2408 return None;
2409 }
2410 let len = libm::sqrtf(dx * dx + dy * dy);
2411 if len < 1e-6 {
2412 return None;
2413 }
2414 Some((dx / len, dy / len, len))
2415}
2416
2417static CANVAS_DIRTY: core::sync::atomic::AtomicBool =
2427 core::sync::atomic::AtomicBool::new(false);
2428
2429pub fn mark_canvas_dirty() {
2431 CANVAS_DIRTY.store(true, core::sync::atomic::Ordering::Relaxed);
2432}
2433
2434pub fn canvas_dirty() -> bool {
2436 CANVAS_DIRTY.load(core::sync::atomic::Ordering::Relaxed)
2437}
2438
2439pub fn take_canvas_dirty() -> bool {
2441 CANVAS_DIRTY.swap(false, core::sync::atomic::Ordering::Relaxed)
2442}
2443
2444pub fn normalize_dash(pattern: &[f32]) -> Option<Vec<f32>> {
2450 if pattern.is_empty() {
2451 return None;
2452 }
2453 for v in pattern {
2454 if !v.is_finite() || *v < 0.0 {
2455 return None;
2456 }
2457 }
2458 if pattern.iter().all(|v| *v == 0.0) {
2459 return None;
2460 }
2461 let mut out: Vec<f32> = pattern.to_vec();
2462 if out.len() % 2 == 1 {
2464 let dup = out.clone();
2465 out.extend(dup);
2466 }
2467 Some(out)
2468}
2469
2470pub fn dash_subpaths(subpaths: &[SubPath], pattern: &[f32], offset: f32) -> Vec<SubPath> {
2478 let Some(pat) = normalize_dash(pattern) else {
2479 return subpaths.to_vec();
2480 };
2481 let total: f32 = pat.iter().sum();
2482 if total <= 0.0 {
2483 return subpaths.to_vec();
2484 }
2485 let mut out: Vec<SubPath> = Vec::new();
2486 for sp in subpaths {
2487 let n = sp.points.len();
2488 if n < 2 {
2489 continue;
2490 }
2491 let mut phase = if offset.is_finite() {
2493 let m = libm::fmodf(offset, total);
2494 if m < 0.0 {
2495 m + total
2496 } else {
2497 m
2498 }
2499 } else {
2500 0.0
2501 };
2502 let mut idx = 0usize;
2504 while phase >= pat[idx] {
2505 phase -= pat[idx];
2506 idx = (idx + 1) % pat.len();
2507 }
2508 let mut drawing = idx % 2 == 0;
2509 let mut cur: Vec<(f32, f32)> = Vec::new();
2510 if drawing {
2511 cur.push(sp.points[0]);
2512 }
2513
2514 let seg_end = if sp.closed { n } else { n - 1 };
2515 for i in 0..seg_end {
2516 let a = sp.points[i];
2517 let b = sp.points[(i + 1) % n];
2518 let Some((dx, dy, len)) = unit_delta(a, b) else {
2519 continue;
2520 };
2521 let mut travelled = 0.0f32;
2522 while travelled < len {
2523 let remain = pat[idx] - phase;
2525 let step = remain.min(len - travelled);
2526 travelled += step;
2527 phase += step;
2528 let p = (a.0 + dx * travelled, a.1 + dy * travelled);
2529 if phase >= pat[idx] - 1e-6 {
2530 if drawing {
2532 cur.push(p);
2533 if cur.len() >= 2 {
2534 out.push(SubPath {
2535 points: core::mem::take(&mut cur),
2536 closed: false,
2537 });
2538 } else {
2539 cur.clear();
2540 }
2541 } else {
2542 cur.clear();
2543 cur.push(p);
2544 }
2545 phase = 0.0;
2546 idx = (idx + 1) % pat.len();
2547 drawing = !drawing;
2548 } else if drawing {
2549 cur.push(p);
2551 }
2552 }
2553 }
2554 if drawing && cur.len() >= 2 {
2555 out.push(SubPath {
2556 points: cur,
2557 closed: false,
2558 });
2559 }
2560 }
2561 out
2562}
2563
2564impl Canvas2dContext {
2565 pub fn apply_dash(&self, subpaths: &[SubPath]) -> Vec<SubPath> {
2567 if self.state.line_dash.is_empty() {
2568 return subpaths.to_vec();
2569 }
2570 dash_subpaths(subpaths, &self.state.line_dash, self.state.line_dash_offset)
2571 }
2572
2573 pub fn is_point_in_stroke(&self, subpaths: &[SubPath], x: f32, y: f32) -> bool {
2576 let dashed = self.apply_dash(subpaths);
2577 let outline = stroke_to_outline(
2578 &dashed,
2579 self.state.line_width,
2580 self.state.line_cap,
2581 self.state.line_join,
2582 self.state.miter_limit,
2583 );
2584 is_point_in_path(&outline, x, y, FillRule::NonZero)
2585 }
2586}
2587
2588#[derive(Debug, Clone)]
2595pub struct ClipMask {
2596 pub width: u32,
2597 pub height: u32,
2598 pub bits: Vec<bool>,
2600}
2601
2602impl ClipMask {
2603 pub fn from_path(width: u32, height: u32, subpaths: &[SubPath], rule: FillRule) -> ClipMask {
2607 let mut bits = alloc::vec![false; (width as usize) * (height as usize)];
2608 for y in 0..height {
2609 let spans = scanline_spans(subpaths, y as f32 + 0.5, rule);
2611 for (x0, x1) in spans {
2612 let a = (libm::ceilf(x0 - 0.5) as i32).max(0);
2613 let b = (libm::ceilf(x1 - 0.5) as i32).min(width as i32);
2614 for x in a..b {
2615 bits[y as usize * width as usize + x as usize] = true;
2616 }
2617 }
2618 }
2619 ClipMask {
2620 width,
2621 height,
2622 bits,
2623 }
2624 }
2625
2626 pub fn intersect(&self, other: &ClipMask) -> ClipMask {
2629 if self.width != other.width || self.height != other.height {
2630 return self.clone();
2632 }
2633 let bits = self
2634 .bits
2635 .iter()
2636 .zip(other.bits.iter())
2637 .map(|(a, b)| *a && *b)
2638 .collect();
2639 ClipMask {
2640 width: self.width,
2641 height: self.height,
2642 bits,
2643 }
2644 }
2645
2646 pub fn allows(&self, x: i32, y: i32) -> bool {
2648 if x < 0 || y < 0 || x as u32 >= self.width || y as u32 >= self.height {
2649 return false;
2650 }
2651 self.bits[y as usize * self.width as usize + x as usize]
2652 }
2653}
2654
2655impl Canvas2dContext {
2656 pub fn clip(&mut self, rule: FillRule) {
2661 let sub = self.path.finish();
2662 if sub.is_empty() {
2663 return;
2664 }
2665 let mask = ClipMask::from_path(self.surface.width, self.surface.height, &sub, rule);
2666 let merged = match &self.state.clip {
2667 Some(prev) => prev.intersect(&mask),
2668 None => mask,
2669 };
2670 self.state.clip = Some(alloc::sync::Arc::new(merged));
2671 }
2672
2673 pub fn sync_clip(&mut self) {
2675 self.surface.clip = self.state.clip.clone();
2676 self.surface.composite_op = self.state.global_composite_operation;
2677 }
2678}
2679
2680pub fn get_image_data(s: &Surface, x: i32, y: i32, w: i32, h: i32) -> Vec<u8> {
2687 if w <= 0 || h <= 0 {
2688 return Vec::new();
2689 }
2690 let mut out = alloc::vec![0u8; (w as usize) * (h as usize) * 4];
2691 for row in 0..h {
2692 for col in 0..w {
2693 let (sx, sy) = (x + col, y + row);
2694 if sx < 0 || sy < 0 || sx as u32 >= s.width || sy as u32 >= s.height {
2695 continue;
2696 }
2697 let argb = s.pixels[sy as usize * s.width as usize + sx as usize];
2698 let o = ((row as usize) * (w as usize) + col as usize) * 4;
2699 out[o] = ((argb >> 16) & 0xFF) as u8;
2700 out[o + 1] = ((argb >> 8) & 0xFF) as u8;
2701 out[o + 2] = (argb & 0xFF) as u8;
2702 out[o + 3] = ((argb >> 24) & 0xFF) as u8;
2703 }
2704 }
2705 out
2706}
2707
2708pub fn put_image_data(s: &mut Surface, data: &[u8], w: i32, h: i32, x: i32, y: i32) {
2713 if w <= 0 || h <= 0 {
2714 return;
2715 }
2716 mark_canvas_dirty();
2718 for row in 0..h {
2719 for col in 0..w {
2720 let o = ((row as usize) * (w as usize) + col as usize) * 4;
2721 if o + 3 >= data.len() {
2722 return;
2723 }
2724 let (dx, dy) = (x + col, y + row);
2725 if dx < 0 || dy < 0 || dx as u32 >= s.width || dy as u32 >= s.height {
2726 continue;
2727 }
2728 let argb = ((data[o + 3] as u32) << 24)
2729 | ((data[o] as u32) << 16)
2730 | ((data[o + 1] as u32) << 8)
2731 | (data[o + 2] as u32);
2732 s.pixels[dy as usize * s.width as usize + dx as usize] = argb;
2733 }
2734 }
2735}
2736
2737pub fn resize_context(id: u32, width: u32, height: u32) -> bool {
2745 let Some(fresh) = Surface::new(width, height) else {
2746 return false;
2747 };
2748 mark_canvas_dirty();
2749 with_context(id, |c| {
2750 c.surface = fresh;
2751 c.state = DrawState::default();
2752 c.path = PathBuilder::new();
2753 c.stack.clear();
2754 })
2755 .is_some()
2756}
2757
2758pub struct ImageSource<'a> {
2762 pub width: u32,
2763 pub height: u32,
2764 pub rgba: &'a [u8],
2765}
2766
2767impl ImageSource<'_> {
2768 pub fn sample(&self, x: i32, y: i32) -> u32 {
2770 if x < 0 || y < 0 || x as u32 >= self.width || y as u32 >= self.height {
2771 return 0;
2772 }
2773 let o = (y as usize * self.width as usize + x as usize) * 4;
2774 if o + 3 >= self.rgba.len() {
2775 return 0;
2776 }
2777 ((self.rgba[o + 3] as u32) << 24)
2778 | ((self.rgba[o] as u32) << 16)
2779 | ((self.rgba[o + 1] as u32) << 8)
2780 | (self.rgba[o + 2] as u32)
2781 }
2782
2783 pub fn sample_bilinear(&self, sxf: f32, syf: f32) -> u32 {
2785 let u = sxf - 0.5;
2786 let v = syf - 0.5;
2787 let x0 = libm::floorf(u) as i32;
2788 let y0 = libm::floorf(v) as i32;
2789 let x1 = x0 + 1;
2790 let y1 = y0 + 1;
2791
2792 let fx = ((u - x0 as f32) * 256.0).clamp(0.0, 256.0) as u32;
2793 let fy = ((v - y0 as f32) * 256.0).clamp(0.0, 256.0) as u32;
2794
2795 let c00 = self.sample(x0, y0);
2796 let c10 = self.sample(x1, y0);
2797 let c01 = self.sample(x0, y1);
2798 let c11 = self.sample(x1, y1);
2799
2800 if c00 == c10 && c10 == c01 && c01 == c11 {
2801 return c00;
2802 }
2803
2804 let w00 = (256 - fx) * (256 - fy);
2805 let w10 = fx * (256 - fy);
2806 let w01 = (256 - fx) * fy;
2807 let w11 = fx * fy;
2808
2809 let a = (((c00 >> 24) & 0xFF) * w00
2810 + ((c10 >> 24) & 0xFF) * w10
2811 + ((c01 >> 24) & 0xFF) * w01
2812 + ((c11 >> 24) & 0xFF) * w11 + 32768) >> 16;
2813
2814 let r = (((c00 >> 16) & 0xFF) * w00
2815 + ((c10 >> 16) & 0xFF) * w10
2816 + ((c01 >> 16) & 0xFF) * w01
2817 + ((c11 >> 16) & 0xFF) * w11 + 32768) >> 16;
2818
2819 let g = (((c00 >> 8) & 0xFF) * w00
2820 + ((c10 >> 8) & 0xFF) * w10
2821 + ((c01 >> 8) & 0xFF) * w01
2822 + ((c11 >> 8) & 0xFF) * w11 + 32768) >> 16;
2823
2824 let b = ((c00 & 0xFF) * w00
2825 + (c10 & 0xFF) * w10
2826 + (c01 & 0xFF) * w01
2827 + (c11 & 0xFF) * w11 + 32768) >> 16;
2828
2829 (a.min(255) << 24) | (r.min(255) << 16) | (g.min(255) << 8) | b.min(255)
2830 }
2831}
2832
2833#[derive(Debug, Clone, Copy)]
2835pub struct BlitSpec {
2836 pub sx: f32,
2838 pub sy: f32,
2839 pub sw: f32,
2840 pub sh: f32,
2841 pub dx: f32,
2843 pub dy: f32,
2844 pub dw: f32,
2845 pub dh: f32,
2846}
2847
2848impl Surface {
2849 pub fn draw_image(&mut self, src: &ImageSource, spec: &BlitSpec, m: &Matrix, alpha: f32, smooth: bool) {
2859 self.draw_image_with_filter(src, spec, m, alpha, smooth, "none");
2860 }
2861
2862 pub fn draw_image_with_filter(
2864 &mut self,
2865 src: &ImageSource,
2866 spec: &BlitSpec,
2867 m: &Matrix,
2868 alpha: f32,
2869 smooth: bool,
2870 filter: &str,
2871 ) {
2872 if spec.dw == 0.0 || spec.dh == 0.0 || spec.sw == 0.0 || spec.sh == 0.0 {
2873 return;
2874 }
2875 for v in [
2876 spec.sx, spec.sy, spec.sw, spec.sh, spec.dx, spec.dy, spec.dw, spec.dh,
2877 ] {
2878 if !v.is_finite() {
2879 return;
2880 }
2881 }
2882 let Some(inv) = m.invert() else {
2883 return;
2884 };
2885 mark_canvas_dirty();
2886
2887 let corners = [
2889 m.apply(spec.dx, spec.dy),
2890 m.apply(spec.dx + spec.dw, spec.dy),
2891 m.apply(spec.dx + spec.dw, spec.dy + spec.dh),
2892 m.apply(spec.dx, spec.dy + spec.dh),
2893 ];
2894 let mut x0 = corners[0].0;
2895 let mut x1 = corners[0].0;
2896 let mut y0 = corners[0].1;
2897 let mut y1 = corners[0].1;
2898 for (cx, cy) in corners.iter().skip(1) {
2899 x0 = x0.min(*cx);
2900 x1 = x1.max(*cx);
2901 y0 = y0.min(*cy);
2902 y1 = y1.max(*cy);
2903 }
2904 let px0 = (libm::floorf(x0) as i32).max(0);
2905 let px1 = (libm::ceilf(x1) as i32).min(self.width as i32);
2906 let py0 = (libm::floorf(y0) as i32).max(0);
2907 let py1 = (libm::ceilf(y1) as i32).min(self.height as i32);
2908
2909 let a = alpha.clamp(0.0, 1.0);
2910 let has_filter = filter != "none" && !filter.is_empty();
2911 for py in py0..py1 {
2912 for px in px0..px1 {
2913 let (ux, uy) = inv.apply(px as f32 + 0.5, py as f32 + 0.5);
2915 let tx = (ux - spec.dx) / spec.dw;
2917 let ty = (uy - spec.dy) / spec.dh;
2918 if !(0.0..1.0).contains(&tx) || !(0.0..1.0).contains(&ty) {
2919 continue;
2920 }
2921 let sxf = spec.sx + tx * spec.sw;
2923 let syf = spec.sy + ty * spec.sh;
2924 let mut argb = if smooth {
2925 src.sample_bilinear(sxf, syf)
2926 } else {
2927 src.sample(libm::floorf(sxf) as i32, libm::floorf(syf) as i32)
2928 };
2929 if argb >> 24 == 0 {
2930 continue;
2931 }
2932 if has_filter {
2933 argb = crate::os_lib::web_engine::css_filters::apply_css_filters_argb(filter, argb);
2934 }
2935 let argb = if a >= 1.0 {
2936 argb
2937 } else {
2938 let na = (((argb >> 24) & 0xFF) as f32 * a) as u32;
2939 (na << 24) | (argb & 0x00FF_FFFF)
2940 };
2941 self.blend_pixel(px, py, argb);
2942 }
2943 }
2944 }
2945}
2946
2947#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2951pub enum TextAlign {
2952 Start,
2953 End,
2954 Left,
2955 Right,
2956 Center,
2957}
2958
2959#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2961pub enum TextBaseline {
2962 Top,
2963 Hanging,
2964 Middle,
2965 Alphabetic,
2966 Ideographic,
2967 Bottom,
2968}
2969
2970pub fn parse_text_align(s: &str) -> Option<TextAlign> {
2971 match s.trim() {
2972 "start" => Some(TextAlign::Start),
2973 "end" => Some(TextAlign::End),
2974 "left" => Some(TextAlign::Left),
2975 "right" => Some(TextAlign::Right),
2976 "center" => Some(TextAlign::Center),
2977 _ => None,
2978 }
2979}
2980
2981pub fn parse_text_baseline(s: &str) -> Option<TextBaseline> {
2982 match s.trim() {
2983 "top" => Some(TextBaseline::Top),
2984 "hanging" => Some(TextBaseline::Hanging),
2985 "middle" => Some(TextBaseline::Middle),
2986 "alphabetic" => Some(TextBaseline::Alphabetic),
2987 "ideographic" => Some(TextBaseline::Ideographic),
2988 "bottom" => Some(TextBaseline::Bottom),
2989 _ => None,
2990 }
2991}
2992
2993#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2995pub enum CanvasDirection {
2996 Ltr,
2997 Rtl,
2998 Inherit,
2999}
3000
3001impl Default for CanvasDirection {
3002 fn default() -> Self {
3003 CanvasDirection::Inherit
3004 }
3005}
3006
3007pub fn parse_canvas_direction(s: &str) -> Option<CanvasDirection> {
3009 let s = s.trim();
3010 if s.eq_ignore_ascii_case("ltr") {
3011 Some(CanvasDirection::Ltr)
3012 } else if s.eq_ignore_ascii_case("rtl") {
3013 Some(CanvasDirection::Rtl)
3014 } else if s.eq_ignore_ascii_case("inherit") {
3015 Some(CanvasDirection::Inherit)
3016 } else {
3017 None
3018 }
3019}
3020
3021pub fn align_offset_with_direction(
3028 align: TextAlign,
3029 total_advance: f32,
3030 direction: CanvasDirection,
3031) -> f32 {
3032 let is_rtl = matches!(direction, CanvasDirection::Rtl);
3033 match align {
3034 TextAlign::Start => {
3035 if is_rtl {
3036 -total_advance
3037 } else {
3038 0.0
3039 }
3040 }
3041 TextAlign::End => {
3042 if is_rtl {
3043 0.0
3044 } else {
3045 -total_advance
3046 }
3047 }
3048 TextAlign::Left => 0.0,
3049 TextAlign::Right => -total_advance,
3050 TextAlign::Center => -total_advance / 2.0,
3051 }
3052}
3053
3054pub fn align_offset(align: TextAlign, total_advance: f32) -> f32 {
3056 align_offset_with_direction(align, total_advance, CanvasDirection::Ltr)
3057}
3058
3059pub fn baseline_offset(baseline: TextBaseline, size_px: f32) -> f32 {
3066 match baseline {
3067 TextBaseline::Alphabetic => 0.0,
3068 TextBaseline::Top => size_px * 0.85,
3069 TextBaseline::Hanging => size_px * 0.8,
3070 TextBaseline::Middle => size_px * 0.35,
3071 TextBaseline::Ideographic | TextBaseline::Bottom => -size_px * 0.15,
3072 }
3073}
3074
3075pub fn parse_font_size(font: &str) -> Option<f32> {
3081 for tok in font.split(|c: char| c.is_whitespace() || c == '/') {
3082 let t = tok.trim();
3083 let Some(num) = t.strip_suffix("px") else {
3084 continue;
3085 };
3086 if let Ok(v) = num.parse::<f32>() {
3087 if v.is_finite() && v > 0.0 {
3088 return Some(v);
3089 }
3090 }
3091 }
3092 None
3093}
3094
3095pub struct GlyphBitmap<'a> {
3100 pub width: u32,
3101 pub height: u32,
3102 pub x_offset: i32,
3104 pub y_offset: i32,
3105 pub advance: f32,
3107 pub alpha: &'a [u8],
3109}
3110
3111impl Surface {
3112 pub fn draw_glyph(&mut self, g: &GlyphBitmap, pen_x: f32, pen_y: f32, argb: u32, m: &Matrix) {
3118 if g.width == 0 || g.height == 0 {
3119 return;
3120 }
3121 let n = (g.width as usize) * (g.height as usize);
3122 if g.alpha.len() < n {
3123 return;
3124 }
3125 let (r, gg, b) = ((argb >> 16) & 0xFF, (argb >> 8) & 0xFF, argb & 0xFF);
3126 let base_a = (argb >> 24) & 0xFF;
3127 let mut rgba = alloc::vec![0u8; n * 4];
3128 for i in 0..n {
3129 let a = (g.alpha[i] as u32 * base_a / 255) as u8;
3130 rgba[i * 4] = r as u8;
3131 rgba[i * 4 + 1] = gg as u8;
3132 rgba[i * 4 + 2] = b as u8;
3133 rgba[i * 4 + 3] = a;
3134 }
3135 let src = ImageSource {
3136 width: g.width,
3137 height: g.height,
3138 rgba: &rgba,
3139 };
3140 let spec = BlitSpec {
3141 sx: 0.0,
3142 sy: 0.0,
3143 sw: g.width as f32,
3144 sh: g.height as f32,
3145 dx: pen_x + g.x_offset as f32,
3146 dy: pen_y + g.y_offset as f32,
3147 dw: g.width as f32,
3148 dh: g.height as f32,
3149 };
3150 self.draw_image(&src, &spec, m, 1.0, false);
3151 }
3152
3153 pub fn draw_glyph_stroke(
3157 &mut self,
3158 g: &GlyphBitmap,
3159 pen_x: f32,
3160 pen_y: f32,
3161 stroke_argb: u32,
3162 line_width: f32,
3163 m: &Matrix,
3164 ) {
3165 if g.width == 0 || g.height == 0 || line_width <= 0.0 {
3166 return;
3167 }
3168 let n = (g.width as usize) * (g.height as usize);
3169 if g.alpha.len() < n {
3170 return;
3171 }
3172 let r = (line_width / 2.0).clamp(0.5, 4.0);
3173 let ir = libm::ceilf(r) as i32;
3174 let w = g.width as i32;
3175 let h = g.height as i32;
3176
3177 let mut stroke_alpha = alloc::vec![0u8; n];
3178 for y in 0..h {
3179 for x in 0..w {
3180 let idx = (y * w + x) as usize;
3181 let a = g.alpha[idx];
3182 if a > 0 {
3183 for dy in -ir..=ir {
3184 for dx in -ir..=ir {
3185 let dist = libm::sqrtf((dx * dx + dy * dy) as f32);
3186 if dist <= r {
3187 let nx = x + dx;
3188 let ny = y + dy;
3189 if nx >= 0 && nx < w && ny >= 0 && ny < h {
3190 let nidx = (ny * w + nx) as usize;
3191 let falloff = if r > 0.0 {
3192 (1.0 - (dist / (r + 0.5))).clamp(0.0, 1.0)
3193 } else {
3194 1.0
3195 };
3196 let strk_val = (a as f32 * falloff) as u8;
3197 if strk_val > stroke_alpha[nidx] {
3198 stroke_alpha[nidx] = strk_val;
3199 }
3200 }
3201 }
3202 }
3203 }
3204 }
3205 }
3206 }
3207 let (r_col, g_col, b_col) = (
3208 (stroke_argb >> 16) & 0xFF,
3209 (stroke_argb >> 8) & 0xFF,
3210 stroke_argb & 0xFF,
3211 );
3212 let base_a = (stroke_argb >> 24) & 0xFF;
3213 let mut rgba = alloc::vec![0u8; n * 4];
3214 for i in 0..n {
3215 let a = (stroke_alpha[i] as u32 * base_a / 255) as u8;
3216 rgba[i * 4] = r_col as u8;
3217 rgba[i * 4 + 1] = g_col as u8;
3218 rgba[i * 4 + 2] = b_col as u8;
3219 rgba[i * 4 + 3] = a;
3220 }
3221 let src = ImageSource {
3222 width: g.width,
3223 height: g.height,
3224 rgba: &rgba,
3225 };
3226 let spec = BlitSpec {
3227 sx: 0.0,
3228 sy: 0.0,
3229 sw: g.width as f32,
3230 sh: g.height as f32,
3231 dx: pen_x + g.x_offset as f32,
3232 dy: pen_y + g.y_offset as f32,
3233 dw: g.width as f32,
3234 dh: g.height as f32,
3235 };
3236 self.draw_image(&src, &spec, m, 1.0, false);
3237 }
3238}
3239
3240
3241struct SvgPathTokenizer<'a> {
3245 data: &'a [u8],
3246 pos: usize,
3247}
3248
3249impl<'a> SvgPathTokenizer<'a> {
3250 fn new(data: &'a str) -> Self {
3251 Self {
3252 data: data.as_bytes(),
3253 pos: 0,
3254 }
3255 }
3256
3257 fn skip_whitespace_and_comma(&mut self) {
3258 while self.pos < self.data.len() {
3259 let c = self.data[self.pos];
3260 if c == b' ' || c == b'\t' || c == b'\n' || c == b'\r' || c == b',' {
3261 self.pos += 1;
3262 } else {
3263 break;
3264 }
3265 }
3266 }
3267
3268 fn next_cmd(&mut self) -> Option<char> {
3269 self.skip_whitespace_and_comma();
3270 if self.pos >= self.data.len() {
3271 return None;
3272 }
3273 let c = self.data[self.pos] as char;
3274 if c.is_ascii_alphabetic() {
3275 self.pos += 1;
3276 Some(c)
3277 } else {
3278 None
3279 }
3280 }
3281
3282 fn next_f32(&mut self) -> Option<f32> {
3283 self.skip_whitespace_and_comma();
3284 if self.pos >= self.data.len() {
3285 return None;
3286 }
3287 let start = self.pos;
3288 if self.pos < self.data.len() && (self.data[self.pos] == b'-' || self.data[self.pos] == b'+') {
3289 self.pos += 1;
3290 }
3291 let mut has_digit = false;
3292 while self.pos < self.data.len() && self.data[self.pos].is_ascii_digit() {
3293 self.pos += 1;
3294 has_digit = true;
3295 }
3296 if self.pos < self.data.len() && self.data[self.pos] == b'.' {
3297 self.pos += 1;
3298 while self.pos < self.data.len() && self.data[self.pos].is_ascii_digit() {
3299 self.pos += 1;
3300 has_digit = true;
3301 }
3302 }
3303 if has_digit && self.pos < self.data.len() && (self.data[self.pos] == b'e' || self.data[self.pos] == b'E') {
3304 self.pos += 1;
3305 if self.pos < self.data.len() && (self.data[self.pos] == b'-' || self.data[self.pos] == b'+') {
3306 self.pos += 1;
3307 }
3308 while self.pos < self.data.len() && self.data[self.pos].is_ascii_digit() {
3309 self.pos += 1;
3310 }
3311 }
3312 if !has_digit {
3313 self.pos = start;
3314 return None;
3315 }
3316 let s = core::str::from_utf8(&self.data[start..self.pos]).ok()?;
3317 s.parse::<f32>().ok()
3318 }
3319
3320 fn next_flag(&mut self) -> Option<bool> {
3321 self.skip_whitespace_and_comma();
3322 if self.pos >= self.data.len() {
3323 return None;
3324 }
3325 let b = self.data[self.pos];
3326 if b == b'0' {
3327 self.pos += 1;
3328 Some(false)
3329 } else if b == b'1' {
3330 self.pos += 1;
3331 Some(true)
3332 } else {
3333 None
3334 }
3335 }
3336}
3337
3338#[allow(clippy::too_many_arguments)]
3341fn svg_arc_to_center(
3342 x1: f32,
3343 y1: f32,
3344 mut rx: f32,
3345 mut ry: f32,
3346 angle_deg: f32,
3347 large_arc: bool,
3348 sweep: bool,
3349 x2: f32,
3350 y2: f32,
3351) -> Option<(f32, f32, f32, f32, f32, f32, f32, bool)> {
3352 if libm::fabsf(x1 - x2) < 1e-6 && libm::fabsf(y1 - y2) < 1e-6 {
3353 return None;
3354 }
3355 rx = libm::fabsf(rx);
3356 ry = libm::fabsf(ry);
3357 if rx < 1e-6 || ry < 1e-6 {
3358 return None;
3359 }
3360
3361 let phi = angle_deg * (core::f32::consts::PI / 180.0);
3362 let cos_phi = libm::cosf(phi);
3363 let sin_phi = libm::sinf(phi);
3364
3365 let dx = (x1 - x2) / 2.0;
3367 let dy = (y1 - y2) / 2.0;
3368 let x1_prime = cos_phi * dx + sin_phi * dy;
3369 let y1_prime = -sin_phi * dx + cos_phi * dy;
3370
3371 let lambda = (x1_prime * x1_prime) / (rx * rx) + (y1_prime * y1_prime) / (ry * ry);
3373 if lambda > 1.0 {
3374 let sqrt_lambda = libm::sqrtf(lambda);
3375 rx *= sqrt_lambda;
3376 ry *= sqrt_lambda;
3377 }
3378
3379 let rx_sq = rx * rx;
3381 let ry_sq = ry * ry;
3382 let x1_p_sq = x1_prime * x1_prime;
3383 let y1_p_sq = y1_prime * y1_prime;
3384
3385 let num = rx_sq * ry_sq - rx_sq * y1_p_sq - ry_sq * x1_p_sq;
3386 let den = rx_sq * y1_p_sq + ry_sq * x1_p_sq;
3387 let sq = if den > 0.0 { libm::fmaxf(0.0, num / den) } else { 0.0 };
3388 let sign = if large_arc == sweep { -1.0 } else { 1.0 };
3389 let coef = sign * libm::sqrtf(sq);
3390
3391 let cx_prime = coef * ((rx * y1_prime) / ry);
3392 let cy_prime = coef * -((ry * x1_prime) / rx);
3393
3394 let cx = cos_phi * cx_prime - sin_phi * cy_prime + (x1 + x2) / 2.0;
3396 let cy = sin_phi * cx_prime + cos_phi * cy_prime + (y1 + y2) / 2.0;
3397
3398 let ux = (x1_prime - cx_prime) / rx;
3400 let uy = (y1_prime - cy_prime) / ry;
3401 let vx = (-x1_prime - cx_prime) / rx;
3402 let vy = (-y1_prime - cy_prime) / ry;
3403
3404 let u_len = libm::sqrtf(ux * ux + uy * uy);
3405 let cos_theta1 = if u_len > 0.0 { (ux / u_len).clamp(-1.0, 1.0) } else { 1.0 };
3406 let mut theta1 = libm::acosf(cos_theta1);
3407 if uy < 0.0 {
3408 theta1 = -theta1;
3409 }
3410
3411 let dot = ux * vx + uy * vy;
3412 let v_len = libm::sqrtf(vx * vx + vy * vy);
3413 let uv_len = u_len * v_len;
3414 let cos_dtheta = if uv_len > 0.0 { (dot / uv_len).clamp(-1.0, 1.0) } else { 1.0 };
3415 let mut dtheta = libm::acosf(cos_dtheta);
3416 if (ux * vy - uy * vx) < 0.0 {
3417 dtheta = -dtheta;
3418 }
3419
3420 let tau = core::f32::consts::PI * 2.0;
3421 if !sweep && dtheta > 0.0 {
3422 dtheta -= tau;
3423 } else if sweep && dtheta < 0.0 {
3424 dtheta += tau;
3425 }
3426
3427 let anticlockwise = !sweep;
3428 let theta2 = theta1 + dtheta;
3429 Some((cx, cy, rx, ry, phi, theta1, theta2, anticlockwise))
3430}
3431
3432pub fn parse_svg_path_into(d: &str, p: &mut PathBuilder, m: &Matrix) {
3434 let mut tok = SvgPathTokenizer::new(d);
3435 let mut curr_x = 0.0f32;
3436 let mut curr_y = 0.0f32;
3437 let mut start_x = 0.0f32;
3438 let mut start_y = 0.0f32;
3439 let mut last_cmd = ' ';
3440 let mut last_cubic_ctrl: Option<(f32, f32)> = None;
3441 let mut last_quad_ctrl: Option<(f32, f32)> = None;
3442
3443 while tok.pos < tok.data.len() {
3444 tok.skip_whitespace_and_comma();
3445 if tok.pos >= tok.data.len() {
3446 break;
3447 }
3448 let cmd = if let Some(c) = tok.next_cmd() {
3449 last_cmd = c;
3450 c
3451 } else if last_cmd != ' ' {
3452 if last_cmd == 'M' {
3453 'L'
3454 } else if last_cmd == 'm' {
3455 'l'
3456 } else {
3457 last_cmd
3458 }
3459 } else {
3460 break;
3461 };
3462
3463 match cmd {
3464 'M' => {
3465 if let (Some(x), Some(y)) = (tok.next_f32(), tok.next_f32()) {
3466 curr_x = x;
3467 curr_y = y;
3468 start_x = x;
3469 start_y = y;
3470 p.move_to(m, x, y);
3471 last_cubic_ctrl = None;
3472 last_quad_ctrl = None;
3473 }
3474 }
3475 'm' => {
3476 if let (Some(dx), Some(dy)) = (tok.next_f32(), tok.next_f32()) {
3477 curr_x += dx;
3478 curr_y += dy;
3479 start_x = curr_x;
3480 start_y = curr_y;
3481 p.move_to(m, curr_x, curr_y);
3482 last_cubic_ctrl = None;
3483 last_quad_ctrl = None;
3484 }
3485 }
3486 'L' => {
3487 if let (Some(x), Some(y)) = (tok.next_f32(), tok.next_f32()) {
3488 curr_x = x;
3489 curr_y = y;
3490 p.line_to(m, x, y);
3491 last_cubic_ctrl = None;
3492 last_quad_ctrl = None;
3493 }
3494 }
3495 'l' => {
3496 if let (Some(dx), Some(dy)) = (tok.next_f32(), tok.next_f32()) {
3497 curr_x += dx;
3498 curr_y += dy;
3499 p.line_to(m, curr_x, curr_y);
3500 last_cubic_ctrl = None;
3501 last_quad_ctrl = None;
3502 }
3503 }
3504 'H' => {
3505 if let Some(x) = tok.next_f32() {
3506 curr_x = x;
3507 p.line_to(m, curr_x, curr_y);
3508 last_cubic_ctrl = None;
3509 last_quad_ctrl = None;
3510 }
3511 }
3512 'h' => {
3513 if let Some(dx) = tok.next_f32() {
3514 curr_x += dx;
3515 p.line_to(m, curr_x, curr_y);
3516 last_cubic_ctrl = None;
3517 last_quad_ctrl = None;
3518 }
3519 }
3520 'V' => {
3521 if let Some(y) = tok.next_f32() {
3522 curr_y = y;
3523 p.line_to(m, curr_x, curr_y);
3524 last_cubic_ctrl = None;
3525 last_quad_ctrl = None;
3526 }
3527 }
3528 'v' => {
3529 if let Some(dy) = tok.next_f32() {
3530 curr_y += dy;
3531 p.line_to(m, curr_x, curr_y);
3532 last_cubic_ctrl = None;
3533 last_quad_ctrl = None;
3534 }
3535 }
3536 'C' => {
3537 if let (Some(x1), Some(y1), Some(x2), Some(y2), Some(x), Some(y)) = (
3538 tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32()
3539 ) {
3540 p.bezier_curve_to(m, x1, y1, x2, y2, x, y);
3541 last_cubic_ctrl = Some((x2, y2));
3542 last_quad_ctrl = None;
3543 curr_x = x;
3544 curr_y = y;
3545 }
3546 }
3547 'c' => {
3548 if let (Some(dx1), Some(dy1), Some(dx2), Some(dy2), Some(dx), Some(dy)) = (
3549 tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32()
3550 ) {
3551 let (x1, y1) = (curr_x + dx1, curr_y + dy1);
3552 let (x2, y2) = (curr_x + dx2, curr_y + dy2);
3553 let (x, y) = (curr_x + dx, curr_y + dy);
3554 p.bezier_curve_to(m, x1, y1, x2, y2, x, y);
3555 last_cubic_ctrl = Some((x2, y2));
3556 last_quad_ctrl = None;
3557 curr_x = x;
3558 curr_y = y;
3559 }
3560 }
3561 'S' => {
3562 if let (Some(x2), Some(y2), Some(x), Some(y)) = (
3563 tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32()
3564 ) {
3565 let (x1, y1) = if let Some((prev_x2, prev_y2)) = last_cubic_ctrl {
3566 (2.0 * curr_x - prev_x2, 2.0 * curr_y - prev_y2)
3567 } else {
3568 (curr_x, curr_y)
3569 };
3570 p.bezier_curve_to(m, x1, y1, x2, y2, x, y);
3571 last_cubic_ctrl = Some((x2, y2));
3572 last_quad_ctrl = None;
3573 curr_x = x;
3574 curr_y = y;
3575 }
3576 }
3577 's' => {
3578 if let (Some(dx2), Some(dy2), Some(dx), Some(dy)) = (
3579 tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32()
3580 ) {
3581 let (x1, y1) = if let Some((prev_x2, prev_y2)) = last_cubic_ctrl {
3582 (2.0 * curr_x - prev_x2, 2.0 * curr_y - prev_y2)
3583 } else {
3584 (curr_x, curr_y)
3585 };
3586 let (x2, y2) = (curr_x + dx2, curr_y + dy2);
3587 let (x, y) = (curr_x + dx, curr_y + dy);
3588 p.bezier_curve_to(m, x1, y1, x2, y2, x, y);
3589 last_cubic_ctrl = Some((x2, y2));
3590 last_quad_ctrl = None;
3591 curr_x = x;
3592 curr_y = y;
3593 }
3594 }
3595 'Q' => {
3596 if let (Some(x1), Some(y1), Some(x), Some(y)) = (
3597 tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32()
3598 ) {
3599 p.quadratic_curve_to(m, x1, y1, x, y);
3600 last_quad_ctrl = Some((x1, y1));
3601 last_cubic_ctrl = None;
3602 curr_x = x;
3603 curr_y = y;
3604 }
3605 }
3606 'q' => {
3607 if let (Some(dx1), Some(dy1), Some(dx), Some(dy)) = (
3608 tok.next_f32(), tok.next_f32(), tok.next_f32(), tok.next_f32()
3609 ) {
3610 let (x1, y1) = (curr_x + dx1, curr_y + dy1);
3611 let (x, y) = (curr_x + dx, curr_y + dy);
3612 p.quadratic_curve_to(m, x1, y1, x, y);
3613 last_quad_ctrl = Some((x1, y1));
3614 last_cubic_ctrl = None;
3615 curr_x = x;
3616 curr_y = y;
3617 }
3618 }
3619 'T' => {
3620 if let (Some(x), Some(y)) = (tok.next_f32(), tok.next_f32()) {
3621 let (cx, cy) = if let Some((prev_cx, prev_cy)) = last_quad_ctrl {
3622 (2.0 * curr_x - prev_cx, 2.0 * curr_y - prev_cy)
3623 } else {
3624 (curr_x, curr_y)
3625 };
3626 p.quadratic_curve_to(m, cx, cy, x, y);
3627 last_quad_ctrl = Some((cx, cy));
3628 last_cubic_ctrl = None;
3629 curr_x = x;
3630 curr_y = y;
3631 }
3632 }
3633 't' => {
3634 if let (Some(dx), Some(dy)) = (tok.next_f32(), tok.next_f32()) {
3635 let (cx, cy) = if let Some((prev_cx, prev_cy)) = last_quad_ctrl {
3636 (2.0 * curr_x - prev_cx, 2.0 * curr_y - prev_cy)
3637 } else {
3638 (curr_x, curr_y)
3639 };
3640 let (x, y) = (curr_x + dx, curr_y + dy);
3641 p.quadratic_curve_to(m, cx, cy, x, y);
3642 last_quad_ctrl = Some((cx, cy));
3643 last_cubic_ctrl = None;
3644 curr_x = x;
3645 curr_y = y;
3646 }
3647 }
3648 'A' | 'a' => {
3649 let rx_opt = tok.next_f32();
3650 let ry_opt = tok.next_f32();
3651 let angle_opt = tok.next_f32();
3652 let large_arc_opt = tok.next_flag();
3653 let sweep_opt = tok.next_flag();
3654 let x_opt = tok.next_f32();
3655 let y_opt = tok.next_f32();
3656
3657 if let (Some(rx), Some(ry), Some(angle), Some(large_arc), Some(sweep), Some(x), Some(y)) =
3658 (rx_opt, ry_opt, angle_opt, large_arc_opt, sweep_opt, x_opt, y_opt)
3659 {
3660 let (target_x, target_y) = if cmd == 'a' {
3661 (curr_x + x, curr_y + y)
3662 } else {
3663 (x, y)
3664 };
3665
3666 if let Some((cx, cy, r_x, r_y, phi, theta1, theta2, anticlockwise)) =
3667 svg_arc_to_center(curr_x, curr_y, rx, ry, angle, large_arc, sweep, target_x, target_y)
3668 {
3669 p.ellipse(m, cx, cy, r_x, r_y, phi, theta1, theta2, anticlockwise);
3670 } else {
3671 p.line_to(m, target_x, target_y);
3672 }
3673 curr_x = target_x;
3674 curr_y = target_y;
3675 last_cubic_ctrl = None;
3676 last_quad_ctrl = None;
3677 }
3678 }
3679 'Z' | 'z' => {
3680 p.close_path();
3681 curr_x = start_x;
3682 curr_y = start_y;
3683 last_cubic_ctrl = None;
3684 last_quad_ctrl = None;
3685 }
3686 _ => {
3687 tok.pos += 1;
3688 }
3689 }
3690 }
3691}
3692
3693static PATH2D_MAP: spin::Mutex<alloc::collections::BTreeMap<u32, PathBuilder>> =
3696 spin::Mutex::new(alloc::collections::BTreeMap::new());
3697static NEXT_PATH2D_ID: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(1);
3698const MAX_PATH2D: usize = 64;
3699
3700pub fn create_path2d(init_svg: Option<&str>) -> u32 {
3701 let id = NEXT_PATH2D_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
3702 let mut builder = PathBuilder::new();
3703 if let Some(svg) = init_svg {
3704 parse_svg_path_into(svg, &mut builder, &Matrix::identity());
3705 }
3706 let mut map = PATH2D_MAP.lock();
3707 while map.len() >= MAX_PATH2D {
3708 if let Some(&oldest) = map.keys().next() {
3709 map.remove(&oldest);
3710 } else {
3711 break;
3712 }
3713 }
3714 map.insert(id, builder);
3715 id
3716}
3717
3718pub fn clone_path2d(src_id: u32) -> u32 {
3719 let new_id = NEXT_PATH2D_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
3720 let mut map = PATH2D_MAP.lock();
3721 while map.len() >= MAX_PATH2D {
3722 if let Some(&oldest) = map.keys().next() {
3723 map.remove(&oldest);
3724 } else {
3725 break;
3726 }
3727 }
3728 let cloned = map.get(&src_id).cloned().unwrap_or_else(PathBuilder::new);
3729 map.insert(new_id, cloned);
3730 new_id
3731}
3732
3733pub fn with_path2d<R>(id: u32, f: impl FnOnce(&mut PathBuilder) -> R) -> Option<R> {
3734 let mut map = PATH2D_MAP.lock();
3735 map.get_mut(&id).map(f)
3736}
3737
3738pub fn get_path2d_subpaths(id: u32) -> Option<Vec<SubPath>> {
3739 let map = PATH2D_MAP.lock();
3740 map.get(&id).map(|p| p.finish())
3741}
3742
3743pub fn add_path_to_path2d(dst_id: u32, src_id: u32, transform: Option<&Matrix>) {
3744 let mut map = PATH2D_MAP.lock();
3745 if let Some(src) = map.get(&src_id).cloned() {
3746 if let Some(dst) = map.get_mut(&dst_id) {
3747 dst.add_path(&src, transform);
3748 }
3749 }
3750}
3751
3752impl Canvas2dContext {
3753 pub fn fill_subpaths(&mut self, subpaths: &[SubPath], rule: FillRule) {
3755 self.sync_clip();
3756 if let Some(ref grad) = self.state.fill_gradient {
3757 self.surface.fill_path_gradient(subpaths, rule, grad, self.state.global_alpha.clamp(0.0, 1.0));
3758 } else if let Some(ref pat) = self.state.fill_pattern {
3759 self.surface.fill_path_pattern(subpaths, rule, pat, self.state.global_alpha.clamp(0.0, 1.0));
3760 } else {
3761 let color = self.apply_alpha(self.state.fill);
3762 self.surface.fill_path(subpaths, rule, color);
3763 }
3764 }
3765
3766 pub fn stroke_subpaths(&mut self, subpaths: &[SubPath]) {
3768 self.stroke_subpaths_internal(subpaths);
3769 }
3770
3771 pub fn clip_subpaths(&mut self, subpaths: &[SubPath], rule: FillRule) {
3773 if subpaths.is_empty() {
3774 return;
3775 }
3776 let mask = ClipMask::from_path(self.surface.width, self.surface.height, subpaths, rule);
3777 let merged = match &self.state.clip {
3778 Some(prev) => prev.intersect(&mask),
3779 None => mask,
3780 };
3781 self.state.clip = Some(alloc::sync::Arc::new(merged));
3782 }
3783}
3784
3785
3786static GRADIENT_MAP: spin::Mutex<alloc::collections::BTreeMap<u32, CanvasGradient>> =
3789 spin::Mutex::new(alloc::collections::BTreeMap::new());
3790static NEXT_GRADIENT_ID: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(1);
3791const MAX_GRADIENTS: usize = 64;
3792
3793pub fn create_linear_gradient(x0: f32, y0: f32, x1: f32, y1: f32) -> u32 {
3794 let id = NEXT_GRADIENT_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
3795 let grad = CanvasGradient::new_linear(x0, y0, x1, y1);
3796 let mut map = GRADIENT_MAP.lock();
3797 while map.len() >= MAX_GRADIENTS {
3798 if let Some(&oldest) = map.keys().next() {
3799 map.remove(&oldest);
3800 } else {
3801 break;
3802 }
3803 }
3804 map.insert(id, grad);
3805 id
3806}
3807
3808pub fn create_radial_gradient(x0: f32, y0: f32, r0: f32, x1: f32, y1: f32, r1: f32) -> u32 {
3809 let id = NEXT_GRADIENT_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
3810 let grad = CanvasGradient::new_radial(x0, y0, r0, x1, y1, r1);
3811 let mut map = GRADIENT_MAP.lock();
3812 while map.len() >= MAX_GRADIENTS {
3813 if let Some(&oldest) = map.keys().next() {
3814 map.remove(&oldest);
3815 } else {
3816 break;
3817 }
3818 }
3819 map.insert(id, grad);
3820 id
3821}
3822
3823pub fn create_conic_gradient(start_angle: f32, cx: f32, cy: f32) -> u32 {
3824 let id = NEXT_GRADIENT_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
3825 let grad = CanvasGradient::new_conic(start_angle, cx, cy);
3826 let mut map = GRADIENT_MAP.lock();
3827 while map.len() >= MAX_GRADIENTS {
3828 if let Some(&oldest) = map.keys().next() {
3829 map.remove(&oldest);
3830 } else {
3831 break;
3832 }
3833 }
3834 map.insert(id, grad);
3835 id
3836}
3837
3838pub fn add_color_stop_to_gradient(id: u32, offset: f32, color: u32) {
3839 let mut map = GRADIENT_MAP.lock();
3840 if let Some(grad) = map.get_mut(&id) {
3841 grad.add_color_stop(offset, color);
3842 }
3843}
3844
3845pub fn get_gradient(id: u32) -> Option<CanvasGradient> {
3846 let map = GRADIENT_MAP.lock();
3847 map.get(&id).cloned()
3848}
3849
3850
3851static PATTERN_MAP: spin::Mutex<alloc::collections::BTreeMap<u32, CanvasPattern>> =
3854 spin::Mutex::new(alloc::collections::BTreeMap::new());
3855static NEXT_PATTERN_ID: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(1);
3856const MAX_PATTERNS: usize = 64;
3857
3858pub fn create_pattern(width: u32, height: u32, pixels: Vec<u32>, repetition: Repetition) -> u32 {
3859 let id = NEXT_PATTERN_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
3860 let pat = CanvasPattern::new(width, height, pixels, repetition);
3861 let mut map = PATTERN_MAP.lock();
3862 while map.len() >= MAX_PATTERNS {
3863 if let Some(&oldest) = map.keys().next() {
3864 map.remove(&oldest);
3865 } else {
3866 break;
3867 }
3868 }
3869 map.insert(id, pat);
3870 id
3871}
3872
3873pub fn get_pattern(id: u32) -> Option<CanvasPattern> {
3874 let map = PATTERN_MAP.lock();
3875 map.get(&id).cloned()
3876}
3877
3878pub fn set_pattern_transform(id: u32, transform: Matrix) {
3879 let mut map = PATTERN_MAP.lock();
3880 if let Some(pat) = map.get_mut(&id) {
3881 pat.set_transform(transform);
3882 }
3883}