1#![allow(dead_code)]
13
14extern crate alloc;
15use alloc::collections::BTreeMap;
16use alloc::vec::Vec;
17use spin::Mutex;
18
19#[derive(Copy, Clone)]
24struct Point {
25 x: f32,
26 y: f32,
27}
28
29#[derive(Clone)]
30struct Segment {
31 p1: Point,
32 p2: Point,
33}
34
35struct PathParser<'a> {
41 data: &'a [u8],
42 pos: usize,
43}
44
45impl<'a> PathParser<'a> {
46 fn new(data: &'a [u8]) -> Self {
47 Self { data, pos: 0 }
48 }
49
50 fn skip_whitespace_and_comma(&mut self) {
51 while self.pos < self.data.len() {
52 let c = self.data[self.pos];
53 if c == b' ' || c == b'\t' || c == b'\n' || c == b'\r' || c == b',' {
54 self.pos += 1;
55 } else {
56 break;
57 }
58 }
59 }
60
61 fn peek(&self) -> Option<u8> {
62 if self.pos < self.data.len() {
63 Some(self.data[self.pos])
64 } else {
65 None
66 }
67 }
68
69 fn next_char(&mut self) -> Option<u8> {
70 if self.pos < self.data.len() {
71 let c = self.data[self.pos];
72 self.pos += 1;
73 Some(c)
74 } else {
75 None
76 }
77 }
78
79 fn next_f32(&mut self) -> Option<f32> {
81 self.skip_whitespace_and_comma();
82 if self.pos >= self.data.len() {
83 return None;
84 }
85
86 let start = self.pos;
87 if self.pos < self.data.len()
89 && (self.data[self.pos] == b'-' || self.data[self.pos] == b'+')
90 {
91 self.pos += 1;
92 }
93 while self.pos < self.data.len() && self.data[self.pos].is_ascii_digit() {
95 self.pos += 1;
96 }
97 if self.pos < self.data.len() && self.data[self.pos] == b'.' {
99 self.pos += 1;
100 while self.pos < self.data.len() && self.data[self.pos].is_ascii_digit() {
101 self.pos += 1;
102 }
103 }
104 if self.pos < self.data.len()
106 && (self.data[self.pos] == b'e' || self.data[self.pos] == b'E')
107 {
108 self.pos += 1;
109 if self.pos < self.data.len()
110 && (self.data[self.pos] == b'-' || self.data[self.pos] == b'+')
111 {
112 self.pos += 1;
113 }
114 while self.pos < self.data.len() && self.data[self.pos].is_ascii_digit() {
115 self.pos += 1;
116 }
117 }
118
119 if self.pos == start {
120 return None;
121 }
122
123 let s = core::str::from_utf8(&self.data[start..self.pos]).ok()?;
125 parse_f32(s)
126 }
127
128 fn next_cmd(&mut self) -> Option<u8> {
130 self.skip_whitespace_and_comma();
131 match self.peek() {
132 Some(c) if c.is_ascii_alphabetic() => {
133 self.pos += 1;
134 Some(c)
135 }
136 _ => None,
137 }
138 }
139}
140
141fn parse_f32(s: &str) -> Option<f32> {
143 let bytes = s.as_bytes();
144 let mut i = 0;
145 let neg = if i < bytes.len() && bytes[i] == b'-' {
146 i += 1;
147 true
148 } else if i < bytes.len() && bytes[i] == b'+' {
149 i += 1;
150 false
151 } else {
152 false
153 };
154
155 let mut int_part: f32 = 0.0;
156 while i < bytes.len() && bytes[i].is_ascii_digit() {
157 int_part = int_part * 10.0 + (bytes[i] - b'0') as f32;
158 i += 1;
159 }
160
161 let mut frac_part: f32 = 0.0;
162 let mut frac_div: f32 = 1.0;
163 if i < bytes.len() && bytes[i] == b'.' {
164 i += 1;
165 while i < bytes.len() && bytes[i].is_ascii_digit() {
166 frac_part = frac_part * 10.0 + (bytes[i] - b'0') as f32;
167 frac_div *= 10.0;
168 i += 1;
169 }
170 }
171
172 let mut exp: i32 = 0;
174 let mut exp_neg = false;
175 if i < bytes.len() && (bytes[i] == b'e' || bytes[i] == b'E') {
176 i += 1;
177 if i < bytes.len() && bytes[i] == b'-' {
178 exp_neg = true;
179 i += 1;
180 } else if i < bytes.len() && bytes[i] == b'+' {
181 i += 1;
182 }
183 while i < bytes.len() && bytes[i].is_ascii_digit() {
184 exp = exp * 10 + (bytes[i] - b'0') as i32;
185 i += 1;
186 }
187 }
188
189 let mut val = int_part + frac_part / frac_div;
190 if exp != 0 {
191 let mut e: f32 = 1.0;
192 for _ in 0..exp {
193 e *= 10.0;
194 }
195 if exp_neg {
196 val /= e;
197 } else {
198 val *= e;
199 }
200 }
201 if neg {
202 val = -val;
203 }
204
205 if i == 0 {
206 None
207 } else {
208 Some(val)
209 }
210}
211
212fn parse_path_d(d: &str) -> Vec<Segment> {
214 let mut segments: Vec<Segment> = Vec::new();
215 let bytes = d.as_bytes();
216 let mut parser = PathParser::new(bytes);
217
218 let mut cur = Point { x: 0.0, y: 0.0 };
219 let mut start = Point { x: 0.0, y: 0.0 };
220 let mut last_cmd: u8 = 0;
221
222 loop {
223 let cmd = if let Some(c) = parser.next_cmd() {
225 last_cmd = c;
226 c
227 } else {
228 if parser.peek().is_none() {
230 break;
231 }
232 match last_cmd {
234 b'M' => b'L',
235 b'm' => b'l',
236 c => c,
237 }
238 };
239
240 match cmd {
241 b'M' => {
242 if let (Some(x), Some(y)) = (parser.next_f32(), parser.next_f32()) {
243 cur = Point { x, y };
244 start = cur;
245 }
246 }
247 b'm' => {
248 if let (Some(dx), Some(dy)) = (parser.next_f32(), parser.next_f32()) {
249 cur = Point {
250 x: cur.x + dx,
251 y: cur.y + dy,
252 };
253 start = cur;
254 }
255 }
256 b'L' => {
257 if let (Some(x), Some(y)) = (parser.next_f32(), parser.next_f32()) {
258 let to = Point { x, y };
259 segments.push(Segment { p1: cur, p2: to });
260 cur = to;
261 } else {
262 break;
263 }
264 }
265 b'l' => {
266 if let (Some(dx), Some(dy)) = (parser.next_f32(), parser.next_f32()) {
267 let to = Point {
268 x: cur.x + dx,
269 y: cur.y + dy,
270 };
271 segments.push(Segment { p1: cur, p2: to });
272 cur = to;
273 } else {
274 break;
275 }
276 }
277 b'H' => {
278 if let Some(x) = parser.next_f32() {
279 let to = Point { x, y: cur.y };
280 segments.push(Segment { p1: cur, p2: to });
281 cur = to;
282 } else {
283 break;
284 }
285 }
286 b'h' => {
287 if let Some(dx) = parser.next_f32() {
288 let to = Point {
289 x: cur.x + dx,
290 y: cur.y,
291 };
292 segments.push(Segment { p1: cur, p2: to });
293 cur = to;
294 } else {
295 break;
296 }
297 }
298 b'V' => {
299 if let Some(y) = parser.next_f32() {
300 let to = Point { x: cur.x, y };
301 segments.push(Segment { p1: cur, p2: to });
302 cur = to;
303 } else {
304 break;
305 }
306 }
307 b'v' => {
308 if let Some(dy) = parser.next_f32() {
309 let to = Point {
310 x: cur.x,
311 y: cur.y + dy,
312 };
313 segments.push(Segment { p1: cur, p2: to });
314 cur = to;
315 } else {
316 break;
317 }
318 }
319 b'C' => {
320 loop {
322 let x1 = parser.next_f32();
323 let y1 = parser.next_f32();
324 let x2 = parser.next_f32();
325 let y2 = parser.next_f32();
326 let x = parser.next_f32();
327 let y = parser.next_f32();
328 if let (Some(x1), Some(y1), Some(x2), Some(y2), Some(x), Some(y)) =
329 (x1, y1, x2, y2, x, y)
330 {
331 let p1 = Point { x: x1, y: y1 };
332 let p2 = Point { x: x2, y: y2 };
333 let to = Point { x, y };
334 flatten_cubic(cur, p1, p2, to, &mut segments);
335 cur = to;
336 } else {
337 break;
338 }
339 parser.skip_whitespace_and_comma();
341 match parser.peek() {
342 Some(c) if !c.is_ascii_alphabetic() => continue,
343 _ => break,
344 }
345 }
346 }
347 b'c' => loop {
348 let x1 = parser.next_f32();
349 let y1 = parser.next_f32();
350 let x2 = parser.next_f32();
351 let y2 = parser.next_f32();
352 let x = parser.next_f32();
353 let y = parser.next_f32();
354 if let (Some(x1), Some(y1), Some(x2), Some(y2), Some(x), Some(y)) =
355 (x1, y1, x2, y2, x, y)
356 {
357 let p1 = Point {
358 x: cur.x + x1,
359 y: cur.y + y1,
360 };
361 let p2 = Point {
362 x: cur.x + x2,
363 y: cur.y + y2,
364 };
365 let to = Point {
366 x: cur.x + x,
367 y: cur.y + y,
368 };
369 flatten_cubic(cur, p1, p2, to, &mut segments);
370 cur = to;
371 } else {
372 break;
373 }
374 parser.skip_whitespace_and_comma();
375 match parser.peek() {
376 Some(c) if !c.is_ascii_alphabetic() => continue,
377 _ => break,
378 }
379 },
380 b'Z' | b'z' => {
381 if cur.x != start.x || cur.y != start.y {
382 segments.push(Segment { p1: cur, p2: start });
383 }
384 cur = start;
385 }
386 _ => {
387 parser.next_char();
389 }
390 }
391 }
392
393 segments
394}
395
396fn flatten_cubic(p0: Point, p1: Point, p2: Point, p3: Point, out: &mut Vec<Segment>) {
398 const STEPS: u32 = 8;
399 let mut prev = p0;
400 for i in 1..=STEPS {
401 let t = i as f32 / STEPS as f32;
402 let mt = 1.0 - t;
403 let x = mt * mt * mt * p0.x
404 + 3.0 * mt * mt * t * p1.x
405 + 3.0 * mt * t * t * p2.x
406 + t * t * t * p3.x;
407 let y = mt * mt * mt * p0.y
408 + 3.0 * mt * mt * t * p1.y
409 + 3.0 * mt * t * t * p2.y
410 + t * t * t * p3.y;
411 let cur = Point { x, y };
412 out.push(Segment { p1: prev, p2: cur });
413 prev = cur;
414 }
415}
416
417fn rasterize(segments: &[Segment], vb_size: f32, out_size: u32) -> Vec<u8> {
423 let scale = out_size as f32 / vb_size;
424 let w = out_size;
425 let h = out_size;
426 let mut data = alloc::vec![0u8; (w * h) as usize];
427
428 for py in 0..h {
429 for px in 0..w {
430 let mut inside = 0u32;
431 for sy in 0..4u32 {
432 let sub_y = py as f32 + (sy as f32 + 0.5) / 4.0;
433 for sx in 0..4u32 {
434 let sub_x = px as f32 + (sx as f32 + 0.5) / 4.0;
435 let vx = sub_x / scale;
437 let vy = sub_y / scale;
438 let mut crossings = 0u32;
439 for seg in segments {
440 let y1 = seg.p1.y;
441 let y2 = seg.p2.y;
442 let y_min = if y1 < y2 { y1 } else { y2 };
443 let y_max = if y1 > y2 { y1 } else { y2 };
444 if vy >= y_min && vy < y_max && y1 != y2 {
445 let t = (vy - y1) / (y2 - y1);
446 let ix = seg.p1.x + t * (seg.p2.x - seg.p1.x);
447 if ix > vx {
448 crossings += 1;
449 }
450 }
451 }
452 if !crossings.is_multiple_of(2) {
453 inside += 1;
454 }
455 }
456 }
457 data[(py * w + px) as usize] = ((inside * 255) / 16).min(255) as u8;
458 }
459 }
460 data
461}
462
463fn extract_path_d(svg: &[u8]) -> Option<alloc::string::String> {
469 let text = core::str::from_utf8(svg).ok()?;
470 let mut search = text;
472 while let Some(pos) = find_substr(search, " d=\"") {
473 let after = search.get(pos + 4..).unwrap_or(""); if let Some(end) = find_char(after.as_bytes(), b'"') {
475 return Some(alloc::string::String::from(after.get(..end).unwrap_or("")));
476 }
477 search = search.get(pos + 1..).unwrap_or("");
478 }
479 let mut search2 = text;
481 while let Some(pos) = find_substr(search2, " d='") {
482 let after = search2.get(pos + 4..).unwrap_or("");
483 if let Some(end) = find_char(after.as_bytes(), b'\'') {
484 return Some(alloc::string::String::from(after.get(..end).unwrap_or("")));
485 }
486 search2 = search2.get(pos + 1..).unwrap_or("");
487 }
488 None
489}
490
491fn extract_viewbox_size(svg: &[u8]) -> f32 {
493 let text = core::str::from_utf8(svg).unwrap_or("");
494 if let Some(pos) = find_substr(text, "viewBox=\"") {
495 let after = text.get(pos + 9..).unwrap_or("");
496 let mut p = PathParser::new(after.as_bytes());
498 let _ = p.next_f32(); let _ = p.next_f32(); if let Some(w) = p.next_f32() {
501 return w;
502 }
503 }
504 20.0 }
506
507fn find_substr(haystack: &str, needle: &str) -> Option<usize> {
508 let hb = haystack.as_bytes();
509 let nb = needle.as_bytes();
510 if nb.is_empty() {
511 return Some(0);
512 }
513 hb.windows(nb.len()).position(|w| w == nb)
514}
515
516fn find_char(haystack: &[u8], needle: u8) -> Option<usize> {
517 haystack.iter().position(|&b| b == needle)
518}
519
520struct SvgShape {
527 segments: Vec<Segment>,
528 fill: u32, }
530
531pub fn decode_svg_image(svg: &[u8], target_w: u32, target_h: u32) -> Option<(u32, u32, Vec<u8>)> {
534 let text = core::str::from_utf8(svg).ok()?;
535 if target_w == 0 || target_h == 0 || target_w > 4096 || target_h > 4096 {
536 return None;
537 }
538
539 let (vb_x, vb_y, vb_w, vb_h) = extract_viewbox_rect(text);
541
542 let shapes = collect_svg_shapes(text);
544 if shapes.is_empty() {
545 return None;
546 }
547
548 let sx = target_w as f32 / vb_w;
549 let sy = target_h as f32 / vb_h;
550 let mut rgba = alloc::vec![0u8; (target_w * target_h * 4) as usize];
551
552 for shape in &shapes {
553 let a = (shape.fill >> 24) & 0xFF;
554 if a == 0 {
555 continue;
556 }
557 let fr = ((shape.fill >> 16) & 0xFF) as f32;
558 let fg = ((shape.fill >> 8) & 0xFF) as f32;
559 let fb = (shape.fill & 0xFF) as f32;
560 let fa = a as f32 / 255.0;
561
562 for py in 0..target_h {
563 for px in 0..target_w {
564 let mut inside = 0u32;
566 for sample in 0..4u32 {
567 let ox = if sample & 1 == 0 { 0.25 } else { 0.75 };
568 let oy = if sample & 2 == 0 { 0.25 } else { 0.75 };
569 let vx = (px as f32 + ox) / sx + vb_x;
570 let vy = (py as f32 + oy) / sy + vb_y;
571 let mut crossings = 0u32;
572 for seg in &shape.segments {
573 let (y1, y2) = (seg.p1.y, seg.p2.y);
574 let ymin = if y1 < y2 { y1 } else { y2 };
575 let ymax = if y1 > y2 { y1 } else { y2 };
576 if vy >= ymin && vy < ymax && y1 != y2 {
577 let t = (vy - y1) / (y2 - y1);
578 let ix = seg.p1.x + t * (seg.p2.x - seg.p1.x);
579 if ix > vx {
580 crossings += 1;
581 }
582 }
583 }
584 if crossings % 2 == 1 {
585 inside += 1;
586 }
587 }
588 if inside == 0 {
589 continue;
590 }
591 let cov = (inside as f32 / 4.0) * fa;
592 let o = ((py * target_w + px) * 4) as usize;
593 let dr = rgba[o] as f32;
595 let dg = rgba[o + 1] as f32;
596 let db = rgba[o + 2] as f32;
597 let da = rgba[o + 3] as f32 / 255.0;
598 let out_a = cov + da * (1.0 - cov);
599 if out_a <= 0.0 {
600 continue;
601 }
602 rgba[o] = (((fr * cov + dr * da * (1.0 - cov)) / out_a).min(255.0)) as u8;
603 rgba[o + 1] = (((fg * cov + dg * da * (1.0 - cov)) / out_a).min(255.0)) as u8;
604 rgba[o + 2] = (((fb * cov + db * da * (1.0 - cov)) / out_a).min(255.0)) as u8;
605 rgba[o + 3] = (out_a * 255.0).min(255.0) as u8;
606 }
607 }
608 }
609
610 Some((target_w, target_h, rgba))
611}
612
613pub fn natural_size(svg: &[u8]) -> (f32, f32) {
615 if let Ok(text) = core::str::from_utf8(svg) {
616 let (_, _, w, h) = extract_viewbox_rect(text);
617 (w, h)
618 } else {
619 (100.0, 100.0)
620 }
621}
622
623fn extract_viewbox_rect(text: &str) -> (f32, f32, f32, f32) {
625 if let Some(pos) = find_substr(text, "viewBox=\"") {
626 let after = text.get(pos + 9..).unwrap_or("");
627 if let Some(end) = find_char(after.as_bytes(), b'"') {
628 let mut p = PathParser::new(&after.as_bytes()[..end]);
629 let x = p.next_f32().unwrap_or(0.0);
630 let y = p.next_f32().unwrap_or(0.0);
631 let w = p.next_f32().unwrap_or(100.0);
632 let h = p.next_f32().unwrap_or(100.0);
633 if w > 0.0 && h > 0.0 {
634 return (x, y, w, h);
635 }
636 }
637 }
638 let w = extract_attr_f32(text, "width").unwrap_or(100.0);
639 let h = extract_attr_f32(text, "height").unwrap_or(100.0);
640 (0.0, 0.0, w.max(1.0), h.max(1.0))
641}
642
643fn extract_attr_f32(text: &str, name: &str) -> Option<f32> {
645 let needle = alloc::format!(" {}=\"", name);
646 let pos = find_substr(text, &needle)?;
647 let after = text.get(pos + needle.len()..).unwrap_or("");
648 let end = find_char(after.as_bytes(), b'"')?;
649 let mut p = PathParser::new(&after.as_bytes()[..end]);
650 p.next_f32()
651}
652
653fn tag_attr<'a>(tag: &'a str, name: &str) -> Option<&'a str> {
655 for q in ['"', '\''] {
656 let needle = alloc::format!("{}={}", name, q);
657 if let Some(pos) = find_substr(tag, &needle) {
658 let after = tag.get(pos + needle.len()..).unwrap_or("");
659 if let Some(end) = find_char(after.as_bytes(), q as u8) {
660 return after.get(..end);
661 }
662 }
663 }
664 None
665}
666
667fn resolve_fill(tag: &str) -> u32 {
669 let mut fill_str: Option<&str> = tag_attr(tag, "fill");
670 if fill_str.is_none() {
672 if let Some(style) = tag_attr(tag, "style") {
673 if let Some(fp) = find_substr(style, "fill:") {
674 let after = style.get(fp + 5..).unwrap_or("");
675 let end = after.find(';').unwrap_or(after.len());
676 fill_str = Some(after.get(..end).unwrap_or("").trim());
677 }
678 }
679 }
680 let opacity = tag_attr(tag, "fill-opacity")
681 .and_then(|s| {
682 let mut p = PathParser::new(s.as_bytes());
683 p.next_f32()
684 })
685 .unwrap_or(1.0);
686 match fill_str {
687 Some(s) if s.trim() == "none" => 0x00000000,
688 Some(s) => {
689 let rgb = crate::os_lib::css::parse_color(s.trim()).unwrap_or(0xFF000000) & 0x00FFFFFF;
690 let a = (opacity.clamp(0.0, 1.0) * 255.0) as u32;
691 (a << 24) | rgb
692 }
693 None => 0xFF000000, }
695}
696
697fn collect_svg_shapes(text: &str) -> Vec<SvgShape> {
699 let mut shapes = Vec::new();
700 let bytes = text.as_bytes();
701 let mut i = 0;
702 while i < bytes.len() {
703 if bytes[i] != b'<' {
704 i += 1;
705 continue;
706 }
707 let tag_start = i + 1;
709 let mut j = tag_start;
710 while j < bytes.len() && bytes[j].is_ascii_alphabetic() {
711 j += 1;
712 }
713 let name = text.get(tag_start..j).unwrap_or("");
714 let end = match find_char(&bytes[i..], b'>') {
716 Some(e) => i + e,
717 None => break,
718 };
719 let tag = text.get(i..end + 1).unwrap_or("");
720
721 let fill = resolve_fill(tag);
722 match name {
723 "path" => {
724 if let Some(d) = tag_attr(tag, "d") {
725 let segs = parse_path_d(d);
726 if !segs.is_empty() {
727 shapes.push(SvgShape {
728 segments: close_segments(segs),
729 fill,
730 });
731 }
732 }
733 }
734 "rect" => {
735 let x = attr_num(tag, "x");
736 let y = attr_num(tag, "y");
737 let w = attr_num(tag, "width");
738 let h = attr_num(tag, "height");
739 if w > 0.0 && h > 0.0 {
740 let pts = [
741 Point { x, y },
742 Point { x: x + w, y },
743 Point { x: x + w, y: y + h },
744 Point { x, y: y + h },
745 ];
746 shapes.push(SvgShape {
747 segments: poly_segments(&pts, true),
748 fill,
749 });
750 }
751 }
752 "circle" => {
753 let cx = attr_num(tag, "cx");
754 let cy = attr_num(tag, "cy");
755 let r = attr_num(tag, "r");
756 if r > 0.0 {
757 shapes.push(SvgShape {
758 segments: ellipse_segments(cx, cy, r, r),
759 fill,
760 });
761 }
762 }
763 "ellipse" => {
764 let cx = attr_num(tag, "cx");
765 let cy = attr_num(tag, "cy");
766 let rx = attr_num(tag, "rx");
767 let ry = attr_num(tag, "ry");
768 if rx > 0.0 && ry > 0.0 {
769 shapes.push(SvgShape {
770 segments: ellipse_segments(cx, cy, rx, ry),
771 fill,
772 });
773 }
774 }
775 "polygon" | "polyline" => {
776 if let Some(pts_str) = tag_attr(tag, "points") {
777 let pts = parse_points(pts_str);
778 if pts.len() >= 3 {
779 shapes.push(SvgShape {
780 segments: poly_segments(&pts, true),
781 fill,
782 });
783 }
784 }
785 }
786 _ => {}
787 }
788 i = end + 1;
789 }
790 shapes
791}
792
793fn attr_num(tag: &str, name: &str) -> f32 {
794 tag_attr(tag, name)
795 .and_then(|s| {
796 let mut p = PathParser::new(s.as_bytes());
797 p.next_f32()
798 })
799 .unwrap_or(0.0)
800}
801
802fn parse_points(s: &str) -> Vec<Point> {
803 let mut p = PathParser::new(s.as_bytes());
804 let mut pts = Vec::new();
805 while let (Some(x), Some(y)) = (p.next_f32(), p.next_f32()) {
806 pts.push(Point { x, y });
807 }
808 pts
809}
810
811fn poly_segments(pts: &[Point], close: bool) -> Vec<Segment> {
813 let mut segs = Vec::new();
814 for w in pts.windows(2) {
815 segs.push(Segment { p1: w[0], p2: w[1] });
816 }
817 if close && pts.len() >= 2 {
818 segs.push(Segment {
819 p1: pts[pts.len() - 1],
820 p2: pts[0],
821 });
822 }
823 segs
824}
825
826fn close_segments(mut segs: Vec<Segment>) -> Vec<Segment> {
828 if let (Some(first), Some(last)) = (segs.first().cloned(), segs.last().cloned()) {
829 if first.p1.x != last.p2.x || first.p1.y != last.p2.y {
830 segs.push(Segment {
831 p1: last.p2,
832 p2: first.p1,
833 });
834 }
835 }
836 segs
837}
838
839fn ellipse_segments(cx: f32, cy: f32, rx: f32, ry: f32) -> Vec<Segment> {
841 const N: u32 = 32;
842 let mut pts = Vec::with_capacity(N as usize);
843 for i in 0..N {
844 let a = (i as f32) * core::f32::consts::TAU / (N as f32);
845 pts.push(Point {
846 x: cx + rx * libm::cosf(a),
847 y: cy + ry * libm::sinf(a),
848 });
849 }
850 poly_segments(&pts, true)
851}
852
853pub struct SvgCache {
859 cache: BTreeMap<usize, Vec<u8>>, }
861
862pub static SVG_CACHE: Mutex<Option<SvgCache>> = Mutex::new(None);
863
864pub fn init_cache() {
865 *SVG_CACHE.lock() = Some(SvgCache {
866 cache: BTreeMap::new(),
867 });
868}
869
870pub fn get_or_rasterize(svg_data: &[u8], size: u32) -> Option<&'static [u8]> {
873 let key = (svg_data.as_ptr() as usize).wrapping_add(size as usize * 0x10000);
874
875 let mut guard = SVG_CACHE.lock();
876 let cache = guard.as_mut()?;
877
878 if let alloc::collections::btree_map::Entry::Vacant(entry) = cache.cache.entry(key) {
879 let d_str = extract_path_d(svg_data)?;
881 let vb_size = extract_viewbox_size(svg_data);
882 let segments = parse_path_d(&d_str);
883 if segments.is_empty() {
884 return None;
885 }
886 let alpha = rasterize(&segments, vb_size, size);
887 entry.insert(alpha);
888 }
889
890 let slice = cache.cache.get(&key)?.as_slice();
891 Some(unsafe { core::mem::transmute::<&[u8], &'static [u8]>(slice) })
893}