//! A paragraph whose `w:pStyle` names a style `styles.xml` doesn't define is formatted as Normal, //! as in Word, not with the document defaults alone (#5077). Pandoc writes footers as //! `` without defining Footer. use craft_wordmodel::props::LineSpacing; use craft_wordmodel::{Block, Document, PartKind}; use std::io::Write; const W: &str = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"; const R: &str = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; fn package(normal_id: &str) -> Vec { let root = r#""#; let doc = format!( r#"bodynormal"# ); let footer = format!( r#"1"# ); let styles = format!( r#""# ); let rels = r#""#; let mut zw = zip::ZipWriter::new(std::io::Cursor::new(Vec::new())); for (name, body) in [ ("_rels/.rels", root), ("word/document.xml", doc.as_str()), ("word/_rels/document.xml.rels", rels), ("word/styles.xml", styles.as_str()), ("word/footer1.xml", footer.as_str()), ] { zw.start_file(name, zip::write::SimpleFileOptions::default()).unwrap(); zw.write_all(body.as_bytes()).unwrap(); } zw.finish().unwrap().into_inner() } fn footer_para(d: &Document) -> &craft_wordmodel::Paragraph { let part = d.parts.values().find(|p| p.kind == PartKind::Footer).expect("a footer"); match part.blocks.first().map(|b| &**b) { Some(Block::Para(p)) => p, _ => panic!("the footer starts with a paragraph"), } } #[test] fn a_paragraph_naming_an_undefined_style_is_formatted_as_normal() { // Also when the file's default paragraph style has another id (read as Normal). for normal_id in ["Normal", "a"] { let d = craft_docx::read(&package(normal_id)).unwrap(); let Some(Block::Para(body)) = d.body.first().map(|b| &**b) else { panic!("a body paragraph") }; let Some(Block::Para(normal)) = d.body.get(1).map(|b| &**b) else { panic!("a second body paragraph") }; let normal_rp = d.styles.resolve_para(&normal.props); assert_eq!((normal_rp.space_before, normal_rp.space_after), (1.0, 2.0), "{normal_id}"); for p in [body, footer_para(&d)] { // The paragraph keeps the style it names, for the round trip. assert_eq!(p.props.style.as_deref(), Some("Footer")); let rp = d.styles.resolve_para(&p.props); assert_eq!((rp.space_before, rp.space_after), (1.0, 2.0), "{normal_id}: Normal's spacing, not the defaults' 10 pt after"); assert_eq!(rp.line_spacing, LineSpacing::Multiple(259.0 / 240.0), "{normal_id}"); assert_eq!(d.styles.resolve_char(Some("Footer"), &Default::default()).size, 13.0, "{normal_id}"); } } } #[test] fn an_undefined_style_is_written_back_as_it_was_named() { let d = craft_docx::read(&package("Normal")).unwrap(); let again = craft_docx::read(&craft_docx::write(&d).unwrap()).unwrap(); assert_eq!(footer_para(&again).props.style.as_deref(), Some("Footer")); assert_eq!(again.styles.resolve_para(&footer_para(&again).props).space_after, 2.0); }