fonts: Add font variations support for Windows (#38831)

Unlike other platforms where we read the default axis values and combine
it with variations from style to make the font face, we set the
variations from the style when creating the font face and then read the
final variations from the face. It seems that DirectWrite does the
normalization of variation values internally.

This depends on servo/dwrote-rs#68.

Testing: We currently don't have tests for Windows, but variation
support is
covered by the WPT tests.
Fixes: This is part of #38800.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2025-08-22 04:10:17 -07:00 committed by GitHub
parent 16ba172ba8
commit 2ac8665e03
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 63 additions and 17 deletions

8
Cargo.lock generated
View file

@ -2174,9 +2174,9 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]] [[package]]
name = "dwrote" name = "dwrote"
version = "0.11.3" version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfe1f192fcce01590bd8d839aca53ce0d11d803bf291b2a6c4ad925a8f0024be" checksum = "20c93d234bac0cdd0e2ac08bc8a5133f8df2169e95b262dfcea5e5cb7855672f"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
"libc", "libc",
@ -4903,7 +4903,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"windows-targets 0.48.5", "windows-targets 0.52.6",
] ]
[[package]] [[package]]
@ -10079,7 +10079,7 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22"
dependencies = [ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]

View file

@ -73,7 +73,7 @@ fontconfig_sys = { package = "yeslogic-fontconfig-sys", version = "6" }
xml-rs = "0.8" xml-rs = "0.8"
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(target_os = "windows")'.dependencies]
dwrote = "0.11.2" dwrote = "0.11.4"
truetype = { version = "0.47.3", features = ["ignore-invalid-language-ids"] } truetype = { version = "0.47.3", features = ["ignore-invalid-language-ids"] }
[lints.rust] [lints.rust]

View file

@ -12,7 +12,9 @@ use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
use app_units::Au; use app_units::Au;
use dwrote::{FontCollection, FontFace, FontFile}; use dwrote::{
DWRITE_FONT_AXIS_VALUE, DWRITE_FONT_SIMULATIONS_NONE, FontCollection, FontFace, FontFile,
};
use euclid::default::{Point2D, Rect, Size2D}; use euclid::default::{Point2D, Rect, Size2D};
use log::{debug, warn}; use log::{debug, warn};
use style::Zero; use style::Zero;
@ -67,6 +69,7 @@ pub struct PlatformFont {
em_size: f32, em_size: f32,
du_to_px: f32, du_to_px: f32,
scaled_du_to_px: f32, scaled_du_to_px: f32,
variations: Vec<FontVariation>,
} }
// Based on information from the Skia codebase, it seems that DirectWrite APIs from // Based on information from the Skia codebase, it seems that DirectWrite APIs from
@ -92,7 +95,11 @@ impl<T> Deref for Nondebug<T> {
} }
impl PlatformFont { impl PlatformFont {
fn new(font_face: FontFace, pt_size: Option<Au>) -> Result<Self, &'static str> { fn new(
font_face: FontFace,
pt_size: Option<Au>,
variations: Vec<FontVariation>,
) -> Result<Self, &'static str> {
let pt_size = pt_size.unwrap_or(au_from_pt(12.)); let pt_size = pt_size.unwrap_or(au_from_pt(12.));
let du_per_em = font_face.metrics().metrics0().designUnitsPerEm as f32; let du_per_em = font_face.metrics().metrics0().designUnitsPerEm as f32;
@ -107,8 +114,51 @@ impl PlatformFont {
em_size, em_size,
du_to_px: design_units_to_pixels, du_to_px: design_units_to_pixels,
scaled_du_to_px: scaled_design_units_to_pixels, scaled_du_to_px: scaled_design_units_to_pixels,
variations,
}) })
} }
fn new_with_variations(
font_face: FontFace,
pt_size: Option<Au>,
variations: &[FontVariation],
) -> Result<Self, &'static str> {
if variations.is_empty() {
return Self::new(font_face, pt_size, vec![]);
}
// On FreeType and CoreText platforms, the platform layer is able to read the minimum, maxmimum,
// and default values of each axis. This doesn't seem possible here and it seems that Gecko
// also just sets the value of the axis based on the values from the style as well.
//
// dwrote (and presumably the Windows APIs) accept a reversed version of the table
// tag bytes, which means that `u32::swap_bytes` must be called here in order to
// use a byte order compatible with the rest of Servo.
let variations: Vec<_> = variations
.into_iter()
.map(|variation| DWRITE_FONT_AXIS_VALUE {
axisTag: variation.tag.swap_bytes(),
value: variation.value,
})
.collect();
let Some(font_face) =
font_face.create_font_face_with_variations(DWRITE_FONT_SIMULATIONS_NONE, &variations)
else {
return Err("Could not adapt FontFace to given variations");
};
let variations = font_face.variations().unwrap_or_default();
let variations = variations
.iter()
.map(|dwrote_variation| FontVariation {
tag: dwrote_variation.axisTag.swap_bytes(),
value: dwrote_variation.value,
})
.collect();
Self::new(font_face, pt_size, variations)
}
} }
impl PlatformFontMethods for PlatformFont { impl PlatformFontMethods for PlatformFont {
@ -116,22 +166,19 @@ impl PlatformFontMethods for PlatformFont {
_font_identifier: FontIdentifier, _font_identifier: FontIdentifier,
data: &FontData, data: &FontData,
pt_size: Option<Au>, pt_size: Option<Au>,
_variations: &[FontVariation], variations: &[FontVariation],
) -> Result<Self, &'static str> { ) -> Result<Self, &'static str> {
let font_face = FontFile::new_from_buffer(Arc::new(data.clone())) let font_face = FontFile::new_from_buffer(Arc::new(data.clone()))
.ok_or("Could not create FontFile")? .ok_or("Could not create FontFile")?
.create_face( .create_face(0 /* face_index */, DWRITE_FONT_SIMULATIONS_NONE)
0, /* face_index */
dwrote::DWRITE_FONT_SIMULATIONS_NONE,
)
.map_err(|_| "Could not create FontFace")?; .map_err(|_| "Could not create FontFace")?;
Self::new(font_face, pt_size) Self::new_with_variations(font_face, pt_size, variations)
} }
fn new_from_local_font_identifier( fn new_from_local_font_identifier(
font_identifier: LocalFontIdentifier, font_identifier: LocalFontIdentifier,
pt_size: Option<Au>, pt_size: Option<Au>,
_variations: &[FontVariation], variations: &[FontVariation],
) -> Result<PlatformFont, &'static str> { ) -> Result<PlatformFont, &'static str> {
let font_face = FontCollection::system() let font_face = FontCollection::system()
.font_from_descriptor(&font_identifier.font_descriptor) .font_from_descriptor(&font_identifier.font_descriptor)
@ -139,7 +186,7 @@ impl PlatformFontMethods for PlatformFont {
.flatten() .flatten()
.ok_or("Could not create Font from descriptor")? .ok_or("Could not create Font from descriptor")?
.create_font_face(); .create_font_face();
Self::new(font_face, pt_size) Self::new_with_variations(font_face, pt_size, variations)
} }
fn descriptor(&self) -> FontTemplateDescriptor { fn descriptor(&self) -> FontTemplateDescriptor {
@ -324,7 +371,6 @@ impl PlatformFontMethods for PlatformFont {
} }
fn variations(&self) -> &[FontVariation] { fn variations(&self) -> &[FontVariation] {
// FIXME: implement this for windows &self.variations
&[]
} }
} }