Skip to main content

cvkg_render_gpu/
draw.rs

1//! SVG parsing helpers and free functions.
2use 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                            // Parse keyTimes if present
45                            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, // Will be filled during tessellation
87                    });
88                }
89            }
90        }
91    }
92    parsed_animations
93}
94
95// --- SVG Helpers ---
96
97pub(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    // Helper to transform a point
102    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}