Skip to content

Commit

Permalink
Add new font converter (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
rfuest authored Oct 11, 2023
1 parent a880098 commit ef89a27
Show file tree
Hide file tree
Showing 34 changed files with 2,036 additions and 328 deletions.
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@
members = [
"bdf-parser",
"eg-bdf",
"eg-bdf-macros",
]
"eg-bdf-examples",
"eg-font-converter",
]
resolver = "2"
10 changes: 5 additions & 5 deletions bdf-parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@
name = "bdf-parser"
description = "BDF font parser"
version = "0.1.0"
edition = "2018"
edition = "2021"
authors = ["James Waples <james@wapl.es>", "Ralf Fuest <mail@rfuest.de>"]
repository = "https://github.com/embedded-graphics/bdf"
categories = ["parser-implementations"]
keywords = ["parser", "bdf", "font", "nom"]
license = "MIT OR Apache-2.0"

[dependencies]
bstr = "1.5.0"
bstr = "1.7.0"
nom = "7.1.3"
strum = { version = "0.24.1", features = ["derive"] }
thiserror = "1.0.24"
strum = { version = "0.25.0", features = ["derive"] }
thiserror = "1.0.49"

[dev-dependencies]
indoc = "2.0.1"
indoc = "2.0.4"
81 changes: 73 additions & 8 deletions bdf-parser/src/glyph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use std::convert::TryFrom;
use crate::{helpers::*, BoundingBox, Coord};

