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