Run both shapers at once

Signed-off-by: Nico Burns <nico@nicoburns.com>
This commit is contained in:
Nico Burns
2025-08-28 14:34:39 +01:00
parent 5e3358b85a
commit 775a292b79
6 changed files with 153 additions and 8 deletions

View File

@@ -14,7 +14,7 @@ test = true
doctest = false
[features]
default = ["harfrust"]
default = ["harfrust", "harfbuzz"]
harfbuzz = ["dep:harfbuzz-sys"]
harfrust = ["dep:harfrust"]
tracing = ["dep:tracing"]

View File

@@ -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

View File

@@ -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<FontBaseline> {
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();
}
}

View File

@@ -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) }
}

View File

@@ -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) }
}

View File

@@ -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,