Skip to main content

qframe/animation/
write.rs

1//! Writing an animation back as the `[animations.<name>]` table the loaders read.
2
3use std::fmt::Write as _;
4
5use super::CellAnimation;
6use crate::icons::GlyphMode;
7
8impl CellAnimation {
9    /// The `[animations.<name>]` table for this animation, ready to paste into an icon set, theme
10    /// or animation file. Reading it back gives the same animation. Glyphs a frame leaves to its
11    /// fallback are left out, and Nerd Font glyphs from the private use areas are written as
12    /// `\u` escapes so they survive fonts and clipboards that cannot show them.
13    #[must_use]
14    pub fn to_toml(&self, name: &str) -> String {
15        let mut out = format!("[animations.{name}]\n");
16        // Writing into a `String` cannot fail, so there is no error here to carry anywhere; the
17        // results are dropped for that reason and no other.
18        let _ = writeln!(out, "frame = {}", quoted(&self.frame_time.to_string()));
19        let _ = writeln!(out, "playback = {}", quoted(self.playback.name()));
20        let _ = writeln!(out, "colors = {}", quoted(self.colors.name()));
21        if let Some(rest) = self.rest {
22            let _ = writeln!(out, "rest = {}", rest + 1);
23        }
24        out.push_str("frames = [\n");
25        for frame in &self.frames {
26            let mut fields = Vec::new();
27            for (key, mode) in [("nerd", GlyphMode::Nerd), ("unicode", GlyphMode::Unicode), ("ascii", GlyphMode::Ascii)]
28            {
29                if let Some(glyph) = frame.own_glyph(mode) {
30                    fields.push(format!("{key} = {}", quoted(glyph)));
31                }
32            }
33            if let Some(color) = &frame.color {
34                fields.push(format!("color = {}", quoted(color.as_str())));
35            }
36            if let Some(duration) = frame.duration {
37                fields.push(format!("duration = {}", quoted(&duration.to_string())));
38            }
39            let _ = writeln!(out, "  {{ {} }},", fields.join(", "));
40        }
41        out.push_str("]\n");
42        out
43    }
44}
45
46/// `text` as a TOML basic string.
47fn quoted(text: &str) -> String {
48    let mut out = String::from("\"");
49    // Writing into a `String` cannot fail, so there is no error here to carry anywhere; the
50    // results are dropped for that reason and no other.
51    for c in text.chars() {
52        match c {
53            '"' => out.push_str("\\\""),
54            '\\' => out.push_str("\\\\"),
55            c if c.is_control() || is_private_use(c) => {
56                let code = u32::from(c);
57                if code > 0xFFFF {
58                    let _ = write!(out, "\\U{code:08X}");
59                } else {
60                    let _ = write!(out, "\\u{code:04X}");
61                }
62            }
63            c => out.push(c),
64        }
65    }
66    out.push('"');
67    out
68}
69
70/// Whether `c` is in a private use area, where Nerd Font glyphs live.
71fn is_private_use(c: char) -> bool {
72    matches!(u32::from(c), 0xE000..=0xF8FF | 0xF0000..=0xFFFFD | 0x10_0000..=0x10_FFFD)
73}