use std::array::TryFromSliceError;
use std::fmt;
use thiserror::Error;
use crate::gfx::pixels::PixelsMut;
use crate::gfx::*;
use crate::math::*;
use crate::ui::TextureId;
#[derive(Debug, Error)]
pub enum Error {
#[error("Invalid font")]
TryFromSlice(#[from] TryFromSliceError),
#[error("Invalid tile count `{0}`, expected `{1}`")]
TileCount(usize, usize),
#[error("Invalid font byte length '{0}'")]
ByteLength(usize),
}
#[derive(Debug, Clone, Copy)]
pub enum FontFormat {
UF1,
UF2,
}
impl FontFormat {
pub fn size(&self) -> Size<f32> {
match self {
Self::UF1 => Size::from(8.),
Self::UF2 => Size::from(16.),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct Font {
pub widths: [u8; 256],
pub texture_id: TextureId,
pub tile: Size<f32>,
}
impl Font {
pub fn decode(bytes: &[u8], format: FontFormat) -> Result<(Image, [u8; 256]), Error> {
const T: usize = 8;
const N: usize = 256;
const G: usize = 2 * 2;
assert!(matches!(format, FontFormat::UF2));
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()?;
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((Image::new(texels, size), widths))
}
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 text_height(&self) -> f32 {
FontFormat::UF2.size().h
}
}
#[derive(Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct FontId(pub String);
impl fmt::Display for FontId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl Default for FontId {
fn default() -> Self {
Self(String::from("default"))
}
}
impl From<&str> for FontId {
fn from(other: &str) -> Self {
Self(other.to_owned())
}
}
impl From<String> for FontId {
fn from(other: String) -> Self {
Self(other)
}
}