Skip to main content

ferroui_base/media/fonts/
system_font_collection.rs

1use std::rc::Rc;
2
3use crate::media::fonts::font_collection_base::equals_ordinal_ignore_case;
4use crate::media::fonts::{FontCollectionBase, FontCollectionBaseImpl, FontCollectionKey};
5use crate::media::{
6    FontFamily, FontManager, FontSimulations, FontStretch, FontStyle, FontWeight, GlyphTypeface, Typeface,
7};
8use crate::platform::IFontManagerImpl;
9use crate::utilities::{CultureInfo, Uri};
10
11/// The collection of the fonts installed in the system, backed by the font
12/// manager backend.
13pub struct SystemFontCollection {
14    base: FontCollectionBase,
15    platform_impl: Rc<dyn IFontManagerImpl>,
16}
17
18impl SystemFontCollection {
19    pub fn new(platform_impl: Rc<dyn IFontManagerImpl>) -> Self {
20        let collection = Self { base: FontCollectionBase::new(), platform_impl };
21
22        for family_name in collection.platform_impl.get_installed_font_family_names(false) {
23            if !family_name.is_empty() {
24                collection.base.add_font_family(FontFamily::new(&family_name));
25            }
26        }
27
28        collection
29    }
30}
31
32impl FontCollectionBaseImpl for SystemFontCollection {
33    fn base(this: &Self) -> &FontCollectionBase {
34        &this.base
35    }
36
37    fn key(_this: &Self) -> Uri {
38        FontManager::system_fonts_key()
39    }
40
41    fn try_get_glyph_typeface(
42        this: &Self,
43        family_name: &str,
44        style: FontStyle,
45        weight: FontWeight,
46        stretch: FontStretch,
47    ) -> Option<Rc<GlyphTypeface>> {
48        let (typeface, family_name) =
49            Typeface::from_name_with_style(family_name, style, weight, stretch).normalize();
50        let family_name = family_name.as_str();
51        let key = FontCollectionKey::from(&typeface);
52
53        // Find an exact match first
54        if let Some(glyph_typeface) = FontCollectionBase::try_get_glyph_typeface_by_key(this, family_name, key, false)
55        {
56            return Some(glyph_typeface);
57        }
58
59        // Check cache first to avoid unnecessary calls to the font manager
60        if let Some(glyph_typefaces) = this.base.glyph_typeface_cache().try_get_value(family_name) {
61            if let Some(glyph_typeface) = glyph_typefaces.borrow().get(&key) {
62                return glyph_typeface.clone();
63            }
64        }
65
66        // Try to create the glyph typeface via system font manager
67        let Some(platform_typeface) = this.platform_impl.try_create_glyph_typeface(family_name, style, weight, stretch)
68        else {
69            // Add `None` to cache to avoid future calls
70            FontCollectionBase::try_add_glyph_typeface_by_name(this, family_name, key, None);
71
72            return None;
73        };
74
75        // The font manager didn't return a perfect match either. Find the nearest match ourselves.
76        if key != FontCollectionKey::from(&*platform_typeface) {
77            if let Some(glyph_typeface) =
78                FontCollectionBase::try_get_glyph_typeface_by_key(this, family_name, key, true)
79            {
80                return Some(glyph_typeface);
81            }
82        }
83
84        let platform_family_name = platform_typeface.family_name();
85
86        let glyph_typeface = GlyphTypeface::try_create(platform_typeface, FontSimulations::None)?;
87
88        // Add to cache with platform typeface family name first
89        FontCollectionBase::try_add_glyph_typeface_by_name(
90            this,
91            &platform_family_name,
92            key,
93            Some(glyph_typeface.clone()),
94        );
95
96        // Then the requested family name
97        if !equals_ordinal_ignore_case(family_name, &platform_family_name) {
98            FontCollectionBase::try_add_glyph_typeface_by_name(this, family_name, key, Some(glyph_typeface.clone()));
99        }
100
101        // Add to cache
102        if !FontCollectionBase::try_add_glyph_typeface(this, &glyph_typeface) {
103            // An entry for this key may have been added while the glyph typeface was created.
104            // Re-check the cache and yield the existing glyph typeface if present.
105            return this
106                .base
107                .glyph_typeface_cache()
108                .try_get_value(family_name)
109                .and_then(|existing_map| existing_map.borrow().get(&key).cloned().flatten());
110        }
111
112        // Requested glyph typeface should be in cache now
113        FontCollectionBase::try_get_glyph_typeface_by_key(this, family_name, key, false)
114    }
115
116    fn try_get_family_typefaces(this: &Self, family_name: &str) -> Option<Vec<Typeface>> {
117        this.platform_impl.try_get_family_typefaces(family_name)
118    }
119
120    fn try_match_character(
121        this: &Self,
122        codepoint: i32,
123        style: FontStyle,
124        weight: FontWeight,
125        stretch: FontStretch,
126        family_name: Option<&str>,
127        culture: Option<&CultureInfo>,
128    ) -> Option<Typeface> {
129        // Delegate to the base algorithm. The platform call is exposed through
130        // `try_match_character_from_platform` and invoked at most once per (script-bucket, culture)
131        // pair by the base implementation, after every cached candidate has been considered.
132        FontCollectionBase::try_match_character(this, codepoint, style, weight, stretch, family_name, culture)
133    }
134
135    fn try_match_character_from_platform(
136        this: &Self,
137        codepoint: i32,
138        key: FontCollectionKey,
139        family_name: Option<&str>,
140        culture: Option<&CultureInfo>,
141    ) -> Option<Rc<GlyphTypeface>> {
142        let platform_typeface =
143            this.platform_impl.try_match_character(codepoint, key.style, key.weight, key.stretch, family_name, culture)?;
144
145        let platform_key = FontCollectionKey::from(&*platform_typeface);
146        let platform_family_name = platform_typeface.family_name();
147
148        // Check cache first to avoid creating a duplicate glyph typeface.
149        if let Some(glyph_typefaces) = this.base.glyph_typeface_cache().try_get_value(&platform_family_name) {
150            if let Some(Some(existing)) = glyph_typefaces.borrow().get(&platform_key) {
151                return Some(existing.clone());
152            }
153        }
154
155        let glyph_typeface = GlyphTypeface::try_create(platform_typeface, FontSimulations::None)?;
156
157        // Register in the cache so future lookups can short-circuit through the cache sweep of
158        // the character match without re-invoking the platform.
159        FontCollectionBase::try_add_glyph_typeface_by_name(
160            this,
161            &platform_family_name,
162            platform_key,
163            Some(glyph_typeface.clone()),
164        );
165        FontCollectionBase::try_add_glyph_typeface_with_key(this, &glyph_typeface, platform_key);
166
167        Some(glyph_typeface)
168    }
169}