diff --git a/crates/docx/tests/fixtures.rs b/crates/docx/tests/fixtures.rs index c4e5aee99..350a007b0 100644 --- a/crates/docx/tests/fixtures.rs +++ b/crates/docx/tests/fixtures.rs @@ -1609,6 +1609,88 @@ fn cover_page_group_fills_freeforms_and_text_box_margins() { check(&wordcraft_docx::read(&wordcraft_docx::write(&d).unwrap()).unwrap()); } +/// #886: saving dropped Word's DrawingML shapes (rectangles, ellipses, triangles, arrows, +/// stars, connectors and their theme fills). Each kind the report lists is read, saved and read +/// back with the same kind, fill, outline, size and position, and the saved part still holds a +/// `wps:wsp` with its preset geometry. +#[test] +fn drawingml_shapes_survive_saving() { + let style = r#""#; + let geom = |prst: &str| format!(r#""#); + let presets = ["rect", "ellipse", "triangle", "rightArrow", "star5"]; + let mut body: String = presets.iter().map(|p| anchored_wsp(p, 2009775, 819150, &geom(p), style, "")).collect(); + body.push_str(&anchored_wsp( + "Connector", + 2362200, + 390525, + r#""#, + style, + "", + )); + body.push_str(&anchored_wsp( + "Text Box", + 2324100, + 571500, + r#""#, + "", + r#"Floating text box"#, + )); + let shapes = |d: &Document| { + paras(d)[0] + .objects + .iter() + .map(|o| match o { + InlineObject::Shape { kind, w, h, fill, stroke, stroke_width, float, extra, .. } => { + (*kind, *w, *h, *fill, *stroke, *stroke_width, float.x, float.y, float.wrap, extra.arrow_end) + } + o => panic!("{o:?}"), + }) + .collect::>() + }; + let d = read_body(&format!("{body}")); + let accent = d.settings.theme_colors[4]; + let read = shapes(&d); + let kinds: Vec = read.iter().map(|s| s.0).collect(); + assert_eq!( + kinds, + [ + ShapeKind::Rectangle, + ShapeKind::Ellipse, + ShapeKind::Triangle, + ShapeKind::Arrow, + ShapeKind::Star, + ShapeKind::StraightConnector, + ShapeKind::TextBox + ] + ); + for s in &read[..5] { + assert_eq!(s.3, Some(accent), "{:?} is filled from the theme", s.0); + assert!(s.4.is_some() && s.5 > 0.0, "{:?} has its outline", s.0); + assert_eq!((s.6, s.7, s.8), (1.0, 2.0, Wrap::InFrontOfText), "{:?} keeps its place", s.0); + } + assert!(read[5].9, "the connector keeps its arrowhead"); + let bytes = wordcraft_docx::write(&d).unwrap(); + let again = wordcraft_docx::read(&bytes).unwrap(); + assert_eq!(shapes(&again), read, "every shape survives saving"); + assert_eq!(part_text_box(&again), "Floating text box"); + let mut z = zip::ZipArchive::new(std::io::Cursor::new(&bytes[..])).unwrap(); + let mut xml = String::new(); + std::io::Read::read_to_string(&mut z.by_name("word/document.xml").unwrap(), &mut xml).unwrap(); + assert_eq!(xml.matches("").count(), 7); + for p in presets.iter().chain(&["straightConnector1"]) { + assert!(xml.contains(&format!(r#""#)), "{p} is written"); + } +} + +/// The text of the first text box in `d`. +fn part_text_box(d: &Document) -> String { + let story = paras(d)[0].objects.iter().find_map(|o| match o { + InlineObject::Shape { kind: ShapeKind::TextBox, story, .. } => *story, + _ => None, + }); + story.and_then(|id| d.parts.get(&id)).map(|p| p.blocks.iter().filter_map(|b| b.as_para()).map(|p| p.plain_text()).collect()).unwrap_or_default() +} + #[test] fn embedded_fonts_survive_a_round_trip() { let table = r#""#;