codecraft 0.2.0

A minimalist 3D game engine built on parts of Bevy (ECS, color) with wgpu and winit: OpenPBR materials, clustered lighting, a yakui-drawn UI, audio and gamepad haptics; its binary maps any folder, and the symbols of its Rust files, as a 3D wall of boxes
Documentation
//! Phosphor icons, rasterised on first use into white-mask textures the UI tints.

mod embedded;
mod names;
pub use embedded::source;
pub use names::path;

/// Icons of our own, for the filled pointer shapes Phosphor has none of.
pub mod ours {
    pub const POINTER_UP: &str = "ui/pointer-up.svg";
    pub const POINTER_DOWN: &str = "ui/pointer-down.svg";
    pub const POINTER_LEFT: &str = "ui/pointer-left.svg";
    pub const POINTER_RIGHT: &str = "ui/pointer-right.svg";

    pub const ALL: [&str; 4] = [POINTER_UP, POINTER_DOWN, POINTER_LEFT, POINTER_RIGHT];
}

pub(crate) fn rasterize(svg: &[u8], size: u32) -> Option<Vec<u8>> {
    // usvg resolves currentColor to black, and black times a tint stays black: the mask must be white.
    rasterize_as(svg, size, "#ffffff")
}

/// Rasterises an SVG to straight-alpha RGBA, `size` square, with `current_color` standing in for `currentColor`.
pub(crate) fn rasterize_as(svg: &[u8], size: u32, current_color: &str) -> Option<Vec<u8>> {
    use resvg::{tiny_skia, usvg};

    let svg = String::from_utf8_lossy(svg).replace("currentColor", current_color);
    let tree = usvg::Tree::from_data(svg.as_bytes(), &usvg::Options::default()).ok()?;
    let mut pixmap = tiny_skia::Pixmap::new(size, size)?;

    let drawing = tree.size();
    let scale = (size as f32 / drawing.width()).min(size as f32 / drawing.height());
    let transform = tiny_skia::Transform::from_scale(scale, scale).post_translate(
        (size as f32 - drawing.width() * scale) / 2.0,
        (size as f32 - drawing.height() * scale) / 2.0,
    );
    resvg::render(&tree, transform, &mut pixmap.as_mut());

    // tiny-skia is premultiplied and the UI samples straight alpha; without demultiply every icon gets a dark fringe.
    let mut data = Vec::with_capacity((size * size * 4) as usize);
    for pixel in pixmap.pixels() {
        let pixel = pixel.demultiply();
        data.extend_from_slice(&[pixel.red(), pixel.green(), pixel.blue(), pixel.alpha()]);
    }
    Some(data)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ui::widgets::ICON_SIZE;

    fn assets() -> std::path::PathBuf {
        std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("assets")
    }

    #[test]
    fn every_named_icon_ships() {
        let missing: Vec<&str> = path::ALL
            .iter()
            .filter(|(_, path)| !assets().join(path).is_file())
            .map(|(name, _)| *name)
            .collect();
        assert!(missing.is_empty(), "assets is missing: {missing:?}");
    }

    #[test]
    fn no_two_names_share_an_icon() {
        let mut seen = std::collections::HashMap::new();
        for (name, path) in path::ALL {
            if let Some(other) = seen.insert(*path, *name) {
                panic!("{name} and {other} are the same icon");
            }
        }
    }

    #[test]
    fn an_icon_rasterizes_to_a_white_mask() {
        let svg = source(path::CUBE).expect("the cube icon is compiled in");
        let raster = rasterize(svg.as_bytes(), 64).expect("it rasterizes");

        let opaque: Vec<&[u8]> = raster.chunks(4).filter(|p| p[3] > 200).collect();
        assert!(!opaque.is_empty(), "the icon drew nothing");
        for pixel in opaque {
            assert_eq!(
                &pixel[..3],
                &[255, 255, 255],
                "a stroke is not white, so tinting it will not work",
            );
        }
    }

    #[test]
    fn every_embedded_icon_draws_something() {
        for (path, svg) in embedded::ALL {
            let raster = rasterize(svg.as_bytes(), ICON_SIZE)
                .unwrap_or_else(|| panic!("{path} does not rasterise"));
            assert!(
                raster.chunks(4).any(|p| p[3] > 0),
                "{path} rasterises to nothing",
            );
        }
    }
}