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