webfont-generator 0.1.0

Generate webfonts (SVG, TTF, EOT, WOFF, WOFF2) from SVG icons
Documentation
use std::path::Path;
use std::process::ExitCode;

use clap::Parser;
use webfont_generator::{FontType, GenerateWebfontsOptions};

#[derive(Parser)]
#[command(
    name = "webfont-generator",
    version,
    about = "Generate webfonts from SVG icons"
)]
struct Cli {
    /// SVG files or directories containing SVG files
    #[arg(required = true)]
    files: Vec<String>,

    /// Output directory
    #[arg(short, long)]
    dest: String,

    /// Font name
    #[arg(short = 'n', long, default_value = "iconfont")]
    font_name: String,

    /// Font types to generate (svg, ttf, eot, woff, woff2)
    #[arg(short, long, value_delimiter = ',')]
    types: Option<Vec<String>>,

    /// Generate CSS
    #[arg(long, default_value_t = true)]
    css: bool,

    /// Generate HTML preview
    #[arg(long)]
    html: bool,

    /// Custom CSS template path
    #[arg(long)]
    css_template: Option<String>,

    /// Custom HTML template path
    #[arg(long)]
    html_template: Option<String>,

    /// CSS fonts URL prefix
    #[arg(long)]
    css_fonts_url: Option<String>,

    /// Do not write output files (dry run)
    #[arg(long)]
    no_write: bool,

    /// Enable ligatures
    #[arg(long)]
    ligature: bool,

    /// Font height
    #[arg(long)]
    font_height: Option<f64>,

    /// Ascent value
    #[arg(long)]
    ascent: Option<f64>,

    /// Descent value
    #[arg(long)]
    descent: Option<f64>,

    /// Start codepoint (hex, e.g. 0xF101)
    #[arg(long)]
    start_codepoint: Option<String>,
}

fn parse_font_type(s: &str) -> Option<FontType> {
    match s.to_lowercase().as_str() {
        "svg" => Some(FontType::Svg),
        "ttf" => Some(FontType::Ttf),
        "eot" => Some(FontType::Eot),
        "woff" => Some(FontType::Woff),
        "woff2" => Some(FontType::Woff2),
        _ => None,
    }
}

fn collect_svg_files(paths: &[String]) -> std::io::Result<Vec<String>> {
    let mut result = Vec::new();
    for path in paths {
        let p = Path::new(path);
        if p.is_dir() {
            let mut dir_files: Vec<String> = std::fs::read_dir(p)?
                .filter_map(|e| e.ok())
                .map(|e| e.path())
                .filter(|p| p.extension().and_then(|e| e.to_str()) == Some("svg"))
                .map(|p| p.to_string_lossy().into_owned())
                .collect();
            dir_files.sort();
            result.extend(dir_files);
        } else {
            result.push(path.clone());
        }
    }
    Ok(result)
}

fn main() -> ExitCode {
    let cli = Cli::parse();

    let files = match collect_svg_files(&cli.files) {
        Ok(files) => files,
        Err(error) => {
            eprintln!("Error: failed to read input path: {error}");
            return ExitCode::FAILURE;
        }
    };
    if files.is_empty() {
        eprintln!("Error: no SVG files found in the specified paths.");
        return ExitCode::FAILURE;
    }

    let types = cli.types.map(|ts| {
        ts.iter()
            .filter_map(|t| {
                let parsed = parse_font_type(t);
                if parsed.is_none() {
                    eprintln!("Warning: unknown font type '{t}', skipping.");
                }
                parsed
            })
            .collect()
    });

    let start_codepoint = cli.start_codepoint.and_then(|s| {
        let s = s.trim();
        if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
            u32::from_str_radix(hex, 16).ok()
        } else {
            s.parse::<u32>().ok()
        }
    });

    let options = GenerateWebfontsOptions {
        ascent: cli.ascent,
        css: Some(cli.css),
        css_template: cli.css_template,
        css_fonts_url: cli.css_fonts_url,
        descent: cli.descent,
        dest: cli.dest,
        files,
        font_height: cli.font_height,
        font_name: Some(cli.font_name),
        html: Some(cli.html),
        html_template: cli.html_template,
        ligature: Some(cli.ligature),
        start_codepoint,
        types,
        write_files: Some(!cli.no_write),
        ..Default::default()
    };

    match webfont_generator::generate_sync(options, None) {
        Ok(result) => {
            let mut generated = Vec::new();
            if result.svg_string().is_some() {
                generated.push("SVG");
            }
            if result.ttf_bytes().is_some() {
                generated.push("TTF");
            }
            if result.eot_bytes().is_some() {
                generated.push("EOT");
            }
            if result.woff_bytes().is_some() {
                generated.push("WOFF");
            }
            if result.woff2_bytes().is_some() {
                generated.push("WOFF2");
            }
            println!("Generated: {}", generated.join(", "));
            ExitCode::SUCCESS
        }
        Err(error) => {
            eprintln!("Error: {error}");
            ExitCode::FAILURE
        }
    }
}