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