use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use rayon::prelude::*;
use rusttype::{Font, Scale, point};
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(|error| format!("failed to create {}: {error}", 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(|error| format!("failed to download DejaVu Sans: {error}"))?;
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(|error| format!("failed to extract DejaVu Sans: {error}"))?;
if !status.success() {
return Err(format!("failed to extract DejaVu Sans: {status}"));
}
Ok(font)
}
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(|glyph| glyph.position().x + glyph.unpositioned().h_metrics().advance_width)
.last()
.unwrap_or(0.0)
}
fn generate_widths(font_path: &Path) -> Result<String, String> {
let font_data = fs::read(font_path)
.map_err(|error| format!("failed to read {}: {error}", font_path.display()))?;
let font = Font::try_from_vec(font_data)
.ok_or_else(|| format!("failed to parse font data from {}", font_path.display()))?;
let size = 110.0;
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");
calc_width(&font, &character.to_string(), size)
}
})
.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(|error| format!("failed to write {}: {error}", 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);
}
}