fonts: Implement CSS font-variation-settings property for FreeType platforms (#38642)

This change adds support for variable fonts via the
[`font-variation-settings`](https://developer.mozilla.org/en-US/docs/Web/CSS/font-variation-settings)
property.

There are three areas where we need to set the variation values:
* Webrender (`compositor.rs`), for drawing the glyphs
* Harfbuzz (`shaper.rs`), for most shaping tasks
* PlatformFont (`fonts/platform/`), for horizontal advances and kerning

For now, freetype is the only platform shaper that supports variable
fonts. I can't easily test the fonts with non-freetype shapers. Thats
why variable fonts are behind the `layout_variable_fonts_enabled` pref,
which is disabled by default.

<img width="1250" height="710" alt="image"
src="https://github.com/user-attachments/assets/1aee1407-f3a2-42f6-a106-af0443fcd588"
/>

<details><summary>HTML test file</summary>

```html
<style>
@font-face {
  font-family: "Amstelvar VF";
  src: url("https://mdn.github.io/shared-assets/fonts/variable-fonts/AmstelvarAlpha-VF.woff2")
    format("woff2-variations");
  font-weight: 300 900;
  font-stretch: 35% 100%;
  font-style: normal;
  font-display: swap;
}

p {
  font:
    1.2em "Amstelvar VF",
    Georgia,
    serif;
  font-size: 4rem;
  margin: 1rem;
  display: inline-block;
}

.p1 {
  font-variation-settings: "wght" 300;
}

.p2 {
  font-variation-settings: "wght" 625;
}

.p3 {
  font-variation-settings: "wght" 900;
}

</style>
<div>
  <p class="p1">Weight</p>
  <span>(font-variation-settings: "wght" 300)</span>
</div>
<div>
  <p class="p2">Weight</p>
  <span>(font-variation-settings: "wght" 625)</span>
</div>
<div>
  <p class="p3">Weight</p>
  <span>(font-variation-settings: "wght" 900)</span>
</div>
</div>
```
</details>



https://github.com/user-attachments/assets/9e21101a-796a-49fe-b82c-8999d8fa9ee1


Testing: Needs decision on whether we want to enable the pref in CI
Works towards https://github.com/servo/servo/issues/37236

Depends on https://github.com/servo/stylo/pull/230

---------

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
This commit is contained in:
Simon Wülker 2025-08-18 18:30:14 +02:00 committed by GitHub
parent ce16fbce75
commit 7471ad7730
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 274 additions and 79 deletions

View file

@ -32,7 +32,7 @@ use style::shared_lock::SharedRwLockReadGuard;
use style::stylesheets::{CssRule, DocumentStyleSheet, FontFaceRule, StylesheetInDocument};
use style::values::computed::font::{FamilyName, FontFamilyNameSyntax, SingleFontFamily};
use url::Url;
use webrender_api::{FontInstanceFlags, FontInstanceKey, FontKey};
use webrender_api::{FontInstanceFlags, FontInstanceKey, FontKey, FontVariation};
use crate::font::{
Font, FontDescriptor, FontFamilyDescriptor, FontGroup, FontRef, FontSearchScope,
@ -45,6 +45,8 @@ use crate::{FontData, LowercaseFontFamilyName, PlatformFontMethods, SystemFontSe
static SMALL_CAPS_SCALE_FACTOR: f32 = 0.8; // Matches FireFox (see gfxFont.h)
pub type FontParameters = (FontKey, Au, Vec<FontVariation>);
#[derive(MallocSizeOf)]
struct FontGroupRef(#[conditional_malloc_size_of] Arc<RwLock<FontGroup>>);
@ -87,7 +89,7 @@ pub struct FontContext {
/// A collection of WebRender [`FontInstanceKey`]s generated for the web fonts that
/// this [`FontContext`] controls.
webrender_font_instance_keys: RwLock<HashMap<(FontKey, Au), FontInstanceKey>>,
webrender_font_instance_keys: RwLock<HashMap<FontParameters, FontInstanceKey>>,
/// The data for each web font [`FontIdentifier`]. This data might be used by more than one
/// [`FontTemplate`] as each identifier refers to a URL.
@ -270,7 +272,7 @@ impl FontContext {
) -> Result<FontRef, &'static str> {
Ok(FontRef(Arc::new(Font::new(
font_template.clone(),
font_descriptor.clone(),
font_descriptor,
self.get_font_data(&font_template.identifier()),
synthesized_small_caps,
)?)))
@ -282,11 +284,13 @@ impl FontContext {
font.template.identifier(),
font.descriptor.pt_size,
font.webrender_font_instance_flags(),
font.variations().to_owned(),
),
FontIdentifier::Web(_) => self.create_web_font_instance(
font.template.clone(),
font.descriptor.pt_size,
font.webrender_font_instance_flags(),
font.variations().to_owned(),
),
}
}
@ -296,6 +300,7 @@ impl FontContext {
font_template: FontTemplateRef,
pt_size: Au,
flags: FontInstanceFlags,
variations: Vec<FontVariation>,
) -> FontInstanceKey {
let identifier = font_template.identifier().clone();
let font_data = self
@ -318,7 +323,7 @@ impl FontContext {
let key = *self
.webrender_font_instance_keys
.write()
.entry((font_key, pt_size))
.entry((font_key, pt_size, variations.clone()))
.or_insert_with(|| {
let font_instance_key = self.system_font_service_proxy.generate_font_instance_key();
self.compositor_api.lock().add_font_instance(
@ -326,6 +331,7 @@ impl FontContext {
font_key,
pt_size.to_f32_px(),
flags,
variations,
);
font_instance_key
});
@ -612,7 +618,7 @@ impl FontContextWebFontMethods for Arc<FontContext> {
});
let mut removed_instance_keys: HashSet<FontInstanceKey> = HashSet::new();
webrender_font_instance_keys.retain(|(font_key, _), instance_key| {
webrender_font_instance_keys.retain(|(font_key, _, _), instance_key| {
if removed_keys.contains(font_key) {
removed_instance_keys.insert(*instance_key);
false
@ -857,11 +863,11 @@ impl RemoteWebFontDownloader {
let url: ServoUrl = self.url.clone().into();
let identifier = FontIdentifier::Web(url.clone());
let Ok(handle) = PlatformFont::new_from_data(identifier, &font_data, None) else {
let Ok(handle) = PlatformFont::new_from_data(identifier, &font_data, None, &[]) else {
return false;
};
let state = self.take_state();
let mut descriptor = handle.descriptor();
descriptor
.override_values_with_css_font_template_descriptors(&state.css_font_face_descriptors);