use crate::{kerning::Kerning, ligatures::Ligatures, side_bearings::SideBearings};
use embedded_graphics::{
geometry::{Point, Size},
image::ImageRaw,
mono_font::mapping::{GlyphMapping, StrGlyphMapping},
pixelcolor::BinaryColor,
primitives::Rectangle,
};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Charset {
pub(crate) image: ImageRaw<'static, BinaryColor>,
pub(crate) glyph_mapping: StrGlyphMapping<'static>,
pub(crate) glyph_data: &'static [u8],
pub(crate) ligatures: Ligatures<'static>,
pub(crate) side_bearings: SideBearings<'static>,
pub(crate) kerning: Kerning<'static>,
pub(crate) line_height: u32,
pub(crate) baseline: u32,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) struct GlyphIndex(pub(crate) usize);
impl Charset {
pub(crate) fn glyph_indices<'t>(
&'static self,
text: &'t str,
) -> impl Iterator<Item = GlyphIndex> + 't {
let mut byte_offset = 0;
let mut chars = text.chars();
core::iter::from_fn(move || {
if let Some((liga_index, mut liga_size)) =
self.ligatures.substitute(&text[byte_offset..])
{
while liga_size > 0 {
if let Some(glyph) = chars.next() {
byte_offset += glyph.len_utf8();
} else {
return None;
}
liga_size -= 1;
}
return Some(GlyphIndex(liga_index));
} else if let Some(glyph) = chars.next() {
byte_offset += glyph.len_utf8();
return Some(self.glyph_index(glyph));
} else {
return None;
}
})
}
pub(crate) fn glyph_area(&self, glyph: GlyphIndex) -> Rectangle {
let start = glyph.0 * 5;
let end = start + 3;
if let [x, y, dimensions] = self.glyph_data[start..end] {
let width = (dimensions & 0x0F) as u32; let height = (dimensions >> 4) as u32; Rectangle::new(Point::new(x as i32, y as i32), Size::new(width, height))
} else {
Rectangle::zero()
}
}
pub(crate) fn glyph_width(&self, index: GlyphIndex) -> i32 {
(self.glyph_data[index.0 * 5 + 2] & 0x0F) as i32
}
pub(crate) fn spacing(&self, prev_glyph: Option<GlyphIndex>, next_glyph: GlyphIndex) -> i32 {
match prev_glyph {
Some(prev) => {
let right_bearing = self.side_bearings.right(prev);
let left_bearing = self.side_bearings.left(next_glyph);
let kerning = self.kerning(prev, next_glyph).unwrap_or_default();
right_bearing + kerning + left_bearing
}
None => self.side_bearings.left(next_glyph),
}
}
fn glyph_index(&self, char: char) -> GlyphIndex {
GlyphIndex(self.glyph_mapping.index(char))
}
fn kerning(&self, left: GlyphIndex, right: GlyphIndex) -> Option<i32> {
self.kerning.kerning_override(left, right).or_else(|| {
self.kerning.kerning(
self.left_kerning_class(left),
self.right_kerning_class(right),
)
})
}
fn left_kerning_class(&self, index: GlyphIndex) -> u8 {
self.glyph_data[index.0 * 5 + 3]
}
fn right_kerning_class(&self, index: GlyphIndex) -> u8 {
self.glyph_data[index.0 * 5 + 4]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::generated::ASCII;
use embedded_graphics::{image::ImageDrawable, mock_display::MockDisplay};
#[test]
fn test_glyph() {
let area = ASCII.glyph_area(ASCII.glyph_index('a'));
let mut display = MockDisplay::new();
ASCII.image.draw_sub_image(&mut display, &area).unwrap();
display.assert_pattern(&[
"...", "...", "...", "##.", "..#", "###", "#.#", ".##", "...", "...", "...", ]);
}
#[test]
fn test_glyph_area() {
assert_eq!(
ASCII.glyph_area(ASCII.glyph_index('a')),
Rectangle::new(Point::new(58, 24), Size::new(3, 11))
);
assert_eq!(
ASCII.glyph_area(ASCII.glyph_index('!')),
Rectangle::new(Point::new(4, 0), Size::new(1, 11))
);
assert_eq!(
ASCII.glyph_area(ASCII.glyph_index('熊')),
ASCII.glyph_area(ASCII.glyph_index('?'))
);
}
#[test]
fn test_ligature_substitution_in_text() {
let text = "虫ffifijjjssyj";
let ligatures_offset = ASCII.ligatures.offset;
let mut glyphs = ASCII.glyph_indices(text);
assert_eq!(glyphs.next(), Some(ASCII.glyph_index('虫')));
assert_eq!(glyphs.next(), Some(GlyphIndex(ligatures_offset + 0))); assert_eq!(glyphs.next(), Some(GlyphIndex(ligatures_offset + 2))); assert_eq!(glyphs.next(), Some(GlyphIndex(ligatures_offset + 5))); assert_eq!(glyphs.next(), Some(ASCII.glyph_index('j')));
assert_eq!(glyphs.next(), Some(GlyphIndex(ligatures_offset + 6))); assert_eq!(glyphs.next(), Some(GlyphIndex(ligatures_offset + 7))); assert_eq!(glyphs.next(), None);
}
#[test]
fn test_letter_spacing() {
assert_eq!(
ASCII.spacing(Some(ASCII.glyph_index('o')), ASCII.glyph_index(',')),
0
);
assert_eq!(
ASCII.kerning(ASCII.glyph_index('f'), ASCII.glyph_index('o')),
Some(-1)
);
}
#[test]
fn test_kerning() {
assert_eq!(
ASCII.kerning(ASCII.glyph_index('s'), ASCII.glyph_index('`')),
Some(-1)
);
assert_eq!(
ASCII.kerning(ASCII.glyph_index('f'), ASCII.glyph_index('o')),
Some(-1)
);
assert_eq!(
ASCII.kerning(ASCII.glyph_index('T'), ASCII.glyph_index('/')),
Some(-2)
);
assert_eq!(
ASCII.kerning(ASCII.glyph_index('/'), ASCII.glyph_index('/')),
Some(-2)
);
assert_eq!(
ASCII.kerning(ASCII.glyph_index('o'), ASCII.glyph_index(',')),
Some(-1)
);
assert_eq!(
ASCII.kerning(ASCII.glyph_index('u'), ASCII.glyph_index('s')),
None
);
}
#[test]
fn test_kerning_classes() {
assert_eq!(
ASCII.right_kerning_class(ASCII.glyph_index('Y')),
ASCII.right_kerning_class(ASCII.glyph_index('`'))
);
assert_eq!(
ASCII.right_kerning_class(ASCII.glyph_index('"')),
ASCII.right_kerning_class(ASCII.glyph_index('\''))
);
assert_eq!(ASCII.left_kerning_class(ASCII.glyph_index('o')), 16);
assert_eq!(ASCII.right_kerning_class(ASCII.glyph_index(',')), 14);
}
#[test]
fn test_side_bearings() {
assert_eq!(ASCII.side_bearings.left(ASCII.glyph_index(',')), 0);
assert_eq!(ASCII.side_bearings.right(ASCII.glyph_index('o')), 1);
}
}