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