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 ascender: i16,
55 descender: i16,
56 line_gap: i16,
57 shaper_data: harfrust::ShaperData,
60}
61
62pub struct FontManager {
64 db: fontdb::Database,
65 cache: HashMap<FontKey, usize>,
67 fonts: Vec<LoadedFont>,
69 next_id: u32,
71}
72
73impl Default for FontManager {
74 fn default() -> Self {
75 Self::new()
76 }
77}
78
79impl FontManager {
80 pub fn new() -> Self {
85 let mut db = fontdb::Database::new();
86
87 for (_family, data) in crate::bundled_fonts::bundled_font_data() {
89 db.load_font_data(data.to_vec());
90 }
91
92 db.load_system_fonts();
94
95 FontManager {
96 db,
97 cache: HashMap::new(),
98 fonts: Vec::new(),
99 next_id: 0,
100 }
101 }
102
103 pub fn load_additional_fonts(&mut self, font_files: &[crate::input::FontFile]) {
108 for font_file in font_files {
109 self.db.load_font_data(font_file.data.clone());
110 }
111 self.cache.clear();
113 }
114
115 pub fn new_with_fonts(fonts: Vec<(String, Vec<u8>)>) -> Self {
120 let mut db = fontdb::Database::new();
121 for (_name, data) in &fonts {
122 db.load_font_data(data.clone());
123 }
124 FontManager {
125 db,
126 cache: HashMap::new(),
127 fonts: Vec::new(),
128 next_id: 0,
129 }
130 }
131
132 pub fn resolve_font(
135 &mut self,
136 family: Option<&str>,
137 bold: bool,
138 italic: bool,
139 ) -> Result<FontId> {
140 let family_name = family.unwrap_or("Arial");
141
142 let key = FontKey {
143 family: family_name.to_string(),
144 bold,
145 italic,
146 };
147
148 if let Some(&idx) = self.cache.get(&key) {
149 return Ok(self.fonts[idx].id);
150 }
151
152 let mapped = map_font_name(family_name);
154
155 let mut fallbacks: Vec<&str> = Vec::with_capacity(10);
157 fallbacks.push(family_name);
158 for alt in mapped {
159 if *alt != family_name {
160 fallbacks.push(alt);
161 }
162 }
163 for generic in &[
164 "Carlito",
165 "Arial",
166 "Liberation Sans",
167 "Helvetica",
168 "DejaVu Sans",
169 "Noto Sans",
170 ] {
171 if !fallbacks.contains(generic) {
172 fallbacks.push(generic);
173 }
174 }
175
176 let style = if italic {
177 fontdb::Style::Italic
178 } else {
179 fontdb::Style::Normal
180 };
181 let weight = if bold {
182 fontdb::Weight::BOLD
183 } else {
184 fontdb::Weight::NORMAL
185 };
186
187 let mut found_id = None;
188 for fallback in &fallbacks {
189 let query = fontdb::Query {
190 families: &[fontdb::Family::Name(fallback)],
191 weight,
192 style,
193 stretch: fontdb::Stretch::Normal,
194 };
195
196 if let Some(id) = self.db.query(&query) {
197 found_id = Some(id);
198 break;
199 }
200 }
201
202 if found_id.is_none() {
204 for generic_family in &[
205 fontdb::Family::SansSerif,
206 fontdb::Family::Serif,
207 fontdb::Family::Monospace,
208 ] {
209 let query = fontdb::Query {
210 families: &[*generic_family],
211 weight,
212 style,
213 stretch: fontdb::Stretch::Normal,
214 };
215 if let Some(id) = self.db.query(&query) {
216 found_id = Some(id);
217 break;
218 }
219 }
220 }
221
222 let db_id = found_id.ok_or_else(|| {
223 LayoutError::FontNotFound(format!("No font found for family '{family_name}'"))
224 })?;
225
226 let font_id = FontId(self.next_id);
227 self.next_id += 1;
228
229 let (data, face_index) = self
231 .db
232 .with_face_data(db_id, |data, idx| (Arc::new(data.to_vec()), idx))
233 .ok_or_else(|| LayoutError::FontParse("Failed to load font data".into()))?;
234
235 let (units_per_em, ascender, descender, line_gap) = {
236 let face = ttf_parser::Face::parse(&data, face_index)
237 .map_err(|e| LayoutError::FontParse(format!("ttf-parser error: {e}")))?;
238 (
239 face.units_per_em(),
240 face.ascender(),
241 face.descender(),
242 face.line_gap(),
243 )
244 };
245
246 if units_per_em == 0 {
249 return Err(LayoutError::FontParse(format!(
250 "font '{family_name}' declares zero units per em"
251 )));
252 }
253
254 let shaper_data = {
255 let face = harfrust::FontRef::from_index(&data, face_index)
256 .map_err(|e| LayoutError::FontParse(format!("failed to read font face: {e}")))?;
257 harfrust::ShaperData::new(&face)
258 };
259
260 let actual_family = self
261 .db
262 .face(db_id)
263 .map(|f| {
264 f.families
265 .first()
266 .map(|(name, _)| name.clone())
267 .unwrap_or_else(|| family_name.to_string())
268 })
269 .unwrap_or_else(|| family_name.to_string());
270
271 let idx = self.fonts.len();
272 self.fonts.push(LoadedFont {
273 id: font_id,
274 family: actual_family,
275 bold,
276 italic,
277 data,
278 face_index,
279 units_per_em,
280 ascender,
281 descender,
282 line_gap,
283 shaper_data,
284 });
285 self.cache.insert(key, idx);
286
287 Ok(font_id)
288 }
289
290 pub fn metrics(&self, font_id: FontId, size_pt: f64) -> Result<FontMetrics> {
292 let font = self.get_font(font_id)?;
293 let scale = size_pt / font.units_per_em as f64;
294
295 Ok(FontMetrics {
296 ascent: font.ascender as f64 * scale,
297 descent: -(font.descender as f64) * scale, line_gap: font.line_gap as f64 * scale,
299 units_per_em: font.units_per_em,
300 })
301 }
302
303 pub fn shape_text(&self, font_id: FontId, text: &str, size_pt: f64) -> Result<ShapedText> {
305 if text.is_empty() {
308 return Ok(ShapedText {
309 glyph_ids: Vec::new(),
310 advances: Vec::new(),
311 width: 0.0,
312 });
313 }
314
315 let font = self.get_font(font_id)?;
316
317 let face = harfrust::FontRef::from_index(&font.data, font.face_index)
318 .map_err(|e| LayoutError::Shaping(format!("failed to read font face: {e}")))?;
319
320 let shaper = font.shaper_data.shaper(&face).build();
321
322 let mut buffer = harfrust::UnicodeBuffer::new();
323 buffer.push_str(text);
324 buffer.guess_segment_properties();
327
328 let output = shaper.shape(buffer, harfrust::ShapeOptions::default());
329 let infos = output.glyph_infos();
330 let positions = output.glyph_positions();
331
332 let upem = font.units_per_em as f64;
333 let scale = size_pt / upem;
334
335 let mut glyph_ids = Vec::with_capacity(infos.len());
336 let mut advances = Vec::with_capacity(positions.len());
337 let mut total_width = 0.0;
338
339 for (info, pos) in infos.iter().zip(positions.iter()) {
340 glyph_ids.push(info.glyph_id as u16);
341 let advance = pos.x_advance as f64 * scale;
342 advances.push(advance);
343 total_width += advance;
344 }
345
346 Ok(ShapedText {
347 glyph_ids,
348 advances,
349 width: total_width,
350 })
351 }
352
353 pub fn font_data(&self, font_id: FontId) -> Result<crate::output::FontData> {
355 let font = self.get_font(font_id)?;
356 Ok(crate::output::FontData {
357 id: font.id,
358 family: font.family.clone(),
359 data: (*font.data).clone(),
360 face_index: font.face_index,
361 bold: font.bold,
362 italic: font.italic,
363 })
364 }
365
366 pub fn all_font_data(&self) -> Vec<crate::output::FontData> {
368 self.fonts
369 .iter()
370 .map(|f| crate::output::FontData {
371 id: f.id,
372 family: f.family.clone(),
373 data: (*f.data).clone(),
374 face_index: f.face_index,
375 bold: f.bold,
376 italic: f.italic,
377 })
378 .collect()
379 }
380
381 fn get_font(&self, font_id: FontId) -> Result<&LoadedFont> {
382 self.fonts
383 .iter()
384 .find(|f| f.id == font_id)
385 .ok_or_else(|| LayoutError::FontNotFound(format!("FontId({}) not loaded", font_id.0)))
386 }
387}
388
389fn map_font_name(name: &str) -> &[&str] {
396 match name {
397 "Calibri" => &["Calibri", "Carlito"],
398 "Calibri Light" => &["Calibri Light", "Carlito"],
399 "Cambria" => &["Cambria", "Caladea"],
400 "Cambria Math" => &["Cambria Math", "Cambria", "Caladea"],
401 "Arial" => &["Arial", "Liberation Sans", "Helvetica"],
402 "Times New Roman" => &["Times New Roman", "Liberation Serif", "Times"],
403 "Courier New" => &["Courier New", "Liberation Mono", "Courier"],
404 "Consolas" => &["Consolas", "Liberation Mono", "DejaVu Sans Mono"],
405 "Segoe UI" => &["Segoe UI", "Carlito", "Liberation Sans"],
406 "Tahoma" => &["Tahoma", "Liberation Sans", "Helvetica"],
407 "Verdana" => &["Verdana", "Liberation Sans", "DejaVu Sans"],
408 "Georgia" => &["Georgia", "Caladea", "Liberation Serif"],
409 "Palatino Linotype" => &["Palatino Linotype", "Palatino", "Liberation Serif"],
410 "Book Antiqua" => &["Book Antiqua", "Palatino", "Liberation Serif"],
411 "Garamond" => &["Garamond", "Caladea", "Liberation Serif"],
412 "Trebuchet MS" => &["Trebuchet MS", "Liberation Sans", "DejaVu Sans"],
413 "Impact" => &["Impact", "Liberation Sans", "Arial"],
414 "Comic Sans MS" => &["Comic Sans MS", "Liberation Sans", "DejaVu Sans"],
415 "Symbol" => &["Symbol", "DejaVu Sans"],
416 "Wingdings" => &["Wingdings", "Symbol"],
417 _ => &[],
418 }
419}
420
421#[cfg(test)]
422mod tests {
423 use super::*;
424
425 #[test]
426 fn load_system_font() {
427 let mut fm = FontManager::new();
428 let result = fm.resolve_font(None, false, false);
430 if let Ok(id) = result {
432 assert_eq!(id.0, 0);
433 }
434 }
435
436 #[test]
437 fn font_metrics_positive() {
438 let mut fm = FontManager::new();
439 if let Ok(id) = fm.resolve_font(None, false, false) {
440 let metrics = fm.metrics(id, 12.0).unwrap();
441 assert!(metrics.ascent > 0.0);
442 assert!(metrics.descent > 0.0);
443 assert!(metrics.units_per_em > 0);
444 }
445 }
446
447 #[test]
448 fn shape_hello_world() {
449 let mut fm = FontManager::new();
450 if let Ok(id) = fm.resolve_font(None, false, false) {
451 let shaped = fm.shape_text(id, "Hello World", 12.0).unwrap();
452 assert!(!shaped.glyph_ids.is_empty());
453 assert_eq!(shaped.glyph_ids.len(), shaped.advances.len());
454 assert!(shaped.width > 0.0);
455 }
456 }
457
458 #[test]
459 fn font_caching() {
460 let mut fm = FontManager::new();
461 if let Ok(id1) = fm.resolve_font(Some("Arial"), false, false) {
462 let id2 = fm.resolve_font(Some("Arial"), false, false).unwrap();
463 assert_eq!(id1, id2);
464 }
465 }
466
467 #[test]
468 fn bold_italic_variants() {
469 let mut fm = FontManager::new();
470 let regular = fm.resolve_font(None, false, false);
471 let bold = fm.resolve_font(None, true, false);
472 if let (Ok(r), Ok(b)) = (regular, bold) {
473 assert_ne!(r, b);
475 }
476 }
477}