badgelib 0.5.2

Render customizable SVG badges with gradients, animations, and icons without a hosted service
Documentation
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;

use rayon::prelude::*;
use skrifa::GlyphId;
use skrifa::prelude::{FontRef, LocationRef, MetadataProvider, Size};

const FONT_URL: &str =
  "https://sourceforge.net/projects/dejavu/files/dejavu/2.37/dejavu-sans-ttf-2.37.zip/download";
const FONT_MEMBER: &str = "dejavu-sans-ttf-2.37/ttf/DejaVuSans.ttf";

fn prepare_font(root: &Path) -> Result<PathBuf, String> {
  let target = root.join("target");
  let font = target.join("DejaVuSans.ttf");
  if font.is_file() {
    return Ok(font);
  }

  fs::create_dir_all(&target).map_err(|e| format!("failed to create {}: {e}", target.display()))?;
  let archive = target.join("dejavu-sans-ttf-2.37.zip");
  let status = Command::new("curl")
    .args(["-fsSL", FONT_URL, "-o"])
    .arg(&archive)
    .status()
    .map_err(|e| format!("failed to download DejaVu Sans: {e}"))?;
  if !status.success() {
    return Err(format!("failed to download DejaVu Sans: {status}"));
  }

  let status = Command::new("unzip")
    .args(["-jo"])
    .arg(&archive)
    .arg(FONT_MEMBER)
    .arg("-d")
    .arg(&target)
    .status()
    .map_err(|e| format!("failed to extract DejaVu Sans: {e}"))?;
  if !status.success() {
    return Err(format!("failed to extract DejaVu Sans: {status}"));
  }
  Ok(font)
}

fn generate_widths(font_path: &Path) -> Result<String, String> {
  let font_data =
    fs::read(font_path).map_err(|e| format!("failed to read {}: {e}", font_path.display()))?;
  let font = FontRef::new(&font_data)
    .map_err(|e| format!("failed to parse font data from {}: {e}", font_path.display()))?;
  let size = 110.0;
  let loc = LocationRef::default();
  let f_metrics = font.metrics(Size::unscaled(), loc);
  let g_metrics = font.glyph_metrics(Size::unscaled(), loc);
  let charmap = font.charmap();
  let scale_y = size / (f_metrics.ascent - f_metrics.descent);
  let scale_x = scale_y * ((size * 1.15) / size);
  let widths = (0..2u32.pow(13))
    .into_par_iter()
    .map(|codepoint| {
      if codepoint <= 32 || codepoint == 127 {
        0.0
      } else {
        let character = char::from_u32(codepoint).expect("width-table codepoint must be valid");
        let glyph_id = charmap.map(character).unwrap_or(GlyphId::new(0));
        g_metrics.advance_width(glyph_id).unwrap_or(0.0) * scale_x
      }
    })
    .collect::<Vec<_>>();
  Ok(format!("pub(crate) static WIDTHS: [f32; 8192] = {widths:?};"))
}

fn run() -> Result<(), String> {
  let root = Path::new(env!("CARGO_MANIFEST_DIR"));
  let output = root.join("src/_width.rs");
  let body = generate_widths(&prepare_font(root)?)?;
  let code = format!("// This file is generated by scripts/update-widths.rs\n{body}\n");
  fs::write(&output, code).map_err(|e| format!("failed to write {}: {e}", output.display()))?;
  println!("Generated 8,192 font widths");
  Ok(())
}

fn main() {
  if let Err(error) = run() {
    eprintln!("Font-width update failed: {error}");
    std::process::exit(1);
  }
}