mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-29 21:07:24 -06:00
Tables: a row with less room than its minimum height moves on whole (#1618)
* Tables: a row with less room than its minimum height moves on whole Measured in Word with about 49 pt left on a page: a table row of eight lines splits there, but the same row with an at-least height of 63.6 pt goes to the next page whole, and so does one holding a nested table. Without the height both split, as before. WordCraft split them all, so a report's title row (a logo beside a small nested table) started at the foot of its cover page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tables: preserve splitting for oversized minimum-height rows --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
Brandon Thomas
parent
fee9b984eb
commit
1b60773196
@@ -3630,7 +3630,15 @@ fn place_table(ctx: &mut Ctx, pb: &mut PageBuilder, t: &wordcraft_doc::Table, bl
|
||||
if pb.y + cur.height <= pb.bottom + 0.01 {
|
||||
break;
|
||||
}
|
||||
let split = if splittable { table::split_row(cur, pb.bottom - pb.y) } else { None };
|
||||
// Measured in Word: a row with an at-least height doesn't split where less than that
|
||||
// height is left; it starts the next page whole.
|
||||
let room = pb.bottom - pb.y;
|
||||
let short_of_min = rest.is_none()
|
||||
&& t.rows.get(ri).is_some_and(|r| {
|
||||
r.props.height_rule == wordcraft_doc::props::HeightRule::AtLeast
|
||||
&& r.props.height.is_some_and(|h| h > room + 0.01 && h <= pb.bottom - body_top + 0.01)
|
||||
});
|
||||
let split = if splittable && !short_of_min { table::split_row(cur, room) } else { None };
|
||||
match split {
|
||||
Some((a, b)) => {
|
||||
place(pb, &a);
|
||||
|
||||
@@ -2160,7 +2160,8 @@ fn split_row_cells_each_continue_at_the_top() {
|
||||
}
|
||||
|
||||
/// Word never splits a row of an exact height: one the page bottom cuts through, its text
|
||||
/// included, moves to the next page whole (an "at least" row of that height splits).
|
||||
/// included, moves to the next page whole. An at-least row also moves when the remaining
|
||||
/// room is less than its minimum height.
|
||||
#[test]
|
||||
fn exact_rows_move_on_whole() {
|
||||
let text = "Dotted leader lines to fill in by hand later on. ".repeat(14);
|
||||
@@ -2193,7 +2194,8 @@ fn exact_rows_move_on_whole() {
|
||||
assert_eq!(exact.len(), 2);
|
||||
assert_eq!(exact[0], 0, "the exact row moves on whole: {exact:?}");
|
||||
let at_least = lay_row(wordcraft_doc::props::HeightRule::AtLeast);
|
||||
assert!(at_least[0] > 0 && at_least.get(1).is_some_and(|n| *n > 0), "an at-least row splits: {at_least:?}");
|
||||
assert_eq!(at_least[0], 0, "less than the minimum height remains: {at_least:?}");
|
||||
assert!(at_least.get(1).is_some_and(|n| *n > 0), "the at-least row moves: {at_least:?}");
|
||||
}
|
||||
|
||||
/// A nested table's cell the page bottom cuts through goes on at the top of the next page with
|
||||
@@ -4145,6 +4147,30 @@ fn autofit_handles_spanning_cells_long_words_and_empty_tables() {
|
||||
assert!(x > 72.0 + 200.0, "the second 50% column starts at {x}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_row_with_less_room_than_its_minimum_height_moves_on_whole() {
|
||||
use wordcraft_doc::props::HeightRule;
|
||||
// Measured in Word: with 49 pt left on the page, a row of eight lines splits, but the same row
|
||||
// with an at-least height of 63.6 pt goes to the next page whole.
|
||||
let first_row_line_page = |min: Option<f32>| {
|
||||
let lines: Vec<String> = (1..=200).map(|i| format!("Filler {i}")).collect();
|
||||
let one_page = lay(&Document::from_text(&lines.join("\n"))).pages[0].items.iter().filter(|i| matches!(i, Placed::Lines { .. })).count();
|
||||
let mut t = Table::new(1, 1, 400.0);
|
||||
t.rows[0].props.height = min;
|
||||
t.rows[0].props.height_rule = HeightRule::AtLeast;
|
||||
t.rows[0].cells[0].blocks =
|
||||
(1..=8).map(|k| wordcraft_doc::para_block(wordcraft_doc::Paragraph::with_text(&format!("Row line {k}"), Default::default()))).collect();
|
||||
// Fill the page, leaving two filler paragraphs' room (less than 63.6 pt) for the row.
|
||||
let mut d = Document::from_text(&lines[..one_page - 2].join("\n"));
|
||||
let end = d.body.len();
|
||||
d.insert_block(StoryRef::Body, &Path::top(end), wordcraft_doc::Block::Table(t)).unwrap();
|
||||
let l = lay(&d);
|
||||
l.pages.iter().position(|p| p.items.iter().any(|i| matches!(i, Placed::Lines { path, .. } if path.0.first() == Some(&(end as u32)))))
|
||||
};
|
||||
assert_eq!(first_row_line_page(None), Some(0), "a row without a minimum height splits");
|
||||
assert_eq!(first_row_line_page(Some(63.6)), Some(1), "a row with a minimum height that doesn't fit moves on");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_overflowing_an_exact_row_is_clipped_to_it() {
|
||||
use wordcraft_doc::props::HeightRule;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Layout and pagination parity with Microsoft Word
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** trivial (text past an exact-height row is cut off; previously trivial: a list number stays with text after a leading page break; previously trivial: a bookmark after a page break takes no line; previously trivial: a page break followed by a page break before or a next-page section starts one page; previously trivial: All Caps and Small Caps draw ß as SS, #1581; previously trivial: justified vertical page alignment, #1320; previously trivial: page numbers restart at 0; previously trivial: a list label moves with its first line when the paragraph is centred or end-aligned (#1159); previously trivial: PAGE fields in a paragraph split across pages show the page they are on, #1352; previously trivial: a dragged floating object anchors in the column it lands in, #884; previously trivial: a paragraph ending on a page with footnotes keeps its space after above the separator, #1116; previously trivial: symbol-font text drawn as Unicode symbols, #776; previously trivial: large-table scrolling measured and fixed, #811; previously trivial: text boxes use their own margins; Resize shape to fit text never cuts text, #743; previously trivial: complex-script shaping and Myanmar line breaking, #604; previously trivial: body page totals, table-cell footnotes and bookmark link pages fixed, #450 #451 #455; previously major: first version; line breaking, pagination and object placement checked against Word's behaviour from the layout source on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-14 · **Change:** trivial (a fitting row with less room than its minimum height moves on whole; oversized rows still split; previously trivial: text past an exact-height row is cut off; previously trivial: a list number stays with text after a leading page break; previously trivial: a bookmark after a page break takes no line; previously trivial: a page break followed by a page break before or a next-page section starts one page; previously trivial: All Caps and Small Caps draw ß as SS, #1581; previously trivial: justified vertical page alignment, #1320; previously trivial: page numbers restart at 0; previously trivial: a list label moves with its first line when the paragraph is centred or end-aligned (#1159); previously trivial: PAGE fields in a paragraph split across pages show the page they are on, #1352; previously trivial: a dragged floating object anchors in the column it lands in, #884; previously trivial: a paragraph ending on a page with footnotes keeps its space after above the separator, #1116; previously trivial: symbol-font text drawn as Unicode symbols, #776; previously trivial: large-table scrolling measured and fixed, #811; previously trivial: text boxes use their own margins; Resize shape to fit text never cuts text, #743; previously trivial: complex-script shaping and Myanmar line breaking, #604; previously trivial: body page totals, table-cell footnotes and bookmark link pages fixed, #450 #451 #455; previously major: first version; line breaking, pagination and object placement checked against Word's behaviour from the layout source on origin/main) · **Target:** Microsoft Word (Microsoft 365) for Mac 16.113.4
|
||||
|
||||
A document only "opens correctly" if its pages break where Word breaks them: the same lines, the
|
||||
same page count, footnotes and pictures on the same pages. This checklist covers line breaking,
|
||||
@@ -159,6 +159,7 @@ No overall parity estimate changes from these probes.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-14 | trivial | A table row with an at-least height doesn't split where less than that height is left on the page; it starts the next page whole, as measured in Word |
|
||||
| 2026-10-14 | trivial | Text that doesn't fit an exact-height table row is cut off at the cell's edge, as in Word, instead of drawn over the rows below |
|
||||
| 2026-10-14 | trivial | A numbered paragraph whose text opens with a page break draws its number on the next page with the text, as Word does |
|
||||
| 2026-10-14 | trivial | A bookmark (or other zero-width marker) after a paragraph's closing page break stays with the break instead of taking a line at the top of the next page |
|
||||
|
||||
Reference in New Issue
Block a user