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
parent
8aec069b83
commit
86786eaa7b
|
@ -63,6 +63,7 @@ mod outline_lvl;
|
||||||
mod page_margin;
|
mod page_margin;
|
||||||
mod page_size;
|
mod page_size;
|
||||||
mod paragraph;
|
mod paragraph;
|
||||||
|
mod paragraph_borders;
|
||||||
mod paragraph_property;
|
mod paragraph_property;
|
||||||
mod paragraph_property_change;
|
mod paragraph_property_change;
|
||||||
mod paragraph_style;
|
mod paragraph_style;
|
||||||
|
@ -184,6 +185,7 @@ pub use outline_lvl::*;
|
||||||
pub use page_margin::*;
|
pub use page_margin::*;
|
||||||
pub use page_size::*;
|
pub use page_size::*;
|
||||||
pub use paragraph::*;
|
pub use paragraph::*;
|
||||||
|
pub use paragraph_borders::*;
|
||||||
pub use paragraph_property::*;
|
pub use paragraph_property::*;
|
||||||
pub use paragraph_property_change::*;
|
pub use paragraph_property_change::*;
|
||||||
pub use paragraph_style::*;
|
pub use paragraph_style::*;
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ use serde::Serialize;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::documents::BuildXML;
|
use crate::documents::BuildXML;
|
||||||
|
use crate::ParagraphBorderPosition;
|
||||||
use crate::types::{AlignmentType, SpecialIndentType};
|
use crate::types::{AlignmentType, SpecialIndentType};
|
||||||
use crate::xml_builder::*;
|
use crate::xml_builder::*;
|
||||||
|
|
||||||
|
@ -37,6 +38,8 @@ pub struct ParagraphProperty {
|
||||||
pub(crate) div_id: Option<String>,
|
pub(crate) div_id: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub paragraph_property_change: Option<ParagraphPropertyChange>,
|
pub paragraph_property_change: Option<ParagraphPropertyChange>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub borders: Option<ParagraphBorders>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 17.3.1.26
|
// 17.3.1.26
|
||||||
|
@ -148,6 +151,26 @@ impl ParagraphProperty {
|
||||||
}
|
}
|
||||||
self
|
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> {
|
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.indent)
|
||||||
.add_optional_child(&p.line_spacing)
|
.add_optional_child(&p.line_spacing)
|
||||||
.add_optional_child(&p.outline_lvl)
|
.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 let Some(v) = p.keep_next {
|
||||||
if v {
|
if v {
|
||||||
|
|
|
@ -27,3 +27,15 @@ pub enum TableCellBorderPosition {
|
||||||
Tl2br,
|
Tl2br,
|
||||||
Tr2bl,
|
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,
|
||||||
|
}
|
||||||
|
|
|
@ -125,6 +125,7 @@ impl XMLBuilder {
|
||||||
// i.e. <w:r ... >
|
// i.e. <w:r ... >
|
||||||
open!(open_run, "w:r");
|
open!(open_run, "w:r");
|
||||||
open!(open_run_property, "w:rPr");
|
open!(open_run_property, "w:rPr");
|
||||||
|
open!(open_paragraph_borders, "w:pBdr");
|
||||||
open!(open_run_property_default, "w:rPrDefault");
|
open!(open_run_property_default, "w:rPrDefault");
|
||||||
// i.e. <w:qFormat ... >
|
// i.e. <w:qFormat ... >
|
||||||
closed!(q_format, "w:qFormat");
|
closed!(q_format, "w:qFormat");
|
||||||
|
@ -139,6 +140,13 @@ impl XMLBuilder {
|
||||||
open!(open_structured_tag_property, "w:sdtPr");
|
open!(open_structured_tag_property, "w:sdtPr");
|
||||||
closed_with_str!(alias, "w:alias");
|
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 ...>
|
// i.e. <w:outlineLvl ...>
|
||||||
closed_with_usize!(outline_lvl, "w:outlineLvl");
|
closed_with_usize!(outline_lvl, "w:outlineLvl");
|
||||||
// i.e. <w:name ... >
|
// i.e. <w:name ... >
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -77,8 +77,8 @@ impl XMLBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn add_child<T>(mut self, child: &T) -> Self
|
pub(crate) fn add_child<T>(mut self, child: &T) -> Self
|
||||||
where
|
where
|
||||||
T: BuildXML,
|
T: BuildXML,
|
||||||
{
|
{
|
||||||
let buf = child.build();
|
let buf = child.build();
|
||||||
let text = str::from_utf8(&buf).unwrap();
|
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
|
pub(crate) fn add_optional_child<T>(mut self, child: &Option<T>) -> Self
|
||||||
where
|
where
|
||||||
T: BuildXML,
|
T: BuildXML,
|
||||||
{
|
{
|
||||||
if let Some(c) = child {
|
if let Some(c) = child {
|
||||||
self = self.add_child(c)
|
self = self.add_child(c)
|
||||||
|
@ -103,8 +103,8 @@ impl XMLBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn add_children<T>(mut self, children: &[T]) -> Self
|
pub(crate) fn add_children<T>(mut self, children: &[T]) -> Self
|
||||||
where
|
where
|
||||||
T: BuildXML,
|
T: BuildXML,
|
||||||
{
|
{
|
||||||
for c in children {
|
for c in children {
|
||||||
self = self.add_child(c);
|
self = self.add_child(c);
|
||||||
|
@ -134,7 +134,6 @@ impl XMLBuilder {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in New Issue