/// Glyph.
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Glyph {
/// Name.
pub name: String,
Expand Down Expand Up @@ -60,17 +60,32 @@ impl Glyph {
/// top left corner of the bounding box and don't take the offset into account. Y coordinates
/// increase downwards.
///
/// # Panics
///
/// This method panics if the coordinates are outside the bitmap.
pub fn pixel(&self, x: usize, y: usize) -> bool {
/// Returns `None` if the coordinates are outside the bitmap.
pub fn pixel(&self, x: usize, y: usize) -> Option<bool> {
let width = usize::try_from(self.bounding_box.size.x).unwrap();

if x >= width {
return None;
}

let bytes_per_row = (width + 7) / 8;
let byte_offset = x / 8;
let bit_mask = 0x80 >> (x % 8);

self.bitmap[byte_offset + bytes_per_row * y] & bit_mask != 0
self.bitmap
.get(byte_offset + bytes_per_row * y)
.map(|v| v & bit_mask != 0)
}

/// Returns an iterator over the pixels in the glyph bitmap.
///
/// Iteration starts at the top left corner of the bounding box and ends at the bottom right
/// corner.
pub fn pixels(&self) -> impl Iterator<Item = bool> + '_ {
let width = usize::try_from(self.bounding_box.size.x).unwrap();
let height = usize::try_from(self.bounding_box.size.y).unwrap();

(0..height).flat_map(move |y| (0..width).map(move |x| self.pixel(x, y).unwrap()))
}
}

Expand Down Expand Up @@ -126,6 +141,11 @@ impl Glyphs {
.map_or(None, |i| Some(&self.glyphs[i]))
}

/// Returns `true` if the collection contains the given character.
pub fn contains(&self, c: char) -> bool {
self.get(c).is_some()
}

/// Returns an iterator over all glyphs.
pub fn iter(&self) -> impl Iterator<Item = &Glyph> {
self.glyphs.iter()
Expand Down Expand Up @@ -234,15 +254,15 @@ mod tests {
}

#[test]
fn access_pixels() {
fn pixel_getter() {
let (chardata, _) = test_data();
let (input, glyph) = Glyph::parse(chardata).unwrap();
assert!(input.is_empty());

let bitmap = (0..16)
.map(|y| {
(0..8)
.map(|x| if glyph.pixel(x, y) { '#' } else { ' ' })
.map(|x| if glyph.pixel(x, y).unwrap() { '#' } else { ' ' })
.collect::<String>()
})
.collect::<Vec<_>>();
Expand Down Expand Up @@ -273,6 +293,51 @@ mod tests {
);
}

#[test]
fn pixels_iterator() {
let (chardata, _) = test_data();
let (input, glyph) = Glyph::parse(chardata).unwrap();
assert!(input.is_empty());

let bitmap = glyph
.pixels()
.map(|v| if v { '#' } else { ' ' })
.collect::<String>();

assert_eq!(
bitmap,
concat!(
" ", //
" ", //
" ", //
" ", //
" ## ", //
" # # ", //
" # # ", //
" # # ", //
" # # ", //
" ###### ", //
" # # ", //
" # # ", //
" # # ", //
" # # ", //
" ", //
" ", //
)
);
}

#[test]
fn pixel_getter_outside() {
let (chardata, _) = test_data();
let (input, glyph) = Glyph::parse(chardata).unwrap();
assert!(input.is_empty());

assert_eq!(glyph.pixel(8, 0), None);
assert_eq!(glyph.pixel(0, 16), None);
assert_eq!(glyph.pixel(8, 16), None);
}

#[test]
fn parse_glyph_with_no_encoding() {
let chardata = indoc! {br#"
Expand Down
119 changes: 105 additions & 14 deletions bdf-parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ impl BdfFont {
/// BDF files are expected to be ASCII encoded according to the BDF specification. Any non
/// ASCII characters in strings will be replaced by the `U+FFFD` replacement character.
pub fn parse(input: &[u8]) -> Result<Self, ParserError> {
let input = skip_whitespace(input);
let (input, metadata) = Metadata::parse(input).map_err(|_| ParserError::Metadata)?;
let input = skip_whitespace(input);
let (input, properties) = Properties::parse(input).map_err(|_| ParserError::Properties)?;
Expand Down Expand Up @@ -74,19 +75,66 @@ fn end_of_file(input: &[u8]) -> IResult<&[u8], &[u8]> {
}

/// Bounding box.
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
#[derive(Debug, Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct BoundingBox {
/// Offset to the lower left corner of the bounding box.
pub offset: Coord,

/// Size of the bounding box.
pub size: Coord,
}

/// Offset to the lower left corner of the bounding box.
pub offset: Coord,
impl BoundingBox {
pub(crate) fn parse(input: &[u8]) -> IResult<&[u8], Self> {
map(
separated_pair(Coord::parse, space1, Coord::parse),
|(size, offset)| Self { size, offset },
)(input)
}

fn upper_right(&self) -> Coord {
Coord::new(
self.offset.x + self.size.x - 1,
self.offset.y + self.size.y - 1,
)
}

/// Calculates the smallest bounding box that surrounds two bounding boxes.
///
/// # Panics
///
/// Panics if any bounding box has a negative size.
pub fn union(&self, other: &Self) -> Self {
assert!(self.size.x >= 0);
assert!(self.size.y >= 0);
assert!(other.size.x >= 0);
assert!(other.size.y >= 0);

if other.size.x == 0 || other.size.y == 0 {
*self
} else if self.size.x == 0 || self.size.y == 0 {
*other
} else {
let self_ur = self.upper_right();
let other_ur = other.upper_right();

let x_min = self.offset.x.min(other.offset.x);
let y_min = self.offset.y.min(other.offset.y);
let x_max = self_ur.x.max(other_ur.x);
let y_max = self_ur.y.max(other_ur.y);

Self {
offset: Coord::new(x_min, y_min),
size: Coord::new(x_max - x_min + 1, y_max - y_min + 1),
}
}
}
}

/// Coordinate.
///
/// BDF files use a cartesian coordinate system, where the positive half-axis points upwards.
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
#[derive(Debug, Default, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub struct Coord {
/// X coordinate.
pub x: i32,
Expand All @@ -97,7 +145,7 @@ pub struct Coord {

impl Coord {
/// Creates a new coord.
pub fn new(x: i32, y: i32) -> Self {
pub const fn new(x: i32, y: i32) -> Self {
Self { x, y }
}

Expand All @@ -109,15 +157,6 @@ impl Coord {
}
}

impl BoundingBox {
pub(crate) fn parse(input: &[u8]) -> IResult<&[u8], Self> {
map(
separated_pair(Coord::parse, space1, Coord::parse),
|(size, offset)| Self { size, offset },
)(input)
}
}

/// Parser error.
#[derive(Debug, PartialEq, thiserror::Error)]
pub enum ParserError {
Expand Down Expand Up @@ -151,6 +190,7 @@ mod tests {
STARTPROPERTIES 3
COPYRIGHT "Copyright123"
FONT_ASCENT 1
COMMENT comment
FONT_DESCENT 2
ENDPROPERTIES
STARTCHAR Char 0
Expand Down Expand Up @@ -257,4 +297,55 @@ mod tests {
Err(ParserError::EndOfFile)
);
}

const fn bb(offset_x: i32, offset_y: i32, size_x: i32, size_y: i32) -> BoundingBox {
BoundingBox {
offset: Coord::new(offset_x, offset_y),
size: Coord::new(size_x, size_y),
}
}

#[test]
fn parse_with_leading_whitespace() {
let lines: Vec<_> = std::iter::once("").chain(FONT.lines()).collect();
let input = lines.join("\n");

test_font(&BdfFont::parse(input.as_bytes()).unwrap());
}

#[test]
fn union() {
for ((bb1, bb2), expected_union) in [
// Non overlapping
((bb(0, 0, 4, 5), bb(4, 0, 4, 5)), bb(0, 0, 8, 5)),
((bb(0, 0, 4, 5), bb(5, 0, 4, 5)), bb(0, 0, 9, 5)),
((bb(0, 0, 4, 5), bb(-4, 0, 4, 5)), bb(-4, 0, 8, 5)),
((bb(0, 0, 4, 5), bb(-6, 0, 4, 5)), bb(-6, 0, 10, 5)),
((bb(0, 0, 4, 5), bb(0, 5, 4, 5)), bb(0, 0, 4, 10)),
((bb(0, 0, 4, 5), bb(0, 6, 4, 5)), bb(0, 0, 4, 11)),
((bb(0, 0, 4, 5), bb(0, -5, 4, 5)), bb(0, -5, 4, 10)),
((bb(0, 0, 4, 5), bb(0, -10, 4, 5)), bb(0, -10, 4, 15)),
((bb(1, 2, 3, 4), bb(5, 6, 7, 8)), bb(1, 2, 11, 12)),
// Overlapping
((bb(0, 0, 4, 5), bb(2, 0, 4, 5)), bb(0, 0, 6, 5)),
((bb(0, 0, 4, 5), bb(-3, 0, 4, 5)), bb(-3, 0, 7, 5)),
((bb(0, 0, 4, 5), bb(0, 3, 4, 5)), bb(0, 0, 4, 8)),
((bb(0, 0, 4, 5), bb(0, -2, 4, 5)), bb(0, -2, 4, 7)),
((bb(1, 2, 5, 7), bb(5, 6, 3, 4)), bb(1, 2, 7, 8)),
// Inside
((bb(-1, -2, 3, 5), bb(0, 0, 1, 2)), bb(-1, -2, 3, 5)),
// Zero sized
((bb(0, 0, 0, 0), bb(0, 0, 0, 0)), bb(0, 0, 0, 0)),
((bb(1, 2, 3, 4), bb(0, 0, 0, 0)), bb(1, 2, 3, 4)),
((bb(1, 2, 3, 4), bb(0, 0, 1, 0)), bb(1, 2, 3, 4)),
((bb(1, 2, 3, 4), bb(0, 0, 0, 1)), bb(1, 2, 3, 4)),
((bb(0, 0, 0, 0), bb(1, 2, 3, 4)), bb(1, 2, 3, 4)),
((bb(0, 0, 1, 0), bb(1, 2, 3, 4)), bb(1, 2, 3, 4)),
((bb(0, 0, 0, 1), bb(1, 2, 3, 4)), bb(1, 2, 3, 4)),
]
.into_iter()
{
assert_eq!(bb1.union(&bb2), expected_union, "{bb1:?}, {bb2:?}");
}
}
}
4 changes: 2 additions & 2 deletions bdf-parser/src/properties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::helpers::*;

/// BDF file property.
///
/// Source: https://www.x.org/releases/X11R7.6/doc/xorg-docs/specs/XLFD/xlfd.html
/// Source: <https://www.x.org/releases/X11R7.6/doc/xorg-docs/specs/XLFD/xlfd.html>
#[derive(Debug, PartialEq, Copy, Clone, Eq, PartialOrd, Ord, strum::Display)]
#[strum(serialize_all = "shouty_snake_case")]
pub enum Property {
Expand Down Expand Up @@ -151,7 +151,7 @@ impl Properties {
take_until("ENDPROPERTIES"),
statement("ENDPROPERTIES", eof),
),
many0(property),
many0(skip_comments(property)),
)),
|properties| {
// Convert vector of properties into a HashMap
Expand Down
17 changes: 17 additions & 0 deletions eg-bdf-examples/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "eg-bdf-examples"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
embedded-graphics = "0.8.1"
embedded-graphics-simulator = "0.5.0"
# TODO: add non path dependency
eg-bdf = { path = "../eg-bdf" }
eg-font-converter = { path = "../eg-font-converter" }
anyhow = "1.0.66"

[build-dependencies]
# TODO: add non path dependency
eg-font-converter = { path = "../eg-font-converter" }
Loading

0 comments on commit ef89a27

Please sign in to comment.