use super::{Font, FontError};
use fstr::FStr;
use std::io::{Read, Write};
use tfon::tfon::Parser;
use tfon::{Bitmap, Prop};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("I/O {0}")]
Io(#[from] std::io::Error),
#[error("tfon: {0}")]
Tfon(#[from] tfon::Error),
#[error("Font: {0}")]
Invalid(#[from] FontError),
}
type Result<T> = std::result::Result<T, Error>;
pub fn read<const C: usize, R: Read>(mut reader: R) -> Result<Font<C>> {
let mut buf = String::with_capacity(4096);
reader.read_to_string(&mut buf)?;
parse(&buf)
}
pub fn parse<const C: usize>(buf: &str) -> Result<Font<C>> {
let mut font = Font::default();
let mut row = 0;
for prop in Parser::new(buf) {
match prop {
Prop::FontName(nm) => font.name = FStr::from_str_lossy(nm, b'\0'),
Prop::FontNumber(num) => font.number = num,
Prop::CharSpacing(cs) => font.char_spacing = cs,
Prop::LineSpacing(ls) => font.line_spacing = ls,
Prop::CodePoint(cp) => font.characters[row].number = cp,
Prop::Bitmap(bmap) => {
if font.height > 0 {
if bmap.height() != font.height {
return Err(FontError::InvalidCharHeight.into());
}
} else {
font.height = bmap.height();
}
font.characters[row].width = bmap.width();
font.characters[row].bitmap = bmap.into_bits();
row += 1;
}
_ => (),
}
}
Ok(font)
}
pub fn write<const C: usize, W: Write>(
writer: W,
font: &Font<C>,
) -> Result<()> {
font.validate()?;
let mut props = Vec::with_capacity(128);
props.push(Prop::FontName(font.name.slice_to_terminator('\0')));
props.push(Prop::FontNumber(font.number));
props.push(Prop::CharSpacing(font.char_spacing));
props.push(Prop::LineSpacing(font.line_spacing));
let height = font.height;
for ch in &font.characters {
if ch.number > 0
&& let Some(bmap) =
Bitmap::from_bits(height, ch.width, ch.bitmap.clone())
{
props.push(Prop::CodePoint(ch.number));
props.push(Prop::Bitmap(bmap));
}
}
tfon::tfon::write(writer, props.into_iter())?;
Ok(())
}