fonts: Use fontations to read the OS/2 table of DirectWrite fonts (#38851)

Instead of copying the font table data in memory and parsing it with the
`truetype` crate, use a non-copying API from DirectWrite to implement a
`fontations` `TableProvider`. This has two benefits:

- We remove the dependency on the `truetype` crate finally.
- We do not have to make an in-memory copy of the table data when
  parsing the table.

The hope is that the `TableProvider` will be more generally useful in
the future.

Testing: There are no automated tests for Windows, but I manually
verified
that the data retrived via `fontations` matched that retrived by
`truetype`.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2025-08-22 09:23:35 -07:00 committed by GitHub
parent 176e42d36d
commit 777373054f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 107 additions and 116 deletions

17
Cargo.lock generated
View file

@ -2708,11 +2708,11 @@ dependencies = [
"stylo",
"stylo_atoms",
"tracing",
"truetype",
"unicode-properties",
"unicode-script",
"url",
"webrender_api",
"winapi",
"xml-rs",
"yeslogic-fontconfig-sys",
]
@ -8988,15 +8988,6 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce607aae8ab0ab3abf3a2723a9ab6f09bb8639ed83fdd888d857b8e556c868d8"
[[package]]
name = "truetype"
version = "0.47.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05ce9543b570c6e8a392274b67e1001816bce953aa89724e52a4639db02a10e0"
dependencies = [
"typeface",
]
[[package]]
name = "try-lock"
version = "0.2.5"
@ -9037,12 +9028,6 @@ version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
[[package]]
name = "typeface"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "191ea6762cbcd705fbed8f58573fd6c654453ff3da60adb293d9f255bc92af8e"
[[package]]
name = "typeid"
version = "1.0.3"

View file

@ -74,7 +74,7 @@ xml-rs = "0.8"
[target.'cfg(target_os = "windows")'.dependencies]
dwrote = "0.11.4"
truetype = { version = "0.47.3", features = ["ignore-invalid-language-ids"] }
winapi = { workspace = true }
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(ohos_mock)'] }

View file

@ -18,6 +18,7 @@ use euclid::num::Zero;
use log::debug;
use malloc_size_of_derive::MallocSizeOf;
use parking_lot::RwLock;
use read_fonts::tables::os2::{Os2, SelectionFlags};
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use style::computed_values::font_variant_caps;
@ -119,6 +120,29 @@ pub trait PlatformFontMethods: Sized {
/// Return all the variation values that the font was instantiated with.
fn variations(&self) -> &[FontVariation];
fn descriptor_from_os2_table(os2: &Os2) -> FontTemplateDescriptor {
let mut style = FontStyle::NORMAL;
if os2.fs_selection().contains(SelectionFlags::ITALIC) {
style = FontStyle::ITALIC;
}
let weight = FontWeight::from_float(os2.us_weight_class() as f32);
let stretch = match os2.us_width_class() {
1 => FontStretch::ULTRA_CONDENSED,
2 => FontStretch::EXTRA_CONDENSED,
3 => FontStretch::CONDENSED,
4 => FontStretch::SEMI_CONDENSED,
5 => FontStretch::NORMAL,
6 => FontStretch::SEMI_EXPANDED,
7 => FontStretch::EXPANDED,
8 => FontStretch::EXTRA_EXPANDED,
9 => FontStretch::ULTRA_EXPANDED,
_ => FontStretch::NORMAL,
};
FontTemplateDescriptor::new(weight, stretch, style)
}
}
// Used to abstract over the shaper's choice of fixed int representation.

View file

@ -15,14 +15,10 @@ use freetype_sys::{
use log::debug;
use memmap2::Mmap;
use parking_lot::ReentrantMutex;
use read_fonts::tables::os2::SelectionFlags;
use read_fonts::types::Tag;
use read_fonts::{FontRef, ReadError, TableProvider};
use servo_arc::Arc;
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, FontVariation};
use super::LocalFontIdentifier;
@ -135,31 +131,10 @@ impl PlatformFontMethods for PlatformFont {
let Ok(font_ref) = self.table_provider_data.font_ref() else {
return FontTemplateDescriptor::default();
};
let Ok(os2) = font_ref.os2() else {
return FontTemplateDescriptor::default();
};
let mut style = FontStyle::NORMAL;
if os2.fs_selection().contains(SelectionFlags::ITALIC) {
style = FontStyle::ITALIC;
}
let weight = FontWeight::from_float(os2.us_weight_class() as f32);
let stretch = match os2.us_width_class() {
1 => FontStretch::ULTRA_CONDENSED,
2 => FontStretch::EXTRA_CONDENSED,
3 => FontStretch::CONDENSED,
4 => FontStretch::SEMI_CONDENSED,
5 => FontStretch::NORMAL,
6 => FontStretch::SEMI_EXPANDED,
7 => FontStretch::EXPANDED,
8 => FontStretch::EXTRA_EXPANDED,
9 => FontStretch::ULTRA_EXPANDED,
_ => FontStretch::NORMAL,
};
FontTemplateDescriptor::new(weight, stretch, style)
Self::descriptor_from_os2_table(&os2)
}
fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {

View file

@ -6,8 +6,9 @@
// information for an approach that we'll likely need to take when the
// renderer moves to a sandboxed process.
use std::cell::RefCell;
use std::ffi::c_void;
use std::fmt;
use std::io::Cursor;
use std::ops::Deref;
use std::sync::Arc;
@ -16,19 +17,17 @@ use dwrote::{
DWRITE_FONT_AXIS_VALUE, DWRITE_FONT_SIMULATIONS_NONE, FontCollection, FontFace, FontFile,
};
use euclid::default::{Point2D, Rect, Size2D};
use log::{debug, warn};
use log::debug;
use read_fonts::TableProvider;
use skrifa::Tag;
use style::Zero;
use style::computed_values::font_stretch::T as StyleFontStretch;
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, FontVariation};
use winapi::shared::minwindef::{BOOL, FALSE};
use super::font_list::LocalFontIdentifier;
use crate::{
FontData, FontIdentifier, FontMetrics, FontTableMethods, FontTableTag, FontTemplateDescriptor,
FractionalPixel, GlyphId, PlatformFontMethods, ot_tag,
FractionalPixel, GlyphId, PlatformFontMethods,
};
// 1em = 12pt = 16px, assuming 72 points per inch and 96 px per inch
@ -190,71 +189,11 @@ impl PlatformFontMethods for PlatformFont {
}
fn descriptor(&self) -> FontTemplateDescriptor {
// We need the font (DWriteFont) in order to be able to query things like
// the family name, face name, weight, etc. On Windows 10, the
// DWriteFontFace3 interface provides this on the FontFace, but that's only
// available on Win10+.
//
// Instead, we do the parsing work using the truetype crate for raw fonts.
// We're just extracting basic info, so this is sufficient for now.
//
// The `dwrote` APIs take SFNT table tags in a reversed byte order, which
// is why `u32::swap_bytes()` is called here.
let windows_metrics_bytes = self
.face
.get_font_table(u32::swap_bytes(ot_tag!('O', 'S', '/', '2')));
if windows_metrics_bytes.is_none() {
warn!("Could not find OS/2 table in font.");
return FontTemplateDescriptor::default();
}
let mut cursor = Cursor::new(windows_metrics_bytes.as_ref().unwrap());
let Ok(table) = WindowsMetrics::read(&mut cursor) else {
warn!("Could not read OS/2 table in font.");
return FontTemplateDescriptor::default();
};
let (weight_val, width_val, italic_bool) = match table {
WindowsMetrics::Version0(ref m) => {
(m.weight_class, m.width_class, m.selection_flags.0 & 1 == 1)
},
WindowsMetrics::Version1(ref m) => {
(m.weight_class, m.width_class, m.selection_flags.0 & 1 == 1)
},
WindowsMetrics::Version2(ref m) |
WindowsMetrics::Version3(ref m) |
WindowsMetrics::Version4(ref m) => {
(m.weight_class, m.width_class, m.selection_flags.0 & 1 == 1)
},
WindowsMetrics::Version5(ref m) => {
(m.weight_class, m.width_class, m.selection_flags.0 & 1 == 1)
},
};
let weight = StyleFontWeight::from_float(weight_val as f32);
let stretch = match width_val.clamp(1, 9) {
1 => StyleFontStretch::ULTRA_CONDENSED,
2 => StyleFontStretch::EXTRA_CONDENSED,
3 => StyleFontStretch::CONDENSED,
4 => StyleFontStretch::SEMI_CONDENSED,
5 => StyleFontStretch::NORMAL,
6 => StyleFontStretch::SEMI_EXPANDED,
7 => StyleFontStretch::EXPANDED,
8 => StyleFontStretch::EXTRA_EXPANDED,
9 => StyleFontStretch::ULTRA_CONDENSED,
_ => {
warn!("Unknown stretch size.");
StyleFontStretch::NORMAL
},
};
let style = if italic_bool {
StyleFontStyle::ITALIC
} else {
StyleFontStyle::NORMAL
};
FontTemplateDescriptor::new(weight, stretch, style)
DirectWriteTableProvider::new(self)
.os2()
.as_ref()
.map(Self::descriptor_from_os2_table)
.unwrap_or_default()
}
fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
@ -374,3 +313,71 @@ impl PlatformFontMethods for PlatformFont {
&self.variations
}
}
/// A wrapper struct around [`PlatformFont`] which is responsible for
/// implementing [`TableProvider`] and cleaning up any font table contexts from
/// DirectWrite when the struct is dropped.
struct DirectWriteTableProvider<'platform_font> {
platform_font: &'platform_font PlatformFont,
contexts: RefCell<Vec<*mut c_void>>,
}
impl<'platform_font> DirectWriteTableProvider<'platform_font> {
fn new(platform_font: &'platform_font PlatformFont) -> Self {
Self {
platform_font,
contexts: Default::default(),
}
}
}
impl Drop for DirectWriteTableProvider<'_> {
fn drop(&mut self) {
let direct_write_face = unsafe { self.platform_font.face.as_ptr() };
assert!(!direct_write_face.is_null());
let direct_write_face = unsafe { &*direct_write_face };
for context in self.contexts.borrow_mut().drain(..) {
unsafe { direct_write_face.ReleaseFontTable(context) };
}
}
}
impl<'platform_font> TableProvider<'platform_font> for DirectWriteTableProvider<'platform_font> {
fn data_for_tag(&self, tag: Tag) -> Option<read_fonts::FontData<'platform_font>> {
let direct_write_face = unsafe { self.platform_font.face.as_ptr() };
if direct_write_face.is_null() {
return None;
}
let direct_write_face = unsafe { &*direct_write_face };
let direct_write_tag = u32::from_be_bytes(tag.to_be_bytes()).swap_bytes();
let mut table_data_ptr: *const u8 = std::ptr::null_mut();
let mut table_size: u32 = 0;
let mut table_context: *mut c_void = std::ptr::null_mut();
let mut exists: BOOL = FALSE;
let hr = unsafe {
direct_write_face.TryGetFontTable(
direct_write_tag,
&mut table_data_ptr as *mut *const _ as *mut *const c_void,
&mut table_size,
&mut table_context,
&mut exists,
)
};
if hr != 0 || exists == 0 {
return None;
}
self.contexts.borrow_mut().push(table_context);
if table_data_ptr.is_null() || table_size == 0 {
return None;
}
let bytes = unsafe { std::slice::from_raw_parts(table_data_ptr, table_size as usize) };
Some(read_fonts::FontData::new(bytes))
}
}