Skip to main content

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            .add_optional_child(&self.section_property)?
248            .close()?
249            .into_inner()
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256    use crate::types::SectionType;
257    use crate::types::LineSpacingType;
258    #[cfg(test)]
259    use pretty_assertions::assert_eq;
260    use std::str;
261
262    #[test]
263    fn test_default() {
264        let c = ParagraphProperty::new();
265        let b = c.build();
266        assert_eq!(str::from_utf8(&b).unwrap(), r#"<w:pPr><w:rPr /></w:pPr>"#);
267    }
268
269    #[test]
270    fn test_bidi(){
271        let c=ParagraphProperty::new().bidi(true);
272        let b=c.build();
273        println!("-----Test bidi: {}", str::from_utf8(&b).unwrap());
274        assert_eq!(str::from_utf8(&b).unwrap(),r#"<w:pPr><w:rPr /><w:bidi /></w:pPr>"#);
275    }
276    #[test]
277    fn test_alignment() {
278        let c = ParagraphProperty::new();
279        let b = c.align(AlignmentType::Right).build();
280        assert_eq!(
281            str::from_utf8(&b).unwrap(),
282            r#"<w:pPr><w:rPr /><w:jc w:val="right" /></w:pPr>"#
283        );
284    }
285
286    #[test]
287    fn test_indent() {
288        let c = ParagraphProperty::new();
289        let b = c.indent(Some(20), None, None, None).build();
290        assert_eq!(
291            str::from_utf8(&b).unwrap(),
292            r#"<w:pPr><w:rPr /><w:ind w:left="20" w:right="0" /></w:pPr>"#
293        );
294    }
295
296    #[test]
297    fn test_keep_next() {
298        let c = ParagraphProperty::new();
299        let b = c.keep_next(true).build();
300        assert_eq!(
301            str::from_utf8(&b).unwrap(),
302            r#"<w:pPr><w:rPr /><w:keepNext /></w:pPr>"#
303        );
304    }
305
306    #[test]
307    fn test_outline_lvl() {
308        let props = ParagraphProperty::new();
309        let bytes = props.outline_lvl(1).build();
310        assert_eq!(
311            str::from_utf8(&bytes).unwrap(),
312            r#"<w:pPr><w:rPr /><w:outlineLvl w:val="1" /></w:pPr>"#
313        )
314    }
315
316    #[test]
317    fn test_indent_json() {
318        let c = ParagraphProperty::new();
319        let b = c.indent(Some(20), Some(SpecialIndentType::FirstLine(10)), None, None);
320        assert_eq!(
321            serde_json::to_string(&b).unwrap(),
322            r#"{"runProperty":{},"indent":{"start":20,"startChars":null,"end":null,"specialIndent":{"type":"firstLine","val":10},"hangingChars":null,"firstLineChars":null},"tabs":[]}"#
323        );
324    }
325
326    #[test]
327    fn test_line_spacing() {
328        let props = ParagraphProperty::new();
329        let spacing = LineSpacing::new()
330            .line_rule(LineSpacingType::AtLeast)
331            .line(100);
332        let bytes = props.line_spacing(spacing).build();
333        assert_eq!(
334            str::from_utf8(&bytes).unwrap(),
335            r#"<w:pPr><w:rPr /><w:spacing w:line="100" w:lineRule="atLeast" /></w:pPr>"#
336        )
337    }
338
339    #[test]
340    fn test_section_property() {
341        let props = ParagraphProperty::new().section_property(SectionProperty {
342            section_type: Some(SectionType::NextPage),
343            ..Default::default()
344        });
345        let bytes = props.build();
346        assert_eq!(
347            str::from_utf8(&bytes).unwrap(),
348            r#"<w:pPr><w:rPr /><w:sectPr><w:pgSz w:w="11906" w:h="16838" /><w:pgMar w:top="1985" w:right="1701" w:bottom="1701" w:left="1701" w:header="851" w:footer="992" w:gutter="0" /><w:cols w:space="425" w:num="1" /><w:type w:val="nextPage" /></w:sectPr></w:pPr>"#
349        )
350    }
351}