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

40
Cargo.lock generated
View file

@ -2382,7 +2382,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
dependencies = [
"libc",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@ -3042,7 +3042,7 @@ dependencies = [
"gobject-sys",
"libc",
"system-deps",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@ -4535,7 +4535,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@ -4839,7 +4839,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
dependencies = [
"cfg-if",
"windows-targets 0.52.6",
"windows-targets 0.48.5",
]
[[package]]
@ -7004,7 +7004,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.4.15",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@ -7017,7 +7017,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys 0.9.4",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@ -7354,7 +7354,7 @@ dependencies = [
[[package]]
name = "selectors"
version = "0.31.0"
source = "git+https://github.com/servo/stylo?branch=2025-08-01#d2519c05c9d7db31c91552d3337578270645d797"
source = "git+https://github.com/servo/stylo?branch=2025-08-01#8a6763eb2a66d3c93860313fba37fc3f09c7f70f"
dependencies = [
"bitflags 2.9.1",
"cssparser",
@ -7660,7 +7660,7 @@ dependencies = [
[[package]]
name = "servo_arc"
version = "0.4.1"
source = "git+https://github.com/servo/stylo?branch=2025-08-01#d2519c05c9d7db31c91552d3337578270645d797"
source = "git+https://github.com/servo/stylo?branch=2025-08-01#8a6763eb2a66d3c93860313fba37fc3f09c7f70f"
dependencies = [
"serde",
"stable_deref_trait",
@ -8123,7 +8123,7 @@ dependencies = [
[[package]]
name = "stylo"
version = "0.6.0"
source = "git+https://github.com/servo/stylo?branch=2025-08-01#d2519c05c9d7db31c91552d3337578270645d797"
source = "git+https://github.com/servo/stylo?branch=2025-08-01#8a6763eb2a66d3c93860313fba37fc3f09c7f70f"
dependencies = [
"app_units",
"arrayvec",
@ -8180,7 +8180,7 @@ dependencies = [
[[package]]
name = "stylo_atoms"
version = "0.6.0"
source = "git+https://github.com/servo/stylo?branch=2025-08-01#d2519c05c9d7db31c91552d3337578270645d797"
source = "git+https://github.com/servo/stylo?branch=2025-08-01#8a6763eb2a66d3c93860313fba37fc3f09c7f70f"
dependencies = [
"string_cache",
"string_cache_codegen",
@ -8189,12 +8189,12 @@ dependencies = [
[[package]]
name = "stylo_config"
version = "0.6.0"
source = "git+https://github.com/servo/stylo?branch=2025-08-01#d2519c05c9d7db31c91552d3337578270645d797"
source = "git+https://github.com/servo/stylo?branch=2025-08-01#8a6763eb2a66d3c93860313fba37fc3f09c7f70f"
[[package]]
name = "stylo_derive"
version = "0.6.0"
source = "git+https://github.com/servo/stylo?branch=2025-08-01#d2519c05c9d7db31c91552d3337578270645d797"
source = "git+https://github.com/servo/stylo?branch=2025-08-01#8a6763eb2a66d3c93860313fba37fc3f09c7f70f"
dependencies = [
"darling",
"proc-macro2",
@ -8206,7 +8206,7 @@ dependencies = [
[[package]]
name = "stylo_dom"
version = "0.6.0"
source = "git+https://github.com/servo/stylo?branch=2025-08-01#d2519c05c9d7db31c91552d3337578270645d797"
source = "git+https://github.com/servo/stylo?branch=2025-08-01#8a6763eb2a66d3c93860313fba37fc3f09c7f70f"
dependencies = [
"bitflags 2.9.1",
"stylo_malloc_size_of",
@ -8215,7 +8215,7 @@ dependencies = [
[[package]]
name = "stylo_malloc_size_of"
version = "0.6.0"
source = "git+https://github.com/servo/stylo?branch=2025-08-01#d2519c05c9d7db31c91552d3337578270645d797"
source = "git+https://github.com/servo/stylo?branch=2025-08-01#8a6763eb2a66d3c93860313fba37fc3f09c7f70f"
dependencies = [
"app_units",
"cssparser",
@ -8232,12 +8232,12 @@ dependencies = [
[[package]]
name = "stylo_static_prefs"
version = "0.6.0"
source = "git+https://github.com/servo/stylo?branch=2025-08-01#d2519c05c9d7db31c91552d3337578270645d797"
source = "git+https://github.com/servo/stylo?branch=2025-08-01#8a6763eb2a66d3c93860313fba37fc3f09c7f70f"
[[package]]
name = "stylo_traits"
version = "0.6.0"
source = "git+https://github.com/servo/stylo?branch=2025-08-01#d2519c05c9d7db31c91552d3337578270645d797"
source = "git+https://github.com/servo/stylo?branch=2025-08-01#8a6763eb2a66d3c93860313fba37fc3f09c7f70f"
dependencies = [
"app_units",
"bitflags 2.9.1",
@ -8411,7 +8411,7 @@ dependencies = [
"getrandom 0.3.3",
"once_cell",
"rustix 1.0.8",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
@ -8643,7 +8643,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "to_shmem"
version = "0.2.0"
source = "git+https://github.com/servo/stylo?branch=2025-08-01#d2519c05c9d7db31c91552d3337578270645d797"
source = "git+https://github.com/servo/stylo?branch=2025-08-01#8a6763eb2a66d3c93860313fba37fc3f09c7f70f"
dependencies = [
"cssparser",
"servo_arc",
@ -8656,7 +8656,7 @@ dependencies = [
[[package]]
name = "to_shmem_derive"
version = "0.1.0"
source = "git+https://github.com/servo/stylo?branch=2025-08-01#d2519c05c9d7db31c91552d3337578270645d797"
source = "git+https://github.com/servo/stylo?branch=2025-08-01#8a6763eb2a66d3c93860313fba37fc3f09c7f70f"
dependencies = [
"darling",
"proc-macro2",
@ -9946,7 +9946,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.48.0",
]
[[package]]

View file

@ -43,9 +43,9 @@ use webrender_api::units::{
use webrender_api::{
self, BuiltDisplayList, DirtyRect, DisplayListPayload, DocumentId, Epoch as WebRenderEpoch,
ExternalScrollId, FontInstanceFlags, FontInstanceKey, FontInstanceOptions, FontKey,
HitTestFlags, PipelineId as WebRenderPipelineId, PropertyBinding, ReferenceFrameKind,
RenderReasons, SampledScrollOffset, ScrollLocation, SpaceAndClipInfo, SpatialId,
SpatialTreeItemKey, TransformStyle,
FontVariation, HitTestFlags, PipelineId as WebRenderPipelineId, PropertyBinding,
ReferenceFrameKind, RenderReasons, SampledScrollOffset, ScrollLocation, SpaceAndClipInfo,
SpatialId, SpatialTreeItemKey, TransformStyle,
};
use crate::InitialCompositorState;
@ -713,8 +713,14 @@ impl IOCompositor {
self.global.borrow_mut().send_transaction(transaction);
},
CompositorMsg::AddFontInstance(font_instance_key, font_key, size, flags) => {
self.add_font_instance(font_instance_key, font_key, size, flags);
CompositorMsg::AddFontInstance(
font_instance_key,
font_key,
size,
flags,
variations,
) => {
self.add_font_instance(font_instance_key, font_key, size, flags, variations);
},
CompositorMsg::RemoveFonts(keys, instance_keys) => {
@ -1506,7 +1512,14 @@ impl IOCompositor {
font_key: FontKey,
size: f32,
flags: FontInstanceFlags,
variations: Vec<FontVariation>,
) {
let variations = if pref!(layout_variable_fonts_enabled) {
variations
} else {
vec![]
};
let mut transaction = Transaction::new();
let font_instance_options = FontInstanceOptions {
@ -1519,7 +1532,7 @@ impl IOCompositor {
size,
Some(font_instance_options),
None,
Vec::new(),
variations,
);
self.global.borrow_mut().send_transaction(transaction);

View file

@ -48,6 +48,10 @@ pub fn set(preferences: Preferences) {
"layout.container-queries.enabled",
preferences.layout_container_queries_enabled,
);
stylo_config::set_bool(
"layout.variable_fonts.enabled",
preferences.layout_variable_fonts_enabled,
);
let changed = preferences.diff(&PREFERENCES.read().unwrap());
@ -229,6 +233,7 @@ pub struct Preferences {
pub layout_flexbox_enabled: bool,
pub layout_threads: i64,
pub layout_unimplemented: bool,
pub layout_variable_fonts_enabled: bool,
pub layout_writing_mode_enabled: bool,
/// Enable hardware acceleration for video playback.
pub media_glvideo_enabled: bool,
@ -407,6 +412,7 @@ impl Preferences {
// TODO(mrobinson): This should likely be based on the number of processors.
layout_threads: 3,
layout_unimplemented: false,
layout_variable_fonts_enabled: false,
layout_writing_mode_enabled: false,
media_glvideo_enabled: false,
media_testing_enabled: false,

View file

@ -4,6 +4,7 @@
use std::borrow::ToOwned;
use std::collections::HashMap;
use std::hash::Hash;
use std::ops::Deref;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, OnceLock};
@ -26,7 +27,7 @@ use style::values::computed::font::{
};
use style::values::computed::{FontStretch, FontStyle, FontWeight};
use unicode_script::Script;
use webrender_api::{FontInstanceFlags, FontInstanceKey};
use webrender_api::{FontInstanceFlags, FontInstanceKey, FontVariation};
use crate::platform::font::{FontTable, PlatformFont};
pub use crate::platform::font_list::fallback_font_families;
@ -43,13 +44,14 @@ macro_rules! ot_tag {
};
}
pub const GPOS: u32 = ot_tag!('G', 'P', 'O', 'S');
pub const GSUB: u32 = ot_tag!('G', 'S', 'U', 'B');
pub const KERN: u32 = ot_tag!('k', 'e', 'r', 'n');
pub const SBIX: u32 = ot_tag!('s', 'b', 'i', 'x');
pub const CBDT: u32 = ot_tag!('C', 'B', 'D', 'T');
pub const COLR: u32 = ot_tag!('C', 'O', 'L', 'R');
pub const BASE: u32 = ot_tag!('B', 'A', 'S', 'E');
pub type OpenTypeTableTag = u32;
pub const GPOS: OpenTypeTableTag = ot_tag!('G', 'P', 'O', 'S');
pub const GSUB: OpenTypeTableTag = ot_tag!('G', 'S', 'U', 'B');
pub const KERN: OpenTypeTableTag = ot_tag!('k', 'e', 'r', 'n');
pub const SBIX: OpenTypeTableTag = ot_tag!('s', 'b', 'i', 'x');
pub const CBDT: OpenTypeTableTag = ot_tag!('C', 'B', 'D', 'T');
pub const COLR: OpenTypeTableTag = ot_tag!('C', 'O', 'L', 'R');
pub const BASE: OpenTypeTableTag = ot_tag!('B', 'A', 'S', 'E');
pub const LAST_RESORT_GLYPH_ADVANCE: FractionalPixel = 10.0;
@ -66,6 +68,7 @@ pub trait PlatformFontMethods: Sized {
fn new_from_template(
template: FontTemplateRef,
pt_size: Option<Au>,
variations: &[FontVariation],
data: &Option<FontData>,
) -> Result<PlatformFont, &'static str> {
let template = template.borrow();
@ -73,13 +76,14 @@ pub trait PlatformFontMethods: Sized {
match font_identifier {
FontIdentifier::Local(font_identifier) => {
Self::new_from_local_font_identifier(font_identifier, pt_size)
Self::new_from_local_font_identifier(font_identifier, pt_size, variations)
},
FontIdentifier::Web(_) => Self::new_from_data(
font_identifier,
data.as_ref()
.expect("Should never create a web font without data."),
pt_size,
variations,
),
}
}
@ -87,12 +91,14 @@ pub trait PlatformFontMethods: Sized {
fn new_from_local_font_identifier(
font_identifier: LocalFontIdentifier,
pt_size: Option<Au>,
variations: &[FontVariation],
) -> Result<PlatformFont, &'static str>;
fn new_from_data(
font_identifier: FontIdentifier,
data: &FontData,
pt_size: Option<Au>,
variations: &[FontVariation],
) -> Result<PlatformFont, &'static str>;
/// Get a [`FontTemplateDescriptor`] from a [`PlatformFont`]. This is used to get
@ -109,6 +115,9 @@ pub trait PlatformFontMethods: Sized {
/// Get the necessary [`FontInstanceFlags`]` for this font.
fn webrender_font_instance_flags(&self) -> FontInstanceFlags;
/// Return all the variation values that the font was instantiated with.
fn variations(&self) -> &[FontVariation];
}
// Used to abstract over the shaper's choice of fixed int representation.
@ -192,18 +201,29 @@ pub struct FontDescriptor {
pub style: FontStyle,
pub variant: font_variant_caps::T,
pub pt_size: Au,
pub variation_settings: Vec<FontVariation>,
}
impl Eq for FontDescriptor {}
impl<'a> From<&'a FontStyleStruct> for FontDescriptor {
fn from(style: &'a FontStyleStruct) -> Self {
let variation_settings = style
.clone_font_variation_settings()
.0
.into_iter()
.map(|setting| FontVariation {
tag: setting.tag.0,
value: setting.value,
})
.collect();
FontDescriptor {
weight: style.font_weight,
stretch: style.font_stretch,
style: style.font_style,
variant: style.font_variant_caps,
pt_size: Au::from_f32_px(style.font_size.computed_size().px()),
variation_settings,
}
}
}
@ -280,8 +300,12 @@ impl Font {
data: Option<FontData>,
synthesized_small_caps: Option<FontRef>,
) -> Result<Font, &'static str> {
let handle =
PlatformFont::new_from_template(template.clone(), Some(descriptor.pt_size), &data)?;
let handle = PlatformFont::new_from_template(
template.clone(),
Some(descriptor.pt_size),
&descriptor.variation_settings,
&data,
)?;
let metrics = handle.metrics();
Ok(Font {
@ -336,6 +360,10 @@ impl Font {
)
})
}
pub fn variations(&self) -> &[FontVariation] {
self.handle.variations()
}
}
bitflags! {
@ -426,9 +454,8 @@ impl Font {
}
fn shape_text_harfbuzz(&self, text: &str, options: &ShapingOptions, glyphs: &mut GlyphStore) {
let this = self as *const Font;
self.shaper
.get_or_init(|| Shaper::new(this))
.get_or_init(|| Shaper::new(self))
.shape_text(text, options, glyphs);
}
@ -544,8 +571,7 @@ impl Font {
/// Get the [`FontBaseline`] for this font.
pub fn baseline(&self) -> Option<FontBaseline> {
let this = self as *const Font;
self.shaper.get_or_init(|| Shaper::new(this)).baseline()
self.shaper.get_or_init(|| Shaper::new(self)).baseline()
}
}

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);

View file

@ -315,7 +315,7 @@ impl<'a> DetailedGlyphStore {
// This struct is used by GlyphStore clients to provide new glyph data.
// It should be allocated on the stack and passed by reference to GlyphStore.
#[derive(Clone, Copy)]
#[derive(Clone, Copy, Debug)]
pub struct GlyphData {
id: GlyphId,
advance: Au,

View file

@ -23,7 +23,7 @@ use style::Zero;
use style::computed_values::font_stretch::T as FontStretch;
use style::computed_values::font_weight::T as FontWeight;
use style::values::computed::font::FontStyle;
use webrender_api::FontInstanceFlags;
use webrender_api::{FontInstanceFlags, FontVariation};
use super::LocalFontIdentifier;
use super::library_handle::FreeTypeLibraryHandle;
@ -63,6 +63,7 @@ pub struct PlatformFont {
face: ReentrantMutex<FreeTypeFace>,
requested_face_size: Au,
actual_face_size: Au,
variations: Vec<FontVariation>,
/// A member that allows using `skrifa` to read values from this font.
table_provider_data: FreeTypeFaceTableProviderData,
@ -73,11 +74,14 @@ impl PlatformFontMethods for PlatformFont {
_font_identifier: FontIdentifier,
font_data: &FontData,
requested_size: Option<Au>,
variations: &[FontVariation],
) -> Result<PlatformFont, &'static str> {
let library = FreeTypeLibraryHandle::get().lock();
let data: &[u8] = font_data.as_ref();
let face = FreeTypeFace::new_from_memory(&library, data)?;
let normalized_variations = face.set_variations_for_font(variations, &library)?;
let (requested_face_size, actual_face_size) = match requested_size {
Some(requested_size) => (requested_size, face.set_size(requested_size)?),
None => (Au::zero(), Au::zero()),
@ -88,18 +92,22 @@ impl PlatformFontMethods for PlatformFont {
requested_face_size,
actual_face_size,
table_provider_data: FreeTypeFaceTableProviderData::Web(font_data.clone()),
variations: normalized_variations,
})
}
fn new_from_local_font_identifier(
font_identifier: LocalFontIdentifier,
requested_size: Option<Au>,
variations: &[FontVariation],
) -> Result<PlatformFont, &'static str> {
let library = FreeTypeLibraryHandle::get().lock();
let filename = CString::new(&*font_identifier.path).expect("filename contains NUL byte!");
let face = FreeTypeFace::new_from_file(&library, &filename, font_identifier.index())?;
let normalized_variations = face.set_variations_for_font(variations, &library)?;
let (requested_face_size, actual_face_size) = match requested_size {
Some(requested_size) => (requested_size, face.set_size(requested_size)?),
None => (Au::zero(), Au::zero()),
@ -119,6 +127,7 @@ impl PlatformFontMethods for PlatformFont {
Arc::new(memory_mapped_font_data),
font_identifier.index(),
),
variations: normalized_variations,
})
}
@ -377,6 +386,10 @@ impl PlatformFontMethods for PlatformFont {
// loading bitmaps. There's no harm to always passing it.
FontInstanceFlags::EMBEDDED_BITMAPS
}
fn variations(&self) -> &[FontVariation] {
&self.variations
}
}
impl PlatformFont {

View file

@ -7,10 +7,13 @@ use std::ptr;
use app_units::Au;
use freetype_sys::{
FT_Done_Face, FT_F26Dot6, FT_FACE_FLAG_COLOR, FT_FACE_FLAG_FIXED_SIZES, FT_FACE_FLAG_SCALABLE,
FT_Face, FT_FaceRec, FT_Int32, FT_LOAD_COLOR, FT_LOAD_DEFAULT, FT_Long, FT_New_Face,
FT_New_Memory_Face, FT_Pos, FT_Select_Size, FT_Set_Char_Size, FT_UInt,
FT_Done_Face, FT_Done_MM_Var, FT_F26Dot6, FT_FACE_FLAG_COLOR, FT_FACE_FLAG_FIXED_SIZES,
FT_FACE_FLAG_SCALABLE, FT_Face, FT_FaceRec, FT_Fixed, FT_Get_MM_Var, FT_HAS_MULTIPLE_MASTERS,
FT_Int32, FT_LOAD_COLOR, FT_LOAD_DEFAULT, FT_Long, FT_MM_Var, FT_New_Face, FT_New_Memory_Face,
FT_Pos, FT_Select_Size, FT_Set_Char_Size, FT_Set_Var_Design_Coordinates, FT_UInt,
FTErrorMethods,
};
use webrender_api::FontVariation;
use crate::platform::freetype::library_handle::FreeTypeLibraryHandle;
@ -164,6 +167,74 @@ impl FreeTypeFace {
load_flags as FT_Int32
}
/// Applies to provided variations to the font face.
///
/// Returns the normalized font variations, which are clamped
/// to fit within the range of their respective axis. Variation
/// values for nonexistent axes are not included.
pub(crate) fn set_variations_for_font(
&self,
variations: &[FontVariation],
library: &FreeTypeLibraryHandle,
) -> Result<Vec<FontVariation>, &'static str> {
if !FT_HAS_MULTIPLE_MASTERS(self.as_ptr()) ||
variations.is_empty() ||
!servo_config::pref!(layout_variable_fonts_enabled)
{
// Nothing to do
return Ok(vec![]);
}
// Query variation axis of font
let mut mm_var: *mut FT_MM_Var = ptr::null_mut();
let result = unsafe { FT_Get_MM_Var(self.as_ptr(), &mut mm_var as *mut _) };
if !result.succeeded() {
return Err("Failed to query font variations");
}
// Prepare values for each axis. These are either the provided values (if any) or the default
// ones for the axis.
let num_axis = unsafe { (*mm_var).num_axis } as usize;
let mut normalized_axis_values = Vec::with_capacity(variations.len());
let mut coords = vec![0; num_axis];
for (index, coord) in coords.iter_mut().enumerate() {
let axis_data = unsafe { &*(*mm_var).axis.add(index) };
let Some(variation) = variations
.iter()
.find(|variation| variation.tag == axis_data.tag as u32)
else {
*coord = axis_data.def;
continue;
};
// Freetype uses a 16.16 fixed point format for variation values
let shift_factor = 16.0_f32.exp2();
let min_value = axis_data.minimum as f32 / shift_factor;
let max_value = axis_data.maximum as f32 / shift_factor;
normalized_axis_values.push(FontVariation {
tag: variation.tag,
value: variation.value.min(max_value).max(min_value),
});
*coord = (variation.value * shift_factor) as FT_Fixed;
}
// Free the MM_Var structure
unsafe {
FT_Done_MM_Var(library.freetype_library, mm_var);
}
// Set the values for each variation axis
let result = unsafe {
FT_Set_Var_Design_Coordinates(self.as_ptr(), coords.len() as u32, coords.as_ptr())
};
if !result.succeeded() {
return Err("Could not set variations for font face");
}
Ok(normalized_axis_values)
}
}
/// FT_Face can be used in multiple threads, but from only one thread at a time.

View file

@ -19,7 +19,7 @@ use core_text::font_descriptor::{
use euclid::default::{Point2D, Rect, Size2D};
use log::debug;
use style::values::computed::font::{FontStretch, FontStyle, FontWeight};
use webrender_api::FontInstanceFlags;
use webrender_api::{FontInstanceFlags, FontVariation};
use super::core_text_font_cache::CoreTextFontCache;
use super::font_list::LocalFontIdentifier;
@ -201,6 +201,7 @@ impl PlatformFontMethods for PlatformFont {
font_identifier: FontIdentifier,
data: &FontData,
requested_size: Option<Au>,
_variations: &[FontVariation],
) -> Result<PlatformFont, &'static str> {
Self::new(font_identifier, Some(data), requested_size)
}
@ -208,6 +209,7 @@ impl PlatformFontMethods for PlatformFont {
fn new_from_local_font_identifier(
font_identifier: LocalFontIdentifier,
requested_size: Option<Au>,
_variations: &[FontVariation],
) -> Result<PlatformFont, &'static str> {
Self::new(FontIdentifier::Local(font_identifier), None, requested_size)
}
@ -353,6 +355,11 @@ impl PlatformFontMethods for PlatformFont {
Size2D::new(rect.size.width as f32, rect.size.height as f32),
)
}
fn variations(&self) -> &[FontVariation] {
// FIXME: Implement this for macos
&[]
}
}
pub(super) trait CoreTextFontTraitsMapping {

View file

@ -21,7 +21,7 @@ use style::computed_values::font_weight::T as StyleFontWeight;
use style::values::computed::font::FontStyle as StyleFontStyle;
use truetype::tables::WindowsMetrics;
use truetype::value::Read;
use webrender_api::FontInstanceFlags;
use webrender_api::{FontInstanceFlags, FontVariation};
use super::font_list::LocalFontIdentifier;
use crate::{
@ -116,6 +116,7 @@ impl PlatformFontMethods for PlatformFont {
_font_identifier: FontIdentifier,
data: &FontData,
pt_size: Option<Au>,
_variations: &[FontVariation],
) -> Result<Self, &'static str> {
let font_face = FontFile::new_from_buffer(Arc::new(data.clone()))
.ok_or("Could not create FontFile")?
@ -130,6 +131,7 @@ impl PlatformFontMethods for PlatformFont {
fn new_from_local_font_identifier(
font_identifier: LocalFontIdentifier,
pt_size: Option<Au>,
_variations: &[FontVariation],
) -> Result<PlatformFont, &'static str> {
let font_face = FontCollection::system()
.font_from_descriptor(&font_identifier.font_descriptor)
@ -320,4 +322,9 @@ impl PlatformFontMethods for PlatformFont {
Size2D::new(width, height),
)
}
fn variations(&self) -> &[FontVariation] {
// FIXME: implement this for windows
&[]
}
}

View file

@ -22,8 +22,9 @@ use harfbuzz_sys::{
hb_face_create_for_tables, hb_face_destroy, hb_face_t, hb_feature_t, hb_font_create,
hb_font_destroy, hb_font_funcs_create, hb_font_funcs_set_glyph_h_advance_func,
hb_font_funcs_set_nominal_glyph_func, hb_font_funcs_t, hb_font_set_funcs, hb_font_set_ppem,
hb_font_set_scale, hb_font_t, hb_glyph_info_t, hb_glyph_position_t, hb_ot_layout_get_baseline,
hb_position_t, hb_script_from_iso15924_tag, hb_shape, hb_tag_t,
hb_font_set_scale, hb_font_set_variations, hb_font_t, hb_glyph_info_t, hb_glyph_position_t,
hb_ot_layout_get_baseline, hb_position_t, hb_script_from_iso15924_tag, hb_shape, hb_tag_t,
hb_variation_t,
};
use log::debug;
use num_traits::Zero;
@ -32,13 +33,14 @@ use crate::font::advance_for_shaped_glyph;
use crate::platform::font::FontTable;
use crate::{
BASE, ByteIndex, Font, FontBaseline, FontTableMethods, FontTableTag, GlyphData, GlyphId,
GlyphStore, KERN, ShapingFlags, ShapingOptions, fixed_to_float, float_to_fixed, ot_tag,
GlyphStore, KERN, OpenTypeTableTag, ShapingFlags, ShapingOptions, fixed_to_float,
float_to_fixed, ot_tag,
};
const NO_GLYPH: i32 = -1;
const LIGA: u32 = ot_tag!('l', 'i', 'g', 'a');
const HB_OT_TAG_DEFAULT_SCRIPT: u32 = ot_tag!('D', 'F', 'L', 'T');
const HB_OT_TAG_DEFAULT_LANGUAGE: u32 = ot_tag!('d', 'f', 'l', 't');
const LIGA: OpenTypeTableTag = ot_tag!('l', 'i', 'g', 'a');
const HB_OT_TAG_DEFAULT_SCRIPT: OpenTypeTableTag = ot_tag!('D', 'F', 'L', 'T');
const HB_OT_TAG_DEFAULT_LANGUAGE: OpenTypeTableTag = ot_tag!('d', 'f', 'l', 't');
pub struct ShapedGlyphData {
count: usize,
@ -155,18 +157,17 @@ impl Drop for Shaper {
}
impl Shaper {
#[allow(clippy::not_unsafe_ptr_arg_deref)] // Has an unsafe block inside
pub fn new(font: *const Font) -> Shaper {
pub fn new(font: &Font) -> Shaper {
unsafe {
let hb_face: *mut hb_face_t = hb_face_create_for_tables(
Some(font_table_func),
font as *const c_void as *mut c_void,
font as *const Font as *mut c_void,
None,
);
let hb_font: *mut hb_font_t = hb_font_create(hb_face);
// Set points-per-em. if zero, performs no hinting in that direction.
let pt_size = (*font).descriptor.pt_size.to_f64_px();
let pt_size = font.descriptor.pt_size.to_f64_px();
hb_font_set_ppem(hb_font, pt_size as c_uint, pt_size as c_uint);
// Set scaling. Note that this takes 16.16 fixed point.
@ -180,10 +181,26 @@ impl Shaper {
hb_font_set_funcs(
hb_font,
HB_FONT_FUNCS.0,
font as *mut Font as *mut c_void,
font as *const Font as *mut c_void,
None,
);
if servo_config::pref!(layout_variable_fonts_enabled) {
let variations = &font.variations();
if !variations.is_empty() {
let variations: Vec<_> = variations
.iter()
.map(|variation| hb_variation_t {
tag: variation.tag,
value: variation.value,
})
.collect();
hb_font_set_variations(hb_font, variations.as_ptr(), variations.len() as u32);
}
}
Shaper {
hb_face,
hb_font,
@ -270,6 +287,7 @@ impl Shaper {
features.as_mut_ptr(),
features.len() as u32,
);
self.save_glyph_results(text, options, glyphs, hb_buffer);
hb_buffer_destroy(hb_buffer);
}

View file

@ -28,7 +28,7 @@ use style::values::computed::font::{
};
use style::values::computed::{FontStretch, FontWeight};
use style::values::specified::FontStretch as SpecifiedFontStretch;
use webrender_api::{FontInstanceFlags, FontInstanceKey, FontKey};
use webrender_api::{FontInstanceFlags, FontInstanceKey, FontKey, FontVariation};
use crate::font::FontDescriptor;
use crate::font_store::FontStore;
@ -65,6 +65,7 @@ pub enum SystemFontServiceMessage {
FontIdentifier,
Au,
FontInstanceFlags,
Vec<FontVariation>,
IpcSender<FontInstanceKey>,
),
GetFontKey(IpcSender<FontKey>),
@ -94,7 +95,7 @@ pub struct SystemFontService {
local_families: FontStore,
compositor_api: CrossProcessCompositorApi,
webrender_fonts: HashMap<FontIdentifier, FontKey>,
font_instances: HashMap<(FontKey, Au), FontInstanceKey>,
font_instances: HashMap<(FontKey, Au, Vec<FontVariation>), FontInstanceKey>,
generic_fonts: ResolvedGenericFontFamilies,
/// This is an optimization that allows the [`SystemFontService`] to send font data to
@ -176,8 +177,15 @@ impl SystemFontService {
let _ =
result_sender.send(self.get_font_templates(font_descriptor, font_family));
},
SystemFontServiceMessage::GetFontInstance(identifier, pt_size, flags, result) => {
let _ = result.send(self.get_font_instance(identifier, pt_size, flags));
SystemFontServiceMessage::GetFontInstance(
identifier,
pt_size,
flags,
variations,
result,
) => {
let _ =
result.send(self.get_font_instance(identifier, pt_size, flags, variations));
},
SystemFontServiceMessage::GetFontKey(result_sender) => {
self.fetch_new_keys();
@ -281,6 +289,7 @@ impl SystemFontService {
identifier: FontIdentifier,
pt_size: Au,
flags: FontInstanceFlags,
variations: Vec<FontVariation>,
) -> FontInstanceKey {
self.fetch_new_keys();
@ -301,7 +310,7 @@ impl SystemFontService {
*self
.font_instances
.entry((font_key, pt_size))
.entry((font_key, pt_size, variations.clone()))
.or_insert_with(|| {
let font_instance_key = self.free_font_instance_keys.pop().unwrap();
compositor_api.add_font_instance(
@ -309,6 +318,7 @@ impl SystemFontService {
font_key,
pt_size.to_f32_px(),
flags,
variations,
);
font_instance_key
})
@ -473,6 +483,7 @@ impl SystemFontServiceProxy {
identifier: FontIdentifier,
size: Au,
flags: FontInstanceFlags,
variations: Vec<FontVariation>,
) -> FontInstanceKey {
let (response_chan, response_port) = ipc::channel().expect("failed to create IPC channel");
self.sender
@ -481,6 +492,7 @@ impl SystemFontServiceProxy {
identifier,
size,
flags,
variations,
response_chan,
))
.expect("failed to send message to system font service");

View file

@ -27,7 +27,7 @@ fn make_font(path: PathBuf) -> Font {
let data = FontData::from_bytes(&bytes);
let identifier = FontIdentifier::Web(ServoUrl::from_file_path(path).unwrap());
let platform_font = PlatformFont::new_from_data(identifier.clone(), &data, None).unwrap();
let platform_font = PlatformFont::new_from_data(identifier.clone(), &data, None, &[]).unwrap();
let template = FontTemplate {
identifier,
@ -40,6 +40,7 @@ fn make_font(path: PathBuf) -> Font {
style: FontStyle::normal(),
variant: FontVariantCaps::Normal,
pt_size: Au::from_px(24),
variation_settings: vec![],
};
Font::new(FontTemplateRef::new(template), descriptor, Some(data), None).unwrap()
}

View file

@ -133,7 +133,7 @@ mod font_context {
);
},
SystemFontServiceMessage::GetFontInstanceKey(result_sender) |
SystemFontServiceMessage::GetFontInstance(_, _, _, result_sender) => {
SystemFontServiceMessage::GetFontInstance(_, _, _, _, result_sender) => {
let _ = result_sender.send(FontInstanceKey(IdNamespace(0), 0));
},
SystemFontServiceMessage::GetFontKey(result_sender) => {
@ -187,9 +187,12 @@ mod font_context {
path: path.to_str().expect("Could not load test font").into(),
variation_index: 0,
};
let handle =
PlatformFont::new_from_local_font_identifier(local_font_identifier.clone(), None)
.expect("Could not load test font");
let handle = PlatformFont::new_from_local_font_identifier(
local_font_identifier.clone(),
None,
&[],
)
.expect("Could not load test font");
family.add_template(FontTemplate::new(
FontIdentifier::Local(local_font_identifier),
@ -352,6 +355,7 @@ mod font_context {
style: FontStyle::normal(),
variant: FontVariantCaps::Normal,
pt_size: Au(10),
variation_settings: vec![],
};
let family = SingleFontFamily::FamilyName(FamilyName {

View file

@ -36,7 +36,7 @@ fn test_font_template_descriptor() {
.unwrap();
let data = FontData::from_bytes(&bytes);
let handle = PlatformFont::new_from_data(identifier, &data, None).unwrap();
let handle = PlatformFont::new_from_data(identifier, &data, None, &[]).unwrap();
handle.descriptor()
}

View file

@ -432,6 +432,7 @@ impl TextRun {
&shaping_options,
font,
);
segment
})
.collect();

View file

@ -837,6 +837,7 @@ malloc_size_of_is_webrender_malloc_size_of!(webrender_api::LineStyle);
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::MixBlendMode);
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::NormalBorder);
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::RepeatMode);
malloc_size_of_is_webrender_malloc_size_of!(webrender_api::FontVariation);
macro_rules! malloc_size_of_is_stylo_malloc_size_of(
($($ty:ty),+) => (

View file

@ -122,6 +122,7 @@ def add_css_properties_attributes(css_properties_json: str, parser: Parser) -> N
["layout.css.transition-behavior.enabled", "layout_css_transition_behavior_enabled"],
["layout.writing-mode.enabled", "layout_writing_mode_enabled"],
["layout.container-queries.enabled", "layout_container_queries_enabled"],
["layout.variable_fonts.enabled", "layout_variable_fonts_enabled"]
]
for mapping in MAPPING:
if mapping[0] == preference_name:

View file

@ -14,7 +14,7 @@ use log::warn;
use malloc_size_of_derive::MallocSizeOf;
use smallvec::SmallVec;
use strum_macros::IntoStaticStr;
use webrender_api::DocumentId;
use webrender_api::{DocumentId, FontVariation};
pub mod display_list;
pub mod rendering_context;
@ -132,7 +132,13 @@ pub enum CompositorMsg {
/// Add a system font with the given font key and handle.
AddSystemFont(FontKey, NativeFontHandle),
/// Add an instance of a font with the given instance key.
AddFontInstance(FontInstanceKey, FontKey, f32, FontInstanceFlags),
AddFontInstance(
FontInstanceKey,
FontKey,
f32,
FontInstanceFlags,
Vec<FontVariation>,
),
/// Remove the given font resources from our WebRender instance.
RemoveFonts(Vec<FontKey>, Vec<FontInstanceKey>),
/// Measure the current memory usage associated with the compositor.
@ -303,12 +309,14 @@ impl CrossProcessCompositorApi {
font_key: FontKey,
size: f32,
flags: FontInstanceFlags,
variations: Vec<FontVariation>,
) {
let _x = self.0.send(CompositorMsg::AddFontInstance(
font_instance_key,
font_key,
size,
flags,
variations,
));
}