diff --git a/crates/text/src/layout/breaking.rs b/crates/text/src/layout/breaking.rs index 4b89f79ae6..cfe5fa0eb6 100644 --- a/crates/text/src/layout/breaking.rs +++ b/crates/text/src/layout/breaking.rs @@ -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::(), - }; - 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::(), + }; + 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); diff --git a/crates/text/src/layout/wrapping_tests.rs b/crates/text/src/layout/wrapping_tests.rs index be089b4434..89d40386b5 100644 --- a/crates/text/src/layout/wrapping_tests.rs +++ b/crates/text/src/layout/wrapping_tests.rs @@ -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]