1use quick_xml::Reader;
8use quick_xml::events::Event;
9
10pub use super::color::{ParsedStyle, parse_color, parse_color_hex, parse_style};
11pub use super::geometry::{
12 Point2D, Transform2D, parse_transform, sample_cubic_bezier, sample_elliptical_arc,
13 sample_quad_bezier,
14};
15use crate::error::Result;
16
17#[derive(Clone, Debug, PartialEq)]
19pub enum SvgElement {
20 Line {
21 p1: Point2D,
22 p2: Point2D,
23 stroke_color: Option<u32>,
24 stroke_width: f64,
25 layer: String,
26 },
27 Rect {
28 x: f64,
29 y: f64,
30 width: f64,
31 height: f64,
32 stroke_color: Option<u32>,
33 fill_color: Option<u32>,
34 layer: String,
35 },
36 Circle {
37 center: Point2D,
38 radius: f64,
39 stroke_color: Option<u32>,
40 fill_color: Option<u32>,
41 layer: String,
42 },
43 Polyline {
44 points: Vec<Point2D>,
45 is_closed: bool,
46 stroke_color: Option<u32>,
47 fill_color: Option<u32>,
48 layer: String,
49 },
50 Text {
51 pos: Point2D,
52 font_size: f64,
53 color: Option<u32>,
54 layer: String,
55 content: String,
56 },
57}
58
59pub struct SvgPathTokenizer<'a> {
61 remaining: &'a str,
62}
63
64impl<'a> SvgPathTokenizer<'a> {
65 pub fn new(d: &'a str) -> Self {
66 Self { remaining: d }
67 }
68}
69
70#[derive(Clone, Copy, Debug, PartialEq)]
72pub enum PathToken<'a> {
73 Command(char),
74 Number(f64),
75 Raw(&'a str),
76}
77
78impl<'a> Iterator for SvgPathTokenizer<'a> {
79 type Item = PathToken<'a>;
80
81 fn next(&mut self) -> Option<Self::Item> {
82 let bytes = self.remaining.as_bytes();
83 let mut i = 0;
84
85 while i < bytes.len() && (bytes[i].is_ascii_whitespace() || bytes[i] == b',') {
87 i += 1;
88 }
89 if i >= bytes.len() {
90 self.remaining = "";
91 return None;
92 }
93
94 let b = bytes[i];
95 if b.is_ascii_alphabetic() {
97 let cmd = b as char;
98 self.remaining = &self.remaining[i + 1..];
99 return Some(PathToken::Command(cmd));
100 }
101
102 let start = i;
104 if b == b'+' || b == b'-' {
105 i += 1;
106 }
107 let mut has_digits = false;
108 while i < bytes.len() && bytes[i].is_ascii_digit() {
109 has_digits = true;
110 i += 1;
111 }
112 if i < bytes.len() && bytes[i] == b'.' {
113 i += 1;
114 while i < bytes.len() && bytes[i].is_ascii_digit() {
115 has_digits = true;
116 i += 1;
117 }
118 }
119 if has_digits && i < bytes.len() && (bytes[i] == b'e' || bytes[i] == b'E') {
121 let exp_start = i;
122 i += 1;
123 if i < bytes.len() && (bytes[i] == b'+' || bytes[i] == b'-') {
124 i += 1;
125 }
126 let exp_digits_start = i;
127 while i < bytes.len() && bytes[i].is_ascii_digit() {
128 i += 1;
129 }
130 if i == exp_digits_start {
131 i = exp_start;
133 }
134 }
135
136 if has_digits {
137 let slice = &self.remaining[start..i];
138 self.remaining = &self.remaining[i..];
139 if let Ok(num) = slice.parse::<f64>() {
140 return Some(PathToken::Number(num));
141 }
142 return Some(PathToken::Raw(slice));
143 }
144
145 let slice = &self.remaining[start..start + 1];
147 self.remaining = &self.remaining[start + 1..];
148 Some(PathToken::Raw(slice))
149 }
150}
151
152pub fn decompose_svg_path(d: &str) -> Vec<(Vec<Point2D>, bool)> {
156 let mut polylines = Vec::new();
157 let mut current_verts = Vec::new();
158 let mut is_closed = false;
159
160 let mut cur = Point2D::new(0.0, 0.0);
161 let mut start = Point2D::new(0.0, 0.0);
162 let mut last_cubic_ctrl: Option<Point2D> = None;
163 let mut last_quad_ctrl: Option<Point2D> = None;
164
165 let mut tokenizer = SvgPathTokenizer::new(d);
166 let mut cur_cmd = 'M';
167
168 while let Some(tok) = tokenizer.next() {
169 match tok {
170 PathToken::Command(c) => {
171 cur_cmd = c;
172 }
173 PathToken::Number(first_num) => {
174 match cur_cmd {
176 'M' | 'm' => {
177 last_cubic_ctrl = None;
178 last_quad_ctrl = None;
179 if !current_verts.is_empty() {
180 polylines.push((std::mem::take(&mut current_verts), is_closed));
181 is_closed = false;
182 }
183 let py = if let Some(PathToken::Number(y)) = tokenizer.next() {
184 y
185 } else {
186 0.0
187 };
188 if cur_cmd == 'm' {
189 cur.x += first_num;
190 cur.y += py;
191 } else {
192 cur.x = first_num;
193 cur.y = py;
194 }
195 start = cur;
196 current_verts.push(cur);
197 cur_cmd = if cur_cmd == 'm' { 'l' } else { 'L' };
198 }
199 'L' | 'l' => {
200 last_cubic_ctrl = None;
201 last_quad_ctrl = None;
202 let py = if let Some(PathToken::Number(y)) = tokenizer.next() {
203 y
204 } else {
205 0.0
206 };
207 if cur_cmd == 'l' {
208 cur.x += first_num;
209 cur.y += py;
210 } else {
211 cur.x = first_num;
212 cur.y = py;
213 }
214 current_verts.push(cur);
215 }
216 'H' | 'h' => {
217 last_cubic_ctrl = None;
218 last_quad_ctrl = None;
219 if cur_cmd == 'h' {
220 cur.x += first_num;
221 } else {
222 cur.x = first_num;
223 }
224 current_verts.push(cur);
225 }
226 'V' | 'v' => {
227 last_cubic_ctrl = None;
228 last_quad_ctrl = None;
229 if cur_cmd == 'v' {
230 cur.y += first_num;
231 } else {
232 cur.y = first_num;
233 }
234 current_verts.push(cur);
235 }
236 'C' | 'c' => {
237 let y1 = if let Some(PathToken::Number(v)) = tokenizer.next() {
238 v
239 } else {
240 0.0
241 };
242 let x2 = if let Some(PathToken::Number(v)) = tokenizer.next() {
243 v
244 } else {
245 0.0
246 };
247 let y2 = if let Some(PathToken::Number(v)) = tokenizer.next() {
248 v
249 } else {
250 0.0
251 };
252 let x = if let Some(PathToken::Number(v)) = tokenizer.next() {
253 v
254 } else {
255 0.0
256 };
257 let y = if let Some(PathToken::Number(v)) = tokenizer.next() {
258 v
259 } else {
260 0.0
261 };
262
263 let (p1, p2, p3) = if cur_cmd == 'c' {
264 (
265 Point2D::new(cur.x + first_num, cur.y + y1),
266 Point2D::new(cur.x + x2, cur.y + y2),
267 Point2D::new(cur.x + x, cur.y + y),
268 )
269 } else {
270 (
271 Point2D::new(first_num, y1),
272 Point2D::new(x2, y2),
273 Point2D::new(x, y),
274 )
275 };
276
277 sample_cubic_bezier(cur, p1, p2, p3, 8, |pt| {
278 current_verts.push(pt);
279 });
280 cur = p3;
281 last_cubic_ctrl = Some(p2);
282 last_quad_ctrl = None;
283 }
284 'S' | 's' => {
285 let y2 = if let Some(PathToken::Number(v)) = tokenizer.next() {
286 v
287 } else {
288 0.0
289 };
290 let x = if let Some(PathToken::Number(v)) = tokenizer.next() {
291 v
292 } else {
293 0.0
294 };
295 let y = if let Some(PathToken::Number(v)) = tokenizer.next() {
296 v
297 } else {
298 0.0
299 };
300
301 let (p2, p3) = if cur_cmd == 's' {
302 (
303 Point2D::new(cur.x + first_num, cur.y + y2),
304 Point2D::new(cur.x + x, cur.y + y),
305 )
306 } else {
307 (Point2D::new(first_num, y2), Point2D::new(x, y))
308 };
309
310 let p1 = if let Some(last_ctrl) = last_cubic_ctrl {
311 Point2D::new(2.0 * cur.x - last_ctrl.x, 2.0 * cur.y - last_ctrl.y)
312 } else {
313 cur
314 };
315
316 sample_cubic_bezier(cur, p1, p2, p3, 8, |pt| {
317 current_verts.push(pt);
318 });
319 cur = p3;
320 last_cubic_ctrl = Some(p2);
321 last_quad_ctrl = None;
322 }
323 'Q' | 'q' => {
324 let y1 = if let Some(PathToken::Number(v)) = tokenizer.next() {
325 v
326 } else {
327 0.0
328 };
329 let x = if let Some(PathToken::Number(v)) = tokenizer.next() {
330 v
331 } else {
332 0.0
333 };
334 let y = if let Some(PathToken::Number(v)) = tokenizer.next() {
335 v
336 } else {
337 0.0
338 };
339
340 let (p1, p2) = if cur_cmd == 'q' {
341 (
342 Point2D::new(cur.x + first_num, cur.y + y1),
343 Point2D::new(cur.x + x, cur.y + y),
344 )
345 } else {
346 (Point2D::new(first_num, y1), Point2D::new(x, y))
347 };
348
349 sample_quad_bezier(cur, p1, p2, 8, |pt| {
350 current_verts.push(pt);
351 });
352 cur = p2;
353 last_quad_ctrl = Some(p1);
354 last_cubic_ctrl = None;
355 }
356 'T' | 't' => {
357 let y = if let Some(PathToken::Number(v)) = tokenizer.next() {
358 v
359 } else {
360 0.0
361 };
362 let p2 = if cur_cmd == 't' {
363 Point2D::new(cur.x + first_num, cur.y + y)
364 } else {
365 Point2D::new(first_num, y)
366 };
367
368 let p1 = if let Some(last_ctrl) = last_quad_ctrl {
369 Point2D::new(2.0 * cur.x - last_ctrl.x, 2.0 * cur.y - last_ctrl.y)
370 } else {
371 cur
372 };
373
374 sample_quad_bezier(cur, p1, p2, 8, |pt| {
375 current_verts.push(pt);
376 });
377 cur = p2;
378 last_quad_ctrl = Some(p1);
379 last_cubic_ctrl = None;
380 }
381 'A' | 'a' => {
382 let ry = if let Some(PathToken::Number(v)) = tokenizer.next() {
383 v
384 } else {
385 0.0
386 };
387 let x_rot = if let Some(PathToken::Number(v)) = tokenizer.next() {
388 v
389 } else {
390 0.0
391 };
392 let large_arc = if let Some(PathToken::Number(v)) = tokenizer.next() {
393 v
394 } else {
395 0.0
396 };
397 let sweep = if let Some(PathToken::Number(v)) = tokenizer.next() {
398 v
399 } else {
400 0.0
401 };
402 let x = if let Some(PathToken::Number(v)) = tokenizer.next() {
403 v
404 } else {
405 0.0
406 };
407 let y = if let Some(PathToken::Number(v)) = tokenizer.next() {
408 v
409 } else {
410 0.0
411 };
412
413 let dest = if cur_cmd == 'a' {
414 Point2D::new(cur.x + x, cur.y + y)
415 } else {
416 Point2D::new(x, y)
417 };
418
419 sample_elliptical_arc(
420 cur,
421 dest,
422 first_num,
423 ry,
424 x_rot,
425 large_arc != 0.0,
426 sweep != 0.0,
427 16,
428 |pt| current_verts.push(pt),
429 );
430 cur = dest;
431 last_cubic_ctrl = None;
432 last_quad_ctrl = None;
433 }
434 _ => {}
435 }
436 }
437 PathToken::Raw(_) => {}
438 }
439
440 if cur_cmd == 'Z' || cur_cmd == 'z' {
441 if !current_verts.is_empty() {
442 current_verts.push(start);
443 is_closed = true;
444 polylines.push((std::mem::take(&mut current_verts), is_closed));
445 is_closed = false;
446 }
447 cur = start;
448 last_cubic_ctrl = None;
449 last_quad_ctrl = None;
450 }
451 }
452
453 if !current_verts.is_empty() {
454 polylines.push((current_verts, is_closed));
455 }
456
457 polylines
458}
459
460#[derive(Clone, Debug, Default)]
462pub struct SvgVectorDocument {
463 pub width: f64,
464 pub height: f64,
465 pub elements: Vec<SvgElement>,
466}
467
468pub type SvgDocument = SvgVectorDocument;
470
471pub fn parse_svg_elements(svg_content: &str) -> Result<SvgVectorDocument> {
475 let mut reader = Reader::from_str(svg_content);
476 reader.config_mut().trim_text(true);
477
478 let mut doc = SvgVectorDocument {
479 width: 800.0,
480 height: 600.0,
481 elements: Vec::new(),
482 };
483
484 let mut current_layer = "0".to_string();
485 let mut layer_stack = Vec::new();
486 let mut current_transform = Transform2D::identity();
487 let mut transform_stack = Vec::new();
488 let mut buf = Vec::new();
489
490 let mut current_text_state: Option<(Point2D, f64, Option<u32>, String, String, Transform2D)> =
491 None;
492
493 while let Ok(event) = reader.read_event_into(&mut buf) {
494 match event {
495 Event::Start(e) | Event::Empty(e) => {
496 let name = e.name().as_ref().to_vec();
497 let tag = String::from_utf8_lossy(&name).to_ascii_lowercase();
498
499 if tag == "svg" {
500 for attr in e.attributes().flatten() {
501 let k = String::from_utf8_lossy(attr.key.as_ref()).to_ascii_lowercase();
502 let v = String::from_utf8_lossy(&attr.value);
503 if k == "viewbox" {
504 let parts: Vec<f64> =
505 v.split([' ', ',']).filter_map(|s| s.parse().ok()).collect();
506 if parts.len() >= 4 && parts[2] > 0.0 && parts[3] > 0.0 {
507 doc.width = parts[2];
508 doc.height = parts[3];
509 }
510 } else if k == "width"
511 && let Ok(w) = v
512 .trim_end_matches("pt")
513 .trim_end_matches("px")
514 .parse::<f64>()
515 && w > 0.0
516 {
517 doc.width = w;
518 } else if k == "height"
519 && let Ok(h) = v
520 .trim_end_matches("pt")
521 .trim_end_matches("px")
522 .parse::<f64>()
523 && h > 0.0
524 {
525 doc.height = h;
526 }
527 }
528 } else if tag == "g" {
529 let mut layer_name = current_layer.clone();
530 let mut g_transform = current_transform;
531 for attr in e.attributes().flatten() {
532 let k = String::from_utf8_lossy(attr.key.as_ref()).to_ascii_lowercase();
533 let v = String::from_utf8_lossy(&attr.value);
534 if k == "id" {
535 let sanitized = v.trim_start_matches("layer-");
536 if !sanitized.is_empty() {
537 layer_name = sanitized.to_string();
538 }
539 } else if k == "transform" {
540 let tf = parse_transform(&v);
541 g_transform = current_transform.multiply(&tf);
542 }
543 }
544 layer_stack.push(current_layer.clone());
545 current_layer = layer_name;
546 transform_stack.push(current_transform);
547 current_transform = g_transform;
548 } else if tag == "line" {
549 let mut p1 = Point2D::default();
550 let mut p2 = Point2D::default();
551 let mut stroke = None;
552 let mut width = 1.0;
553 let mut elem_tf = Transform2D::identity();
554 for attr in e.attributes().flatten() {
555 let k = String::from_utf8_lossy(attr.key.as_ref()).to_ascii_lowercase();
556 let v = String::from_utf8_lossy(&attr.value);
557 match k.as_str() {
558 "x1" => p1.x = v.parse().unwrap_or(0.0),
559 "y1" => p1.y = v.parse().unwrap_or(0.0),
560 "x2" => p2.x = v.parse().unwrap_or(0.0),
561 "y2" => p2.y = v.parse().unwrap_or(0.0),
562 "stroke" => stroke = parse_color(&v),
563 "stroke-width" => {
564 width = v
565 .trim_end_matches("px")
566 .trim_end_matches("pt")
567 .parse()
568 .unwrap_or(1.0)
569 }
570 "style" => {
571 let st = parse_style(&v);
572 if let Some(s) = st.stroke {
573 stroke = s;
574 }
575 if let Some(w) = st.stroke_width {
576 width = w;
577 }
578 }
579 "transform" => elem_tf = parse_transform(&v),
580 _ => {}
581 }
582 }
583 let tf = current_transform.multiply(&elem_tf);
584 doc.elements.push(SvgElement::Line {
585 p1: tf.apply(p1),
586 p2: tf.apply(p2),
587 stroke_color: stroke,
588 stroke_width: width,
589 layer: current_layer.clone(),
590 });
591 } else if tag == "rect" {
592 let mut x = 0.0;
593 let mut y = 0.0;
594 let mut width = 0.0;
595 let mut height = 0.0;
596 let mut stroke = None;
597 let mut fill = None;
598 let mut elem_tf = Transform2D::identity();
599 for attr in e.attributes().flatten() {
600 let k = String::from_utf8_lossy(attr.key.as_ref()).to_ascii_lowercase();
601 let v = String::from_utf8_lossy(&attr.value);
602 match k.as_str() {
603 "x" => x = v.parse().unwrap_or(0.0),
604 "y" => y = v.parse().unwrap_or(0.0),
605 "width" => width = v.parse().unwrap_or(0.0),
606 "height" => height = v.parse().unwrap_or(0.0),
607 "stroke" => stroke = parse_color(&v),
608 "fill" => fill = parse_color(&v),
609 "style" => {
610 let st = parse_style(&v);
611 if let Some(s) = st.stroke {
612 stroke = s;
613 }
614 if let Some(f) = st.fill {
615 fill = f;
616 }
617 }
618 "transform" => elem_tf = parse_transform(&v),
619 _ => {}
620 }
621 }
622 if width > 0.0 && height > 0.0 {
623 let tf = current_transform.multiply(&elem_tf);
624 if tf.b.abs() < 1e-9 && tf.c.abs() < 1e-9 && tf.a > 0.0 && tf.d > 0.0 {
625 let origin = tf.apply(Point2D::new(x, y));
626 doc.elements.push(SvgElement::Rect {
627 x: origin.x,
628 y: origin.y,
629 width: width * tf.a,
630 height: height * tf.d,
631 stroke_color: stroke,
632 fill_color: fill,
633 layer: current_layer.clone(),
634 });
635 } else {
636 let p0 = tf.apply(Point2D::new(x, y));
638 let p1 = tf.apply(Point2D::new(x + width, y));
639 let p2 = tf.apply(Point2D::new(x + width, y + height));
640 let p3 = tf.apply(Point2D::new(x, y + height));
641 doc.elements.push(SvgElement::Polyline {
642 points: vec![p0, p1, p2, p3, p0],
643 is_closed: true,
644 stroke_color: stroke,
645 fill_color: fill,
646 layer: current_layer.clone(),
647 });
648 }
649 }
650 } else if tag == "circle" {
651 let mut center = Point2D::default();
652 let mut radius = 0.0;
653 let mut stroke = None;
654 let mut fill = None;
655 let mut elem_tf = Transform2D::identity();
656 for attr in e.attributes().flatten() {
657 let k = String::from_utf8_lossy(attr.key.as_ref()).to_ascii_lowercase();
658 let v = String::from_utf8_lossy(&attr.value);
659 match k.as_str() {
660 "cx" => center.x = v.parse().unwrap_or(0.0),
661 "cy" => center.y = v.parse().unwrap_or(0.0),
662 "r" => radius = v.parse().unwrap_or(0.0),
663 "stroke" => stroke = parse_color(&v),
664 "fill" => fill = parse_color(&v),
665 "style" => {
666 let st = parse_style(&v);
667 if let Some(s) = st.stroke {
668 stroke = s;
669 }
670 if let Some(f) = st.fill {
671 fill = f;
672 }
673 }
674 "transform" => elem_tf = parse_transform(&v),
675 _ => {}
676 }
677 }
678 if radius > 0.0 {
679 let tf = current_transform.multiply(&elem_tf);
680 let center_trans = tf.apply(center);
681 if tf.b.abs() < 1e-9
682 && tf.c.abs() < 1e-9
683 && (tf.a.abs() - tf.d.abs()).abs() < 1e-6
684 {
685 doc.elements.push(SvgElement::Circle {
686 center: center_trans,
687 radius: radius * tf.a.abs(),
688 stroke_color: stroke,
689 fill_color: fill,
690 layer: current_layer.clone(),
691 });
692 } else {
693 let mut pts = Vec::with_capacity(33);
695 for i in 0..=32 {
696 let angle = (i as f64 / 32.0) * std::f64::consts::PI * 2.0;
697 let px = center.x + radius * angle.cos();
698 let py = center.y + radius * angle.sin();
699 pts.push(tf.apply(Point2D::new(px, py)));
700 }
701 doc.elements.push(SvgElement::Polyline {
702 points: pts,
703 is_closed: true,
704 stroke_color: stroke,
705 fill_color: fill,
706 layer: current_layer.clone(),
707 });
708 }
709 }
710 } else if tag == "polyline" || tag == "polygon" {
711 let is_poly = tag == "polygon";
712 let mut points = Vec::new();
713 let mut stroke = None;
714 let mut fill = None;
715 let mut elem_tf = Transform2D::identity();
716 for attr in e.attributes().flatten() {
717 let k = String::from_utf8_lossy(attr.key.as_ref()).to_ascii_lowercase();
718 let v = String::from_utf8_lossy(&attr.value);
719 if k == "points" {
720 let coords: Vec<f64> =
721 v.split([' ', ',']).filter_map(|s| s.parse().ok()).collect();
722 points = coords
723 .chunks_exact(2)
724 .map(|c| Point2D::new(c[0], c[1]))
725 .collect();
726 } else if k == "stroke" {
727 stroke = parse_color(&v);
728 } else if k == "fill" {
729 fill = parse_color(&v);
730 } else if k == "style" {
731 let st = parse_style(&v);
732 if let Some(s) = st.stroke {
733 stroke = s;
734 }
735 if let Some(f) = st.fill {
736 fill = f;
737 }
738 } else if k == "transform" {
739 elem_tf = parse_transform(&v);
740 }
741 }
742 if !points.is_empty() {
743 let tf = current_transform.multiply(&elem_tf);
744 let transformed_pts: Vec<Point2D> =
745 points.into_iter().map(|p| tf.apply(p)).collect();
746 doc.elements.push(SvgElement::Polyline {
747 points: transformed_pts,
748 is_closed: is_poly,
749 stroke_color: stroke,
750 fill_color: fill,
751 layer: current_layer.clone(),
752 });
753 }
754 } else if tag == "path" {
755 let mut d_str = String::new();
756 let mut stroke = None;
757 let mut fill = None;
758 let mut elem_tf = Transform2D::identity();
759 for attr in e.attributes().flatten() {
760 let k = String::from_utf8_lossy(attr.key.as_ref()).to_ascii_lowercase();
761 let v = String::from_utf8_lossy(&attr.value);
762 if k == "d" {
763 d_str = v.to_string();
764 } else if k == "stroke" {
765 stroke = parse_color(&v);
766 } else if k == "fill" {
767 fill = parse_color(&v);
768 } else if k == "style" {
769 let st = parse_style(&v);
770 if let Some(s) = st.stroke {
771 stroke = s;
772 }
773 if let Some(f) = st.fill {
774 fill = f;
775 }
776 } else if k == "transform" {
777 elem_tf = parse_transform(&v);
778 }
779 }
780 if !d_str.is_empty() {
781 let tf = current_transform.multiply(&elem_tf);
782 let polylines = decompose_svg_path(&d_str);
783 for (pts, is_closed) in polylines {
784 if !pts.is_empty() {
785 let transformed_pts =
786 pts.into_iter().map(|p| tf.apply(p)).collect();
787 doc.elements.push(SvgElement::Polyline {
788 points: transformed_pts,
789 is_closed,
790 stroke_color: stroke,
791 fill_color: fill,
792 layer: current_layer.clone(),
793 });
794 }
795 }
796 }
797 } else if tag == "text" {
798 let mut x = 0.0;
799 let mut y = 0.0;
800 let mut size = 12.0;
801 let mut fill = None;
802 let mut elem_tf = Transform2D::identity();
803 for attr in e.attributes().flatten() {
804 let k = String::from_utf8_lossy(attr.key.as_ref()).to_ascii_lowercase();
805 let v = String::from_utf8_lossy(&attr.value);
806 if k == "x" {
807 x = v.parse().unwrap_or(0.0);
808 } else if k == "y" {
809 y = v.parse().unwrap_or(0.0);
810 } else if k == "font-size" {
811 size = v
812 .trim_end_matches("px")
813 .trim_end_matches("pt")
814 .parse()
815 .unwrap_or(12.0);
816 } else if k == "fill" {
817 fill = parse_color(&v);
818 } else if k == "style" {
819 let st = parse_style(&v);
820 if let Some(f) = st.fill {
821 fill = f;
822 }
823 if let Some(sz) = st.font_size {
824 size = sz;
825 }
826 } else if k == "transform" {
827 elem_tf = parse_transform(&v);
828 }
829 }
830 let tf = current_transform.multiply(&elem_tf);
831 current_text_state = Some((
832 Point2D::new(x, y),
833 size,
834 fill,
835 current_layer.clone(),
836 String::new(),
837 tf,
838 ));
839 }
840 }
841 Event::Text(e) => {
842 if let Some((_, _, _, _, ref mut text, _)) = current_text_state {
843 let text_content = String::from_utf8_lossy(&e);
844 text.push_str(&text_content);
845 }
846 }
847 Event::End(e) => {
848 let name = e.name().as_ref().to_vec();
849 let tag = String::from_utf8_lossy(&name).to_ascii_lowercase();
850 if tag == "g" {
851 if let Some(prev) = layer_stack.pop() {
852 current_layer = prev;
853 }
854 if let Some(prev_tf) = transform_stack.pop() {
855 current_transform = prev_tf;
856 }
857 } else if tag == "text"
858 && let Some((pos, font_size, color, layer, text, tf)) =
859 current_text_state.take()
860 {
861 let trimmed = text.trim();
862 if !trimmed.is_empty() {
863 doc.elements.push(SvgElement::Text {
864 pos: tf.apply(pos),
865 font_size,
866 color,
867 layer,
868 content: trimmed.to_string(),
869 });
870 }
871 }
872 }
873 Event::Eof => break,
874 _ => {}
875 }
876 buf.clear();
877 }
878
879 Ok(doc)
880}
881
882pub fn extract_embedded_source(svg_bytes: &[u8]) -> Option<String> {
885 let mut reader = Reader::from_reader(svg_bytes);
886 reader.config_mut().trim_text(false);
887 let mut buffer = Vec::new();
888
889 loop {
890 match reader.read_event_into(&mut buffer).ok()? {
891 Event::Start(start) | Event::Empty(start) => {
892 let name = start.name();
893 if name.as_ref() == b"svg" {
894 for attr in start.attributes().flatten() {
895 if attr.key.as_ref() == b"content" {
896 let val_str = std::str::from_utf8(&attr.value).ok()?;
897 let unescaped = quick_xml::escape::unescape(val_str).ok()?;
898 if !unescaped.trim().is_empty() {
899 return Some(unescaped.into_owned());
900 }
901 }
902 }
903 return None;
904 }
905 }
906 Event::Eof => return None,
907 _ => {}
908 }
909 buffer.clear();
910 }
911}
912
913pub fn extract_source_format(svg_bytes: &[u8]) -> Option<String> {
915 let mut reader = Reader::from_reader(svg_bytes);
916 reader.config_mut().trim_text(false);
917 let mut buffer = Vec::new();
918 loop {
919 match reader.read_event_into(&mut buffer).ok()? {
920 Event::Start(start) | Event::Empty(start) if start.name().as_ref() == b"svg" => {
921 for attr in start.attributes().flatten() {
922 if attr.key.as_ref() == b"data-source-format" {
923 let value = std::str::from_utf8(&attr.value).ok()?;
924 return quick_xml::escape::unescape(value)
925 .ok()
926 .map(|value| value.into_owned());
927 }
928 }
929 return None;
930 }
931 Event::Eof => return None,
932 _ => {}
933 }
934 buffer.clear();
935 }
936}