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