Skip to content

Commit

Permalink
Add the ability to list the fonts detected
Browse files Browse the repository at this point in the history
Adds a new CLI flag allowing the user to list the fonts which are
detected on the system.

Keep the pdf conversion as the default commandi. If no command is
provided, an error message will be displayed.

Signed-off-by: Rémy Greinhofer <[email protected]>
  • Loading branch information
rgreinho committed Dec 19, 2023
1 parent 11f34c7 commit 58625e5
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 31 deletions.
2 changes: 1 addition & 1 deletion cli/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))?;
Expand Down
41 changes: 38 additions & 3 deletions cli/src/args.rs
Original file line number Diff line number Diff line change
@@ -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<Command>,

/// 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<PathBuf>,
/// Path to write PDF file to.
pub output: Option<PathBuf>,
/// 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.
Expand All @@ -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,
}
36 changes: 36 additions & 0 deletions cli/src/convert.rs
Original file line number Diff line number Diff line change
@@ -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<PathBuf>,
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(())
}
40 changes: 40 additions & 0 deletions cli/src/fonts.rs
Original file line number Diff line number Diff line change
@@ -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<String, Vec<String>> = 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(())
}
50 changes: 23 additions & 27 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -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() {
Expand All @@ -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(())
}
Expand Down

0 comments on commit 58625e5

Please sign in to comment.