1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use skia_safe::{
4 Canvas, ColorType, ImageInfo, Matrix, Paint, PaintStyle, Path, PathBuilder, PathMeasure, Rect,
5};
6
7use rustmotion_core::css::CssStyle;
8use rustmotion_core::engine::animator::AnimatedProperties;
9use rustmotion_core::engine::layout_pass::BoxLayout;
10use rustmotion_core::engine::renderer::asset_cache;
11use rustmotion_core::schema::TimelineStep;
12use rustmotion_core::traits::{PaintCtx, Painter, TimingConfig};
13
14#[derive(Debug, Serialize, Deserialize, JsonSchema)]
15pub struct Svg {
16 #[serde(default)]
17 pub src: Option<String>,
18 #[serde(default)]
19 pub data: Option<String>,
20 #[serde(flatten)]
21 pub timing: TimingConfig,
22 #[serde(default)]
23 pub style: CssStyle,
24 #[serde(default)]
25 pub timeline: Vec<TimelineStep>,
26 #[serde(default)]
27 pub stagger: Option<f32>,
28 #[serde(default)]
30 pub draw: bool,
31 #[serde(default = "default_draw_stroke_width")]
33 pub draw_stroke_width: f32,
34 #[serde(default)]
37 pub draw_overlap: f32,
38}
39
40fn default_draw_stroke_width() -> f32 {
41 2.0
42}
43
44rustmotion_core::impl_traits!(Svg {
45 Animatable => animation,
46 Timed => timing,
47 Styled => style,
48});
49
50fn tiny_path_to_skia(tsp: &tiny_skia::Path, abs_transform: tiny_skia::Transform) -> Path {
59 let t = abs_transform;
63 let matrix = Matrix::new_all(t.sx, t.kx, t.tx, t.ky, t.sy, t.ty, 0.0, 0.0, 1.0);
64
65 let mut skia_path = PathBuilder::new();
66 for segment in tsp.segments() {
67 match segment {
68 tiny_skia::PathSegment::MoveTo(p) => {
69 let pt = matrix.map_point((p.x, p.y));
70 skia_path.move_to(pt);
71 }
72 tiny_skia::PathSegment::LineTo(p) => {
73 let pt = matrix.map_point((p.x, p.y));
74 skia_path.line_to(pt);
75 }
76 tiny_skia::PathSegment::QuadTo(p1, p2) => {
77 let cp = matrix.map_point((p1.x, p1.y));
78 let ep = matrix.map_point((p2.x, p2.y));
79 skia_path.quad_to(cp, ep);
80 }
81 tiny_skia::PathSegment::CubicTo(p1, p2, p3) => {
82 let cp1 = matrix.map_point((p1.x, p1.y));
83 let cp2 = matrix.map_point((p2.x, p2.y));
84 let ep = matrix.map_point((p3.x, p3.y));
85 skia_path.cubic_to(cp1, cp2, ep);
86 }
87 tiny_skia::PathSegment::Close => {
88 skia_path.close();
89 }
90 }
91 }
92 skia_path.detach()
93}
94
95fn collect_paths(
98 group: &usvg::Group,
99 draw_stroke_width: f32,
100 out: &mut Vec<(Path, skia_safe::Color, f32)>,
101) {
102 for node in group.children() {
103 match node {
104 usvg::Node::Group(g) => {
105 collect_paths(g, draw_stroke_width, out);
106 }
107 usvg::Node::Path(p) => {
108 if !p.is_visible() {
109 continue;
110 }
111 let skia_path = tiny_path_to_skia(p.data(), p.abs_transform());
112 let (color, sw) = if let Some(stroke) = p.stroke() {
115 let sw = stroke.width().get();
116 let c = match stroke.paint() {
117 usvg::Paint::Color(col) => {
118 let alpha = (stroke.opacity().get() * 255.0) as u8;
119 skia_safe::Color::from_argb(alpha, col.red, col.green, col.blue)
120 }
121 _ => skia_safe::Color::WHITE,
123 };
124 (c, sw)
125 } else if let Some(fill) = p.fill() {
126 let c = match fill.paint() {
127 usvg::Paint::Color(col) => {
128 let alpha = (fill.opacity().get() * 255.0) as u8;
129 skia_safe::Color::from_argb(alpha, col.red, col.green, col.blue)
130 }
131 _ => skia_safe::Color::WHITE,
132 };
133 (c, draw_stroke_width)
134 } else {
135 (skia_safe::Color::WHITE, draw_stroke_width)
136 };
137 out.push((skia_path, color, sw));
138 }
139 _ => {}
141 }
142 }
143}
144
145fn paint_draw_on(
148 canvas: &Canvas,
149 group: &usvg::Group,
150 svg_size: usvg::Size,
151 layout: &BoxLayout,
152 progress: f32,
153 draw_stroke_width: f32,
154 draw_overlap: f32,
155) {
156 let progress = progress.clamp(0.0, 1.0);
157
158 let mut paths_with_colors: Vec<(Path, skia_safe::Color, f32)> = Vec::new();
160 collect_paths(group, draw_stroke_width, &mut paths_with_colors);
161
162 if paths_with_colors.is_empty() {
163 return;
164 }
165
166 let scale_x = if svg_size.width() > 0.0 {
168 layout.width / svg_size.width()
169 } else {
170 1.0
171 };
172 let scale_y = if svg_size.height() > 0.0 {
173 layout.height / svg_size.height()
174 } else {
175 1.0
176 };
177
178 canvas.save();
179 canvas.scale((scale_x, scale_y));
180
181 let lengths: Vec<f32> = paths_with_colors
184 .iter()
185 .map(|(path, _, _)| {
186 let mut pm = PathMeasure::new(path, false, None);
187 pm.length()
188 })
189 .collect();
190
191 let total_length: f32 = lengths.iter().sum();
192 if total_length <= 0.0 {
193 canvas.restore();
194 return;
195 }
196
197 let overlap = draw_overlap.clamp(0.0, 1.0);
199
200 let mut cumulative = 0.0f32;
211 for ((path, color, sw), length) in paths_with_colors.iter().zip(lengths.iter()) {
212 let base_frac = length / total_length;
213 let window_size = base_frac * (1.0 - overlap) + overlap;
214 let start_frac = cumulative * (1.0 - overlap);
215 cumulative += base_frac;
216
217 let local_t = if window_size > 0.0 {
220 ((progress - start_frac) / window_size).clamp(0.0, 1.0)
221 } else {
222 if progress >= start_frac {
223 1.0
224 } else {
225 0.0
226 }
227 };
228
229 if local_t <= 0.0 {
230 continue;
232 }
233
234 let draw_len = length * local_t;
235
236 let mut paint = Paint::default();
237 paint.set_color(*color);
238 paint.set_style(PaintStyle::Stroke);
239 paint.set_stroke_width(*sw);
240 paint.set_anti_alias(true);
241
242 if local_t < 1.0 && draw_len > 0.0 {
243 let remaining = length - draw_len;
244 let intervals = [draw_len, remaining + 0.01];
246 if let Some(dash) = skia_safe::PathEffect::dash(&intervals, 0.0) {
247 paint.set_path_effect(dash);
248 }
249 }
250 let scale_avg = (scale_x * scale_y).sqrt();
260 if scale_avg > 0.0 {
261 paint.set_stroke_width(sw / scale_avg);
262 }
263
264 canvas.draw_path(path, &paint);
265 }
266
267 canvas.restore();
268}
269
270impl Painter for Svg {
271 fn paint_content(
272 &self,
273 canvas: &Canvas,
274 layout: &BoxLayout,
275 props: &AnimatedProperties,
276 _ctx: &PaintCtx,
277 ) {
278 let draw_active = self.draw || (props.draw_progress >= 0.0 && props.draw_progress < 1.0);
279
280 if draw_active {
281 let progress = if props.draw_progress >= 0.0 {
283 props.draw_progress
284 } else {
285 1.0
287 };
288
289 if progress <= 0.0 {
290 return;
291 }
292
293 let svg_data = if let Some(ref src) = self.src {
294 match std::fs::read(src) {
295 Ok(d) => d,
296 Err(_) => return,
297 }
298 } else if let Some(ref data) = self.data {
299 data.as_bytes().to_vec()
300 } else {
301 return;
302 };
303
304 let opt = usvg::Options::default();
305 let Ok(tree) = usvg::Tree::from_data(&svg_data, &opt) else {
306 return;
307 };
308
309 let svg_size = tree.size();
310
311 if progress >= 1.0 {
312 self.paint_resvg(canvas, layout, &svg_data, &tree, svg_size);
314 } else {
315 paint_draw_on(
316 canvas,
317 tree.root(),
318 svg_size,
319 layout,
320 progress,
321 self.draw_stroke_width,
322 self.draw_overlap,
323 );
324 }
325 } else {
326 self.paint_static(canvas, layout);
328 }
329 }
330}
331
332impl Svg {
333 fn paint_static(&self, canvas: &Canvas, layout: &BoxLayout) {
335 let target_w_opt: Option<u32> = if layout.width > 0.0 {
336 Some(layout.width as u32)
337 } else {
338 None
339 };
340 let target_h_opt: Option<u32> = if layout.height > 0.0 {
341 Some(layout.height as u32)
342 } else {
343 None
344 };
345
346 let cache_key = if let Some(ref src) = self.src {
347 format!(
348 "svg:{}:{}x{}",
349 src,
350 target_w_opt.unwrap_or(0),
351 target_h_opt.unwrap_or(0)
352 )
353 } else if let Some(ref data) = self.data {
354 use std::collections::hash_map::DefaultHasher;
355 use std::hash::{Hash, Hasher};
356 let mut hasher = DefaultHasher::new();
357 data.hash(&mut hasher);
358 format!(
359 "svg-inline:{}:{}x{}",
360 hasher.finish(),
361 target_w_opt.unwrap_or(0),
362 target_h_opt.unwrap_or(0)
363 )
364 } else {
365 return;
366 };
367
368 let cache = asset_cache();
369 let img = if let Some(cached) = cache.get(&cache_key) {
370 cached.clone()
371 } else {
372 let svg_data = if let Some(ref src) = self.src {
373 let Ok(data) = std::fs::read(src) else { return };
374 data
375 } else if let Some(ref data) = self.data {
376 data.as_bytes().to_vec()
377 } else {
378 return;
379 };
380
381 let opt = usvg::Options::default();
382 let Ok(tree) = usvg::Tree::from_data(&svg_data, &opt) else {
383 return;
384 };
385
386 let svg_size = tree.size();
387 let target_w = target_w_opt.unwrap_or(svg_size.width() as u32);
388 let target_h = target_h_opt.unwrap_or(svg_size.height() as u32);
389
390 let Some(mut pixmap) = tiny_skia::Pixmap::new(target_w, target_h) else {
391 return;
392 };
393
394 let scale_x = target_w as f32 / svg_size.width();
395 let scale_y = target_h as f32 / svg_size.height();
396 let transform = tiny_skia::Transform::from_scale(scale_x, scale_y);
397
398 resvg::render(&tree, transform, &mut pixmap.as_mut());
399
400 let img_data = skia_safe::Data::new_copy(pixmap.data());
401 let img_info = ImageInfo::new(
402 (target_w as i32, target_h as i32),
403 ColorType::RGBA8888,
404 skia_safe::AlphaType::Premul,
405 None,
406 );
407 let Some(decoded) =
408 skia_safe::images::raster_from_data(&img_info, img_data, target_w as usize * 4)
409 else {
410 return;
411 };
412 cache.insert(cache_key, decoded.clone());
413 decoded
414 };
415
416 let dst = Rect::from_xywh(0.0, 0.0, layout.width, layout.height);
417 let paint = Paint::default();
418 canvas.draw_image_rect(img, None, dst, &paint);
419 }
420
421 fn paint_resvg(
423 &self,
424 canvas: &Canvas,
425 layout: &BoxLayout,
426 svg_data: &[u8],
427 tree: &usvg::Tree,
428 svg_size: usvg::Size,
429 ) {
430 let target_w = if layout.width > 0.0 {
431 layout.width as u32
432 } else {
433 svg_size.width() as u32
434 };
435 let target_h = if layout.height > 0.0 {
436 layout.height as u32
437 } else {
438 svg_size.height() as u32
439 };
440
441 if target_w == 0 || target_h == 0 {
442 return;
443 }
444
445 let Some(mut pixmap) = tiny_skia::Pixmap::new(target_w, target_h) else {
446 return;
447 };
448
449 let scale_x = target_w as f32 / svg_size.width();
450 let scale_y = target_h as f32 / svg_size.height();
451 let transform = tiny_skia::Transform::from_scale(scale_x, scale_y);
452 resvg::render(tree, transform, &mut pixmap.as_mut());
453
454 let img_data = skia_safe::Data::new_copy(pixmap.data());
455 let img_info = ImageInfo::new(
456 (target_w as i32, target_h as i32),
457 ColorType::RGBA8888,
458 skia_safe::AlphaType::Premul,
459 None,
460 );
461 let Some(img) =
462 skia_safe::images::raster_from_data(&img_info, img_data, target_w as usize * 4)
463 else {
464 return;
465 };
466
467 let dst = Rect::from_xywh(0.0, 0.0, layout.width, layout.height);
468 let paint = Paint::default();
469 canvas.draw_image_rect(img, None, dst, &paint);
470
471 let _ = svg_data; }
473}