Skip to main content

ferroui_base/media/
font_manager.rs

1use std::cell::{OnceCell, RefCell};
2use std::collections::HashMap;
3use std::rc::Rc;
4
5use crate::logging::{LogArea, LogEventLevel, Logger};
6use crate::media::composite_font_family_key::CompositeFontFamilyKey;
7use crate::media::fonts::font_collection_base::compare_ordinal_ignore_case;
8use crate::media::fonts::{
9    EmbeddedFontCollection, EmptySystemFontCollection, FontCollectionBase, FontFamilyKey, IFontCollection,
10    SystemFontCollection,
11};
12use crate::media::text_formatting::unicode::Script;
13use crate::media::{
14    FontFallback, FontFamily, FontManagerOptions, FontStretch, FontStyle, FontWeight, GlyphTypeface, Typeface,
15};
16use crate::platform::IFontManagerImpl;
17use crate::utilities::{CultureInfo, Uri, UriExtensions, UriKind};
18use crate::{FerroLocator, LocatorExtensions};
19
20/// The font manager is used to query the system's installed fonts and is
21/// responsible for caching loaded fonts. It is also responsible for the font
22/// fallback.
23pub struct FontManager {
24    font_collections: RefCell<HashMap<Uri, Rc<dyn IFontCollection>>>,
25    font_fallbacks: Option<Vec<FontFallback>>,
26    /// The configured family name mappings, sorted by name ignoring case.
27    font_family_mappings: Option<Vec<(String, FontFamily)>>,
28    platform_impl: Rc<dyn IFontManagerImpl>,
29    default_font_family: OnceCell<FontFamily>,
30}
31
32/// Font managers compare by reference, so that a handle can be an untyped
33/// value (the value of the static member markup names).
34impl PartialEq for FontManager {
35    #[inline]
36    fn eq(&self, other: &Self) -> bool {
37        std::ptr::eq(self, other)
38    }
39}
40
41impl FontManager {
42    pub const FONT_COLLECTION_SCHEME: &'static str = "fonts";
43    pub const SYSTEM_FONT_SCHEME: &'static str = "systemfont";
44    pub const COMPOSITE_FONT_SCHEME: &'static str = "compositefont";
45
46    /// The key of the system font collection (`fonts:SystemFonts`).
47    ///
48    /// Internal upstream, where the Skia unit tests see it; public here so
49    /// that the tests of the Skia crate reach it.
50    pub fn system_fonts_key() -> Uri {
51        crate::ferro_thread_local! {
52            static SYSTEM_FONTS_KEY: Uri = Uri::try_create("fonts:SystemFonts", UriKind::Absolute)
53                .expect("the system fonts key is an absolute uri");
54        }
55
56        SYSTEM_FONTS_KEY.with(Clone::clone)
57    }
58
59    /// Creates a font manager on top of a font backend. The
60    /// [`FontManagerOptions`] registered with the locator, if any, are applied.
61    ///
62    /// Panics when neither the options nor the backend nor the installed
63    /// fonts give a default font family name.
64    pub fn new(platform_impl: Rc<dyn IFontManagerImpl>) -> Rc<FontManager> {
65        let options = FerroLocator::current().get_service::<FontManagerOptions>();
66
67        let font_manager = FontManager {
68            font_collections: RefCell::new(HashMap::new()),
69            font_fallbacks: options.as_ref().and_then(|options| options.font_fallbacks.clone()),
70            font_family_mappings: Self::create_font_family_mappings(
71                options.as_ref().and_then(|options| options.font_family_mappings.as_ref()),
72            ),
73            platform_impl,
74            default_font_family: OnceCell::new(),
75        };
76
77        let default_font_family_name = font_manager.get_default_font_family_name(options.as_deref());
78
79        font_manager
80            .default_font_family
81            .get_or_init(|| FontFamily::new(&default_font_family_name));
82
83        Rc::new(font_manager)
84    }
85
86    /// Copies the configured mappings into a case-insensitive lookup. Family
87    /// names are matched case-insensitively everywhere else, so a mapping
88    /// must apply whatever casing the requested name arrives in.
89    fn create_font_family_mappings(
90        font_family_mappings: Option<&HashMap<String, FontFamily>>,
91    ) -> Option<Vec<(String, FontFamily)>> {
92        let font_family_mappings = font_family_mappings.filter(|mappings| !mappings.is_empty())?;
93
94        // Names that collide only by casing were distinct entries before the copy. One of them
95        // is taken rather than failing: a mapping table is configuration, and failing here would
96        // bring the application down at startup. The entries are visited in name order so that
97        // the choice (the last one) is deterministic.
98        let mut entries: Vec<(&String, &FontFamily)> = font_family_mappings.iter().collect();
99
100        entries.sort_by(|a, b| a.0.cmp(b.0));
101
102        let mut mappings: Vec<(String, FontFamily)> = Vec::with_capacity(entries.len());
103
104        for (name, font_family) in entries {
105            match mappings.binary_search_by(|(existing, _)| compare_ordinal_ignore_case(existing, name)) {
106                Ok(index) => mappings[index].1 = font_family.clone(),
107                Err(index) => mappings.insert(index, (name.clone(), font_family.clone())),
108            }
109        }
110
111        Some(mappings)
112    }
113
114    fn try_get_font_family_mapping(&self, family_name: &str) -> Option<&FontFamily> {
115        let mappings = self.font_family_mappings.as_ref()?;
116
117        mappings
118            .binary_search_by(|(existing, _)| compare_ordinal_ignore_case(existing, family_name))
119            .ok()
120            .map(|index| &mappings[index].1)
121    }
122
123    /// Get the current font manager instance.
124    ///
125    /// The font manager registered with the locator, or a new one created on
126    /// top of the registered font backend (and then registered).
127    ///
128    /// Panics when no font backend (`IFontManagerImpl`) is registered.
129    pub fn current() -> Rc<FontManager> {
130        if let Some(current) = FerroLocator::current().get_service::<FontManager>() {
131            return current;
132        }
133
134        let font_manager_impl = FerroLocator::current().get_required_service::<dyn IFontManagerImpl>();
135
136        let current = FontManager::new(font_manager_impl);
137
138        FerroLocator::current_mutable().bind::<FontManager>().to_constant(current.clone());
139
140        current
141    }
142
143    /// Gets the system's default font family.
144    pub fn default_font_family(&self) -> &FontFamily {
145        self.default_font_family.get().expect("the default font family is set by the constructor")
146    }
147
148    /// Get all system fonts.
149    pub fn system_fonts(&self) -> Rc<dyn IFontCollection> {
150        if let Some(font_collection) = self.try_get_font_collection(&Self::system_fonts_key()) {
151            return font_collection;
152        }
153
154        // Fallback to an empty system font collection
155        Rc::new(EmptySystemFontCollection::new())
156    }
157
158    /// The font backend.
159    ///
160    /// Internal upstream, where the Skia unit tests see it; public here so
161    /// that the tests of the Skia crate reach it.
162    pub fn platform_impl(&self) -> &Rc<dyn IFontManagerImpl> {
163        &self.platform_impl
164    }
165
166    /// Tries to get a glyph typeface for specified typeface.
167    ///
168    /// Returns the glyph typeface if the font manager could create it,
169    /// `None` otherwise.
170    pub fn try_get_glyph_typeface(&self, typeface: &Typeface) -> Option<Rc<GlyphTypeface>> {
171        let font_family = self
172            .try_get_font_family_mapping(typeface.font_family().family_names().primary_family_name())
173            .unwrap_or(typeface.font_family());
174
175        if typeface.font_family().name() == FontFamily::DEFAULT_FONT_FAMILY_NAME {
176            return self.try_get_glyph_typeface(&self.default_typeface(typeface));
177        }
178
179        if let Some(font_family_key) = font_family.key() {
180            if let Some(composite_keys) = CompositeFontFamilyKey::keys(font_family_key) {
181                for (i, composite_key) in composite_keys.iter().enumerate() {
182                    let mut key = composite_key.clone();
183
184                    let mut family_name = &font_family.family_names()[i];
185
186                    if let Some(mapped_font_family) = self.try_get_font_family_mapping(family_name) {
187                        key = match mapped_font_family.key() {
188                            Some(mapped_key) => mapped_key.clone(),
189                            None => FontFamilyKey::new(Self::system_fonts_key()),
190                        };
191
192                        family_name = mapped_font_family.family_names().primary_family_name();
193                    }
194
195                    if family_name == FontFamily::DEFAULT_FONT_FAMILY_NAME {
196                        return self.try_get_glyph_typeface(&self.default_typeface(typeface));
197                    }
198
199                    if let Some(glyph_typeface) = self.try_get_glyph_typeface_by_key_and_name(typeface, &key, family_name)
200                    {
201                        if glyph_typeface.family_name().contains(family_name) {
202                            return Some(glyph_typeface);
203                        }
204                    }
205                }
206            } else {
207                let family_name = font_family.family_names().primary_family_name();
208
209                return self.try_get_glyph_typeface_by_key_and_name(typeface, font_family_key, family_name);
210            }
211        } else {
212            let family_name = font_family.family_names().primary_family_name();
213
214            if let Some(glyph_typeface) = self.system_fonts().try_get_glyph_typeface(
215                family_name,
216                typeface.style(),
217                typeface.weight(),
218                typeface.stretch(),
219            ) {
220                return Some(glyph_typeface);
221            }
222        }
223
224        if typeface.font_family() == self.default_font_family() {
225            return None;
226        }
227
228        // Nothing was found so use the default
229        self.try_get_glyph_typeface(&self.default_typeface(typeface))
230    }
231
232    /// The typeface of the default font family with the style of `typeface`.
233    fn default_typeface(&self, typeface: &Typeface) -> Typeface {
234        Typeface::with_style(
235            self.default_font_family().clone(),
236            typeface.style(),
237            typeface.weight(),
238            typeface.stretch(),
239        )
240    }
241
242    fn try_get_glyph_typeface_by_key_and_name(
243        &self,
244        typeface: &Typeface,
245        key: &FontFamilyKey,
246        family_name: &str,
247    ) -> Option<Rc<GlyphTypeface>> {
248        let source = key.source().ensure_absolute(key.base_uri());
249
250        let font_collection = self.try_get_font_collection(&source)?;
251
252        if let Some(glyph_typeface) =
253            font_collection.try_get_glyph_typeface(family_name, typeface.style(), typeface.weight(), typeface.stretch())
254        {
255            return Some(glyph_typeface);
256        }
257
258        if let Some(logger) = Logger::try_get(LogEventLevel::Debug, LogArea::FONTS) {
259            let present: Vec<String> =
260                font_collection.font_families().iter().map(|font_family| font_family.to_string()).collect();
261
262            logger.log(
263                Some(self),
264                &format!(
265                    "Font family '{family_name}' could not be found. Present font families: [{}]",
266                    present.join(",")
267                ),
268            );
269        }
270
271        None
272    }
273
274    /// Add a font collection to the manager.
275    ///
276    /// If a font collection's key is already present the collection is
277    /// replaced (and the replaced collection disposed).
278    ///
279    /// Panics when the key of the collection does not follow the `fonts:` scheme.
280    pub fn add_font_collection(&self, font_collection: Rc<dyn IFontCollection>) {
281        let key = font_collection.key();
282
283        if !key.is_absolute_uri() || !UriExtensions::is_font_collection(&key) {
284            panic!("Font collection Key should follow the fonts: scheme.");
285        }
286
287        let old_collection = self.font_collections.borrow_mut().insert(key, font_collection);
288
289        if let Some(old_collection) = old_collection {
290            old_collection.dispose();
291        }
292    }
293
294    /// Removes the font collection that corresponds to specified key.
295    pub fn remove_font_collection(&self, key: &Uri) {
296        let font_collection = self.font_collections.borrow_mut().remove(key);
297
298        if let Some(font_collection) = font_collection {
299            font_collection.dispose();
300        }
301    }
302
303    /// Tries to match a specified character to a [`Typeface`] that supports
304    /// specified font properties.
305    ///
306    /// `font_family` is optional and used for fallback lookup.
307    ///
308    /// Returns the matching typeface if the font manager could match the
309    /// character to specified parameters, `None` otherwise.
310    pub fn try_match_character(
311        &self,
312        codepoint: i32,
313        font_style: FontStyle,
314        font_weight: FontWeight,
315        font_stretch: FontStretch,
316        font_family: Option<&FontFamily>,
317        culture: Option<&CultureInfo>,
318    ) -> Option<Typeface> {
319        self.try_match_character_with_script(
320            codepoint,
321            font_style,
322            font_weight,
323            font_stretch,
324            font_family,
325            culture,
326            Script::Unknown,
327        )
328    }
329
330    /// Character-to-typeface match with an optional shaping-capability
331    /// constraint: when `shaping_script` is a complex script, only fonts that
332    /// can shape it are considered. [`Script::Unknown`] imposes no constraint
333    /// and is identical to the public overload.
334    #[allow(clippy::too_many_arguments)]
335    pub(crate) fn try_match_character_with_script(
336        &self,
337        codepoint: i32,
338        font_style: FontStyle,
339        font_weight: FontWeight,
340        font_stretch: FontStretch,
341        font_family: Option<&FontFamily>,
342        culture: Option<&CultureInfo>,
343        shaping_script: Script,
344    ) -> Option<Typeface> {
345        if let Some(font_fallbacks) = &self.font_fallbacks {
346            for fallback in font_fallbacks {
347                if fallback.unicode_range.is_in_range(codepoint) {
348                    let typeface =
349                        Typeface::with_style(fallback.font_family.clone(), font_style, font_weight, font_stretch);
350
351                    if let Some(glyph_typeface) = self.try_get_glyph_typeface(&typeface) {
352                        if glyph_typeface.character_to_glyph_map().try_get_glyph(codepoint).is_some()
353                            && (shaping_script == Script::Unknown || glyph_typeface.can_shape_script(shaping_script))
354                        {
355                            return Some(typeface);
356                        }
357                    }
358                }
359            }
360        }
361
362        // Try to match against fallbacks first
363        if let Some(font_family) = font_family {
364            if let Some(font_family_key) = font_family.key() {
365                if let Some(composite_keys) = CompositeFontFamilyKey::keys(font_family_key) {
366                    for (i, key) in composite_keys.iter().enumerate() {
367                        let mut family_name = &font_family.family_names()[i];
368                        let source = key.source().ensure_absolute(key.base_uri());
369
370                        if family_name == FontFamily::DEFAULT_FONT_FAMILY_NAME {
371                            family_name = self.default_font_family().name();
372                        }
373
374                        let Some(font_collection) = self.try_get_font_collection(&source) else {
375                            continue;
376                        };
377
378                        // With composite fonts we need to first check if the font collection contains the family if not we skip it
379                        if font_collection
380                            .try_get_glyph_typeface(family_name, font_style, font_weight, font_stretch)
381                            .is_none()
382                        {
383                            continue;
384                        }
385
386                        if let Some(typeface) = Self::try_match_character_in_collection(
387                            &*font_collection,
388                            codepoint,
389                            font_style,
390                            font_weight,
391                            font_stretch,
392                            Some(family_name),
393                            culture,
394                            shaping_script,
395                        ) {
396                            if typeface.font_family().name() == self.default_font_family().name()
397                                && i + 1 < composite_keys.len()
398                            {
399                                continue;
400                            }
401
402                            return Some(typeface);
403                        }
404                    }
405                }
406
407                let font_uri = font_family_key.source().ensure_absolute(font_family_key.base_uri());
408
409                if UriExtensions::is_font_collection(&font_uri) {
410                    if let Some(font_collection) = self.try_get_font_collection(&font_uri) {
411                        if let Some(typeface) = Self::try_match_character_in_collection(
412                            &*font_collection,
413                            codepoint,
414                            font_style,
415                            font_weight,
416                            font_stretch,
417                            Some(font_family.name()),
418                            culture,
419                            shaping_script,
420                        ) {
421                            return Some(typeface);
422                        }
423                    }
424                }
425            }
426        }
427
428        // Try to find a match with the system font collection
429        Self::try_match_character_in_collection(
430            &*self.system_fonts(),
431            codepoint,
432            font_style,
433            font_weight,
434            font_stretch,
435            font_family.map(|font_family| font_family.name()),
436            culture,
437            shaping_script,
438        )
439    }
440
441    // Routes through the shaping-aware overload when the collection derives from the font
442    // collection base (all built-in collections do); a foreign collection gets the
443    // unconstrained match.
444    #[allow(clippy::too_many_arguments)]
445    fn try_match_character_in_collection(
446        font_collection: &dyn IFontCollection,
447        codepoint: i32,
448        font_style: FontStyle,
449        font_weight: FontWeight,
450        font_stretch: FontStretch,
451        family_name: Option<&str>,
452        culture: Option<&CultureInfo>,
453        shaping_script: Script,
454    ) -> Option<Typeface> {
455        if let Some(font_collection_base) = font_collection.as_font_collection_base() {
456            return FontCollectionBase::try_match_character_with_script(
457                font_collection_base,
458                codepoint,
459                font_style,
460                font_weight,
461                font_stretch,
462                family_name,
463                culture,
464                shaping_script,
465            );
466        }
467
468        font_collection.try_match_character(codepoint, font_style, font_weight, font_stretch, family_name, culture)
469    }
470
471    /// The typefaces of a font family; empty when the family is unknown.
472    ///
473    /// Internal upstream, where the Skia unit tests see it; public here so
474    /// that the tests of the Skia crate reach it.
475    pub fn get_family_typefaces(&self, font_family: &FontFamily) -> Vec<Typeface> {
476        match font_family.key() {
477            None => self.system_fonts().try_get_family_typefaces(font_family.name()),
478            Some(key) => {
479                let source = key.source().ensure_absolute(key.base_uri());
480
481                self.try_get_font_collection(&source)
482                    .and_then(|font_collection| font_collection.try_get_family_typefaces(font_family.name()))
483            }
484        }
485        .unwrap_or_default()
486    }
487
488    /// The font collection of an absolute source URI: the system font
489    /// collection (`systemfont:` or the system fonts key), a registered
490    /// collection (`fonts:`) or the collection of embedded fonts at the
491    /// source (`resm:`, `ferres:`), which is created on first use.
492    pub(crate) fn try_get_font_collection(&self, source: &Uri) -> Option<Rc<dyn IFontCollection>> {
493        debug_assert!(source.is_absolute_uri());
494
495        // Both the systemfont: scheme and the system fonts key (fonts:SystemFonts) map to the
496        // system font collection. The key is checked before the generic font collection branch
497        // so that the system font collection is created on demand regardless of which URI form
498        // is used.
499        if source.scheme() == Self::SYSTEM_FONT_SCHEME || *source == Self::system_fonts_key() {
500            return Some(self.get_or_create_font_collection(&Self::system_fonts_key(), |_| {
501                Rc::new(SystemFontCollection::new(self.platform_impl.clone()))
502            }));
503        }
504
505        // Other fonts: URIs are only returned when they have been explicitly registered
506        // via `add_font_collection` - no implicit creation to avoid caching nothing for unknown keys.
507        if UriExtensions::is_font_collection(source) {
508            return self.font_collections.borrow().get(source).cloned();
509        }
510
511        if UriExtensions::is_absolute_resm(source) || UriExtensions::is_asset(source) {
512            return Some(self.get_or_create_font_collection(source, |key| {
513                Rc::new(EmbeddedFontCollection::new(key.clone(), key.clone()))
514            }));
515        }
516
517        None
518    }
519
520    /// Returns the registered collection, or creates and registers one. A
521    /// collection registered for the key while the factory ran wins, and the
522    /// new one is disposed.
523    fn get_or_create_font_collection(
524        &self,
525        key: &Uri,
526        factory: impl FnOnce(&Uri) -> Rc<dyn IFontCollection>,
527    ) -> Rc<dyn IFontCollection> {
528        if let Some(existing) = self.font_collections.borrow().get(key) {
529            return existing.clone();
530        }
531
532        // The factory runs without a borrow of the collections: it calls into the font backend.
533        let candidate = factory(key);
534
535        let winner = self.font_collections.borrow_mut().entry(key.clone()).or_insert_with(|| candidate.clone()).clone();
536
537        if !Rc::ptr_eq(&winner, &candidate) {
538            // Our candidate lost - dispose it to avoid the leak.
539            candidate.dispose();
540        }
541
542        winner
543    }
544
545    fn get_default_font_family_name(&self, options: Option<&FontManagerOptions>) -> String {
546        let mut default_font_family_name = options
547            .and_then(|options| options.default_family_name.clone())
548            .unwrap_or_else(|| self.platform_impl.get_default_font_family_name());
549
550        if default_font_family_name.is_empty() {
551            let system_fonts = self.system_fonts();
552
553            if system_fonts.count() > 0 {
554                default_font_family_name = system_fonts.get(0).name().to_owned();
555            }
556        }
557
558        if default_font_family_name.is_empty() {
559            panic!("Default font family name can't be null or empty.");
560        }
561
562        if default_font_family_name == FontFamily::DEFAULT_FONT_FAMILY_NAME {
563            panic!(
564                "'{}' is a placeholder and cannot be used as the default font family name. Provide a concrete font family name via FontManagerOptions or the platform implementation.",
565                FontFamily::DEFAULT_FONT_FAMILY_NAME
566            );
567        }
568
569        default_font_family_name
570    }
571
572    /// Disposes every font collection and the font backend.
573    pub fn dispose(&self) {
574        let font_collections: Vec<Rc<dyn IFontCollection>> =
575            self.font_collections.borrow_mut().drain().map(|(_, font_collection)| font_collection).collect();
576
577        for font_collection in font_collections {
578            font_collection.dispose();
579        }
580
581        self.platform_impl.dispose();
582    }
583}