Layout: text after a column break takes its new column's width (#934)

Found in NIH's SARP form compared with Word for Windows: the form's
second section has a narrow label column (67 pt) and a wide text column
(438 pt), and each answer starts with a column break into the wide one.

Before: the paragraph's lines were broken once, for the column it
started in, so the text after the break ran a word or two wide down the
wide column. After: when a column break lands the rest of a paragraph
in a column of another width, that rest is broken again for it, as in
Word. Equal columns are unaffected (nothing is laid out twice).

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Brandon Thomas
2026-10-12 10:09:42 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 05e5ff0e0d
commit 97ade895d7
4 changed files with 35 additions and 1 deletions
+1
View File
@@ -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) |
|---|---|
+15 -1
View File
@@ -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),
_ => {}
+18
View File
@@ -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:?}");
}
+1
View File
@@ -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) |