Skip to main content

fonts_traits/
font_template.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::fmt::{Debug, Error, Formatter};
6use std::ops::{Deref, RangeInclusive};
7use std::sync::Arc;
8
9use atomic_refcell::{AtomicRef, AtomicRefCell};
10use malloc_size_of_derive::MallocSizeOf;
11use read_fonts::collections::int_set::Domain;
12use read_fonts::types::Tag;
13use serde::{Deserialize, Serialize};
14use style::computed_values::font_optical_sizing::T as FontOpticalSizing;
15use style::computed_values::font_stretch::T as FontStretch;
16use style::computed_values::font_style::T as FontStyle;
17use style::font_face::{
18    ComputedFontStretchRange, ComputedFontStyleDescriptor, ComputedFontWeightRange,
19};
20use style::properties::generated::font_face::Descriptors as FontFaceRuleDescriptors;
21use style::values::computed::font::FontWeight;
22use webrender_api::FontVariation;
23
24use crate::{CSSFontFaceDescriptors, FontDescriptor, FontIdentifier};
25
26/// A reference to a [`FontTemplate`] with shared ownership and mutability.
27#[derive(Clone, Debug, MallocSizeOf)]
28pub struct FontTemplateRef(#[conditional_malloc_size_of] Arc<AtomicRefCell<FontTemplate>>);
29
30impl FontTemplateRef {
31    pub fn new(template: FontTemplate) -> Self {
32        Self(Arc::new(AtomicRefCell::new(template)))
33    }
34}
35
36impl Deref for FontTemplateRef {
37    type Target = Arc<AtomicRefCell<FontTemplate>>;
38    fn deref(&self) -> &Self::Target {
39        &self.0
40    }
41}
42
43/// Describes how to select a font from a given family. This is very basic at the moment and needs
44/// to be expanded or refactored when we support more of the font styling parameters.
45///
46/// NB: If you change this, you will need to update `style::properties::compute_font_hash()`.
47#[derive(Clone, Debug, Deserialize, Hash, MallocSizeOf, PartialEq, Serialize)]
48pub struct FontTemplateDescriptor {
49    pub weight: ComputedFontWeightRange,
50    pub stretch: ComputedFontStretchRange,
51    pub style: (FontStyle, FontStyle),
52    #[ignore_malloc_size_of = "MallocSizeOf does not yet support RangeInclusive"]
53    pub unicode_range: Option<Vec<RangeInclusive<u32>>>,
54}
55
56impl Default for FontTemplateDescriptor {
57    fn default() -> Self {
58        Self::new(FontWeight::normal(), FontStretch::NORMAL, FontStyle::NORMAL)
59    }
60}
61
62/// FontTemplateDescriptor contains floats, which are not Eq because of NaN. However,
63/// we know they will never be NaN, so we can manually implement Eq.
64impl Eq for FontTemplateDescriptor {}
65
66impl FontTemplateDescriptor {
67    #[inline]
68    pub fn new(weight: FontWeight, stretch: FontStretch, style: FontStyle) -> Self {
69        Self {
70            weight: ComputedFontWeightRange(weight, weight),
71            stretch: ComputedFontStretchRange(stretch, stretch),
72            style: (style, style),
73            unicode_range: None,
74        }
75    }
76
77    pub fn is_variation_font(&self) -> bool {
78        self.weight.0 != self.weight.1 ||
79            self.stretch.0 != self.stretch.1 ||
80            self.style.0 != self.style.1
81    }
82
83    /// Returns a score indicating how far apart visually the two font descriptors are. This is
84    /// used for implmenting the CSS Font Matching algorithm:
85    /// <https://drafts.csswg.org/css-fonts/#font-matching-algorithm>.
86    ///
87    /// The smaller the score, the better the fonts match. 0 indicates an exact match. This must
88    /// be commutative (distance(A, B) == distance(B, A)).
89    #[inline]
90    fn distance_from(&self, target: &FontDescriptor) -> f32 {
91        let stretch_distance = target.stretch.match_distance(&self.stretch);
92        let style_distance = target.style.match_distance(&self.style);
93        let weight_distance = target.weight.match_distance(&self.weight);
94
95        // Sanity-check that the distances are within the expected range
96        // (update if implementation of the distance functions is changed).
97        assert!((0.0..=2000.0).contains(&stretch_distance));
98        assert!((0.0..=500.0).contains(&style_distance));
99        assert!((0.0..=1600.0).contains(&weight_distance));
100
101        // Factors used to weight the distances between the available and target font
102        // properties during font-matching. These ensure that we respect the CSS-fonts
103        // requirement that font-stretch >> font-style >> font-weight; and in addition,
104        // a mismatch between the desired and actual glyph presentation (emoji vs text)
105        // will take precedence over any of the style attributes.
106        //
107        // Also relevant for font selection is the emoji presentation preference, but this
108        // is handled later when filtering fonts based on the glyphs they contain.
109        const STRETCH_FACTOR: f32 = 1.0e8;
110        const STYLE_FACTOR: f32 = 1.0e4;
111        const WEIGHT_FACTOR: f32 = 1.0e0;
112
113        stretch_distance * STRETCH_FACTOR +
114            style_distance * STYLE_FACTOR +
115            weight_distance * WEIGHT_FACTOR
116    }
117
118    fn matches(&self, descriptor_to_match: &FontDescriptor) -> bool {
119        self.weight.0 <= descriptor_to_match.weight &&
120            self.weight.1 >= descriptor_to_match.weight &&
121            self.style.0 <= descriptor_to_match.style &&
122            self.style.1 >= descriptor_to_match.style &&
123            self.stretch.0 <= descriptor_to_match.stretch &&
124            self.stretch.1 >= descriptor_to_match.stretch
125    }
126
127    pub fn override_values_with_css_font_template_descriptors(
128        &mut self,
129        css_font_template_descriptors: &CSSFontFaceDescriptors,
130    ) {
131        if let Some(ref weight) = css_font_template_descriptors.weight {
132            self.weight = weight.clone();
133        }
134        self.style = match css_font_template_descriptors.style {
135            Some(ComputedFontStyleDescriptor::Italic) => (FontStyle::ITALIC, FontStyle::ITALIC),
136            Some(ComputedFontStyleDescriptor::Oblique(angle_1, angle_2)) => (
137                FontStyle::oblique(angle_1.to_float()),
138                FontStyle::oblique(angle_2.to_float()),
139            ),
140            None => self.style,
141        };
142        if let Some(ref stretch) = css_font_template_descriptors.stretch {
143            self.stretch = stretch.clone();
144        }
145        if let Some(ref unicode_range) = css_font_template_descriptors.unicode_range {
146            self.unicode_range = Some(unicode_range.clone());
147        }
148    }
149}
150
151/// This describes all the information needed to create
152/// font instance handles. It contains a unique
153/// FontTemplateData structure that is platform specific.
154#[derive(Clone, Deserialize, MallocSizeOf, Serialize)]
155pub struct FontTemplate {
156    pub identifier: FontIdentifier,
157    pub descriptor: FontTemplateDescriptor,
158
159    /// If this font is a web font, this is a reference to the `@font-face` rule that
160    /// created it.
161    #[serde(skip)]
162    pub font_face_rule: Option<FontFaceRuleDescriptors>,
163}
164
165impl Debug for FontTemplate {
166    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
167        self.identifier.fmt(f)
168    }
169}
170
171/// Holds all of the template information for a font that
172/// is common, regardless of the number of instances of
173/// this font handle per thread.
174impl FontTemplate {
175    /// Create a new [`FontTemplate`].
176    pub fn new(
177        identifier: FontIdentifier,
178        descriptor: FontTemplateDescriptor,
179        font_face_rule: Option<FontFaceRuleDescriptors>,
180    ) -> FontTemplate {
181        FontTemplate {
182            identifier,
183            descriptor,
184            font_face_rule,
185        }
186    }
187
188    /// Create a new [`FontTemplate`] for a `@font-family` with a `local(...)` `src`. This takes in
189    /// the template of the local font and creates a new one that reflects the properties specified
190    /// by `@font-family` in the stylesheet.
191    pub fn new_for_local_web_font(
192        local_template: FontTemplateRef,
193        css_font_template_descriptors: &CSSFontFaceDescriptors,
194        font_face_rule: Option<FontFaceRuleDescriptors>,
195    ) -> Result<FontTemplate, &'static str> {
196        let mut alias_template = local_template.borrow().clone();
197        alias_template
198            .descriptor
199            .override_values_with_css_font_template_descriptors(css_font_template_descriptors);
200        alias_template.font_face_rule = font_face_rule;
201        Ok(alias_template)
202    }
203
204    pub fn identifier(&self) -> &FontIdentifier {
205        &self.identifier
206    }
207
208    /// <https://drafts.csswg.org/css-fonts-4/#apply-font-matching-variations>
209    pub fn compute_variations(&self, descriptor: &FontDescriptor) -> Vec<FontVariation> {
210        // The steps in this algorithm are inverted order because they are listed in ascending order of precedence.
211        let mut variations: Vec<FontVariation> = vec![];
212
213        let mut add_variation = |variation: FontVariation| {
214            if !variations
215                .iter()
216                .any(|existing_variation| existing_variation.tag == variation.tag)
217            {
218                variations.push(variation);
219            }
220        };
221
222        // Step 12. Font variations implied by the value of the font-variation-settings property are applied.
223        // These values should be clamped to the values that are supported by the font.
224        // NOTE: Clamping happens inside the PlatformFont.
225        descriptor
226            .variation_settings
227            .iter()
228            .copied()
229            .for_each(&mut add_variation);
230
231        // Step 9. Font variations implied by the value of the font-optical-sizing property are applied.
232        // NOTE The precise behaviour of font-optical-sizing:auto is not defined.
233        // We choose to set "opsz" to the font size if it's not already set elsewhere. This is the easiest
234        // at the end of this function, so we move this step down.
235
236        if let Some(font_face_rule) = &self.font_face_rule {
237            // Step 6. If the font is defined via an @font-face rule, the font variations implied by the font-variation-settings
238            // descriptor in the @font-face rule are applied.
239            if let Some(variation_settings) = font_face_rule.font_variation_settings.as_ref() {
240                variation_settings
241                    .0
242                    .iter()
243                    .map(|variation| FontVariation {
244                        tag: variation.tag.0,
245                        value: variation.value.get().expect(
246                            "The value is enforced to be resolvable at parse time \
247                            (see FontVariationSettings::parse_for_font_face_rule).",
248                        ),
249                    })
250                    .for_each(&mut add_variation);
251            }
252        }
253
254        // Step 2. Font variations as enabled by the font-weight, font-width, and font-style properties are applied.
255        // FIXME: Apply variations for font-style
256        // NOTE: font-stretch is a legacy alias to font-width
257        add_variation(FontVariation {
258            tag: Tag::new(b"wght").to_u32(),
259            value: descriptor.weight.value(),
260        });
261
262        add_variation(FontVariation {
263            tag: Tag::new(b"wdth").to_u32(),
264            value: descriptor.stretch.0.to_float(),
265        });
266
267        // This is the implementation for Step 9. Refer to the note on Step 9 for an explanation of why it's here.
268        if descriptor.optical_sizing == FontOpticalSizing::Auto {
269            add_variation(FontVariation {
270                tag: Tag::new(b"opsz").to_u32(),
271                value: descriptor.pt_size.to_f32_px(),
272            });
273        }
274
275        variations
276    }
277
278    pub fn is_defined_by_font_face_rule(&self, rule: &FontFaceRuleDescriptors) -> bool {
279        self.font_face_rule
280            .as_ref()
281            .is_some_and(|defining_rule| defining_rule == rule)
282    }
283}
284
285pub trait FontTemplateRefMethods {
286    /// Get the descriptor.
287    fn descriptor(&self) -> FontTemplateDescriptor;
288    /// Get the [`FontIdentifier`] for this template.
289    fn identifier(&self) -> FontIdentifier;
290    /// Returns true if the given descriptor matches the one in this [`FontTemplate`].
291    fn matches_font_descriptor(&self, descriptor_to_match: &FontDescriptor) -> bool;
292    /// Calculate the distance from this [`FontTemplate`]s descriptor and return it
293    /// or None if this is not a valid [`FontTemplate`].
294    fn descriptor_distance(&self, descriptor_to_match: &FontDescriptor) -> f32;
295    /// Whether or not this character is in the unicode ranges specified in
296    /// this temlates `@font-face` definition, if any.
297    fn char_in_unicode_range(&self, character: char) -> bool;
298
299    /// Return the `@font-face` rule that defined this template, if any.
300    fn font_face_rule(&self) -> Option<AtomicRef<'_, FontFaceRuleDescriptors>>;
301}
302
303impl FontTemplateRefMethods for FontTemplateRef {
304    fn descriptor(&self) -> FontTemplateDescriptor {
305        self.borrow().descriptor.clone()
306    }
307
308    fn identifier(&self) -> FontIdentifier {
309        self.borrow().identifier.clone()
310    }
311
312    fn matches_font_descriptor(&self, descriptor_to_match: &FontDescriptor) -> bool {
313        self.descriptor().matches(descriptor_to_match)
314    }
315
316    fn descriptor_distance(&self, descriptor_to_match: &FontDescriptor) -> f32 {
317        self.descriptor().distance_from(descriptor_to_match)
318    }
319
320    fn char_in_unicode_range(&self, character: char) -> bool {
321        let character = character as u32;
322        self.borrow()
323            .descriptor
324            .unicode_range
325            .as_ref()
326            .is_none_or(|ranges| ranges.iter().any(|range| range.contains(&character)))
327    }
328
329    fn font_face_rule(&self) -> Option<AtomicRef<'_, FontFaceRuleDescriptors>> {
330        AtomicRef::filter_map(self.borrow(), |template| template.font_face_rule.as_ref())
331    }
332}
333
334/// A trait for implementing the CSS font matching algorithm against various font features.
335/// See <https://drafts.csswg.org/css-fonts/#font-matching-algorithm>.
336///
337/// This implementation is ported from Gecko at:
338/// <https://searchfox.org/mozilla-central/rev/0529464f0d2981347ef581f7521ace8b7af7f7ac/gfx/thebes/gfxFontUtils.h#1217>.
339trait FontMatchDistanceMethod<T>: Sized {
340    fn match_distance(&self, range: &T) -> f32;
341    fn to_float(&self) -> f32;
342}
343
344impl FontMatchDistanceMethod<ComputedFontStretchRange> for FontStretch {
345    fn match_distance(&self, range: &ComputedFontStretchRange) -> f32 {
346        // stretch distance ==> [0,2000]
347        const REVERSE_DISTANCE: f32 = 1000.0;
348
349        let min_stretch = range.0;
350        let max_stretch = range.1;
351
352        // The stretch value is a (non-negative) percentage; currently we support
353        // values in the range 0 .. 1000. (If the upper limit is ever increased,
354        // the kReverseDistance value used here may need to be adjusted.)
355        // If aTargetStretch is >100, we prefer larger values if available;
356        // if <=100, we prefer smaller values if available.
357        if *self < min_stretch {
358            if *self > FontStretch::NORMAL {
359                return min_stretch.to_float() - self.to_float();
360            }
361            return (min_stretch.to_float() - self.to_float()) + REVERSE_DISTANCE;
362        }
363
364        if *self > max_stretch {
365            if *self <= FontStretch::NORMAL {
366                return self.to_float() - max_stretch.to_float();
367            }
368            return (self.to_float() - max_stretch.to_float()) + REVERSE_DISTANCE;
369        }
370        0.0
371    }
372
373    fn to_float(&self) -> f32 {
374        self.0.to_float()
375    }
376}
377
378impl FontMatchDistanceMethod<ComputedFontWeightRange> for FontWeight {
379    // Calculate weight distance with values in the range (0..1000). In general,
380    // heavier weights match towards even heavier weights while lighter weights
381    // match towards even lighter weights. Target weight values in the range
382    // [400..500] are special, since they will first match up to 500, then down
383    // towards 0, then up again towards 999.
384    //
385    // Example: with target 600 and font weight 800, distance will be 200. With
386    // target 300 and font weight 600, distance will be 900, since heavier
387    // weights are farther away than lighter weights. If the target is 5 and the
388    // font weight 995, the distance would be 1590 for the same reason.
389
390    fn match_distance(&self, range: &ComputedFontWeightRange) -> f32 {
391        // weight distance ==> [0,1600]
392        const NOT_WITHIN_CENTRAL_RANGE: f32 = 100.0;
393        const REVERSE_DISTANCE: f32 = 600.0;
394
395        let min_weight = range.0;
396        let max_weight = range.1;
397
398        if *self >= min_weight && *self <= max_weight {
399            // Target is within the face's range, so it's a perfect match
400            return 0.0;
401        }
402
403        if *self < FontWeight::NORMAL {
404            // Requested a lighter-than-400 weight
405            if max_weight < *self {
406                return self.to_float() - max_weight.to_float();
407            }
408
409            // Add reverse-search penalty for bolder faces
410            return (min_weight.to_float() - self.to_float()) + REVERSE_DISTANCE;
411        }
412
413        if *self > FontWeight::from_float(500.) {
414            // Requested a bolder-than-500 weight
415            if min_weight > *self {
416                return min_weight.to_float() - self.to_float();
417            }
418            // Add reverse-search penalty for lighter faces
419            return (self.to_float() - max_weight.to_float()) + REVERSE_DISTANCE;
420        }
421
422        // Special case for requested weight in the [400..500] range
423        if min_weight > *self {
424            if min_weight <= FontWeight::from_float(500.) {
425                // Bolder weight up to 500 is first choice
426                return min_weight.to_float() - self.to_float();
427            }
428            // Other bolder weights get a reverse-search penalty
429            return (min_weight.to_float() - self.to_float()) + REVERSE_DISTANCE;
430        }
431        // Lighter weights are not as good as bolder ones within [400..500]
432        (self.to_float() - max_weight.to_float()) + NOT_WITHIN_CENTRAL_RANGE
433    }
434
435    fn to_float(&self) -> f32 {
436        self.value()
437    }
438}
439
440impl FontMatchDistanceMethod<(Self, Self)> for FontStyle {
441    fn match_distance(&self, range: &(Self, Self)) -> f32 {
442        // style distance ==> [0,500]
443        let min_style = range.0;
444        if *self == min_style {
445            return 0.0; // styles match exactly ==> 0
446        }
447
448        // bias added to angle difference when searching in the non-preferred
449        // direction from a target angle
450        const REVERSE: f32 = 100.0;
451
452        // bias added when we've crossed from positive to negative angles or
453        // vice versa
454        const NEGATE: f32 = 200.0;
455
456        if *self == FontStyle::NORMAL {
457            if min_style.is_oblique() {
458                // to distinguish oblique 0deg from normal, we add 1.0 to the angle
459                let min_angle = min_style.oblique_degrees();
460                if min_angle >= 0.0 {
461                    return 1.0 + min_angle;
462                }
463                let max_style = range.1;
464                let max_angle = max_style.oblique_degrees();
465                if max_angle >= 0.0 {
466                    // [min,max] range includes 0.0, so just return our minimum
467                    return 1.0;
468                }
469                // negative oblique is even worse than italic
470                return NEGATE - max_angle;
471            }
472            // must be italic, which is worse than any non-negative oblique;
473            // treat as a match in the wrong search direction
474            assert!(min_style == FontStyle::ITALIC);
475            return REVERSE;
476        }
477
478        let default_oblique_angle = FontStyle::OBLIQUE.oblique_degrees();
479        if *self == FontStyle::ITALIC {
480            if min_style.is_oblique() {
481                let min_angle = min_style.oblique_degrees();
482                if min_angle >= default_oblique_angle {
483                    return 1.0 + (min_angle - default_oblique_angle);
484                }
485                let max_style = range.1;
486                let max_angle = max_style.oblique_degrees();
487                if max_angle >= default_oblique_angle {
488                    return 1.0;
489                }
490                if max_angle > 0.0 {
491                    // wrong direction but still > 0, add bias of 100
492                    return REVERSE + (default_oblique_angle - max_angle);
493                }
494                // negative oblique angle, add bias of 300
495                return REVERSE + NEGATE + (default_oblique_angle - max_angle);
496            }
497            // normal is worse than oblique > 0, but better than oblique <= 0
498            assert!(min_style == FontStyle::NORMAL);
499            return NEGATE;
500        }
501
502        // target is oblique <angle>: four different cases depending on
503        // the value of the <angle>, which determines the preferred direction
504        // of search
505        let target_angle = self.oblique_degrees();
506        if target_angle >= default_oblique_angle {
507            if min_style.is_oblique() {
508                let min_angle = min_style.oblique_degrees();
509                if min_angle >= target_angle {
510                    return min_angle - target_angle;
511                }
512                let max_style = range.1;
513                let max_angle = max_style.oblique_degrees();
514                if max_angle >= target_angle {
515                    return 0.0;
516                }
517                if max_angle > 0.0 {
518                    return REVERSE + (target_angle - max_angle);
519                }
520                return REVERSE + NEGATE + (target_angle - max_angle);
521            }
522            if min_style == FontStyle::ITALIC {
523                return REVERSE + NEGATE;
524            }
525            return REVERSE + NEGATE + 1.0;
526        }
527
528        if target_angle <= -default_oblique_angle {
529            if min_style.is_oblique() {
530                let max_style = range.1;
531                let max_angle = max_style.oblique_degrees();
532                if max_angle <= target_angle {
533                    return target_angle - max_angle;
534                }
535                let min_angle = min_style.oblique_degrees();
536                if min_angle <= target_angle {
537                    return 0.0;
538                }
539                if min_angle < 0.0 {
540                    return REVERSE + (min_angle - target_angle);
541                }
542                return REVERSE + NEGATE + (min_angle - target_angle);
543            }
544            if min_style == FontStyle::ITALIC {
545                return REVERSE + NEGATE;
546            }
547            return REVERSE + NEGATE + 1.0;
548        }
549
550        if target_angle >= 0.0 {
551            if min_style.is_oblique() {
552                let min_angle = min_style.oblique_degrees();
553                if min_angle > target_angle {
554                    return REVERSE + (min_angle - target_angle);
555                }
556                let max_style = range.1;
557                let max_angle = max_style.oblique_degrees();
558                if max_angle >= target_angle {
559                    return 0.0;
560                }
561                if max_angle > 0.0 {
562                    return target_angle - max_angle;
563                }
564                return REVERSE + NEGATE + (target_angle - max_angle);
565            }
566            if min_style == FontStyle::ITALIC {
567                return REVERSE + NEGATE - 2.0;
568            }
569            return REVERSE + NEGATE - 1.0;
570        }
571
572        // last case: (targetAngle < 0.0 && targetAngle > kDefaultAngle)
573        if min_style.is_oblique() {
574            let max_style = range.1;
575            let max_angle = max_style.oblique_degrees();
576            if max_angle < target_angle {
577                return REVERSE + (target_angle - max_angle);
578            }
579            let min_angle = min_style.oblique_degrees();
580            if min_angle <= target_angle {
581                return 0.0;
582            }
583            if min_angle < 0.0 {
584                return min_angle - target_angle;
585            }
586            return REVERSE + NEGATE + (min_angle - target_angle);
587        }
588        if min_style == FontStyle::ITALIC {
589            return REVERSE + NEGATE - 2.0;
590        }
591        REVERSE + NEGATE - 1.0
592    }
593
594    fn to_float(&self) -> f32 {
595        unimplemented!("Don't know how to convert FontStyle to float.");
596    }
597}
598
599pub trait IsOblique {
600    fn is_oblique(&self) -> bool;
601}
602
603impl IsOblique for FontStyle {
604    fn is_oblique(&self) -> bool {
605        *self != FontStyle::NORMAL && *self != FontStyle::ITALIC
606    }
607}