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