From aaec22c681824d0635e8a3024ee34c0fdd3770e5 Mon Sep 17 00:00:00 2001 From: justinjohn0306 Date: Mon, 12 Oct 2026 17:30:03 +0530 Subject: [PATCH] PPTX: a lone guide keeps the name its preset uses, adj1 or adj (#214) The writer named a preset's only guide `adj` whenever the preset has one adjust value, but bentConnector3, curvedConnector3, frame, mathPlus, mathMinus and mathMultiply call it `adj1`, and PowerPoint refuses to open a file that renames it. The names come from PowerPoint itself: every autoshape and connector created with an adjusted first value and saved; these six are the only single-guide presets named `adj1`. Co-Authored-By: Claude Opus 5.5 --- crates/pptx/src/write/shapes.rs | 5 ++++- crates/pptx/tests/roundtrip.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/crates/pptx/src/write/shapes.rs b/crates/pptx/src/write/shapes.rs index 9b7309c0..55ac30ae 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 420a3921..8e259ed9 100644 --- a/crates/pptx/tests/roundtrip.rs +++ b/crates/pptx/tests/roundtrip.rs @@ -730,3 +730,33 @@ fn date_fields_keep_their_type_and_saved_text() { assert_eq!(by_name(&q.slides[0], name).text, by_name(&p.slides[0], name).text, "{name}"); } } + +/// #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}"); + } +}