From 777373054fc0675f49fa285e4f56007348a2b675 Mon Sep 17 00:00:00 2001 From: Martin Robinson Date: Fri, 22 Aug 2025 09:23:35 -0700 Subject: [PATCH] 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 --- Cargo.lock | 17 +-- components/fonts/Cargo.toml | 2 +- components/fonts/font.rs | 24 ++++ components/fonts/platform/freetype/font.rs | 27 +--- components/fonts/platform/windows/font.rs | 153 +++++++++++---------- 5 files changed, 107 insertions(+), 116 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0edef019254..63616275f56 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/components/fonts/Cargo.toml b/components/fonts/Cargo.toml index 7cb96dab891..1495728f505 100644 --- a/components/fonts/Cargo.toml +++ b/components/fonts/Cargo.toml @@ -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)'] } diff --git a/components/fonts/font.rs b/components/fonts/font.rs index 8c41625e01c..b024210ddf5 100644 --- a/components/fonts/font.rs +++ b/components/fonts/font.rs @@ -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. diff --git a/components/fonts/platform/freetype/font.rs b/components/fonts/platform/freetype/font.rs index 8d95cc9e3d3..327ff0d8f3a 100644 --- a/components/fonts/platform/freetype/font.rs +++ b/components/fonts/platform/freetype/font.rs @@ -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 { diff --git a/components/fonts/platform/windows/font.rs b/components/fonts/platform/windows/font.rs index 09e0e8f1642..a48fb1b8590 100644 --- a/components/fonts/platform/windows/font.rs +++ b/components/fonts/platform/windows/font.rs @@ -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 { @@ -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>, +} + +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> { + 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)) + } +}