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