Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion eg-font-converter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -448,12 +448,13 @@ impl GlyphMapping for ConvertedFont {
// TODO: assumes unicode
let encoding = Encoding::Standard(c as u32);

// TODO: support replacement character
self.glyphs
.iter()
.enumerate()
.find(|(_, glyph)| glyph.encoding == encoding)
.map(|(index, _)| index)
.unwrap()
.unwrap_or_default()
}
}

Expand Down
6 changes: 6 additions & 0 deletions tools/test-bdf-parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,10 @@ edition = "2018"

[dependencies]
bdf-parser = { path = "../../bdf-parser" }
eg-bdf = { path = "../../eg-bdf" }
eg-font-converter = { path = "../../eg-font-converter" }
owo-colors = "4.2.2"
clap = { version = "4.5.40", features = [ "derive" ] }
anyhow = "1.0.98"
embedded-graphics = "0.8.1"
embedded-graphics-simulator = { version = "0.7.0", default-features = false }
126 changes: 104 additions & 22 deletions tools/test-bdf-parser/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,41 +1,123 @@
use anyhow::Result;
use owo_colors::OwoColorize;
use std::{
ffi::OsStr,
fs, io,
path::{Path, PathBuf},
};

use bdf_parser::Font;
use bdf_parser::{Font, ParserError};

