From 7020573defaec844348d015ca7e3db0d809925a6 Mon Sep 17 00:00:00 2001 From: gcailly <109429289+gcailly@users.noreply.github.com> Date: Wed, 25 Mar 2026 19:58:56 +0100 Subject: [PATCH] Add test_gpos_kerning to verify OpenType GPOS kerning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Verify that letter pairs like AV, VA, AT are measurably tighter when laid out together than the sum of their individual widths. This test fails without harfrust text shaping (kern adjustment ≈ 0) and passes with it (kern adjustment > 0.5px). --- crates/epaint/src/text/text_layout.rs | 47 +++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/crates/epaint/src/text/text_layout.rs b/crates/epaint/src/text/text_layout.rs index 5ff350e2b..40f8025e7 100644 --- a/crates/epaint/src/text/text_layout.rs +++ b/crates/epaint/src/text/text_layout.rs @@ -1549,4 +1549,51 @@ mod tests { galley.rows[0].row.glyphs.len() ); } + + #[test] + fn test_gpos_kerning() { + // GPOS kerning: pairs like "AV", "VA", "AT" should be tighter than + // the sum of individual character widths. Without text shaping, egui + // only uses the legacy `kern` table, so these pairs had diff ≈ 0. + // With harfrust, GPOS kerning applies proper negative adjustments. + let pixels_per_point = 1.0; + let mut fonts = FontsImpl::new(TextOptions::default(), FontDefinitions::default()); + let font_id = FontId::proportional(14.0); + + for pair in ["AV", "VA", "AT"] { + let (pair_w, _, _) = measure_text(&mut fonts, pair, &font_id, pixels_per_point); + let chars: Vec = pair.chars().collect(); + let (w1, _, _) = + measure_text(&mut fonts, &chars[0].to_string(), &font_id, pixels_per_point); + let (w2, _, _) = + measure_text(&mut fonts, &chars[1].to_string(), &font_id, pixels_per_point); + let sum = w1 + w2; + let kern_adjustment = sum - pair_w; + + assert!( + kern_adjustment > 0.5, + "GPOS kerning for '{pair}': expected pair to be noticeably tighter \ + than sum of individuals. pair_width={pair_w:.2}, sum={sum:.2}, \ + kern_adjustment={kern_adjustment:.2} (should be > 0.5)", + ); + } + } + + fn measure_text( + fonts: &mut FontsImpl, + text: &str, + font_id: &FontId, + pixels_per_point: f32, + ) -> (f32, usize, Vec<(char, f32)>) { + let job = LayoutJob::simple( + text.to_owned(), + font_id.clone(), + Color32::WHITE, + f32::INFINITY, + ); + let galley = layout(fonts, pixels_per_point, job.into()); + let glyphs = &galley.rows[0].row.glyphs; + let details: Vec<_> = glyphs.iter().map(|g| (g.chr, g.advance_width)).collect(); + (galley.size().x, glyphs.len(), details) + } }