use std::array::TryFromSliceError;
use std::fmt;
use crate::gfx::pixels::PixelsMut;
use crate::gfx::*;
use crate::math::*;
#[derive(Debug, Clone)]
pub enum Error {
TryFromSlice(TryFromSliceError),
TileCount(usize, usize),
ByteLength(usize),
}
impl std::error::Error for Error {}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::TryFromSlice(e) => write!(f, "invalid font: {e}"),
Self::TileCount(a, e) => write!(f, "invalid tile count `{a}`, expected `{e}`"),
Self::ByteLength(l) => write!(f, "invalid font byte length '{l}'"),
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum FontFormat {
UF1,
UF2,
}
impl FontFormat {
pub fn line_height(&self) -> f32 {
match self {
Self::UF1 => 8.,
Self::UF2 => 16.,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Font<T> {
pub widths: [u8; 256],
pub image: T,
pub format: FontFormat,
}
impl<T> Font<T> {
pub fn decode(bytes: &[u8], format: FontFormat) -> Result<Font<Image>, Error> {
const T: usize = 8;
const N: usize = 256;
const G: usize = 2 * 2;
assert!(
matches!(format, FontFormat::UF2),
"Only UF2 fonts are currently supported"
);
let (widths, glyphs) = bytes.split_at(N);
let (head, tiles, tail) = unsafe { glyphs.align_to::<[u8; T]>() };
if !head.is_empty() || !tail.is_empty() {
return Err(Error::ByteLength(glyphs.len()));
}
if tiles.len() != N * G {
return Err(Error::TileCount(tiles.len(), N * G));
}
let size = Size::new(N, N);
let widths: [u8; N] = widths.try_into().map_err(Error::TryFromSlice)?;
let mut texels = vec![Rgba8::ZERO; size.area()];
let mut pixels = PixelsMut::new(&mut texels, size.w, size.h);
let v = Rgba8::WHITE;
let (mut x, mut y) = (0, 0);
for window in tiles.chunks(G / 2) {
if let &[a, b] = window {
pixels.icn(a, x, y, v);
pixels.icn(b, x, y + T, v);
x += T;
if x == size.w {
x = 0;
y += T + T;
}
}
}
Ok(Font {
widths,
format,
image: Image::new(texels, size),
})
}
pub fn glyph_width(&self, c: u8) -> f32 {
self.widths[c as usize] as f32
}
pub fn text_width(&self, text: &str) -> f32 {
text.bytes().map(|c| self.glyph_width(c)).sum()
}
pub fn line_height(&self) -> f32 {
self.format.line_height()
}
}