mod embedded;
mod names;
pub use embedded::source;
pub use names::path;
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(super) fn rasterize(svg: &[u8], size: u32) -> Option<Vec<u8>> {
use resvg::{tiny_skia, usvg};
let svg = String::from_utf8_lossy(svg).replace("currentColor", "#ffffff");
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());
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::atlas::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",
);
}
}
}