docx_rs/documents/elements/
paragraph_property.rs1use 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 #[serde(skip_serializing_if = "Option::is_none")]
52 pub shading: Option<Shading>,
53 #[serde(skip_serializing_if = "Option::is_none")]
55 pub(crate) div_id: Option<String>,
56}
57
58impl ParagraphProperty {
64 pub fn new() -> ParagraphProperty {
65 Default::default()
66 }
67
68 pub fn align(mut self, alignment_type: AlignmentType) -> Self {
69 self.alignment = Some(Justification::new(alignment_type.to_string()));
70 self
71 }
72
73 pub fn style(mut self, style_id: &str) -> Self {
74 self.style = Some(ParagraphStyle::new(Some(style_id)));
75 self
76 }
77
78 pub fn indent(
79 mut self,
80 left: Option<i32>,
81 special_indent: Option<SpecialIndentType>,
82 end: Option<i32>,
83 start_chars: Option<i32>,
84 ) -> Self {
85 self.indent = Some(Indent::new(left, special_indent, end, start_chars));
86 self
87 }
88
89 pub fn numbering(mut self, id: NumberingId, level: IndentLevel) -> Self {
90 self.numbering_property = Some(NumberingProperty::new().add_num(id, level));
91 self
92 }
93
94 pub fn numbering_property(mut self, np: NumberingProperty) -> Self {
95 self.numbering_property = Some(np);
96 self
97 }
98
99 pub fn line_spacing(mut self, spacing: LineSpacing) -> Self {
100 self.line_spacing = Some(spacing);
101 self
102 }
103
104 pub fn character_spacing(mut self, spacing: i32) -> Self {
105 self.run_property.character_spacing = Some(CharacterSpacing::new(spacing));
106 self
107 }
108
109 pub fn snap_to_grid(mut self, v: bool) -> Self {
110 self.snap_to_grid = Some(v);
111 self
112 }
113
114 pub fn shading(mut self, s: Shading) -> Self {
115 self.shading = Some(s);
116 self
117 }
118
119 pub fn keep_next(mut self, v: bool) -> Self {
120 self.keep_next = Some(v);
121 self
122 }
123
124 pub fn keep_lines(mut self, v: bool) -> Self {
125 self.keep_lines = Some(v);
126 self
127 }
128
129 pub fn outline_lvl(mut self, v: usize) -> Self {
130 if v >= 10 {
131 self.outline_lvl = Some(OutlineLvl::new(9));
133 return self;
134 }
135 self.outline_lvl = Some(OutlineLvl::new(v));
136 self
137 }
138
139 pub fn page_break_before(mut self, v: bool) -> Self {
140 self.page_break_before = Some(v);
141 self
142 }
143 pub fn bidi(mut self, v: bool) -> Self {
144 self.bidi = Some(v);
145 self
146 }
147 pub fn widow_control(mut self, v: bool) -> Self {
148 self.widow_control = Some(v);
149 self
150 }
151
152 pub fn add_tab(mut self, t: Tab) -> Self {
153 self.tabs.push(t);
154 self
155 }
156
157 pub fn section_property(mut self, s: SectionProperty) -> Self {
158 self.section_property = Some(s);
159 self
160 }
161
162 pub fn paragraph_property_change(mut self, p: ParagraphPropertyChange) -> Self {
163 self.paragraph_property_change = Some(p);
164 self
165 }
166
167 pub fn frame_property(mut self, s: FrameProperty) -> Self {
168 self.frame_property = Some(s);
169 self
170 }
171
172 pub fn run_property(mut self, s: RunProperty) -> Self {
173 self.run_property = s;
174 self
175 }
176
177 pub fn text_alignment(mut self, s: TextAlignmentType) -> Self {
178 self.text_alignment = Some(TextAlignment::new(s));
179 self
180 }
181
182 pub fn adjust_right_ind(mut self, s: isize) -> Self {
183 self.adjust_right_ind = Some(AdjustRightInd::new(s));
184 self
185 }
186
187 pub(crate) fn hanging_chars(mut self, chars: i32) -> Self {
188 if let Some(indent) = self.indent {
189 self.indent = Some(indent.hanging_chars(chars));
190 }
191 self
192 }
193
194 pub(crate) fn first_line_chars(mut self, chars: i32) -> Self {
195 if let Some(indent) = self.indent {
196 self.indent = Some(indent.first_line_chars(chars));
197 }
198 self
199 }
200
201 pub fn set_borders(mut self, borders: ParagraphBorders) -> Self {
202 self.borders = Some(borders);
203 self
204 }
205
206 pub fn set_border(mut self, border: ParagraphBorder) -> Self {
207 self.borders = Some(self.borders.unwrap_or_default().set(border));
208 self
209 }
210
211 pub fn clear_border(mut self, position: ParagraphBorderPosition) -> Self {
212 self.borders = Some(self.borders.unwrap_or_default().clear(position));
213 self
214 }
215
216 pub fn clear_all_borders(mut self) -> Self {
217 self.borders = Some(self.borders.unwrap_or_default().clear_all());
218 self
219 }
220}
221
222impl BuildXML for ParagraphProperty {
223 fn build_to<W: Write>(
224 &self,
225 stream: crate::xml::writer::EventWriter<W>,
226 ) -> crate::xml::writer::Result<crate::xml::writer::EventWriter<W>> {
227 XMLBuilder::from(stream)
228 .open_paragraph_property()?
229 .add_child(&self.run_property)?
230 .add_optional_child(&self.style)?
231 .add_optional_child(&self.numbering_property)?
232 .add_optional_child(&self.frame_property)?
233 .add_optional_child(&self.alignment)?
234 .add_optional_child(&self.indent)?
235 .add_optional_child(&self.line_spacing)?
236 .add_optional_child(&self.outline_lvl)?
237 .add_optional_child(&self.paragraph_property_change)?
238 .add_optional_child(&self.borders)?
239 .add_optional_child(&self.shading)?
240 .add_optional_child(&self.text_alignment)?
241 .add_optional_child(&self.adjust_right_ind)?
242 .apply_opt(self.snap_to_grid, |v, b| b.snap_to_grid(v))?
243 .apply_if(self.keep_next, |b| b.keep_next())?
244 .apply_if(self.keep_lines, |b| b.keep_lines())?
245 .apply_if(self.page_break_before, |b| b.page_break_before())?
246 .apply_if(self.bidi, |b| b.bidi())?
247 .apply_opt(self.widow_control, |flag, b| {
248 b.widow_control(if flag { "1" } else { "0" })
249 })?
250 .apply_if(!self.tabs.is_empty(), |b| {
251 b.open_tabs()?
252 .apply_each(&self.tabs, |tab, b| b.tab(tab.val, tab.leader, tab.pos))?
253 .close()
254 })?
255 .add_optional_child(&self.section_property)?
256 .close()?
257 .into_inner()
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264 use crate::types::LineSpacingType;
265 use crate::types::SectionType;
266 #[cfg(test)]
267 use pretty_assertions::assert_eq;
268 use std::str;
269
270 #[test]
271 fn test_default() {
272 let c = ParagraphProperty::new();
273 let b = c.build();
274 assert_eq!(str::from_utf8(&b).unwrap(), r#"<w:pPr><w:rPr /></w:pPr>"#);
275 }
276
277 #[test]
278 fn test_bidi() {
279 let c = ParagraphProperty::new().bidi(true);
280 let b = c.build();
281 println!("-----Test bidi: {}", str::from_utf8(&b).unwrap());
282 assert_eq!(
283 str::from_utf8(&b).unwrap(),
284 r#"<w:pPr><w:rPr /><w:bidi /></w:pPr>"#
285 );
286 }
287 #[test]
288 fn test_alignment() {
289 let c = ParagraphProperty::new();
290 let b = c.align(AlignmentType::Right).build();
291 assert_eq!(
292 str::from_utf8(&b).unwrap(),
293 r#"<w:pPr><w:rPr /><w:jc w:val="right" /></w:pPr>"#
294 );
295 }
296
297 #[test]
298 fn test_indent() {
299 let c = ParagraphProperty::new();
300 let b = c.indent(Some(20), None, None, None).build();
301 assert_eq!(
302 str::from_utf8(&b).unwrap(),
303 r#"<w:pPr><w:rPr /><w:ind w:left="20" w:right="0" /></w:pPr>"#
304 );
305 }
306
307 #[test]
308 fn test_keep_next() {
309 let c = ParagraphProperty::new();
310 let b = c.keep_next(true).build();
311 assert_eq!(
312 str::from_utf8(&b).unwrap(),
313 r#"<w:pPr><w:rPr /><w:keepNext /></w:pPr>"#
314 );
315 }
316
317 #[test]
318 fn test_outline_lvl() {
319 let props = ParagraphProperty::new();
320 let bytes = props.outline_lvl(1).build();
321 assert_eq!(
322 str::from_utf8(&bytes).unwrap(),
323 r#"<w:pPr><w:rPr /><w:outlineLvl w:val="1" /></w:pPr>"#
324 )
325 }
326
327 #[test]
328 fn test_indent_json() {
329 let c = ParagraphProperty::new();
330 let b = c.indent(Some(20), Some(SpecialIndentType::FirstLine(10)), None, None);
331 assert_eq!(
332 serde_json::to_string(&b).unwrap(),
333 r#"{"runProperty":{},"indent":{"start":20,"startChars":null,"end":null,"specialIndent":{"type":"firstLine","val":10},"hangingChars":null,"firstLineChars":null},"tabs":[]}"#
334 );
335 }
336
337 #[test]
338 fn test_line_spacing() {
339 let props = ParagraphProperty::new();
340 let spacing = LineSpacing::new()
341 .line_rule(LineSpacingType::AtLeast)
342 .line(100);
343 let bytes = props.line_spacing(spacing).build();
344 assert_eq!(
345 str::from_utf8(&bytes).unwrap(),
346 r#"<w:pPr><w:rPr /><w:spacing w:line="100" w:lineRule="atLeast" /></w:pPr>"#
347 )
348 }
349
350 #[test]
351 fn test_shading() {
352 let props = ParagraphProperty::new().shading(Shading::new().fill("F0F0F0"));
353 let bytes = props.build();
354 assert_eq!(
355 str::from_utf8(&bytes).unwrap(),
356 r#"<w:pPr><w:rPr /><w:shd w:val="clear" w:color="auto" w:fill="F0F0F0" /></w:pPr>"#
357 );
358 }
359
360 #[test]
361 fn test_section_property() {
362 let props = ParagraphProperty::new().section_property(SectionProperty {
363 section_type: Some(SectionType::NextPage),
364 ..Default::default()
365 });
366 let bytes = props.build();
367 assert_eq!(
368 str::from_utf8(&bytes).unwrap(),
369 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>"#
370 )
371 }
372}