Skip to main content

docx_rs/documents/elements/
run_property.rs

1use serde::Serialize;
2use std::io::Write;
3
4use super::*;
5use crate::documents::BuildXML;
6use crate::types::*;
7use crate::xml_builder::*;
8
9#[derive(Debug, Clone, Serialize, PartialEq, Default)]
10#[serde(rename_all = "camelCase")]
11pub struct RunProperty {
12    #[serde(skip_serializing_if = "Option::is_none")]
13    pub style: Option<RunStyle>,
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub sz: Option<Sz>,
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub sz_cs: Option<SzCs>,
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub color: Option<Color>,
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub highlight: Option<Highlight>,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub vert_align: Option<VertAlign>,
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub underline: Option<Underline>,
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub bold: Option<Bold>,
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub bold_cs: Option<BoldCs>,
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub caps: Option<Caps>,
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub italic: Option<Italic>,
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub italic_cs: Option<ItalicCs>,
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub vanish: Option<Vanish>,
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub spec_vanish: Option<SpecVanish>,
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub character_spacing: Option<CharacterSpacing>,
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub fit_text: Option<FitText>,
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub stretch: Option<Stretch>,
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub fonts: Option<RunFonts>,
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub text_border: Option<TextBorder>,
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub del: Option<Delete>,
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub ins: Option<Insert>,
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub strike: Option<Strike>,
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub dstrike: Option<Dstrike>,
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub positional_tab: Option<PositionalTab>,
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub shading: Option<Shading>,
62}
63
64impl RunProperty {
65    pub fn new() -> RunProperty {
66        Default::default()
67    }
68
69    pub fn style(mut self, style_id: &str) -> Self {
70        self.style = Some(RunStyle::new(style_id));
71        self
72    }
73
74    pub fn size(mut self, size: usize) -> RunProperty {
75        self.sz = Some(Sz::new(size));
76        self.sz_cs = Some(SzCs::new(size));
77        self
78    }
79
80    pub fn spacing(mut self, spacing: i32) -> RunProperty {
81        self.character_spacing = Some(CharacterSpacing::new(spacing));
82        self
83    }
84
85    pub fn color(mut self, color: impl Into<String>) -> RunProperty {
86        self.color = Some(Color::new(color));
87        self
88    }
89
90    /// Sets the theme color reference (`w:themeColor`) on the run color.
91    ///
92    /// The hex value set via [`color`](Self::color) is kept as a fallback. If no
93    /// color has been set yet this defaults the fallback to `auto`.
94    pub fn theme_color(mut self, theme_color: crate::types::ThemeColor) -> RunProperty {
95        let color = self.color.take().unwrap_or_else(|| Color::new("auto"));
96        self.color = Some(color.theme_color(theme_color));
97        self
98    }
99
100    /// Sets the theme shade modifier (`w:themeShade`) on the run color.
101    pub fn theme_shade(mut self, theme_shade: impl Into<String>) -> RunProperty {
102        let color = self.color.take().unwrap_or_else(|| Color::new("auto"));
103        self.color = Some(color.theme_shade(theme_shade));
104        self
105    }
106
107    /// Sets the theme tint modifier (`w:themeTint`) on the run color.
108    pub fn theme_tint(mut self, theme_tint: impl Into<String>) -> RunProperty {
109        let color = self.color.take().unwrap_or_else(|| Color::new("auto"));
110        self.color = Some(color.theme_tint(theme_tint));
111        self
112    }
113
114    pub fn highlight(mut self, color: impl Into<String>) -> RunProperty {
115        self.highlight = Some(Highlight::new(color));
116        self
117    }
118
119    pub fn vert_align(mut self, a: VertAlignType) -> Self {
120        self.vert_align = Some(VertAlign::new(a));
121        self
122    }
123
124    pub fn bold(mut self) -> RunProperty {
125        self.bold = Some(Bold::new());
126        self.bold_cs = Some(BoldCs::new());
127        self
128    }
129
130    pub fn disable_bold(mut self) -> RunProperty {
131        self.bold = Some(Bold::new().disable());
132        self.bold_cs = Some(BoldCs::new().disable());
133        self
134    }
135
136    pub fn caps(mut self) -> RunProperty {
137        self.caps = Some(Caps::new());
138        self
139    }
140
141    pub fn italic(mut self) -> RunProperty {
142        self.italic = Some(Italic::new());
143        self.italic_cs = Some(ItalicCs::new());
144        self
145    }
146
147    pub fn strike(mut self) -> RunProperty {
148        self.strike = Some(Strike::new());
149        self.dstrike = None;
150        self
151    }
152
153    pub fn disable_strike(mut self) -> RunProperty {
154        self.strike = Some(Strike::new().disable());
155        self
156    }
157
158    pub fn dstrike(mut self) -> RunProperty {
159        self.dstrike = Some(Dstrike::new());
160        self.strike = None;
161        self
162    }
163
164    pub fn disable_dstrike(mut self) -> RunProperty {
165        self.dstrike = Some(Dstrike::new().disable());
166        self
167    }
168
169    pub fn disable_italic(mut self) -> RunProperty {
170        self.italic = Some(Italic::new().disable());
171        self.italic_cs = Some(ItalicCs::new().disable());
172        self
173    }
174
175    pub fn underline(mut self, line_type: impl Into<String>) -> RunProperty {
176        self.underline = Some(Underline::new(line_type));
177        self
178    }
179
180    pub fn vanish(mut self) -> RunProperty {
181        self.vanish = Some(Vanish::new());
182        self
183    }
184
185    pub fn spec_vanish(mut self) -> RunProperty {
186        self.spec_vanish = Some(SpecVanish::new());
187        self
188    }
189
190    pub fn fonts(mut self, font: RunFonts) -> RunProperty {
191        self.fonts = Some(font);
192        self
193    }
194
195    pub fn character_spacing(mut self, v: i32) -> RunProperty {
196        self.character_spacing = Some(CharacterSpacing::new(v));
197        self
198    }
199
200    pub fn stretch(mut self, v: i32) -> RunProperty {
201        self.stretch = Some(Stretch::new(v));
202        self
203    }
204
205    pub fn fit_text(mut self, val: usize, id: Option<u32>) -> RunProperty {
206        let mut fit_text = FitText::new(val);
207        if let Some(id) = id {
208            fit_text = fit_text.id(id);
209        }
210        self.fit_text = Some(fit_text);
211        self
212    }
213
214    pub fn text_border(mut self, b: TextBorder) -> Self {
215        self.text_border = Some(b);
216        self
217    }
218
219    pub fn delete(mut self, d: Delete) -> Self {
220        self.del = Some(d);
221        self
222    }
223
224    pub fn insert(mut self, i: Insert) -> Self {
225        self.ins = Some(i);
226        self
227    }
228
229    pub fn ptab(mut self, ptab: PositionalTab) -> Self {
230        self.positional_tab = Some(ptab);
231        self
232    }
233
234    pub fn shading(mut self, s: Shading) -> Self {
235        self.shading = Some(s);
236        self
237    }
238}
239
240impl BuildXML for RunProperty {
241    fn build_to<W: Write>(
242        &self,
243        stream: crate::xml::writer::EventWriter<W>,
244    ) -> crate::xml::writer::Result<crate::xml::writer::EventWriter<W>> {
245        XMLBuilder::from(stream)
246            .open_run_property()?
247            .add_optional_child(&self.sz)?
248            .add_optional_child(&self.sz_cs)?
249            .add_optional_child(&self.color)?
250            .add_optional_child(&self.bold)?
251            .add_optional_child(&self.bold_cs)?
252            .add_optional_child(&self.caps)?
253            .add_optional_child(&self.italic)?
254            .add_optional_child(&self.italic_cs)?
255            .add_optional_child(&self.strike)?
256            .add_optional_child(&self.dstrike)?
257            .add_optional_child(&self.highlight)?
258            .add_optional_child(&self.underline)?
259            .add_optional_child(&self.vanish)?
260            .add_optional_child(&self.spec_vanish)?
261            .add_optional_child(&self.fonts)?
262            .add_optional_child(&self.text_border)?
263            .add_optional_child(&self.ins)?
264            .add_optional_child(&self.del)?
265            .add_optional_child(&self.vert_align)?
266            .add_optional_child(&self.character_spacing)?
267            .add_optional_child(&self.fit_text)?
268            .add_optional_child(&self.stretch)?
269            .add_optional_child(&self.style)?
270            .add_optional_child(&self.positional_tab)?
271            .add_optional_child(&self.shading)?
272            .close()?
273            .into_inner()
274    }
275}
276
277#[cfg(test)]
278mod tests {
279
280    use super::*;
281    #[cfg(test)]
282    use pretty_assertions::assert_eq;
283    use std::str;
284
285    #[test]
286    fn test_size() {
287        let c = RunProperty::new().size(10).color("FFFFFF");
288        let b = c.build();
289        assert_eq!(
290            str::from_utf8(&b).unwrap(),
291            r#"<w:rPr><w:sz w:val="10" /><w:szCs w:val="10" /><w:color w:val="FFFFFF" /></w:rPr>"#
292        );
293    }
294
295    #[test]
296    fn test_highlight() {
297        let c = RunProperty::new().highlight("FFFFFF");
298        let b = c.build();
299        assert_eq!(
300            str::from_utf8(&b).unwrap(),
301            r#"<w:rPr><w:highlight w:val="FFFFFF" /></w:rPr>"#
302        );
303    }
304
305    #[test]
306    fn test_bold() {
307        let c = RunProperty::new().bold();
308        let b = c.build();
309        assert_eq!(
310            str::from_utf8(&b).unwrap(),
311            r#"<w:rPr><w:b /><w:bCs /></w:rPr>"#
312        );
313    }
314
315    #[test]
316    fn test_strike() {
317        let c = RunProperty::new().strike();
318        let b = c.build();
319        assert_eq!(
320            str::from_utf8(&b).unwrap(),
321            r#"<w:rPr><w:strike /></w:rPr>"#
322        );
323    }
324
325    #[test]
326    fn test_underline() {
327        let c = RunProperty::new().underline("single");
328        let b = c.build();
329        assert_eq!(
330            str::from_utf8(&b).unwrap(),
331            r#"<w:rPr><w:u w:val="single" /></w:rPr>"#
332        );
333    }
334
335    #[test]
336    fn test_vanish() {
337        let c = RunProperty::new().vanish();
338        let b = c.build();
339        assert_eq!(
340            str::from_utf8(&b).unwrap(),
341            r#"<w:rPr><w:vanish /></w:rPr>"#
342        );
343    }
344
345    #[test]
346    fn test_run_fonts() {
347        let c = RunProperty::new().fonts(RunFonts::new().east_asia("Hiragino"));
348        let b = c.build();
349        assert_eq!(
350            str::from_utf8(&b).unwrap(),
351            r#"<w:rPr><w:rFonts w:eastAsia="Hiragino" /></w:rPr>"#
352        );
353    }
354
355    #[test]
356    fn test_character_spacing() {
357        let c = RunProperty::new().spacing(20);
358        let b = c.build();
359        assert_eq!(
360            str::from_utf8(&b).unwrap(),
361            r#"<w:rPr><w:spacing w:val="20" /></w:rPr>"#
362        );
363    }
364
365    #[test]
366    fn test_stretch() {
367        let c = RunProperty::new().stretch(80);
368        let b = c.build();
369        assert_eq!(
370            str::from_utf8(&b).unwrap(),
371            r#"<w:rPr><w:w w:val="80" /></w:rPr>"#
372        );
373    }
374
375    #[test]
376    fn test_fit_text() {
377        let c = RunProperty::new().fit_text(840, Some(1266434317));
378        let b = c.build();
379        assert_eq!(
380            str::from_utf8(&b).unwrap(),
381            r#"<w:rPr><w:fitText w:val="840" w:id="1266434317" /></w:rPr>"#
382        );
383    }
384
385    #[test]
386    fn test_ptab() {
387        let c = RunProperty::new().ptab(PositionalTab::new(
388            PositionalTabAlignmentType::Left,
389            PositionalTabRelativeTo::Margin,
390            TabLeaderType::None,
391        ));
392        let b = c.build();
393        assert_eq!(
394            str::from_utf8(&b).unwrap(),
395            r#"<w:rPr><w:ptab w:alignment="left" w:relativeTo="margin" w:leader="none" /></w:rPr>"#
396        );
397    }
398
399    #[test]
400    fn test_character_shading() {
401        let c = RunProperty::new().shading(
402            Shading::new()
403                .shd_type(ShdType::Clear)
404                .fill("FFFFFF")
405                .color("auto"),
406        );
407        let b = c.build();
408        assert_eq!(
409            str::from_utf8(&b).unwrap(),
410            r#"<w:rPr><w:shd w:val="clear" w:color="auto" w:fill="FFFFFF" /></w:rPr>"#
411        );
412    }
413
414    #[test]
415    fn test_dstrike() {
416        let c = RunProperty::new().dstrike();
417        let b = c.build();
418        assert_eq!(
419            str::from_utf8(&b).unwrap(),
420            r#"<w:rPr><w:dstrike /></w:rPr>"#
421        );
422    }
423}