1extern crate alloc;
24use alloc::vec::Vec;
25
26#[derive(Debug, Clone, Copy, PartialEq)]
28pub struct Matrix {
29 pub a: f32,
30 pub b: f32,
31 pub c: f32,
32 pub d: f32,
33 pub e: f32,
34 pub f: f32,
35}
36
37impl Default for Matrix {
38 fn default() -> Self {
39 Self::identity()
40 }
41}
42
43impl Matrix {
44 pub fn identity() -> Self {
45 Matrix { a: 1.0, b: 0.0, c: 0.0, d: 1.0, e: 0.0, f: 0.0 }
46 }
47
48 pub fn translate(tx: f32, ty: f32) -> Self {
49 Matrix { a: 1.0, b: 0.0, c: 0.0, d: 1.0, e: tx, f: ty }
50 }
51
52 pub fn scale(sx: f32, sy: f32) -> Self {
53 Matrix { a: sx, b: 0.0, c: 0.0, d: sy, e: 0.0, f: 0.0 }
54 }
55
56 pub fn rotate(rad: f32) -> Self {
59 let (s, c) = (libm::sinf(rad), libm::cosf(rad));
60 Matrix { a: c, b: s, c: -s, d: c, e: 0.0, f: 0.0 }
61 }
62
63 pub fn multiply(&self, m: &Matrix) -> Matrix {
68 Matrix {
69 a: self.a * m.a + self.c * m.b,
70 b: self.b * m.a + self.d * m.b,
71 c: self.a * m.c + self.c * m.d,
72 d: self.b * m.c + self.d * m.d,
73 e: self.a * m.e + self.c * m.f + self.e,
74 f: self.b * m.e + self.d * m.f + self.f,
75 }
76 }
77
78 pub fn apply(&self, x: f32, y: f32) -> (f32, f32) {
80 (
81 self.a * x + self.c * y + self.e,
82 self.b * x + self.d * y + self.f,
83 )
84 }
85
86 pub fn determinant(&self) -> f32 {
88 self.a * self.d - self.b * self.c
89 }
90
91 pub fn invert(&self) -> Option<Matrix> {
95 let det = self.determinant();
96 if !det.is_finite() || libm::fabsf(det) < 1e-12 {
97 return None;
98 }
99 let inv = 1.0 / det;
100 Some(Matrix {
101 a: self.d * inv,
102 b: -self.b * inv,
103 c: -self.c * inv,
104 d: self.a * inv,
105 e: (self.c * self.f - self.d * self.e) * inv,
106 f: (self.b * self.e - self.a * self.f) * inv,
107 })
108 }
109}
110
111#[derive(Debug, Clone, PartialEq)]
113pub struct SubPath {
114 pub points: Vec<(f32, f32)>,
116 pub closed: bool,
118}
119
120#[derive(Debug, Clone, Default)]
125pub struct PathBuilder {
126 subpaths: Vec<SubPath>,
127 current: Option<SubPath>,
128 last_user: Option<(f32, f32)>,
130}
131
132const ARC_SEGMENTS_PER_QUARTER: usize = 8;
134const BEZIER_SEGMENTS: usize = 16;
136
137impl PathBuilder {
138 pub fn new() -> Self {
139 Self::default()
140 }
141
142 fn flush(&mut self) {
147 if let Some(sp) = self.current.take() {
148 if sp.points.len() >= 2 {
149 self.subpaths.push(sp);
150 }
151 }
152 }
153
154 pub fn begin_path(&mut self) {
155 self.subpaths.clear();
156 self.current = None;
157 self.last_user = None;
158 }
159
160 pub fn move_to(&mut self, m: &Matrix, x: f32, y: f32) {
161 self.flush();
162 self.current = Some(SubPath {
163 points: alloc::vec![m.apply(x, y)],
164 closed: false,
165 });
166 self.last_user = Some((x, y));
167 }
168
169 pub fn line_to(&mut self, m: &Matrix, x: f32, y: f32) {
170 if self.current.is_none() {
172 self.move_to(m, x, y);
173 return;
174 }
175 if let Some(sp) = self.current.as_mut() {
176 sp.points.push(m.apply(x, y));
177 }
178 self.last_user = Some((x, y));
179 }
180
181 pub fn close_path(&mut self) {
182 if let Some(sp) = self.current.as_mut() {
183 sp.closed = true;
184 }
185 self.flush();
186 }
187
188 pub fn bezier_curve_to(
190 &mut self,
191 m: &Matrix,
192 c1x: f32,
193 c1y: f32,
194 c2x: f32,
195 c2y: f32,
196 x: f32,
197 y: f32,
198 ) {
199 let (x0, y0) = match self.last_user {
200 Some(p) => p,
201 None => {
202 self.move_to(m, c1x, c1y);
203 (c1x, c1y)
204 }
205 };
206 for i in 1..=BEZIER_SEGMENTS {
207 let t = i as f32 / BEZIER_SEGMENTS as f32;
208 let mt = 1.0 - t;
209 let px = mt * mt * mt * x0
210 + 3.0 * mt * mt * t * c1x
211 + 3.0 * mt * t * t * c2x
212 + t * t * t * x;
213 let py = mt * mt * mt * y0
214 + 3.0 * mt * mt * t * c1y
215 + 3.0 * mt * t * t * c2y
216 + t * t * t * y;
217 self.line_to(m, px, py);
218 }
219 self.last_user = Some((x, y));
220 }
221
222 pub fn quadratic_curve_to(&mut self, m: &Matrix, cx: f32, cy: f32, x: f32, y: f32) {
224 let (x0, y0) = match self.last_user {
225 Some(p) => p,
226 None => {
227 self.move_to(m, cx, cy);
228 (cx, cy)
229 }
230 };
231 let c1x = x0 + 2.0 / 3.0 * (cx - x0);
233 let c1y = y0 + 2.0 / 3.0 * (cy - y0);
234 let c2x = x + 2.0 / 3.0 * (cx - x);
235 let c2y = y + 2.0 / 3.0 * (cy - y);
236 self.bezier_curve_to(m, c1x, c1y, c2x, c2y, x, y);
237 }
238
239 pub fn arc(
241 &mut self,
242 m: &Matrix,
243 cx: f32,
244 cy: f32,
245 r: f32,
246 start: f32,
247 end: f32,
248 anticlockwise: bool,
249 ) {
250 if !r.is_finite() || r < 0.0 || !start.is_finite() || !end.is_finite() {
253 return;
254 }
255 let sweep = normalize_sweep(start, end, anticlockwise);
256 let steps = arc_steps(sweep);
257 for i in 0..=steps {
258 let t = i as f32 / steps as f32;
259 let ang = start + sweep * t;
260 let px = cx + r * libm::cosf(ang);
261 let py = cy + r * libm::sinf(ang);
262 if i == 0 && self.current.is_none() {
263 self.move_to(m, px, py);
264 } else {
265 self.line_to(m, px, py);
266 }
267 }
268 }
269
270 pub fn rect(&mut self, m: &Matrix, x: f32, y: f32, w: f32, h: f32) {
272 self.flush();
273 self.current = Some(SubPath {
274 points: alloc::vec![
275 m.apply(x, y),
276 m.apply(x + w, y),
277 m.apply(x + w, y + h),
278 m.apply(x, y + h),
279 ],
280 closed: true,
281 });
282 self.flush();
283 self.last_user = Some((x, y));
284 }
285
286 pub fn finish(&self) -> Vec<SubPath> {
288 let mut out = self.subpaths.clone();
289 if let Some(sp) = &self.current {
290 if sp.points.len() >= 2 {
291 out.push(sp.clone());
292 }
293 }
294 out
295 }
296}
297
298pub fn normalize_sweep(start: f32, end: f32, anticlockwise: bool) -> f32 {
304 let tau = core::f32::consts::PI * 2.0;
305 let mut sweep = end - start;
306 if anticlockwise {
307 if sweep > 0.0 {
308 sweep -= tau;
309 }
310 if sweep < -tau {
311 sweep = -tau;
312 }
313 } else {
314 if sweep < 0.0 {
315 sweep += tau;
316 }
317 if sweep > tau {
318 sweep = tau;
319 }
320 }
321 sweep
322}
323
324pub fn arc_steps(sweep: f32) -> usize {
326 let quarters = libm::fabsf(sweep) / (core::f32::consts::PI / 2.0);
327 let n = (quarters * ARC_SEGMENTS_PER_QUARTER as f32) as usize;
328 n.max(1)
329}
330
331#[derive(Debug, Clone, Copy, PartialEq, Eq)]
335pub enum FillRule {
336 NonZero,
338 EvenOdd,
340}
341
342pub fn scanline_spans(subpaths: &[SubPath], y: f32, rule: FillRule) -> Vec<(f32, f32)> {
356 let mut hits: Vec<(f32, i32)> = Vec::new();
358 for sp in subpaths {
359 let n = sp.points.len();
360 if n < 2 {
361 continue;
362 }
363 for i in 0..n {
365 let (x0, y0) = sp.points[i];
366 let (x1, y1) = sp.points[(i + 1) % n];
367 if !y0.is_finite() || !y1.is_finite() || !x0.is_finite() || !x1.is_finite() {
368 continue;
369 }
370 if (y0 - y1).abs() < f32::EPSILON {
372 continue;
373 }
374 let (ylo, yhi) = if y0 < y1 { (y0, y1) } else { (y1, y0) };
380 if y < ylo || y >= yhi {
381 continue;
382 }
383 let t = (y - y0) / (y1 - y0);
384 let x = x0 + t * (x1 - x0);
385 let dir = if y1 > y0 { 1 } else { -1 };
387 hits.push((x, dir));
388 }
389 }
390 if hits.is_empty() {
391 return Vec::new();
392 }
393 hits.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(core::cmp::Ordering::Equal));
395
396 let mut spans = Vec::new();
397 match rule {
398 FillRule::EvenOdd => {
399 let mut i = 0;
400 while i + 1 < hits.len() {
401 spans.push((hits[i].0, hits[i + 1].0));
402 i += 2;
403 }
404 }
405 FillRule::NonZero => {
406 let mut winding = 0i32;
407 let mut start = 0.0f32;
408 for &(x, dir) in &hits {
409 let was_inside = winding != 0;
410 winding += dir;
411 let now_inside = winding != 0;
412 if !was_inside && now_inside {
413 start = x;
414 } else if was_inside && !now_inside {
415 spans.push((start, x));
416 }
417 }
418 }
419 }
420 spans
421}
422
423
424#[derive(Debug, Clone)]
432pub struct Surface {
433 pub width: u32,
434 pub height: u32,
435 pub pixels: Vec<u32>,
437 pub clip: Option<alloc::sync::Arc<ClipMask>>,
443}
444
445impl Surface {
446 pub fn new(width: u32, height: u32) -> Option<Self> {
452 const MAX_SIDE: u32 = 4096;
453 if width == 0 || height == 0 || width > MAX_SIDE || height > MAX_SIDE {
454 return None;
455 }
456 let n = (width as usize).checked_mul(height as usize)?;
457 Some(Surface {
458 width,
459 height,
460 pixels: alloc::vec![0u32; n],
461 clip: None,
462 })
463 }
464
465 pub fn blend_pixel(&mut self, x: i32, y: i32, argb: u32) {
467 if x < 0 || y < 0 || x as u32 >= self.width || y as u32 >= self.height {
468 return;
469 }
470 if let Some(c) = &self.clip {
471 if !c.allows(x, y) {
472 return;
473 }
474 }
475 let a = (argb >> 24) & 0xFF;
476 if a == 0 {
477 return;
478 }
479 let idx = y as usize * self.width as usize + x as usize;
480 if a == 255 {
481 self.pixels[idx] = argb;
482 return;
483 }
484 let dst = self.pixels[idx];
485 let inv = 255 - a;
486 let da = (dst >> 24) & 0xFF;
488 let out_a = a + da * inv / 255;
489 let mix = |shift: u32| -> u32 {
490 let s = (argb >> shift) & 0xFF;
491 let d = (dst >> shift) & 0xFF;
492 (s * a + d * inv) / 255
493 };
494 self.pixels[idx] =
495 (out_a.min(255) << 24) | (mix(16) << 16) | (mix(8) << 8) | mix(0);
496 }
497
498 pub fn fill_rect(&mut self, x: i32, y: i32, w: i32, h: i32, argb: u32, replace: bool) {
502 mark_canvas_dirty();
503 if w <= 0 || h <= 0 {
504 return;
505 }
506 let x0 = x.max(0);
507 let y0 = y.max(0);
508 let x1 = (x.saturating_add(w)).min(self.width as i32);
509 let y1 = (y.saturating_add(h)).min(self.height as i32);
510 for py in y0..y1 {
511 for px in x0..x1 {
512 if replace {
513 if let Some(c) = &self.clip {
516 if !c.allows(px, py) {
517 continue;
518 }
519 }
520 let idx = py as usize * self.width as usize + px as usize;
521 self.pixels[idx] = argb;
522 } else {
523 self.blend_pixel(px, py, argb);
524 }
525 }
526 }
527 }
528
529 pub fn fill_path(&mut self, subpaths: &[SubPath], rule: FillRule, argb: u32) {
534 mark_canvas_dirty();
535 if subpaths.is_empty() {
536 return;
537 }
538 let (mut ymin, mut ymax) = (f32::MAX, f32::MIN);
540 for sp in subpaths {
541 for &(_, py) in &sp.points {
542 if py.is_finite() {
543 ymin = ymin.min(py);
544 ymax = ymax.max(py);
545 }
546 }
547 }
548 if ymin > ymax {
549 return;
550 }
551 let y_start = (libm::floorf(ymin) as i32).max(0);
552 let y_end = (libm::ceilf(ymax) as i32).min(self.height as i32);
553 for py in y_start..y_end {
554 let spans = scanline_spans(subpaths, py as f32 + 0.5, rule);
555 for (sx, ex) in spans {
556 let x0 = (libm::roundf(sx) as i32).max(0);
558 let x1 = (libm::roundf(ex) as i32).min(self.width as i32);
559 for px in x0..x1 {
560 self.blend_pixel(px, py, argb);
561 }
562 }
563 }
564 }
565
566 pub fn stroke_path(&mut self, subpaths: &[SubPath], argb: u32) {
570 mark_canvas_dirty();
571 for sp in subpaths {
572 let n = sp.points.len();
573 if n < 2 {
574 continue;
575 }
576 let last = if sp.closed { n } else { n - 1 };
577 for i in 0..last {
578 let (x0, y0) = sp.points[i];
579 let (x1, y1) = sp.points[(i + 1) % n];
580 self.draw_line(x0, y0, x1, y1, argb);
581 }
582 }
583 }
584
585 fn draw_line(&mut self, x0: f32, y0: f32, x1: f32, y1: f32, argb: u32) {
587 if !x0.is_finite() || !y0.is_finite() || !x1.is_finite() || !y1.is_finite() {
588 return;
589 }
590 let mut x = libm::roundf(x0) as i32;
591 let mut y = libm::roundf(y0) as i32;
592 let xe = libm::roundf(x1) as i32;
593 let ye = libm::roundf(y1) as i32;
594 let dx = (xe - x).abs();
595 let dy = -(ye - y).abs();
596 let sx = if x < xe { 1 } else { -1 };
597 let sy = if y < ye { 1 } else { -1 };
598 let mut err = dx + dy;
599 let mut guard = 0i32;
601 let limit = (self.width as i32 + self.height as i32) * 4 + 16;
602 loop {
603 self.blend_pixel(x, y, argb);
604 if (x == xe && y == ye) || guard > limit {
605 break;
606 }
607 guard += 1;
608 let e2 = err * 2;
609 if e2 >= dy {
610 err += dy;
611 x += sx;
612 }
613 if e2 <= dx {
614 err += dx;
615 y += sy;
616 }
617 }
618 }
619}
620
621
622#[derive(Debug, Clone)]
626pub struct DrawState {
627 pub transform: Matrix,
628 pub fill: u32,
630 pub stroke: u32,
632 pub line_width: f32,
633 pub line_cap: LineCap,
635 pub line_join: LineJoin,
637 pub miter_limit: f32,
639 pub line_dash: Vec<f32>,
641 pub line_dash_offset: f32,
643 pub clip: Option<alloc::sync::Arc<ClipMask>>,
645 pub global_alpha: f32,
647}
648
649impl Default for DrawState {
650 fn default() -> Self {
651 DrawState {
652 transform: Matrix::identity(),
653 fill: 0xFF00_0000,
655 stroke: 0xFF00_0000,
656 line_width: 1.0,
657 line_cap: LineCap::Butt,
658 line_join: LineJoin::Miter,
659 miter_limit: DEFAULT_MITER_LIMIT,
660 line_dash: Vec::new(),
661 line_dash_offset: 0.0,
662 clip: None,
663 global_alpha: 1.0,
664 }
665 }
666}
667
668#[derive(Debug)]
674pub struct Canvas2dContext {
675 pub surface: Surface,
676 pub path: PathBuilder,
677 pub state: DrawState,
678 stack: Vec<DrawState>,
680}
681
682const MAX_SAVE_DEPTH: usize = 32;
687
688impl Canvas2dContext {
689 pub fn new(width: u32, height: u32) -> Option<Self> {
690 Some(Canvas2dContext {
691 surface: Surface::new(width, height)?,
692 path: PathBuilder::new(),
693 state: DrawState::default(),
694 stack: Vec::new(),
695 })
696 }
697
698 pub fn save(&mut self) {
701 if self.stack.len() >= MAX_SAVE_DEPTH {
702 return;
703 }
704 self.stack.push(self.state.clone());
705 }
706
707 pub fn restore(&mut self) {
710 if let Some(s) = self.stack.pop() {
711 self.state = s;
712 }
713 }
714
715 pub fn apply_alpha(&self, argb: u32) -> u32 {
720 let ga = self.state.global_alpha.clamp(0.0, 1.0);
721 if ga >= 1.0 {
722 return argb;
723 }
724 let a = ((argb >> 24) & 0xFF) as f32 * ga;
725 let a = libm::roundf(a).clamp(0.0, 255.0) as u32;
726 (a << 24) | (argb & 0x00FF_FFFF)
727 }
728
729 pub fn fill_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
735 self.sync_clip();
736 let mut p = PathBuilder::new();
737 p.rect(&self.state.transform, x, y, w, h);
738 let color = self.apply_alpha(self.state.fill);
739 self.surface.fill_path(&p.finish(), FillRule::NonZero, color);
740 }
741
742 pub fn clear_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
747 self.sync_clip();
748 let m = &self.state.transform;
749 let pts = [
750 m.apply(x, y),
751 m.apply(x + w, y),
752 m.apply(x + w, y + h),
753 m.apply(x, y + h),
754 ];
755 let (mut x0, mut y0, mut x1, mut y1) = (f32::MAX, f32::MAX, f32::MIN, f32::MIN);
756 for (px, py) in pts {
757 if !px.is_finite() || !py.is_finite() {
758 return;
759 }
760 x0 = x0.min(px);
761 y0 = y0.min(py);
762 x1 = x1.max(px);
763 y1 = y1.max(py);
764 }
765 self.surface.fill_rect(
766 libm::floorf(x0) as i32,
767 libm::floorf(y0) as i32,
768 libm::ceilf(x1 - x0) as i32,
769 libm::ceilf(y1 - y0) as i32,
770 0,
771 true,
772 );
773 }
774
775 pub fn fill(&mut self, rule: FillRule) {
777 self.sync_clip();
778 let color = self.apply_alpha(self.state.fill);
779 let sub = self.path.finish();
780 self.surface.fill_path(&sub, rule, color);
781 }
782
783 pub fn stroke(&mut self) {
785 self.sync_clip();
786 let color = self.apply_alpha(self.state.stroke);
787 let sub = self.path.finish();
788 self.surface.stroke_path(&sub, color);
789 }
790
791 pub fn translate(&mut self, tx: f32, ty: f32) {
793 self.state.transform = self.state.transform.multiply(&Matrix::translate(tx, ty));
794 }
795
796 pub fn scale(&mut self, sx: f32, sy: f32) {
798 self.state.transform = self.state.transform.multiply(&Matrix::scale(sx, sy));
799 }
800
801 pub fn rotate(&mut self, rad: f32) {
803 self.state.transform = self.state.transform.multiply(&Matrix::rotate(rad));
804 }
805
806 pub fn set_transform(&mut self, m: Matrix) {
808 self.state.transform = m;
809 }
810
811 pub fn reset_transform(&mut self) {
813 self.state.transform = Matrix::identity();
814 }
815}
816
817
818static CONTEXTS: spin::Mutex<alloc::collections::BTreeMap<u32, Canvas2dContext>> =
827 spin::Mutex::new(alloc::collections::BTreeMap::new());
828
829static NEXT_ID: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(1);
831
832const MAX_CONTEXTS: usize = 8;
837
838pub fn create_context(width: u32, height: u32) -> Option<u32> {
842 let ctx = Canvas2dContext::new(width, height)?;
843 let id = NEXT_ID.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
844 let mut map = CONTEXTS.lock();
845 while map.len() >= MAX_CONTEXTS {
847 let Some(&oldest) = map.keys().next() else {
848 break;
849 };
850 map.remove(&oldest);
851 }
852 map.insert(id, ctx);
853 Some(id)
854}
855
856pub fn with_context<R>(id: u32, f: impl FnOnce(&mut Canvas2dContext) -> R) -> Option<R> {
861 let mut map = CONTEXTS.lock();
862 map.get_mut(&id).map(f)
863}
864
865pub fn clear_contexts() {
867 CONTEXTS.lock().clear();
868}
869
870pub fn id_from_tag(tag: &str) -> Option<u32> {
874 tag.strip_prefix("canvas2d:")?.parse::<u32>().ok()
875}
876
877pub fn tag_for_id(id: u32) -> alloc::string::String {
879 alloc::format!("canvas2d:{}", id)
880}
881
882impl PathBuilder {
883 #[allow(clippy::too_many_arguments)]
889 pub fn ellipse(
890 &mut self,
891 m: &Matrix,
892 cx: f32,
893 cy: f32,
894 rx: f32,
895 ry: f32,
896 rotation: f32,
897 start: f32,
898 end: f32,
899 anticlockwise: bool,
900 ) {
901 if !rx.is_finite()
902 || !ry.is_finite()
903 || rx < 0.0
904 || ry < 0.0
905 || !start.is_finite()
906 || !end.is_finite()
907 || !rotation.is_finite()
908 {
909 return;
910 }
911 let sweep = normalize_sweep(start, end, anticlockwise);
912 let steps = arc_steps(sweep);
913 let (rs, rc) = (libm::sinf(rotation), libm::cosf(rotation));
914 for i in 0..=steps {
915 let t = i as f32 / steps as f32;
916 let ang = start + sweep * t;
917 let ux = rx * libm::cosf(ang);
919 let uy = ry * libm::sinf(ang);
920 let px = cx + ux * rc - uy * rs;
922 let py = cy + ux * rs + uy * rc;
923 if i == 0 && self.current.is_none() {
924 self.move_to(m, px, py);
925 } else {
926 self.line_to(m, px, py);
927 }
928 }
929 }
930
931 #[allow(clippy::too_many_arguments)]
937 pub fn round_rect(
938 &mut self,
939 m: &Matrix,
940 x: f32,
941 y: f32,
942 w: f32,
943 h: f32,
944 radii: [f32; 4],
945 ) {
946 if !w.is_finite() || !h.is_finite() || w == 0.0 || h == 0.0 {
947 return;
948 }
949 let (x, w) = if w < 0.0 { (x + w, -w) } else { (x, w) };
951 let (y, h) = if h < 0.0 { (y + h, -h) } else { (y, h) };
952
953 let mut r = [0.0f32; 4];
954 for (i, v) in radii.iter().enumerate() {
955 r[i] = if v.is_finite() && *v > 0.0 { *v } else { 0.0 };
956 }
957 let mut scale = 1.0f32;
959 let pairs = [
960 (r[0] + r[1], w), (r[2] + r[3], w), (r[1] + r[2], h), (r[3] + r[0], h), ];
965 for (sum, len) in pairs {
966 if sum > 0.0 && sum > len {
967 scale = scale.min(len / sum);
968 }
969 }
970 for v in r.iter_mut() {
971 *v *= scale;
972 }
973
974 let half_pi = core::f32::consts::PI / 2.0;
975 let pi = core::f32::consts::PI;
976 self.flush();
977 self.move_to(m, x + r[0], y);
979 self.line_to(m, x + w - r[1], y);
980 if r[1] > 0.0 {
981 self.arc(m, x + w - r[1], y + r[1], r[1], -half_pi, 0.0, false);
982 }
983 self.line_to(m, x + w, y + h - r[2]);
984 if r[2] > 0.0 {
985 self.arc(m, x + w - r[2], y + h - r[2], r[2], 0.0, half_pi, false);
986 }
987 self.line_to(m, x + r[3], y + h);
988 if r[3] > 0.0 {
989 self.arc(m, x + r[3], y + h - r[3], r[3], half_pi, pi, false);
990 }
991 self.line_to(m, x, y + r[0]);
992 if r[0] > 0.0 {
993 self.arc(m, x + r[0], y + r[0], r[0], pi, pi + half_pi, false);
994 }
995 self.close_path();
996 }
997}
998
999impl Surface {
1000 pub fn stroke_path_width(&mut self, subpaths: &[SubPath], argb: u32, width: f32) {
1015 self.stroke_path_styled(
1016 subpaths,
1017 argb,
1018 width,
1019 LineCap::Butt,
1020 LineJoin::Round,
1021 DEFAULT_MITER_LIMIT,
1022 );
1023 }
1024
1025 fn fill_disc(&mut self, cx: f32, cy: f32, r: f32, argb: u32) {
1027 if !cx.is_finite() || !cy.is_finite() || !r.is_finite() || r <= 0.0 {
1028 return;
1029 }
1030 let y0 = (libm::floorf(cy - r) as i32).max(0);
1031 let y1 = (libm::ceilf(cy + r) as i32).min(self.height as i32);
1032 let r2 = r * r;
1033 for py in y0..y1 {
1034 let dy = py as f32 + 0.5 - cy;
1035 let d = r2 - dy * dy;
1036 if d < 0.0 {
1037 continue;
1038 }
1039 let dx = libm::sqrtf(d);
1040 let x0 = (libm::roundf(cx - dx) as i32).max(0);
1041 let x1 = (libm::roundf(cx + dx) as i32).min(self.width as i32);
1042 for px in x0..x1 {
1043 self.blend_pixel(px, py, argb);
1044 }
1045 }
1046 }
1047}
1048
1049impl Canvas2dContext {
1050 pub fn stroke_with_width(&mut self) {
1052 self.sync_clip();
1053 let color = self.apply_alpha(self.state.stroke);
1054 let w = self.state.line_width;
1055 let (cap, join, limit) = (
1056 self.state.line_cap,
1057 self.state.line_join,
1058 self.state.miter_limit,
1059 );
1060 let sub = self.path.finish();
1061 let sub = self.apply_dash(&sub);
1062 self.surface
1063 .stroke_path_styled(&sub, color, w, cap, join, limit);
1064 }
1065}
1066
1067#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1069pub enum LineCap {
1070 Butt,
1072 Round,
1074 Square,
1076}
1077
1078#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1080pub enum LineJoin {
1081 Miter,
1083 Round,
1085 Bevel,
1087}
1088
1089pub const DEFAULT_MITER_LIMIT: f32 = 10.0;
1091
1092pub fn parse_line_cap(s: &str) -> Option<LineCap> {
1093 match s.trim() {
1094 "butt" => Some(LineCap::Butt),
1095 "round" => Some(LineCap::Round),
1096 "square" => Some(LineCap::Square),
1097 _ => None,
1098 }
1099}
1100
1101pub fn parse_line_join(s: &str) -> Option<LineJoin> {
1102 match s.trim() {
1103 "miter" => Some(LineJoin::Miter),
1104 "round" => Some(LineJoin::Round),
1105 "bevel" => Some(LineJoin::Bevel),
1106 _ => None,
1107 }
1108}
1109
1110pub fn line_intersection(
1115 p1: (f32, f32),
1116 d1: (f32, f32),
1117 p2: (f32, f32),
1118 d2: (f32, f32),
1119) -> Option<(f32, f32)> {
1120 let den = d1.0 * d2.1 - d1.1 * d2.0;
1121 if libm::fabsf(den) < 1e-6 {
1123 return None;
1124 }
1125 let t = ((p2.0 - p1.0) * d2.1 - (p2.1 - p1.1) * d2.0) / den;
1126 let x = p1.0 + d1.0 * t;
1127 let y = p1.1 + d1.1 * t;
1128 if !x.is_finite() || !y.is_finite() {
1129 return None;
1130 }
1131 Some((x, y))
1132}
1133
1134impl Surface {
1135 pub fn stroke_path_styled(
1139 &mut self,
1140 subpaths: &[SubPath],
1141 argb: u32,
1142 width: f32,
1143 cap: LineCap,
1144 join: LineJoin,
1145 miter_limit: f32,
1146 ) {
1147 mark_canvas_dirty();
1148 if !width.is_finite() || width <= 1.0 {
1150 self.stroke_path(subpaths, argb);
1151 return;
1152 }
1153 let half = width / 2.0;
1154 let limit = if miter_limit.is_finite() && miter_limit > 0.0 {
1155 miter_limit
1156 } else {
1157 DEFAULT_MITER_LIMIT
1158 };
1159 for sp in subpaths {
1160 let n = sp.points.len();
1161 if n < 2 {
1162 continue;
1163 }
1164 let seg_end = if sp.closed { n } else { n - 1 };
1165
1166 for i in 0..seg_end {
1168 let a = sp.points[i];
1169 let b = sp.points[(i + 1) % n];
1170 let Some((dx, dy, len)) = unit_delta(a, b) else {
1171 continue;
1172 };
1173 let (mut ax, mut ay) = a;
1175 let (mut bx, mut by) = b;
1176 if cap == LineCap::Square && !sp.closed {
1177 if i == 0 {
1178 ax -= dx * half;
1179 ay -= dy * half;
1180 }
1181 if i == seg_end - 1 {
1182 bx += dx * half;
1183 by += dy * half;
1184 }
1185 }
1186 let _ = len;
1187 let (nx, ny) = (-dy * half, dx * half);
1188 self.fill_quad(
1189 (ax + nx, ay + ny),
1190 (bx + nx, by + ny),
1191 (bx - nx, by - ny),
1192 (ax - nx, ay - ny),
1193 argb,
1194 );
1195 }
1196
1197 let joint_from = if sp.closed { 0 } else { 1 };
1199 let joint_to = if sp.closed { n } else { n - 1 };
1200 for j in joint_from..joint_to {
1201 let prev = sp.points[(j + n - 1) % n];
1202 let cur = sp.points[j];
1203 let next = sp.points[(j + 1) % n];
1204 self.fill_joint(prev, cur, next, half, argb, join, limit);
1205 }
1206
1207 if cap == LineCap::Round && !sp.closed {
1209 let first = sp.points[0];
1210 let last = sp.points[n - 1];
1211 self.fill_disc(first.0, first.1, half, argb);
1212 self.fill_disc(last.0, last.1, half, argb);
1213 }
1214 }
1215 }
1216
1217 #[allow(clippy::too_many_arguments)]
1219 fn fill_joint(
1220 &mut self,
1221 prev: (f32, f32),
1222 cur: (f32, f32),
1223 next: (f32, f32),
1224 half: f32,
1225 argb: u32,
1226 join: LineJoin,
1227 limit: f32,
1228 ) {
1229 if join == LineJoin::Round {
1230 self.fill_disc(cur.0, cur.1, half, argb);
1231 return;
1232 }
1233 let (Some((d1x, d1y, _)), Some((d2x, d2y, _))) =
1234 (unit_delta(prev, cur), unit_delta(cur, next))
1235 else {
1236 return;
1237 };
1238 let cross = d1x * d2y - d1y * d2x;
1240 if libm::fabsf(cross) < 1e-6 {
1241 return;
1243 }
1244 let s = if cross > 0.0 { -1.0f32 } else { 1.0f32 };
1246 let p1 = (cur.0 + s * -d1y * half, cur.1 + s * d1x * half);
1247 let p2 = (cur.0 + s * -d2y * half, cur.1 + s * d2x * half);
1248
1249 if join == LineJoin::Miter {
1250 if let Some(mp) = line_intersection(p1, (d1x, d1y), p2, (d2x, d2y)) {
1251 let (mx, my) = (mp.0 - cur.0, mp.1 - cur.1);
1252 let miter_len = libm::sqrtf(mx * mx + my * my);
1253 if miter_len / half <= limit {
1256 self.fill_quad(cur, p1, mp, p2, argb);
1257 return;
1258 }
1259 }
1260 }
1261 self.fill_tri(cur, p1, p2, argb);
1263 }
1264
1265 fn fill_quad(
1266 &mut self,
1267 a: (f32, f32),
1268 b: (f32, f32),
1269 c: (f32, f32),
1270 d: (f32, f32),
1271 argb: u32,
1272 ) {
1273 for p in [a, b, c, d] {
1274 if !p.0.is_finite() || !p.1.is_finite() {
1275 return;
1276 }
1277 }
1278 let poly = alloc::vec![SubPath {
1279 points: alloc::vec![a, b, c, d],
1280 closed: true,
1281 }];
1282 self.fill_path(&poly, FillRule::NonZero, argb);
1283 }
1284
1285 fn fill_tri(&mut self, a: (f32, f32), b: (f32, f32), c: (f32, f32), argb: u32) {
1286 for p in [a, b, c] {
1287 if !p.0.is_finite() || !p.1.is_finite() {
1288 return;
1289 }
1290 }
1291 let poly = alloc::vec![SubPath {
1292 points: alloc::vec![a, b, c],
1293 closed: true,
1294 }];
1295 self.fill_path(&poly, FillRule::NonZero, argb);
1296 }
1297}
1298
1299pub fn unit_delta(a: (f32, f32), b: (f32, f32)) -> Option<(f32, f32, f32)> {
1301 let (dx, dy) = (b.0 - a.0, b.1 - a.1);
1302 if !dx.is_finite() || !dy.is_finite() {
1303 return None;
1304 }
1305 let len = libm::sqrtf(dx * dx + dy * dy);
1306 if len < 1e-6 {
1307 return None;
1308 }
1309 Some((dx / len, dy / len, len))
1310}
1311
1312static CANVAS_DIRTY: core::sync::atomic::AtomicBool =
1322 core::sync::atomic::AtomicBool::new(false);
1323
1324pub fn mark_canvas_dirty() {
1326 CANVAS_DIRTY.store(true, core::sync::atomic::Ordering::Relaxed);
1327}
1328
1329pub fn canvas_dirty() -> bool {
1331 CANVAS_DIRTY.load(core::sync::atomic::Ordering::Relaxed)
1332}
1333
1334pub fn take_canvas_dirty() -> bool {
1336 CANVAS_DIRTY.swap(false, core::sync::atomic::Ordering::Relaxed)
1337}
1338
1339pub fn normalize_dash(pattern: &[f32]) -> Option<Vec<f32>> {
1345 if pattern.is_empty() {
1346 return None;
1347 }
1348 for v in pattern {
1349 if !v.is_finite() || *v < 0.0 {
1350 return None;
1351 }
1352 }
1353 if pattern.iter().all(|v| *v == 0.0) {
1354 return None;
1355 }
1356 let mut out: Vec<f32> = pattern.to_vec();
1357 if out.len() % 2 == 1 {
1359 let dup = out.clone();
1360 out.extend(dup);
1361 }
1362 Some(out)
1363}
1364
1365pub fn dash_subpaths(subpaths: &[SubPath], pattern: &[f32], offset: f32) -> Vec<SubPath> {
1373 let Some(pat) = normalize_dash(pattern) else {
1374 return subpaths.to_vec();
1375 };
1376 let total: f32 = pat.iter().sum();
1377 if total <= 0.0 {
1378 return subpaths.to_vec();
1379 }
1380 let mut out: Vec<SubPath> = Vec::new();
1381 for sp in subpaths {
1382 let n = sp.points.len();
1383 if n < 2 {
1384 continue;
1385 }
1386 let mut phase = if offset.is_finite() {
1388 let m = libm::fmodf(offset, total);
1389 if m < 0.0 {
1390 m + total
1391 } else {
1392 m
1393 }
1394 } else {
1395 0.0
1396 };
1397 let mut idx = 0usize;
1399 while phase >= pat[idx] {
1400 phase -= pat[idx];
1401 idx = (idx + 1) % pat.len();
1402 }
1403 let mut drawing = idx % 2 == 0;
1404 let mut cur: Vec<(f32, f32)> = Vec::new();
1405 if drawing {
1406 cur.push(sp.points[0]);
1407 }
1408
1409 let seg_end = if sp.closed { n } else { n - 1 };
1410 for i in 0..seg_end {
1411 let a = sp.points[i];
1412 let b = sp.points[(i + 1) % n];
1413 let Some((dx, dy, len)) = unit_delta(a, b) else {
1414 continue;
1415 };
1416 let mut travelled = 0.0f32;
1417 while travelled < len {
1418 let remain = pat[idx] - phase;
1420 let step = remain.min(len - travelled);
1421 travelled += step;
1422 phase += step;
1423 let p = (a.0 + dx * travelled, a.1 + dy * travelled);
1424 if phase >= pat[idx] - 1e-6 {
1425 if drawing {
1427 cur.push(p);
1428 if cur.len() >= 2 {
1429 out.push(SubPath {
1430 points: core::mem::take(&mut cur),
1431 closed: false,
1432 });
1433 } else {
1434 cur.clear();
1435 }
1436 } else {
1437 cur.clear();
1438 cur.push(p);
1439 }
1440 phase = 0.0;
1441 idx = (idx + 1) % pat.len();
1442 drawing = !drawing;
1443 } else if drawing {
1444 cur.push(p);
1446 }
1447 }
1448 }
1449 if drawing && cur.len() >= 2 {
1450 out.push(SubPath {
1451 points: cur,
1452 closed: false,
1453 });
1454 }
1455 }
1456 out
1457}
1458
1459impl Canvas2dContext {
1460 pub fn apply_dash(&self, subpaths: &[SubPath]) -> Vec<SubPath> {
1462 if self.state.line_dash.is_empty() {
1463 return subpaths.to_vec();
1464 }
1465 dash_subpaths(subpaths, &self.state.line_dash, self.state.line_dash_offset)
1466 }
1467}
1468
1469#[derive(Debug, Clone)]
1476pub struct ClipMask {
1477 pub width: u32,
1478 pub height: u32,
1479 pub bits: Vec<bool>,
1481}
1482
1483impl ClipMask {
1484 pub fn from_path(width: u32, height: u32, subpaths: &[SubPath], rule: FillRule) -> ClipMask {
1488 let mut bits = alloc::vec![false; (width as usize) * (height as usize)];
1489 for y in 0..height {
1490 let spans = scanline_spans(subpaths, y as f32 + 0.5, rule);
1492 for (x0, x1) in spans {
1493 let a = (libm::ceilf(x0 - 0.5) as i32).max(0);
1494 let b = (libm::ceilf(x1 - 0.5) as i32).min(width as i32);
1495 for x in a..b {
1496 bits[y as usize * width as usize + x as usize] = true;
1497 }
1498 }
1499 }
1500 ClipMask {
1501 width,
1502 height,
1503 bits,
1504 }
1505 }
1506
1507 pub fn intersect(&self, other: &ClipMask) -> ClipMask {
1510 if self.width != other.width || self.height != other.height {
1511 return self.clone();
1513 }
1514 let bits = self
1515 .bits
1516 .iter()
1517 .zip(other.bits.iter())
1518 .map(|(a, b)| *a && *b)
1519 .collect();
1520 ClipMask {
1521 width: self.width,
1522 height: self.height,
1523 bits,
1524 }
1525 }
1526
1527 pub fn allows(&self, x: i32, y: i32) -> bool {
1529 if x < 0 || y < 0 || x as u32 >= self.width || y as u32 >= self.height {
1530 return false;
1531 }
1532 self.bits[y as usize * self.width as usize + x as usize]
1533 }
1534}
1535
1536impl Canvas2dContext {
1537 pub fn clip(&mut self, rule: FillRule) {
1542 let sub = self.path.finish();
1543 if sub.is_empty() {
1544 return;
1545 }
1546 let mask = ClipMask::from_path(self.surface.width, self.surface.height, &sub, rule);
1547 let merged = match &self.state.clip {
1548 Some(prev) => prev.intersect(&mask),
1549 None => mask,
1550 };
1551 self.state.clip = Some(alloc::sync::Arc::new(merged));
1552 }
1553
1554 pub fn sync_clip(&mut self) {
1556 self.surface.clip = self.state.clip.clone();
1557 }
1558}
1559
1560pub fn get_image_data(s: &Surface, x: i32, y: i32, w: i32, h: i32) -> Vec<u8> {
1567 if w <= 0 || h <= 0 {
1568 return Vec::new();
1569 }
1570 let mut out = alloc::vec![0u8; (w as usize) * (h as usize) * 4];
1571 for row in 0..h {
1572 for col in 0..w {
1573 let (sx, sy) = (x + col, y + row);
1574 if sx < 0 || sy < 0 || sx as u32 >= s.width || sy as u32 >= s.height {
1575 continue;
1576 }
1577 let argb = s.pixels[sy as usize * s.width as usize + sx as usize];
1578 let o = ((row as usize) * (w as usize) + col as usize) * 4;
1579 out[o] = ((argb >> 16) & 0xFF) as u8;
1580 out[o + 1] = ((argb >> 8) & 0xFF) as u8;
1581 out[o + 2] = (argb & 0xFF) as u8;
1582 out[o + 3] = ((argb >> 24) & 0xFF) as u8;
1583 }
1584 }
1585 out
1586}
1587
1588pub fn put_image_data(s: &mut Surface, data: &[u8], w: i32, h: i32, x: i32, y: i32) {
1593 if w <= 0 || h <= 0 {
1594 return;
1595 }
1596 mark_canvas_dirty();
1598 for row in 0..h {
1599 for col in 0..w {
1600 let o = ((row as usize) * (w as usize) + col as usize) * 4;
1601 if o + 3 >= data.len() {
1602 return;
1603 }
1604 let (dx, dy) = (x + col, y + row);
1605 if dx < 0 || dy < 0 || dx as u32 >= s.width || dy as u32 >= s.height {
1606 continue;
1607 }
1608 let argb = ((data[o + 3] as u32) << 24)
1609 | ((data[o] as u32) << 16)
1610 | ((data[o + 1] as u32) << 8)
1611 | (data[o + 2] as u32);
1612 s.pixels[dy as usize * s.width as usize + dx as usize] = argb;
1613 }
1614 }
1615}
1616
1617pub fn resize_context(id: u32, width: u32, height: u32) -> bool {
1625 let Some(fresh) = Surface::new(width, height) else {
1626 return false;
1627 };
1628 mark_canvas_dirty();
1629 with_context(id, |c| {
1630 c.surface = fresh;
1631 c.state = DrawState::default();
1632 c.path = PathBuilder::new();
1633 c.stack.clear();
1634 })
1635 .is_some()
1636}
1637
1638pub struct ImageSource<'a> {
1642 pub width: u32,
1643 pub height: u32,
1644 pub rgba: &'a [u8],
1645}
1646
1647impl ImageSource<'_> {
1648 fn sample(&self, x: i32, y: i32) -> u32 {
1650 if x < 0 || y < 0 || x as u32 >= self.width || y as u32 >= self.height {
1651 return 0;
1652 }
1653 let o = (y as usize * self.width as usize + x as usize) * 4;
1654 if o + 3 >= self.rgba.len() {
1655 return 0;
1656 }
1657 ((self.rgba[o + 3] as u32) << 24)
1658 | ((self.rgba[o] as u32) << 16)
1659 | ((self.rgba[o + 1] as u32) << 8)
1660 | (self.rgba[o + 2] as u32)
1661 }
1662}
1663
1664#[derive(Debug, Clone, Copy)]
1666pub struct BlitSpec {
1667 pub sx: f32,
1669 pub sy: f32,
1670 pub sw: f32,
1671 pub sh: f32,
1672 pub dx: f32,
1674 pub dy: f32,
1675 pub dw: f32,
1676 pub dh: f32,
1677}
1678
1679impl Surface {
1680 pub fn draw_image(&mut self, src: &ImageSource, spec: &BlitSpec, m: &Matrix, alpha: f32) {
1690 if spec.dw == 0.0 || spec.dh == 0.0 || spec.sw == 0.0 || spec.sh == 0.0 {
1691 return;
1692 }
1693 for v in [
1694 spec.sx, spec.sy, spec.sw, spec.sh, spec.dx, spec.dy, spec.dw, spec.dh,
1695 ] {
1696 if !v.is_finite() {
1697 return;
1698 }
1699 }
1700 let Some(inv) = m.invert() else {
1701 return;
1702 };
1703 mark_canvas_dirty();
1704
1705 let corners = [
1707 m.apply(spec.dx, spec.dy),
1708 m.apply(spec.dx + spec.dw, spec.dy),
1709 m.apply(spec.dx + spec.dw, spec.dy + spec.dh),
1710 m.apply(spec.dx, spec.dy + spec.dh),
1711 ];
1712 let mut x0 = corners[0].0;
1713 let mut x1 = corners[0].0;
1714 let mut y0 = corners[0].1;
1715 let mut y1 = corners[0].1;
1716 for (cx, cy) in corners.iter().skip(1) {
1717 x0 = x0.min(*cx);
1718 x1 = x1.max(*cx);
1719 y0 = y0.min(*cy);
1720 y1 = y1.max(*cy);
1721 }
1722 let px0 = (libm::floorf(x0) as i32).max(0);
1723 let px1 = (libm::ceilf(x1) as i32).min(self.width as i32);
1724 let py0 = (libm::floorf(y0) as i32).max(0);
1725 let py1 = (libm::ceilf(y1) as i32).min(self.height as i32);
1726
1727 let a = alpha.clamp(0.0, 1.0);
1728 for py in py0..py1 {
1729 for px in px0..px1 {
1730 let (ux, uy) = inv.apply(px as f32 + 0.5, py as f32 + 0.5);
1732 let tx = (ux - spec.dx) / spec.dw;
1734 let ty = (uy - spec.dy) / spec.dh;
1735 if !(0.0..1.0).contains(&tx) || !(0.0..1.0).contains(&ty) {
1736 continue;
1737 }
1738 let sxf = spec.sx + tx * spec.sw;
1740 let syf = spec.sy + ty * spec.sh;
1741 let argb = src.sample(libm::floorf(sxf) as i32, libm::floorf(syf) as i32);
1742 if argb >> 24 == 0 {
1743 continue;
1744 }
1745 let argb = if a >= 1.0 {
1746 argb
1747 } else {
1748 let na = (((argb >> 24) & 0xFF) as f32 * a) as u32;
1749 (na << 24) | (argb & 0x00FF_FFFF)
1750 };
1751 self.blend_pixel(px, py, argb);
1752 }
1753 }
1754 }
1755}
1756
1757#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1761pub enum TextAlign {
1762 Start,
1763 End,
1764 Left,
1765 Right,
1766 Center,
1767}
1768
1769#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1771pub enum TextBaseline {
1772 Top,
1773 Hanging,
1774 Middle,
1775 Alphabetic,
1776 Ideographic,
1777 Bottom,
1778}
1779
1780pub fn parse_text_align(s: &str) -> Option<TextAlign> {
1781 match s.trim() {
1782 "start" => Some(TextAlign::Start),
1783 "end" => Some(TextAlign::End),
1784 "left" => Some(TextAlign::Left),
1785 "right" => Some(TextAlign::Right),
1786 "center" => Some(TextAlign::Center),
1787 _ => None,
1788 }
1789}
1790
1791pub fn parse_text_baseline(s: &str) -> Option<TextBaseline> {
1792 match s.trim() {
1793 "top" => Some(TextBaseline::Top),
1794 "hanging" => Some(TextBaseline::Hanging),
1795 "middle" => Some(TextBaseline::Middle),
1796 "alphabetic" => Some(TextBaseline::Alphabetic),
1797 "ideographic" => Some(TextBaseline::Ideographic),
1798 "bottom" => Some(TextBaseline::Bottom),
1799 _ => None,
1800 }
1801}
1802
1803pub fn align_offset(align: TextAlign, total_advance: f32) -> f32 {
1808 match align {
1809 TextAlign::Start | TextAlign::Left => 0.0,
1810 TextAlign::End | TextAlign::Right => -total_advance,
1811 TextAlign::Center => -total_advance / 2.0,
1812 }
1813}
1814
1815pub fn baseline_offset(baseline: TextBaseline, size_px: f32) -> f32 {
1822 match baseline {
1823 TextBaseline::Alphabetic => 0.0,
1824 TextBaseline::Top => size_px * 0.85,
1825 TextBaseline::Hanging => size_px * 0.8,
1826 TextBaseline::Middle => size_px * 0.35,
1827 TextBaseline::Ideographic | TextBaseline::Bottom => -size_px * 0.15,
1828 }
1829}
1830
1831pub fn parse_font_size(font: &str) -> Option<f32> {
1837 for tok in font.split(|c: char| c.is_whitespace() || c == '/') {
1838 let t = tok.trim();
1839 let Some(num) = t.strip_suffix("px") else {
1840 continue;
1841 };
1842 if let Ok(v) = num.parse::<f32>() {
1843 if v.is_finite() && v > 0.0 {
1844 return Some(v);
1845 }
1846 }
1847 }
1848 None
1849}
1850
1851pub struct GlyphBitmap<'a> {
1856 pub width: u32,
1857 pub height: u32,
1858 pub x_offset: i32,
1860 pub y_offset: i32,
1861 pub advance: f32,
1863 pub alpha: &'a [u8],
1865}
1866
1867impl Surface {
1868 pub fn draw_glyph(&mut self, g: &GlyphBitmap, pen_x: f32, pen_y: f32, argb: u32, m: &Matrix) {
1874 if g.width == 0 || g.height == 0 {
1875 return;
1876 }
1877 let n = (g.width as usize) * (g.height as usize);
1878 if g.alpha.len() < n {
1879 return;
1880 }
1881 let (r, gg, b) = ((argb >> 16) & 0xFF, (argb >> 8) & 0xFF, argb & 0xFF);
1882 let base_a = (argb >> 24) & 0xFF;
1883 let mut rgba = alloc::vec![0u8; n * 4];
1884 for i in 0..n {
1885 let a = (g.alpha[i] as u32 * base_a / 255) as u8;
1886 rgba[i * 4] = r as u8;
1887 rgba[i * 4 + 1] = gg as u8;
1888 rgba[i * 4 + 2] = b as u8;
1889 rgba[i * 4 + 3] = a;
1890 }
1891 let src = ImageSource {
1892 width: g.width,
1893 height: g.height,
1894 rgba: &rgba,
1895 };
1896 let spec = BlitSpec {
1897 sx: 0.0,
1898 sy: 0.0,
1899 sw: g.width as f32,
1900 sh: g.height as f32,
1901 dx: pen_x + g.x_offset as f32,
1902 dy: pen_y + g.y_offset as f32,
1903 dw: g.width as f32,
1904 dh: g.height as f32,
1905 };
1906 self.draw_image(&src, &spec, m, 1.0);
1907 }
1908}