whiskers.git / web / worker / src / pic.rs
pic.rsannotatedpic.rssource73 lines · 2.8 KB · raw

Pixel icons and the three-column table the page uses for facts. The icons are site::config::Pic: shuqikhor's SVGs, vendored unchanged, drawn inline at about four times a title-bar icon (page.css, .pic). The text of a file is trusted (it is a committed, tested file, never anything a visitor sent); everything else goes through maud's escaping.

6use maud::{Markup, PreEscaped, html};
7use site::config::{Art, Pic};

One icon, hidden from a screen reader: the words beside it say what it means.

10pub fn pic(p: Pic) -> Markup {
11    match p.art().2 {
12        Art::Svg(text) => html! { span .pic aria-hidden="true" { (PreEscaped(text)) } },
13        // A PNG goes in as a data address (the page's policy allows `data:` images), drawn
14        // unsmoothed so its pixels stay square.
15        Art::Png(bytes) => html! { span .pic .png aria-hidden="true" { img src=(format!("data:image/png;base64,{}", base64(bytes))) alt="" width="32" height="32"; } },
16    }
17}

Several icons side by side, for a download that has more than one.

20pub fn pics(list: &[Pic]) -> Markup {
21    html! { span .pics { @for p in list { (pic(*p)) } } }
22}
24fn base64(bytes: &[u8]) -> String {
25    const T: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
26    let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
27    for chunk in bytes.chunks(3) {
28        let n = (u32::from(chunk[0]) << 16) | (u32::from(*chunk.get(1).unwrap_or(&0)) << 8) | u32::from(*chunk.get(2).unwrap_or(&0));
29        for i in 0..4 {
30            if i <= chunk.len() { out.push(T[(n >> (18 - 6 * i) & 63) as usize] as char) } else { out.push('=') }
31        }
32    }
33    out
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39
40    #[test]
41    fn base64_matches_the_standard_examples() {
42        for (plain, coded) in [("", ""), ("f", "Zg=="), ("fo", "Zm8="), ("foo", "Zm9v"), ("foob", "Zm9vYg=="), ("fooba", "Zm9vYmE="), ("foobar", "Zm9vYmFy")] {
43            assert_eq!(base64(plain.as_bytes()), coded);
44        }
45    }
46}

One row: an icon, a title and what it says.

49pub struct Row {
50    pub icon: Pic,
51    pub title: Markup,
52    pub text: Markup,
53}

An icon column, a title column (th scope="row") and a description column. The caption and the header row are for a screen reader; at phone width each row stacks (page.css).

57pub fn facts(caption: &str, rows: Vec<Row>) -> Markup {
58    html! {
59        table .facts {
60            caption .vh { (caption) }
61            thead .vh { tr { th scope="col" { "Icon" } th scope="col" { "Item" } th scope="col" { "What it is" } } }
62            tbody {
63                @for r in rows {
64                    tr {
65                        td .icon-cell { (pic(r.icon)) }
66                        th scope="row" { (r.title) }
67                        td { (r.text) }
68                    }
69                }
70            }
71        }
72    }
73}