pub fn collect_font_files(dir: &Path) -> io::Result<Vec<PathBuf>> {
let mut files = Vec::new();
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct FontPath {
pub absolute: PathBuf,
pub relative: PathBuf,
}

#[derive(Debug)]
pub struct FontFile {
pub path: FontPath,
pub parsed: Result<Font, ParserError>,
}

#[derive(Debug, Default)]
struct DirWalker {
files: Vec<FontPath>,

prefix: PathBuf,
}

impl DirWalker {
fn new<F: Fn(&Path) -> bool>(path: &Path, filter: F) -> io::Result<Self> {
let mut self_ = Self::default();

self_.walk(path, &filter, true)?;
self_.files.sort();

Ok(self_)
}

fn walk<F: Fn(&Path) -> bool>(
&mut self,
path: &Path,
filter: &F,
root: bool,
) -> io::Result<()> {
let file_name = path.file_name().unwrap();

if path.is_dir() {
let old_prefix = self.prefix.clone();
if !root {
self.prefix.push(file_name);
}

if dir.is_dir() {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
for entry in fs::read_dir(path)? {
let entry = entry?;

if path.is_file() && path.to_string_lossy().ends_with(".bdf") {
files.push(path.to_path_buf());
} else if path.is_dir() {
let sub = collect_font_files(&path).unwrap();
for subfile in sub {
files.push(subfile);
}
self.walk(&entry.path(), filter, false)?;
}

self.prefix = old_prefix;
} else if path.is_file() {
if path.extension() == Some(OsStr::new("bdf")) && filter(path) {
self.files.push(FontPath {
absolute: path.to_path_buf(),
relative: self.prefix.join(file_name),
});
}
} else {
panic!("path is not a dir or file");
}

Ok(())
}
}

pub fn parse_fonts(path: &Path) -> Result<Vec<FontFile>> {
parse_fonts_with_filter(path, |_| true)
}

pub fn parse_fonts_with_filter<F: Fn(&Path) -> bool>(
path: &Path,
filter: F,
) -> Result<Vec<FontFile>> {
let paths = DirWalker::new(path, filter).map(|walker| walker.files)?;

files.sort();
let files = paths
.into_iter()
.map(|path| {
let bdf = std::fs::read(&path.absolute).unwrap();
let str = String::from_utf8_lossy(&bdf);
let parsed = Font::parse(&str);

FontFile { path, parsed }
})
.collect::<Vec<_>>();

Ok(files)
}

pub fn test_font_parse(filepath: &Path) -> Result<(), String> {
let bdf = std::fs::read(filepath).unwrap();
let str = String::from_utf8_lossy(&bdf);
let font = Font::parse(&str);
pub fn print_parser_result(files: &[FontFile]) -> usize {
let mut num_errors = 0;

for font_file in files {
if font_file.parsed.is_err() {
num_errors += 1;
}

match font {
Ok(_font) => Ok(()),
Err(e) => Err(e.to_string()),
print!("{0: <60}", font_file.path.relative.to_string_lossy());
match &font_file.parsed {
Ok(_font) => println!("{}", "OK".green()),
Err(e) => println!("{} {:}", "Error:".red(), e),
}
}

println!(
"\n{} out of {} fonts passed ({} failed)\n",
files.len() - num_errors,
files.len(),
num_errors
);

num_errors
}
150 changes: 122 additions & 28 deletions tools/test-bdf-parser/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,47 +1,141 @@
use clap::Parser;
use std::path::PathBuf;
use eg_bdf::BdfTextStyle;
use eg_font_converter::FontConverter;
use embedded_graphics::{
mono_font::MonoTextStyle,
pixelcolor::Rgb888,
prelude::*,
primitives::{Line, PrimitiveStyle, StyledDrawable},
text::{renderer::TextRenderer, Baseline, Text},
};
use embedded_graphics_simulator::{OutputSettingsBuilder, SimulatorDisplay};
use owo_colors::OwoColorize;
use std::{fs, path::PathBuf};

use test_bdf_parser::*;

#[derive(Parser)]
struct Arguments {
/// Output directory for font specimens in PNG format.
#[arg(long)]
png_out: Option<PathBuf>,

/// Output scale for PNG images.
#[arg(long, default_value = "1")]
png_scale: u32,

/// Path to a BDF file or a directory containing BDF files.
file_or_directory: PathBuf,
}

pub fn main() {
let args: Arguments = Arguments::parse();
fn draw_specimen(style: impl TextRenderer<Color = Rgb888> + Copy) -> SimulatorDisplay<Rgb888> {
let single_line = Text::with_baseline(
"The quick brown fox jumps over the lazy dog.",
Point::zero(),
style,
Baseline::Top,
);

// 10 px minimum line height to ensure output even if metrics are wrong.
let single_line_height = single_line.bounding_box().size.height.max(10);

let display_height = single_line_height * 3;
let display_width = (single_line.bounding_box().size.width + 10).max(display_height);

let text_position = Point::new(5, single_line_height as i32);

if args.file_or_directory.is_dir() {
let fonts =
collect_font_files(&args.file_or_directory).expect("Could not get list of fonts");
let mut display = SimulatorDisplay::<Rgb888>::new(Size::new(display_width, display_height));

let results = fonts.iter().map(|fpath| test_font_parse(fpath));
// Draw baseline grid

let mut num_errors = 0;
for offset in [Point::zero(), Point::new(0, single_line_height as i32)] {
Line::with_delta(
text_position.y_axis() + offset,
Point::new(display_width as i32, 0),
)
.draw_styled(
&PrimitiveStyle::with_stroke(Rgb888::CSS_DARK_SLATE_GRAY, 1),
&mut display,
)
.unwrap();
}

// Draw marker for X start position

Line::with_delta(text_position.x_axis(), Point::new(0, display_height as i32))
.draw_styled(
&PrimitiveStyle::with_stroke(Rgb888::CSS_DARK_SLATE_GRAY, 1),
&mut display,
)
.unwrap();

let text = Text::new(
"The quick brown fox jumps over the lazy dog.\n0123456789",
text_position,
style,
);

for (font, result) in fonts.iter().zip(results) {
if result.is_err() {
num_errors += 1;
}
// Draw bounding box

text.bounding_box()
.draw_styled(
&PrimitiveStyle::with_stroke(Rgb888::CSS_LIGHT_SLATE_GRAY, 1),
&mut display,
)
.unwrap();

text.draw(&mut display).unwrap();

display
}

pub fn main() {
let args: Arguments = Arguments::parse();

let fonts = parse_fonts(&args.file_or_directory).expect("Could not parse fonts");
let num_errors = print_parser_result(&fonts);

let output_settings = OutputSettingsBuilder::new().scale(args.png_scale).build();

if let Some(png_directory) = args.png_out {
for file in fonts.iter().filter(|file| file.parsed.is_ok()) {
println!(
"{0: <60} {1:?}",
font.file_name().unwrap().to_str().unwrap(),
result
"Generating specimen: {}",
file.path.relative.to_string_lossy()
);
}

println!(
"\n{} out of {} fonts passed ({} failed)\n",
(fonts.len() - num_errors),
fonts.len(),
num_errors
);

assert_eq!(num_errors, 0, "Not all font files parsed successfully");
} else if args.file_or_directory.is_file() {
test_font_parse(&args.file_or_directory).unwrap();
} else {
panic!("Invalid path: {:?}", args.file_or_directory);
let output_file = png_directory.join(&file.path.relative);
let output_dir = output_file.parent().unwrap();

fs::create_dir_all(output_dir).unwrap();

let converter = FontConverter::with_file(&file.path.absolute, "FONT");

match converter.convert_eg_bdf() {
Ok(converted_bdf) => {
let bdf_specimen =
draw_specimen(BdfTextStyle::new(&converted_bdf.as_font(), Rgb888::WHITE));
bdf_specimen
.to_rgb_output_image(&output_settings)
.save_png(output_file.with_extension("bdf.png"))
.unwrap();
}
Err(e) => println!("{} {e}", "Error (eg-bdf):".red()),
};

match converter.convert_mono_font() {
Ok(converted_mono) => {
let mono_specimen =
draw_specimen(MonoTextStyle::new(&converted_mono.as_font(), Rgb888::WHITE));
mono_specimen
.to_rgb_output_image(&output_settings)
.save_png(output_file.with_extension("mono.png"))
.unwrap();
}
Err(e) => println!("{} {e}", "Error (mono):".red()),
};
}
}

assert_eq!(num_errors, 0, "Not all font files parsed successfully");
}
26 changes: 2 additions & 24 deletions tools/test-bdf-parser/tests/tecate_suite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,8 @@ fn it_parses_all_tecate_fonts() {
.canonicalize()
.unwrap();

let fonts = collect_font_files(&fontdir).expect("Could not get list of fonts");

let results = fonts.iter().map(|fpath| test_font_parse(fpath));

let mut num_errors = 0;

for (font, result) in fonts.iter().zip(results) {
if result.is_err() {
num_errors += 1;
}

println!(
"{0: <60} {1:?}",
font.file_name().unwrap().to_str().unwrap(),
result
);
}

println!(
"\n{} out of {} fonts passed ({} failed)\n",
(fonts.len() - num_errors),
fonts.len(),
num_errors
);
let fonts = parse_fonts(&fontdir).expect("Could not parse fonts");
let num_errors = print_parser_result(&fonts);

assert_eq!(num_errors, 0, "Not all font files parsed successfully");
}
Loading