Skip to main content

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