Keep a document's own styles and defaults when applying a style set (#4013)

Fixes #3650

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-25 11:01:31 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 85db9cfef5
commit b638caa2d2
2 changed files with 48 additions and 5 deletions
+13 -5
View File
@@ -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);
+35
View File
@@ -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("<w:latentStyles/>".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("<w:latentStyles/>"), "{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));
}