diff --git a/ROADMAP.md b/ROADMAP.md index 012013d..54c5b00 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -142,6 +142,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | 2026-10-12 | PDF export of long documents with many internal links (a linked table of contents) is linear: NIH's 67-page protocol template 10.6 s → 0.9 s | | 2026-10-12 | Keytips: Word for Windows' tab letters (File F, Design G, References S, Review R, View W, Help Y) | | 2026-10-12 | Table-of-contents links look like Word's: plain text, not blue and underlined (Word ignores the Hyperlink style on TOC entry links; measured in 9 cases) | +| 2026-10-12 | Line heights like Word's: spaces and tabs never make a line taller (13 cases measured in Word for Windows); tables of contents with big tab runs were a third taller | | 2026-10-11 | Hide White Space (#645): `view.hideWhiteSpace` (View › Show checkbox, or double-click the gap between pages) collapses margins, headers and footers between pages to a thin line in Print Layout; screen only, page count unchanged | | 2026-10-11 | Fonts: Palatino Linotype and Book Antiqua fall back to Palatino (macOS) or TeX Gyre Pagella / P052 / URW Palladio (Linux) before generic serif (#670) | |---|---| diff --git a/crates/layout/src/para.rs b/crates/layout/src/para.rs index 11aba54..dd98ff7 100644 --- a/crates/layout/src/para.rs +++ b/crates/layout/src/para.rs @@ -1781,7 +1781,10 @@ fn break_lines(pl: &mut ParaLayout, env: &ParaEnv, mark_style: u16, level: Optio let mut any = false; let dropped = pl.drop_cap.map_or(0, |d| d.0); for (k, c) in pl.clusters.get(c0..c1).into_iter().flatten().enumerate() { - if c.kind == ClKind::Marker || c0 + k < dropped { + // Spaces and tabs never make a line taller, whatever their size: Word sizes a line by + // its other characters (a line of only them takes the paragraph mark's), measured in + // Word for Windows with 9 pt text and 11–20 pt tabs, spaces and ideographic spaces. + if matches!(c.kind, ClKind::Marker | ClKind::Space | ClKind::Tab) || c0 + k < dropped { continue; } if let Some(st) = pl.styles.get(c.style as usize) { diff --git a/crates/layout/src/tests.rs b/crates/layout/src/tests.rs index cd10dd9..9f209a2 100644 --- a/crates/layout/src/tests.rs +++ b/crates/layout/src/tests.rs @@ -4143,3 +4143,37 @@ fn bulgarian_text_is_hyphenated_by_its_own_rules() { assert!(wordcraft_proof::hyphen::hyphen_points_in(&word, &lim, wordcraft_proof::ProofLang::Bg).contains(&at), "{head}-{tail}"); } } + +/// Spaces and tabs never make a line taller in Word, whatever their size (measured in Word for +/// Windows: 9 pt text with 11–20 pt tabs, spaces and ideographic spaces kept the 9 pt line; a +/// line of only big spaces took the paragraph mark's height; a big letter or no-break space did +/// grow it). Tables of contents with big tab runs were a third taller than Word's. +#[test] +fn spaces_and_tabs_do_not_make_a_line_taller() { + use wordcraft_doc::props::CharProps; + let small = CharProps { size: Some(9.0), ..Default::default() }; + let big = CharProps { size: Some(20.0), ..Default::default() }; + let pitch = |middle: &str, middle_props: &CharProps| { + let mut d = Document::new(); + d.body = (0..3) + .map(|i| { + let mut p = wordcraft_doc::Paragraph::with_text(&format!("L{i}A"), small.clone()); + let n = p.len(); + p.insert_text(n, middle, middle_props).unwrap(); + let n = p.len(); + p.insert_text(n, "B", &small).unwrap(); + p.props = ParaProps { space_before: Some(0.0), space_after: Some(0.0), ..Default::default() }; + p.mark = small.clone(); + wordcraft_doc::para_block(p) + }) + .collect(); + let l = lay(&d); + l.caret(&Pos::body(1, 0)).unwrap().top - l.caret(&Pos::body(0, 0)).unwrap().top + }; + let plain = pitch(" ", &small); + for (what, middle) in [("a tab", "\t"), ("a space", " "), ("two spaces", " "), ("an ideographic space", "\u{3000}")] { + assert!((pitch(middle, &big) - plain).abs() < 0.01, "{what} at 20 pt: {} vs {plain}", pitch(middle, &big)); + } + assert!(pitch("x", &big) > plain + 5.0, "a 20 pt letter does make it taller"); + assert!(pitch("\u{A0}", &big) > plain + 5.0, "so does a no-break space"); +} diff --git a/docs/layout-parity.md b/docs/layout-parity.md index 42401d4..1dd4064 100644 --- a/docs/layout-parity.md +++ b/docs/layout-parity.md @@ -75,6 +75,7 @@ font can't match Word's metrics. | Date | Change | Summary | |---|---|---| +| 2026-10-12 | trivial | Spaces, tabs and ideographic spaces don't count toward a line's height, as in Word (a line of only them takes the paragraph mark's) | | 2026-10-11 | trivial | Chinese documents (#534): indents in characters, a page-anchored floating table after a full page moves to the next page, no blank page from a page break at the bottom of a full page | | 2026-10-11 | trivial | East Asian list number formats (一、二、三, 第一章, ①); list labels whose characters come from two fonts draw each part with its own font (the digits of "1、" no longer show as other letters); a list level without indents leaves the label at the paragraph edge (#534) | | 2026-10-11 | trivial | Vertical sections: `w:textDirection` in `w:sectPr`, upright East Asian glyphs (#491) |