1use std::{error::Error, fs, path::Path};
2
3use rusttype::{point, Scale, VMetrics};
4
5use crate::geometry::{Orientation, Rectangle};
6
7use super::{GlyphData, Word};
8
9pub struct Font<'font> {
10 font: Box<rusttype::Font<'font>>,
11}
12
13impl<'font> Font<'font> {
14 pub fn new(font_file: String) -> Result<Font<'font>, Box<dyn Error>> {
15 if !Path::new(&font_file).exists() {
16 return Err(format!("Font file not found: {}", font_file).into());
17 }
18
19 let content = fs::read(&font_file)?;
20
21 let Some(font) = rusttype::Font::try_from_vec(content) else {
22 return Err(format!("Cannot load font: {}", font_file).into());
23 };
24
25 Ok(Font {
26 font: Box::new(font),
27 })
28 }
29
30 pub fn get_metrics(&self, scale: Scale) -> VMetrics {
31 self.font.v_metrics(scale)
32 }
33
34 pub fn height(&self, scale: Scale) -> u32 {
35 let metrics = self.get_metrics(scale);
36 (metrics.ascent - metrics.descent).ceil() as u32
37 }
38
39 pub fn get_bounding_box(&self, word: &Word, scale: Scale) -> Rectangle {
40 let glyph_data = self.get_glyph_data(&word.text, scale);
41
42 match word.orientation {
43 Orientation::Horizontal => Rectangle::new(0, 0, glyph_data.width, glyph_data.height),
44 Orientation::Vertical => Rectangle::new(0, 0, glyph_data.height, glyph_data.width),
45 }
46 }
47
48 pub fn get_glyph_data(&self, text: &str, scale: Scale) -> GlyphData {
49 let glyphs: Vec<_> = self.font.layout(text, scale, point(0.0, 0.0)).collect();
50 let height = self.height(scale);
51 let width = {
52 let min_x = glyphs
53 .first()
54 .map(|g| g.pixel_bounding_box().unwrap().min.x)
55 .unwrap();
56 let max_x = glyphs
57 .last()
58 .map(|g| g.pixel_bounding_box().unwrap().max.x)
59 .unwrap();
60 (max_x - min_x) as u32
61 };
62
63 GlyphData {
64 width,
65 height,
66 glyphs,
67 }
68 }
69}