diff --git a/cli/build.rs b/cli/build.rs index 7ba59d35..95ef75b2 100644 --- a/cli/build.rs +++ b/cli/build.rs @@ -18,7 +18,7 @@ fn main() -> Result<(), std::io::Error> { let artifacts_path = outdir_path.join("artifacts"); std::fs::create_dir_all(&artifacts_path)?; - let mut cmd = args::Args::command(); + let mut cmd = args::CliArguments::command(); let man = clap_mangen::Man::new(cmd.clone()); let mut manpage_file = std::fs::File::create(artifacts_path.join("svg2pdf.1"))?; diff --git a/cli/src/args.rs b/cli/src/args.rs index 905997c8..e5ade22e 100644 --- a/cli/src/args.rs +++ b/cli/src/args.rs @@ -1,10 +1,37 @@ +use clap::{ArgAction, Parser, Subcommand}; use std::path::PathBuf; -use clap::Parser; - #[derive(Debug, Parser)] #[clap(about, version)] -pub struct Args { +pub struct CliArguments { + /// The command to run + #[command(subcommand)] + pub command: Option, + + /// Sets the level of logging verbosity: + /// -v = warning & error, -vv = info, -vvv = debug, -vvvv = trace + #[clap(short, long, action = ArgAction::Count)] + pub verbosity: u8, + /// Path to read SVG file from. + pub input: Option, + /// Path to write PDF file to. + pub output: Option, + /// The number of SVG pixels per PDF points. + #[clap(long, default_value = "72.0")] + pub dpi: f32, +} + +// What to do. +#[derive(Debug, Clone, Subcommand)] +#[command()] +pub enum Command { + /// Lists all discovered fonts in system + Fonts(FontsCommand), +} + +/// Lists all discovered fonts in system. +#[derive(Debug, Clone, Parser)] +pub struct ConvertCommand { /// Path to read SVG file from. pub input: PathBuf, /// Path to write PDF file to. @@ -13,3 +40,11 @@ pub struct Args { #[clap(long, default_value = "72.0")] pub dpi: f32, } + +/// Lists all discovered fonts in system. +#[derive(Debug, Clone, Parser)] +pub struct FontsCommand { + /// Also lists style variants of each font family + #[arg(long)] + pub all: bool, +} diff --git a/cli/src/convert.rs b/cli/src/convert.rs new file mode 100644 index 00000000..24889899 --- /dev/null +++ b/cli/src/convert.rs @@ -0,0 +1,36 @@ +use crate::args::ConvertCommand; +use std::path::{Path, PathBuf}; +use svg2pdf::Options; +use usvg::{TreeParsing, TreeTextToPath}; + +/// Execute a font listing command. +pub fn _convert(command: ConvertCommand) -> Result<(), String> { + convert_(&command.input, command.output, command.dpi) +} + +pub fn convert_( + input: &PathBuf, + output: Option, + dpi: f32, +) -> Result<(), String> { + // Prepare the font database. + let mut fontdb = fontdb::Database::new(); + fontdb.load_system_fonts(); + + // Convert the file. + let name = Path::new(input.file_name().ok_or("Input path does not point to a file")?); + let output = output.unwrap_or_else(|| name.with_extension("pdf")); + + let svg = std::fs::read_to_string(input).map_err(|_| "Failed to load SVG file")?; + + let options = usvg::Options::default(); + + let mut tree = usvg::Tree::from_str(&svg, &options).map_err(|err| err.to_string())?; + tree.convert_text(&fontdb); + + let pdf = svg2pdf::convert_tree(&tree, Options { dpi, ..Options::default() }); + + std::fs::write(output, pdf).map_err(|_| "Failed to write PDF file")?; + + Ok(()) +} diff --git a/cli/src/fonts.rs b/cli/src/fonts.rs new file mode 100644 index 00000000..a51da44f --- /dev/null +++ b/cli/src/fonts.rs @@ -0,0 +1,40 @@ +use crate::args::FontsCommand; +use std::collections::BTreeMap; + +/// Execute a font listing command. +pub fn fonts(command: &FontsCommand) -> Result<(), String> { + // Prepare the font database. + let mut fontdb = fontdb::Database::new(); + fontdb.load_system_fonts(); + + // Collect the font famillies. + let mut font_families: BTreeMap> = BTreeMap::new(); + for face in fontdb.faces() { + for family in &face.families { + font_families + .entry(family.0.clone()) + .and_modify(|value| value.push(face.post_script_name.clone())) + .or_insert(vec![face.post_script_name.clone()]); + } + } + + // Display the results. + for (family, mut names) in font_families { + names.sort(); + let mut name_string = String::new(); + name_string.push_str(&family); + if command.all { + for (idx, name) in names.iter().enumerate() { + if idx == (names.len() - 1) { + name_string.push_str("\n└ ") + } else { + name_string.push_str("\n├ ") + } + name_string.push_str(name); + } + } + println!("{name_string}"); + } + + Ok(()) +} diff --git a/cli/src/main.rs b/cli/src/main.rs index 722ef72d..454fb431 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,13 +1,14 @@ -use std::io::{self, Write}; -use std::path::Path; -use std::process; +mod args; +mod convert; +mod fonts; +use crate::args::{CliArguments, Command}; use clap::Parser; -use svg2pdf::Options; +use std::{ + io::{self, Write}, + process, +}; use termcolor::{ColorChoice, ColorSpec, StandardStream, WriteColor}; -use usvg::{TreeParsing, TreeTextToPath}; - -mod args; fn main() { if let Err(msg) = run() { @@ -17,26 +18,21 @@ fn main() { } fn run() -> Result<(), String> { - let args = args::Args::parse(); - - let name = - Path::new(args.input.file_name().ok_or("Input path does not point to a file")?); - let output = args.output.unwrap_or_else(|| name.with_extension("pdf")); - - let svg = - std::fs::read_to_string(&args.input).map_err(|_| "Failed to load SVG file")?; - - let options = usvg::Options::default(); - let mut fontdb = fontdb::Database::new(); - fontdb.load_system_fonts(); - - let mut tree = usvg::Tree::from_str(&svg, &options).map_err(|err| err.to_string())?; - tree.convert_text(&fontdb); - - let pdf = - svg2pdf::convert_tree(&tree, Options { dpi: args.dpi, ..Options::default() }); - - std::fs::write(output, pdf).map_err(|_| "Failed to write PDF file")?; + let args = CliArguments::parse(); + + // If an input argument was provided, convert the svg file to pdf. + if let Some(input) = args.input { + return convert::convert_(&input, args.output, args.dpi); + }; + + // Otherwise execute the command provided if any. + if let Some(command) = args.command { + match command { + Command::Fonts(command) => crate::fonts::fonts(&command)?, + } + } else { + return Err("no command was provided".to_string()); + }; Ok(()) }