diff --git a/crates/engine/src/cmd/design.rs b/crates/engine/src/cmd/design.rs index d1fedc160..eb39891d1 100644 --- a/crates/engine/src/cmd/design.rs +++ b/crates/engine/src/cmd/design.rs @@ -169,11 +169,19 @@ fn style_set(s: &mut Session, v: &Value) -> CmdResult { _ => {} } } - // Keep custom styles and fonts. - let customs: Vec<_> = s.doc.styles.styles.iter().filter(|x| !x.builtin).cloned().collect(); - fresh.styles.extend(customs); - fresh.default_chr.font = s.doc.styles.default_chr.font.clone(); - s.doc.styles = fresh; + // Overlay the set onto the document's sheet: every other style, the latent styles and the + // document defaults stay as they are (no set defines its own defaults). + for mut st in fresh.styles { + match s.doc.styles.styles.iter_mut().find(|x| x.id == st.id) { + Some(old) => { + st.default = old.default; + st.locked = old.locked; + st.builtin = old.builtin; + *old = st; + } + None => s.doc.styles.styles.push(st), + } + } let (h, b) = (s.doc.settings.major_font.clone(), s.doc.settings.minor_font.clone()); set_fonts(s, &h, &b); set_accent(s, accent); diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index f44e5c716..1f273d828 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -10577,3 +10577,38 @@ fn export_png_refuses_a_path_that_is_not_png() { } let _ = std::fs::remove_dir_all(&dir); } + +#[test] +fn style_set_keeps_imported_styles_latent_styles_and_defaults() { + use wordcraft_doc::props::{Rgb, TextColor}; + use wordcraft_doc::styles::{Style, StyleKind}; + for name in ["basic", "lines", "shaded", "casual", "centered", "minimalist", "title"] { + let mut s = s(); + let mut body = s.doc.styles.get("Normal").unwrap().clone(); + body.id = "BodyText".into(); + body.name = "Body Text".into(); + body.builtin = true; + body.based_on = Some("Normal".into()); + body.chr.size = Some(20.0); + body.chr.color = Some(TextColor::Rgb(Rgb(0xFF, 0, 0))); + let grid = Style { id: "GridTable4".into(), name: "Grid Table 4".into(), kind: StyleKind::Table, builtin: true, ..Default::default() }; + s.doc.styles.styles.push(body.clone()); + s.doc.styles.styles.push(grid.clone()); + s.doc.styles.latent_styles = Some("".into()); + s.doc.styles.default_chr.lang = Some("de-DE".into()); + s.doc.styles.default_chr.size = Some(11.0); + run(&mut s, "design.styleSet", json!({ "name": name })); + assert_eq!(s.doc.styles.get("BodyText"), Some(&body), "{name}: BodyText"); + assert_eq!(s.doc.styles.get("GridTable4"), Some(&grid), "{name}: GridTable4"); + assert_eq!(s.doc.styles.latent_styles.as_deref(), Some(""), "{name}"); + assert_eq!(s.doc.styles.default_chr.lang.as_deref(), Some("de-DE"), "{name}"); + assert_eq!(s.doc.styles.default_chr.size, Some(11.0), "{name}"); + assert_eq!(s.doc.styles.styles.iter().filter(|x| x.id == "Heading1").count(), 1, "{name}"); + let saved = crate::io::save_bytes("x.docx", &s.doc).unwrap(); + let back = crate::io::open_bytes("x.docx", &saved).unwrap(); + assert!(back.styles.get("BodyText").is_some(), "{name}: BodyText after round trip"); + } + let mut s = s(); + run(&mut s, "design.styleSet", json!({ "name": "title" })); + assert_eq!(s.doc.styles.get("Title").unwrap().chr.size, Some(36.0)); +}