use std::{error::Error, fs, path::Path};
use rusttype::{point, Scale, VMetrics};
use crate::geometry::{Orientation, Rectangle};
use super::{GlyphData, Word};
pub struct Font<'font> {
font: Box<rusttype::Font<'font>>,
}
impl<'font> Font<'font> {
pub fn new(font_file: String) -> Result<Font<'font>, Box<dyn Error>> {
if !Path::new(&font_file).exists() {
return Err(format!("Font file not found: {}", font_file).into());
}
let content = fs::read(&font_file)?;
let Some(font) = rusttype::Font::try_from_vec(content) else {
return Err(format!("Cannot load font: {}", font_file).into());
};
Ok(Font {
font: Box::new(font),
})
}
pub fn get_metrics(&self, scale: Scale) -> VMetrics {
self.font.v_metrics(scale)
}
pub fn height(&self, scale: Scale) -> u32 {
let metrics = self.get_metrics(scale);
(metrics.ascent - metrics.descent).ceil() as u32
}
pub fn get_bounding_box(&self, word: &Word, scale: Scale) -> Rectangle {
let glyph_data = self.get_glyph_data(&word.text, scale);
match word.orientation {
Orientation::Horizontal => Rectangle::new(0, 0, glyph_data.width, glyph_data.height),
Orientation::Vertical => Rectangle::new(0, 0, glyph_data.height, glyph_data.width),
}
}
pub fn get_glyph_data(&self, text: &str, scale: Scale) -> GlyphData {
let glyphs: Vec<_> = self.font.layout(text, scale, point(0.0, 0.0)).collect();
let height = self.height(scale);
let width = {
let min_x = glyphs
.first()
.map(|g| g.pixel_bounding_box().unwrap().min.x)
.unwrap();
let max_x = glyphs
.last()
.map(|g| g.pixel_bounding_box().unwrap().max.x)
.unwrap();
(max_x - min_x) as u32
};
GlyphData {
width,
height,
glyphs,
}
}
}