Type: a tab to a default or left stop reads none of the text after it

tab_advance read the text after a tab up to the next tab for every
stop, though only right, center and decimal stops align it. The first
tab of a long paragraph read the paragraph to its end, even on a line
that stops reading a bounded number of breaks past its overflow. It
now reads that text only when it reaches a stop that aligns it.
This commit is contained in:
dotCooCoo
2026-11-14 12:50:18 -08:00
parent 1a6000662c
commit 2b781525bb
2 changed files with 42 additions and 12 deletions
+24 -12
View File
@@ -214,18 +214,30 @@ pub(super) fn tab_advance<'t, 'g>(
) -> (f64, &'t str) {
use vectorcraft_doc::TabAlign;
let rel = x - origin;
let seg: Vec<&SGlyph> = rest.into_iter().take_while(|g| g.ch != '\t' && g.ch != '\u{2028}').collect();
let width: f64 = seg.iter().map(|g| g.adv).sum();
let target = |pos: f64, align: TabAlign, on: char| match align {
TabAlign::Left => pos,
TabAlign::Right => pos - width,
TabAlign::Center => pos - width / 2.0,
TabAlign::Decimal => pos - seg.iter().take_while(|g| g.ch != on).map(|g| g.adv).sum::<f64>(),
};
let explicit = tabs.iter().find_map(|t| {
let at = target(t.position, t.align, t.align_on);
(at > rel + 1e-6).then_some((at, t.leader.as_str()))
});
// The text after the tab is read only when a right, center or decimal stop is reached, since
// only those align it. A tab to a default or left stop reads none of it.
let mut rest = Some(rest);
let mut seg: Vec<&SGlyph> = Vec::new();
let mut width = 0.0;
let mut explicit = None;
for t in tabs {
if t.align != TabAlign::Left
&& let Some(r) = rest.take()
{
seg = r.into_iter().take_while(|g| g.ch != '\t' && g.ch != '\u{2028}').collect();
width = seg.iter().map(|g| g.adv).sum();
}
let at = match t.align {
TabAlign::Left => t.position,
TabAlign::Right => t.position - width,
TabAlign::Center => t.position - width / 2.0,
TabAlign::Decimal => t.position - seg.iter().take_while(|g| g.ch != t.align_on).map(|g| g.adv).sum::<f64>(),
};
if at > rel + 1e-6 {
explicit = Some((at, t.leader.as_str()));
break;
}
}
let (at, leader) = explicit.unwrap_or_else(|| {
let last = tabs.iter().map(|t| t.position).fold(0.0, f64::max).max(0.0);
let base = rel.max(last);
+18
View File
@@ -745,6 +745,24 @@ fn a_tabbed_line_stepping_back_ends_at_a_final_soft_hyphen_without_a_hyphen() {
assert_eq!(ended, (len, false));
}
/// A tab to a default or left stop does not read the text after it, which only right, center and
/// decimal stops align: the first tab of a long paragraph does not read the paragraph to its end.
#[test]
fn a_tab_to_a_default_or_left_stop_reads_no_text_after_it() {
use vectorcraft_doc::{TabAlign, TabStop};
let (_, glyphs) = stepping_back_tabbed_line(2000);
let read = std::cell::Cell::new(0usize);
let rest = || glyphs[2..].iter().inspect(|_| read.set(read.get() + 1));
let left = [TabStop { position: 100.0, align: TabAlign::Left, leader: String::new(), align_on: '.' }];
assert_eq!(tab_advance(&[], 0.0, 1.0, rest()).0, 35.0);
assert_eq!(tab_advance(&left, 0.0, 1.0, rest()).0, 99.0);
assert_eq!(read.get(), 0, "read {} glyphs after the tab", read.get());
// A right stop reads up to the next tab and aligns that text on the stop.
let right = [TabStop { position: 100.0, align: TabAlign::Right, leader: String::new(), align_on: '.' }];
let short = synthetic_glyphs(&[(0, 1, 2.0, 'b'), (1, 1, 3.0, 'b'), (2, 1, 0.0, '\t'), (3, 1, 7.0, 'b')]);
assert_eq!(tab_advance(&right, 0.0, 1.0, short.iter()).0, 94.0);
}
/// A long tabbed line with a glyph that steps back ends at the latest break that fits as drawn, even
/// where a later break than one that does not fit fits again.
#[test]