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) =
38 if let Some(values) = node.attribute("values") {
39 let parts: Vec<&str> = values.split(';').collect();
40 let vals: Vec<f32> = parts
41 .iter()
42 .map(|p| p.trim().parse::<f32>().unwrap_or(0.0))
43 .collect();
44 let kt: Vec<f32> = if let Some(kt_str) = node.attribute("keyTimes") {
46 kt_str
47 .split(';')
48 .map(|p| p.trim().parse::<f32>().unwrap_or(0.0))
49 .collect()
50 } else {
51 Vec::new()
52 };
53 (vals, kt)
54 } else {
55 let f = node
56 .attribute("from")
57 .unwrap_or(if attr == "stroke-dashoffset" {
58 "1"
59 } else {
60 "0"
61 })
62 .parse::<f32>()
63 .unwrap_or(0.0);
64 let t = node
65 .attribute("to")
66 .unwrap_or(if attr == "stroke-dashoffset" {
67 "0"
68 } else {
69 "360"
70 })
71 .parse::<f32>()
72 .unwrap_or(if attr == "stroke-dashoffset" {
73 0.0
74 } else {
75 360.0
76 });
77 (vec![f, t], Vec::new())
78 };
79
80 parsed_animations.push(SvgAnimation {
81 target_id,
82 attribute_name: attr,
83 keyframe_values,
84 key_times,
85 duration,
86 vertex_range: 0..0, });
88 }
89 }
90 }
91 }
92 parsed_animations
93}
94
95pub(crate) fn usvg_to_lyon(path: &usvg::Path, transform: usvg::Transform) -> lyon::path::Path {
98 let mut builder = lyon::path::Path::builder();
99 let mut is_open = false;
100
101 let tx = |p: usvg::tiny_skia_path::Point| -> lyon::math::Point {
103 let nx = transform.sx * p.x + transform.kx * p.y + transform.tx;
104 let ny = transform.ky * p.x + transform.sy * p.y + transform.ty;
105 lyon::math::point(nx, ny)
106 };
107
108 for segment in path.data().segments() {
109 match segment {
110 usvg::tiny_skia_path::PathSegment::MoveTo(p) => {
111 if is_open {
112 builder.end(false);
113 }
114 builder.begin(tx(p));
115 is_open = true;
116 }
117 usvg::tiny_skia_path::PathSegment::LineTo(p) => {
118 builder.line_to(tx(p));
119 }
120 usvg::tiny_skia_path::PathSegment::QuadTo(p1, p) => {
121 builder.quadratic_bezier_to(tx(p1), tx(p));
122 }
123 usvg::tiny_skia_path::PathSegment::CubicTo(p1, p2, p) => {
124 builder.cubic_bezier_to(tx(p1), tx(p2), tx(p));
125 }
126 usvg::tiny_skia_path::PathSegment::Close => {
127 if is_open {
128 builder.end(true);
129 is_open = false;
130 }
131 }
132 }
133 }
134 if is_open {
135 builder.end(false);
136 }
137 builder.build()
138}