From 754841aaa45947fb1b059f534ef4634cd0fc9841 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Tue, 13 Oct 2026 06:39:06 -0400 Subject: [PATCH] Pptx: a table naming no style reads as the table grid style A table whose tblPr names no tableStyleId drew without any lines. PowerPoint draws such a table with thin dark grid lines (as its "No Style, Table Grid"); observed on a synthetic deck with and without the explicit "No Style, No Grid" GUID. Such tables now read as the grid style. Wild synthetic set: tables with cell margins/fills/styles 4.80 -> 2.82, 11.28 -> 10.15, 6.86 -> 3.16. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/pptx/src/read/shapes.rs | 4 +++- crates/pptx/tests/fills.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/crates/pptx/src/read/shapes.rs b/crates/pptx/src/read/shapes.rs index 51b2b86..c4b3f77 100644 --- a/crates/pptx/src/read/shapes.rs +++ b/crates/pptx/src/read/shapes.rs @@ -436,7 +436,9 @@ fn smartart(imp: &mut Imp, part: &Part, gd: &El, frame: &Shape, depth: usize) -> } fn table(imp: &mut Imp, part: &Part, tbl: &El) -> Table { - let mut t = Table { style: "none".into(), first_row: false, band_row: false, ..Default::default() }; + // A table naming no style is drawn by PowerPoint with thin dark grid lines ("No Style, Table + // Grid"), not without lines. + let mut t = Table { style: "grid".into(), first_row: false, band_row: false, ..Default::default() }; if let Some(pr) = tbl.child("tblPr") { t.background = pr.elements().find(|e| e.local().ends_with("Fill") && e.local() != "noFill").and_then(|f| dml::fill_el(imp, part, f)); t.first_row = pr.bool("firstRow").unwrap_or(false); diff --git a/crates/pptx/tests/fills.rs b/crates/pptx/tests/fills.rs index 8620b76..77e719e 100644 --- a/crates/pptx/tests/fills.rs +++ b/crates/pptx/tests/fills.rs @@ -170,3 +170,36 @@ fn a_gradient_tile_rect_is_read_and_round_trips() { let s = q.slides[0].shapes.iter().find(|s| s.name == "Corner").expect("shape"); assert!(matches!(&s.fill, Some(Fill::Gradient(g)) if g.tile == [-1.0, -1.0, 0.0, 0.0]), "{:?}", s.fill); } + +#[test] +fn a_table_naming_no_style_is_drawn_with_the_table_grid() { + let mut p = defaults::new_presentation(None); + let id = ShapeId(p.alloc_id()); + let first = p.slides[0].id; + let mut t = deckcraft_model::Table::new(2, 2, 200.0, 40.0); + t.style = "none".into(); + p.slide_mut(first).expect("slide").shapes.push(Shape { + id, + name: "Grid".into(), + xfrm: Some(Xfrm::new(10.0, 10.0, 200.0, 80.0)), + kind: deckcraft_model::ShapeKind::Table(t), + ..Default::default() + }); + let bytes = deckcraft_pptx::export(&p).expect("export"); + let style = |bytes: &[u8]| { + let q = deckcraft_pptx::import(bytes).expect("import"); + let s = q.slides[0].shapes.iter().find(|s| s.name == "Grid").expect("shape").clone(); + match s.kind { + deckcraft_model::ShapeKind::Table(t) => t.style, + other => panic!("{other:?}"), + } + }; + // Written with its style, it reads back as written. + assert_eq!(style(&bytes), "none"); + // Without a:tableStyleId PowerPoint draws thin dark grid lines. + let bare = edit_slides(&bytes, |x| { + let (Some(a), Some(b)) = (x.find(""), x.find("")) else { return x }; + format!("{}{}", &x[..a], &x[b + "".len()..]) + }); + assert_eq!(style(&bare), "grid"); +}