fonts: Add support for more @font-face features (#32164)

There are a couple major changes here:

1. Support is added for the `weight`, `style`, `stretch` and
   `unicode-range` declarations in `@font-face`.
2. Font matching in the font cache can return templates and
   `FontGroupFamily` can own mulitple templates. This is due to needing
   support for "composite fonts". These are `@font-face` declarations
   that only differ in their `unicode-range` definition.

This fixes a lot of non-determinism in font selection especially when
dealing with pages that define "composite faces." A notable example of
such a page is servo.org, which now consistently displays the correct
web font.

One test starts to fail due to an uncovered bug, but this will be fixed
in a followup change.

Fixes #20686.
Fixes #20684.

Co-authored-by: Mukilan Thiyagarajan <mukilan@igalia.com>
This commit is contained in:
Martin Robinson 2024-04-29 19:02:07 +02:00 committed by GitHub
parent 628e33bfa9
commit 4732da3477
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
56 changed files with 613 additions and 593 deletions

View file

@ -4,6 +4,7 @@
use std::cell::RefCell;
use std::fmt::{Debug, Error, Formatter};
use std::ops::RangeInclusive;
use std::rc::Rc;
use std::sync::Arc;
@ -11,33 +12,33 @@ use serde::{Deserialize, Serialize};
use servo_url::ServoUrl;
use style::computed_values::font_stretch::T as FontStretch;
use style::computed_values::font_style::T as FontStyle;
use style::properties::style_structs::Font as FontStyleStruct;
use style::values::computed::font::FontWeight;
use crate::font_cache_thread::FontIdentifier;
use crate::font::{FontDescriptor, PlatformFontMethods};
use crate::font_cache_thread::{
CSSFontFaceDescriptors, ComputedFontStyleDescriptor, FontIdentifier,
};
use crate::platform::font::PlatformFont;
use crate::platform::font_list::LocalFontIdentifier;
/// A reference to a [`FontTemplate`] with shared ownership and mutability.
pub(crate) type FontTemplateRef = Rc<RefCell<FontTemplate>>;
pub type FontTemplateRef = Rc<RefCell<FontTemplate>>;
/// Describes how to select a font from a given family. This is very basic at the moment and needs
/// to be expanded or refactored when we support more of the font styling parameters.
///
/// NB: If you change this, you will need to update `style::properties::compute_font_hash()`.
#[derive(Clone, Copy, Debug, Deserialize, Hash, PartialEq, Serialize)]
#[derive(Clone, Debug, Deserialize, Hash, PartialEq, Serialize)]
pub struct FontTemplateDescriptor {
pub weight: FontWeight,
pub stretch: FontStretch,
pub style: FontStyle,
pub weight: (FontWeight, FontWeight),
pub stretch: (FontStretch, FontStretch),
pub style: (FontStyle, FontStyle),
pub unicode_range: Option<Vec<RangeInclusive<u32>>>,
}
impl Default for FontTemplateDescriptor {
fn default() -> Self {
FontTemplateDescriptor {
weight: FontWeight::normal(),
stretch: FontStretch::NORMAL,
style: FontStyle::NORMAL,
}
Self::new(FontWeight::normal(), FontStretch::NORMAL, FontStyle::NORMAL)
}
}
@ -57,9 +58,10 @@ impl FontTemplateDescriptor {
#[inline]
pub fn new(weight: FontWeight, stretch: FontStretch, style: FontStyle) -> Self {
Self {
weight,
stretch,
style,
weight: (weight, weight),
stretch: (stretch, stretch),
style: (style, style),
unicode_range: None,
}
}
@ -71,24 +73,51 @@ impl FontTemplateDescriptor {
///
/// The policy is to care most about differences in italicness, then weight, then stretch
#[inline]
fn distance_from(&self, other: &FontTemplateDescriptor) -> f32 {
fn distance_from(&self, other: &FontDescriptor) -> f32 {
let weight = self.weight.0;
let style = self.style.0;
let stretch = self.stretch.0;
// 0 <= style_part <= 180, since font-style obliqueness should be
// between -90 and +90deg.
let style_part = (style_to_number(&self.style) - style_to_number(&other.style)).abs();
let style_part = (style_to_number(&style) - style_to_number(&other.style)).abs();
// 0 <= weightPart <= 800
let weight_part = (self.weight.value() - other.weight.value()).abs();
let weight_part = (weight.value() - other.weight.value()).abs();
// 0 <= stretchPart <= 8
let stretch_part = (self.stretch.to_percentage().0 - other.stretch.to_percentage().0).abs();
let stretch_part = (stretch.to_percentage().0 - other.stretch.to_percentage().0).abs();
style_part + weight_part + stretch_part
}
}
impl<'a> From<&'a FontStyleStruct> for FontTemplateDescriptor {
fn from(style: &'a FontStyleStruct) -> Self {
FontTemplateDescriptor {
weight: style.font_weight,
stretch: style.font_stretch,
style: style.font_style,
fn matches(&self, descriptor_to_match: &FontDescriptor) -> bool {
self.weight.0 <= descriptor_to_match.weight &&
self.weight.1 >= descriptor_to_match.weight &&
self.style.0 <= descriptor_to_match.style &&
self.style.1 >= descriptor_to_match.style &&
self.stretch.0 <= descriptor_to_match.stretch &&
self.stretch.1 >= descriptor_to_match.stretch
}
fn override_values_with_css_font_template_descriptors(
&mut self,
css_font_template_descriptors: CSSFontFaceDescriptors,
) {
if let Some(weight) = css_font_template_descriptors.weight {
self.weight = weight;
}
self.style = match css_font_template_descriptors.style {
Some(ComputedFontStyleDescriptor::Italic) => (FontStyle::ITALIC, FontStyle::ITALIC),
Some(ComputedFontStyleDescriptor::Normal) => (FontStyle::NORMAL, FontStyle::NORMAL),
Some(ComputedFontStyleDescriptor::Oblique(angle_1, angle_2)) => (
FontStyle::oblique(angle_1.to_float()),
FontStyle::oblique(angle_2.to_float()),
),
None => self.style,
};
if let Some(stretch) = css_font_template_descriptors.stretch {
self.stretch = stretch;
}
if let Some(unicode_range) = css_font_template_descriptors.unicode_range {
self.unicode_range = Some(unicode_range);
}
}
}
@ -129,14 +158,22 @@ impl FontTemplate {
pub fn new_web_font(
url: ServoUrl,
descriptor: FontTemplateDescriptor,
data: Arc<Vec<u8>>,
) -> FontTemplate {
FontTemplate {
css_font_template_descriptors: CSSFontFaceDescriptors,
) -> Result<FontTemplate, &'static str> {
let identifier = FontIdentifier::Web(url.clone());
let Ok(handle) = PlatformFont::new_from_data(identifier, data.clone(), 0, None) else {
return Err("Could not initialize platform font data for: {url:?}");
};
let mut descriptor = handle.descriptor();
descriptor
.override_values_with_css_font_template_descriptors(css_font_template_descriptors);
Ok(FontTemplate {
identifier: FontIdentifier::Web(url),
descriptor,
data: Some(data),
}
})
}
pub fn identifier(&self) -> &FontIdentifier {
@ -155,26 +192,35 @@ pub trait FontTemplateRefMethods {
/// operation (depending on the platform) which performs synchronous disk I/O
/// and should never be done lightly.
fn data(&self) -> Arc<Vec<u8>>;
/// Get the descriptor. Returns `None` when instantiating the data fails.
/// Get the descriptor.
fn descriptor(&self) -> FontTemplateDescriptor;
/// Get the [`FontIdentifier`] for this template.
fn identifier(&self) -> FontIdentifier;
/// Returns true if the given descriptor matches the one in this [`FontTemplate`].
fn descriptor_matches(&self, requested_desc: &FontTemplateDescriptor) -> bool;
fn matches_font_descriptor(&self, descriptor_to_match: &FontDescriptor) -> bool;
/// Calculate the distance from this [`FontTemplate`]s descriptor and return it
/// or None if this is not a valid [`FontTemplate`].
fn descriptor_distance(&self, requested_descriptor: &FontTemplateDescriptor) -> f32;
fn descriptor_distance(&self, descriptor_to_match: &FontDescriptor) -> f32;
/// Whether or not this character is in the unicode ranges specified in
/// this temlates `@font-face` definition, if any.
fn char_in_unicode_range(&self, character: char) -> bool;
}
impl FontTemplateRefMethods for FontTemplateRef {
fn descriptor(&self) -> FontTemplateDescriptor {
self.borrow().descriptor
self.borrow().descriptor.clone()
}
fn descriptor_matches(&self, requested_descriptor: &FontTemplateDescriptor) -> bool {
self.descriptor() == *requested_descriptor
fn identifier(&self) -> FontIdentifier {
self.borrow().identifier.clone()
}
fn descriptor_distance(&self, requested_descriptor: &FontTemplateDescriptor) -> f32 {
self.descriptor().distance_from(requested_descriptor)
fn matches_font_descriptor(&self, descriptor_to_match: &FontDescriptor) -> bool {
self.descriptor().matches(descriptor_to_match)
}
fn descriptor_distance(&self, descriptor_to_match: &FontDescriptor) -> f32 {
self.descriptor().distance_from(descriptor_to_match)
}
fn data(&self) -> Arc<Vec<u8>> {
@ -190,4 +236,15 @@ impl FontTemplateRefMethods for FontTemplateRef {
})
.clone()
}
fn char_in_unicode_range(&self, character: char) -> bool {
let character = character as u32;
self.borrow()
.descriptor
.unicode_range
.as_ref()
.map_or(true, |ranges| {
ranges.iter().any(|range| range.contains(&character))
})
}
}