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 <bokuweb12@gmail.com>

---------

Co-authored-by: gwq <guowanqi@tianchuangsec.com>
Co-authored-by: bokuweb <bokuweb12@gmail.com>
main
Griklit 2023-07-27 20:43:15 +08:00 committed by GitHub
parent 8aec069b83
commit 86786eaa7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 267 additions and 8 deletions

View File

@ -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::*;

View File

@ -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<bool>,
// pub theme_color: Option<String>,
// pub theme_shade: Option<String>,
// pub theme_tint: Option<String>,
// pub frame: Option<bool>,
}
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<String>) -> 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<String>) -> Self {
// self.theme_color = Some(theme_color.into());
// self
// }
//
// pub fn theme_shade(mut self, theme_shade: impl Into<String>) -> Self {
// self.theme_shade = Some(theme_shade.into());
// self
// }
//
// pub fn theme_tint(mut self, theme_tint: impl Into<String>) -> 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<u8> {
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<ParagraphBorder>,
right: Option<ParagraphBorder>,
top: Option<ParagraphBorder>,
bottom: Option<ParagraphBorder>,
between: Option<ParagraphBorder>,
bar: Option<ParagraphBorder>,
}
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<u8> {
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()
}
}

View File

@ -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<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub paragraph_property_change: Option<ParagraphPropertyChange>,
#[serde(skip_serializing_if = "Option::is_none")]
pub borders: Option<ParagraphBorders>,
}
// 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<u8> {
@ -160,7 +183,8 @@ fn inner_build(p: &ParagraphProperty) -> Vec<u8> {
.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 {

View File

@ -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,
}

View File

@ -125,6 +125,7 @@ impl XMLBuilder {
// i.e. <w:r ... >
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. <w:qFormat ... >
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. <w:outlineLvl ...>
closed_with_usize!(outline_lvl, "w:outlineLvl");
// i.e. <w:name ... >

View File

@ -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()
}
};
}

View File

@ -77,8 +77,8 @@ impl XMLBuilder {
}
pub(crate) fn add_child<T>(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<T>(mut self, child: &Option<T>) -> 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<T>(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]