use crate::string_error_to_io_error;
use endbasic_std::console::{CharsXY, SizeInPixels};
use once_cell::sync::Lazy;
use sdl2::ttf::{Font, FontError, InitError, Sdl2TtfContext};
use std::convert::TryFrom;
use std::io;
use std::path::Path;
static TTF_CONTEXT: Lazy<Result<Sdl2TtfContext, InitError>> = Lazy::new(sdl2::ttf::init);
fn init_error_to_io_error(e: &'static InitError) -> io::Error {
match e {
InitError::AlreadyInitializedError => {
panic!("Initialization from once_cell should happen only once")
}
InitError::InitializationError(e) => io::Error::new(e.kind(), format!("{}", e)),
}
}
pub(crate) fn font_error_to_io_error(e: FontError) -> io::Error {
let kind = match e {
FontError::InvalidLatin1Text(_) => io::ErrorKind::InvalidInput,
FontError::SdlError(_) => io::ErrorKind::Other,
};
io::Error::new(kind, e)
}
pub(crate) struct MonospacedFont<'a> {
pub(crate) font: Font<'a, 'static>,
pub(crate) glyph_size: SizeInPixels,
}
impl<'a> MonospacedFont<'a> {
pub(crate) fn load(path: &Path, point_size: u16) -> io::Result<MonospacedFont<'a>> {
let ttf_context = TTF_CONTEXT.as_ref().map_err(init_error_to_io_error)?;
let font = ttf_context.load_font(path, point_size).map_err(string_error_to_io_error)?;
if !font.face_is_fixed_width() {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("Font {} is not monospaced", path.display()),
));
}
let glyph_size = {
let metrics = match font.find_glyph_metrics('A') {
Some(metrics) => metrics,
None => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Font lacks a glyph for 'A'; is it valid?",
))
}
};
let width = match u16::try_from(metrics.advance) {
Ok(0) => {
return Err(io::Error::new(io::ErrorKind::InvalidInput, "Invalid font width 0"))
}
Ok(width) => width,
Err(e) => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("Invalid font width {}: {}", metrics.advance, e),
))
}
};
let height = match u16::try_from(font.height()) {
Ok(0) => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"Invalid font height 0",
))
}
Ok(height) => height,
Err(e) => {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("Invalid font height {}: {}", font.height(), e),
))
}
};
SizeInPixels::new(width, height)
};
Ok(MonospacedFont { font, glyph_size })
}
pub(crate) fn chars_in_area(&self, area: SizeInPixels) -> CharsXY {
CharsXY::new(
area.width
.checked_div(self.glyph_size.width)
.expect("Glyph size tested for non-zero during init"),
area.height
.checked_div(self.glyph_size.height)
.expect("Glyph size tested for non-zero during init"),
)
}
}