1use crate::types::SvgAnimation;
3
4pub fn parse_svg_animations(data: &[u8]) -> Vec<SvgAnimation> {
5 let mut parsed_animations = Vec::new();
6 if let Ok(xml_doc) = roxmltree::Document::parse(std::str::from_utf8(data).unwrap_or("")) {
7 for node in xml_doc.descendants() {
8 if node.tag_name().name() == "animateTransform" || node.tag_name().name() == "animate" {
9 let target_id = node
10 .attribute("href")
11 .or_else(|| node.attribute(("http://www.w3.org/1999/xlink", "href")))
12 .or_else(|| node.attribute("xlink:href"))
13 .or_else(|| node.parent_element().and_then(|p| p.attribute("id")))
14 .unwrap_or("")
15 .trim_start_matches('#')
16 .to_string();
17
18 if !target_id.is_empty() {
19 let dur_str = node.attribute("dur").unwrap_or("1s");
20 let duration = if dur_str == "indefinite" {
21 f32::INFINITY
22 } else if dur_str.ends_with("ms") {
23 dur_str
24 .trim_end_matches("ms")
25 .parse::<f32>()
26 .unwrap_or(1000.0)
27 / 1000.0
28 } else {
29 dur_str.trim_end_matches('s').parse::<f32>().unwrap_or(1.0)
30 };
31
32 let attr = node
33 .attribute("attributeName")
34 .unwrap_or("transform")
35 .to_string();
36
37 let (keyframe_values, key_times) = if let Some(values) = node.attribute("values") {
38 let parts: Vec<&str> = values.split(';').collect();
39 let vals: Vec<f32> = parts.iter()
40 .map(|p| p.trim().parse::<f32>().unwrap_or(0.0))
41 .collect();
42 let kt: Vec<f32> = if let Some(kt_str) = node.attribute("keyTimes") {
44 kt_str.split(';').map(|p| p.trim().parse::<f32>().unwrap_or(0.0)).collect()
45 } else {
46 Vec::new()
47 };
48 (vals, kt)
49 } else {
50 let f = node
51 .attribute("from")
52 .unwrap_or(if attr == "stroke-dashoffset" { "1" } else { "0" })
53 .parse::<f32>()
54 .unwrap_or(0.0);
55 let t = node
56 .attribute("to")
57 .unwrap_or(if attr == "stroke-dashoffset" { "0" } else { "360" })
58 .parse::<f32>()
59 .unwrap_or(if attr == "stroke-dashoffset" { 0.0 } else { 360.0 });
60 (vec![f, t], Vec::new())
61 };
62
63 parsed_animations.push(SvgAnimation {
64 target_id,
65 attribute_name: attr,
66 keyframe_values,
67 key_times,
68 duration,
69 vertex_range: 0..0, });
71 }
72 }
73 }
74 }
75 parsed_animations
76}
77
78pub(crate) fn usvg_to_lyon(path: &usvg::Path, transform: usvg::Transform) -> lyon::path::Path {
81 let mut builder = lyon::path::Path::builder();
82 let mut is_open = false;
83
84 let tx = |p: usvg::tiny_skia_path::Point| -> lyon::math::Point {
86 let nx = transform.sx * p.x + transform.kx * p.y + transform.tx;
87 let ny = transform.ky * p.x + transform.sy * p.y + transform.ty;
88 lyon::math::point(nx, ny)
89 };
90
91 for segment in path.data().segments() {
92 match segment {
93 usvg::tiny_skia_path::PathSegment::MoveTo(p) => {
94 if is_open {
95 builder.end(false);
96 }
97 builder.begin(tx(p));
98 is_open = true;
99 }
100 usvg::tiny_skia_path::PathSegment::LineTo(p) => {
101 builder.line_to(tx(p));
102 }
103 usvg::tiny_skia_path::PathSegment::QuadTo(p1, p) => {
104 builder.quadratic_bezier_to(tx(p1), tx(p));
105 }
106 usvg::tiny_skia_path::PathSegment::CubicTo(p1, p2, p) => {
107 builder.cubic_bezier_to(tx(p1), tx(p2), tx(p));
108 }
109 usvg::tiny_skia_path::PathSegment::Close => {
110 if is_open {
111 builder.end(true);
112 is_open = false;
113 }
114 }
115 }
116 }
117 if is_open {
118 builder.end(false);
119 }
120 builder.build()
121}