From 1dd86b0d0afb0de5aac03f7dca35211fcedcc524 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 20 Oct 2026 09:23:17 +0800 Subject: [PATCH] A floating picture in a table cell grows the row to hold it (#3108) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A row/cell's height was computed from the floating *tables* that grow it (float_tables -> floats_bottom in crates/layout/src/lib.rs), but a floating (anchored, non-inline wrap) picture or shape in a cell's paragraph wasn't tracked the same way. The row sized to the cell's text alone, so a picture taller than the text hung past the row's own bottom edge and overlapped whatever came after the table — reproduced with the reporter's own document and confirmed against their real Microsoft Office 365 screenshot, which lays the same file out with the table growing to fully contain the picture. - crates/layout/src/lib.rs: layout_box now tracks the deepest point any floating (Wrap != Inline) picture or shape reaches in a box, the same way it already tracks float_tables, and folds it into floats_bottom. - crates/layout/src/tests.rs: a_floating_picture_in_a_cell_grows_the_row_to_hold_it, mirroring the existing a_floating_table_in_a_cell_takes_no_room_and_the_cell_holds_it. cargo xtask ci: fmt, clippy, assets, layers and wasm all pass. Full workspace tests pass except the same 3 pre-existing, environment-only Thai-font failures already noted on #3119 and #3184 (no CRAFT_FONTS_DIR/TH Sarabun New in this build environment). Closes #3108. Co-Authored-By: Claude Sonnet 5 --- crates/layout/src/lib.rs | 12 +++++++++++- crates/layout/src/tests.rs | 21 +++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/crates/layout/src/lib.rs b/crates/layout/src/lib.rs index a60c5f94d..4d367e0ad 100644 --- a/crates/layout/src/lib.rs +++ b/crates/layout/src/lib.rs @@ -1178,6 +1178,9 @@ fn layout_box( let mut excl: Vec = Vec::new(); // Areas of the box's floating tables, which other floating tables may have to avoid. let mut float_tables: Vec = Vec::new(); + // Deepest a floating picture or shape reaches, so the box (a table cell especially) grows + // to hold it instead of letting it hang past the box's own bottom edge (#3108). + let mut float_objects_bottom = 0.0f32; let mut table_areas = Vec::new(); let mut page_table_wraps = Vec::new(); let mut y = 0.0f32; @@ -1282,6 +1285,13 @@ fn layout_box( if !p.objects.is_empty() { let at = ObjFrame { page: frame, col: (0.0, width), para_y: y, table_chr: table.map(|t| &t.chr), pinned: Some(&pinned) }; let (back, front) = place_objects(ctx, story, &path, p, &pl, (0, pl.lines.len()), (0.0, y + lead), &at, &floats, depth); + for placed in back.iter().chain(front.iter()) { + if let Placed::Object { rect, wrap, .. } = placed + && *wrap != Wrap::Inline + { + float_objects_bottom = float_objects_bottom.max(rect.bottom()); + } + } let at = behind.min(items.len()); behind += back.len(); items.splice(at..at, back); @@ -1342,7 +1352,7 @@ fn layout_box( } } } - let floats_bottom = float_tables.iter().map(|r| r.bottom()).fold(0.0, f32::max); + let floats_bottom = float_tables.iter().map(|r| r.bottom()).fold(float_objects_bottom, f32::max); ctx.hide_deleted = outer_hide; BoxLayout { items, height: y.max(0.0), floats_bottom, last_top: last_top.max(0.0), table_areas, page_table_wraps } } diff --git a/crates/layout/src/tests.rs b/crates/layout/src/tests.rs index 174dde174..4e32f5fb2 100644 --- a/crates/layout/src/tests.rs +++ b/crates/layout/src/tests.rs @@ -4995,6 +4995,27 @@ fn a_floating_table_in_a_cell_takes_no_room_and_the_cell_holds_it() { assert!(below_y > last_y + 10.0, "text below the outer table at {below_y}, the inner table's last row at {last_y}"); } +/// A floating (anchored, non-inline) picture in a cell grows the row to hold it too, the same +/// way a floating table does: it must not hang past the row's own bottom edge and overlap +/// whatever follows the table (#3108). +#[test] +fn a_floating_picture_in_a_cell_grows_the_row_to_hold_it() { + use wordcraft_doc::para::{Anchor, Float, Wrap}; + let mut t = Table::new(1, 1, 468.0); + let mut cp = wordcraft_doc::Paragraph::with_text("Caption", Default::default()); + // Short text alone would need one line (well under 100pt); the floated picture is much + // taller than that, so only its own height should set the row's height. + let picture = Float { wrap: Wrap::Square, h_rel: Anchor::Margin, v_rel: Anchor::Margin, x: 0.0, y: 0.0, ..Default::default() }; + cp.insert_object(0, rect_shape(200.0, 300.0, picture), &Default::default()).unwrap(); + t.rows[0].cells[0].blocks = vec![wordcraft_doc::para_block(cp)]; + let mut d = Document::from_text("Below the table"); + d.insert_block(StoryRef::Body, &Path::top(0), wordcraft_doc::Block::Table(t)).unwrap(); + let l = lay(&d); + let [(_, row_y, _)] = para_at(&l, &[0, 0, 0, 0])[..] else { panic!("cell text line missing") }; + let [(_, below_y, _)] = para_at(&l, &[1])[..] else { panic!("line after the table missing") }; + assert!(below_y - row_y > 290.0, "text after the table at {below_y}, the row's text at {row_y}: the row didn't grow for the 300pt picture"); +} + #[test] fn autofit_sizes_columns_by_their_content() { let text = |s: &str| wordcraft_doc::para_block(wordcraft_doc::Paragraph::with_text(s, Default::default()));