From ec6a7b7e9a71971c5d2136757a2ee32fc7f5a333 Mon Sep 17 00:00:00 2001 From: Yonghye Kwon Date: Wed, 22 Jul 2026 04:07:25 +0900 Subject: [PATCH] feat: parse paragraph-level shading (w:pPr/w:shd) Word paints paragraph-wide shading from pPr shading; the reader only handled run and table-cell shading, so code-block backgrounds were dropped (office2pdf#351). Signed-off-by: Yonghye Kwon --- .../documents/elements/paragraph_property.rs | 8 +++++ docx-core/src/reader/paragraph_property.rs | 34 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/docx-core/src/documents/elements/paragraph_property.rs b/docx-core/src/documents/elements/paragraph_property.rs index 0baa0abc2..f82e57a1b 100644 --- a/docx-core/src/documents/elements/paragraph_property.rs +++ b/docx-core/src/documents/elements/paragraph_property.rs @@ -48,6 +48,8 @@ pub struct ParagraphProperty { pub adjust_right_ind: Option, #[serde(skip_serializing_if = "Option::is_none")] pub snap_to_grid: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub shading: Option, // read only #[serde(skip_serializing_if = "Option::is_none")] pub(crate) div_id: Option, @@ -109,6 +111,11 @@ impl ParagraphProperty { self } + pub fn shading(mut self, shd: Shading) -> Self { + self.shading = Some(shd); + self + } + pub fn keep_next(mut self, v: bool) -> Self { self.keep_next = Some(v); self @@ -229,6 +236,7 @@ impl BuildXML for ParagraphProperty { .add_optional_child(&self.outline_lvl)? .add_optional_child(&self.paragraph_property_change)? .add_optional_child(&self.borders)? + .add_optional_child(&self.shading)? .add_optional_child(&self.text_alignment)? .add_optional_child(&self.adjust_right_ind)? .apply_opt(self.snap_to_grid, |v, b| b.snap_to_grid(v))? diff --git a/docx-core/src/reader/paragraph_property.rs b/docx-core/src/reader/paragraph_property.rs index 326494668..85cbf2a07 100644 --- a/docx-core/src/reader/paragraph_property.rs +++ b/docx-core/src/reader/paragraph_property.rs @@ -135,6 +135,11 @@ impl ElementReader for ParagraphProperty { p = p.set_borders(borders); } } + XMLElement::Shading => { + if let Ok(shd) = Shading::read(r, &attributes) { + p = p.shading(shd); + } + } XMLElement::Tabs => { if let Ok(tabs) = Tabs::read(r, &attributes) { for t in tabs.tabs { @@ -157,3 +162,32 @@ impl ElementReader for ParagraphProperty { } } } + +#[cfg(test)] +mod tests { + use super::*; + #[cfg(test)] + use pretty_assertions::assert_eq; + use std::io::Cursor; + + #[test] + fn test_read_paragraph_shading() { + // Word paints paragraph-wide shading from ; dropping + // it loses code-block backgrounds (office2pdf#351). + let xml = r#" + + "#; + let mut parser = EventReader::new(Cursor::new(xml)); + // consume the StartElement for pPr first, mirroring Document::read + loop { + if let Ok(XmlEvent::StartElement { name, .. }) = parser.next() { + if name.local_name == "pPr" { + break; + } + } + } + let p = ParagraphProperty::read(&mut parser, &[]).unwrap(); + let shd = p.shading.expect("paragraph shading must be parsed"); + assert_eq!(shd.fill, "F4F4F4"); + } +}