Skip to content

Commit 8d4af3b

Browse files
authored
Add PNG specimen output to teset-bdf-parser (#46)
1 parent 76ab0e3 commit 8d4af3b

6 files changed

Lines changed: 247 additions & 106 deletions

File tree

eg-font-converter/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -448,12 +448,13 @@ impl GlyphMapping for ConvertedFont {
448448
// TODO: assumes unicode
449449
let encoding = Encoding::Standard(c as u32);
450450

451+
// TODO: support replacement character
451452
self.glyphs
452453
.iter()
453454
.enumerate()
454455
.find(|(_, glyph)| glyph.encoding == encoding)
455456
.map(|(index, _)| index)
456-
.unwrap()
457+
.unwrap_or_default()
457458
}
458459
}
459460

tools/test-bdf-parser/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,10 @@ edition = "2018"
66

77
[dependencies]
88
bdf-parser = { path = "../../bdf-parser" }
9+
eg-bdf = { path = "../../eg-bdf" }
10+
eg-font-converter = { path = "../../eg-font-converter" }
11+
owo-colors = "4.2.2"
912
clap = { version = "4.5.40", features = [ "derive" ] }
13+
anyhow = "1.0.98"
14+
embedded-graphics = "0.8.1"
15+
embedded-graphics-simulator = { version = "0.7.0", default-features = false }

tools/test-bdf-parser/src/lib.rs

Lines changed: 104 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,123 @@
1+
use anyhow::Result;
2+
use owo_colors::OwoColorize;
13
use std::{
4+
ffi::OsStr,
25
fs, io,
36
path::{Path, PathBuf},
47
};
58

6-
use bdf_parser::Font;
9+
use bdf_parser::{Font, ParserError};
710

8-
pub fn collect_font_files(dir: &Path) -> io::Result<Vec<PathBuf>> {
9-
let mut files = Vec::new();
11+
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
12+
pub struct FontPath {
13+
pub absolute: PathBuf,
14+
pub relative: PathBuf,
15+
}
16+
17+
#[derive(Debug)]
18+
pub struct FontFile {
19+
pub path: FontPath,
20+
pub parsed: Result<Font, ParserError>,
21+
}
22+
23+
#[derive(Debug, Default)]
24+
struct DirWalker {
25+
files: Vec<FontPath>,
26+
27+
prefix: PathBuf,
28+
}
29+
30+
impl DirWalker {
31+
fn new<F: Fn(&Path) -> bool>(path: &Path, filter: F) -> io::Result<Self> {
32+
let mut self_ = Self::default();
33+
34+
self_.walk(path, &filter, true)?;
35+
self_.files.sort();
36+
37+
Ok(self_)
38+
}
39+
40+
fn walk<F: Fn(&Path) -> bool>(
41+
&mut self,
42+
path: &Path,
43+
filter: &F,
44+
root: bool,
45+
) -> io::Result<()> {
46+
let file_name = path.file_name().unwrap();
47+
48+
if path.is_dir() {
49+
let old_prefix = self.prefix.clone();
50+
if !root {
51+
self.prefix.push(file_name);
52+
}
1053

11-
if dir.is_dir() {
12-
for entry in fs::read_dir(dir)? {
13-
let entry = entry?;
14-
let path = entry.path();
54+
for entry in fs::read_dir(path)? {
55+
let entry = entry?;
1556

16-
if path.is_file() && path.to_string_lossy().ends_with(".bdf") {
17-
files.push(path.to_path_buf());
18-
} else if path.is_dir() {
19-
let sub = collect_font_files(&path).unwrap();
20-
for subfile in sub {
21-
files.push(subfile);
22-
}
57+
self.walk(&entry.path(), filter, false)?;
2358
}
59+
60+
self.prefix = old_prefix;
61+
} else if path.is_file() {
62+
if path.extension() == Some(OsStr::new("bdf")) && filter(path) {
63+
self.files.push(FontPath {
64+
absolute: path.to_path_buf(),
65+
relative: self.prefix.join(file_name),
66+
});
67+
}
68+
} else {
69+
panic!("path is not a dir or file");
2470
}
71+
72+
Ok(())
2573
}
74+
}
75+
76+
pub fn parse_fonts(path: &Path) -> Result<Vec<FontFile>> {
77+
parse_fonts_with_filter(path, |_| true)
78+
}
79+
80+
pub fn parse_fonts_with_filter<F: Fn(&Path) -> bool>(
81+
path: &Path,
82+
filter: F,
83+
) -> Result<Vec<FontFile>> {
84+
let paths = DirWalker::new(path, filter).map(|walker| walker.files)?;
2685

27-
files.sort();
86+
let files = paths
87+
.into_iter()
88+
.map(|path| {
89+
let bdf = std::fs::read(&path.absolute).unwrap();
90+
let str = String::from_utf8_lossy(&bdf);
91+
let parsed = Font::parse(&str);
92+
93+
FontFile { path, parsed }
94+
})
95+
.collect::<Vec<_>>();
2896

2997
Ok(files)
3098
}
3199

32-
pub fn test_font_parse(filepath: &Path) -> Result<(), String> {
33-
let bdf = std::fs::read(filepath).unwrap();
34-
let str = String::from_utf8_lossy(&bdf);
35-
let font = Font::parse(&str);
100+
pub fn print_parser_result(files: &[FontFile]) -> usize {
101+
let mut num_errors = 0;
102+
103+
for font_file in files {
104+
if font_file.parsed.is_err() {
105+
num_errors += 1;
106+
}
36107

37-
match font {
38-
Ok(_font) => Ok(()),
39-
Err(e) => Err(e.to_string()),
108+
print!("{0: <60}", font_file.path.relative.to_string_lossy());
109+
match &font_file.parsed {
110+
Ok(_font) => println!("{}", "OK".green()),
111+
Err(e) => println!("{} {:}", "Error:".red(), e),
112+
}
40113
}
114+
115+
println!(
116+
"\n{} out of {} fonts passed ({} failed)\n",
117+
files.len() - num_errors,
118+
files.len(),
119+
num_errors
120+
);
121+
122+
num_errors
41123
}

tools/test-bdf-parser/src/main.rs

Lines changed: 122 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,141 @@
11
use clap::Parser;
2-
use std::path::PathBuf;
2+
use eg_bdf::BdfTextStyle;
3+
use eg_font_converter::FontConverter;
4+
use embedded_graphics::{
5+
mono_font::MonoTextStyle,
6+
pixelcolor::Rgb888,
7+
prelude::*,
8+
primitives::{Line, PrimitiveStyle, StyledDrawable},
9+
text::{renderer::TextRenderer, Baseline, Text},
10+
};
11+
use embedded_graphics_simulator::{OutputSettingsBuilder, SimulatorDisplay};
12+
use owo_colors::OwoColorize;
13+
use std::{fs, path::PathBuf};
314

415
use test_bdf_parser::*;
516

617
#[derive(Parser)]
718
struct Arguments {
19+
/// Output directory for font specimens in PNG format.
20+
#[arg(long)]
21+
png_out: Option<PathBuf>,
22+
23+
/// Output scale for PNG images.
24+
#[arg(long, default_value = "1")]
25+
png_scale: u32,
26+
27+
/// Path to a BDF file or a directory containing BDF files.
828
file_or_directory: PathBuf,
929
}
1030

11-
pub fn main() {
12-
let args: Arguments = Arguments::parse();
31+
fn draw_specimen(style: impl TextRenderer<Color = Rgb888> + Copy) -> SimulatorDisplay<Rgb888> {
32+
let single_line = Text::with_baseline(
33+
"The quick brown fox jumps over the lazy dog.",
34+
Point::zero(),
35+
style,
36+
Baseline::Top,
37+
);
38+
39+
// 10 px minimum line height to ensure output even if metrics are wrong.
40+
let single_line_height = single_line.bounding_box().size.height.max(10);
41+
42+
let display_height = single_line_height * 3;
43+
let display_width = (single_line.bounding_box().size.width + 10).max(display_height);
44+
45+
let text_position = Point::new(5, single_line_height as i32);
1346

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

18-
let results = fonts.iter().map(|fpath| test_font_parse(fpath));
49+
// Draw baseline grid
1950

20-
let mut num_errors = 0;
51+
for offset in [Point::zero(), Point::new(0, single_line_height as i32)] {
52+
Line::with_delta(
53+
text_position.y_axis() + offset,
54+
Point::new(display_width as i32, 0),
55+
)
56+
.draw_styled(
57+
&PrimitiveStyle::with_stroke(Rgb888::CSS_DARK_SLATE_GRAY, 1),
58+
&mut display,
59+
)
60+
.unwrap();
61+
}
62+
63+
// Draw marker for X start position
64+
65+
Line::with_delta(text_position.x_axis(), Point::new(0, display_height as i32))
66+
.draw_styled(
67+
&PrimitiveStyle::with_stroke(Rgb888::CSS_DARK_SLATE_GRAY, 1),
68+
&mut display,
69+
)
70+
.unwrap();
71+
72+
let text = Text::new(
73+
"The quick brown fox jumps over the lazy dog.\n0123456789",
74+
text_position,
75+
style,
76+
);
2177

22-
for (font, result) in fonts.iter().zip(results) {
23-
if result.is_err() {
24-
num_errors += 1;
25-
}
78+
// Draw bounding box
2679

80+
text.bounding_box()
81+
.draw_styled(
82+
&PrimitiveStyle::with_stroke(Rgb888::CSS_LIGHT_SLATE_GRAY, 1),
83+
&mut display,
84+
)
85+
.unwrap();
86+
87+
text.draw(&mut display).unwrap();
88+
89+
display
90+
}
91+
92+
pub fn main() {
93+
let args: Arguments = Arguments::parse();
94+
95+
let fonts = parse_fonts(&args.file_or_directory).expect("Could not parse fonts");
96+
let num_errors = print_parser_result(&fonts);
97+
98+
let output_settings = OutputSettingsBuilder::new().scale(args.png_scale).build();
99+
100+
if let Some(png_directory) = args.png_out {
101+
for file in fonts.iter().filter(|file| file.parsed.is_ok()) {
27102
println!(
28-
"{0: <60} {1:?}",
29-
font.file_name().unwrap().to_str().unwrap(),
30-
result
103+
"Generating specimen: {}",
104+
file.path.relative.to_string_lossy()
31105
);
32-
}
33106

34-
println!(
35-
"\n{} out of {} fonts passed ({} failed)\n",
36-
(fonts.len() - num_errors),
37-
fonts.len(),
38-
num_errors
39-
);
40-
41-
assert_eq!(num_errors, 0, "Not all font files parsed successfully");
42-
} else if args.file_or_directory.is_file() {
43-
test_font_parse(&args.file_or_directory).unwrap();
44-
} else {
45-
panic!("Invalid path: {:?}", args.file_or_directory);
107+
let output_file = png_directory.join(&file.path.relative);
108+
let output_dir = output_file.parent().unwrap();
109+
110+
fs::create_dir_all(output_dir).unwrap();
111+
112+
let converter = FontConverter::with_file(&file.path.absolute, "FONT");
113+
114+
match converter.convert_eg_bdf() {
115+
Ok(converted_bdf) => {
116+
let bdf_specimen =
117+
draw_specimen(BdfTextStyle::new(&converted_bdf.as_font(), Rgb888::WHITE));
118+
bdf_specimen
119+
.to_rgb_output_image(&output_settings)
120+
.save_png(output_file.with_extension("bdf.png"))
121+
.unwrap();
122+
}
123+
Err(e) => println!("{} {e}", "Error (eg-bdf):".red()),
124+
};
125+
126+
match converter.convert_mono_font() {
127+
Ok(converted_mono) => {
128+
let mono_specimen =
129+
draw_specimen(MonoTextStyle::new(&converted_mono.as_font(), Rgb888::WHITE));
130+
mono_specimen
131+
.to_rgb_output_image(&output_settings)
132+
.save_png(output_file.with_extension("mono.png"))
133+
.unwrap();
134+
}
135+
Err(e) => println!("{} {e}", "Error (mono):".red()),
136+
};
137+
}
46138
}
139+
140+
assert_eq!(num_errors, 0, "Not all font files parsed successfully");
47141
}

tools/test-bdf-parser/tests/tecate_suite.rs

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,8 @@ fn it_parses_all_tecate_fonts() {
88
.canonicalize()
99
.unwrap();
1010

11-
let fonts = collect_font_files(&fontdir).expect("Could not get list of fonts");
12-
13-
let results = fonts.iter().map(|fpath| test_font_parse(fpath));
14-
15-
let mut num_errors = 0;
16-
17-
for (font, result) in fonts.iter().zip(results) {
18-
if result.is_err() {
19-
num_errors += 1;
20-
}
21-
22-
println!(
23-
"{0: <60} {1:?}",
24-
font.file_name().unwrap().to_str().unwrap(),
25-
result
26-
);
27-
}
28-
29-
println!(
30-
"\n{} out of {} fonts passed ({} failed)\n",
31-
(fonts.len() - num_errors),
32-
fonts.len(),
33-
num_errors
34-
);
11+
let fonts = parse_fonts(&fontdir).expect("Could not parse fonts");
12+
let num_errors = print_parser_result(&fonts);
3513

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

0 commit comments

Comments
 (0)