diff --git a/ROADMAP.md b/ROADMAP.md index 4787271d1..9913c5287 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -146,6 +146,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | 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-12 | `.doc` import: tab stops (centred titles, right-aligned dates, leaders) and sections ending at their section marks (continuous breaks, footers); EPA .doc templates now paginate like Word | | 2026-10-12 | DOCX headings numbered like Word when a heading style gives only a list level (6.4.5, not 6.15) | +| 2026-10-12 | A list label makes its line taller above the baseline only, as in Word (6 cases measured in Word for Windows): Symbol bullets no longer add their deeper descent, so bulleted lists keep Word's height and page breaks | | 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 dd98ff797..1a33d2eae 100644 --- a/crates/layout/src/para.rs +++ b/crates/layout/src/para.rs @@ -1813,10 +1813,12 @@ fn break_lines(pl: &mut ParaLayout, env: &ParaEnv, mark_style: u16, level: Optio asc = st.ascent; desc = st.descent; } + // A list label can make the line taller above the baseline, never below it: Word keeps + // the text's descent (measured in Word for Windows: a Symbol bullet's taller ascent + // counts, a Courier New label's deeper descent doesn't). if first && let Some(lab) = &pl.label { for st in lab.runs.iter().filter_map(|r| pl.styles.get(r.style as usize)) { asc = asc.max(st.ascent); - desc = desc.max(st.descent); } } // Line spacing scales the text's height, never a picture's: a line is at least as tall diff --git a/crates/layout/src/tests.rs b/crates/layout/src/tests.rs index 9f209a27b..cd891bb74 100644 --- a/crates/layout/src/tests.rs +++ b/crates/layout/src/tests.rs @@ -4177,3 +4177,34 @@ fn spaces_and_tabs_do_not_make_a_line_taller() { 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"); } + +/// A list label taller than its text makes the first line taller above the baseline only: Word +/// keeps the text's descent below it (measured in Word for Windows, `plan/word` R18: a Symbol +/// bullet's taller ascent counts, a Courier New label's deeper descent doesn't). NIH's bulleted +/// items each came out a fifth of a point taller, enough to push a paragraph's last lines over +/// the page bottom. +#[test] +fn a_list_label_adds_ascent_but_not_descent() { + let first_line = |label_size: Option| { + let mut d = Document::new(); + let num = d.numbering.add_list(wordcraft_doc::ListKind::Numbered); + for a in &mut d.numbering.abstracts { + for l in &mut a.levels { + l.chr.size = label_size; + } + } + let ten = wordcraft_doc::props::CharProps { size: Some(10.0), ..Default::default() }; + let mut p = wordcraft_doc::Paragraph::with_text("Item", ten.clone()); + p.props.numbering = Some(wordcraft_doc::props::NumRef { num, level: 0 }); + p.mark = ten; + p.props.line_spacing = Some(wordcraft_doc::props::LineSpacing::Multiple(1.0)); + d.body = vec![wordcraft_doc::para_block(p)]; + let l = lay(&d); + l.pages[0].items.iter().find_map(|it| if let Placed::Lines { para, .. } = it { para.lines.first().cloned() } else { None }).unwrap() + }; + let plain = first_line(None); + let big = first_line(Some(30.0)); + let below = |l: &crate::para::Line| l.top + l.height - l.baseline; + assert!(big.baseline - big.top > plain.baseline - plain.top + 5.0, "the big label raises the baseline"); + assert!((below(&big) - below(&plain)).abs() < 0.01, "the text's descent: {} vs {}", below(&big), below(&plain)); +} diff --git a/docs/layout-parity.md b/docs/layout-parity.md index 86e04d7f9..b94484989 100644 --- a/docs/layout-parity.md +++ b/docs/layout-parity.md @@ -76,6 +76,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-12 | trivial | A list label's font raises the first line's ascent but never its descent (Word keeps the text's descent below the baseline) | | 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) |