From bc1d67cb615ac0672b498fbdc74047db65336f2e Mon Sep 17 00:00:00 2001 From: Erige005 <162100015+Erige005@users.noreply.github.com> Date: Fri, 9 Oct 2026 20:14:34 +0700 Subject: [PATCH] Export to Word: detect tables of at most 63 columns, Word's limit (#479) (#483) --- crates/export/src/lib.rs | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/crates/export/src/lib.rs b/crates/export/src/lib.rs index e92d09ec1..ba165b09c 100644 --- a/crates/export/src/lib.rs +++ b/crates/export/src/lib.rs @@ -96,10 +96,11 @@ fn level(b: &Block, body: f64) -> u8 { /// How far two cell left edges may drift (points) and still be the same grid column. const COL_TOL: f64 = 4.0; -/// More grid columns than this isn't a table. It also bounds a table's size: every row is -/// materialized to all its columns, so a hostile page laid out as a staircase of blocks would -/// otherwise make (blocks / 2)² cells. -const MAX_COLS: usize = 64; +/// More grid columns than this isn't a table. It is Word's limit: a .docx whose table has 64 or +/// more columns doesn't open. It also bounds a table's size: every row is materialized to all +/// its columns, so a hostile page laid out as a staircase of blocks would otherwise make +/// (blocks / 2)² cells. +const MAX_COLS: usize = 63; /// A cell is short: taller blocks are body text (or a multi-column layout), not table cells. fn is_cell_like(b: &Block) -> bool { (b.rect[3] - b.rect[1]) <= b.size * 5.0 && !b.text.trim().is_empty() @@ -809,6 +810,28 @@ mod tests { assert!(started.elapsed() < std::time::Duration::from_secs(5), "took {:?}", started.elapsed()); } + #[test] + fn word_opens_tables_up_to_its_column_limit() { + // Rows of short numbers on a regular grid, n columns wide. + let grid = |n: usize| Page { + width: 1500.0, + height: 792.0, + blocks: (0..3) + .flat_map(|row| { + (0..n).map(move |col| { + let (x, y) = (20.0 + col as f64 * 22.0, 700.0 - row as f64 * 14.0); + Block { text: (col + 1).to_string(), rect: [x, y, x + 8.0, y + 8.0], size: 8.0, bold: false, italic: false } + }) + }) + .collect(), + images: Vec::new(), + }; + let grid_cols = |p: &Page| part(&docx(std::slice::from_ref(p), "Grid"), "word/document.xml").matches("