word_cloud/words/
fonts_provider.rs1use crate::{
2 visualization::Image,
3 words::{Font, Word},
4};
5use image::Rgb;
6use rand::seq::SliceRandom;
7use rusttype::Scale;
8use serde::{Deserialize, Serialize};
9use serde_json::from_str;
10use std::{collections::HashMap, error::Error, fs::read_to_string};
11
12pub struct FontProvider<'font> {
50 fonts: HashMap<String, Font<'font>>,
51}
52
53impl<'font> Default for FontProvider<'font> {
54 fn default() -> Self {
55 Self::new()
56 }
57}
58
59impl<'font> FontProvider<'font> {
60 pub fn new() -> FontProvider<'font> {
61 FontProvider {
62 fonts: HashMap::new(),
63 }
64 }
65
66 pub fn from_file(filename: &str) -> FontProvider<'font> {
67 #[derive(Serialize, Deserialize)]
68 struct FontFile {
69 name: String,
70 file: String,
71 }
72
73 let mut fp = FontProvider::new();
74 let Ok(data) = read_to_string(filename) else {
75 panic!("Unable to read file '{}'", filename);
76 };
77
78 let Ok(fonts): Result<Vec<FontFile>, _> = from_str(&data) else {
79 panic!("Unable to parse file '{}'", filename);
80 };
81
82 for font in fonts {
83 let file = font.file.clone();
84 fp.register(&font.name, &file).unwrap();
85 }
86
87 fp
88 }
89
90 pub fn register(&mut self, name: &str, font_file: &str) -> Result<(), Box<dyn Error>> {
91 let font = Font::new(font_file.to_string())?;
92 self.fonts.insert(name.to_string(), font);
93
94 Ok(())
95 }
96
97 pub fn get_font(&self, name: &str) -> Result<&Font, String> {
98 match self.fonts.get(name) {
99 Some(font) => Ok(font),
100 None => Err(format!("Font not registered: {}", name)),
101 }
102 }
103
104 pub fn random_font_name(&self) -> String {
105 let keys: Vec<_> = self.fonts.keys().collect();
106 keys.choose(&mut rand::thread_rng()).unwrap().to_string()
107 }
108
109 pub fn debug_font(&self, font_name: &str, filename: &str) {
110 if let Some(font) = self.fonts.get(font_name) {
111 let word = Word::new(
112 font_name,
113 crate::geometry::Orientation::Horizontal,
114 Rgb([0, 0, 0]),
115 "",
116 64,
117 );
118 let bb = font.get_bounding_box(&word, Scale::uniform(64.));
119 let mut img = Image::new(bb.size().0 + 40, bb.size().1, Some(Rgb([255, 255, 255])));
120 img.draw_text(
121 (15, 0),
122 font_name,
123 font,
124 Scale::uniform(64.),
125 Rgb([0, 0, 0]),
126 &crate::geometry::Orientation::Horizontal,
127 );
128 img.save(filename).unwrap();
129 }
130 }
131}
132
133#[cfg(test)]
134mod test {
135 use super::*;
136
137 #[test]
138 fn register_and_read() {
139 let mut fp = FontProvider::new();
140 fp.register("dejavu", "assets/fonts/DroidSans.ttf").unwrap();
141 let _ = fp.get_font("dejavu").unwrap();
142 }
144
145 #[test]
146 #[should_panic(expected = "Font file not found: unexisting.ttf")]
147 fn register_file_not_found() {
148 let mut fp = FontProvider::new();
149 fp.register("dejavu", "unexisting.ttf").unwrap();
150 }
151
152 #[test]
153 #[should_panic(expected = "Font not registered: dejavu")]
154 fn get_unexisting() {
155 let fp = FontProvider::new();
156 let _ = fp.get_font("dejavu").unwrap();
157 }
158}