diff --git a/crates/text/src/layout.rs b/crates/text/src/layout.rs index a0f3878f..50fbed58 100644 --- a/crates/text/src/layout.rs +++ b/crates/text/src/layout.rs @@ -682,7 +682,8 @@ pub fn layout_rich_uncached(text: &str, style: &TextStyle, runs: &[StyleRun], pa lay.underline_runs.push(*si); } } else if style.underline && pl.content > 0.0 { - lay.underlines.push([x, baseline + pm.underline_pos, x + pl.content, baseline + pm.underline_pos + pm.underline_thickness]); + let y = baseline - style.baseline_shift + pm.underline_pos; + lay.underlines.push([x, y, x + pl.content, y + pm.underline_thickness]); lay.underline_runs.push(0); } lay.lines.push(Line { diff --git a/crates/text/tests/underline_shift.rs b/crates/text/tests/underline_shift.rs new file mode 100644 index 00000000..8cb5e4e3 --- /dev/null +++ b/crates/text/tests/underline_shift.rs @@ -0,0 +1,25 @@ +//! Whole-style underline must follow `baseline_shift` the same way character-style runs do. +use filmcraft_text::{ParagraphStyle, StyleRun, TextStyle, layout, layout_rich}; + +fn style(shift: f32) -> TextStyle { + TextStyle { size: 100.0, underline: true, baseline_shift: shift, ..Default::default() } +} + +fn underline_y(shift: f32) -> (f32, f32) { + let para = ParagraphStyle::default(); + let s = style(shift); + let plain = layout("Hi", &s, ¶); + let rich = layout_rich("Hi", &s, &[StyleRun { range: 0..2, style: s.clone() }], ¶); + (plain.underlines[0][1], rich.underlines[0][1]) +} + +#[test] +fn whole_style_underline_follows_baseline_shift() { + let (p0, r0) = underline_y(0.0); + for shift in [40.0, -40.0] { + let (plain, rich) = underline_y(shift); + assert!((plain - rich).abs() < 1e-3, "plain {plain} vs rich {rich} at shift {shift}"); + assert!((plain - (p0 - shift)).abs() < 1e-3, "plain underline y {plain} should move with shift {shift}"); + assert!((rich - (r0 - shift)).abs() < 1e-3, "rich underline y {rich} should move with shift {shift}"); + } +}