Skip to content
Merged
12 changes: 11 additions & 1 deletion eg-bdf-examples/examples/font_viewer.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use anyhow::{anyhow, Context, Result};
use eg_bdf::BdfTextStyle;
use eg_bdf::{BdfTextStyle, SerializedBdfTextStyle};
use eg_font_converter::{FontConverter, Mapping};
use embedded_graphics::{
geometry::AnchorPoint,
Expand Down Expand Up @@ -114,6 +114,10 @@ fn try_main() -> Result<()> {
.with_context(|| "couldn't convert font")?;
let bdf_font = bdf_output.as_font();

let serialized_bdf = eg_bdf::SerializedBdfFont {
data: &eg_font_converter::serialize(bdf_font),
};

let mono_output = converter
.convert_mono_font()
.with_context(|| "couldn't convert font")?;
Expand All @@ -138,6 +142,7 @@ fn try_main() -> Result<()> {
let mut window = Window::new("Font viewer", &settings);

let mut use_mono_font = false;
let use_serialized_font = true;

'main_loop: loop {
window.update(&display);
Expand All @@ -163,6 +168,11 @@ fn try_main() -> Result<()> {
draw(&mut display, style, line_height);

hint.insert_str(0, "Mono | ");
} else if use_serialized_font {
let style = SerializedBdfTextStyle::new(&serialized_bdf, Rgb888::WHITE);
draw(&mut display, style, line_height);

hint.insert_str(0, "SerializedBdf | ");
} else {
let style = BdfTextStyle::new(&bdf_font, Rgb888::WHITE);
draw(&mut display, style, line_height);
Expand Down
17 changes: 6 additions & 11 deletions eg-bdf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ use embedded_graphics::{
primitives::Rectangle,
};

mod text;
pub use text::BdfTextStyle;
mod proportional;
mod serialized;
pub use proportional::{ProportionalFont, ProportionalTextStyle};
pub use serialized::{SerializedBdfFont, SerializedBdfTextStyle};

/// BDF font.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
Expand All @@ -38,15 +40,8 @@ pub struct BdfFont<'a> {
pub data: &'a [u8],
}

impl<'a> BdfFont<'a> {
fn get_glyph(&self, c: char) -> &'a BdfGlyph {
self.glyphs
.iter()
.find(|g| g.character == c)
// TODO: don't panic if replacement_character is invalid
.unwrap_or_else(|| &self.glyphs[self.replacement_character])
}
}
/// Unserialized BDF text style
pub type BdfTextStyle<'a, C> = ProportionalTextStyle<'a, BdfFont<'a>, C>;

/// BDF glyph information.
// TODO: store more efficiently (e.g. use smaller integer types if possible, store as struct of arrays instead of array of structs)
Expand Down
175 changes: 175 additions & 0 deletions eg-bdf/src/proportional.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
use crate::{BdfFont, BdfGlyph};
use embedded_graphics::{
prelude::*,
primitives::Rectangle,
text::{
renderer::{CharacterStyle, TextMetrics, TextRenderer},
Baseline,
},
};

/// A proportional font
pub trait ProportionalFont<'a>: Clone {
/// Returns the global font ascent
fn ascent(&self) -> u16;

/// Returns the global font descent
fn descent(&self) -> u16;

/// Returns index of the replacement character
fn replacement(&self) -> u32;

/// Data is indexed from the start of the data block, not the start of the font
///
/// Example: `glyph.draw(position, self.color, &self.font.data[self.font.data_offset()..], target)?;`
fn data_offset(&self) -> usize;

/// Returns a slice of the binary bitmap data
fn data(&self) -> &'a [u8];

/// Finds the BDF glyph corresponding to a character
fn lookup(&self, c: char) -> BdfGlyph;

/// Returns the baseline offset
fn baseline_offset(&self, baseline: Baseline) -> i32 {
match baseline {
Baseline::Top => self.ascent().saturating_sub(1) as i32,
Baseline::Bottom => -(self.descent() as i32),
Baseline::Middle => (self.ascent() as i32 - self.descent() as i32) / 2,
Baseline::Alphabetic => 0,
}
}

/// Returns the default line height
fn line_height(&self) -> u32 {
(self.ascent() + self.descent()) as u32
}
}
Comment thread
iriswebb marked this conversation as resolved.

impl<'a> ProportionalFont<'a> for BdfFont<'a> {
fn ascent(&self) -> u16 {
self.ascent as u16
}

fn descent(&self) -> u16 {
self.descent as u16
}

fn replacement(&self) -> u32 {
self.replacement_character as u32
}

fn data_offset(&self) -> usize {
0
}

fn data(&self) -> &'a [u8] {
self.data
}

fn lookup(&self, c: char) -> BdfGlyph {
*self
.glyphs
.iter()
.find(|g| g.character == c)
// TODO: don't panic if replacement_character is invalid
.unwrap_or_else(|| &self.glyphs[self.replacement_character])
}
}

/// A generalized text style for proportional fonts
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ProportionalTextStyle<'a, F: ProportionalFont<'a>, C: PixelColor> {
font: &'a F,
color: C,
}

impl<'a, F: ProportionalFont<'a>, C: PixelColor> ProportionalTextStyle<'a, F, C> {
/// Creates a new text style
pub fn new(font: &'a F, color: C) -> Self {
Self { font, color }
}
}

