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 {
#[arg(required = true)]
files: Vec<String>,
#[arg(short, long)]
dest: String,
#[arg(short = 'n', long, default_value = "iconfont")]
font_name: String,
#[arg(short, long, value_delimiter = ',')]
types: Option<Vec<String>>,
#[arg(long, default_value_t = true)]
css: bool,
#[arg(long)]
html: bool,
#[arg(long)]
css_template: Option<String>,
#[arg(long)]
html_template: Option<String>,
#[arg(long)]
css_fonts_url: Option<String>,
#[arg(long)]
no_write: bool,
#[arg(long)]
ligature: bool,
#[arg(long)]
font_height: Option<f64>,
#[arg(long)]
ascent: Option<f64>,
#[arg(long)]
descent: Option<f64>,
#[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
}
}
}