1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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,
        }
    }
}