fonts/
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::borrow::ToOwned;
6use std::collections::HashMap;
7use std::hash::Hash;
8use std::ops::Deref;
9use std::sync::atomic::{AtomicUsize, Ordering};
10use std::sync::{Arc, OnceLock};
11use std::time::Instant;
12use std::{iter, str};
13
14use app_units::Au;
15use bitflags::bitflags;
16use euclid::default::{Point2D, Rect};
17use euclid::num::Zero;
18use fonts_traits::FontDescriptor;
19use icu_locid::subtags::Language;
20use log::debug;
21use malloc_size_of_derive::MallocSizeOf;
22use parking_lot::RwLock;
23use read_fonts::tables::os2::{Os2, SelectionFlags};
24use read_fonts::types::Tag;
25use rustc_hash::FxHashMap;
26use serde::{Deserialize, Serialize};
27use servo_base::id::PainterId;
28use servo_base::text::{UnicodeBlock, UnicodeBlockMethod};
29use smallvec::SmallVec;
30use style::computed_values::font_variant_caps;
31use style::properties::style_structs::Font as FontStyleStruct;
32use style::values::computed::font::{
33    FamilyName, FontFamilyNameSyntax, GenericFontFamily, SingleFontFamily,
34};
35use style::values::computed::{FontStretch, FontStyle, FontSynthesis, FontWeight, XLang};
36use unicode_script::Script;
37use webrender_api::{FontInstanceFlags, FontInstanceKey, FontVariation};
38
39use crate::platform::font::{FontTable, PlatformFont};
40use crate::platform::font_list::fallback_font_families;
41use crate::{
42    EmojiPresentationPreference, FallbackFontSelectionOptions, FontContext, FontData,
43    FontDataAndIndex, FontDataError, FontIdentifier, FontTemplateDescriptor, FontTemplateRef,
44    FontTemplateRefMethods, GlyphId, GlyphStore, LocalFontIdentifier, ShapedGlyph, Shaper,
45};
46
47pub(crate) const GPOS: Tag = Tag::new(b"GPOS");
48pub(crate) const GSUB: Tag = Tag::new(b"GSUB");
49pub(crate) const KERN: Tag = Tag::new(b"kern");
50pub(crate) const SBIX: Tag = Tag::new(b"sbix");
51pub(crate) const CBDT: Tag = Tag::new(b"CBDT");
52pub(crate) const COLR: Tag = Tag::new(b"COLR");
53pub(crate) const BASE: Tag = Tag::new(b"BASE");
54pub(crate) const LIGA: Tag = Tag::new(b"liga");
55
56pub const LAST_RESORT_GLYPH_ADVANCE: FractionalPixel = 10.0;
57
58/// Nanoseconds spent shaping text across all layout threads.
59static TEXT_SHAPING_PERFORMANCE_COUNTER: AtomicUsize = AtomicUsize::new(0);
60
61// PlatformFont encapsulates access to the platform's font API,
62// e.g. quartz, FreeType. It provides access to metrics and tables
63// needed by the text shaper as well as access to the underlying font
64// resources needed by the graphics layer to draw glyphs.
65
66pub trait PlatformFontMethods: Sized {
67    #[servo_tracing::instrument(name = "PlatformFontMethods::new_from_template", skip_all)]
68    fn new_from_template(
69        template: FontTemplateRef,
70        pt_size: Option<Au>,
71        variations: &[FontVariation],
72        data: &Option<FontData>,
73        synthetic_bold: bool,
74    ) -> Result<PlatformFont, &'static str> {
75        let template = template.borrow();
76        let font_identifier = template.identifier.clone();
77
78        match font_identifier {
79            FontIdentifier::Local(font_identifier) => Self::new_from_local_font_identifier(
80                font_identifier,
81                pt_size,
82                variations,
83                synthetic_bold,
84            ),
85            FontIdentifier::Web(_) | FontIdentifier::ArrayBuffer(_) => Self::new_from_data(
86                font_identifier,
87                data.as_ref()
88                    .expect("Should never create a web font without data."),
89                pt_size,
90                variations,
91                synthetic_bold,
92            ),
93        }
94    }
95
96    fn new_from_local_font_identifier(
97        font_identifier: LocalFontIdentifier,
98        pt_size: Option<Au>,
99        variations: &[FontVariation],
100        synthetic_bold: bool,
101    ) -> Result<PlatformFont, &'static str>;
102
103    fn new_from_data(
104        font_identifier: FontIdentifier,
105        data: &FontData,
106        pt_size: Option<Au>,
107        variations: &[FontVariation],
108        synthetic_bold: bool,
109    ) -> Result<PlatformFont, &'static str>;
110
111    /// Get a [`FontTemplateDescriptor`] from a [`PlatformFont`]. This is used to get
112    /// descriptors for web fonts.
113    fn descriptor(&self) -> FontTemplateDescriptor;
114
115    fn glyph_index(&self, codepoint: char) -> Option<GlyphId>;
116    fn glyph_h_advance(&self, _: GlyphId) -> Option<FractionalPixel>;
117    fn glyph_h_kerning(&self, glyph0: GlyphId, glyph1: GlyphId) -> FractionalPixel;
118
119    fn metrics(&self) -> FontMetrics;
120    fn table_for_tag(&self, _: Tag) -> Option<FontTable>;
121    fn typographic_bounds(&self, _: GlyphId) -> Rect<f32>;
122
123    /// Get the necessary [`FontInstanceFlags`]` for this font.
124    fn webrender_font_instance_flags(&self) -> FontInstanceFlags;
125
126    /// Return all the variation values that the font was instantiated with.
127    fn variations(&self) -> &[FontVariation];
128
129    fn descriptor_from_os2_table(os2: &Os2) -> FontTemplateDescriptor {
130        let mut style = FontStyle::NORMAL;
131        if os2.fs_selection().contains(SelectionFlags::ITALIC) {
132            style = FontStyle::ITALIC;
133        }
134
135        let weight = FontWeight::from_float(os2.us_weight_class() as f32);
136        let stretch = match os2.us_width_class() {
137            1 => FontStretch::ULTRA_CONDENSED,
138            2 => FontStretch::EXTRA_CONDENSED,
139            3 => FontStretch::CONDENSED,
140            4 => FontStretch::SEMI_CONDENSED,
141            5 => FontStretch::NORMAL,
142            6 => FontStretch::SEMI_EXPANDED,
143            7 => FontStretch::EXPANDED,
144            8 => FontStretch::EXTRA_EXPANDED,
145            9 => FontStretch::ULTRA_EXPANDED,
146            _ => FontStretch::NORMAL,
147        };
148
149        FontTemplateDescriptor::new(weight, stretch, style)
150    }
151}
152
153// Used to abstract over the shaper's choice of fixed int representation.
154pub(crate) type FractionalPixel = f64;
155
156pub(crate) trait FontTableMethods {
157    fn buffer(&self) -> &[u8];
158}
159
160#[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
161pub struct FontMetrics {
162    pub underline_size: Au,
163    pub underline_offset: Au,
164    pub strikeout_size: Au,
165    pub strikeout_offset: Au,
166    pub leading: Au,
167    pub x_height: Au,
168    pub em_size: Au,
169    pub ascent: Au,
170    pub descent: Au,
171    pub max_advance: Au,
172    pub average_advance: Au,
173    pub line_gap: Au,
174    pub zero_horizontal_advance: Option<Au>,
175    pub ic_horizontal_advance: Option<Au>,
176    /// The advance of the space character (' ') in this font or if there is no space,
177    /// the average char advance.
178    pub space_advance: Au,
179}
180
181impl FontMetrics {
182    /// Create an empty [`FontMetrics`] mainly to be used in situations where
183    /// no font can be found.
184    pub fn empty() -> Arc<Self> {
185        static EMPTY: OnceLock<Arc<FontMetrics>> = OnceLock::new();
186        EMPTY.get_or_init(Default::default).clone()
187    }
188
189    /// Whether or not the block metrics of the two `FontMetrics` instances differ in a way
190    /// that requires the resulting block size of a containing inline box to change.
191    pub fn block_metrics_meaningfully_differ(&self, other: &Self) -> bool {
192        self.ascent != other.ascent ||
193            self.descent != other.descent ||
194            self.line_gap != other.line_gap
195    }
196}
197
198#[derive(Debug, Default)]
199struct CachedShapeData {
200    glyph_advances: HashMap<GlyphId, FractionalPixel>,
201    glyph_indices: HashMap<char, Option<GlyphId>>,
202    shaped_text: HashMap<ShapeCacheEntry, Arc<GlyphStore>>,
203}
204
205impl malloc_size_of::MallocSizeOf for CachedShapeData {
206    fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
207        // Estimate the size of the shaped text cache. This will be smaller, because
208        // HashMap has some overhead, but we are mainly interested in the actual data.
209        let shaped_text_size = self
210            .shaped_text
211            .iter()
212            .map(|(key, value)| key.size_of(ops) + (*value).size_of(ops))
213            .sum::<usize>();
214        self.glyph_advances.size_of(ops) + self.glyph_indices.size_of(ops) + shaped_text_size
215    }
216}
217
218pub struct Font {
219    pub(crate) handle: PlatformFont,
220    pub(crate) template: FontTemplateRef,
221    pub metrics: Arc<FontMetrics>,
222    pub descriptor: FontDescriptor,
223
224    /// The data for this font. And the index of the font within the data (in case it's a TTC)
225    /// This might be uninitialized for system fonts.
226    data_and_index: OnceLock<FontDataAndIndex>,
227
228    shaper: OnceLock<Shaper>,
229    cached_shape_data: RwLock<CachedShapeData>,
230    font_instance_key: RwLock<FxHashMap<PainterId, FontInstanceKey>>,
231
232    /// If this is a synthesized small caps font, then this font reference is for
233    /// the version of the font used to replace lowercase ASCII letters. It's up
234    /// to the consumer of this font to properly use this reference.
235    pub(crate) synthesized_small_caps: Option<FontRef>,
236
237    /// Whether or not this font supports color bitmaps or a COLR table. This is
238    /// essentially equivalent to whether or not we use it for emoji presentation.
239    /// This is cached, because getting table data is expensive.
240    has_color_bitmap_or_colr_table: OnceLock<bool>,
241
242    /// Whether or not this font can do fast shaping, ie whether or not it has
243    /// a kern table, but no GSUB and GPOS tables. When this is true, Servo will
244    /// shape Latin horizontal left-to-right text without using Harfbuzz.
245    ///
246    /// FIXME: This should be removed entirely in favor of better caching if necessary.
247    /// See <https://github.com/servo/servo/pull/11273#issuecomment-222332873>.
248    can_do_fast_shaping: OnceLock<bool>,
249}
250
251impl std::fmt::Debug for Font {
252    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
253        f.debug_struct("Font")
254            .field("template", &self.template)
255            .field("descriptor", &self.descriptor)
256            .finish()
257    }
258}
259
260impl malloc_size_of::MallocSizeOf for Font {
261    fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
262        // TODO: Collect memory usage for platform fonts and for shapers.
263        // This skips the template, because they are already stored in the template cache.
264
265        self.metrics.size_of(ops) +
266            self.descriptor.size_of(ops) +
267            self.cached_shape_data.read().size_of(ops) +
268            self.font_instance_key
269                .read()
270                .values()
271                .map(|key| key.size_of(ops))
272                .sum::<usize>()
273    }
274}
275
276impl Font {
277    pub fn new(
278        template: FontTemplateRef,
279        descriptor: FontDescriptor,
280        data: Option<FontData>,
281        synthesized_small_caps: Option<FontRef>,
282    ) -> Result<Font, &'static str> {
283        let synthetic_bold = {
284            let is_bold = descriptor.weight >= FontWeight::BOLD_THRESHOLD;
285            let allows_synthetic_bold = matches!(descriptor.synthesis_weight, FontSynthesis::Auto);
286
287            is_bold && allows_synthetic_bold
288        };
289
290        let handle = PlatformFont::new_from_template(
291            template.clone(),
292            Some(descriptor.pt_size),
293            &descriptor.variation_settings,
294            &data,
295            synthetic_bold,
296        )?;
297        let metrics = Arc::new(handle.metrics());
298
299        Ok(Font {
300            handle,
301            template,
302            metrics,
303            descriptor,
304            data_and_index: data
305                .map(|data| OnceLock::from(FontDataAndIndex { data, index: 0 }))
306                .unwrap_or_default(),
307            shaper: OnceLock::new(),
308            cached_shape_data: Default::default(),
309            font_instance_key: Default::default(),
310            synthesized_small_caps,
311            has_color_bitmap_or_colr_table: OnceLock::new(),
312            can_do_fast_shaping: OnceLock::new(),
313        })
314    }
315
316    /// A unique identifier for the font, allowing comparison.
317    pub fn identifier(&self) -> FontIdentifier {
318        self.template.identifier()
319    }
320
321    pub(crate) fn webrender_font_instance_flags(&self) -> FontInstanceFlags {
322        self.handle.webrender_font_instance_flags()
323    }
324
325    pub(crate) fn has_color_bitmap_or_colr_table(&self) -> bool {
326        *self.has_color_bitmap_or_colr_table.get_or_init(|| {
327            self.table_for_tag(SBIX).is_some() ||
328                self.table_for_tag(CBDT).is_some() ||
329                self.table_for_tag(COLR).is_some()
330        })
331    }
332
333    pub fn key(&self, painter_id: PainterId, font_context: &FontContext) -> FontInstanceKey {
334        *self
335            .font_instance_key
336            .write()
337            .entry(painter_id)
338            .or_insert_with(|| font_context.create_font_instance_key(self, painter_id))
339    }
340
341    /// Return the data for this `Font`. Note that this is currently highly inefficient for system
342    /// fonts and should not be used except in legacy canvas code.
343    pub fn font_data_and_index(&self) -> Result<&FontDataAndIndex, FontDataError> {
344        if let Some(data_and_index) = self.data_and_index.get() {
345            return Ok(data_and_index);
346        }
347
348        let FontIdentifier::Local(local_font_identifier) = self.identifier() else {
349            unreachable!("All web fonts should already have initialized data");
350        };
351        let Some(data_and_index) = local_font_identifier.font_data_and_index() else {
352            return Err(FontDataError::FailedToLoad);
353        };
354
355        let data_and_index = self.data_and_index.get_or_init(move || data_and_index);
356        Ok(data_and_index)
357    }
358
359    pub(crate) fn variations(&self) -> &[FontVariation] {
360        self.handle.variations()
361    }
362}
363
364bitflags! {
365    #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
366    pub struct ShapingFlags: u8 {
367        /// Set if the text is entirely whitespace.
368        const IS_WHITESPACE_SHAPING_FLAG = 1 << 0;
369        /// Set if the text ends with whitespace.
370        const ENDS_WITH_WHITESPACE_SHAPING_FLAG = 1 << 1;
371        /// Set if we are to ignore ligatures.
372        const IGNORE_LIGATURES_SHAPING_FLAG = 1 << 2;
373        /// Set if we are to disable kerning.
374        const DISABLE_KERNING_SHAPING_FLAG = 1 << 3;
375        /// Text direction is right-to-left.
376        const RTL_FLAG = 1 << 4;
377        /// Set if word-break is set to keep-all.
378        const KEEP_ALL_FLAG = 1 << 5;
379    }
380}
381
382/// Various options that control text shaping.
383#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
384pub struct ShapingOptions {
385    /// Spacing to add between each letter. Corresponds to the CSS 2.1 `letter-spacing` property.
386    /// NB: You will probably want to set the `IGNORE_LIGATURES_SHAPING_FLAG` if this is non-null.
387    ///
388    /// Letter spacing is not applied to all characters. Use [Self::letter_spacing_for_character] to
389    /// determine the amount of spacing to apply.
390    pub letter_spacing: Option<Au>,
391    /// Spacing to add between each word. Corresponds to the CSS 2.1 `word-spacing` property.
392    pub word_spacing: Au,
393    /// The Unicode script property of the characters in this run.
394    pub script: Script,
395    /// The preferred language, obtained from the `lang` attribute.
396    pub language: Language,
397    /// Various flags.
398    pub flags: ShapingFlags,
399}
400
401impl ShapingOptions {
402    pub(crate) fn letter_spacing_for_character(&self, character: char) -> Option<Au> {
403        // https://drafts.csswg.org/css-text/#letter-spacing-property
404        // Letter spacing ignores invisible zero-width formatting characters (such as those from the Unicode Cf category).
405        // Spacing must be added as if those characters did not exist in the document.
406        self.letter_spacing.filter(|_| {
407            icu_properties::maps::general_category().get(character) !=
408                icu_properties::GeneralCategory::Format
409        })
410    }
411}
412
413/// An entry in the shape cache.
414#[derive(Clone, Debug, Eq, Hash, PartialEq)]
415struct ShapeCacheEntry {
416    text: String,
417    options: ShapingOptions,
418}
419
420impl Font {
421    pub fn shape_text(&self, text: &str, options: &ShapingOptions) -> Arc<GlyphStore> {
422        let lookup_key = ShapeCacheEntry {
423            text: text.to_owned(),
424            options: *options,
425        };
426        {
427            let cache = self.cached_shape_data.read();
428            if let Some(shaped_text) = cache.shaped_text.get(&lookup_key) {
429                return shaped_text.clone();
430            }
431        }
432
433        let start_time = Instant::now();
434        let glyphs = if self.can_do_fast_shaping(text, options) {
435            debug!("shape_text: Using ASCII fast path.");
436            self.shape_text_fast(text, options)
437        } else {
438            debug!("shape_text: Using Harfbuzz.");
439            self.shaper
440                .get_or_init(|| Shaper::new(self))
441                .shape_text(self, text, options)
442        };
443
444        let shaped_text = Arc::new(glyphs);
445        let mut cache = self.cached_shape_data.write();
446        cache.shaped_text.insert(lookup_key, shaped_text.clone());
447
448        TEXT_SHAPING_PERFORMANCE_COUNTER.fetch_add(
449            ((Instant::now() - start_time).as_nanos()) as usize,
450            Ordering::Relaxed,
451        );
452
453        shaped_text
454    }
455
456    /// Whether not a particular text and [`ShapingOptions`] combination can use
457    /// "fast shaping" ie shaping without Harfbuzz.
458    ///
459    /// Note: This will eventually be removed.
460    pub fn can_do_fast_shaping(&self, text: &str, options: &ShapingOptions) -> bool {
461        options.script == Script::Latin &&
462            !options.flags.contains(ShapingFlags::RTL_FLAG) &&
463            *self.can_do_fast_shaping.get_or_init(|| {
464                self.table_for_tag(KERN).is_some() &&
465                    self.table_for_tag(GPOS).is_none() &&
466                    self.table_for_tag(GSUB).is_none()
467            }) &&
468            text.is_ascii()
469    }
470
471    /// Fast path for ASCII text that only needs simple horizontal LTR kerning.
472    fn shape_text_fast(&self, text: &str, options: &ShapingOptions) -> GlyphStore {
473        let mut glyph_store = GlyphStore::new(text, text.len(), options);
474        let mut prev_glyph_id = None;
475        for (string_byte_offset, byte) in text.bytes().enumerate() {
476            let character = byte as char;
477            let Some(glyph_id) = self.glyph_index(character) else {
478                continue;
479            };
480
481            let mut advance = advance_for_shaped_glyph(
482                Au::from_f64_px(self.glyph_h_advance(glyph_id)),
483                character,
484                options,
485            );
486            let offset = prev_glyph_id.map(|prev| {
487                let h_kerning = Au::from_f64_px(self.glyph_h_kerning(prev, glyph_id));
488                advance += h_kerning;
489                Point2D::new(h_kerning, Au::zero())
490            });
491
492            glyph_store.add_glyph(
493                character,
494                &ShapedGlyph {
495                    glyph_id,
496                    string_byte_offset,
497                    advance,
498                    offset,
499                },
500            );
501            prev_glyph_id = Some(glyph_id);
502        }
503        glyph_store
504    }
505
506    pub(crate) fn table_for_tag(&self, tag: Tag) -> Option<FontTable> {
507        let result = self.handle.table_for_tag(tag);
508        let status = if result.is_some() {
509            "Found"
510        } else {
511            "Didn't find"
512        };
513
514        debug!(
515            "{} font table[{}] in {:?},",
516            status,
517            str::from_utf8(tag.as_ref()).unwrap(),
518            self.identifier()
519        );
520        result
521    }
522
523    #[inline]
524    pub fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
525        {
526            let cache = self.cached_shape_data.read();
527            if let Some(glyph) = cache.glyph_indices.get(&codepoint) {
528                return *glyph;
529            }
530        }
531        let codepoint = match self.descriptor.variant {
532            font_variant_caps::T::SmallCaps => codepoint.to_ascii_uppercase(),
533            font_variant_caps::T::Normal => codepoint,
534        };
535        let glyph_index = self.handle.glyph_index(codepoint);
536
537        let mut cache = self.cached_shape_data.write();
538        cache.glyph_indices.insert(codepoint, glyph_index);
539        glyph_index
540    }
541
542    pub(crate) fn has_glyph_for(&self, codepoint: char) -> bool {
543        self.glyph_index(codepoint).is_some()
544    }
545
546    pub(crate) fn glyph_h_kerning(
547        &self,
548        first_glyph: GlyphId,
549        second_glyph: GlyphId,
550    ) -> FractionalPixel {
551        self.handle.glyph_h_kerning(first_glyph, second_glyph)
552    }
553
554    pub fn glyph_h_advance(&self, glyph_id: GlyphId) -> FractionalPixel {
555        {
556            let cache = self.cached_shape_data.read();
557            if let Some(width) = cache.glyph_advances.get(&glyph_id) {
558                return *width;
559            }
560        }
561
562        let new_width = self
563            .handle
564            .glyph_h_advance(glyph_id)
565            .unwrap_or(LAST_RESORT_GLYPH_ADVANCE as FractionalPixel);
566        let mut cache = self.cached_shape_data.write();
567        cache.glyph_advances.insert(glyph_id, new_width);
568        new_width
569    }
570
571    pub fn typographic_bounds(&self, glyph_id: GlyphId) -> Rect<f32> {
572        self.handle.typographic_bounds(glyph_id)
573    }
574
575    /// Get the [`FontBaseline`] for this font.
576    pub fn baseline(&self) -> Option<FontBaseline> {
577        self.shaper.get_or_init(|| Shaper::new(self)).baseline()
578    }
579
580    #[cfg(not(target_os = "macos"))]
581    pub(crate) fn find_fallback_using_system_font_api(
582        &self,
583        _: &FallbackFontSelectionOptions,
584    ) -> Option<FontRef> {
585        None
586    }
587}
588
589#[derive(Clone, Debug, MallocSizeOf)]
590pub struct FontRef(#[conditional_malloc_size_of] pub(crate) Arc<Font>);
591
592impl Deref for FontRef {
593    type Target = Arc<Font>;
594    fn deref(&self) -> &Self::Target {
595        &self.0
596    }
597}
598
599#[derive(Clone, Debug, Eq, Hash, MallocSizeOf, PartialEq)]
600pub struct FallbackKey {
601    script: Script,
602    unicode_block: Option<UnicodeBlock>,
603    lang: XLang,
604}
605
606impl FallbackKey {
607    fn new(options: &FallbackFontSelectionOptions) -> Self {
608        Self {
609            script: Script::from(options.character),
610            unicode_block: options.character.block(),
611            lang: options.lang.clone(),
612        }
613    }
614}
615
616/// A `FontGroup` is a prioritised list of fonts for a given set of font styles. It is used by
617/// `TextRun` to decide which font to render a character with. If none of the fonts listed in the
618/// styles are suitable, a fallback font may be used.
619#[derive(MallocSizeOf)]
620pub struct FontGroup {
621    /// The [`FontDescriptor`] which describes the properties of the fonts that should
622    /// be loaded for this [`FontGroup`].
623    descriptor: FontDescriptor,
624    /// The families that have been loaded for this [`FontGroup`]. This correponds to the
625    /// list of fonts specified in CSS.
626    families: SmallVec<[FontGroupFamily; 8]>,
627    /// A list of fallbacks that have been used in this [`FontGroup`]. Currently this
628    /// can grow indefinitely, but maybe in the future it should be an LRU cache.
629    /// It's unclear if this is the right thing to do. Perhaps fallbacks should
630    /// always be stored here as it's quite likely that they will be used again.
631    fallbacks: RwLock<HashMap<FallbackKey, FontRef>>,
632}
633
634impl FontGroup {
635    pub(crate) fn new(style: &FontStyleStruct, descriptor: FontDescriptor) -> FontGroup {
636        let families: SmallVec<[FontGroupFamily; 8]> = style
637            .font_family
638            .families
639            .iter()
640            .map(FontGroupFamily::local_or_web)
641            .collect();
642
643        FontGroup {
644            descriptor,
645            families,
646            fallbacks: Default::default(),
647        }
648    }
649
650    /// Finds the first font, or else the first fallback font, which contains a glyph for
651    /// `codepoint`. If no such font is found, returns the first available font or fallback font
652    /// (which will cause a "glyph not found" character to be rendered). If no font at all can be
653    /// found, returns None.
654    pub fn find_by_codepoint(
655        &self,
656        font_context: &FontContext,
657        codepoint: char,
658        next_codepoint: Option<char>,
659        lang: XLang,
660    ) -> Option<FontRef> {
661        // Tab characters are converted into spaces when rendering.
662        // TODO: We should not render a tab character. Instead they should be converted into tab stops
663        // based upon the width of a space character in inline formatting contexts.
664        let codepoint = match codepoint {
665            '\t' => ' ',
666            _ => codepoint,
667        };
668
669        let options = FallbackFontSelectionOptions::new(codepoint, next_codepoint, lang);
670
671        let should_look_for_small_caps = self.descriptor.variant == font_variant_caps::T::SmallCaps &&
672            options.character.is_ascii_lowercase();
673        let font_or_synthesized_small_caps = |font: FontRef| {
674            if should_look_for_small_caps && font.synthesized_small_caps.is_some() {
675                return font.synthesized_small_caps.clone();
676            }
677            Some(font)
678        };
679
680        let font_has_glyph_and_presentation = |font: &FontRef| {
681            // Do not select this font if it goes against our emoji preference.
682            match options.presentation_preference {
683                EmojiPresentationPreference::Text if font.has_color_bitmap_or_colr_table() => {
684                    return false;
685                },
686                EmojiPresentationPreference::Emoji if !font.has_color_bitmap_or_colr_table() => {
687                    return false;
688                },
689                _ => {},
690            }
691            font.has_glyph_for(options.character)
692        };
693
694        let char_in_template =
695            |template: FontTemplateRef| template.char_in_unicode_range(options.character);
696
697        if let Some(font) = self.find(
698            font_context,
699            &char_in_template,
700            &font_has_glyph_and_presentation,
701        ) {
702            return font_or_synthesized_small_caps(font);
703        }
704
705        let fallback_key = FallbackKey::new(&options);
706        if let Some(fallback) = self.fallbacks.read().get(&fallback_key) {
707            if char_in_template(fallback.template.clone()) &&
708                font_has_glyph_and_presentation(fallback)
709            {
710                return font_or_synthesized_small_caps(fallback.clone());
711            }
712        }
713
714        if let Some(font) = self.find_fallback_using_system_font_list(
715            font_context,
716            options.clone(),
717            &char_in_template,
718            &font_has_glyph_and_presentation,
719        ) {
720            let fallback = font_or_synthesized_small_caps(font);
721            if let Some(fallback) = fallback.clone() {
722                self.fallbacks.write().insert(fallback_key, fallback);
723            }
724            return fallback;
725        }
726
727        let first_font = self.first(font_context);
728        if let Some(fallback) = first_font
729            .as_ref()
730            .and_then(|font| font.find_fallback_using_system_font_api(&options))
731        {
732            if font_has_glyph_and_presentation(&fallback) {
733                return Some(fallback);
734            }
735        }
736
737        first_font
738    }
739
740    /// Find the first available font in the group, or the first available fallback font.
741    pub fn first(&self, font_context: &FontContext) -> Option<FontRef> {
742        // From https://drafts.csswg.org/css-fonts/#first-available-font:
743        // > The first available font, used for example in the definition of font-relative lengths
744        // > such as ex or in the definition of the line-height property, is defined to be the first
745        // > font for which the character U+0020 (space) is not excluded by a unicode-range, given the
746        // > font families in the font-family list (or a user agent’s default font if none are
747        // > available).
748        // > Note: it does not matter whether that font actually has a glyph for the space character.
749        let space_in_template = |template: FontTemplateRef| template.char_in_unicode_range(' ');
750        let font_predicate = |_: &FontRef| true;
751        self.find(font_context, &space_in_template, &font_predicate)
752            .or_else(|| {
753                self.find_fallback_using_system_font_list(
754                    font_context,
755                    FallbackFontSelectionOptions::default(),
756                    &space_in_template,
757                    &font_predicate,
758                )
759            })
760    }
761
762    /// Attempts to find a font which matches the given `template_predicate` and `font_predicate`.
763    /// This method mutates because we may need to load new font data in the process of finding
764    /// a suitable font.
765    fn find(
766        &self,
767        font_context: &FontContext,
768        template_predicate: &impl Fn(FontTemplateRef) -> bool,
769        font_predicate: &impl Fn(&FontRef) -> bool,
770    ) -> Option<FontRef> {
771        self.families
772            .iter()
773            .flat_map(|family| family.templates(font_context, &self.descriptor))
774            .find_map(|template| {
775                template.font_if_matches(
776                    font_context,
777                    &self.descriptor,
778                    template_predicate,
779                    font_predicate,
780                )
781            })
782    }
783
784    /// Attempts to find a suitable fallback font which matches the given `template_predicate` and
785    /// `font_predicate` using the system font list. The default family (i.e. "serif") will be tried
786    /// first, followed by platform-specific family names. If a `codepoint` is provided, then its
787    /// Unicode block may be used to refine
788    /// the list of family names which will be tried.
789    fn find_fallback_using_system_font_list(
790        &self,
791        font_context: &FontContext,
792        options: FallbackFontSelectionOptions,
793        template_predicate: &impl Fn(FontTemplateRef) -> bool,
794        font_predicate: &impl Fn(&FontRef) -> bool,
795    ) -> Option<FontRef> {
796        iter::once(FontFamilyDescriptor::default())
797            .chain(
798                fallback_font_families(options)
799                    .into_iter()
800                    .map(|family_name| {
801                        let family = SingleFontFamily::FamilyName(FamilyName {
802                            name: family_name.into(),
803                            syntax: FontFamilyNameSyntax::Quoted,
804                        });
805                        FontFamilyDescriptor::new(family, FontSearchScope::Local)
806                    }),
807            )
808            .find_map(|family_descriptor| {
809                FontGroupFamily::from(family_descriptor)
810                    .templates(font_context, &self.descriptor)
811                    .find_map(|template| {
812                        template.font_if_matches(
813                            font_context,
814                            &self.descriptor,
815                            template_predicate,
816                            font_predicate,
817                        )
818                    })
819            })
820    }
821}
822
823/// A [`FontGroupFamily`] can have multiple associated `FontTemplate`s if it is a
824/// "composite face", meaning that it is defined by multiple `@font-face`
825/// declarations which vary only by their `unicode-range` descriptors. In this case,
826/// font selection will select a single member that contains the necessary unicode
827/// character. Unicode ranges are specified by the [`FontGroupFamilyTemplate::template`]
828/// member.
829#[derive(MallocSizeOf)]
830struct FontGroupFamilyTemplate {
831    #[ignore_malloc_size_of = "This measured in the FontContext template cache."]
832    template: FontTemplateRef,
833    #[ignore_malloc_size_of = "This measured in the FontContext font cache."]
834    font: OnceLock<Option<FontRef>>,
835}
836
837impl From<FontTemplateRef> for FontGroupFamilyTemplate {
838    fn from(template: FontTemplateRef) -> Self {
839        Self {
840            template,
841            font: Default::default(),
842        }
843    }
844}
845
846impl FontGroupFamilyTemplate {
847    fn font(
848        &self,
849        font_context: &FontContext,
850        font_descriptor: &FontDescriptor,
851    ) -> Option<FontRef> {
852        self.font
853            .get_or_init(|| font_context.font(self.template.clone(), font_descriptor))
854            .clone()
855    }
856
857    fn font_if_matches(
858        &self,
859        font_context: &FontContext,
860        font_descriptor: &FontDescriptor,
861        template_predicate: &impl Fn(FontTemplateRef) -> bool,
862        font_predicate: &impl Fn(&FontRef) -> bool,
863    ) -> Option<FontRef> {
864        if !template_predicate(self.template.clone()) {
865            return None;
866        }
867        self.font(font_context, font_descriptor)
868            .filter(font_predicate)
869    }
870}
871
872/// A `FontGroupFamily` is a single font family in a `FontGroup`. It corresponds to one of the
873/// families listed in the `font-family` CSS property. The corresponding font data is lazy-loaded,
874/// only if actually needed. A single `FontGroupFamily` can have multiple fonts, in the case that
875/// individual fonts only cover part of the Unicode range.
876#[derive(MallocSizeOf)]
877struct FontGroupFamily {
878    family_descriptor: FontFamilyDescriptor,
879    members: OnceLock<Vec<FontGroupFamilyTemplate>>,
880}
881
882impl From<FontFamilyDescriptor> for FontGroupFamily {
883    fn from(family_descriptor: FontFamilyDescriptor) -> Self {
884        Self {
885            family_descriptor,
886            members: Default::default(),
887        }
888    }
889}
890
891impl FontGroupFamily {
892    fn local_or_web(family: &SingleFontFamily) -> FontGroupFamily {
893        FontFamilyDescriptor::new(family.clone(), FontSearchScope::Any).into()
894    }
895
896    fn templates(
897        &self,
898        font_context: &FontContext,
899        font_descriptor: &FontDescriptor,
900    ) -> impl Iterator<Item = &FontGroupFamilyTemplate> {
901        self.members
902            .get_or_init(|| {
903                font_context
904                    .matching_templates(font_descriptor, &self.family_descriptor)
905                    .into_iter()
906                    .map(Into::into)
907                    .collect()
908            })
909            .iter()
910    }
911}
912
913/// The scope within which we will look for a font.
914#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
915pub enum FontSearchScope {
916    /// All fonts will be searched, including those specified via `@font-face` rules.
917    Any,
918
919    /// Only local system fonts will be searched.
920    Local,
921}
922
923/// The font family parameters for font selection.
924#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
925pub struct FontFamilyDescriptor {
926    pub(crate) family: SingleFontFamily,
927    pub(crate) scope: FontSearchScope,
928}
929
930impl FontFamilyDescriptor {
931    pub fn new(family: SingleFontFamily, scope: FontSearchScope) -> FontFamilyDescriptor {
932        FontFamilyDescriptor { family, scope }
933    }
934
935    fn default() -> FontFamilyDescriptor {
936        FontFamilyDescriptor {
937            family: SingleFontFamily::Generic(GenericFontFamily::None),
938            scope: FontSearchScope::Local,
939        }
940    }
941}
942
943pub struct FontBaseline {
944    pub ideographic_baseline: f32,
945    pub alphabetic_baseline: f32,
946    pub hanging_baseline: f32,
947}
948
949/// Given a mapping array `mapping` and a value, map that value onto
950/// the value specified by the array. For instance, for FontConfig
951/// values of weights, we would map these onto the CSS [0..1000] range
952/// by creating an array as below. Values that fall between two mapped
953/// values, will be adjusted by the weighted mean.
954///
955/// ```rust
956/// let mapping = [
957///     (0., 0.),
958///     (FC_WEIGHT_REGULAR as f64, 400 as f64),
959///     (FC_WEIGHT_BOLD as f64, 700 as f64),
960///     (FC_WEIGHT_EXTRABLACK as f64, 1000 as f64),
961/// ];
962/// let mapped_weight = apply_font_config_to_style_mapping(&mapping, weight as f64);
963/// ```
964#[cfg(all(
965    any(target_os = "linux", target_os = "macos", target_os = "freebsd"),
966    not(target_env = "ohos")
967))]
968pub(crate) fn map_platform_values_to_style_values(mapping: &[(f64, f64)], value: f64) -> f64 {
969    if value < mapping[0].0 {
970        return mapping[0].1;
971    }
972
973    for window in mapping.windows(2) {
974        let (font_config_value_a, css_value_a) = window[0];
975        let (font_config_value_b, css_value_b) = window[1];
976
977        if value >= font_config_value_a && value <= font_config_value_b {
978            let ratio = (value - font_config_value_a) / (font_config_value_b - font_config_value_a);
979            return css_value_a + ((css_value_b - css_value_a) * ratio);
980        }
981    }
982
983    mapping[mapping.len() - 1].1
984}
985
986/// Computes the total advance for a glyph, taking `letter-spacing` and `word-spacing` into account.
987pub(super) fn advance_for_shaped_glyph(
988    mut advance: Au,
989    character: char,
990    options: &ShapingOptions,
991) -> Au {
992    if let Some(letter_spacing) = options.letter_spacing_for_character(character) {
993        advance += letter_spacing;
994    };
995
996    // CSS 2.1 § 16.4 states that "word spacing affects each space (U+0020) and non-breaking
997    // space (U+00A0) left in the text after the white space processing rules have been
998    // applied. The effect of the property on other word-separator characters is undefined."
999    // We elect to only space the two required code points.
1000    if character == ' ' || character == '\u{a0}' {
1001        // https://drafts.csswg.org/css-text-3/#word-spacing-property
1002        advance += options.word_spacing;
1003    }
1004
1005    advance
1006}