Skip to content
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 sbdf_data = eg_font_converter::serialize(bdf_font)?;

let serialized_bdf = eg_bdf::SerializedBdfFont::new(&sbdf_data).unwrap();

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
58 changes: 36 additions & 22 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,43 +40,55 @@ 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])
/// BDF glyph.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BdfGlyph {
/// The corresponding character.
pub character: char,
/// The glyph bounding box.
pub bounding_box: Rectangle,
/// The horizontal distance to the start point of the next glyph.
pub device_width: u32,
/// The bitmap data of the glyph.
pub start_index: usize,
}

impl<'a> BdfGlyph {
fn into_glyph(self, font: &'a BdfFont) -> DisplayBdfGlyph<'a> {
DisplayBdfGlyph {
character: self.character,
bounding_box: self.bounding_box,
device_width: self.device_width,
bitmap_data: &font.data[self.start_index..],
}
}
}

/// 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)
/// Unserialized BDF text style
pub type BdfTextStyle<'a, C> = ProportionalTextStyle<'a, BdfFont<'a>, C>;

/// BDF glyph.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BdfGlyph {
pub struct DisplayBdfGlyph<'a> {
/// The corresponding character.
pub character: char,
/// The glyph bounding box.
pub bounding_box: Rectangle,
/// The horizontal distance to the start point of the next glyph.
pub device_width: u32,
/// The start index in the bitmap data.
pub start_index: usize,
/// The bitmap data of the glyph.
pub bitmap_data: &'a [u8],
}

impl BdfGlyph {
fn draw<D: DrawTarget>(
impl<'a> DisplayBdfGlyph<'a> {
/// Draws a glyph at a certain place and color
pub fn draw<D: DrawTarget>(
&self,
position: Point,
color: D::Color,
data: &[u8],
target: &mut D,
) -> Result<(), D::Error> {
let mut data_iter = RawDataSlice::<RawU1, LittleEndian>::new(data).into_iter();

if self.start_index > 0 {
data_iter.nth(self.start_index - 1);
}
let mut data_iter = RawDataSlice::<RawU1, LittleEndian>::new(self.bitmap_data).into_iter();

self.bounding_box
.translate(position)
Expand Down
158 changes: 158 additions & 0 deletions eg-bdf/src/proportional.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
use crate::{BdfFont, DisplayBdfGlyph};
use embedded_graphics::{
prelude::*,
primitives::Rectangle,
text::{
renderer::{CharacterStyle, TextMetrics, TextRenderer},
Baseline,
},
};

#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)]
pub struct Metrics {
pub ascent: u32,
pub descent: u32,
pub line_height: u32,
}

/// A proportional font
pub trait ProportionalFont<'a>: Clone {
/// Returns a struct containing ascent, descent, baseline_offset, and line_height
fn metrics(&self) -> Metrics;
/// Finds a BdfGlyph for a character
fn lookup(&self, c: char) -> Option<DisplayBdfGlyph<'_>>;
/// Finds the replacement glyph
fn replacement_glyph(&'a self) -> DisplayBdfGlyph<'a>;

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

/// Returns a glyph, or a replacement character if no corresponding glyph exists
fn glyph_or_replacement(&'a self, c: char) -> DisplayBdfGlyph<'a> {
self.lookup(c).unwrap_or(self.replacement_glyph())
}
}

impl<'a> ProportionalFont<'a> for BdfFont<'a> {
fn metrics(&self) -> Metrics {
Metrics {
ascent: self.ascent,
descent: self.descent,
line_height: self.ascent + self.descent,
}
}

fn replacement_glyph(&'a self) -> DisplayBdfGlyph<'a> {
self.glyphs[self.replacement_character].into_glyph(self)
}

fn lookup(&self, c: char) -> Option<DisplayBdfGlyph<'_>> {
if let Some(&g) = self.glyphs.iter().find(|g| g.character == c) {
Some(g.into_glyph(self))
} else {
None
}
}
}

/// 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.glyph_or_replacement(c);

glyph.draw(position, self.color, 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.glyph_or_replacement(c).device_width)
.sum();

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

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

fn line_height(&self) -> u32 {
self.font.metrics().line_height
}
}
Loading