diff --git a/crates/pptx/src/tables.rs b/crates/pptx/src/tables.rs index 24b011c3..67d25d62 100644 --- a/crates/pptx/src/tables.rs +++ b/crates/pptx/src/tables.rs @@ -229,6 +229,16 @@ pub fn table_styles_xml(used: &[String], defs: &[deckcraft_model::table::TableSt let name = names.iter().find(|(id, _)| id == s).map(|(_, n)| format!("DeckCraft {n}")).unwrap_or_else(|| s.clone()); style_def(&mut w, s, &name); } + // Styles the file defined but no table uses still belong to the deck. + for d in defs { + if done.iter().any(|s| s.eq_ignore_ascii_case(&d.id)) { + continue; + } + if crate::xml::parse(d.raw.as_bytes()).is_ok_and(|x| x.root.is("tblStyle")) { + done.push(&d.id); + w.raw(&d.raw); + } + } w.close("a:tblStyleLst"); w.finish() } diff --git a/crates/pptx/tests/table_styles.rs b/crates/pptx/tests/table_styles.rs index 015543f2..27fca053 100644 --- a/crates/pptx/tests/table_styles.rs +++ b/crates/pptx/tests/table_styles.rs @@ -82,3 +82,16 @@ fn table_styles_the_file_defines_are_read_used_and_kept() { let def2 = q.table_styles.iter().find(|d| d.id == STYLE_GUID).expect("kept on save"); assert_eq!(def2.parts, def.parts); } + +#[test] +fn table_style_no_table_uses_is_kept_on_save() { + let mut p = deckcraft_pptx::import(&table_deck()).expect("import"); + assert!(p.table_styles.iter().any(|d| d.id == STYLE_GUID)); + // Drop the only table that uses the style; the deck still defines it. + for s in &mut p.slides { + std::sync::Arc::make_mut(s).shapes.retain(|sh| !matches!(sh.kind, deckcraft_model::ShapeKind::Table(_))); + } + let q = deckcraft_pptx::import(&deckcraft_pptx::export(&p).expect("export")).expect("reimport"); + let d = q.table_styles.iter().find(|d| d.id == STYLE_GUID).expect("style kept"); + assert_eq!(d.name, "Probe Style"); +}