1use std::fmt::Write;
4
5use crate::color::Color;
6use crate::geom::VPath;
7use crate::mobject::SceneState;
8use crate::{FRAME_HEIGHT, FRAME_WIDTH};
9
10pub fn to_svg(state: &SceneState, background: Color) -> String {
12 let (w, h) = (FRAME_WIDTH, FRAME_HEIGHT);
13 let mut out = String::new();
14 let _ = writeln!(
15 out,
16 r#"<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="{} {} {} {}">"#,
17 num(-w / 2.0),
18 num(-h / 2.0),
19 num(w),
20 num(h)
21 );
22 let _ = writeln!(
23 out,
24 r#"<rect x="{}" y="{}" width="{}" height="{}" fill="{}"/>"#,
25 num(-w / 2.0),
26 num(-h / 2.0),
27 num(w),
28 num(h),
29 background.to_hex()
30 );
31 out.push_str("<g transform=\"scale(1,-1)\">\n");
33 let mut order: Vec<_> = state.iter().collect();
34 order.sort_by_key(|(id, m)| (m.z_index, **id));
35 for (_, m) in order {
36 let d = path_data(&m.path.trim(m.draw_range.clone()));
37 if m.opacity <= 0.0 || d.is_empty() {
38 continue;
39 }
40 let _ = writeln!(
41 out,
42 r#"<path d="{d}" fill="{}" fill-opacity="{}" stroke="{}" stroke-opacity="{}" stroke-width="{}" stroke-linejoin="round" stroke-linecap="round" opacity="{}"/>"#,
43 m.fill.to_hex(),
44 num(m.fill.a.into()),
45 m.stroke.color.to_hex(),
46 num(m.stroke.color.a.into()),
47 num(m.stroke.width),
48 num(m.opacity.into()),
49 );
50 }
51 out.push_str("</g>\n</svg>\n");
52 out
53}
54
55pub fn path_data(p: &VPath) -> String {
57 let mut d = String::new();
58 for sp in &p.subpaths {
59 let Some(first) = sp.segments.first() else {
60 continue;
61 };
62 let _ = write!(d, "M{} {}", num(first.p0.x), num(first.p0.y));
63 for s in &sp.segments {
64 let _ = write!(
65 d,
66 "C{} {} {} {} {} {}",
67 num(s.p1.x),
68 num(s.p1.y),
69 num(s.p2.x),
70 num(s.p2.y),
71 num(s.p3.x),
72 num(s.p3.y)
73 );
74 }
75 if sp.closed {
76 d.push('Z');
77 }
78 }
79 d
80}
81
82fn num(x: f64) -> String {
84 let s = format!("{x:.4}");
85 let s = s.trim_end_matches('0').trim_end_matches('.');
86 if s == "-0" { "0".into() } else { s.into() }
87}