impl<'a, C: PixelColor, F: ProportionalFont<'a>> CharacterStyle
for ProportionalTextStyle<'a, F, C>
{
type Color = C;

fn set_text_color(&mut self, text_color: Option<C>) {
// TODO: support transparent text
if let Some(color) = text_color {
self.color = color;
}
}

// TODO: implement additional methods
}

impl<'a, C: PixelColor, F: ProportionalFont<'a>> TextRenderer for ProportionalTextStyle<'a, F, C> {
type Color = C;

fn draw_string<D>(
&self,
text: &str,
position: Point,
baseline: Baseline,
target: &mut D,
) -> Result<Point, D::Error>
where
D: DrawTarget<Color = Self::Color>,
{
let mut position = position + Point::new(0, self.font.baseline_offset(baseline));

for c in text.chars() {
let glyph = self.font.lookup(c);

glyph.draw(
position,
self.color,
&self.font.data()[self.font.data_offset()..],
target,
)?;

position.x += glyph.device_width as i32;
}

Ok(position)
}

fn draw_whitespace<D>(
&self,
width: u32,
position: Point,
baseline: Baseline,
_target: &mut D,
) -> Result<Point, D::Error>
where
D: DrawTarget<Color = Self::Color>,
{
let position = position + Point::new(0, self.font.baseline_offset(baseline));

Ok(position + Size::new(width, 0))
}

fn measure_string(&self, text: &str, position: Point, baseline: Baseline) -> TextMetrics {
let position = position + Point::new(0, self.font.baseline_offset(baseline));

let dx = text.chars().map(|c| self.font.lookup(c).device_width).sum();

// TODO: calculate correct bounding box
let bounding_box = Rectangle::new(
position - Size::new(0, self.font.ascent().saturating_sub(1) as u32),
Size::new(dx, self.line_height()),
);

TextMetrics {
bounding_box,
next_position: position + Size::new(dx, 0),
}
}

fn line_height(&self) -> u32 {
self.font.line_height()
}
}
106 changes: 106 additions & 0 deletions eg-bdf/src/serialized.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use crate::{BdfGlyph, ProportionalFont, ProportionalTextStyle};
use embedded_graphics::{
prelude::*,
primitives::Rectangle,
};

/// * Header (12 Bytes):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case this format is also used to load a font at runtime it would be a good idea to add some validation that it is a valid font file. A magic value, a version, and the file size should be enough.

/// - Ascent (pixels, u16 BE)
/// - Descent (pixels, u16 BE)
/// - Replacement Character (index into character table, u32 BE)
/// - Character Table Length (entries, u32 BE)
///
/// * Glyph Table (17 Bytes Per Entry):
/// - corresponding codepoint (u32 BE)
/// - top_left.x (i16 BE)
/// - top_left.y (i16 BE)
/// - size.width (u16 BE)
/// - size.height (u16 BE)
/// - device_width (pixels, u8)
/// - data index (bytes from start of data, u32 BE)
///
/// Font bitmap data is stored afterwards
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SerializedBdfFont<'a> {
/// The raw u8 data of the serialized font
pub data: &'a [u8],
}
impl SerializedBdfFont<'_> {
/// Returns the length of the glyph table
pub const fn character_count(self) -> u32 {
u32::from_be_bytes([self.data[8], self.data[9], self.data[10], self.data[11]])
}

/// Returns a BdfGlyph in the glyph table
pub const fn character_table(self, idx: u32) -> BdfGlyph {
let offset = 12 + (idx * 17) as usize;
let corresponding_character = char::from_u32(u32::from_be_bytes([
self.data[offset],
self.data[offset + 1],
self.data[offset + 2],
self.data[offset + 3],
]));
let top_left_x = i16::from_be_bytes([self.data[offset + 4], self.data[offset + 5]]);
let top_left_y = i16::from_be_bytes([self.data[offset + 6], self.data[offset + 7]]);
let width = u16::from_be_bytes([self.data[offset + 8], self.data[offset + 9]]);
let height = u16::from_be_bytes([self.data[offset + 10], self.data[offset + 11]]);
let kerning = self.data[offset + 12];
let data_index = u32::from_be_bytes([
self.data[offset + 13],
self.data[offset + 14],
self.data[offset + 15],
self.data[offset + 16],
]);

BdfGlyph {
character: corresponding_character.unwrap(),
bounding_box: Rectangle {
top_left: Point {
x: top_left_x as i32,
y: top_left_y as i32,
},
size: Size {
width: width as u32,
height: height as u32,
},
},
device_width: kerning as u32,
start_index: data_index as usize,
}
}
}
impl<'a> ProportionalFont<'a> for SerializedBdfFont<'a> {
fn ascent(&self) -> u16 {
u16::from_be_bytes([self.data[0], self.data[1]])
}

fn descent(&self) -> u16 {
u16::from_be_bytes([self.data[2], self.data[3]])
}

fn replacement(&self) -> u32 {
u32::from_be_bytes([self.data[4], self.data[5], self.data[6], self.data[7]])
}

fn data_offset(&self) -> usize {
(12 + (self.character_count() * 17)) as usize
}

fn data(&self) -> &'a [u8] {
self.data
}

fn lookup(&self, c: char) -> BdfGlyph {
for i in 0..self.character_count() {
let tested_character = self.character_table(i);
if self.character_table(i).character == c {
return tested_character;
}
}

self.character_table(self.replacement())
}
}

/// Stylized serialized BDF text
pub type SerializedBdfTextStyle<'a, C> = ProportionalTextStyle<'a, SerializedBdfFont<'a>, C>;
Loading