1use std::collections::HashMap;
12use std::fs;
13use std::path::{Path, PathBuf};
14use std::sync::Arc;
15
16use read_fonts::{FontRef as ReadFontRef, TableProvider};
17
18use typf_core::{
19 error::{FontLoadError, Result},
20 traits::FontRef as TypfFontRef,
21 types::{FontMetrics, VariationAxis},
22};
23
24#[derive(Clone, Debug)]
26pub struct TypfFontSource {
27 path: Option<PathBuf>,
28 face_index: u32,
29}
30
31impl TypfFontSource {
32 pub fn new(path: Option<PathBuf>, face_index: u32) -> Self {
33 Self { path, face_index }
34 }
35
36 pub fn path(&self) -> Option<&Path> {
37 self.path.as_deref()
38 }
39
40 pub fn face_index(&self) -> u32 {
41 self.face_index
42 }
43}
44
45pub struct TypfFontFace {
51 data: Arc<Vec<u8>>,
52 source: TypfFontSource,
53 units_per_em: u16,
54 metrics: FontMetrics,
55}
56
57impl TypfFontFace {
58 pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
60 Self::from_file_index(path, 0)
61 }
62
63 pub fn from_file_index(path: impl AsRef<Path>, face_index: u32) -> Result<Self> {
65 let data = fs::read(path.as_ref())
66 .map_err(|_| FontLoadError::FileNotFound(path.as_ref().display().to_string()))?;
67
68 Self::from_data_index_with_path(data, face_index, Some(path.as_ref().to_path_buf()))
69 }
70
71 pub fn from_data(data: Vec<u8>) -> Result<Self> {
73 Self::from_data_index(data, 0)
74 }
75
76 pub fn from_data_index(data: Vec<u8>, face_index: u32) -> Result<Self> {
78 Self::from_data_index_with_path(data, face_index, None)
79 }
80
81 fn from_data_index_with_path(
82 data: Vec<u8>,
83 face_index: u32,
84 path: Option<PathBuf>,
85 ) -> Result<Self> {
86 let font_ref = ReadFontRef::from_index(data.as_slice(), face_index)
87 .map_err(|_| FontLoadError::InvalidData)?;
88
89 let units_per_em = font_ref
90 .head()
91 .map(|head| head.units_per_em())
92 .unwrap_or(1000);
93
94 let (ascent, descent, line_gap) = font_ref
95 .os2()
96 .ok()
97 .map(|os2| {
98 (
99 os2.s_typo_ascender(),
100 os2.s_typo_descender(),
101 os2.s_typo_line_gap(),
102 )
103 })
104 .or_else(|| {
105 font_ref.hhea().ok().map(|hhea| {
106 (
107 hhea.ascender().to_i16(),
108 hhea.descender().to_i16(),
109 hhea.line_gap().to_i16(),
110 )
111 })
112 })
113 .unwrap_or((0, 0, 0));
114
115 Ok(TypfFontFace {
116 data: Arc::new(data),
117 source: TypfFontSource::new(path, face_index),
118 units_per_em,
119 metrics: FontMetrics {
120 units_per_em,
121 ascent,
122 descent,
123 line_gap,
124 },
125 })
126 }
127
128 pub fn source(&self) -> &TypfFontSource {
129 &self.source
130 }
131
132 pub fn face_index(&self) -> u32 {
133 self.source.face_index
134 }
135
136 pub fn path(&self) -> Option<&Path> {
137 self.source.path()
138 }
139
140 fn font_ref(&self) -> Option<ReadFontRef<'_>> {
141 ReadFontRef::from_index(self.data.as_slice(), self.source.face_index).ok()
142 }
143
144 pub fn glyph_id(&self, ch: char) -> Option<u32> {
145 self.font_ref()
146 .and_then(|font| font.cmap().ok()?.map_codepoint(ch).map(|gid| gid.to_u32()))
147 }
148
149 pub fn advance_width(&self, glyph_id: u32) -> f32 {
154 self.font_ref()
155 .and_then(|font| {
156 let hmtx = font.hmtx().ok()?;
157
158 use read_fonts::types::GlyphId;
159 let glyph = GlyphId::new(glyph_id);
160 let advance = hmtx.advance(glyph)?;
161
162 let upem = self.units_per_em as f32;
163 Some(advance as f32 / upem * 1000.0)
164 })
165 .unwrap_or(500.0)
166 }
167
168 pub fn glyph_count(&self) -> Option<u32> {
169 self.font_ref()
170 .and_then(|font| font.maxp().ok().map(|maxp| maxp.num_glyphs() as u32))
171 }
172
173 pub fn variation_axes(&self) -> Option<Vec<VariationAxis>> {
175 let font = self.font_ref()?;
176 let fvar = font.fvar().ok()?;
177 let axes_slice = fvar.axes().ok()?;
178 let name_table = font.name().ok();
179
180 let axes: Vec<VariationAxis> = axes_slice
181 .iter()
182 .map(|axis| {
183 let tag_bytes = axis.axis_tag().into_bytes();
184 let tag = String::from_utf8_lossy(&tag_bytes).to_string();
185
186 let name = name_table.as_ref().and_then(|nt| {
187 let name_id = axis.axis_name_id();
188 nt.name_record()
189 .iter()
190 .find(|record| record.name_id() == name_id)
191 .and_then(|record| record.string(nt.string_data()).ok())
192 .map(|s| s.to_string())
193 });
194
195 let hidden = axis.flags() & 0x0001 != 0;
196
197 VariationAxis {
198 tag,
199 name,
200 min_value: axis.min_value().to_f32(),
201 default_value: axis.default_value().to_f32(),
202 max_value: axis.max_value().to_f32(),
203 hidden,
204 }
205 })
206 .collect();
207
208 Some(axes)
209 }
210}
211
212impl TypfFontRef for TypfFontFace {
213 fn data(&self) -> &[u8] {
214 self.data.as_slice()
215 }
216
217 fn data_shared(&self) -> Option<Arc<dyn AsRef<[u8]> + Send + Sync>> {
218 Some(self.data.clone())
219 }
220
221 fn units_per_em(&self) -> u16 {
222 self.units_per_em
223 }
224
225 fn metrics(&self) -> Option<FontMetrics> {
226 Some(self.metrics)
227 }
228
229 fn glyph_id(&self, ch: char) -> Option<u32> {
230 self.glyph_id(ch)
231 }
232
233 fn advance_width(&self, glyph_id: u32) -> f32 {
234 self.advance_width(glyph_id)
235 }
236
237 fn glyph_count(&self) -> Option<u32> {
238 self.glyph_count()
239 }
240
241 fn variation_axes(&self) -> Option<Vec<VariationAxis>> {
242 self.variation_axes()
243 }
244}
245
246pub struct FontDatabase {
248 fonts: Vec<Arc<TypfFontFace>>,
249 sources: Vec<TypfFontSource>,
250 path_cache: HashMap<(PathBuf, u32), Arc<TypfFontFace>>,
251 default_font: Option<Arc<TypfFontFace>>,
252}
253
254impl FontDatabase {
255 pub fn new() -> Self {
257 Self {
258 fonts: Vec::new(),
259 sources: Vec::new(),
260 path_cache: HashMap::new(),
261 default_font: None,
262 }
263 }
264
265 pub fn load_font(&mut self, path: impl AsRef<Path>) -> Result<Arc<TypfFontFace>> {
267 let path = path.as_ref();
268
269 let canonical = path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
270 let face_index = 0;
271 let cache_key = (canonical.clone(), face_index);
272
273 if let Some(font) = self.path_cache.get(&cache_key) {
274 return Ok(font.clone());
275 }
276
277 let font = Arc::new(TypfFontFace::from_file(path)?);
278 self.path_cache.insert(cache_key, font.clone());
279 self.fonts.push(font.clone());
280 self.sources
281 .push(TypfFontSource::new(Some(canonical), face_index));
282
283 if self.default_font.is_none() {
284 self.default_font = Some(font.clone());
285 }
286
287 Ok(font)
288 }
289
290 pub fn load_font_data(&mut self, data: Vec<u8>) -> Result<Arc<TypfFontFace>> {
291 let font = Arc::new(TypfFontFace::from_data(data)?);
292 self.fonts.push(font.clone());
293 self.sources
294 .push(TypfFontSource::new(None, font.face_index()));
295
296 if self.default_font.is_none() {
297 self.default_font = Some(font.clone());
298 }
299
300 Ok(font)
301 }
302
303 pub fn default_font(&self) -> Option<Arc<TypfFontFace>> {
304 self.default_font.clone()
305 }
306
307 pub fn fonts(&self) -> &[Arc<TypfFontFace>] {
308 &self.fonts
309 }
310
311 pub fn sources(&self) -> &[TypfFontSource] {
312 &self.sources
313 }
314
315 pub fn find_font(&self, _name: &str) -> Option<Arc<TypfFontFace>> {
320 self.default_font.clone()
321 }
322
323 pub fn clear(&mut self) {
324 self.fonts.clear();
325 self.sources.clear();
326 self.path_cache.clear();
327 self.default_font = None;
328 }
329
330 pub fn font_count(&self) -> usize {
331 self.fonts.len()
332 }
333}
334
335impl Default for FontDatabase {
336 fn default() -> Self {
337 Self::new()
338 }
339}
340
341#[cfg(test)]
342mod tests {
343 use super::*;
344 use typf_core::traits::FontRef;
345
346 #[test]
347 fn test_empty_database() {
348 let db = FontDatabase::new();
349 assert!(db.default_font().is_none());
350 assert_eq!(db.fonts().len(), 0);
351 assert_eq!(db.sources().len(), 0);
352 assert_eq!(db.font_count(), 0);
353 }
354
355 #[test]
356 fn test_font_from_data() {
357 let data = vec![0; 100];
359 let result = TypfFontFace::from_data(data);
360 assert!(result.is_err());
362 }
363
364 #[test]
365 fn test_font_from_data_index_invalid() {
366 let data = vec![0; 100];
368 let result = TypfFontFace::from_data_index(data, 5);
369 assert!(result.is_err());
370 }
371
372 #[test]
373 fn test_clear_database() {
374 let mut db = FontDatabase::new();
375 db.clear();
377 assert!(db.default_font().is_none());
378 assert_eq!(db.sources().len(), 0);
379 assert_eq!(db.font_count(), 0);
380 }
381
382 #[test]
383 fn test_face_index_default() {
384 let data = vec![0; 100];
387 let result = TypfFontFace::from_data_index(data, 0);
388 assert!(result.is_err());
390 }
391
392 #[test]
393 fn test_typf_font_face_data_shared_when_loaded_then_some() {
394 let font_path = concat!(
395 env!("CARGO_MANIFEST_DIR"),
396 "/../../test-fonts/NotoSans-Regular.ttf"
397 );
398 let Ok(font) = TypfFontFace::from_file(font_path) else {
399 return;
400 };
401
402 let shared = font.data_shared();
403 assert!(
404 shared.is_some(),
405 "TypfFontFace should provide shared font bytes to avoid copies"
406 );
407
408 if let Some(shared) = shared {
409 assert_eq!(
410 shared.as_ref().as_ref(),
411 font.data(),
412 "shared bytes must match FontRef::data()"
413 );
414 }
415 }
416}