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::RenderingGroupId;
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<RenderingGroupId, 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(
330        &self,
331        rendering_group_id: RenderingGroupId,
332        font_context: &FontContext,
333    ) -> FontInstanceKey {
334        *self
335            .font_instance_key
336            .write()
337            .entry(rendering_group_id)
338            .or_insert_with(|| font_context.create_font_instance_key(self, rendering_group_id))
339    }
340
341    /// Return the data for this `Font`. Note that this is currently highly inefficient for system
342    /// fonts and should not be used except in legacy canvas code.
343    pub fn font_data_and_index(&self) -> Result<&FontDataAndIndex, FontDataError> {
344        if let Some(data_and_index) = self.data_and_index.get() {
345            return Ok(data_and_index);
346        }
347
348        let FontIdentifier::Local(local_font_identifier) = self.identifier() else {
349            unreachable!("All web fonts should already have initialized data");
350        };
351        let Some(data_and_index) = local_font_identifier.font_data_and_index() else {
352            return Err(FontDataError::FailedToLoad);
353        };
354
355        let data_and_index = self.data_and_index.get_or_init(move || data_and_index);
356        Ok(data_and_index)
357    }
358
359    pub(crate) fn variations(&self) -> &[FontVariation] {
360        self.handle.variations()
361    }
362}
363
364bitflags! {
365    #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
366    pub struct ShapingFlags: u8 {
367        /// Set if the text is entirely whitespace.
368        const IS_WHITESPACE_SHAPING_FLAG = 1 << 0;
369        /// Set if the text ends with whitespace.
370        const ENDS_WITH_WHITESPACE_SHAPING_FLAG = 1 << 1;
371        /// Set if we are to ignore ligatures.
372        const IGNORE_LIGATURES_SHAPING_FLAG = 1 << 2;
373        /// Set if we are to disable kerning.
374        const DISABLE_KERNING_SHAPING_FLAG = 1 << 3;
375        /// Text direction is right-to-left.
376        const RTL_FLAG = 1 << 4;
377        /// Set if word-break is set to keep-all.
378        const KEEP_ALL_FLAG = 1 << 5;
379    }
380}
381
382/// Various options that control text shaping.
383#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
384pub struct ShapingOptions {
385    /// Spacing to add between each letter. Corresponds to the CSS 2.1 `letter-spacing` property.
386    /// NB: You will probably want to set the `IGNORE_LIGATURES_SHAPING_FLAG` if this is non-null.
387    pub letter_spacing: Option<Au>,
388    /// Spacing to add between each word. Corresponds to the CSS 2.1 `word-spacing` property.
389    pub word_spacing: Au,
390    /// The Unicode script property of the characters in this run.
391    pub script: Script,
392    /// Various flags.
393    pub flags: ShapingFlags,
394}
395
396/// An entry in the shape cache.
397#[derive(Clone, Debug, Eq, Hash, PartialEq)]
398struct ShapeCacheEntry {
399    text: String,
400    options: ShapingOptions,
401}
402
403impl Font {
404    pub fn shape_text(&self, text: &str, options: &ShapingOptions) -> Arc<GlyphStore> {
405        let lookup_key = ShapeCacheEntry {
406            text: text.to_owned(),
407            options: *options,
408        };
409        {
410            let cache = self.cached_shape_data.read();
411            if let Some(shaped_text) = cache.shaped_text.get(&lookup_key) {
412                return shaped_text.clone();
413            }
414        }
415
416        let is_single_preserved_newline = text.len() == 1 && text.starts_with('\n');
417        let start_time = Instant::now();
418        let mut glyphs = GlyphStore::new(
419            text.len(),
420            options
421                .flags
422                .contains(ShapingFlags::IS_WHITESPACE_SHAPING_FLAG),
423            options
424                .flags
425                .contains(ShapingFlags::ENDS_WITH_WHITESPACE_SHAPING_FLAG),
426            is_single_preserved_newline,
427            options.flags.contains(ShapingFlags::RTL_FLAG),
428        );
429
430        if self.can_do_fast_shaping(text, options) {
431            debug!("shape_text: Using ASCII fast path.");
432            self.shape_text_fast(text, options, &mut glyphs);
433        } else {
434            debug!("shape_text: Using Harfbuzz.");
435            self.shape_text_harfbuzz(text, options, &mut glyphs);
436        }
437
438        let shaped_text = Arc::new(glyphs);
439        let mut cache = self.cached_shape_data.write();
440        cache.shaped_text.insert(lookup_key, shaped_text.clone());
441
442        let end_time = Instant::now();
443        TEXT_SHAPING_PERFORMANCE_COUNTER.fetch_add(
444            (end_time.duration_since(start_time).as_nanos()) as usize,
445            Ordering::Relaxed,
446        );
447
448        shaped_text
449    }
450
451    fn shape_text_harfbuzz(&self, text: &str, options: &ShapingOptions, glyphs: &mut GlyphStore) {
452        self.shaper
453            .get_or_init(|| Shaper::new(self))
454            .shape_text(text, options, glyphs);
455    }
456
457    /// Whether not a particular text and [`ShapingOptions`] combination can use
458    /// "fast shaping" ie shaping without Harfbuzz.
459    ///
460    /// Note: This will eventually be removed.
461    pub fn can_do_fast_shaping(&self, text: &str, options: &ShapingOptions) -> bool {
462        options.script == Script::Latin &&
463            !options.flags.contains(ShapingFlags::RTL_FLAG) &&
464            *self.can_do_fast_shaping.get_or_init(|| {
465                self.table_for_tag(KERN).is_some() &&
466                    self.table_for_tag(GPOS).is_none() &&
467                    self.table_for_tag(GSUB).is_none()
468            }) &&
469            text.is_ascii()
470    }
471
472    /// Fast path for ASCII text that only needs simple horizontal LTR kerning.
473    fn shape_text_fast(&self, text: &str, options: &ShapingOptions, glyphs: &mut GlyphStore) {
474        let mut prev_glyph_id = None;
475        for (i, byte) in text.bytes().enumerate() {
476            let character = byte as char;
477            let glyph_id = match self.glyph_index(character) {
478                Some(id) => id,
479                None => continue,
480            };
481
482            let mut advance = advance_for_shaped_glyph(
483                Au::from_f64_px(self.glyph_h_advance(glyph_id)),
484                character,
485                options,
486            );
487            let offset = prev_glyph_id.map(|prev| {
488                let h_kerning = Au::from_f64_px(self.glyph_h_kerning(prev, glyph_id));
489                advance += h_kerning;
490                Point2D::new(h_kerning, Au::zero())
491            });
492
493            let glyph = GlyphData::new(glyph_id, advance, offset, true, true);
494            glyphs.add_glyph_for_byte_index(ByteIndex(i as isize), character, &glyph);
495            prev_glyph_id = Some(glyph_id);
496        }
497        glyphs.finalize_changes();
498    }
499
500    pub(crate) fn table_for_tag(&self, tag: Tag) -> Option<FontTable> {
501        let result = self.handle.table_for_tag(tag);
502        let status = if result.is_some() {
503            "Found"
504        } else {
505            "Didn't find"
506        };
507
508        debug!(
509            "{} font table[{}] in {:?},",
510            status,
511            str::from_utf8(tag.as_ref()).unwrap(),
512            self.identifier()
513        );
514        result
515    }
516
517    #[inline]
518    pub fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
519        {
520            let cache = self.cached_shape_data.read();
521            if let Some(glyph) = cache.glyph_indices.get(&codepoint) {
522                return *glyph;
523            }
524        }
525        let codepoint = match self.descriptor.variant {
526            font_variant_caps::T::SmallCaps => codepoint.to_ascii_uppercase(),
527            font_variant_caps::T::Normal => codepoint,
528        };
529        let glyph_index = self.handle.glyph_index(codepoint);
530
531        let mut cache = self.cached_shape_data.write();
532        cache.glyph_indices.insert(codepoint, glyph_index);
533        glyph_index
534    }
535
536    pub(crate) fn has_glyph_for(&self, codepoint: char) -> bool {
537        self.glyph_index(codepoint).is_some()
538    }
539
540    pub(crate) fn glyph_h_kerning(
541        &self,
542        first_glyph: GlyphId,
543        second_glyph: GlyphId,
544    ) -> FractionalPixel {
545        self.handle.glyph_h_kerning(first_glyph, second_glyph)
546    }
547
548    pub fn glyph_h_advance(&self, glyph_id: GlyphId) -> FractionalPixel {
549        {
550            let cache = self.cached_shape_data.read();
551            if let Some(width) = cache.glyph_advances.get(&glyph_id) {
552                return *width;
553            }
554        }
555
556        // TODO: Need a fallback strategy.
557        let new_width = match self.handle.glyph_h_advance(glyph_id) {
558            Some(adv) => adv,
559            None => LAST_RESORT_GLYPH_ADVANCE as FractionalPixel,
560        };
561
562        let mut cache = self.cached_shape_data.write();
563        cache.glyph_advances.insert(glyph_id, new_width);
564        new_width
565    }
566
567    pub fn typographic_bounds(&self, glyph_id: GlyphId) -> Rect<f32> {
568        self.handle.typographic_bounds(glyph_id)
569    }
570
571    /// Get the [`FontBaseline`] for this font.
572    pub fn baseline(&self) -> Option<FontBaseline> {
573        self.shaper.get_or_init(|| Shaper::new(self)).baseline()
574    }
575}
576
577#[derive(Clone, MallocSizeOf)]
578pub struct FontRef(#[conditional_malloc_size_of] pub(crate) Arc<Font>);
579
580impl Deref for FontRef {
581    type Target = Arc<Font>;
582    fn deref(&self) -> &Self::Target {
583        &self.0
584    }
585}
586
587/// A `FontGroup` is a prioritised list of fonts for a given set of font styles. It is used by
588/// `TextRun` to decide which font to render a character with. If none of the fonts listed in the
589/// styles are suitable, a fallback font may be used.
590#[derive(MallocSizeOf)]
591pub struct FontGroup {
592    descriptor: FontDescriptor,
593    families: SmallVec<[FontGroupFamily; 8]>,
594}
595
596impl FontGroup {
597    pub(crate) fn new(style: &FontStyleStruct, descriptor: FontDescriptor) -> FontGroup {
598        let families: SmallVec<[FontGroupFamily; 8]> = style
599            .font_family
600            .families
601            .iter()
602            .map(FontGroupFamily::new)
603            .collect();
604
605        FontGroup {
606            descriptor,
607            families,
608        }
609    }
610
611    /// Finds the first font, or else the first fallback font, which contains a glyph for
612    /// `codepoint`. If no such font is found, returns the first available font or fallback font
613    /// (which will cause a "glyph not found" character to be rendered). If no font at all can be
614    /// found, returns None.
615    pub fn find_by_codepoint(
616        &mut self,
617        font_context: &FontContext,
618        codepoint: char,
619        next_codepoint: Option<char>,
620        first_fallback: Option<FontRef>,
621        lang: Option<String>,
622    ) -> Option<FontRef> {
623        // Tab characters are converted into spaces when rendering.
624        // TODO: We should not render a tab character. Instead they should be converted into tab stops
625        // based upon the width of a space character in inline formatting contexts.
626        let codepoint = match codepoint {
627            '\t' => ' ',
628            _ => codepoint,
629        };
630
631        let options = FallbackFontSelectionOptions::new(codepoint, next_codepoint, lang);
632
633        let should_look_for_small_caps = self.descriptor.variant == font_variant_caps::T::SmallCaps &&
634            options.character.is_ascii_lowercase();
635        let font_or_synthesized_small_caps = |font: FontRef| {
636            if should_look_for_small_caps && font.synthesized_small_caps.is_some() {
637                return font.synthesized_small_caps.clone();
638            }
639            Some(font)
640        };
641
642        let font_has_glyph_and_presentation = |font: &FontRef| {
643            // Do not select this font if it goes against our emoji preference.
644            match options.presentation_preference {
645                EmojiPresentationPreference::Text if font.has_color_bitmap_or_colr_table() => {
646                    return false;
647                },
648                EmojiPresentationPreference::Emoji if !font.has_color_bitmap_or_colr_table() => {
649                    return false;
650                },
651                _ => {},
652            }
653            font.has_glyph_for(options.character)
654        };
655
656        let char_in_template =
657            |template: FontTemplateRef| template.char_in_unicode_range(options.character);
658
659        if let Some(font) = self.find(
660            font_context,
661            char_in_template,
662            font_has_glyph_and_presentation,
663        ) {
664            return font_or_synthesized_small_caps(font);
665        }
666
667        if let Some(ref first_fallback) = first_fallback {
668            if char_in_template(first_fallback.template.clone()) &&
669                font_has_glyph_and_presentation(first_fallback)
670            {
671                return font_or_synthesized_small_caps(first_fallback.clone());
672            }
673        }
674
675        if let Some(font) = self.find_fallback(
676            font_context,
677            options.clone(),
678            char_in_template,
679            font_has_glyph_and_presentation,
680        ) {
681            return font_or_synthesized_small_caps(font);
682        }
683
684        self.first(font_context)
685    }
686
687    /// Find the first available font in the group, or the first available fallback font.
688    pub fn first(&mut self, font_context: &FontContext) -> Option<FontRef> {
689        // From https://drafts.csswg.org/css-fonts/#first-available-font:
690        // > The first available font, used for example in the definition of font-relative lengths
691        // > such as ex or in the definition of the line-height property, is defined to be the first
692        // > font for which the character U+0020 (space) is not excluded by a unicode-range, given the
693        // > font families in the font-family list (or a user agent’s default font if none are
694        // > available).
695        // > Note: it does not matter whether that font actually has a glyph for the space character.
696        let space_in_template = |template: FontTemplateRef| template.char_in_unicode_range(' ');
697        let font_predicate = |_: &FontRef| true;
698        self.find(font_context, space_in_template, font_predicate)
699            .or_else(|| {
700                self.find_fallback(
701                    font_context,
702                    FallbackFontSelectionOptions::default(),
703                    space_in_template,
704                    font_predicate,
705                )
706            })
707    }
708
709    /// Attempts to find a font which matches the given `template_predicate` and `font_predicate`.
710    /// This method mutates because we may need to load new font data in the process of finding
711    /// a suitable font.
712    fn find<TemplatePredicate, FontPredicate>(
713        &mut self,
714        font_context: &FontContext,
715        template_predicate: TemplatePredicate,
716        font_predicate: FontPredicate,
717    ) -> Option<FontRef>
718    where
719        TemplatePredicate: Fn(FontTemplateRef) -> bool,
720        FontPredicate: Fn(&FontRef) -> bool,
721    {
722        let font_descriptor = self.descriptor.clone();
723        self.families
724            .iter_mut()
725            .filter_map(|font_group_family| {
726                font_group_family.find(
727                    &font_descriptor,
728                    font_context,
729                    &template_predicate,
730                    &font_predicate,
731                )
732            })
733            .next()
734    }
735
736    /// Attempts to find a suitable fallback font which matches the given `template_predicate` and
737    /// `font_predicate`. The default family (i.e. "serif") will be tried first, followed by
738    /// platform-specific family names. If a `codepoint` is provided, then its Unicode block may be
739    /// used to refine the list of family names which will be tried.
740    fn find_fallback<TemplatePredicate, FontPredicate>(
741        &mut self,
742        font_context: &FontContext,
743        options: FallbackFontSelectionOptions,
744        template_predicate: TemplatePredicate,
745        font_predicate: FontPredicate,
746    ) -> Option<FontRef>
747    where
748        TemplatePredicate: Fn(FontTemplateRef) -> bool,
749        FontPredicate: Fn(&FontRef) -> bool,
750    {
751        iter::once(FontFamilyDescriptor::default())
752            .chain(
753                fallback_font_families(options)
754                    .into_iter()
755                    .map(|family_name| {
756                        let family = SingleFontFamily::FamilyName(FamilyName {
757                            name: family_name.into(),
758                            syntax: FontFamilyNameSyntax::Quoted,
759                        });
760                        FontFamilyDescriptor::new(family, FontSearchScope::Local)
761                    }),
762            )
763            .filter_map(|family_descriptor| {
764                FontGroupFamily {
765                    family_descriptor,
766                    members: None,
767                }
768                .find(
769                    &self.descriptor,
770                    font_context,
771                    &template_predicate,
772                    &font_predicate,
773                )
774            })
775            .next()
776    }
777}
778
779/// A [`FontGroupFamily`] can have multiple members if it is a "composite face", meaning
780/// that it is defined by multiple `@font-face` declarations which vary only by their
781/// `unicode-range` descriptors. In this case, font selection will select a single member
782/// that contains the necessary unicode character. Unicode ranges are specified by the
783/// [`FontGroupFamilyMember::template`] member.
784#[derive(MallocSizeOf)]
785struct FontGroupFamilyMember {
786    #[ignore_malloc_size_of = "This measured in the FontContext template cache."]
787    template: FontTemplateRef,
788    #[ignore_malloc_size_of = "This measured in the FontContext font cache."]
789    font: Option<FontRef>,
790    loaded: bool,
791}
792
793/// A `FontGroupFamily` is a single font family in a `FontGroup`. It corresponds to one of the
794/// families listed in the `font-family` CSS property. The corresponding font data is lazy-loaded,
795/// only if actually needed. A single `FontGroupFamily` can have multiple fonts, in the case that
796/// individual fonts only cover part of the Unicode range.
797#[derive(MallocSizeOf)]
798struct FontGroupFamily {
799    family_descriptor: FontFamilyDescriptor,
800    members: Option<Vec<FontGroupFamilyMember>>,
801}
802
803impl FontGroupFamily {
804    fn new(family: &SingleFontFamily) -> FontGroupFamily {
805        FontGroupFamily {
806            family_descriptor: FontFamilyDescriptor::new(family.clone(), FontSearchScope::Any),
807            members: None,
808        }
809    }
810
811    fn find<TemplatePredicate, FontPredicate>(
812        &mut self,
813        font_descriptor: &FontDescriptor,
814        font_context: &FontContext,
815        template_predicate: &TemplatePredicate,
816        font_predicate: &FontPredicate,
817    ) -> Option<FontRef>
818    where
819        TemplatePredicate: Fn(FontTemplateRef) -> bool,
820        FontPredicate: Fn(&FontRef) -> bool,
821    {
822        self.members(font_descriptor, font_context)
823            .filter_map(|member| {
824                if !template_predicate(member.template.clone()) {
825                    return None;
826                }
827
828                if !member.loaded {
829                    member.font = font_context.font(member.template.clone(), font_descriptor);
830                    member.loaded = true;
831                }
832                if matches!(&member.font, Some(font) if font_predicate(font)) {
833                    return member.font.clone();
834                }
835
836                None
837            })
838            .next()
839    }
840
841    fn members(
842        &mut self,
843        font_descriptor: &FontDescriptor,
844        font_context: &FontContext,
845    ) -> impl Iterator<Item = &mut FontGroupFamilyMember> {
846        let family_descriptor = &self.family_descriptor;
847        let members = self.members.get_or_insert_with(|| {
848            font_context
849                .matching_templates(font_descriptor, family_descriptor)
850                .into_iter()
851                .map(|template| FontGroupFamilyMember {
852                    template,
853                    loaded: false,
854                    font: None,
855                })
856                .collect()
857        });
858
859        members.iter_mut()
860    }
861}
862
863/// The scope within which we will look for a font.
864#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
865pub enum FontSearchScope {
866    /// All fonts will be searched, including those specified via `@font-face` rules.
867    Any,
868
869    /// Only local system fonts will be searched.
870    Local,
871}
872
873/// The font family parameters for font selection.
874#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
875pub struct FontFamilyDescriptor {
876    pub(crate) family: SingleFontFamily,
877    pub(crate) scope: FontSearchScope,
878}
879
880impl FontFamilyDescriptor {
881    pub fn new(family: SingleFontFamily, scope: FontSearchScope) -> FontFamilyDescriptor {
882        FontFamilyDescriptor { family, scope }
883    }
884
885    fn default() -> FontFamilyDescriptor {
886        FontFamilyDescriptor {
887            family: SingleFontFamily::Generic(GenericFontFamily::None),
888            scope: FontSearchScope::Local,
889        }
890    }
891}
892
893pub struct FontBaseline {
894    pub ideographic_baseline: f32,
895    pub alphabetic_baseline: f32,
896    pub hanging_baseline: f32,
897}
898
899/// Given a mapping array `mapping` and a value, map that value onto
900/// the value specified by the array. For instance, for FontConfig
901/// values of weights, we would map these onto the CSS [0..1000] range
902/// by creating an array as below. Values that fall between two mapped
903/// values, will be adjusted by the weighted mean.
904///
905/// ```rust
906/// let mapping = [
907///     (0., 0.),
908///     (FC_WEIGHT_REGULAR as f64, 400 as f64),
909///     (FC_WEIGHT_BOLD as f64, 700 as f64),
910///     (FC_WEIGHT_EXTRABLACK as f64, 1000 as f64),
911/// ];
912/// let mapped_weight = apply_font_config_to_style_mapping(&mapping, weight as f64);
913/// ```
914#[cfg(all(
915    any(target_os = "linux", target_os = "macos"),
916    not(target_env = "ohos")
917))]
918pub(crate) fn map_platform_values_to_style_values(mapping: &[(f64, f64)], value: f64) -> f64 {
919    if value < mapping[0].0 {
920        return mapping[0].1;
921    }
922
923    for window in mapping.windows(2) {
924        let (font_config_value_a, css_value_a) = window[0];
925        let (font_config_value_b, css_value_b) = window[1];
926
927        if value >= font_config_value_a && value <= font_config_value_b {
928            let ratio = (value - font_config_value_a) / (font_config_value_b - font_config_value_a);
929            return css_value_a + ((css_value_b - css_value_a) * ratio);
930        }
931    }
932
933    mapping[mapping.len() - 1].1
934}
935
936/// Computes the total advance for a glyph, taking `letter-spacing` and `word-spacing` into account.
937pub(super) fn advance_for_shaped_glyph(
938    mut advance: Au,
939    character: char,
940    options: &ShapingOptions,
941) -> Au {
942    if let Some(letter_spacing) = options.letter_spacing {
943        advance += letter_spacing;
944    };
945
946    // CSS 2.1 § 16.4 states that "word spacing affects each space (U+0020) and non-breaking
947    // space (U+00A0) left in the text after the white space processing rules have been
948    // applied. The effect of the property on other word-separator characters is undefined."
949    // We elect to only space the two required code points.
950    if character == ' ' || character == '\u{a0}' {
951        // https://drafts.csswg.org/css-text-3/#word-spacing-property
952        advance += options.word_spacing;
953    }
954
955    advance
956}