kas_text/fonts/
library.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! Font library
7
8#![allow(clippy::len_without_is_empty)]
9
10use super::{FaceRef, FontSelector, Resolver};
11use crate::conv::{to_u32, to_usize};
12use fontique::{Blob, QueryStatus, Script, Synthesis};
13use std::collections::hash_map::{Entry, HashMap};
14use std::sync::{LazyLock, Mutex, MutexGuard, RwLock};
15use thiserror::Error;
16pub(crate) use ttf_parser::Face;
17
18/// Font loading errors
19#[derive(Error, Debug)]
20enum FontError {
21    #[error("font load error")]
22    TtfParser(#[from] ttf_parser::FaceParsingError),
23    #[cfg(feature = "ab_glyph")]
24    #[error("font load error")]
25    AbGlyph(#[from] ab_glyph::InvalidFont),
26    #[error("font load error")]
27    Swash,
28}
29
30/// Bad [`FontId`] or no font loaded
31///
32/// This error should be impossible to observe, but exists to avoid panic in
33/// lower level methods.
34#[derive(Error, Debug)]
35#[error("invalid FontId")]
36pub struct InvalidFontId;
37
38/// No matching font found
39///
40/// Text layout failed.
41#[derive(Error, Debug)]
42#[error("no font match")]
43pub struct NoFontMatch;
44
45/// Font face identifier
46///
47/// Identifies a loaded font face within the [`FontLibrary`] by index.
48#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
49pub struct FaceId(pub(crate) u32);
50impl FaceId {
51    /// Get as `usize`
52    pub fn get(self) -> usize {
53        to_usize(self.0)
54    }
55}
56
57impl From<u32> for FaceId {
58    fn from(id: u32) -> Self {
59        FaceId(id)
60    }
61}
62
63/// Font face identifier
64///
65/// Identifies a font list within the [`FontLibrary`] by index.
66#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
67pub struct FontId(u32);
68impl FontId {
69    /// Get as `usize`
70    pub fn get(self) -> usize {
71        to_usize(self.0)
72    }
73}
74
75/// A store of data for a font face, supporting various backends
76pub struct FaceStore {
77    blob: Blob<u8>,
78    index: u32,
79    face: Face<'static>,
80    #[cfg(feature = "rustybuzz")]
81    rustybuzz: rustybuzz::Face<'static>,
82    #[cfg(feature = "ab_glyph")]
83    ab_glyph: ab_glyph::FontRef<'static>,
84    swash: (u32, swash::CacheKey), // (offset, key)
85    synthesis: Synthesis,
86}
87
88impl FaceStore {
89    /// Construct, given a file path, a reference to the loaded data and the face index
90    ///
91    /// The `path` is to be stored; its contents are already loaded in `data`.
92    fn new(blob: Blob<u8>, index: u32, synthesis: Synthesis) -> Result<Self, FontError> {
93        // Safety: this is a private fn used to construct a FaceStore instance
94        // to be stored in FontLibrary which is never deallocated. This
95        // FaceStore holds onto `blob`, so `data` is valid until program exit.
96        let data = unsafe { extend_lifetime(blob.data()) };
97
98        let face = Face::parse(data, index)?;
99
100        Ok(FaceStore {
101            blob,
102            index,
103            #[cfg(feature = "rustybuzz")]
104            rustybuzz: {
105                use {rustybuzz::Variation, ttf_parser::Tag};
106
107                let len = synthesis.variation_settings().len();
108                debug_assert!(len <= 3);
109                let mut vars = [Variation {
110                    tag: Tag(0),
111                    value: 0.0,
112                }; 3];
113                for (r, (tag, value)) in vars.iter_mut().zip(synthesis.variation_settings()) {
114                    r.tag = Tag::from_bytes(&tag.to_be_bytes());
115                    r.value = *value;
116                }
117
118                let mut rustybuzz = rustybuzz::Face::from_face(face.clone());
119                rustybuzz.set_variations(&vars[0..len]);
120                rustybuzz
121            },
122            face,
123            #[cfg(feature = "ab_glyph")]
124            ab_glyph: {
125                let mut font = ab_glyph::FontRef::try_from_slice_and_index(data, index)?;
126                for (tag, value) in synthesis.variation_settings() {
127                    ab_glyph::VariableFont::set_variation(&mut font, &tag.to_be_bytes(), *value);
128                }
129                font
130            },
131            swash: {
132                use easy_cast::Cast;
133                let f = swash::FontRef::from_index(data, index.cast()).ok_or(FontError::Swash)?;
134                (f.offset, f.key)
135            },
136            synthesis,
137        })
138    }
139
140    /// Access the [`Face`] object
141    pub fn face(&self) -> &Face<'static> {
142        &self.face
143    }
144
145    /// Access a [`FaceRef`] object
146    pub fn face_ref(&self) -> FaceRef<'_> {
147        FaceRef(&self.face)
148    }
149
150    /// Access the [`rustybuzz`] object
151    #[cfg(feature = "rustybuzz")]
152    pub fn rustybuzz(&self) -> &rustybuzz::Face<'static> {
153        &self.rustybuzz
154    }
155
156    /// Access the [`ab_glyph`] object
157    #[cfg(feature = "ab_glyph")]
158    pub fn ab_glyph(&self) -> &ab_glyph::FontRef<'static> {
159        &self.ab_glyph
160    }
161
162    /// Get a swash `FontRef`
163    pub fn swash(&self) -> swash::FontRef<'_> {
164        swash::FontRef {
165            data: self.face.raw_face().data,
166            offset: self.swash.0,
167            key: self.swash.1,
168        }
169    }
170
171    /// Get font variation settings
172    pub fn synthesis(&self) -> &Synthesis {
173        &self.synthesis
174    }
175}
176
177#[derive(Default)]
178struct FaceList {
179    // Safety: unsafe code depends on entries never moving (hence the otherwise
180    // redundant use of Box). See e.g. FontLibrary::get_face().
181    #[allow(clippy::vec_box)]
182    faces: Vec<Box<FaceStore>>,
183    // These are vec-maps. Why? Because length should be short.
184    source_hash: Vec<(u64, FaceId)>,
185}
186
187impl FaceList {
188    fn push(&mut self, face: Box<FaceStore>, source_hash: u64) -> FaceId {
189        let id = FaceId(to_u32(self.faces.len()));
190        self.faces.push(face);
191        self.source_hash.push((source_hash, id));
192        id
193    }
194}
195
196#[derive(Default)]
197struct FontList {
198    // A "font" is a list of faces (primary + fallbacks); we cache glyph-lookups per char
199    fonts: Vec<(FontId, Vec<FaceId>, HashMap<char, Option<FaceId>>)>,
200    sel_hash: Vec<(u64, FontId)>,
201}
202
203impl FontList {
204    fn push(&mut self, list: Vec<FaceId>, sel_hash: u64) -> FontId {
205        let id = FontId(to_u32(self.fonts.len()));
206        self.fonts.push((id, list, HashMap::new()));
207        self.sel_hash.push((sel_hash, id));
208        id
209    }
210}
211
212/// Library of loaded fonts
213///
214/// This is the type of the global singleton accessible via the [`library()`]
215/// function. Thread-safety is handled via internal locks.
216pub struct FontLibrary {
217    resolver: Mutex<Resolver>,
218    faces: RwLock<FaceList>,
219    fonts: RwLock<FontList>,
220}
221
222/// Font management
223impl FontLibrary {
224    /// Get a reference to the font resolver
225    pub fn resolver(&self) -> MutexGuard<'_, Resolver> {
226        self.resolver.lock().unwrap()
227    }
228
229    /// Get the first face for a font
230    ///
231    /// Each font identifier has at least one font face. This resolves the first
232    /// (default) one.
233    pub fn first_face_for(&self, font_id: FontId) -> Result<FaceId, InvalidFontId> {
234        let fonts = self.fonts.read().unwrap();
235        for (id, list, _) in &fonts.fonts {
236            if *id == font_id {
237                return Ok(*list.first().unwrap());
238            }
239        }
240        Err(InvalidFontId)
241    }
242
243    /// Get the first face for a font
244    ///
245    /// This is a wrapper around [`FontLibrary::first_face_for`] and [`FontLibrary::get_face`].
246    #[inline]
247    pub fn get_first_face(&self, font_id: FontId) -> Result<FaceRef<'_>, InvalidFontId> {
248        let face_id = self.first_face_for(font_id)?;
249        Ok(self.get_face(face_id))
250    }
251
252    /// Check whether a [`FaceId`] is part of a [`FontId`]
253    pub fn contains_face(&self, font_id: FontId, face_id: FaceId) -> Result<bool, InvalidFontId> {
254        let fonts = self.fonts.read().unwrap();
255        for (id, list, _) in &fonts.fonts {
256            if *id == font_id {
257                return Ok(list.contains(&face_id));
258            }
259        }
260        Err(InvalidFontId)
261    }
262
263    /// Resolve the font face for a character
264    ///
265    /// If `last_face_id` is a face used by `font_id` and this face covers `c`,
266    /// then return `last_face_id`. (This is to avoid changing the font face
267    /// unnecessarily, such as when encountering a space amid Arabic text.)
268    ///
269    /// Otherwise, return the first face of `font_id` which covers `c`.
270    ///
271    /// Otherwise (if no face covers `c`) return the first face (if any).
272    pub fn face_for_char(
273        &self,
274        font_id: FontId,
275        last_face_id: Option<FaceId>,
276        c: char,
277    ) -> Result<Option<FaceId>, InvalidFontId> {
278        // TODO: `face.glyph_index` is a bit slow to use like this where several
279        // faces may return no result before we find a match. Caching results
280        // in a HashMap helps. Perhaps better would be to (somehow) determine
281        // the script/language in use and check whether the font face supports
282        // that, perhaps also checking it has shaping support.
283        let mut fonts = self.fonts.write().unwrap();
284        let font = fonts
285            .fonts
286            .iter_mut()
287            .find(|item| item.0 == font_id)
288            .ok_or(InvalidFontId)?;
289
290        let faces = self.faces.read().unwrap();
291
292        if let Some(face_id) = last_face_id {
293            if font.1.contains(&face_id) {
294                let face = &faces.faces[face_id.get()];
295                // TODO(opt): should we cache this lookup?
296                if face.face.glyph_index(c).is_some() {
297                    return Ok(Some(face_id));
298                }
299            }
300        }
301
302        Ok(match font.2.entry(c) {
303            Entry::Occupied(entry) => *entry.get(),
304            Entry::Vacant(entry) => {
305                let mut id: Option<FaceId> = None;
306                for face_id in font.1.iter() {
307                    let face = &faces.faces[face_id.get()];
308                    if face.face.glyph_index(c).is_some() {
309                        id = Some(*face_id);
310                        break;
311                    }
312                }
313
314                // Prefer to match some font face, even without a match
315                // TODO: we need some mechanism to widen the search when this
316                // fails (certain chars might only be found in a special font).
317                if id.is_none() {
318                    id = font.1.first().map(|id| *id);
319                }
320
321                entry.insert(id);
322                id
323            }
324        })
325    }
326
327    /// Select a font
328    ///
329    /// This method uses internal caching to enable fast look-ups of existing
330    /// (loaded) fonts. Resolving new fonts may be slower.
331    pub fn select_font(
332        &self,
333        selector: &FontSelector,
334        script: Script,
335    ) -> Result<FontId, NoFontMatch> {
336        let sel_hash = {
337            use std::collections::hash_map::DefaultHasher;
338            use std::hash::{Hash, Hasher};
339
340            let mut s = DefaultHasher::new();
341            selector.hash(&mut s);
342            script.hash(&mut s);
343            s.finish()
344        };
345
346        let fonts = self.fonts.read().unwrap();
347        for (h, id) in &fonts.sel_hash {
348            if *h == sel_hash {
349                return Ok(*id);
350            }
351        }
352        drop(fonts);
353
354        let mut faces = Vec::new();
355        let mut families = Vec::new();
356        let mut resolver = self.resolver.lock().unwrap();
357        let mut face_list = self.faces.write().unwrap();
358
359        selector.select(&mut resolver, script, |qf| {
360            if log::log_enabled!(log::Level::Debug) {
361                families.push(qf.family);
362            }
363
364            let source_hash = {
365                use std::hash::{DefaultHasher, Hash, Hasher};
366
367                let mut hasher = DefaultHasher::new();
368                qf.blob.id().hash(&mut hasher);
369                hasher.write_u32(qf.index);
370                hasher.finish()
371            };
372
373            for (h, id) in face_list.source_hash.iter().cloned() {
374                if h == source_hash {
375                    let face = &face_list.faces[id.get()];
376                    if face.blob.id() == qf.blob.id() && face.index == qf.index {
377                        faces.push(id);
378                        return QueryStatus::Continue;
379                    }
380                }
381            }
382
383            match FaceStore::new(qf.blob.clone(), qf.index, qf.synthesis) {
384                Ok(store) => {
385                    let id = face_list.push(Box::new(store), source_hash);
386                    faces.push(id);
387                }
388                Err(err) => {
389                    log::error!("Failed to load font: {err}");
390                }
391            }
392
393            QueryStatus::Continue
394        });
395
396        for family in families {
397            if let Some(name) = resolver.font_family(family.0) {
398                log::debug!("match: {name}");
399            }
400        }
401
402        if faces.is_empty() {
403            return Err(NoFontMatch);
404        }
405        let font = self.fonts.write().unwrap().push(faces, sel_hash);
406        Ok(font)
407    }
408}
409
410/// Face management
411impl FontLibrary {
412    /// Get a font face from its identifier
413    ///
414    /// Panics if `id` is not valid (required: `id.get() < self.num_faces()`).
415    pub fn get_face(&self, id: FaceId) -> FaceRef<'static> {
416        self.get_face_store(id).face_ref()
417    }
418
419    /// Get access to the [`FaceStore`]
420    ///
421    /// Panics if `id` is not valid (required: `id.get() < self.num_faces()`).
422    pub fn get_face_store(&self, id: FaceId) -> &'static FaceStore {
423        let faces = self.faces.read().unwrap();
424        assert!(id.get() < faces.faces.len(), "FontLibrary: invalid {id:?}!",);
425        let faces: &FaceStore = &faces.faces[id.get()];
426        // Safety: elements of self.faces are never dropped or modified
427        unsafe { extend_lifetime(faces) }
428    }
429}
430
431pub(crate) unsafe fn extend_lifetime<'b, T: ?Sized>(r: &'b T) -> &'static T {
432    std::mem::transmute::<&'b T, &'static T>(r)
433}
434
435static LIBRARY: LazyLock<FontLibrary> = LazyLock::new(|| FontLibrary {
436    resolver: Mutex::new(Resolver::new()),
437    faces: Default::default(),
438    fonts: Default::default(),
439});
440
441/// Access the [`FontLibrary`] singleton
442pub fn library() -> &'static FontLibrary {
443    &LIBRARY
444}