Skip to content
Open
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
51 changes: 45 additions & 6 deletions eg-bdf-examples/examples/font_viewer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use eg_font_converter::{FontConverter, Mapping};
use embedded_graphics::{
geometry::AnchorPoint,
mono_font::{ascii::FONT_6X10, MonoTextStyle},
pixelcolor::Rgb888,
pixelcolor::{Gray8, Rgb888},
prelude::*,
primitives::{Line, PrimitiveStyle},
text::{renderer::TextRenderer, Alignment, Baseline, Text, TextStyleBuilder},
Expand All @@ -19,6 +19,40 @@ fn main() {
}
}

fn draw_character_bounding_boxes<S: TextRenderer<Color = Rgb888>>(
display: &mut SimulatorDisplay<Rgb888>,
text: &Text<S>,
) where
S: Clone,
{
let mut dummy_display = display.clone();

let mut p = text.position;
let mut toggle = false;

for c in text.text.chars().map(|c| c.to_string()) {
if c == "\n" {
p.x = text.position.x;
}

let t = Text {
text: &c,
position: p,
..text.clone()
};

let fill_color = Gray8::new(if toggle { 30 } else { 50 }).into();

t.bounding_box()
.into_styled(PrimitiveStyle::with_fill(fill_color))
.draw(display)
.unwrap();

p = t.draw(&mut dummy_display).unwrap();
toggle = !toggle;
}
}

