Skip to main content

koan_server/
icon.rs

1//! The kōan icon set, as the web UI and the share page draw it.
2//!
3//! `glyphs` is compiled by `just icons` from the same SVGs the apps draw
4//! (`apps/macos/Resources/Icons`). Each glyph is plain 24-unit strokes on the
5//! theme's stroke rule (docs/design/koan-theme.md#icons): square caps, mitred
6//! joins, the colour of the text beside it, and a width set by size.
7
8mod glyphs;
9
10use crate::html::Html;
11
12pub use glyphs::*;
13
14/// One glyph: its stroked outline, and the outline of its fill layer (the
15/// heart of a favourite, the tick of a selection) where it has one.
16pub struct Glyph {
17    pub name: &'static str,
18    pub strokes: &'static str,
19    pub fills: &'static str,
20}
21
22impl Glyph {
23    /// The glyph as an inline `<svg>` drawn `px` CSS pixels square, with the
24    /// stylesheet's `class` on it. The stroke is the rule's, 0.6 + 0.04 × the
25    /// point size beside which it stands, where the glyph's square is 1.2 times
26    /// that size.
27    pub(crate) fn svg(&self, px: f32, class: &str) -> Html {
28        let points = px / 1.2;
29        let width = (0.6 + 0.04 * points) * 24.0 / px;
30        let mut out = format!(
31            "<svg class=\"{class}\" viewBox=\"0 0 24 24\" fill=none stroke=currentColor \
32stroke-width=\"{width:.2}\" stroke-linecap=square stroke-linejoin=miter aria-hidden=true>"
33        );
34        if !self.strokes.is_empty() {
35            out.push_str(&format!("<path d=\"{}\"/>", self.strokes));
36        }
37        if !self.fills.is_empty() {
38            out.push_str(&format!(
39                "<path d=\"{}\" fill=currentColor stroke=none/>",
40                self.fills
41            ));
42        }
43        out.push_str("</svg>");
44        Html::trusted(out)
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    #[test]
53    fn the_set_is_whole_and_drawn_square() {
54        assert!(ALL.len() > 100);
55        for g in ALL {
56            assert!(
57                !g.strokes.is_empty() || !g.fills.is_empty(),
58                "{} has no shape",
59                g.name
60            );
61        }
62        let svg = PLAY.svg(18.0, "x");
63        assert!(svg.contains("stroke-linecap=square") && svg.contains("class=\"x\""));
64        assert!(HEART_FILLED.svg(15.0, "").contains("fill=currentColor"));
65    }
66}