Skip to main content

text_typeset/font/
registry.rs

1use std::sync::Arc;
2
3use fontdb::{Database, Family, Query, Source, Style, Weight};
4
5use crate::types::FontFaceId;
6
7pub struct FontEntry {
8    pub fontdb_id: fontdb::ID,
9    pub face_index: u32,
10    pub data: Arc<Vec<u8>>,
11    pub swash_cache_key: swash::CacheKey,
12}
13
14pub struct FontRegistry {
15    fontdb: Database,
16    fonts: Vec<Option<FontEntry>>,
17    generic_families: std::collections::HashMap<String, String>,
18    default_font: Option<FontFaceId>,
19    default_size_px: f32,
20}
21
22impl Default for FontRegistry {
23    fn default() -> Self {
24        Self::new()
25    }
26}
27
28impl FontRegistry {
29    pub fn new() -> Self {
30        Self {
31            fontdb: Database::new(),
32            fonts: Vec::new(),
33            generic_families: std::collections::HashMap::new(),
34            default_font: None,
35            default_size_px: 16.0,
36        }
37    }
38
39    /// Register a font from raw bytes. Returns IDs for all faces found
40    /// (font collections may contain multiple faces).
41    pub fn register_font(&mut self, data: &[u8]) -> Vec<FontFaceId> {
42        let arc_data: Arc<Vec<u8>> = Arc::new(data.to_vec());
43        let source = Source::Binary(arc_data.clone());
44        let fontdb_ids = self.fontdb.load_font_source(source);
45
46        let mut face_ids = Vec::new();
47        for fontdb_id in fontdb_ids {
48            let face_index = self.fontdb.face(fontdb_id).map(|f| f.index).unwrap_or(0);
49
50            let swash_cache_key = swash::CacheKey::new();
51            let entry = FontEntry {
52                fontdb_id,
53                face_index,
54                data: arc_data.clone(),
55                swash_cache_key,
56            };
57
58            let face_id = FontFaceId(self.fonts.len() as u32);
59            self.fonts.push(Some(entry));
60            face_ids.push(face_id);
61        }
62        face_ids
63    }
64
65    /// Register a font with explicit metadata, overriding the font's name table.
66    pub fn register_font_as(
67        &mut self,
68        data: &[u8],
69        family: &str,
70        weight: u16,
71        italic: bool,
72    ) -> Vec<FontFaceId> {
73        let arc_data: Arc<Vec<u8>> = Arc::new(data.to_vec());
74        let source = Source::Binary(arc_data.clone());
75        let fontdb_ids = self.fontdb.load_font_source(source);
76
77        let mut face_ids = Vec::new();
78        for fontdb_id in fontdb_ids {
79            // Override metadata in fontdb
80            if let Some(face_info) = self.fontdb.face(fontdb_id) {
81                let mut info = face_info.clone();
82                info.families = vec![(family.to_string(), fontdb::Language::English_UnitedStates)];
83                info.weight = Weight(weight);
84                info.style = if italic { Style::Italic } else { Style::Normal };
85                // Remove old and re-add with new metadata
86                let face_index = info.index;
87                self.fontdb.remove_face(fontdb_id);
88                let new_id = self.fontdb.push_face_info(info);
89
90                let swash_cache_key = swash::CacheKey::new();
91                let entry = FontEntry {
92                    fontdb_id: new_id,
93                    face_index,
94                    data: arc_data.clone(),
95                    swash_cache_key,
96                };
97
98                let face_id = FontFaceId(self.fonts.len() as u32);
99                self.fonts.push(Some(entry));
100                face_ids.push(face_id);
101            }
102        }
103        face_ids
104    }
105
106    pub fn set_default_font(&mut self, face: FontFaceId, size_px: f32) {
107        self.default_font = Some(face);
108        self.default_size_px = size_px;
109    }
110
111    pub fn default_font(&self) -> Option<FontFaceId> {
112        self.default_font
113    }
114
115    pub fn default_size_px(&self) -> f32 {
116        self.default_size_px
117    }
118
119    pub fn set_generic_family(&mut self, generic: &str, family: &str) {
120        self.generic_families
121            .insert(generic.to_string(), family.to_string());
122    }
123
124    /// Resolve a family name, mapping generic names (serif, monospace, etc.)
125    /// to their configured concrete family names.
126    pub fn resolve_family_name<'a>(&'a self, family: &'a str) -> &'a str {
127        self.generic_families
128            .get(family)
129            .map(|s| s.as_str())
130            .unwrap_or(family)
131    }
132
133    /// Query fontdb for a font matching the given criteria.
134    pub fn query_font(&self, family: &str, weight: u16, italic: bool) -> Option<FontFaceId> {
135        let resolved = self.resolve_family_name(family);
136        let style = if italic { Style::Italic } else { Style::Normal };
137
138        let query = Query {
139            families: &[Family::Name(resolved)],
140            weight: Weight(weight),
141            style,
142            ..Query::default()
143        };
144
145        let fontdb_id = self.fontdb.query(&query)?;
146        self.fontdb_id_to_face_id(fontdb_id)
147    }
148
149    /// Look up a FontEntry by FontFaceId.
150    pub fn get(&self, face_id: FontFaceId) -> Option<&FontEntry> {
151        self.fonts
152            .get(face_id.0 as usize)
153            .and_then(|opt| opt.as_ref())
154    }
155
156    /// Query for a variant (different weight/style) of an existing registered font.
157    /// Looks up the family name of `base_face` and queries fontdb for a match.
158    pub fn query_variant(
159        &self,
160        base_face: FontFaceId,
161        weight: u16,
162        italic: bool,
163    ) -> Option<FontFaceId> {
164        let entry = self.get(base_face)?;
165        let face_info = self.fontdb.face(entry.fontdb_id)?;
166        let family_name = face_info.families.first().map(|(name, _)| name.as_str())?;
167        self.query_font(family_name, weight, italic)
168    }
169
170    /// Find our FontFaceId for a fontdb ID.
171    fn fontdb_id_to_face_id(&self, fontdb_id: fontdb::ID) -> Option<FontFaceId> {
172        self.fonts.iter().enumerate().find_map(|(i, entry)| {
173            entry
174                .as_ref()
175                .filter(|e| e.fontdb_id == fontdb_id)
176                .map(|_| FontFaceId(i as u32))
177        })
178    }
179
180    /// Iterate all registered font entries for glyph fallback.
181    pub fn all_entries(&self) -> impl Iterator<Item = (FontFaceId, &FontEntry)> {
182        self.fonts
183            .iter()
184            .enumerate()
185            .filter_map(|(i, opt)| opt.as_ref().map(|entry| (FontFaceId(i as u32), entry)))
186    }
187}