/// Draws a text and its bounding box.
fn draw_text<S: TextRenderer<Color = Rgb888>>(
display: &mut SimulatorDisplay<Rgb888>,
Expand All @@ -36,6 +70,7 @@ fn draw<S: TextRenderer<Color = Rgb888> + Copy>(
display: &mut SimulatorDisplay<Rgb888>,
style: S,
line_height: u32,
highlight_characters: bool,
) {
let abc = ('a'..='z').collect::<String>();
let digits = ('0'..='9').collect::<String>();
Expand All @@ -54,6 +89,9 @@ fn draw<S: TextRenderer<Color = Rgb888> + Copy>(
.unwrap();

let text = Text::new(&string, position, style);
if highlight_characters {
draw_character_bounding_boxes(display, &text);
}
draw_text(display, &text);

let position = display.bounding_box().anchor_point(AnchorPoint::BottomLeft)
Expand Down Expand Up @@ -138,16 +176,17 @@ fn try_main() -> Result<()> {
let mut window = Window::new("Font viewer", &settings);

let mut use_mono_font = false;
let mut highlight_characters = false;

'main_loop: loop {
window.update(&display);

for event in window.events() {
match event {
SimulatorEvent::KeyDown { keycode, .. } => match keycode {
Keycode::M => {
use_mono_font = !use_mono_font;
}
Keycode::M => use_mono_font = !use_mono_font,
Keycode::C => highlight_characters = !highlight_characters,
Keycode::Q => break 'main_loop,
_ => {}
},
SimulatorEvent::Quit => break 'main_loop,
Expand All @@ -160,12 +199,12 @@ fn try_main() -> Result<()> {
display.clear(Rgb888::BLACK).unwrap();
if use_mono_font {
let style = MonoTextStyle::new(&mono_font, Rgb888::WHITE);
draw(&mut display, style, line_height);
draw(&mut display, style, line_height, highlight_characters);

hint.insert_str(0, "Mono | ");
} else {
let style = BdfTextStyle::new(&bdf_font, Rgb888::WHITE);
draw(&mut display, style, line_height);
draw(&mut display, style, line_height, highlight_characters);

hint.insert_str(0, "Bdf | ");
}
Expand Down
26 changes: 26 additions & 0 deletions eg-font-converter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ pub struct FontConverter<'a> {
data_file_extension: String,
data_file_path: Option<PathBuf>,
comments: Vec<String>,
glyph_alignment: GlyphAlignment,

glyphs: BTreeSet<char>,
missing_glyph_substitute: Option<char>,
Expand Down Expand Up @@ -134,6 +135,7 @@ impl<'a> FontConverter<'a> {
data_file_extension: "data".to_string(),
data_file_path: None,
comments: Vec::new(),
glyph_alignment: GlyphAlignment::Center,
glyphs: BTreeSet::new(),
missing_glyph_substitute: None,
}
Expand Down Expand Up @@ -252,6 +254,17 @@ impl<'a> FontConverter<'a> {
self
}

/// Set the glyph alignment in `MonoFont`s.
///
/// When a proportional font is converted into a `MonoFont` all glyphs have to use the same
/// bounding box, which matches the largest glyph in the included glyph subset. This setting
/// determines how smaller glyphs are aligned in this bounding box.
pub fn glyph_alignment(mut self, glyph_alignment: GlyphAlignment) -> Self {
self.glyph_alignment = glyph_alignment;

self
}

fn convert(&self) -> Result<ConvertedFont> {
ensure!(
is_valid_identifier(&self.name),
Expand Down Expand Up @@ -325,6 +338,7 @@ impl<'a> FontConverter<'a> {
data_file_extension: self.data_file_extension.clone(),
data_file_path: self.data_file_path.clone(),
comments: self.comments.clone(),
glyph_alignment: self.glyph_alignment,
underline_position,
underline_thickness,
strikethrough_position,
Expand Down Expand Up @@ -382,6 +396,7 @@ struct ConvertedFont {
pub data_file_extension: String,
pub data_file_path: Option<PathBuf>,
pub comments: Vec<String>,
pub glyph_alignment: GlyphAlignment,

pub glyphs: Vec<Glyph>,
pub replacement_character: usize,
Expand Down Expand Up @@ -516,6 +531,17 @@ impl Visibility {
}
}

/// Glyph alignment.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum GlyphAlignment {
/// Left aligned.
Left,
/// Center aligned.
Center,
/// Right aligned.
Right,
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
92 changes: 67 additions & 25 deletions eg-font-converter/src/mono_font.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
use std::{fs, io, ops::RangeInclusive, path::Path};

use anyhow::{bail, Context, Result};
use bdf_parser::{Encoding, Font, Glyph};
use anyhow::{bail, ensure, Context, Result};
use bdf_parser::{BoundingBox, Coord, Encoding, Font, Glyph};
use eg_bdf::BdfTextStyle;
use embedded_graphics::{
image::ImageRaw,
mono_font::{mapping::Mapping, DecorationDimensions, MonoFont},
pixelcolor::BinaryColor,
prelude::*,
text::{Baseline, Text},
primitives::Rectangle,
text::Text,
};
use embedded_graphics_simulator::{OutputSettings, SimulatorDisplay};
use quote::{format_ident, quote};

use crate::{ConvertedFont, EgBdfOutput};
use crate::{eg_bdf_font::bounding_box_to_rectangle, ConvertedFont, EgBdfOutput, GlyphAlignment};

/// Font conversion output for [`MonoFont`].
#[derive(Debug)]
Expand All @@ -32,30 +33,38 @@ pub struct MonoFontOutput {
mapping: Option<Mapping>,
}

fn determine_glyph_metrics<'a>(glyphs: impl Iterator<Item = &'a Glyph>) -> (Rectangle, u32) {
// Default to 1x1 pixel bounding box at the origin.
// This ensures that the relative positioning of glyphs that don't touch the baseline is
// maintained in case only this kind of glyph is included.
let mut bounding_box = BoundingBox {
offset: Coord::new(0, 0),
size: Coord::new(1, 1),
};

let mut max_dwidth = 0;
for glyph in glyphs {
bounding_box = bounding_box.union(&glyph.bounding_box);
if let Some(width) = glyph.width_horizontal {
max_dwidth = max_dwidth.max(width.device.x);
}
}

(
bounding_box_to_rectangle(&bounding_box),
max_dwidth.try_into().unwrap(),
)
}

impl MonoFontOutput {
pub(crate) fn new(bdf: EgBdfOutput) -> Result<Self> {
let font = bdf.as_font();
let metrics = &bdf.font.bdf.metrics;
let style = BdfTextStyle::new(&font, BinaryColor::On);

let glyphs_per_row = 16; //TODO: make configurable
let columns = glyphs_per_row; // TODO: allow smaller column count
let rows = font.glyphs.len().div_ceil(glyphs_per_row);

let character_size = bdf.bounding_box().size;
let character_spacing = 0;
let baseline = metrics.ascent.saturating_sub(1);
let strikethrough = DecorationDimensions::new(
bdf.font.strikethrough_position,
bdf.font.strikethrough_thickness,
);
let underline =
DecorationDimensions::new(bdf.font.underline_position, bdf.font.underline_thickness);

let mut bitmap = SimulatorDisplay::new(
character_size.component_mul(Size::new(columns as u32, rows as u32)),
);

let mapping = glyphs_to_mapping(&bdf.font.glyphs);

// Rearrange the glyphs in the correct order if a mapping is used,
Expand All @@ -80,19 +89,52 @@ impl MonoFontOutput {
.collect()
};

for (i, glyph) in glyphs.iter().enumerate() {
let x = (i % glyphs_per_row) as i32 * character_size.width as i32;
let y = (i / glyphs_per_row) as i32 * character_size.height as i32;
ensure!(!glyphs.is_empty(), "Font must contain at least one glyph");

let (bounding_box, max_dwidth) = determine_glyph_metrics(glyphs.iter());

let character_size = bounding_box.size;
let character_spacing = max_dwidth.saturating_sub(character_size.width);

let baseline: u32 = (-bounding_box.top_left.y).try_into().unwrap();
let strikethrough = DecorationDimensions::new(
bdf.font.strikethrough_position,
bdf.font.strikethrough_thickness,
);
let underline =
DecorationDimensions::new(bdf.font.underline_position, bdf.font.underline_thickness);

let mut bitmap = SimulatorDisplay::new(
character_size.component_mul(Size::new(columns as u32, rows as u32)),
);

for (i, glyph) in glyphs.iter().enumerate() {
// TODO: assumes unicode
let c = match glyph.encoding {
Encoding::Standard(index) => char::from_u32(index).unwrap(),
_ => bail!("invalid encoding: '{:?}'", glyph.encoding),
};

Text::with_baseline(&String::from(c), Point::new(x, y), style, Baseline::Top)
.draw(&mut bitmap)
.unwrap();
let (row, column) = (i / glyphs_per_row, i % glyphs_per_row);

let glyph_position =
Point::zero() + Size::new(column as u32, row as u32).component_mul(character_size);
let baseline_offset = Point::new(-glyph.bounding_box.offset.x, baseline as i32);
let alignment_offset = match bdf.font.glyph_alignment {
GlyphAlignment::Left => 0,
GlyphAlignment::Center => {
(character_size.width as i32 - glyph.bounding_box.size.x) / 2
}
GlyphAlignment::Right => character_size.width as i32 - glyph.bounding_box.size.x,
};

Text::new(
&String::from(c),
glyph_position + baseline_offset + Point::new(alignment_offset, 0),
style,
)
.draw(&mut bitmap)
.unwrap();
}

let data = bitmap.to_be_bytes();
Expand Down