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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
use crate::{
    visualization::Image,
    words::{Font, Word},
};
use image::Rgb;
use rand::seq::SliceRandom;
use rusttype::Scale;
use serde::{Deserialize, Serialize};
use serde_json::from_str;
use std::{collections::HashMap, error::Error, fs::read_to_string};

/// The font provider is responsible to provide fonts to the library.
///
/// Before a font can be used it must be registered.
///
/// ## Manual font registration
///
/// Fonts can be registered manually.
///
/// Provide the name alias ("droid" below) and the font file.
///
/// ```rust
/// let mut font_provider = FontProvider::new();
/// font_provider
///     .register("droid", "assets/fonts/DroidSans.ttf")
///     .unwrap();
/// ```
///
/// ## Automatic font registration
///
/// Given there is a file describing the font registrations, the font provider can be created this way:
///
/// ```rust
///    let font_provider = FontProvider::from_file("assets/fonts.json");
///  ```
///
/// ## Format of the font registrations file
///
/// It's a JSON file containing an array of font registrations.
///
/// ```json
/// [
///     {
///         "name": "droid",
///         "file": "fonts/DroidSans.ttf"
///     }
/// ]
/// ```
pub struct FontProvider<'font> {
    fonts: HashMap<String, Font<'font>>,
}

impl<'font> Default for FontProvider<'font> {
    fn default() -> Self {
        Self::new()
    }
}

impl<'font> FontProvider<'font> {
    pub fn new() -> FontProvider<'font> {
        FontProvider {
            fonts: HashMap::new(),
        }
    }

    pub fn from_file(filename: &str) -> FontProvider<'font> {
        #[derive(Serialize, Deserialize)]
        struct FontFile {
            name: String,
            file: String,
        }

        let mut fp = FontProvider::new();
        let Ok(data) = read_to_string(filename) else {
            panic!("Unable to read file '{}'", filename);
        };

        let Ok(fonts): Result<Vec<FontFile>, _> = from_str(&data) else {
            panic!("Unable to parse file '{}'", filename);
        };

        for font in fonts {
            let file = font.file.clone();
            fp.register(&font.name, &file).unwrap();
        }

        fp
    }

    pub fn register(&mut self, name: &str, font_file: &str) -> Result<(), Box<dyn Error>> {
        let font = Font::new(font_file.to_string())?;
        self.fonts.insert(name.to_string(), font);

        Ok(())
    }

    pub fn get_font(&self, name: &str) -> Result<&Font, String> {
        match self.fonts.get(name) {
            Some(font) => Ok(font),
            None => Err(format!("Font not registered: {}", name)),
        }
    }

    pub fn random_font_name(&self) -> String {
        let keys: Vec<_> = self.fonts.keys().collect();
        keys.choose(&mut rand::thread_rng()).unwrap().to_string()
    }

    pub fn debug_font(&self, font_name: &str, filename: &str) {
        if let Some(font) = self.fonts.get(font_name) {
            let word = Word::new(
                font_name,
                crate::geometry::Orientation::Horizontal,
                Rgb([0, 0, 0]),
                "",
                64,
            );
            let bb = font.get_bounding_box(&word, Scale::uniform(64.));
            let mut img = Image::new(bb.size().0 + 40, bb.size().1, Some(Rgb([255, 255, 255])));
            img.draw_text(
                (15, 0),
                font_name,
                font,
                Scale::uniform(64.),
                Rgb([0, 0, 0]),
                &crate::geometry::Orientation::Horizontal,
            );
            img.save(filename).unwrap();
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn register_and_read() {
        let mut fp = FontProvider::new();
        fp.register("dejavu", "assets/fonts/DroidSans.ttf").unwrap();
        let _ = fp.get_font("dejavu").unwrap();
        // The test should not panic.
    }

    #[test]
    #[should_panic(expected = "Font file not found: unexisting.ttf")]
    fn register_file_not_found() {
        let mut fp = FontProvider::new();
        fp.register("dejavu", "unexisting.ttf").unwrap();
    }

    #[test]
    #[should_panic(expected = "Font not registered: dejavu")]
    fn get_unexisting() {
        let fp = FontProvider::new();
        let _ = fp.get_font("dejavu").unwrap();
    }
}