From 0322bb1ffa3a98ce18d2b09a4a184eb746964703 Mon Sep 17 00:00:00 2001 From: Kayla Date: Tue, 6 Oct 2026 09:40:00 -0400 Subject: [PATCH] Paint underlines below the baseline and strikethroughs above it Parley's run metrics carry the font's decoration offsets as the font stores them, y-up: a post-table underline position below the baseline is negative, an OS/2 strikeout position above it is positive. gpui adds the fragment offsets to baseline_y in y-down space, so every underline was painted through the letters and every strikethrough under them. Flip both offsets where the Parley text system builds paint fragments. --- crates/gpui_ce_parley/src/text_system.rs | 38 ++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/crates/gpui_ce_parley/src/text_system.rs b/crates/gpui_ce_parley/src/text_system.rs index f6689900c9..f8a9296730 100644 --- a/crates/gpui_ce_parley/src/text_system.rs +++ b/crates/gpui_ce_parley/src/text_system.rs @@ -2164,8 +2164,11 @@ impl ParleyTextSystem { glyphs, x_range: start..start + px(glyph_run.advance()), style: PaintStyle::from(&source_runs[source_run]), - underline_offset: Some(px(run_metrics.underline_offset)), - strikethrough_offset: Some(px(run_metrics.strikethrough_offset)), + // Parley reports font metrics y-up (an underline below the + // baseline is negative); gpui adds these to `baseline_y` in + // y-down space, so flip them. + underline_offset: Some(px(-run_metrics.underline_offset)), + strikethrough_offset: Some(px(-run_metrics.strikethrough_offset)), }); } @@ -2598,6 +2601,37 @@ mod tests { }) } + #[test] + fn decoration_offsets_are_y_down_from_the_baseline() { + let system = test_system(); + for family in [IBM_PLEX.family, SOURCE_SERIF.family, LILEX.family] { + let text = "Learn how"; + let layout = layout_line(&system, text, px(16.0), &[text_run(text, family)]); + let fragment = &layout.paint_fragments[0]; + let underline = fragment.underline_offset.unwrap(); + let strikethrough = fragment.strikethrough_offset.unwrap(); + + // gpui paints at `baseline_y + offset` with y growing downward: the + // underline sits below the baseline, within the descent, and the + // strikethrough sits above it, within the ascent. + assert!(underline > px(0.0), "{family}: underline={underline:?}"); + assert!( + underline <= layout.descent, + "{family}: underline={underline:?}, descent={:?}", + layout.descent + ); + assert!( + strikethrough < px(0.0), + "{family}: strikethrough={strikethrough:?}" + ); + assert!( + -strikethrough < layout.ascent, + "{family}: strikethrough={strikethrough:?}, ascent={:?}", + layout.ascent + ); + } + } + #[test] fn explicit_rtl_sets_the_base_direction_without_rtl_characters() { let system = test_system();