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