Skip to main content

script/event_loop/
svg_font.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::sync::{Arc, Mutex};
6
7use app_units::Au;
8use fonts::{
9    FallbackFontSelectionOptions, FontContext, FontDescriptor, FontFamilyDescriptor,
10    FontSearchScope, fallback_font_families,
11};
12use net_traits::image_cache::FontResolver;
13use resvg::usvg::{Font, FontFamily, FontStretch, FontStyle, fontdb};
14use rustc_hash::FxHashMap;
15use style::computed_values::font_optical_sizing::T as FontOpticalSizing;
16use style::properties::longhands::font_variant_caps::computed_value::T as FontVariantCaps;
17use style::values::computed::font::{
18    FamilyName, FontFamilyNameSyntax, GenericFontFamily, SingleFontFamily,
19};
20use style::values::computed::{
21    FontStretch as ServoFontStretch, FontStyle as ServoFontStyle, FontSynthesis, FontWeight,
22};
23use webrender_api::FontVariation;
24
25/// Used to dynamically query fonts used in SVGs and insert them into the fontDB used when rasterizing.
26pub struct SvgFontResolver {
27    /// Cache for Font to ID
28    font_id_cache: Mutex<FxHashMap<Font, fontdb::ID>>,
29    fallback_id_cache: Mutex<FxHashMap<char, Vec<fontdb::ID>>>,
30    context: Arc<FontContext>,
31}
32
33impl SvgFontResolver {
34    pub(crate) fn new(context: Arc<FontContext>) -> Self {
35        Self {
36            font_id_cache: Mutex::new(FxHashMap::default()),
37            fallback_id_cache: Mutex::new(FxHashMap::default()),
38            context,
39        }
40    }
41
42    /// Insert the font into the database in [`SvgFontResolver`] and into the cache.
43    fn insert_into_database(
44        &self,
45        font: &Font,
46        database: &mut Arc<fontdb::Database>,
47    ) -> Option<fontdb::ID> {
48        let font_descriptor = font_to_fontdescriptor(font);
49
50        for family in font.families() {
51            let family_descriptor = FontFamilyDescriptor::new(
52                fontfamily_to_singlefontfamily(family),
53                FontSearchScope::Any,
54            );
55
56            let Some(font_template) = self
57                .context
58                .matching_templates(&font_descriptor, &family_descriptor)
59                .into_iter()
60                .next()
61            else {
62                log::debug!(
63                    "Cannot find matching font_template from font {font:?}, font-descriptor {font_descriptor:?}, family_descriptor: {family_descriptor:?} for this family {family:?}"
64                );
65                continue;
66            };
67
68            let Some(font_ref) = self.context.font(font_template, &font_descriptor) else {
69                continue;
70            };
71
72            let Ok(data_and_index) = font_ref.font_data_and_index() else {
73                continue;
74            };
75            let ids = Arc::make_mut(database).load_font_source(fontdb::Source::Binary(
76                data_and_index.data.as_ipc_shared_memory(),
77            ));
78
79            if let Some(id) = ids.get(data_and_index.index as usize).copied() {
80                self.font_id_cache.lock().unwrap().insert(font.clone(), id);
81                return Some(id);
82            }
83        }
84
85        None
86    }
87}
88
89fn font_to_fontdescriptor(font: &Font) -> FontDescriptor {
90    let style = match font.style() {
91        FontStyle::Normal => ServoFontStyle::normal(),
92        FontStyle::Italic => ServoFontStyle::ITALIC,
93        FontStyle::Oblique => ServoFontStyle::OBLIQUE,
94    };
95
96    let stretch = match font.stretch() {
97        FontStretch::UltraCondensed => ServoFontStretch::ULTRA_CONDENSED,
98        FontStretch::ExtraCondensed => ServoFontStretch::EXTRA_CONDENSED,
99        FontStretch::Condensed => ServoFontStretch::CONDENSED,
100        FontStretch::SemiCondensed => ServoFontStretch::SEMI_CONDENSED,
101        FontStretch::Normal => ServoFontStretch::NORMAL,
102        FontStretch::SemiExpanded => ServoFontStretch::SEMI_EXPANDED,
103        FontStretch::Expanded => ServoFontStretch::EXPANDED,
104        FontStretch::ExtraExpanded => ServoFontStretch::EXTRA_EXPANDED,
105        FontStretch::UltraExpanded => ServoFontStretch::ULTRA_EXPANDED,
106    };
107
108    let variation_settings = font
109        .variations()
110        .iter()
111        .map(|variation| FontVariation {
112            tag: u32::from_be_bytes(variation.tag),
113            value: variation.value,
114        })
115        .collect();
116
117    FontDescriptor {
118        weight: FontWeight::from_float(font.weight() as f32),
119        stretch,
120        style,
121        variant: FontVariantCaps::Normal,
122        pt_size: Au::from_px(16),
123        variation_settings,
124        synthesis_weight: FontSynthesis::Auto,
125        optical_sizing: FontOpticalSizing::Auto,
126    }
127}
128
129fn fallback_descriptor() -> FontDescriptor {
130    FontDescriptor {
131        weight: FontWeight::normal(),
132        stretch: ServoFontStretch::hundred(),
133        style: ServoFontStyle::normal(),
134        variant: FontVariantCaps::Normal,
135        pt_size: Au::from_px(16),
136        variation_settings: vec![],
137        synthesis_weight: FontSynthesis::Auto,
138        optical_sizing: FontOpticalSizing::Auto,
139    }
140}
141
142fn fontfamily_to_singlefontfamily(family: &FontFamily) -> SingleFontFamily {
143    match family {
144        FontFamily::Serif => SingleFontFamily::Generic(GenericFontFamily::Serif),
145        FontFamily::SansSerif => SingleFontFamily::Generic(GenericFontFamily::SansSerif),
146        FontFamily::Cursive => SingleFontFamily::Generic(GenericFontFamily::Cursive),
147        FontFamily::Fantasy => SingleFontFamily::Generic(GenericFontFamily::Fantasy),
148        FontFamily::Monospace => SingleFontFamily::Generic(GenericFontFamily::Monospace),
149        FontFamily::Named(name) => SingleFontFamily::FamilyName(FamilyName {
150            name: name.as_str().into(),
151            syntax: FontFamilyNameSyntax::Quoted,
152        }),
153    }
154}
155
156impl FontResolver for SvgFontResolver {
157    fn resolve(&self, font: &Font, database: &mut Arc<fontdb::Database>) -> Option<fontdb::ID> {
158        {
159            let id_cache = self.font_id_cache.lock().unwrap();
160            if let Some(font_id) = id_cache.get(font) {
161                return Some(*font_id);
162            }
163        }
164        self.insert_into_database(font, database)
165    }
166
167    fn resolve_fallback(
168        &self,
169        character: char,
170        excluded: &[fontdb::ID],
171        database: &mut Arc<fontdb::Database>,
172    ) -> Option<fontdb::ID> {
173        {
174            let id_cache = self.fallback_id_cache.lock().unwrap();
175            if let Some(font_id) = id_cache
176                .get(&character)
177                .and_then(|font_ids| font_ids.iter().find(|font_id| !excluded.contains(font_id)))
178            {
179                return Some(*font_id);
180            }
181        }
182        let fallback_options =
183            FallbackFontSelectionOptions::new(character, None, icu_locid::subtags::Language::UND);
184        for family in fallback_font_families(fallback_options) {
185            let family = FontFamilyDescriptor::new(
186                SingleFontFamily::FamilyName(FamilyName {
187                    name: family.into(),
188                    syntax: FontFamilyNameSyntax::Quoted,
189                }),
190                FontSearchScope::Any,
191            );
192            let fallback_descriptor = fallback_descriptor();
193
194            let font_templates = self
195                .context
196                .matching_templates(&fallback_descriptor, &family);
197
198            for font_template in font_templates {
199                let Some(font_ref) = self.context.font(font_template, &fallback_descriptor) else {
200                    continue;
201                };
202                if !font_ref.has_glyph_for(character) {
203                    continue;
204                }
205
206                let Ok(data_and_index) = font_ref.font_data_and_index() else {
207                    continue;
208                };
209
210                let ids = Arc::make_mut(database).load_font_source(fontdb::Source::Binary(
211                    data_and_index.data.as_ipc_shared_memory(),
212                ));
213
214                let Some(id) = ids.get(data_and_index.index as usize) else {
215                    continue;
216                };
217
218                if excluded.contains(id) {
219                    continue;
220                }
221
222                self.fallback_id_cache
223                    .lock()
224                    .unwrap()
225                    .entry(character)
226                    .or_default()
227                    .push(*id);
228                return Some(*id);
229            }
230        }
231        None
232    }
233}