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