diff --git a/ROADMAP.md b/ROADMAP.md index e225021d9..19b09e77a 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -156,6 +156,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm | 2026-10-12 | Keep with next like Word (20 cases measured in Word for Windows): a kept paragraph moves to the next page only when it would fit on this one by itself, otherwise it breaks as usual; chains of kept paragraphs move together | | 2026-10-12 | An empty paragraph that only carries a continuous section break takes no room, as in Word (10 cases measured in Word for Windows): text after it follows right on and balanced columns don't count it | | 2026-10-12 | Floating objects positioned relative to their paragraph start above its space before, as in Word (10 cases measured in Word for Windows) | +| 2026-10-12 | Unequal columns: text after a column break is broken for the column it lands in (NIH's SARP form had a paragraph a word or two wide) | | 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/lib.rs b/crates/layout/src/lib.rs index c3ae083b8..dbe4244ce 100644 --- a/crates/layout/src/lib.rs +++ b/crates/layout/src/lib.rs @@ -2840,7 +2840,21 @@ fn place_para_in_flow(ctx: &mut Ctx, pb: &mut PageBuilder, p: &Paragraph, block: Some(LineEnd::PageBreak) => pb.new_page(block, body_top), Some(LineEnd::ColumnBreak) => { pb.col_break_page = Some(pb.pages.len().saturating_sub(1)); - pb.advance(block, body_top) + pb.advance(block, body_top); + // Unequal columns: the text after the break is broken for its own column's width + // (Word), not the width of the column it started in. + if (pb.col_w() - width).abs() > 0.5 + && let Some(resume) = pl.lines.get(l0).map(|l| l.start) + { + let y0 = pb.y; + let (rects, relaid) = anchor_floats(ctx, pb, p, y0, 0.0, excl_mark, &label); + if let Some(k) = relaid.lines.iter().position(|l| l.start == resume) { + (float_rects, pl) = (rects, relaid); + anchor_y = y0; + n = pl.lines.len(); + l0 = k; + } + } } _ if l0 < n => pb.advance(block, body_top), _ => {} diff --git a/crates/layout/src/tests.rs b/crates/layout/src/tests.rs index 514423d56..1becf2386 100644 --- a/crates/layout/src/tests.rs +++ b/crates/layout/src/tests.rs @@ -4483,3 +4483,21 @@ fn paragraph_relative_floats_start_above_the_space_before() { let (top, first_bottom) = shape_top(0.0, 10.0, 0.0); assert!((top - (first_bottom + 10.0)).abs() < 1.0, "below the previous paragraph's space after: {top} vs {first_bottom}"); } + +/// Unequal columns (a narrow label column and a wide text column, as in NIH's SARP form): text +/// after a column break is broken for the column it lands in. WordCraft broke it for the narrow +/// column it started in, so a paragraph took a dozen lines a word or two wide. +#[test] +fn text_after_a_column_break_takes_the_new_column_s_width() { + let mut d = Document::new(); + d.last_section.columns = wordcraft_doc::section::Columns { count: 2, space: 36.0, separator: false, widths: vec![(67.0, 52.0), (348.0, 0.0)] }; + let brk = wordcraft_doc::para::COLUMN_BREAK; + let text = format!("Label{brk}Provide a description of the planned recruitment methods including use of contact lists and databases."); + d.body = vec![wordcraft_doc::para_block(wordcraft_doc::Paragraph::with_text(&text, Default::default()))]; + let l = lay(&d); + let lines: Vec<(f32, usize)> = + l.pages[0].items.iter().filter_map(|it| if let Placed::Lines { x, l0, l1, .. } = it { Some((*x, l1 - l0)) } else { None }).collect(); + let (wide_x, wide_lines) = lines.last().copied().unwrap(); + assert!(wide_x > 72.0 + 67.0, "the text is in the second column: {lines:?}"); + assert!(wide_lines <= 3, "broken for the 348 pt column, not the 67 pt one: {lines:?}"); +} diff --git a/docs/layout-parity.md b/docs/layout-parity.md index b4ab12236..2571d079e 100644 --- a/docs/layout-parity.md +++ b/docs/layout-parity.md @@ -83,6 +83,7 @@ font can't match Word's metrics. | 2026-10-12 | trivial | Keep with next: a paragraph that doesn't fit on the page anyway breaks as usual (its last lines go over with the next paragraph) instead of moving whole; a chain of kept paragraphs moves together | | 2026-10-12 | trivial | Continuous section breaks: an empty paragraph carrying only the break takes no room (text after it follows on; balanced columns ignore it) | | 2026-10-12 | trivial | Floats relative to a paragraph (or its line) are measured from the paragraph's start above its space before | +| 2026-10-12 | trivial | Text after a column break in unequal columns is broken for its new column's width | | 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) |