Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new font converter #33

Merged
merged 1 commit into from
Oct 11, 2023
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
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
Loading