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