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