badgelib 0.1.1

A library for generating badges in Rust
Documentation
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,
  }
}

// MARK: Font Width

fn load_font() -> Font<'static> {
  // let font_path = "/System/Library/Fonts/Supplemental/Verdana.ttf";
  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")
}

// https://gitlab.redox-os.org/redox-os/rusttype/-/blob/d2b6874c/dev/examples/image.rs
// https://gitlab.redox-os.org/redox-os/rusttype/-/blob/d2b6874c/dev/examples/ascii.rs
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);
}

// MARK: Icons

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);
}

// MARK: Main

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)
  }
}