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")]
53 pub(crate) div_id: Option<String>,
54}
55
56impl 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 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}