docx_rs/documents/elements/
paragraph_property.rs

1use serde::Serialize;
2use std::io::Write;
3
4use super::*;
5use crate::documents::BuildXML;
6use crate::types::{AlignmentType, SpecialIndentType};
7use crate::ParagraphBorderPosition;
8use crate::{xml_builder::*, TextAlignmentType};
9
10#[derive(Serialize, Debug, Clone, PartialEq, Default)]
11#[serde(rename_all = "camelCase")]
12pub struct ParagraphProperty {
13    pub run_property: RunProperty,
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub style: Option<ParagraphStyle>,
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub numbering_property: Option<NumberingProperty>,
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub alignment: Option<Justification>,
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub indent: Option<Indent>,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub line_spacing: Option<LineSpacing>,
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub keep_next: Option<bool>,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub keep_lines: Option<bool>,
28    #[serde(skip_serializing_if="Option::is_none")]
29    pub bidi:Option<bool>,
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub page_break_before: Option<bool>,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub widow_control: Option<bool>,
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub outline_lvl: Option<OutlineLvl>,
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub section_property: Option<SectionProperty>,
38    pub tabs: Vec<Tab>,
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub paragraph_property_change: Option<ParagraphPropertyChange>,
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub borders: Option<ParagraphBorders>,
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub frame_property: Option<FrameProperty>,
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub text_alignment: Option<TextAlignment>,
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub adjust_right_ind: Option<AdjustRightInd>,
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub snap_to_grid: Option<bool>,
51    // read only
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub(crate) div_id: Option<String>,
54}
55
56// 17.3.1.26
57// pPr (Paragraph Properties)
58// This element specifies a set of paragraph properties which shall be applied to the contents of the parent
59// paragraph after all style/numbering/table properties have been applied to the text. These properties are defined
60// as direct formatting, since they are directly applied to the paragraph and supersede any formatting from styles.
61impl ParagraphProperty {
62    pub fn new() -> ParagraphProperty {
63        Default::default()
64    }
65
66    pub fn align(mut self, alignment_type: AlignmentType) -> Self {
67        self.alignment = Some(Justification::new(alignment_type.to_string()));
68        self
69    }
70
71    pub fn style(mut self, style_id: &str) -> Self {
72        self.style = Some(ParagraphStyle::new(Some(style_id)));
73        self
74    }
75
76    pub fn indent(
77        mut self,
78        left: Option<i32>,
79        special_indent: Option<SpecialIndentType>,
80        end: Option<i32>,
81        start_chars: Option<i32>,
82    ) -> Self {
83        self.indent = Some(Indent::new(left, special_indent, end, start_chars));
84        self
85    }
86
87    pub fn numbering(mut self, id: NumberingId, level: IndentLevel) -> Self {
88        self.numbering_property = Some(NumberingProperty::new().add_num(id, level));
89        self
90    }
91
92    pub fn numbering_property(mut self, np: NumberingProperty) -> Self {
93        self.numbering_property = Some(np);
94        self
95    }
96
97    pub fn line_spacing(mut self, spacing: LineSpacing) -> Self {
98        self.line_spacing = Some(spacing);
99        self
100    }
101
102    pub fn character_spacing(mut self, spacing: i32) -> Self {
103        self.run_property.character_spacing = Some(CharacterSpacing::new(spacing));
104        self
105    }
106
107    pub fn snap_to_grid(mut self, v: bool) -> Self {
108        self.snap_to_grid = Some(v);
109        self
110    }
111
112    pub fn keep_next(mut self, v: bool) -> Self {
113        self.keep_next = Some(v);
114        self
115    }
116
117    pub fn keep_lines(mut self, v: bool) -> Self {
118        self.keep_lines = Some(v);
119        self
120    }
121
122    pub fn outline_lvl(mut self, v: usize) -> Self {
123        if v >= 10 {
124            // clamped
125            self.outline_lvl = Some(OutlineLvl::new(9));
126            return self;
127        }
128        self.outline_lvl = Some(OutlineLvl::new(v));
129        self
130    }
131
132    pub fn page_break_before(mut self, v: bool) -> Self {
133        self.page_break_before = Some(v);
134        self
135    }
136    pub fn bidi(mut self,v:bool)->Self{
137        self.bidi=Some(v);
138        self
139    }
140    pub fn widow_control(mut self, v: bool) -> Self {
141        self.widow_control = Some(v);
142        self
143    }
144
145    pub fn add_tab(mut self, t: Tab) -> Self {
146        self.tabs.push(t);
147        self
148    }
149
150    pub fn section_property(mut self, s: SectionProperty) -> Self {
151        self.section_property = Some(s);
152        self
153    }
154
155    pub fn paragraph_property_change(mut self, p: ParagraphPropertyChange) -> Self {
156        self.paragraph_property_change = Some(p);
157        self
158    }
159
160    pub fn frame_property(mut self, s: FrameProperty) -> Self {
161        self.frame_property = Some(s);
162        self
163    }
164
165    pub fn run_property(mut self, s: RunProperty) -> Self {
166        self.run_property = s;
167        self
168    }
169
170    pub fn text_alignment(mut self, s: TextAlignmentType) -> Self {
171        self.text_alignment = Some(TextAlignment::new(s));
172        self
173    }
174
175    pub fn adjust_right_ind(mut self, s: isize) -> Self {
176        self.adjust_right_ind = Some(AdjustRightInd::new(s));
177        self
178    }
179
180    pub(crate) fn hanging_chars(mut self, chars: i32) -> Self {
181        if let Some(indent) = self.indent {
182            self.indent = Some(indent.hanging_chars(chars));
183        }
184        self
185    }
186
187    pub(crate) fn first_line_chars(mut self, chars: i32) -> Self {
188        if let Some(indent) = self.indent {
189            self.indent = Some(indent.first_line_chars(chars));
190        }
191        self
192    }
193
194    pub fn set_borders(mut self, borders: ParagraphBorders) -> Self {
195        self.borders = Some(borders);
196        self
197    }
198
199    pub fn set_border(mut self, border: ParagraphBorder) -> Self {
200        self.borders = Some(self.borders.unwrap_or_default().set(border));
201        self
202    }
203
204    pub fn clear_border(mut self, position: ParagraphBorderPosition) -> Self {
205        self.borders = Some(self.borders.unwrap_or_default().clear(position));
206        self
207    }
208
209    pub fn clear_all_borders(mut self) -> Self {
210        self.borders = Some(self.borders.unwrap_or_default().clear_all());
211        self
212    }
213}
214
215impl BuildXML for ParagraphProperty {
216    fn build_to<W: Write>(
217        &self,
218        stream: xml::writer::EventWriter<W>,
219    ) -> xml::writer::Result<xml::writer::EventWriter<W>> {
220        XMLBuilder::from(stream)
221            .open_paragraph_property()?
222            .add_child(&self.run_property)?
223            .add_optional_child(&self.style)?
224            .add_optional_child(&self.numbering_property)?
225            .add_optional_child(&self.frame_property)?
226            .add_optional_child(&self.alignment)?
227            .add_optional_child(&self.indent)?
228            .add_optional_child(&self.line_spacing)?
229            .add_optional_child(&self.outline_lvl)?
230            .add_optional_child(&self.paragraph_property_change)?
231            .add_optional_child(&self.borders)?
232            .add_optional_child(&self.text_alignment)?
233            .add_optional_child(&self.adjust_right_ind)?
234            .apply_opt(self.snap_to_grid, |v, b| b.snap_to_grid(v))?
235            .apply_if(self.keep_next, |b| b.keep_next())?
236            .apply_if(self.keep_lines, |b| b.keep_lines())?
237            .apply_if(self.page_break_before, |b| b.page_break_before())?
238            .apply_if(self.bidi,|b| b.bidi())?
239            .apply_opt(self.widow_control, |flag, b| {
240                b.widow_control(if flag { "1" } else { "0" })
241            })?
242            .apply_if(!self.tabs.is_empty(), |b| {
243                b.open_tabs()?
244                    .apply_each(&self.tabs, |tab, b| b.tab(tab.val, tab.leader, tab.pos))?
245                    .close()
246            })?
247            .close()?
248            .into_inner()
249    }
250}
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255    use crate::types::LineSpacingType;
256    #[cfg(test)]
257    use pretty_assertions::assert_eq;
258    use std::str;
259
260    #[test]
261    fn test_default() {
262        let c = ParagraphProperty::new();
263        let b = c.build();
264        assert_eq!(str::from_utf8(&b).unwrap(), r#"<w:pPr><w:rPr /></w:pPr>"#);
265    }
266
267    #[test]
268    fn test_bidi(){
269        let c=ParagraphProperty::new().bidi(true);
270        let b=c.build();
271        println!("-----Test bidi: {}", str::from_utf8(&b).unwrap());
272        assert_eq!(str::from_utf8(&b).unwrap(),r#"<w:pPr><w:rPr /><w:bidi /></w:pPr>"#);
273    }
274    #[test]
275    fn test_alignment() {
276        let c = ParagraphProperty::new();
277        let b = c.align(AlignmentType::Right).build();
278        assert_eq!(
279            str::from_utf8(&b).unwrap(),
280            r#"<w:pPr><w:rPr /><w:jc w:val="right" /></w:pPr>"#
281        );
282    }
283
284    #[test]
285    fn test_indent() {
286        let c = ParagraphProperty::new();
287        let b = c.indent(Some(20), None, None, None).build();
288        assert_eq!(
289            str::from_utf8(&b).unwrap(),
290            r#"<w:pPr><w:rPr /><w:ind w:left="20" w:right="0" /></w:pPr>"#
291        );
292    }
293
294    #[test]
295    fn test_keep_next() {
296        let c = ParagraphProperty::new();
297        let b = c.keep_next(true).build();
298        assert_eq!(
299            str::from_utf8(&b).unwrap(),
300            r#"<w:pPr><w:rPr /><w:keepNext /></w:pPr>"#
301        );
302    }
303
304    #[test]
305    fn test_outline_lvl() {
306        let props = ParagraphProperty::new();
307        let bytes = props.outline_lvl(1).build();
308        assert_eq!(
309            str::from_utf8(&bytes).unwrap(),
310            r#"<w:pPr><w:rPr /><w:outlineLvl w:val="1" /></w:pPr>"#
311        )
312    }
313
314    #[test]
315    fn test_indent_json() {
316        let c = ParagraphProperty::new();
317        let b = c.indent(Some(20), Some(SpecialIndentType::FirstLine(10)), None, None);
318        assert_eq!(
319            serde_json::to_string(&b).unwrap(),
320            r#"{"runProperty":{},"indent":{"start":20,"startChars":null,"end":null,"specialIndent":{"type":"firstLine","val":10},"hangingChars":null,"firstLineChars":null},"tabs":[]}"#
321        );
322    }
323
324    #[test]
325    fn test_line_spacing() {
326        let props = ParagraphProperty::new();
327        let spacing = LineSpacing::new()
328            .line_rule(LineSpacingType::AtLeast)
329            .line(100);
330        let bytes = props.line_spacing(spacing).build();
331        assert_eq!(
332            str::from_utf8(&bytes).unwrap(),
333            r#"<w:pPr><w:rPr /><w:spacing w:line="100" w:lineRule="atLeast" /></w:pPr>"#
334        )
335    }
336}