diff --git a/crates/pptx/src/write/mod.rs b/crates/pptx/src/write/mod.rs index 0ce5af26..39e143ea 100644 --- a/crates/pptx/src/write/mod.rs +++ b/crates/pptx/src/write/mod.rs @@ -385,23 +385,27 @@ struct NotesMasterWrite { } fn notes_master_write(p: &Presentation) -> Option { - let kept = p.notes_master.as_ref().filter(|m| !m.shapes.is_empty()); + let nm = p.notes_master.as_deref(); + let kept = nm.filter(|m| !m.shapes.is_empty()); let has_notes = p.slides.iter().any(|s| !s.notes.is_empty()); - if !has_notes && kept.is_none() { + if !has_notes && nm.is_none() { return None; } + let default_bg = || Background::Ref { idx: 1001, color: deckcraft_model::ColorRef::scheme(deckcraft_color::SchemeSlot::Bg1) }; Some(match kept { Some(m) => NotesMasterWrite { shapes: m.shapes.clone(), - background: m.background.clone(), + background: Some(m.background.clone().unwrap_or_else(default_bg)), color_map: m.color_map.clone(), style: if m.body_style.is_empty() { notes_style() } else { m.body_style.clone() }, theme: Some(m.theme.clone()), }, + // A master without placeholders of its own gets PowerPoint's defaults, keeping its + // background and colour map. None => NotesMasterWrite { shapes: notes_master_shapes(p.notes_size), - background: Some(Background::Ref { idx: 1001, color: deckcraft_model::ColorRef::scheme(deckcraft_color::SchemeSlot::Bg1) }), - color_map: vec![], + background: Some(nm.and_then(|m| m.background.clone()).unwrap_or_else(default_bg)), + color_map: nm.map(|m| m.color_map.clone()).unwrap_or_default(), style: notes_style(), theme: None, }, diff --git a/crates/pptx/tests/roundtrip.rs b/crates/pptx/tests/roundtrip.rs index 01422310..88848239 100644 --- a/crates/pptx/tests/roundtrip.rs +++ b/crates/pptx/tests/roundtrip.rs @@ -1376,3 +1376,37 @@ fn color_pulse_keeps_its_effect_colour_and_duration() { assert_eq!(a.amount, None, "no grow/shrink scale is invented"); assert_eq!(a.duration_ms, 1000); } + +#[test] +fn notes_master_placeholders_and_colour_map_survive_save() { + let mut p = defaults::new_presentation(None); + let mut box1 = shape(&mut p, "Confidential", 100000.0, 200000.0, 3000000.0, 400000.0); + box1.text = Some(TextBody::from_text("Company confidential")); + let map: Vec<(String, String)> = [("bg1", "dk1"), ("tx1", "lt1"), ("bg2", "dk2"), ("tx2", "lt2")] + .iter() + .chain( + [ + ("accent1", "accent1"), + ("accent2", "accent2"), + ("accent3", "accent3"), + ("accent4", "accent4"), + ("accent5", "accent5"), + ("accent6", "accent6"), + ("hlink", "hlink"), + ("folHlink", "folHlink"), + ] + .iter(), + ) + .map(|(a, b)| (a.to_string(), b.to_string())) + .collect(); + let m = deckcraft_model::Master { name: "Notes Master".into(), shapes: vec![box1], color_map: map.clone(), ..Default::default() }; + p.notes_master = Some(Arc::new(m)); + // No slide has notes: the notes master must still be written. + let q = round(&p); + let nm = q.notes_master.as_deref().expect("notes master kept"); + assert_eq!(nm.shapes.len(), 1, "own shapes, not the defaults"); + let sh = &nm.shapes[0]; + assert_eq!(sh.name, "Confidential"); + assert_eq!(sh.text.as_ref().map(|t| t.text()), Some("Company confidential".to_string())); + assert_eq!(nm.color_map, map); +}