From 775a292b79a001344a8702c5dc3dcd184fe73430 Mon Sep 17 00:00:00 2001 From: Nico Burns Date: Thu, 28 Aug 2025 14:34:39 +0100 Subject: [PATCH] Run both shapers at once Signed-off-by: Nico Burns --- components/fonts/Cargo.toml | 2 +- components/fonts/font.rs | 11 +++ components/fonts/shapers/both.rs | 113 +++++++++++++++++++++++++++ components/fonts/shapers/harfbuzz.rs | 8 +- components/fonts/shapers/harfrust.rs | 8 +- components/fonts/shapers/mod.rs | 19 ++++- 6 files changed, 153 insertions(+), 8 deletions(-) create mode 100644 components/fonts/shapers/both.rs diff --git a/components/fonts/Cargo.toml b/components/fonts/Cargo.toml index 8351efa2abf..4e9697c0ef0 100644 --- a/components/fonts/Cargo.toml +++ b/components/fonts/Cargo.toml @@ -14,7 +14,7 @@ test = true doctest = false [features] -default = ["harfrust"] +default = ["harfrust", "harfbuzz"] harfbuzz = ["dep:harfbuzz-sys"] harfrust = ["dep:harfrust"] tracing = ["dep:tracing"] diff --git a/components/fonts/font.rs b/components/fonts/font.rs index 5db43d6d160..7b333e4daca 100644 --- a/components/fonts/font.rs +++ b/components/fonts/font.rs @@ -881,12 +881,23 @@ impl FontFamilyDescriptor { } } +#[derive(PartialEq)] pub struct FontBaseline { pub ideographic_baseline: f32, pub alphabetic_baseline: f32, pub hanging_baseline: f32, } +impl std::fmt::Debug for FontBaseline { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{} {} {}", + self.ideographic_baseline, self.alphabetic_baseline, self.hanging_baseline + ) + } +} + /// Given a mapping array `mapping` and a value, map that value onto /// the value specified by the array. For instance, for FontConfig /// values of weights, we would map these onto the CSS [0..1000] range diff --git a/components/fonts/shapers/both.rs b/components/fonts/shapers/both.rs new file mode 100644 index 00000000000..e1b43ceb25d --- /dev/null +++ b/components/fonts/shapers/both.rs @@ -0,0 +1,113 @@ +use std::io::Write as _; + +use app_units::Au; + +use super::harfbuzz::ShapedGlyphData as HarfBuzzShapedGlyphData; +use super::harfrust::ShapedGlyphData as HarfRustShapedGlyphData; +use super::{HarfBuzzShapedGlyphData as THarfBuzzShapedGlyphData, HarfBuzzShaper, HarfRustShaper}; +use crate::{Font, FontBaseline, GlyphStore, ShapingOptions}; + +pub(crate) struct ShapedGlyphData { + buzz: HarfBuzzShapedGlyphData, + rust: HarfRustShapedGlyphData, +} + +pub(crate) struct Shaper { + buzz: HarfBuzzShaper, + rust: HarfRustShaper, +} + +impl Shaper { + pub(crate) fn new(font: &Font) -> Self { + Self { + buzz: HarfBuzzShaper::new(font), + rust: HarfRustShaper::new(font), + } + } + + pub(crate) fn shaped_glyph_data( + &self, + text: &str, + options: &ShapingOptions, + ) -> ShapedGlyphData { + ShapedGlyphData { + buzz: self.buzz.shaped_glyph_data(text, options), + rust: self.rust.shaped_glyph_data(text, options), + } + } + + pub fn shape_text(&self, text: &str, options: &ShapingOptions, glyphs: &mut GlyphStore) { + let glyph_data = self.shaped_glyph_data(text, options); + let font = self.buzz.font(); + + let equal = shape_data_eq(&glyph_data.buzz, &glyph_data.rust); + if !equal { + println!("SHAPING DATA DIFFERENT:"); + println!("Input text:"); + println!("{text}"); + println!("Buzz:"); + log_shape_data(&glyph_data.buzz); + println!("Rust:"); + log_shape_data(&glyph_data.rust); + println!("========================"); + } + + super::shape_text_harfbuzz(&glyph_data.rust, font, text, options, glyphs); + } + + pub fn baseline(&self) -> Option { + let buzz_baseline = self.buzz.baseline(); + let rust_baseline = self.rust.baseline(); + + // Debug log + let equal = buzz_baseline == rust_baseline; + let eq_word = if equal { "same" } else { "diff" }; + println!( + "BL ({eq_word}) C: {:?} | R: {:?}", + buzz_baseline, rust_baseline + ); + + rust_baseline + } +} + +fn shape_data_eq(a: &impl THarfBuzzShapedGlyphData, b: &impl THarfBuzzShapedGlyphData) -> bool { + if a.len() != b.len() { + return false; + } + + let mut a_y_pos = Au::new(0); + let mut b_y_pos = Au::new(0); + for i in 0..a.len() { + if a.byte_offset_of_glyph(i) != b.byte_offset_of_glyph(i) { + return false; + } + + if a.entry_for_glyph(i, &mut a_y_pos) != b.entry_for_glyph(i, &mut b_y_pos) { + return false; + } + } + + true +} + +fn log_shape_data(data: &impl THarfBuzzShapedGlyphData) { + let mut out = std::io::stdout().lock(); + writeln!(&mut out, "len: {}", data.len()).unwrap(); + writeln!(&mut out, "offsets:").unwrap(); + for i in 0..data.len() { + write!(&mut out, "{} ", data.byte_offset_of_glyph(i)).unwrap(); + } + writeln!(&mut out).unwrap(); + writeln!(&mut out, "entries:").unwrap(); + let mut y_pos = Au::new(0); + for i in 0..data.len() { + let entry = data.entry_for_glyph(i, &mut y_pos); + write!(&mut out, "cp: {} ad: {} ", entry.codepoint, entry.advance.0).unwrap(); + match entry.offset { + Some(offset) => write!(&mut out, "Some(x:{}, y:{})", offset.x.0, offset.y.0).unwrap(), + None => write!(&mut out, "None").unwrap(), + }; + writeln!(&mut out).unwrap(); + } +} diff --git a/components/fonts/shapers/harfbuzz.rs b/components/fonts/shapers/harfbuzz.rs index b1fcb3fad97..29a31e3c241 100644 --- a/components/fonts/shapers/harfbuzz.rs +++ b/components/fonts/shapers/harfbuzz.rs @@ -209,7 +209,11 @@ impl Shaper { } /// Calculate the layout metrics associated with the given text with the [`Shaper`]s font. - fn shaped_glyph_data(&self, text: &str, options: &ShapingOptions) -> ShapedGlyphData { + pub(crate) fn shaped_glyph_data( + &self, + text: &str, + options: &ShapingOptions, + ) -> ShapedGlyphData { unsafe { let hb_buffer: *mut hb_buffer_t = hb_buffer_create(); hb_buffer_set_direction( @@ -268,7 +272,7 @@ impl Shaper { } } - fn font(&self) -> &Font { + pub(crate) fn font(&self) -> &Font { unsafe { &(*self.font) } } diff --git a/components/fonts/shapers/harfrust.rs b/components/fonts/shapers/harfrust.rs index 04c83ce7ed5..cc31a840cbc 100644 --- a/components/fonts/shapers/harfrust.rs +++ b/components/fonts/shapers/harfrust.rs @@ -129,7 +129,11 @@ impl Shaper { } impl Shaper { - fn shaped_glyph_data(&self, text: &str, options: &crate::ShapingOptions) -> ShapedGlyphData { + pub(crate) fn shaped_glyph_data( + &self, + text: &str, + options: &crate::ShapingOptions, + ) -> ShapedGlyphData { let mut buffer = UnicodeBuffer::new(); // Set direction @@ -179,7 +183,7 @@ impl Shaper { } #[allow(unsafe_code)] - fn font(&self) -> &Font { + pub(crate) fn font(&self) -> &Font { // SAFETY: the font actually owns this shaper so it cannot have been dropped unsafe { &(*self.font) } } diff --git a/components/fonts/shapers/mod.rs b/components/fonts/shapers/mod.rs index cb9834e110e..de50b2e97ec 100644 --- a/components/fonts/shapers/mod.rs +++ b/components/fonts/shapers/mod.rs @@ -16,12 +16,24 @@ use crate::{Font, GlyphData, GlyphId, GlyphStore, ShapingOptions, advance_for_sh #[cfg(feature = "harfbuzz")] mod harfbuzz; #[cfg(feature = "harfbuzz")] -pub(crate) use harfbuzz::Shaper; +pub(crate) use harfbuzz::Shaper as HarfBuzzShaper; #[cfg(feature = "harfrust")] mod harfrust; -#[cfg(all(feature = "harfrust", not(feature = "harfbuzz")))] -pub(crate) use harfrust::Shaper; +#[cfg(feature = "harfrust")] +pub(crate) use harfrust::Shaper as HarfRustShaper; + +#[cfg(all(feature = "harfbuzz", feature = "harfrust"))] +mod both; +#[cfg(all(feature = "harfbuzz", feature = "harfrust"))] +pub(crate) use BothShaper as Shaper; +// Configure default shaper (actually used) +#[cfg(all(feature = "harfbuzz", not(feature = "harfrust")))] +pub(crate) use HarfBuzzShaper as Shaper; +#[cfg(all(not(feature = "harfbuzz"), feature = "harfrust"))] +pub(crate) use HarfRustShaper as Shaper; +#[cfg(all(feature = "harfbuzz", feature = "harfrust"))] +pub(crate) use both::Shaper as BothShaper; const NO_GLYPH: i32 = -1; @@ -39,6 +51,7 @@ fn unicode_script_to_iso15924_tag(script: unicode_script::Script) -> u32 { u32::from_be_bytes(bytes) } +#[derive(PartialEq)] struct ShapedGlyphEntry { codepoint: GlyphId, advance: Au,