Browse / github.com/storytold/wordcraft / patch
Layout: a right tab past the right indent takes its text up to the stop, as in Word
Layout: a right tab past the right indent wraps its page number although Word keeps it on the line Word's TOC styles give an entry a right indent and a right tab with a dot leader at the margin, past that indent. Word keeps the leader and the page number on the entry's line whenever the number fits between the text and the stop, so the number ends past the indent. WordCraft moves the tab and the number to the next line in most of these cases. What I measured in Word for Mac 16.93 (Letter, 1in margins, Times New Roman 12pt, right indent 1cm = 28.35pt, right tab with dot leader at 468pt, the number "104"): | Text ends short of the indent by | Word | WordCraft | |---|---|---| | 0.4, 1, 2, 3,
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- dfgodvan (imported from pull request #4614, open)
- Base
4b8b06fe03f93c9fc81783ee3e5b01977be89efc- Size
- +62 −4 in 3 files
- Review
- pending
- Shared
- 2026-10-28
- Id
66da6a43e35f7fa6295b77f8
How it went
No other operator has reported using it yet.
Diff
raw · crates/layout/src/para/lines.rs crates/layout/src/tests.rs docs/layout-parity.md
@@ -221,6 +221,11 @@ pub(super) fn break_lines(pl: &mut ParaLayout, env: &ParaEnv, mark_style: u32, l x += w; } _ => {+ // Its room grows from nothing with its text (a tab moved on from the+ // line before may still hold the width it had there).+ if let Some(c) = pl.clusters.get_mut(j) {+ c.adv = 0.0;+ } pending_tab = Some((j, stop, x)); if stop.leader != TabLeader::None { leaders.push((j, stop.leader));@@ -242,11 +247,14 @@ pub(super) fn break_lines(pl: &mut ParaLayout, env: &ParaEnv, mark_style: u32, l // A grapheme can have several drawing segments; fit it as one unit while keeping // each segment's font, glyphs and position for painting. let advance = pl.atomic_advance(j);- // Text after a right/centre tab grows leftwards into the tab's space first.+ // Text after a right/centre tab grows leftwards into the tab's space first. Up to a+ // right or decimal stop it fits even past the right indent (Word: a TOC entry whose+ // text ends less than a digit short of the indent keeps its page number on its line+ // when the stop is at the margin); unshifted, the text after the tab ends at `x - room`. let absorbs = pending_tab.is_some_and(|(tj, stop, _)| { let room = pl.clusters.get(tj).map(|t| t.adv).unwrap_or(0.0); match stop.align {- TabAlign::Right | TabAlign::Decimal => room >= advance,+ TabAlign::Right | TabAlign::Decimal => room >= advance || x - room + advance <= stop.pos + 0.01, TabAlign::Center => room >= advance / 2.0 && x + advance / 2.0 <= right_edge + 0.01, _ => false, }@@ -323,7 +331,9 @@ pub(super) fn break_lines(pl: &mut ParaLayout, env: &ParaEnv, mark_style: u32, l } // Decimal/center/right tab: shift pending text as it grows. if let Some((tj, stop, tx)) = pending_tab {- let seg_w = x - tx;+ // `x` includes the room the tab has so far.+ let old = pl.clusters.get(tj).map(|c| c.adv).unwrap_or(0.0);+ let seg_w = x - tx - old; let shift = match stop.align { TabAlign::Right => stop.pos - tx - seg_w, TabAlign::Center => stop.pos - tx - seg_w / 2.0,@@ -348,7 +358,6 @@ pub(super) fn break_lines(pl: &mut ParaLayout, env: &ParaEnv, mark_style: u32, l _ => 0.0, }; let shift = shift.max(0.0);- let old = pl.clusters.get(tj).map(|c| c.adv).unwrap_or(0.0); if (shift - old).abs() > 0.001 { let delta = shift - old; if let Some(c) = pl.clusters.get_mut(tj) {@@ -1608,6 +1608,54 @@ fn right_tab_whose_text_does_not_fit_moves_to_the_next_line_with_it() { } } +/// A TOC entry with a right indent and its right tab at the margin, past the indent (Word's TOC+/// styles): the page number may end past the indent, up to the stop. Like Word, the number stays+/// on the text's line whenever it fits between the text and the stop, also when the text ends+/// less than a digit short of the indent; when it doesn't fit there, the tab and the number go+/// to the next line as before.+#[test]+fn right_tab_past_the_right_indent_takes_its_number_up_to_the_stop() {+ use wordcraft_doc::props::{TabAlign, TabLeader, TabStop};+ let stop = 72.0 + 468.0;+ let mut close = 0;+ for indent in [28.35f32, 6.0] {+ let edge = stop - indent;+ for m in 20..40 {+ for r in 2..7 {+ let text = format!("{}{}\t104", "abc ".repeat(m), "y".repeat(r));+ let n = text.chars().count();+ let tab = n - 4;+ let mut d = Document::from_text(&text);+ d.format_paragraphs(&Pos::body(0, 0), &Pos::body(0, 0), &|p| {+ p.indent_right = Some(indent);+ p.tabs = Some(vec![TabStop { pos: 468.0, align: TabAlign::Right, leader: TabLeader::Dot }]);+ })+ .unwrap();+ let l = lay(&d);+ let Some(para) = l.pages[0].items.iter().find_map(|i| if let Placed::Lines { para, .. } = i { Some(para.clone()) } else { None })+ else {+ panic!("no lines")+ };+ let line_of = |k: usize| para.lines.iter().position(|l| l.c0 <= k && k < l.c1).unwrap();+ // Where the text ends and how wide the number is (the text's last letter and the number+ // are each on one line; the last two letters are both a "y").+ let last = l.caret(&Pos::body(0, tab - 1)).unwrap().x;+ let text_end = last + (last - l.caret(&Pos::body(0, tab - 2)).unwrap().x);+ let end = l.caret(&Pos::body(0, n)).unwrap().x;+ let number = end - l.caret(&Pos::body(0, n - 3)).unwrap().x;+ assert!((end - stop).abs() < 0.5, "indent {indent}, {m} words + {r}: number ends at {end}");+ assert_eq!(line_of(tab), line_of(n - 1), "indent {indent}, {m} words + {r}: the tab and its number are on different lines");+ let same = line_of(tab) == line_of(tab - 1);+ assert_eq!(same, text_end + number <= stop + 0.01, "indent {indent}, {m} words + {r}: text ends at {text_end}, number {number} wide");+ if same && text_end > edge - number / 3.0 {+ close += 1;+ }+ }+ }+ }+ assert!(close > 0, "no entry whose text ends within a digit of the indent");+}+ /// A left tab whose only stop is past the right indent wraps; on the next line it still can't /// reach that stop, so it takes no room there and its text starts at the indent instead of /// going to a line of its own.@@ -287,6 +287,7 @@ layout suite are pending; this is not a new Word/native-IME acceptance claim. | 2026-10-23 | trivial | Continuous sections running off a page go onto the next; empty marks after a section mark take a line until the page is full, measured in Word (#3907) | | 2026-10-27 | trivial | Empty marks past the page bottom draw nothing, not even a list label; an empty mark after a text paragraph closing a section stays quiet and, like any mark taking no room, draws no bullet (#3907 review) | | 2026-10-27 | trivial | A kept-together paragraph in a continuous section is measured against the next page's body, so a tall different first-page header no longer splits one that fits on page 2 (#3907 CodeRabbit) |+| 2026-10-27 | trivial | A right or decimal tab past the right indent (a TOC entry's page number at the margin) keeps its text on the line while it ends before the stop, also when the text before the tab ends less than a digit short of the indent; measured in Word for Mac 16.93. `layout::tests::right_tab_past_the_right_indent_takes_its_number_up_to_the_stop` | | 2026-10-20 | trivial | A click below a floating table, outside its cells, goes to the paragraph beside or after it instead of a cell of its last row, as checked in Word (#2038) | | 2026-10-19 | trivial | A right, centre or decimal tab whose text doesn't fit, or a left tab past the right indent, starts the next line with its text instead of ending the line (a TOC entry's page number keeps its leader); a left tab still past the right indent there takes no room (#1840) | | 2026-10-19 | trivial | Justified lines (compatibility mode 15) shrink their spaces to fit one more word by at most a quarter of the spaces' width (was a fifth) and by at most 0.355 of that word's own width with the space before it. Measured in Word for Mac with synthetic justified paragraphs: words of 2.7 to 80 pt, spaces of 3.3 and 6.7 pt, only justified text, last lines included, never in modes 12 and 14. A real paper's justified abstract now breaks like Word on all 23 lines (3 before); on 180 real-world papers, page counts matching Word went from 139 to 142, none further |