From 86786eaa7b5b6c0839199ad95b1b70cd1899802a Mon Sep 17 00:00:00 2001 From: Griklit <34975937+Griklit@users.noreply.github.com> Date: Thu, 27 Jul 2023 20:43:15 +0800 Subject: [PATCH] add Paragraph borders (#642) * fix widow_control xml * add character_spacing_control * snapshot fix - character_spacing_values, snack to camelCase * wasm snapshot * snapshot * paragraph_borders * Update docx-core/src/documents/elements/paragraph_property.rs Co-authored-by: bokuweb --------- Co-authored-by: gwq Co-authored-by: bokuweb --- docx-core/src/documents/elements/mod.rs | 2 + .../documents/elements/paragraph_borders.rs | 193 ++++++++++++++++++ .../documents/elements/paragraph_property.rs | 26 ++- docx-core/src/types/border_position.rs | 12 ++ docx-core/src/xml_builder/elements.rs | 8 + docx-core/src/xml_builder/macros.rs | 21 ++ docx-core/src/xml_builder/mod.rs | 13 +- 7 files changed, 267 insertions(+), 8 deletions(-) create mode 100644 docx-core/src/documents/elements/paragraph_borders.rs diff --git a/docx-core/src/documents/elements/mod.rs b/docx-core/src/documents/elements/mod.rs index 40e5ffd..0a62dc9 100644 --- a/docx-core/src/documents/elements/mod.rs +++ b/docx-core/src/documents/elements/mod.rs @@ -63,6 +63,7 @@ mod outline_lvl; mod page_margin; mod page_size; mod paragraph; +mod paragraph_borders; mod paragraph_property; mod paragraph_property_change; mod paragraph_style; @@ -184,6 +185,7 @@ pub use outline_lvl::*; pub use page_margin::*; pub use page_size::*; pub use paragraph::*; +pub use paragraph_borders::*; pub use paragraph_property::*; pub use paragraph_property_change::*; pub use paragraph_style::*; diff --git a/docx-core/src/documents/elements/paragraph_borders.rs b/docx-core/src/documents/elements/paragraph_borders.rs new file mode 100644 index 0000000..493cbb3 --- /dev/null +++ b/docx-core/src/documents/elements/paragraph_borders.rs @@ -0,0 +1,193 @@ +use serde::Serialize; + +use crate::documents::BuildXML; +use crate::types::*; +use crate::xml_builder::*; + +#[derive(Serialize, Debug, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct ParagraphBorder { + position: ParagraphBorderPosition, + pub val: BorderType, + pub size: usize, + pub space: usize, + pub color: String, + // pub shadow: Option, + // pub theme_color: Option, + // pub theme_shade: Option, + // pub theme_tint: Option, + // pub frame: Option, +} + +impl ParagraphBorder { + pub fn new(position: ParagraphBorderPosition) -> Self { + ParagraphBorder { + position, + val: BorderType::Single, + size: 2, + space: 0, + color: "auto".to_owned(), + // shadow: None, + // theme_color: None, + // theme_shade: None, + // theme_tint: None, + // frame: None, + } + } + pub fn val(mut self, val: BorderType) -> Self { + self.val = val; + self + } + + pub fn size(mut self, size: usize) -> Self { + self.size = size; + self + } + + pub fn space(mut self, space: usize) -> Self { + self.space = space; + self + } + + pub fn color(mut self, color: impl Into) -> Self { + self.color = color.into(); + self + } + + // pub fn shadow(mut self, shadow: bool) -> Self { + // self.shadow = Some(shadow); + // self + // } + // + // pub fn theme_color(mut self, theme_color: impl Into) -> Self { + // self.theme_color = Some(theme_color.into()); + // self + // } + // + // pub fn theme_shade(mut self, theme_shade: impl Into) -> Self { + // self.theme_shade = Some(theme_shade.into()); + // self + // } + // + // pub fn theme_tint(mut self, theme_tint: impl Into) -> Self { + // self.theme_tint = Some(theme_tint.into()); + // self + // } + // + // pub fn frame(mut self, frame: bool) -> Self { + // self.frame = Some(frame); + // self + // } +} + +impl BuildXML for ParagraphBorder { + fn build(&self) -> Vec { + let base = XMLBuilder::new(); + let base = { + let val = self.val.to_string(); + let space = self.space.to_string(); + let size = self.size.to_string(); + match self.position { + ParagraphBorderPosition::Top => base.paragraph_border_top(&val, &space, &size, &self.color), + ParagraphBorderPosition::Left => base.paragraph_border_left(&val, &space, &size, &self.color), + ParagraphBorderPosition::Bottom => base.paragraph_border_bottom(&val, &space, &size, &self.color), + ParagraphBorderPosition::Right => base.paragraph_border_right(&val, &space, &size, &self.color), + ParagraphBorderPosition::Between => base.paragraph_border_between(&val, &space, &size, &self.color), + ParagraphBorderPosition::Bar => base.paragraph_border_bar(&val, &space, &size, &self.color), + } + }; + base.build() + } +} + +#[derive(Serialize, Debug, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct ParagraphBorders { + left: Option, + right: Option, + top: Option, + bottom: Option, + between: Option, + bar: Option, +} + + +impl Default for ParagraphBorders { + fn default() -> Self { + ParagraphBorders { + left: Some(ParagraphBorder::new(ParagraphBorderPosition::Left)), + right: Some(ParagraphBorder::new(ParagraphBorderPosition::Right)), + top: Some(ParagraphBorder::new(ParagraphBorderPosition::Top)), + bottom: Some(ParagraphBorder::new(ParagraphBorderPosition::Bottom)), + between: None, + bar: None, + } + } +} + +impl ParagraphBorders { + pub fn new() -> Self { + Self::default() + } + + pub fn with_empty() -> Self { + ParagraphBorders { + left: None, + right: None, + top: None, + bottom: None, + between: None, + bar: None, + } + } + + pub fn set(mut self, border: ParagraphBorder) -> Self { + match border.position { + ParagraphBorderPosition::Top => self.top = Some(border), + ParagraphBorderPosition::Left => self.left = Some(border), + ParagraphBorderPosition::Bottom => self.bottom = Some(border), + ParagraphBorderPosition::Right => self.right = Some(border), + ParagraphBorderPosition::Between => self.between = Some(border), + ParagraphBorderPosition::Bar => self.bar = Some(border), + }; + self + } + + pub fn clear(mut self, position: ParagraphBorderPosition) -> Self { + let nil = ParagraphBorder::new(position.clone()).val(BorderType::Nil); + match position { + ParagraphBorderPosition::Top => self.top = Some(nil), + ParagraphBorderPosition::Left => self.left = Some(nil), + ParagraphBorderPosition::Bottom => self.bottom = Some(nil), + ParagraphBorderPosition::Right => self.right = Some(nil), + ParagraphBorderPosition::Between => self.between = Some(nil), + ParagraphBorderPosition::Bar => self.bar = Some(nil), + }; + self + } + + pub fn clear_all(mut self) -> Self { + self.left = Some(ParagraphBorder::new(ParagraphBorderPosition::Left).val(BorderType::Nil)); + self.right = Some(ParagraphBorder::new(ParagraphBorderPosition::Right).val(BorderType::Nil)); + self.top = Some(ParagraphBorder::new(ParagraphBorderPosition::Top).val(BorderType::Nil)); + self.bottom = Some(ParagraphBorder::new(ParagraphBorderPosition::Bottom).val(BorderType::Nil)); + self.between = Some(ParagraphBorder::new(ParagraphBorderPosition::Between).val(BorderType::Nil)); + self.bar = Some(ParagraphBorder::new(ParagraphBorderPosition::Bar).val(BorderType::Nil)); + self + } +} + +impl BuildXML for ParagraphBorders { + fn build(&self) -> Vec { + XMLBuilder::new() + .open_paragraph_borders() + .add_optional_child(&self.left) + .add_optional_child(&self.right) + .add_optional_child(&self.top) + .add_optional_child(&self.bottom) + .add_optional_child(&self.between) + .add_optional_child(&self.bar) + .close() + .build() + } +} \ No newline at end of file diff --git a/docx-core/src/documents/elements/paragraph_property.rs b/docx-core/src/documents/elements/paragraph_property.rs index 3360410..3d2b16c 100644 --- a/docx-core/src/documents/elements/paragraph_property.rs +++ b/docx-core/src/documents/elements/paragraph_property.rs @@ -2,6 +2,7 @@ use serde::Serialize; use super::*; use crate::documents::BuildXML; +use crate::ParagraphBorderPosition; use crate::types::{AlignmentType, SpecialIndentType}; use crate::xml_builder::*; @@ -37,6 +38,8 @@ pub struct ParagraphProperty { pub(crate) div_id: Option, #[serde(skip_serializing_if = "Option::is_none")] pub paragraph_property_change: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub borders: Option, } // 17.3.1.26 @@ -148,6 +151,26 @@ impl ParagraphProperty { } self } + + pub fn set_borders(mut self, borders: ParagraphBorders) -> Self { + self.borders = Some(borders); + self + } + + pub fn set_border(mut self, border: ParagraphBorder) -> Self { + self.borders = Some(self.borders.unwrap_or_default().set(border)); + self + } + + pub fn clear_border(mut self, position: ParagraphBorderPosition) -> Self { + self.borders = Some(self.borders.unwrap_or_default().clear(position)); + self + } + + pub fn clear_all_borders(mut self) -> Self { + self.borders = Some(self.borders.unwrap_or_default().clear_all()); + self + } } fn inner_build(p: &ParagraphProperty) -> Vec { @@ -160,7 +183,8 @@ fn inner_build(p: &ParagraphProperty) -> Vec { .add_optional_child(&p.indent) .add_optional_child(&p.line_spacing) .add_optional_child(&p.outline_lvl) - .add_optional_child(&p.paragraph_property_change); + .add_optional_child(&p.paragraph_property_change) + .add_optional_child(&p.borders); if let Some(v) = p.keep_next { if v { diff --git a/docx-core/src/types/border_position.rs b/docx-core/src/types/border_position.rs index 2d8b983..eb49028 100644 --- a/docx-core/src/types/border_position.rs +++ b/docx-core/src/types/border_position.rs @@ -27,3 +27,15 @@ pub enum TableCellBorderPosition { Tl2br, Tr2bl, } + +#[cfg_attr(feature = "wasm", wasm_bindgen)] +#[derive(Debug, Clone, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub enum ParagraphBorderPosition { + Left, + Right, + Top, + Bottom, + Between, + Bar, +} diff --git a/docx-core/src/xml_builder/elements.rs b/docx-core/src/xml_builder/elements.rs index 818ccba..832d633 100644 --- a/docx-core/src/xml_builder/elements.rs +++ b/docx-core/src/xml_builder/elements.rs @@ -125,6 +125,7 @@ impl XMLBuilder { // i.e. open!(open_run, "w:r"); open!(open_run_property, "w:rPr"); + open!(open_paragraph_borders, "w:pBdr"); open!(open_run_property_default, "w:rPrDefault"); // i.e. closed!(q_format, "w:qFormat"); @@ -139,6 +140,13 @@ impl XMLBuilder { open!(open_structured_tag_property, "w:sdtPr"); closed_with_str!(alias, "w:alias"); + closed_paragraph_border_el!(paragraph_border_top, "w:top"); + closed_paragraph_border_el!(paragraph_border_left, "w:left"); + closed_paragraph_border_el!(paragraph_border_bottom, "w:bottom"); + closed_paragraph_border_el!(paragraph_border_right, "w:right"); + closed_paragraph_border_el!(paragraph_border_between, "w:between"); + closed_paragraph_border_el!(paragraph_border_bar, "w:bar"); + // i.e. closed_with_usize!(outline_lvl, "w:outlineLvl"); // i.e. diff --git a/docx-core/src/xml_builder/macros.rs b/docx-core/src/xml_builder/macros.rs index e2897bb..5d87cd0 100644 --- a/docx-core/src/xml_builder/macros.rs +++ b/docx-core/src/xml_builder/macros.rs @@ -559,3 +559,24 @@ macro_rules! closed_border_el { } }; } + +macro_rules! closed_paragraph_border_el { + ($name: ident, $ el_name: expr) => { + pub(crate) fn $name<'a>( + mut self, + val: &str, + space: &str, + size: &str, + color: &str, + ) -> Self { + self.writer.write( + XmlEvent::start_element($el_name) + .attr("w:val", val) + .attr("w:space", space) + .attr("w:sz", size) + .attr("w:color", color) + ).expect(EXPECT_MESSAGE); + self.close() + } + }; +} diff --git a/docx-core/src/xml_builder/mod.rs b/docx-core/src/xml_builder/mod.rs index 927ef90..972cfb3 100644 --- a/docx-core/src/xml_builder/mod.rs +++ b/docx-core/src/xml_builder/mod.rs @@ -77,8 +77,8 @@ impl XMLBuilder { } pub(crate) fn add_child(mut self, child: &T) -> Self - where - T: BuildXML, + where + T: BuildXML, { let buf = child.build(); let text = str::from_utf8(&buf).unwrap(); @@ -93,8 +93,8 @@ impl XMLBuilder { } pub(crate) fn add_optional_child(mut self, child: &Option) -> Self - where - T: BuildXML, + where + T: BuildXML, { if let Some(c) = child { self = self.add_child(c) @@ -103,8 +103,8 @@ impl XMLBuilder { } pub(crate) fn add_children(mut self, children: &[T]) -> Self - where - T: BuildXML, + where + T: BuildXML, { for c in children { self = self.add_child(c); @@ -134,7 +134,6 @@ impl XMLBuilder { #[cfg(test)] mod tests { - use super::*; #[test]