Type: centre and right aligned lines set their tabs as if they started at the left (#2457)

A tab's width depends on where it falls from the tab origin. Line breaking and the line width measure it from the left edge, but a centred or right aligned line is placed from its own start, so its tabs stopped elsewhere. The line is now laid out with its tabs measured from the left and then aligned as a whole.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
vangel76
2026-10-17 13:20:26 +07:00
committed by bflatastic
co-authored by Claude Opus 5.5
parent 101fd55fa0
commit 285a3da81a
2 changed files with 22 additions and 1 deletions
+4 -1
View File
@@ -1837,6 +1837,9 @@ fn flow(cx: &mut Ctx<'_>, paras: &[Range<usize>], 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<usize>], t: &TextObject, regions: Optio
}
let mut leader = "";
if g.ch == '\t' {
(adv, leader) = tab_advance(&para.tabs, tab_origin, x, &sg[j + 1..trimmed.max(j + 1)]);
(adv, leader) = tab_advance(&para.tabs, tab_origin, x - tab_shift, &sg[j + 1..trimmed.max(j + 1)]);
} else if j < body {
if g.is_space() {
adv += per_space;
+18
View File
@@ -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};
// "A<tab>B" 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};