mirror of
https://github.com/servo/servo.git
synced 2025-09-29 16:19:14 +01:00
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:
parent
ce16fbce75
commit
7471ad7730
19 changed files with 274 additions and 79 deletions
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
&[]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue