Skip to content

fonts: Add font variations support for Windows#38831

Merged
mrobinson merged 1 commit intoservo:mainfrom
mrobinson:variations-windows
Aug 22, 2025
Merged

fonts: Add font variations support for Windows#38831
mrobinson merged 1 commit intoservo:mainfrom
mrobinson:variations-windows

Conversation

@mrobinson
Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Contributor

@nicoburns nicoburns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. A few minor comments which I would not consider blocking.

let variations: Vec<_> = variations
.into_iter()
.map(|variation| DWRITE_FONT_AXIS_VALUE {
axisTag: variation.tag.swap_bytes(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How was the need for swap_bytes determined?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The table tags that DirectWrite uses have their bytes swapped in relation to the ones that Servo and HarfBuzz uses. See https://learn.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-dwrite_make_opentype_tag.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've copy-and-pasted a comment about this here as well.

Comment on lines +141 to +147
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();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could just be variations.to_vec() where variations is the function parameter?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hrm. I couldn't figure out how to use to_vec() here. We need to convert from DWRITE_FONT_AXIS_VALUE to FontVariation (from WebRender), so I think a map is required.

Comment on lines +124 to +132
// 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.
Copy link
Copy Markdown
Contributor

@d-desiatkin d-desiatkin Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, interesting. It does seem like we can also extract the ranges and defaults, but we don't need to either. From https://learn.microsoft.com/en-us/windows/win32/api/dwrite_3/nf-dwrite_3-idwritefontresource-createfontface:

The axis values that you provide are permitted to be a subset or superset of all the ones actually supported by the font. Any unspecified axes use their default values: values beyond the ranges are clamped, and any non-varying axes have no effect.

It seems like just reading the values from the font is a bit more efficient since they are normalized upon creation anyway.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about DirectWrite, but looking at skrifa the coordinates are also normalized on creation there, but they are normalized into 16-bit (14.2) fixed-point format. So converting that back to f32 is an additional step (it looks to me like the FreeType backend is also converting back from 16.16 to f32).

I was wondering whether we needed to normalize at all, and whether we could just store the unormalized values we get from style directly and rely on anything using them to handle normalization.

@mrobinson mrobinson force-pushed the variations-windows branch 2 times, most recently from f2c29a2 to 0a2d1b4 Compare August 22, 2025 09:03
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.

Signed-off-by: Martin Robinson <[email protected]>
@mrobinson mrobinson enabled auto-merge August 22, 2025 09:11
@mrobinson mrobinson added this pull request to the merge queue Aug 22, 2025
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Aug 22, 2025
@mrobinson mrobinson added this pull request to the merge queue Aug 22, 2025
Merged via the queue into servo:main with commit 2ac8665 Aug 22, 2025
22 checks passed
@mrobinson mrobinson deleted the variations-windows branch August 22, 2025 11:47
@yezhizhen
Copy link
Copy Markdown
Member

warning: use of deprecated method `dwrote::FontFace::get_font_table`: Use `font_table` instead.
   --> components\fonts\platform\windows\font.rs:205:14
    |
205 |             .get_font_table(u32::swap_bytes(ot_tag!('O', 'S', '/', '2')));
    |              ^^^^^^^^^^^^^^
    |
    = note: `#[warn(deprecated)]` on by default

warning: use of deprecated method `dwrote::FontFace::get_glyph_indices`: Use `glyph_indices` instead.
   --> components\fonts\platform\windows\font.rs:261:31
    |
261 |         let glyph = self.face.get_glyph_indices(&[codepoint as u32])[0];
    |                               ^^^^^^^^^^^^^^^^^

warning: use of deprecated method `dwrote::FontFace::get_design_glyph_metrics`: Use `design_glyph_metrics` instead.
   --> components\fonts\platform\windows\font.rs:273:28
    |
273 |         let gm = self.face.get_design_glyph_metrics(&[glyph as u16], false)[0];
    |                            ^^^^^^^^^^^^^^^^^^^^^^^^

warning: use of deprecated method `dwrote::FontFace::get_glyph_pair_kerning_adjustment`: Use `glyph_pair_kerning_adjustment` instead.
   --> components\fonts\platform\windows\font.rs:282:14
    |
282 |             .get_glyph_pair_kerning_adjustment(first_glyph as u16, second_glyph as u16);
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

warning: use of deprecated method `dwrote::FontFace::get_font_table`: Use `font_table` instead.
   --> components\fonts\platform\windows\font.rs:343:14
    |
343 |             .get_font_table(u32::swap_bytes(tag))
    |              ^^^^^^^^^^^^^^

warning: use of deprecated method `dwrote::FontFace::get_design_glyph_metrics`: Use `design_glyph_metrics` instead.
   --> components\fonts\platform\windows\font.rs:354:14
    |
354 |             .get_design_glyph_metrics(&[glyph_id as u16], false);
    |              ^^^^^^^^^^^^^^^^^^^^^^^^

warning: use of deprecated method `dwrote::FontFace::get_files`: Use `files` instead.
  --> components\fonts\platform\windows\font_list.rs:59:14
   |
59 |             .get_files()
   |              ^^^^^^^^^

warning: use of deprecated method `dwrote::FontFile::get_font_file_path`: Use `font_file_path` instead.
  --> components\fonts\platform\windows\font_list.rs:62:14
   |
62 |             .get_font_file_path()
   |              ^^^^^^^^^^^^^^^^^^

warning: use of deprecated method `dwrote::FontFace::get_files`: Use `files` instead.
  --> components\fonts\platform\windows\font_list.rs:76:26
   |
76 |         let files = face.get_files();
   |                          ^^^^^^^^^

warning: use of deprecated method `dwrote::FontFile::get_font_file_bytes`: Use `font_file_bytes` instead.
  --> components\fonts\platform\windows\font_list.rs:79:29
   |
79 |         let data = files[0].get_font_file_bytes();
   |                             ^^^^^^^^^^^^^^^^^^^

I get lots of deprecated warning.

@mrobinson
Copy link
Copy Markdown
Member Author

I get lots of deprecated warning.

Addressed in #38856.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants