1use std::collections::HashMap;
7use std::sync::Arc;
8
9use crate::error::{LayoutError, Result};
10use crate::output::FontId;
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash)]
14struct FontKey {
15 family: String,
16 bold: bool,
17 italic: bool,
18}
19
20#[derive(Debug, Clone, Copy)]
22pub struct FontMetrics {
23 pub ascent: f64,
25 pub descent: f64,
27 pub line_gap: f64,
29 pub units_per_em: u16,
31}
32
33#[derive(Debug, Clone)]
35pub struct ShapedText {
36 pub glyph_ids: Vec<u16>,
38 pub advances: Vec<f64>,
40 pub width: f64,
42}
43
44struct LoadedFont {
46 id: FontId,
47 family: String,
48 bold: bool,
49 italic: bool,
50 data: Arc<Vec<u8>>,
51 face_index: u32,
52 units_per_em: u16,
53}
54
55pub struct FontManager {
57 db: fontdb::Database,
58 cache: HashMap<FontKey, usize>,
60 fonts: Vec<LoadedFont>,
62 next_id: u32,
64}
65
66impl Default for FontManager {
67 fn default() -> Self {
68 Self::new()
69 }
70}
71
72impl FontManager {
73 pub fn new() -> Self {
78 let mut db = fontdb::Database::new();
79
80 for (_family, data) in crate::bundled_fonts::bundled_font_data() {
82 db.load_font_data(data.to_vec());
83 }
84
85 db.load_system_fonts();
87
88 FontManager {
89 db,
90 cache: HashMap::new(),
91 fonts: Vec::new(),
92 next_id: 0,
93 }
94 }
95
96 pub fn load_additional_fonts(&mut self, font_files: &[crate::input::FontFile]) {
101 for font_file in font_files {
102 self.db.load_font_data(font_file.data.clone());
103 }
104 self.cache.clear();
106 }
107
108 pub fn new_with_fonts(fonts: Vec<(String, Vec<u8>)>) -> Self {
113 let mut db = fontdb::Database::new();
114 for (_name, data) in &fonts {
115 db.load_font_data(data.clone());
116 }
117 FontManager {
118 db,
119 cache: HashMap::new(),
120 fonts: Vec::new(),
121 next_id: 0,
122 }
123 }
124
125 pub fn resolve_font(
128 &mut self,
129 family: Option<&str>,
130 bold: bool,
131 italic: bool,
132 ) -> Result<FontId> {
133 let family_name = family.unwrap_or("Arial");
134
135 let key = FontKey {
136 family: family_name.to_string(),
137 bold,
138 italic,
139 };
140
141 if let Some(&idx) = self.cache.get(&key) {
142 return Ok(self.fonts[idx].id);
143 }
144
145 let mapped = map_font_name(family_name);
147
148 let mut fallbacks: Vec<&str> = Vec::with_capacity(10);
150 fallbacks.push(family_name);
151 for alt in mapped {
152 if *alt != family_name {
153 fallbacks.push(alt);
154 }
155 }
156 for generic in &[
157 "Carlito",
158 "Arial",
159 "Liberation Sans",
160 "Helvetica",
161 "DejaVu Sans",
162 "Noto Sans",
163 ] {
164 if !fallbacks.contains(generic) {
165 fallbacks.push(generic);
166 }
167 }
168
169 let style = if italic {
170 fontdb::Style::Italic
171 } else {
172 fontdb::Style::Normal
173 };
174 let weight = if bold {
175 fontdb::Weight::BOLD
176 } else {
177 fontdb::Weight::NORMAL
178 };
179
180 let mut found_id = None;
181 for fallback in &fallbacks {
182 let query = fontdb::Query {
183 families: &[fontdb::Family::Name(fallback)],
184 weight,
185 style,
186 stretch: fontdb::Stretch::Normal,
187 };
188
189 if let Some(id) = self.db.query(&query) {
190 found_id = Some(id);
191 break;
192 }
193 }
194
195 if found_id.is_none() {
197 for generic_family in &[
198 fontdb::Family::SansSerif,
199 fontdb::Family::Serif,
200 fontdb::Family::Monospace,
201 ] {
202 let query = fontdb::Query {
203 families: &[*generic_family],
204 weight,
205 style,
206 stretch: fontdb::Stretch::Normal,
207 };
208 if let Some(id) = self.db.query(&query) {
209 found_id = Some(id);
210 break;
211 }
212 }
213 }
214
215 let db_id = found_id.ok_or_else(|| {
216 LayoutError::FontNotFound(format!("No font found for family '{family_name}'"))
217 })?;
218
219 let font_id = FontId(self.next_id);
220 self.next_id += 1;
221
222 let (data, face_index) = self
224 .db
225 .with_face_data(db_id, |data, idx| (Arc::new(data.to_vec()), idx))
226 .ok_or_else(|| LayoutError::FontParse("Failed to load font data".into()))?;
227
228 let face = ttf_parser::Face::parse(&data, face_index)
229 .map_err(|e| LayoutError::FontParse(format!("ttf-parser error: {e}")))?;
230 let units_per_em = face.units_per_em();
231
232 let actual_family = self
233 .db
234 .face(db_id)
235 .map(|f| {
236 f.families
237 .first()
238 .map(|(name, _)| name.clone())
239 .unwrap_or_else(|| family_name.to_string())
240 })
241 .unwrap_or_else(|| family_name.to_string());
242
243 let idx = self.fonts.len();
244 self.fonts.push(LoadedFont {
245 id: font_id,
246 family: actual_family,
247 bold,
248 italic,
249 data,
250 face_index,
251 units_per_em,
252 });
253 self.cache.insert(key, idx);
254
255 Ok(font_id)
256 }
257
258 pub fn metrics(&self, font_id: FontId, size_pt: f64) -> Result<FontMetrics> {
260 let font = self.get_font(font_id)?;
261 let face = ttf_parser::Face::parse(&font.data, font.face_index)
262 .map_err(|e| LayoutError::FontParse(format!("ttf-parser error: {e}")))?;
263
264 let upem = font.units_per_em as f64;
265 let scale = size_pt / upem;
266
267 Ok(FontMetrics {
268 ascent: face.ascender() as f64 * scale,
269 descent: -(face.descender() as f64) * scale, line_gap: face.line_gap() as f64 * scale,
271 units_per_em: font.units_per_em,
272 })
273 }
274
275 pub fn shape_text(&self, font_id: FontId, text: &str, size_pt: f64) -> Result<ShapedText> {
277 let font = self.get_font(font_id)?;
278
279 let face = rustybuzz::Face::from_slice(&font.data, font.face_index)
280 .ok_or_else(|| LayoutError::Shaping("Failed to create rustybuzz face".into()))?;
281
282 let mut buffer = rustybuzz::UnicodeBuffer::new();
283 buffer.push_str(text);
284
285 let output = rustybuzz::shape(&face, &[], buffer);
286 let infos = output.glyph_infos();
287 let positions = output.glyph_positions();
288
289 let upem = font.units_per_em as f64;
290 let scale = size_pt / upem;
291
292 let mut glyph_ids = Vec::with_capacity(infos.len());
293 let mut advances = Vec::with_capacity(positions.len());
294 let mut total_width = 0.0;
295
296 for (info, pos) in infos.iter().zip(positions.iter()) {
297 glyph_ids.push(info.glyph_id as u16);
298 let advance = pos.x_advance as f64 * scale;
299 advances.push(advance);
300 total_width += advance;
301 }
302
303 Ok(ShapedText {
304 glyph_ids,
305 advances,
306 width: total_width,
307 })
308 }
309
310 pub fn font_data(&self, font_id: FontId) -> Result<crate::output::FontData> {
312 let font = self.get_font(font_id)?;
313 Ok(crate::output::FontData {
314 id: font.id,
315 family: font.family.clone(),
316 data: (*font.data).clone(),
317 face_index: font.face_index,
318 bold: font.bold,
319 italic: font.italic,
320 })
321 }
322
323 pub fn all_font_data(&self) -> Vec<crate::output::FontData> {
325 self.fonts
326 .iter()
327 .map(|f| crate::output::FontData {
328 id: f.id,
329 family: f.family.clone(),
330 data: (*f.data).clone(),
331 face_index: f.face_index,
332 bold: f.bold,
333 italic: f.italic,
334 })
335 .collect()
336 }
337
338 fn get_font(&self, font_id: FontId) -> Result<&LoadedFont> {
339 self.fonts
340 .iter()
341 .find(|f| f.id == font_id)
342 .ok_or_else(|| LayoutError::FontNotFound(format!("FontId({}) not loaded", font_id.0)))
343 }
344}
345
346fn map_font_name(name: &str) -> &[&str] {
353 match name {
354 "Calibri" => &["Calibri", "Carlito"],
355 "Calibri Light" => &["Calibri Light", "Carlito"],
356 "Cambria" => &["Cambria", "Caladea"],
357 "Cambria Math" => &["Cambria Math", "Cambria", "Caladea"],
358 "Arial" => &["Arial", "Liberation Sans", "Helvetica"],
359 "Times New Roman" => &["Times New Roman", "Liberation Serif", "Times"],
360 "Courier New" => &["Courier New", "Liberation Mono", "Courier"],
361 "Consolas" => &["Consolas", "Liberation Mono", "DejaVu Sans Mono"],
362 "Segoe UI" => &["Segoe UI", "Carlito", "Liberation Sans"],
363 "Tahoma" => &["Tahoma", "Liberation Sans", "Helvetica"],
364 "Verdana" => &["Verdana", "Liberation Sans", "DejaVu Sans"],
365 "Georgia" => &["Georgia", "Caladea", "Liberation Serif"],
366 "Palatino Linotype" => &["Palatino Linotype", "Palatino", "Liberation Serif"],
367 "Book Antiqua" => &["Book Antiqua", "Palatino", "Liberation Serif"],
368 "Garamond" => &["Garamond", "Caladea", "Liberation Serif"],
369 "Trebuchet MS" => &["Trebuchet MS", "Liberation Sans", "DejaVu Sans"],
370 "Impact" => &["Impact", "Liberation Sans", "Arial"],
371 "Comic Sans MS" => &["Comic Sans MS", "Liberation Sans", "DejaVu Sans"],
372 "Symbol" => &["Symbol", "DejaVu Sans"],
373 "Wingdings" => &["Wingdings", "Symbol"],
374 _ => &[],
375 }
376}
377
378#[cfg(test)]
379mod tests {
380 use super::*;
381
382 #[test]
383 fn load_system_font() {
384 let mut fm = FontManager::new();
385 let result = fm.resolve_font(None, false, false);
387 if let Ok(id) = result {
389 assert_eq!(id.0, 0);
390 }
391 }
392
393 #[test]
394 fn font_metrics_positive() {
395 let mut fm = FontManager::new();
396 if let Ok(id) = fm.resolve_font(None, false, false) {
397 let metrics = fm.metrics(id, 12.0).unwrap();
398 assert!(metrics.ascent > 0.0);
399 assert!(metrics.descent > 0.0);
400 assert!(metrics.units_per_em > 0);
401 }
402 }
403
404 #[test]
405 fn shape_hello_world() {
406 let mut fm = FontManager::new();
407 if let Ok(id) = fm.resolve_font(None, false, false) {
408 let shaped = fm.shape_text(id, "Hello World", 12.0).unwrap();
409 assert!(!shaped.glyph_ids.is_empty());
410 assert_eq!(shaped.glyph_ids.len(), shaped.advances.len());
411 assert!(shaped.width > 0.0);
412 }
413 }
414
415 #[test]
416 fn font_caching() {
417 let mut fm = FontManager::new();
418 if let Ok(id1) = fm.resolve_font(Some("Arial"), false, false) {
419 let id2 = fm.resolve_font(Some("Arial"), false, false).unwrap();
420 assert_eq!(id1, id2);
421 }
422 }
423
424 #[test]
425 fn bold_italic_variants() {
426 let mut fm = FontManager::new();
427 let regular = fm.resolve_font(None, false, false);
428 let bold = fm.resolve_font(None, true, false);
429 if let (Ok(r), Ok(b)) = (regular, bold) {
430 assert_ne!(r, b);
432 }
433 }
434}