use std::fs;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use rusttype::{Font, Scale, point};
fn write_and_format(out_file: &str, code: &str) {
let code = format!("// This file is generated by build.rs\n{}\n", code);
fs::write(out_file, code).unwrap();
let _ = std::process::Command::new("rustfmt")
.arg("--")
.arg(out_file)
.status()
.expect("Failed to run cargo fmt");
}
fn is_no_file(filepath: &str) -> Option<&str> {
match fs::metadata(filepath).map(|m| m.is_file()).unwrap_or(false) {
false => Some(filepath),
true => None,
}
}
fn load_font() -> Font<'static> {
let font_path = "vendor/DejaVuSans.ttf";
let font_data = std::fs::read(font_path).expect("Failed to read font file");
Font::try_from_vec(font_data).expect("Failed to parse font data")
}
fn calc_width(font: &Font, text: &str, size: f32) -> f32 {
font
.layout(text, Scale { x: size * 1.15, y: size }, point(0.0, 0.0))
.map(|g| g.position().x + g.unpositioned().h_metrics().advance_width)
.last()
.unwrap_or(0.0)
}
fn generate_width(outfile: &str) {
let size = 110.0;
let font = load_font();
let mut widths: Vec<f32> = vec![];
for x in 0..2u32.pow(13) {
if x <= 32 || x == 127 {
widths.push(0.0);
} else {
let ch = char::from_u32(x).unwrap();
let cw = calc_width(&font, &ch.to_string(), size);
widths.push(cw);
}
}
let code = format!("pub(crate) static WIDTHS: [f32; 8192] = {:?};", widths);
write_and_format(outfile, &code);
}
fn get_files_of_kind(base_dir: &str, kind: &str) -> Vec<String> {
fs::read_dir(base_dir)
.unwrap()
.filter_map(|x| match x {
Err(_) => None,
Ok(x) => {
if !x.file_type().unwrap().is_file() {
return None;
}
let path = x.path();
if path.extension().unwrap_or_default() != kind {
return None;
}
Some(path.to_str().unwrap().to_string())
}
})
.collect()
}
fn generate_icons(outfile: &str) {
let icons: Vec<(String, String)> = get_files_of_kind("vendor/simple-icons/icons", "svg")
.into_par_iter()
.map(|x| {
let name = x.split('/').next_back().unwrap().split('.').next().unwrap().to_string();
let data = fs::read_to_string(x).unwrap();
let data = data.split("<path d=\"").last().unwrap().split("\"").next().unwrap();
(name, data.to_string())
})
.collect();
let code = icons
.iter()
.map(|(name, data)| format!(" \"{}\" => r###\"{}\"###,", name, data))
.collect::<Vec<String>>()
.join("\n");
let code = format!(
"pub(crate) static ICONS: phf::Map<&'static str, &'static str> = phf::phf_map! {{\n\
{code}\n\
}};",
);
write_and_format(outfile, &code);
}
fn main() {
println!("cargo::rerun-if-changed=build.rs");
println!("cargo::rerun-if-changed=src/_width.rs");
println!("cargo::rerun-if-changed=src/_icons.rs");
if let Some(outfile) = is_no_file("src/_width.rs") {
generate_width(outfile)
}
if let Some(outfile) = is_no_file("src/_icons.rs") {
generate_icons(outfile)
}
}