embedded-gui 0.1.0

no_std GUI and HUD primitives for embedded-graphics displays
Documentation
use std::{collections::HashMap, env, fs, path::PathBuf};

fn main() {
    let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set"));
    let out_file = out_dir.join("generated_ascii_3x5.rs");
    let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("manifest dir missing"));
    let assets_dir = manifest_dir.join("assets").join("fonts");
    let map_3x5 = load_glyph_overrides(&assets_dir.join("ascii_3x5.txt"));
    let map_4x7 = load_glyph_overrides(&assets_dir.join("ascii_4x7.txt"));

    let mut src = String::from("pub static ASCII_3X5_GLYPHS: [[u8; 5]; 95] = [\n");
    for code in 32u8..=126u8 {
        let ch = code as char;
        let g = map_3x5.get(&ch).copied().unwrap_or_else(|| glyph_3x5(ch));
        src.push_str(&format!(
            "    [{:#05b}, {:#05b}, {:#05b}, {:#05b}, {:#05b}],\n",
            g[0], g[1], g[2], g[3], g[4]
        ));
    }
    src.push_str("];\n");
    src.push('\n');
    src.push_str("pub static ASCII_4X7_GLYPHS: [[u8; 5]; 95] = [\n");
    for code in 32u8..=126u8 {
        let ch = code as char;
        let g = map_4x7.get(&ch).copied().unwrap_or_else(|| glyph_3x5(ch));
        src.push_str(&format!(
            "    [{:#05b}, {:#05b}, {:#05b}, {:#05b}, {:#05b}],\n",
            g[0], g[1], g[2], g[3], g[4]
        ));
    }
    src.push_str("];\n");

    fs::write(out_file, src).expect("failed writing generated ascii font");
}

fn load_glyph_overrides(path: &PathBuf) -> HashMap<char, [u8; 5]> {
    let mut out = HashMap::new();
    let Ok(contents) = fs::read_to_string(path) else {
        return out;
    };

    for line in contents.lines() {
        let line = line.trim();
        if line.is_empty() || line.starts_with('#') {
            continue;
        }
        let Some((key, rows)) = line.split_once(':') else {
            continue;
        };
        let Some(ch) = parse_key(key.trim()) else {
            continue;
        };
        let mut glyph = [0u8; 5];
        let mut ok = true;
        let parts: Vec<&str> = rows.split(',').collect();
        if parts.len() != 5 {
            continue;
        }
        for (idx, part) in parts.into_iter().enumerate() {
            let row = part.trim();
            if row.len() != 3 || row.chars().any(|c| c != '0' && c != '1') {
                ok = false;
                break;
            }
            let mut bits = 0u8;
            for c in row.chars() {
                bits = (bits << 1) | u8::from(c == '1');
            }
            glyph[idx] = bits;
        }
        if ok {
            out.insert(ch, glyph);
        }
    }

    out
}

fn parse_key(key: &str) -> Option<char> {
    if key.eq_ignore_ascii_case("space") {
        return Some(' ');
    }
    if key.chars().count() == 1 {
        return key.chars().next();
    }
    None
}

fn glyph_3x5(ch: char) -> [u8; 5] {
    match ch.to_ascii_uppercase() {
        'A' => [0b010, 0b101, 0b111, 0b101, 0b101],
        'B' => [0b110, 0b101, 0b110, 0b101, 0b110],
        'C' => [0b011, 0b100, 0b100, 0b100, 0b011],
        'D' => [0b110, 0b101, 0b101, 0b101, 0b110],
        'E' => [0b111, 0b100, 0b110, 0b100, 0b111],
        'F' => [0b111, 0b100, 0b110, 0b100, 0b100],
        'G' => [0b011, 0b100, 0b101, 0b101, 0b011],
        'H' => [0b101, 0b101, 0b111, 0b101, 0b101],
        'I' => [0b111, 0b010, 0b010, 0b010, 0b111],
        'J' => [0b001, 0b001, 0b001, 0b101, 0b010],
        'K' => [0b101, 0b101, 0b110, 0b101, 0b101],
        'L' => [0b100, 0b100, 0b100, 0b100, 0b111],
        'M' => [0b101, 0b111, 0b111, 0b101, 0b101],
        'N' => [0b101, 0b111, 0b111, 0b111, 0b101],
        'O' => [0b010, 0b101, 0b101, 0b101, 0b010],
        'P' => [0b110, 0b101, 0b110, 0b100, 0b100],
        'Q' => [0b010, 0b101, 0b101, 0b111, 0b011],
        'R' => [0b110, 0b101, 0b110, 0b101, 0b101],
        'S' => [0b011, 0b100, 0b010, 0b001, 0b110],
        'T' => [0b111, 0b010, 0b010, 0b010, 0b010],
        'U' => [0b101, 0b101, 0b101, 0b101, 0b111],
        'V' => [0b101, 0b101, 0b101, 0b101, 0b010],
        'W' => [0b101, 0b101, 0b111, 0b111, 0b101],
        'X' => [0b101, 0b101, 0b010, 0b101, 0b101],
        'Y' => [0b101, 0b101, 0b010, 0b010, 0b010],
        'Z' => [0b111, 0b001, 0b010, 0b100, 0b111],
        '0' => [0b111, 0b101, 0b101, 0b101, 0b111],
        '1' => [0b010, 0b110, 0b010, 0b010, 0b111],
        '2' => [0b110, 0b001, 0b010, 0b100, 0b111],
        '3' => [0b110, 0b001, 0b010, 0b001, 0b110],
        '4' => [0b101, 0b101, 0b111, 0b001, 0b001],
        '5' => [0b111, 0b100, 0b110, 0b001, 0b110],
        '6' => [0b011, 0b100, 0b110, 0b101, 0b010],
        '7' => [0b111, 0b001, 0b010, 0b010, 0b010],
        '8' => [0b010, 0b101, 0b010, 0b101, 0b010],
        '9' => [0b010, 0b101, 0b011, 0b001, 0b110],
        '-' => [0b000, 0b000, 0b111, 0b000, 0b000],
        '_' => [0b000, 0b000, 0b000, 0b000, 0b111],
        ':' => [0b000, 0b010, 0b000, 0b010, 0b000],
        '.' => [0b000, 0b000, 0b000, 0b000, 0b010],
        '%' => [0b101, 0b001, 0b010, 0b100, 0b101],
        ' ' => [0b000, 0b000, 0b000, 0b000, 0b000],
        _ => [0b111, 0b101, 0b101, 0b101, 0b111],
    }
}