diff --git a/crates/text/src/layout.rs b/crates/text/src/layout.rs index 0559730e2..5b0f200f9 100644 --- a/crates/text/src/layout.rs +++ b/crates/text/src/layout.rs @@ -1837,6 +1837,9 @@ fn flow(cx: &mut Ctx<'_>, paras: &[Range], t: &TextObject, regions: Optio let mut x_end = start_x; // Tab stops are measured from the frame's left edge (point type: the origin). let tab_origin = if regions.is_some() { x0 } else { 0.0 }; + // A line with tabs is measured as if it started at the left (see `set_width`), then + // aligned as a whole: its tabs stop where they were measured, however it is aligned. + let tab_shift = start_x - if regions.is_some() { ax0 } else { ind_l }; let line_start = sg.get(i).map_or(pr.start, |g| g.byte); let line_end = sg.get(end).map_or(pr.end, |g| g.byte); let order = visual_order(bidi.as_ref().filter(|_| !cx.vertical), pr.start, line_start..line_end, &sg[i..end]); @@ -1854,7 +1857,7 @@ fn flow(cx: &mut Ctx<'_>, paras: &[Range], t: &TextObject, regions: Optio } let mut leader = ""; if g.ch == '\t' { - (adv, leader) = tab_advance(¶.tabs, tab_origin, x, &sg[j + 1..trimmed.max(j + 1)]); + (adv, leader) = tab_advance(¶.tabs, tab_origin, x - tab_shift, &sg[j + 1..trimmed.max(j + 1)]); } else if j < body { if g.is_space() { adv += per_space; diff --git a/crates/text/src/tests.rs b/crates/text/src/tests.rs index 41718cf3d..03a0ec18b 100644 --- a/crates/text/src/tests.rs +++ b/crates/text/src/tests.rs @@ -501,6 +501,24 @@ fn a_leading_tab_takes_its_width_in_the_line_and_the_justification() { } } +#[test] +fn a_centred_or_right_aligned_line_sets_its_tabs_as_if_it_started_at_the_left() { + use vectorcraft_doc::{TabAlign, TabStop}; + // "AB" with a stop at 100 pt is 100 pt plus B wide whatever the alignment: the whole line + // moves, the gap between A and B stays 100 pt, and a right aligned line ends on the edge (#2457). + for justify in [Justify::Center, Justify::Right, Justify::JustifyCenter, Justify::JustifyRight] { + let mut t = area("A\tB", style(12.0), Rect::new(0.0, 0.0, 200.0, 100.0), justify); + t.para.tabs = vec![TabStop { position: 100.0, align: TabAlign::Left, leader: String::new(), align_on: '.' }]; + let lay = layout(db(), &t); + let at = |byte: usize| lay.glyphs.iter().find(|g| g.byte == byte).map(|g| g.origin.x).unwrap(); + assert!((at(2) - at(0) - 100.0).abs() < 1e-6, "{justify:?}: A at {}, B at {}", at(0), at(2)); + if matches!(justify, Justify::Right | Justify::JustifyRight) { + let b = lay.glyphs.iter().find(|g| g.byte == 2).unwrap(); + assert!((b.origin.x + b.advance - 200.0).abs() < 1e-6, "{justify:?}: the line ends at {}", b.origin.x + b.advance); + } + } +} + #[test] fn tab_leaders_line_up_from_line_to_line_and_stop_short_of_the_stop() { use vectorcraft_doc::{TabAlign, TabStop};