fonts/
font.rs

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