founder 0.11.0

The package provides a font toolbox.
Documentation
mod support;

use std::io::Result;
use std::path::{Path, PathBuf};

use colored::Colorize;
use svg::node::element;
use svg::Document;

const DOCUMENT_SIZE: f32 = 512.0;
const MARGIN_SIZE: f32 = 8.0;
const REFERENCES: &[char; 2] = &['X', '0'];

fn main() {
    let arguments = arguments::parse(std::env::args()).unwrap();
    let path: PathBuf = arguments
        .get::<String>("path")
        .unwrap_or_else(|| ".".to_string())
        .into();
    let characters = arguments
        .get::<String>("characters")
        .unwrap_or_else(|| "BESbswy".to_string());
    let excludes = arguments.get_all::<String>("exclude").unwrap_or_default();
    let excludes = excludes.iter().map(String::as_str).collect::<Vec<_>>();
    support::summarize(
        &folder::scan(
            &path,
            |path| support::filter(path, &[".otf", ".ttf"], &excludes),
            process,
            characters,
            arguments.get::<usize>("workers"),
        )
        .collect::<Vec<_>>(),
    );
}

fn process(path: &Path, characters: String) -> Result<Option<()>> {
    use std::io::Write;

    match subprocess(path, &characters, DOCUMENT_SIZE, MARGIN_SIZE) {
        Ok(results) => {
            let mut option = None;
            for (character, document) in results
                .into_iter()
                .filter(|(_, option)| option.is_some())
                .map(|(character, option)| (character, option.unwrap()))
            {
                let character = format!("{}-{:#x}", character, character as usize);
                let path = path.parent().unwrap().join(path.file_stem().unwrap());
                std::fs::create_dir_all(&path)?;
                let path = path.join(character).with_extension("svg");
                let mut file = std::fs::File::create(path)?;
                write!(file, "{document}")?;
                option = Some(());
            }
            eprintln!("{} {path:?}", "[success]".green());
            Ok(option)
        }
        Err(error) => {
            eprintln!("{} {path:?} ({error:?})", "[failure]".red());
            Err(error)
        }
    }
}

fn subprocess(
    path: &Path,
    characters: &str,
    document_size: f32,
    margin_size: f32,
) -> Result<Vec<(char, Option<element::SVG>)>> {
    let font::File { mut fonts } = font::File::open(path)?;
    let metrics = fonts[0].metrics()?;
    let mut reference = None;
    for character in REFERENCES.iter() {
        reference = fonts[0].glyph(*character)?;
        if reference.is_some() {
            break;
        }
    }
    let mut results = vec![];
    for character in characters.chars() {
        let (reference, glyph) = match (reference.as_ref(), fonts[0].glyph(character)?) {
            (Some(reference), Some(glyph)) => (reference, glyph),
            _ => {
                results.push((character, None));
                continue;
            }
        };
        let (x, y, scale) = founder::drawing::transform(
            &glyph,
            &metrics,
            reference,
            document_size - 2.0 * margin_size,
        );
        let transform = format!(
            "translate({margin_size} {margin_size}) scale({scale}) translate({x} {y}) scale(1 -1)",
        );
        let glyph = founder::drawing::draw(&glyph).set("transform", transform);
        let style = element::Style::new("path { fill: black; fill-rule: nonzero; }");
        let document = Document::new()
            .set("width", document_size)
            .set("height", document_size)
            .add(style)
            .add(glyph);
        results.push((character, Some(document)));
    }
    Ok(results)
}