diff --git a/crates/pptx/src/write/shapes.rs b/crates/pptx/src/write/shapes.rs index 8b2ea35d..7a069f98 100644 --- a/crates/pptx/src/write/shapes.rs +++ b/crates/pptx/src/write/shapes.rs @@ -100,7 +100,10 @@ pub fn geometry(w: &mut W, g: &Geom) { vals.push(defaults.get(vals.len()).copied().unwrap_or(0.0)); } w.open0("a:avLst"); - let single = if defaults.is_empty() { vals.len() == 1 } else { defaults.len() == 1 }; + // A lone guide is `adj`, except in these presets, which name it `adj1` (as PowerPoint + // writes them); PowerPoint won't open a file that renames it (#214). + let adj1 = matches!(name, "bentConnector3" | "curvedConnector3" | "frame" | "mathPlus" | "mathMinus" | "mathMultiply"); + let single = !adj1 && if defaults.is_empty() { vals.len() == 1 } else { defaults.len() == 1 }; for (i, v) in vals.iter().enumerate() { let n = if single && i == 0 { "adj".to_string() } else { format!("adj{}", i + 1) }; let v = if v.is_finite() { v.round().clamp(-2.0e9, 2.0e9) as i64 } else { 0 }; diff --git a/crates/pptx/tests/roundtrip.rs b/crates/pptx/tests/roundtrip.rs index de2436a9..39a28f0e 100644 --- a/crates/pptx/tests/roundtrip.rs +++ b/crates/pptx/tests/roundtrip.rs @@ -1049,3 +1049,33 @@ fn templates_and_slide_shows_declare_their_own_main_content_type() { assert_eq!(deckcraft_pptx::import(&bytes).expect("import").slides.len(), p.slides.len(), "{ext} reads back"); } } + +/// #214: a lone guide was always written `adj`, but these presets name it `adj1`, and PowerPoint +/// won't open the file otherwise. (Names as PowerPoint writes them.) +#[test] +fn single_guide_names_follow_the_preset() { + let mut p = defaults::new_presentation(None); + let mut s = new_slide(&mut p, "Guides"); + let cases = [ + ("bentConnector3", "adj1"), + ("curvedConnector3", "adj1"), + ("frame", "adj1"), + ("mathPlus", "adj1"), + ("mathMinus", "adj1"), + ("mathMultiply", "adj1"), + ("roundRect", "adj"), + ("chevron", "adj"), + ]; + for (prst, _) in cases { + let mut sh = shape(&mut p, prst, 0.0, 0.0, 100.0, 50.0); + sh.geom = Geom::Preset { name: prst.into(), adj: vec![20000.0] }; + s.shapes.push(sh); + } + p.slides.push(Arc::new(s)); + let bytes = deckcraft_pptx::export(&p).expect("export"); + let mut z = zip::ZipArchive::new(std::io::Cursor::new(&bytes)).expect("zip"); + let xml = std::io::read_to_string(z.by_name("ppt/slides/slide2.xml").expect("slide")).expect("read"); + for (prst, gd) in cases { + assert!(xml.contains(&format!(r#""#)), "{prst} → {gd}: {xml}"); + } +}