1use crate::types;
8#[cfg(feature = "extra-children")]
9use ooxml_xml::PositionedNode;
10use ooxml_xml::{RawXmlElement, RawXmlNode};
11
12impl types::Body {
17 pub fn add_paragraph(&mut self) -> &mut types::Paragraph {
19 self.block_content
20 .push(types::BlockContent::P(Box::default()));
21 match self.block_content.last_mut().unwrap() {
22 types::BlockContent::P(p) => p.as_mut(),
23 _ => unreachable!(),
24 }
25 }
26
27 #[cfg(feature = "wml-tables")]
29 pub fn add_table(&mut self) -> &mut types::Table {
30 let table = types::Table {
31 range_markup: Vec::new(),
32 table_properties: Box::new(types::TableProperties::default()),
33 tbl_grid: Box::new(types::TableGrid::default()),
34 rows: Vec::new(),
35 #[cfg(feature = "extra-children")]
36 extra_children: Vec::new(),
37 };
38 self.block_content
39 .push(types::BlockContent::Tbl(Box::new(table)));
40 match self.block_content.last_mut().unwrap() {
41 types::BlockContent::Tbl(t) => t.as_mut(),
42 _ => unreachable!(),
43 }
44 }
45
46 #[cfg(feature = "wml-layout")]
48 pub fn set_section_properties(&mut self, sect_pr: types::SectionProperties) {
49 self.sect_pr = Some(Box::new(sect_pr));
50 }
51}
52
53impl types::Paragraph {
58 pub fn add_run(&mut self) -> &mut types::Run {
60 self.paragraph_content
61 .push(types::ParagraphContent::R(Box::default()));
62 match self.paragraph_content.last_mut().unwrap() {
63 types::ParagraphContent::R(r) => r.as_mut(),
64 _ => unreachable!(),
65 }
66 }
67
68 #[cfg(feature = "wml-hyperlinks")]
70 pub fn add_hyperlink(&mut self) -> &mut types::Hyperlink {
71 self.paragraph_content
72 .push(types::ParagraphContent::Hyperlink(Box::default()));
73 match self.paragraph_content.last_mut().unwrap() {
74 types::ParagraphContent::Hyperlink(h) => h.as_mut(),
75 _ => unreachable!(),
76 }
77 }
78
79 pub fn add_bookmark_start_u32(&mut self, id: u32, name: &str) {
84 self.add_bookmark_start(id as i64, name);
85 }
86
87 pub fn add_bookmark_end_u32(&mut self, id: u32) {
92 self.add_bookmark_end(id as i64);
93 }
94
95 pub fn add_bookmark_start(&mut self, id: i64, name: &str) {
97 let bookmark = types::Bookmark {
98 id,
99 name: name.to_string(),
100 #[cfg(feature = "wml-settings")]
101 displaced_by_custom_xml: None,
102 #[cfg(feature = "wml-tables")]
103 col_first: None,
104 #[cfg(feature = "wml-tables")]
105 col_last: None,
106 #[cfg(feature = "extra-attrs")]
107 extra_attrs: Default::default(),
108 };
109 self.paragraph_content
110 .push(types::ParagraphContent::BookmarkStart(Box::new(bookmark)));
111 }
112
113 pub fn add_bookmark_end(&mut self, id: i64) {
115 let range = types::CTMarkupRange {
116 id,
117 #[cfg(feature = "wml-settings")]
118 displaced_by_custom_xml: None,
119 #[cfg(feature = "extra-attrs")]
120 extra_attrs: Default::default(),
121 };
122 self.paragraph_content
123 .push(types::ParagraphContent::BookmarkEnd(Box::new(range)));
124 }
125
126 pub fn add_comment_range_start(&mut self, id: u32) {
128 let range = types::CTMarkupRange {
129 id: id as i64,
130 #[cfg(feature = "wml-settings")]
131 displaced_by_custom_xml: None,
132 #[cfg(feature = "extra-attrs")]
133 extra_attrs: Default::default(),
134 };
135 self.paragraph_content
136 .push(types::ParagraphContent::CommentRangeStart(Box::new(range)));
137 }
138
139 pub fn add_comment_range_end(&mut self, id: u32) {
141 let range = types::CTMarkupRange {
142 id: id as i64,
143 #[cfg(feature = "wml-settings")]
144 displaced_by_custom_xml: None,
145 #[cfg(feature = "extra-attrs")]
146 extra_attrs: Default::default(),
147 };
148 self.paragraph_content
149 .push(types::ParagraphContent::CommentRangeEnd(Box::new(range)));
150 }
151
152 #[cfg(feature = "wml-styling")]
154 pub fn set_properties(&mut self, props: types::ParagraphProperties) {
155 self.p_pr = Some(Box::new(props));
156 }
157
158 #[cfg(feature = "wml-styling")]
160 pub fn set_numbering(&mut self, num_id: u32, ilvl: u32) {
161 let ppr = self
162 .p_pr
163 .get_or_insert_with(|| Box::new(types::ParagraphProperties::default()));
164 ppr.num_pr = Some(Box::new(types::NumberingProperties {
165 ilvl: Some(Box::new(types::CTDecimalNumber {
166 value: ilvl as i64,
167 #[cfg(feature = "extra-attrs")]
168 extra_attrs: Default::default(),
169 })),
170 num_id: Some(Box::new(types::CTDecimalNumber {
171 value: num_id as i64,
172 #[cfg(feature = "extra-attrs")]
173 extra_attrs: Default::default(),
174 })),
175 numbering_change: None,
176 ins: None,
177 #[cfg(feature = "extra-children")]
178 extra_children: Vec::new(),
179 }));
180 }
181
182 #[cfg(feature = "wml-styling")]
186 pub fn set_alignment(&mut self, alignment: types::STJc) {
187 let ppr = self
188 .p_pr
189 .get_or_insert_with(|| Box::new(types::ParagraphProperties::default()));
190 ppr.justification = Some(Box::new(types::CTJc {
191 value: alignment,
192 #[cfg(feature = "extra-attrs")]
193 extra_attrs: Default::default(),
194 }));
195 }
196
197 #[cfg(feature = "wml-styling")]
199 pub fn set_spacing(&mut self, before: Option<u32>, after: Option<u32>) {
200 let ppr = self
201 .p_pr
202 .get_or_insert_with(|| Box::new(types::ParagraphProperties::default()));
203 ppr.spacing = Some(Box::new(types::CTSpacing {
204 before: before.map(|b| b.to_string()),
205 after: after.map(|a| a.to_string()),
206 ..Default::default()
207 }));
208 }
209
210 #[cfg(feature = "wml-styling")]
212 pub fn set_indent(&mut self, left: Option<u32>, first_line: Option<u32>) {
213 let ppr = self
214 .p_pr
215 .get_or_insert_with(|| Box::new(types::ParagraphProperties::default()));
216 ppr.indentation = Some(Box::new(types::CTInd {
217 left: left.map(|l| l.to_string()),
218 first_line: first_line.map(|fl| fl.to_string()),
219 ..Default::default()
220 }));
221 }
222
223 pub fn add_page_break(&mut self) -> &mut Self {
228 let mut run = types::Run::default();
229 run.run_content
230 .push(types::RunContent::Br(Box::new(types::CTBr {
231 r#type: Some(types::STBrType::Page),
232 clear: None,
233 #[cfg(feature = "extra-attrs")]
234 extra_attrs: Default::default(),
235 })));
236 self.paragraph_content
237 .push(types::ParagraphContent::R(Box::new(run)));
238 self
239 }
240
241 pub fn add_column_break(&mut self) -> &mut Self {
246 let mut run = types::Run::default();
247 run.run_content
248 .push(types::RunContent::Br(Box::new(types::CTBr {
249 r#type: Some(types::STBrType::Column),
250 clear: None,
251 #[cfg(feature = "extra-attrs")]
252 extra_attrs: Default::default(),
253 })));
254 self.paragraph_content
255 .push(types::ParagraphContent::R(Box::new(run)));
256 self
257 }
258
259 #[cfg(feature = "wml-styling")]
264 pub fn set_space_before(&mut self, twips: u32) -> &mut Self {
265 let ppr = self
266 .p_pr
267 .get_or_insert_with(|| Box::new(types::ParagraphProperties::default()));
268 let spacing = ppr
269 .spacing
270 .get_or_insert_with(|| Box::new(types::CTSpacing::default()));
271 spacing.before = Some(twips.to_string());
272 self
273 }
274
275 #[cfg(feature = "wml-styling")]
280 pub fn set_space_after(&mut self, twips: u32) -> &mut Self {
281 let ppr = self
282 .p_pr
283 .get_or_insert_with(|| Box::new(types::ParagraphProperties::default()));
284 let spacing = ppr
285 .spacing
286 .get_or_insert_with(|| Box::new(types::CTSpacing::default()));
287 spacing.after = Some(twips.to_string());
288 self
289 }
290
291 #[cfg(feature = "wml-styling")]
297 pub fn set_line_spacing(&mut self, twips: u32) -> &mut Self {
298 let ppr = self
299 .p_pr
300 .get_or_insert_with(|| Box::new(types::ParagraphProperties::default()));
301 let spacing = ppr
302 .spacing
303 .get_or_insert_with(|| Box::new(types::CTSpacing::default()));
304 spacing.line = Some(twips.to_string());
305 spacing.line_rule = Some(types::STLineSpacingRule::Auto);
306 self
307 }
308
309 #[cfg(feature = "wml-styling")]
314 pub fn set_indent_left(&mut self, twips: u32) -> &mut Self {
315 let ppr = self
316 .p_pr
317 .get_or_insert_with(|| Box::new(types::ParagraphProperties::default()));
318 let ind = ppr
319 .indentation
320 .get_or_insert_with(|| Box::new(types::CTInd::default()));
321 ind.left = Some(twips.to_string());
322 self
323 }
324
325 #[cfg(feature = "wml-styling")]
330 pub fn set_indent_right(&mut self, twips: u32) -> &mut Self {
331 let ppr = self
332 .p_pr
333 .get_or_insert_with(|| Box::new(types::ParagraphProperties::default()));
334 let ind = ppr
335 .indentation
336 .get_or_insert_with(|| Box::new(types::CTInd::default()));
337 ind.right = Some(twips.to_string());
338 self
339 }
340
341 #[cfg(feature = "wml-styling")]
347 pub fn set_indent_first_line(&mut self, twips: u32) -> &mut Self {
348 let ppr = self
349 .p_pr
350 .get_or_insert_with(|| Box::new(types::ParagraphProperties::default()));
351 let ind = ppr
352 .indentation
353 .get_or_insert_with(|| Box::new(types::CTInd::default()));
354 ind.first_line = Some(twips.to_string());
355 self
356 }
357
358 #[cfg(feature = "wml-styling")]
364 pub fn set_outline_level(&mut self, level: u8) -> &mut Self {
365 let ppr = self
366 .p_pr
367 .get_or_insert_with(|| Box::new(types::ParagraphProperties::default()));
368 ppr.outline_lvl = Some(Box::new(types::CTDecimalNumber {
369 value: level as i64,
370 #[cfg(feature = "extra-attrs")]
371 extra_attrs: Default::default(),
372 }));
373 self
374 }
375}
376
377impl types::Run {
382 pub fn set_text(&mut self, text: impl Into<String>) {
384 let t = types::Text {
385 text: Some(text.into()),
386 #[cfg(feature = "extra-children")]
387 extra_children: Vec::new(),
388 };
389 self.run_content.push(types::RunContent::T(Box::new(t)));
390 }
391
392 #[cfg(feature = "wml-styling")]
394 pub fn set_bold(&mut self, bold: bool) {
395 let rpr = self
396 .r_pr
397 .get_or_insert_with(|| Box::new(types::RunProperties::default()));
398 if bold {
399 rpr.bold = Some(Box::new(types::OnOffElement {
400 value: None, #[cfg(feature = "extra-attrs")]
402 extra_attrs: Default::default(),
403 }));
404 } else {
405 rpr.bold = None;
406 }
407 }
408
409 #[cfg(feature = "wml-styling")]
411 pub fn set_italic(&mut self, italic: bool) {
412 let rpr = self
413 .r_pr
414 .get_or_insert_with(|| Box::new(types::RunProperties::default()));
415 if italic {
416 rpr.italic = Some(Box::new(types::OnOffElement {
417 value: None,
418 #[cfg(feature = "extra-attrs")]
419 extra_attrs: Default::default(),
420 }));
421 } else {
422 rpr.italic = None;
423 }
424 }
425
426 pub fn set_page_break(&mut self) {
428 self.run_content
429 .push(types::RunContent::Br(Box::new(types::CTBr {
430 r#type: Some(types::STBrType::Page),
431 clear: None,
432 #[cfg(feature = "extra-attrs")]
433 extra_attrs: Default::default(),
434 })));
435 }
436
437 #[cfg(feature = "wml-styling")]
439 pub fn set_color(&mut self, hex: &str) {
440 let rpr = self
441 .r_pr
442 .get_or_insert_with(|| Box::new(types::RunProperties::default()));
443 rpr.color = Some(Box::new(types::CTColor {
444 value: hex.to_string(),
445 theme_color: None,
446 theme_tint: None,
447 theme_shade: None,
448 #[cfg(feature = "extra-attrs")]
449 extra_attrs: Default::default(),
450 }));
451 }
452
453 #[cfg(feature = "wml-styling")]
455 pub fn set_font_size(&mut self, half_points: i64) {
456 let rpr = self
457 .r_pr
458 .get_or_insert_with(|| Box::new(types::RunProperties::default()));
459 rpr.size = Some(Box::new(types::HpsMeasureElement {
460 value: half_points.to_string(),
461 #[cfg(feature = "extra-attrs")]
462 extra_attrs: Default::default(),
463 }));
464 }
465
466 #[cfg(feature = "wml-styling")]
468 pub fn set_strikethrough(&mut self, strike: bool) {
469 let rpr = self
470 .r_pr
471 .get_or_insert_with(|| Box::new(types::RunProperties::default()));
472 if strike {
473 rpr.strikethrough = Some(Box::new(types::OnOffElement {
474 value: None,
475 #[cfg(feature = "extra-attrs")]
476 extra_attrs: Default::default(),
477 }));
478 } else {
479 rpr.strikethrough = None;
480 }
481 }
482
483 #[cfg(feature = "wml-styling")]
485 pub fn set_underline(&mut self, style: types::STUnderline) {
486 let rpr = self
487 .r_pr
488 .get_or_insert_with(|| Box::new(types::RunProperties::default()));
489 rpr.underline = Some(Box::new(types::CTUnderline {
490 value: Some(style),
491 color: None,
492 theme_color: None,
493 theme_tint: None,
494 theme_shade: None,
495 #[cfg(feature = "extra-attrs")]
496 extra_attrs: Default::default(),
497 }));
498 }
499
500 #[cfg(feature = "wml-styling")]
502 pub fn set_fonts(&mut self, fonts: types::Fonts) {
503 let rpr = self
504 .r_pr
505 .get_or_insert_with(|| Box::new(types::RunProperties::default()));
506 rpr.fonts = Some(Box::new(fonts));
507 }
508
509 #[cfg(feature = "wml-styling")]
511 pub fn set_properties(&mut self, props: types::RunProperties) {
512 self.r_pr = Some(Box::new(props));
513 }
514
515 pub fn add_drawing(&mut self, drawing: types::CTDrawing) {
517 self.run_content
518 .push(types::RunContent::Drawing(Box::new(drawing)));
519 }
520
521 pub fn add_footnote_ref(&mut self, id: i64) {
523 self.run_content
524 .push(types::RunContent::FootnoteReference(Box::new(
525 types::FootnoteEndnoteRef {
526 #[cfg(feature = "wml-comments")]
527 custom_mark_follows: None,
528 id,
529 #[cfg(feature = "extra-attrs")]
530 extra_attrs: Default::default(),
531 },
532 )));
533 }
534
535 pub fn add_endnote_ref(&mut self, id: i64) {
537 self.run_content
538 .push(types::RunContent::EndnoteReference(Box::new(
539 types::FootnoteEndnoteRef {
540 #[cfg(feature = "wml-comments")]
541 custom_mark_follows: None,
542 id,
543 #[cfg(feature = "extra-attrs")]
544 extra_attrs: Default::default(),
545 },
546 )));
547 }
548
549 pub fn add_comment_ref(&mut self, id: i64) {
551 self.run_content
552 .push(types::RunContent::CommentReference(Box::new(
553 types::CTMarkup {
554 id,
555 #[cfg(feature = "extra-attrs")]
556 extra_attrs: Default::default(),
557 },
558 )));
559 }
560
561 #[cfg(feature = "wml-styling")]
563 fn set_on_off(field: &mut Option<Box<types::OnOffElement>>, on: bool) {
564 if on {
565 *field = Some(Box::new(types::OnOffElement {
566 value: None, #[cfg(feature = "extra-attrs")]
568 extra_attrs: Default::default(),
569 }));
570 } else {
571 *field = None;
572 }
573 }
574
575 #[cfg(feature = "wml-styling")]
580 pub fn set_shadow(&mut self, on: bool) {
581 let rpr = self
582 .r_pr
583 .get_or_insert_with(|| Box::new(types::RunProperties::default()));
584 Self::set_on_off(&mut rpr.shadow, on);
585 }
586
587 #[cfg(feature = "wml-styling")]
592 pub fn set_outline(&mut self, on: bool) {
593 let rpr = self
594 .r_pr
595 .get_or_insert_with(|| Box::new(types::RunProperties::default()));
596 Self::set_on_off(&mut rpr.outline, on);
597 }
598
599 #[cfg(feature = "wml-styling")]
604 pub fn set_emboss(&mut self, on: bool) {
605 let rpr = self
606 .r_pr
607 .get_or_insert_with(|| Box::new(types::RunProperties::default()));
608 Self::set_on_off(&mut rpr.emboss, on);
609 }
610
611 #[cfg(feature = "wml-styling")]
616 pub fn set_imprint(&mut self, on: bool) {
617 let rpr = self
618 .r_pr
619 .get_or_insert_with(|| Box::new(types::RunProperties::default()));
620 Self::set_on_off(&mut rpr.imprint, on);
621 }
622
623 #[cfg(feature = "wml-styling")]
628 pub fn set_small_caps(&mut self, on: bool) {
629 let rpr = self
630 .r_pr
631 .get_or_insert_with(|| Box::new(types::RunProperties::default()));
632 Self::set_on_off(&mut rpr.small_caps, on);
633 }
634
635 #[cfg(feature = "wml-styling")]
640 pub fn set_all_caps(&mut self, on: bool) {
641 let rpr = self
642 .r_pr
643 .get_or_insert_with(|| Box::new(types::RunProperties::default()));
644 Self::set_on_off(&mut rpr.caps, on);
645 }
646
647 #[cfg(feature = "wml-styling")]
653 pub fn set_vanish(&mut self, on: bool) {
654 let rpr = self
655 .r_pr
656 .get_or_insert_with(|| Box::new(types::RunProperties::default()));
657 Self::set_on_off(&mut rpr.vanish, on);
658 }
659
660 #[cfg(feature = "wml-styling")]
665 pub fn set_double_strike(&mut self, on: bool) {
666 let rpr = self
667 .r_pr
668 .get_or_insert_with(|| Box::new(types::RunProperties::default()));
669 Self::set_on_off(&mut rpr.dstrike, on);
670 }
671}
672
673#[cfg(feature = "wml-hyperlinks")]
678impl types::Hyperlink {
679 pub fn add_run(&mut self) -> &mut types::Run {
681 self.paragraph_content
682 .push(types::ParagraphContent::R(Box::default()));
683 match self.paragraph_content.last_mut().unwrap() {
684 types::ParagraphContent::R(r) => r.as_mut(),
685 _ => unreachable!(),
686 }
687 }
688
689 pub fn set_rel_id(&mut self, rel_id: &str) {
691 self.id = Some(rel_id.to_string());
692 }
693
694 pub fn set_anchor(&mut self, anchor: &str) {
696 self.anchor = Some(anchor.to_string());
697 }
698}
699
700#[cfg(feature = "wml-tables")]
705impl types::Table {
706 pub fn add_row(&mut self) -> &mut types::CTRow {
708 self.rows.push(types::RowContent::Tr(Box::default()));
709 match self.rows.last_mut().unwrap() {
710 types::RowContent::Tr(r) => r.as_mut(),
711 _ => unreachable!(),
712 }
713 }
714}
715
716#[cfg(feature = "wml-tables")]
717impl types::CTRow {
718 pub fn add_cell(&mut self) -> &mut types::TableCell {
720 self.cells.push(types::CellContent::Tc(Box::default()));
721 match self.cells.last_mut().unwrap() {
722 types::CellContent::Tc(c) => c.as_mut(),
723 _ => unreachable!(),
724 }
725 }
726}
727
728#[cfg(feature = "wml-tables")]
732#[derive(Debug, Clone, Copy, PartialEq, Eq)]
733pub enum VMergeType {
734 Restart,
736 Continue,
738}
739
740#[cfg(feature = "wml-tables")]
741impl types::TableCell {
742 pub fn add_paragraph(&mut self) -> &mut types::Paragraph {
744 self.block_content
745 .push(types::BlockContent::P(Box::default()));
746 match self.block_content.last_mut().unwrap() {
747 types::BlockContent::P(p) => p.as_mut(),
748 _ => unreachable!(),
749 }
750 }
751
752 pub fn set_grid_span(&mut self, n: u32) {
757 let tcpr = self
758 .cell_properties
759 .get_or_insert_with(|| Box::new(types::TableCellProperties::default()));
760 tcpr.grid_span = Some(Box::new(types::CTDecimalNumber {
761 value: n as i64,
762 #[cfg(feature = "extra-attrs")]
763 extra_attrs: Default::default(),
764 }));
765 }
766
767 pub fn set_vertical_merge(&mut self, merge_type: VMergeType) {
774 let tcpr = self
775 .cell_properties
776 .get_or_insert_with(|| Box::new(types::TableCellProperties::default()));
777 tcpr.vertical_merge = Some(Box::new(types::CTVMerge {
778 value: match merge_type {
779 VMergeType::Restart => Some(types::STMerge::Restart),
780 VMergeType::Continue => None,
781 },
782 #[cfg(feature = "extra-attrs")]
783 extra_attrs: Default::default(),
784 }));
785 }
786}
787
788#[cfg(feature = "wml-tables")]
792#[derive(Debug, Clone, Copy, PartialEq, Eq)]
793pub enum BorderStyle {
794 None,
796 Single,
798 Double,
800 Dashed,
802 Dotted,
804 Thick,
806}
807
808#[cfg(feature = "wml-tables")]
809impl BorderStyle {
810 fn to_st_border(self) -> types::STBorder {
811 match self {
812 BorderStyle::None => types::STBorder::None,
813 BorderStyle::Single => types::STBorder::Single,
814 BorderStyle::Double => types::STBorder::Double,
815 BorderStyle::Dashed => types::STBorder::Dashed,
816 BorderStyle::Dotted => types::STBorder::Dotted,
817 BorderStyle::Thick => types::STBorder::Thick,
818 }
819 }
820}
821
822#[cfg(feature = "wml-tables")]
826#[derive(Debug, Clone, Copy, PartialEq, Eq)]
827pub enum TableWidthUnit {
828 Dxa,
830 Pct,
832}
833
834#[cfg(feature = "wml-tables")]
835impl types::CTRow {
836 pub fn set_height(&mut self, twips: u32) {
841 let row_pr = self
842 .row_properties
843 .get_or_insert_with(|| Box::new(types::TableRowProperties::default()));
844 row_pr.tr_height = Some(Box::new(types::CTHeight {
845 value: Some(twips.to_string()),
846 #[cfg(feature = "wml-tables")]
847 h_rule: Some(types::STHeightRule::Exact),
848 #[cfg(feature = "extra-attrs")]
849 extra_attrs: Default::default(),
850 }));
851 }
852}
853
854#[cfg(feature = "wml-tables")]
855impl types::TableCell {
856 pub fn set_background_color(&mut self, rgb: &str) {
862 let tcpr = self
863 .cell_properties
864 .get_or_insert_with(|| Box::new(types::TableCellProperties::default()));
865 tcpr.shading = Some(Box::new(types::CTShd {
866 value: types::STShd::Clear,
867 #[cfg(feature = "wml-styling")]
868 fill: Some(rgb.to_string()),
869 #[cfg(feature = "wml-styling")]
870 color: Some("auto".to_string()),
871 #[cfg(feature = "wml-styling")]
872 theme_color: None,
873 #[cfg(feature = "wml-styling")]
874 theme_tint: None,
875 #[cfg(feature = "wml-styling")]
876 theme_shade: None,
877 #[cfg(feature = "wml-styling")]
878 theme_fill: None,
879 #[cfg(feature = "wml-styling")]
880 theme_fill_tint: None,
881 #[cfg(feature = "wml-styling")]
882 theme_fill_shade: None,
883 #[cfg(feature = "extra-attrs")]
884 extra_attrs: Default::default(),
885 }));
886 }
887
888 pub fn set_borders(&mut self, style: BorderStyle, width_eights: u32, color: &str) {
895 self.set_border_top(style, width_eights, color);
896 self.set_border_bottom(style, width_eights, color);
897 self.set_border_left(style, width_eights, color);
898 self.set_border_right(style, width_eights, color);
899 }
900
901 pub fn set_border_top(&mut self, style: BorderStyle, width_eights: u32, color: &str) {
905 let tcpr = self
906 .cell_properties
907 .get_or_insert_with(|| Box::new(types::TableCellProperties::default()));
908 let borders = tcpr
909 .tc_borders
910 .get_or_insert_with(|| Box::new(types::CTTcBorders::default()));
911 borders.top = Some(Box::new(make_cell_border(style, width_eights, color)));
912 }
913
914 pub fn set_border_bottom(&mut self, style: BorderStyle, width_eights: u32, color: &str) {
918 let tcpr = self
919 .cell_properties
920 .get_or_insert_with(|| Box::new(types::TableCellProperties::default()));
921 let borders = tcpr
922 .tc_borders
923 .get_or_insert_with(|| Box::new(types::CTTcBorders::default()));
924 borders.bottom = Some(Box::new(make_cell_border(style, width_eights, color)));
925 }
926
927 pub fn set_border_left(&mut self, style: BorderStyle, width_eights: u32, color: &str) {
931 let tcpr = self
932 .cell_properties
933 .get_or_insert_with(|| Box::new(types::TableCellProperties::default()));
934 let borders = tcpr
935 .tc_borders
936 .get_or_insert_with(|| Box::new(types::CTTcBorders::default()));
937 borders.left = Some(Box::new(make_cell_border(style, width_eights, color)));
938 }
939
940 pub fn set_border_right(&mut self, style: BorderStyle, width_eights: u32, color: &str) {
944 let tcpr = self
945 .cell_properties
946 .get_or_insert_with(|| Box::new(types::TableCellProperties::default()));
947 let borders = tcpr
948 .tc_borders
949 .get_or_insert_with(|| Box::new(types::CTTcBorders::default()));
950 borders.right = Some(Box::new(make_cell_border(style, width_eights, color)));
951 }
952
953 pub fn set_padding(&mut self, top: u32, bottom: u32, left: u32, right: u32) {
958 let tcpr = self
959 .cell_properties
960 .get_or_insert_with(|| Box::new(types::TableCellProperties::default()));
961 tcpr.tc_mar = Some(Box::new(types::CTTcMar {
962 top: Some(Box::new(make_tbl_width(top, types::STTblWidth::Dxa))),
963 bottom: Some(Box::new(make_tbl_width(bottom, types::STTblWidth::Dxa))),
964 left: Some(Box::new(make_tbl_width(left, types::STTblWidth::Dxa))),
965 right: Some(Box::new(make_tbl_width(right, types::STTblWidth::Dxa))),
966 #[cfg(feature = "wml-tables")]
967 start: None,
968 #[cfg(feature = "wml-tables")]
969 end: None,
970 #[cfg(feature = "extra-children")]
971 extra_children: Vec::new(),
972 }));
973 }
974}
975
976#[cfg(feature = "wml-tables")]
977impl types::Table {
978 pub fn set_width(&mut self, width: u32, unit: TableWidthUnit) {
984 let type_ = match unit {
985 TableWidthUnit::Dxa => types::STTblWidth::Dxa,
986 TableWidthUnit::Pct => types::STTblWidth::Pct,
987 };
988 self.table_properties.tbl_w = Some(Box::new(make_tbl_width(width, type_)));
989 }
990}
991
992#[cfg(feature = "wml-tables")]
994fn make_cell_border(style: BorderStyle, width_eights: u32, color: &str) -> types::CTBorder {
995 types::CTBorder {
996 value: style.to_st_border(),
997 #[cfg(feature = "wml-styling")]
998 color: Some(color.to_string()),
999 #[cfg(feature = "wml-styling")]
1000 size: Some(width_eights as u64),
1001 #[cfg(feature = "wml-styling")]
1002 space: Some(0u64),
1003 #[cfg(feature = "wml-styling")]
1004 theme_color: None,
1005 #[cfg(feature = "wml-styling")]
1006 theme_tint: None,
1007 #[cfg(feature = "wml-styling")]
1008 theme_shade: None,
1009 #[cfg(feature = "wml-styling")]
1010 shadow: None,
1011 #[cfg(feature = "wml-styling")]
1012 frame: None,
1013 #[cfg(feature = "extra-attrs")]
1014 extra_attrs: Default::default(),
1015 }
1016}
1017
1018#[cfg(feature = "wml-tables")]
1020fn make_tbl_width(width: u32, type_: types::STTblWidth) -> types::CTTblWidth {
1021 types::CTTblWidth {
1022 width: Some(width.to_string()),
1023 r#type: Some(type_),
1024 #[cfg(feature = "extra-attrs")]
1025 extra_attrs: Default::default(),
1026 }
1027}
1028
1029impl types::HeaderFooter {
1034 pub fn add_paragraph(&mut self) -> &mut types::Paragraph {
1036 self.block_content
1037 .push(types::BlockContent::P(Box::default()));
1038 match self.block_content.last_mut().unwrap() {
1039 types::BlockContent::P(p) => p.as_mut(),
1040 _ => unreachable!(),
1041 }
1042 }
1043}
1044
1045impl types::Comment {
1050 pub fn add_paragraph(&mut self) -> &mut types::Paragraph {
1052 self.block_content
1053 .push(types::BlockContent::P(Box::default()));
1054 match self.block_content.last_mut().unwrap() {
1055 types::BlockContent::P(p) => p.as_mut(),
1056 _ => unreachable!(),
1057 }
1058 }
1059}
1060
1061impl types::FootnoteEndnote {
1066 pub fn add_paragraph(&mut self) -> &mut types::Paragraph {
1068 self.block_content
1069 .push(types::BlockContent::P(Box::default()));
1070 match self.block_content.last_mut().unwrap() {
1071 types::BlockContent::P(p) => p.as_mut(),
1072 _ => unreachable!(),
1073 }
1074 }
1075}
1076
1077#[cfg(feature = "wml-track-changes")]
1086fn make_run_track_change(
1087 id: i64,
1088 author: &str,
1089 date: Option<&str>,
1090 text: &str,
1091) -> types::CTRunTrackChange {
1092 let t = types::Text {
1093 text: Some(text.to_string()),
1094 #[cfg(feature = "extra-children")]
1095 extra_children: Vec::new(),
1096 };
1097 let run = types::Run {
1098 #[cfg(feature = "wml-track-changes")]
1099 rsid_r_pr: None,
1100 #[cfg(feature = "wml-track-changes")]
1101 rsid_del: None,
1102 #[cfg(feature = "wml-track-changes")]
1103 rsid_r: None,
1104 #[cfg(feature = "wml-styling")]
1105 r_pr: None,
1106 run_content: vec![types::RunContent::T(Box::new(t))],
1107 #[cfg(feature = "extra-attrs")]
1108 extra_attrs: Default::default(),
1109 #[cfg(feature = "extra-children")]
1110 extra_children: Vec::new(),
1111 };
1112 types::CTRunTrackChange {
1113 id,
1114 author: author.to_string(),
1115 date: date.map(|d| d.to_string()),
1116 run_content: vec![types::RunContentChoice::R(Box::new(run))],
1117 #[cfg(feature = "extra-attrs")]
1118 extra_attrs: Default::default(),
1119 #[cfg(feature = "extra-children")]
1120 extra_children: Vec::new(),
1121 }
1122}
1123
1124#[cfg(feature = "wml-track-changes")]
1142pub fn ins_run(id: i64, author: &str, date: Option<&str>, text: &str) -> types::ParagraphContent {
1143 types::ParagraphContent::Ins(Box::new(make_run_track_change(id, author, date, text)))
1144}
1145
1146#[cfg(feature = "wml-track-changes")]
1164pub fn del_run(id: i64, author: &str, date: Option<&str>, text: &str) -> types::ParagraphContent {
1165 types::ParagraphContent::Del(Box::new(make_run_track_change(id, author, date, text)))
1166}
1167
1168#[cfg(feature = "wml-track-changes")]
1169impl types::Paragraph {
1170 pub fn add_tracked_insertion(
1173 &mut self,
1174 id: i64,
1175 author: &str,
1176 date: Option<&str>,
1177 text: &str,
1178 ) -> &mut types::CTRunTrackChange {
1179 self.paragraph_content.push(ins_run(id, author, date, text));
1180 match self.paragraph_content.last_mut().unwrap() {
1181 types::ParagraphContent::Ins(tc) => tc.as_mut(),
1182 _ => unreachable!(),
1183 }
1184 }
1185
1186 pub fn add_tracked_deletion(
1189 &mut self,
1190 id: i64,
1191 author: &str,
1192 date: Option<&str>,
1193 text: &str,
1194 ) -> &mut types::CTRunTrackChange {
1195 self.paragraph_content.push(del_run(id, author, date, text));
1196 match self.paragraph_content.last_mut().unwrap() {
1197 types::ParagraphContent::Del(tc) => tc.as_mut(),
1198 _ => unreachable!(),
1199 }
1200 }
1201}
1202
1203#[cfg(feature = "wml-settings")]
1211#[derive(Debug, Clone)]
1212pub enum FormFieldType {
1213 PlainText,
1215 RichText,
1217 ComboBox,
1219 DropDownList,
1221 DatePicker,
1223}
1224
1225#[cfg(feature = "wml-settings")]
1229#[derive(Debug, Clone)]
1230pub struct FormFieldConfig {
1231 pub tag: Option<String>,
1233 pub label: Option<String>,
1235 pub field_type: FormFieldType,
1237 pub default_value: Option<String>,
1239 pub placeholder: Option<String>,
1241 pub list_items: Vec<String>,
1243 pub date_format: Option<String>,
1245}
1246
1247#[cfg(feature = "wml-settings")]
1248impl Default for FormFieldConfig {
1249 fn default() -> Self {
1250 Self {
1251 tag: None,
1252 label: None,
1253 field_type: FormFieldType::PlainText,
1254 default_value: None,
1255 placeholder: None,
1256 list_items: Vec::new(),
1257 date_format: None,
1258 }
1259 }
1260}
1261
1262#[cfg(feature = "wml-settings")]
1264fn make_text_paragraph(text: &str) -> types::BlockContentChoice {
1265 let t = types::Text {
1266 text: Some(text.to_string()),
1267 #[cfg(feature = "extra-children")]
1268 extra_children: Vec::new(),
1269 };
1270 let mut run = types::Run::default();
1271 run.run_content.push(types::RunContent::T(Box::new(t)));
1272 let mut para = types::Paragraph::default();
1273 para.paragraph_content
1274 .push(types::ParagraphContent::R(Box::new(run)));
1275 types::BlockContentChoice::P(Box::new(para))
1276}
1277
1278#[cfg(feature = "wml-settings")]
1280fn make_list_item(text: &str) -> types::CTSdtListItem {
1281 types::CTSdtListItem {
1282 display_text: Some(text.to_string()),
1283 value: Some(text.to_string()),
1284 #[cfg(feature = "extra-attrs")]
1285 extra_attrs: Default::default(),
1286 }
1287}
1288
1289#[cfg(feature = "wml-settings")]
1290impl types::Body {
1291 pub fn add_form_field(&mut self, config: FormFieldConfig) -> &mut Self {
1298 let content_text = config
1299 .default_value
1300 .as_deref()
1301 .or(config.placeholder.as_deref())
1302 .unwrap_or("")
1303 .to_string();
1304
1305 let mut sdt_pr = types::CTSdtPr::default();
1307
1308 #[cfg(feature = "wml-settings")]
1309 if let Some(ref tag_val) = config.tag {
1310 sdt_pr.tag = Some(Box::new(types::CTString {
1311 value: tag_val.clone(),
1312 #[cfg(feature = "extra-attrs")]
1313 extra_attrs: Default::default(),
1314 }));
1315 }
1316
1317 #[cfg(feature = "wml-settings")]
1318 if let Some(ref alias_val) = config.label {
1319 sdt_pr.alias = Some(Box::new(types::CTString {
1320 value: alias_val.clone(),
1321 #[cfg(feature = "extra-attrs")]
1322 extra_attrs: Default::default(),
1323 }));
1324 }
1325
1326 #[cfg(feature = "wml-settings")]
1327 match config.field_type {
1328 FormFieldType::PlainText => {
1329 sdt_pr.text = Some(Box::new(types::CTSdtText {
1330 multi_line: None,
1331 #[cfg(feature = "extra-attrs")]
1332 extra_attrs: Default::default(),
1333 }));
1334 }
1335 FormFieldType::RichText => {
1336 sdt_pr.rich_text = Some(Box::new(types::CTEmpty));
1337 }
1338 FormFieldType::ComboBox => {
1339 let items = config
1340 .list_items
1341 .iter()
1342 .map(|s| make_list_item(s))
1343 .collect();
1344 sdt_pr.combo_box = Some(Box::new(types::CTSdtComboBox {
1345 last_value: None,
1346 list_item: items,
1347 #[cfg(feature = "extra-attrs")]
1348 extra_attrs: Default::default(),
1349 #[cfg(feature = "extra-children")]
1350 extra_children: Vec::new(),
1351 }));
1352 }
1353 FormFieldType::DropDownList => {
1354 let items = config
1355 .list_items
1356 .iter()
1357 .map(|s| make_list_item(s))
1358 .collect();
1359 sdt_pr.drop_down_list = Some(Box::new(types::CTSdtDropDownList {
1360 last_value: None,
1361 list_item: items,
1362 #[cfg(feature = "extra-attrs")]
1363 extra_attrs: Default::default(),
1364 #[cfg(feature = "extra-children")]
1365 extra_children: Vec::new(),
1366 }));
1367 }
1368 FormFieldType::DatePicker => {
1369 let date_format_elem = config.date_format.as_deref().map(|fmt| {
1370 Box::new(types::CTString {
1371 value: fmt.to_string(),
1372 #[cfg(feature = "extra-attrs")]
1373 extra_attrs: Default::default(),
1374 })
1375 });
1376 sdt_pr.date = Some(Box::new(types::CTSdtDate {
1377 date_format: date_format_elem,
1378 full_date: None,
1379 lid: None,
1380 store_mapped_data_as: None,
1381 calendar: None,
1382 #[cfg(feature = "extra-attrs")]
1383 extra_attrs: Default::default(),
1384 #[cfg(feature = "extra-children")]
1385 extra_children: Vec::new(),
1386 }));
1387 }
1388 }
1389
1390 let sdt_content = types::CTSdtContentBlock {
1391 block_content: vec![make_text_paragraph(&content_text)],
1392 #[cfg(feature = "extra-children")]
1393 extra_children: Vec::new(),
1394 };
1395
1396 let sdt = types::CTSdtBlock {
1397 sdt_pr: Some(Box::new(sdt_pr)),
1398 sdt_end_pr: None,
1399 sdt_content: Some(Box::new(sdt_content)),
1400 #[cfg(feature = "extra-children")]
1401 extra_children: Vec::new(),
1402 };
1403
1404 self.block_content
1405 .push(types::BlockContent::Sdt(Box::new(sdt)));
1406 self
1407 }
1408}
1409
1410#[cfg(feature = "wml-fields")]
1418#[derive(Debug, Clone)]
1419pub struct TocOptions {
1420 pub title: Option<String>,
1422 pub max_level: u8,
1424 pub right_align_page_numbers: bool,
1426 pub use_hyperlinks: bool,
1428}
1429
1430#[cfg(feature = "wml-fields")]
1431impl Default for TocOptions {
1432 fn default() -> Self {
1433 Self {
1434 title: None,
1435 max_level: 3,
1436 right_align_page_numbers: true,
1437 use_hyperlinks: true,
1438 }
1439 }
1440}
1441
1442#[cfg(feature = "wml-fields")]
1444fn make_fld_char(fld_char_type: types::STFldCharType) -> types::CTFldChar {
1445 types::CTFldChar {
1446 fld_char_type,
1447 #[cfg(feature = "wml-fields")]
1448 fld_lock: None,
1449 #[cfg(feature = "wml-fields")]
1450 dirty: None,
1451 #[cfg(feature = "wml-fields")]
1452 fld_data: None,
1453 #[cfg(feature = "wml-fields")]
1454 ff_data: None,
1455 #[cfg(feature = "wml-track-changes")]
1456 numbering_change: None,
1457 #[cfg(feature = "extra-attrs")]
1458 extra_attrs: Default::default(),
1459 #[cfg(feature = "extra-children")]
1460 extra_children: Vec::new(),
1461 }
1462}
1463
1464#[cfg(feature = "wml-fields")]
1466#[allow(dead_code)]
1467fn make_fld_char_para(fld_char_type: types::STFldCharType) -> types::Paragraph {
1468 let fld_char = make_fld_char(fld_char_type);
1469 let mut run = types::Run::default();
1470 run.run_content
1471 .push(types::RunContent::FldChar(Box::new(fld_char)));
1472 let mut para = types::Paragraph::default();
1473 para.paragraph_content
1474 .push(types::ParagraphContent::R(Box::new(run)));
1475 para
1476}
1477
1478#[cfg(feature = "wml-fields")]
1480#[allow(dead_code)]
1481fn make_instr_text_para(instr: &str) -> types::Paragraph {
1482 let t = types::Text {
1483 text: Some(instr.to_string()),
1484 #[cfg(feature = "extra-children")]
1485 extra_children: Vec::new(),
1486 };
1487 let mut run = types::Run::default();
1488 run.run_content
1489 .push(types::RunContent::InstrText(Box::new(t)));
1490 let mut para = types::Paragraph::default();
1491 para.paragraph_content
1492 .push(types::ParagraphContent::R(Box::new(run)));
1493 para
1494}
1495
1496#[cfg(feature = "wml-fields")]
1497impl types::Body {
1498 pub fn add_toc(&mut self, opts: TocOptions) -> &mut Self {
1507 if let Some(ref title_text) = opts.title {
1509 let para = self.add_paragraph();
1510 para.add_run().set_text(title_text.as_str());
1511 #[cfg(feature = "wml-styling")]
1512 {
1513 let ppr = para
1514 .p_pr
1515 .get_or_insert_with(|| Box::new(types::ParagraphProperties::default()));
1516 ppr.paragraph_style = Some(Box::new(types::CTString {
1517 value: "TOCHeading".to_string(),
1518 #[cfg(feature = "extra-attrs")]
1519 extra_attrs: Default::default(),
1520 }));
1521 }
1522 }
1523
1524 let mut instr = format!(r#" TOC \o "1-{}" "#, opts.max_level);
1526 if opts.use_hyperlinks {
1527 instr.push_str(r"\h ");
1528 }
1529 if opts.right_align_page_numbers {
1530 instr.push_str(r"\z \u ");
1531 }
1532
1533 let fld_begin = make_fld_char(types::STFldCharType::Begin);
1539 let fld_separate = make_fld_char(types::STFldCharType::Separate);
1540 let fld_end = make_fld_char(types::STFldCharType::End);
1541
1542 let instr_t = types::Text {
1543 text: Some(instr),
1544 #[cfg(feature = "extra-children")]
1545 extra_children: Vec::new(),
1546 };
1547
1548 let mut run_begin = types::Run::default();
1549 run_begin
1550 .run_content
1551 .push(types::RunContent::FldChar(Box::new(fld_begin)));
1552
1553 let mut run_instr = types::Run::default();
1554 run_instr
1555 .run_content
1556 .push(types::RunContent::InstrText(Box::new(instr_t)));
1557
1558 let mut run_separate = types::Run::default();
1559 run_separate
1560 .run_content
1561 .push(types::RunContent::FldChar(Box::new(fld_separate)));
1562
1563 let mut run_end = types::Run::default();
1564 run_end
1565 .run_content
1566 .push(types::RunContent::FldChar(Box::new(fld_end)));
1567
1568 let toc_para = self.add_paragraph();
1569 toc_para
1570 .paragraph_content
1571 .push(types::ParagraphContent::R(Box::new(run_begin)));
1572 toc_para
1573 .paragraph_content
1574 .push(types::ParagraphContent::R(Box::new(run_instr)));
1575 toc_para
1576 .paragraph_content
1577 .push(types::ParagraphContent::R(Box::new(run_separate)));
1578 toc_para
1579 .paragraph_content
1580 .push(types::ParagraphContent::R(Box::new(run_end)));
1581
1582 let placeholder = self.add_paragraph();
1584 placeholder
1585 .add_run()
1586 .set_text("[Right-click to update field]");
1587
1588 self
1589 }
1590}
1591
1592pub const NS_M: &str = "http://schemas.openxmlformats.org/officeDocument/2006/math";
1600
1601#[derive(Debug, Clone)]
1608pub struct OMathBuilder {
1609 display: bool,
1610 xml_content: String,
1611}
1612
1613impl OMathBuilder {
1614 pub fn plain(text: &str) -> Self {
1616 Self {
1617 display: false,
1618 xml_content: format!("<m:r><m:t>{}</m:t></m:r>", xml_escape(text)),
1619 }
1620 }
1621
1622 pub fn fraction(numerator: &str, denominator: &str) -> Self {
1626 Self {
1627 display: false,
1628 xml_content: format!(
1629 "<m:f><m:num><m:r><m:t>{}</m:t></m:r></m:num>\
1630 <m:den><m:r><m:t>{}</m:t></m:r></m:den></m:f>",
1631 xml_escape(numerator),
1632 xml_escape(denominator)
1633 ),
1634 }
1635 }
1636
1637 pub fn superscript(base: &str, exp: &str) -> Self {
1641 Self {
1642 display: false,
1643 xml_content: format!(
1644 "<m:sSup><m:e><m:r><m:t>{}</m:t></m:r></m:e>\
1645 <m:sup><m:r><m:t>{}</m:t></m:r></m:sup></m:sSup>",
1646 xml_escape(base),
1647 xml_escape(exp)
1648 ),
1649 }
1650 }
1651
1652 pub fn subscript(base: &str, sub: &str) -> Self {
1656 Self {
1657 display: false,
1658 xml_content: format!(
1659 "<m:sSub><m:e><m:r><m:t>{}</m:t></m:r></m:e>\
1660 <m:sub><m:r><m:t>{}</m:t></m:r></m:sub></m:sSub>",
1661 xml_escape(base),
1662 xml_escape(sub)
1663 ),
1664 }
1665 }
1666
1667 pub fn radical(base: &str) -> Self {
1671 Self {
1672 display: false,
1673 xml_content: format!(
1674 "<m:rad><m:radPr><m:degHide m:val=\"1\"/></m:radPr>\
1675 <m:deg/><m:e><m:r><m:t>{}</m:t></m:r></m:e></m:rad>",
1676 xml_escape(base)
1677 ),
1678 }
1679 }
1680
1681 pub fn as_display(mut self) -> Self {
1683 self.display = true;
1684 self
1685 }
1686
1687 pub fn build(self) -> RawXmlElement {
1692 let omath = RawXmlElement {
1693 name: "m:oMath".to_string(),
1694 attributes: vec![("xmlns:m".to_string(), NS_M.to_string())],
1695 children: vec![RawXmlNode::Text(self.xml_content)],
1696 self_closing: false,
1697 };
1698
1699 if self.display {
1700 RawXmlElement {
1701 name: "m:oMathPara".to_string(),
1702 attributes: vec![("xmlns:m".to_string(), NS_M.to_string())],
1703 children: vec![RawXmlNode::Element({
1704 RawXmlElement {
1706 name: "m:oMath".to_string(),
1707 attributes: vec![],
1708 children: vec![RawXmlNode::Text({
1709 match omath.children.into_iter().next() {
1712 Some(RawXmlNode::Text(t)) => t,
1713 _ => String::new(),
1714 }
1715 })],
1716 self_closing: false,
1717 }
1718 })],
1719 self_closing: false,
1720 }
1721 } else {
1722 omath
1723 }
1724 }
1725}
1726
1727fn xml_escape(s: &str) -> String {
1729 s.replace('&', "&")
1730 .replace('<', "<")
1731 .replace('>', ">")
1732 .replace('"', """)
1733 .replace('\'', "'")
1734}
1735
1736impl types::Paragraph {
1737 #[cfg(feature = "extra-children")]
1744 pub fn add_math(&mut self, builder: OMathBuilder) -> &mut Self {
1745 let elem = builder.build();
1746 let idx = self.paragraph_content.len();
1749 self.extra_children
1750 .push(PositionedNode::new(idx, RawXmlNode::Element(elem)));
1751 self
1752 }
1753}
1754
1755impl types::Paragraph {
1760 #[cfg(feature = "wml-charts")]
1769 pub fn add_inline_chart(&mut self, rel_id: &str, width_emu: i64, height_emu: i64) -> &mut Self {
1770 let drawing_elem = build_chart_inline_element(rel_id, width_emu, height_emu);
1771 let drawing = types::CTDrawing {
1772 #[cfg(feature = "extra-children")]
1773 extra_children: vec![PositionedNode::new(0, RawXmlNode::Element(drawing_elem))],
1774 };
1775 let mut run = types::Run::default();
1776 run.run_content
1777 .push(types::RunContent::Drawing(Box::new(drawing)));
1778 self.paragraph_content
1779 .push(types::ParagraphContent::R(Box::new(run)));
1780 self
1781 }
1782}
1783
1784#[cfg(feature = "wml-charts")]
1786fn build_chart_inline_element(rel_id: &str, width_emu: i64, height_emu: i64) -> RawXmlElement {
1787 let chart_ref = RawXmlElement {
1789 name: "c:chart".to_string(),
1790 attributes: vec![
1791 (
1792 "xmlns:c".to_string(),
1793 "http://schemas.openxmlformats.org/drawingml/2006/chart".to_string(),
1794 ),
1795 (
1796 "xmlns:r".to_string(),
1797 "http://schemas.openxmlformats.org/officeDocument/2006/relationships".to_string(),
1798 ),
1799 ("r:id".to_string(), rel_id.to_string()),
1800 ],
1801 children: vec![],
1802 self_closing: true,
1803 };
1804
1805 let graphic_data = RawXmlElement {
1807 name: "a:graphicData".to_string(),
1808 attributes: vec![(
1809 "uri".to_string(),
1810 "http://schemas.openxmlformats.org/drawingml/2006/chart".to_string(),
1811 )],
1812 children: vec![RawXmlNode::Element(chart_ref)],
1813 self_closing: false,
1814 };
1815
1816 let graphic = RawXmlElement {
1818 name: "a:graphic".to_string(),
1819 attributes: vec![(
1820 "xmlns:a".to_string(),
1821 "http://schemas.openxmlformats.org/drawingml/2006/main".to_string(),
1822 )],
1823 children: vec![RawXmlNode::Element(graphic_data)],
1824 self_closing: false,
1825 };
1826
1827 let extent = RawXmlElement {
1829 name: "wp:extent".to_string(),
1830 attributes: vec![
1831 ("cx".to_string(), width_emu.to_string()),
1832 ("cy".to_string(), height_emu.to_string()),
1833 ],
1834 children: vec![],
1835 self_closing: true,
1836 };
1837
1838 let doc_pr = RawXmlElement {
1840 name: "wp:docPr".to_string(),
1841 attributes: vec![
1842 ("id".to_string(), "1".to_string()),
1843 ("name".to_string(), "Chart 1".to_string()),
1844 ],
1845 children: vec![],
1846 self_closing: true,
1847 };
1848
1849 RawXmlElement {
1851 name: "wp:inline".to_string(),
1852 attributes: vec![
1853 (
1854 "xmlns:wp".to_string(),
1855 "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
1856 .to_string(),
1857 ),
1858 ("distT".to_string(), "0".to_string()),
1859 ("distB".to_string(), "0".to_string()),
1860 ("distL".to_string(), "0".to_string()),
1861 ("distR".to_string(), "0".to_string()),
1862 ],
1863 children: vec![
1864 RawXmlNode::Element(extent),
1865 RawXmlNode::Element(doc_pr),
1866 RawXmlNode::Element(graphic),
1867 ],
1868 self_closing: false,
1869 }
1870}
1871
1872#[cfg(test)]
1877mod feature_tests {
1878 use super::*;
1879
1880 #[test]
1885 #[cfg(feature = "wml-settings")]
1886 fn test_settings_xml_content() {
1887 use crate::writer::DocumentSettingsOptions;
1888
1889 let opts = DocumentSettingsOptions {
1893 default_tab_stop: Some(720),
1894 even_and_odd_headers: true,
1895 track_changes: true,
1896 rsid_root: Some("AB12CD34".to_string()),
1897 compat_mode: true,
1898 };
1899 let _ = opts;
1901 }
1902
1903 #[test]
1904 #[cfg(all(
1905 feature = "wml-settings",
1906 feature = "extra-attrs",
1907 feature = "extra-children"
1908 ))]
1909 fn test_settings_roundtrip() {
1910 use crate::Document;
1911 use crate::writer::{DocumentBuilder, DocumentSettingsOptions};
1912 use std::io::Cursor;
1913
1914 let mut builder = DocumentBuilder::new();
1915 builder.set_settings(DocumentSettingsOptions {
1916 default_tab_stop: Some(720),
1917 even_and_odd_headers: true,
1918 track_changes: false,
1919 rsid_root: None,
1920 compat_mode: false,
1921 });
1922 builder.add_paragraph("Hello");
1923
1924 let mut buf = Cursor::new(Vec::new());
1925 builder.write(&mut buf).unwrap();
1926
1927 buf.set_position(0);
1929 let doc = Document::from_reader(buf).unwrap();
1930 let body = doc.body();
1931 assert!(!body.block_content.is_empty());
1932 }
1933
1934 #[test]
1939 #[cfg(feature = "wml-settings")]
1940 fn test_form_field_plain_text() {
1941 let mut body = types::Body::default();
1942 body.add_form_field(FormFieldConfig {
1943 tag: Some("myTag".to_string()),
1944 label: Some("My Field".to_string()),
1945 field_type: FormFieldType::PlainText,
1946 default_value: Some("default".to_string()),
1947 ..Default::default()
1948 });
1949
1950 assert_eq!(body.block_content.len(), 1);
1951 match &body.block_content[0] {
1952 types::BlockContent::Sdt(sdt) => {
1953 let sdt_pr = sdt.sdt_pr.as_ref().expect("sdt_pr should be present");
1954 assert_eq!(sdt_pr.tag.as_ref().unwrap().value, "myTag");
1955 assert_eq!(sdt_pr.alias.as_ref().unwrap().value, "My Field");
1956 assert!(sdt_pr.text.is_some(), "text element should be set");
1957 let content = sdt.sdt_content.as_ref().expect("sdt_content");
1958 assert_eq!(content.block_content.len(), 1);
1959 }
1960 _ => panic!("expected Sdt block content"),
1961 }
1962 }
1963
1964 #[test]
1965 #[cfg(feature = "wml-settings")]
1966 fn test_form_field_dropdown() {
1967 let mut body = types::Body::default();
1968 body.add_form_field(FormFieldConfig {
1969 field_type: FormFieldType::DropDownList,
1970 list_items: vec!["Option A".to_string(), "Option B".to_string()],
1971 ..Default::default()
1972 });
1973
1974 match &body.block_content[0] {
1975 types::BlockContent::Sdt(sdt) => {
1976 let sdt_pr = sdt.sdt_pr.as_ref().unwrap();
1977 let dd = sdt_pr.drop_down_list.as_ref().expect("drop_down_list");
1978 assert_eq!(dd.list_item.len(), 2);
1979 assert_eq!(dd.list_item[0].display_text.as_deref(), Some("Option A"));
1980 assert_eq!(dd.list_item[1].display_text.as_deref(), Some("Option B"));
1981 }
1982 _ => panic!("expected Sdt"),
1983 }
1984 }
1985
1986 #[test]
1987 #[cfg(feature = "wml-settings")]
1988 fn test_form_field_date_picker() {
1989 let mut body = types::Body::default();
1990 body.add_form_field(FormFieldConfig {
1991 field_type: FormFieldType::DatePicker,
1992 date_format: Some("MM/dd/yyyy".to_string()),
1993 ..Default::default()
1994 });
1995
1996 match &body.block_content[0] {
1997 types::BlockContent::Sdt(sdt) => {
1998 let sdt_pr = sdt.sdt_pr.as_ref().unwrap();
1999 let date = sdt_pr.date.as_ref().expect("date element");
2000 assert_eq!(date.date_format.as_ref().unwrap().value, "MM/dd/yyyy");
2001 }
2002 _ => panic!("expected Sdt"),
2003 }
2004 }
2005
2006 #[test]
2011 #[cfg(feature = "wml-fields")]
2012 fn test_toc_basic() {
2013 let mut body = types::Body::default();
2014 body.add_toc(TocOptions {
2015 title: Some("Contents".to_string()),
2016 max_level: 3,
2017 right_align_page_numbers: true,
2018 use_hyperlinks: true,
2019 });
2020
2021 let paras: Vec<_> = body
2023 .block_content
2024 .iter()
2025 .filter_map(|b| match b {
2026 types::BlockContent::P(p) => Some(p),
2027 _ => None,
2028 })
2029 .collect();
2030 assert_eq!(
2031 paras.len(),
2032 3,
2033 "expected title + field + placeholder paragraphs"
2034 );
2035
2036 let field_para = ¶s[1];
2038 assert_eq!(
2039 field_para.paragraph_content.len(),
2040 4,
2041 "TOC paragraph should have 4 run items"
2042 );
2043 }
2044
2045 #[test]
2046 #[cfg(feature = "wml-fields")]
2047 fn test_toc_no_title() {
2048 let mut body = types::Body::default();
2049 body.add_toc(TocOptions::default());
2050
2051 let paras: Vec<_> = body
2053 .block_content
2054 .iter()
2055 .filter_map(|b| match b {
2056 types::BlockContent::P(p) => Some(p),
2057 _ => None,
2058 })
2059 .collect();
2060 assert_eq!(paras.len(), 2, "expected field + placeholder paragraphs");
2061 }
2062
2063 #[test]
2064 #[cfg(feature = "wml-fields")]
2065 fn test_toc_instr_text_contains_level() {
2066 let mut body = types::Body::default();
2067 body.add_toc(TocOptions {
2068 title: None,
2069 max_level: 2,
2070 right_align_page_numbers: false,
2071 use_hyperlinks: false,
2072 });
2073
2074 let field_para = match &body.block_content[0] {
2075 types::BlockContent::P(p) => p,
2076 _ => panic!("expected paragraph"),
2077 };
2078
2079 match &field_para.paragraph_content[1] {
2081 types::ParagraphContent::R(run) => match &run.run_content[0] {
2082 types::RunContent::InstrText(t) => {
2083 let instr = t.text.as_deref().unwrap_or("");
2084 assert!(
2085 instr.contains("1-2"),
2086 "should contain level range 1-2, got: {}",
2087 instr
2088 );
2089 }
2090 _ => panic!("expected InstrText"),
2091 },
2092 _ => panic!("expected run"),
2093 }
2094 }
2095
2096 #[test]
2101 fn test_omath_plain_build() {
2102 let builder = OMathBuilder::plain("x");
2103 let elem = builder.build();
2104 assert_eq!(elem.name, "m:oMath");
2105 assert!(!elem.attributes.is_empty(), "should have xmlns:m");
2106 assert_eq!(elem.children.len(), 1);
2107 match &elem.children[0] {
2108 RawXmlNode::Text(t) => assert!(t.contains("m:r"), "text should wrap in m:r: {}", t),
2109 _ => panic!("expected text node"),
2110 }
2111 }
2112
2113 #[test]
2114 fn test_omath_fraction_build() {
2115 let builder = OMathBuilder::fraction("a", "b");
2116 let elem = builder.build();
2117 match &elem.children[0] {
2118 RawXmlNode::Text(t) => {
2119 assert!(t.contains("m:f"), "should contain fraction: {}", t);
2120 assert!(t.contains("m:num"), "should contain numerator: {}", t);
2121 assert!(t.contains("m:den"), "should contain denominator: {}", t);
2122 }
2123 _ => panic!("expected text node"),
2124 }
2125 }
2126
2127 #[test]
2128 fn test_omath_superscript_build() {
2129 let builder = OMathBuilder::superscript("x", "2");
2130 let elem = builder.build();
2131 match &elem.children[0] {
2132 RawXmlNode::Text(t) => {
2133 assert!(t.contains("m:sSup"), "should contain sSup: {}", t);
2134 assert!(t.contains("m:e"), "should contain base: {}", t);
2135 assert!(t.contains("m:sup"), "should contain exp: {}", t);
2136 }
2137 _ => panic!("expected text node"),
2138 }
2139 }
2140
2141 #[test]
2142 fn test_omath_subscript_build() {
2143 let builder = OMathBuilder::subscript("x", "i");
2144 let elem = builder.build();
2145 match &elem.children[0] {
2146 RawXmlNode::Text(t) => assert!(t.contains("m:sSub"), "{}", t),
2147 _ => panic!(),
2148 }
2149 }
2150
2151 #[test]
2152 fn test_omath_radical_build() {
2153 let builder = OMathBuilder::radical("x");
2154 let elem = builder.build();
2155 match &elem.children[0] {
2156 RawXmlNode::Text(t) => assert!(t.contains("m:rad"), "{}", t),
2157 _ => panic!(),
2158 }
2159 }
2160
2161 #[test]
2162 fn test_omath_display_wraps_in_para() {
2163 let builder = OMathBuilder::plain("x").as_display();
2164 let elem = builder.build();
2165 assert_eq!(
2166 elem.name, "m:oMathPara",
2167 "display should be wrapped in oMathPara"
2168 );
2169 assert_eq!(elem.children.len(), 1);
2170 match &elem.children[0] {
2171 RawXmlNode::Element(inner) => assert_eq!(inner.name, "m:oMath"),
2172 _ => panic!("expected oMath element child"),
2173 }
2174 }
2175
2176 #[test]
2177 #[cfg(feature = "extra-children")]
2178 fn test_paragraph_add_math() {
2179 let mut para = types::Paragraph::default();
2180 para.add_math(OMathBuilder::plain("y = mx + b"));
2181 assert_eq!(para.extra_children.len(), 1);
2182 }
2183
2184 #[test]
2189 #[cfg(all(
2190 feature = "wml-charts",
2191 feature = "extra-children",
2192 feature = "extra-attrs"
2193 ))]
2194 fn test_embed_chart_and_add_inline() {
2195 use crate::writer::DocumentBuilder;
2196 use std::io::Cursor;
2197
2198 let chart_xml = br#"<?xml version="1.0" encoding="UTF-8"?><c:chartSpace xmlns:c="http://schemas.openxmlformats.org/drawingml/2006/chart"/>"#;
2199
2200 let mut builder = DocumentBuilder::new();
2201 let rel_id = builder.embed_chart(chart_xml).unwrap();
2202 assert!(
2203 rel_id.starts_with("rId"),
2204 "rel_id should be rId-prefixed: {}",
2205 rel_id
2206 );
2207
2208 {
2210 let body = builder.body_mut();
2211 let para = body.add_paragraph();
2212 para.add_inline_chart(&rel_id, 3000000, 2000000);
2213 }
2214
2215 let mut buf = Cursor::new(Vec::new());
2217 builder.write(&mut buf).unwrap();
2218 assert!(!buf.get_ref().is_empty(), "output should not be empty");
2219 }
2220
2221 #[test]
2222 #[cfg(feature = "wml-charts")]
2223 fn test_chart_inline_element_structure() {
2224 let elem = build_chart_inline_element("rId5", 3000000, 2000000);
2225 assert_eq!(elem.name, "wp:inline");
2226 assert_eq!(elem.children.len(), 3);
2228 }
2229}
2230
2231#[cfg(test)]
2232mod tests {
2233 use super::*;
2234
2235 #[test]
2240 fn test_paragraph_add_page_break() {
2241 let mut para = types::Paragraph::default();
2242 para.add_page_break();
2243
2244 assert_eq!(para.paragraph_content.len(), 1);
2245 match ¶.paragraph_content[0] {
2246 types::ParagraphContent::R(run) => {
2247 assert_eq!(run.run_content.len(), 1);
2248 match &run.run_content[0] {
2249 types::RunContent::Br(br) => {
2250 assert_eq!(br.r#type, Some(types::STBrType::Page));
2251 }
2252 _ => panic!("expected Br content"),
2253 }
2254 }
2255 _ => panic!("expected R content"),
2256 }
2257 }
2258
2259 #[test]
2260 fn test_paragraph_add_column_break() {
2261 let mut para = types::Paragraph::default();
2262 para.add_column_break();
2263
2264 assert_eq!(para.paragraph_content.len(), 1);
2265 match ¶.paragraph_content[0] {
2266 types::ParagraphContent::R(run) => {
2267 assert_eq!(run.run_content.len(), 1);
2268 match &run.run_content[0] {
2269 types::RunContent::Br(br) => {
2270 assert_eq!(br.r#type, Some(types::STBrType::Column));
2271 }
2272 _ => panic!("expected Br content"),
2273 }
2274 }
2275 _ => panic!("expected R content"),
2276 }
2277 }
2278
2279 #[test]
2284 #[cfg(feature = "wml-styling")]
2285 fn test_set_space_before() {
2286 let mut para = types::Paragraph::default();
2287 para.set_space_before(240);
2288
2289 let ppr = para.p_pr.as_ref().unwrap();
2290 let spacing = ppr.spacing.as_ref().unwrap();
2291 assert_eq!(spacing.before.as_deref(), Some("240"));
2292 assert!(spacing.after.is_none());
2293 }
2294
2295 #[test]
2296 #[cfg(feature = "wml-styling")]
2297 fn test_set_space_after() {
2298 let mut para = types::Paragraph::default();
2299 para.set_space_after(160);
2300
2301 let ppr = para.p_pr.as_ref().unwrap();
2302 let spacing = ppr.spacing.as_ref().unwrap();
2303 assert_eq!(spacing.after.as_deref(), Some("160"));
2304 assert!(spacing.before.is_none());
2305 }
2306
2307 #[test]
2308 #[cfg(feature = "wml-styling")]
2309 fn test_set_line_spacing() {
2310 let mut para = types::Paragraph::default();
2311 para.set_line_spacing(360); let ppr = para.p_pr.as_ref().unwrap();
2314 let spacing = ppr.spacing.as_ref().unwrap();
2315 assert_eq!(spacing.line.as_deref(), Some("360"));
2316 assert_eq!(spacing.line_rule, Some(types::STLineSpacingRule::Auto));
2317 }
2318
2319 #[test]
2320 #[cfg(feature = "wml-styling")]
2321 fn test_spacing_accumulates() {
2322 let mut para = types::Paragraph::default();
2323 para.set_space_before(240);
2324 para.set_space_after(120);
2325 para.set_line_spacing(480);
2326
2327 let ppr = para.p_pr.as_ref().unwrap();
2328 let spacing = ppr.spacing.as_ref().unwrap();
2329 assert_eq!(spacing.before.as_deref(), Some("240"));
2330 assert_eq!(spacing.after.as_deref(), Some("120"));
2331 assert_eq!(spacing.line.as_deref(), Some("480"));
2332 }
2333
2334 #[test]
2339 #[cfg(feature = "wml-styling")]
2340 fn test_set_indent_left() {
2341 let mut para = types::Paragraph::default();
2342 para.set_indent_left(720);
2343
2344 let ppr = para.p_pr.as_ref().unwrap();
2345 let ind = ppr.indentation.as_ref().unwrap();
2346 assert_eq!(ind.left.as_deref(), Some("720"));
2347 }
2348
2349 #[test]
2350 #[cfg(feature = "wml-styling")]
2351 fn test_set_indent_right() {
2352 let mut para = types::Paragraph::default();
2353 para.set_indent_right(360);
2354
2355 let ppr = para.p_pr.as_ref().unwrap();
2356 let ind = ppr.indentation.as_ref().unwrap();
2357 assert_eq!(ind.right.as_deref(), Some("360"));
2358 }
2359
2360 #[test]
2361 #[cfg(feature = "wml-styling")]
2362 fn test_set_indent_first_line() {
2363 let mut para = types::Paragraph::default();
2364 para.set_indent_first_line(180);
2365
2366 let ppr = para.p_pr.as_ref().unwrap();
2367 let ind = ppr.indentation.as_ref().unwrap();
2368 assert_eq!(ind.first_line.as_deref(), Some("180"));
2369 }
2370
2371 #[test]
2372 #[cfg(feature = "wml-styling")]
2373 fn test_indentation_accumulates() {
2374 let mut para = types::Paragraph::default();
2375 para.set_indent_left(720);
2376 para.set_indent_right(360);
2377 para.set_indent_first_line(180);
2378
2379 let ppr = para.p_pr.as_ref().unwrap();
2380 let ind = ppr.indentation.as_ref().unwrap();
2381 assert_eq!(ind.left.as_deref(), Some("720"));
2382 assert_eq!(ind.right.as_deref(), Some("360"));
2383 assert_eq!(ind.first_line.as_deref(), Some("180"));
2384 }
2385
2386 #[test]
2391 #[cfg(feature = "wml-styling")]
2392 fn test_set_outline_level() {
2393 let mut para = types::Paragraph::default();
2394 para.set_outline_level(1); let ppr = para.p_pr.as_ref().unwrap();
2397 let lvl = ppr.outline_lvl.as_ref().unwrap();
2398 assert_eq!(lvl.value, 1);
2399 }
2400
2401 #[test]
2406 #[cfg(feature = "wml-styling")]
2407 fn test_run_set_shadow() {
2408 let mut run = types::Run::default();
2409 run.set_shadow(true);
2410 assert!(run.r_pr.as_ref().unwrap().shadow.is_some());
2411 run.set_shadow(false);
2412 assert!(run.r_pr.as_ref().unwrap().shadow.is_none());
2413 }
2414
2415 #[test]
2416 #[cfg(feature = "wml-styling")]
2417 fn test_run_set_outline() {
2418 let mut run = types::Run::default();
2419 run.set_outline(true);
2420 assert!(run.r_pr.as_ref().unwrap().outline.is_some());
2421 run.set_outline(false);
2422 assert!(run.r_pr.as_ref().unwrap().outline.is_none());
2423 }
2424
2425 #[test]
2426 #[cfg(feature = "wml-styling")]
2427 fn test_run_set_emboss() {
2428 let mut run = types::Run::default();
2429 run.set_emboss(true);
2430 assert!(run.r_pr.as_ref().unwrap().emboss.is_some());
2431 }
2432
2433 #[test]
2434 #[cfg(feature = "wml-styling")]
2435 fn test_run_set_imprint() {
2436 let mut run = types::Run::default();
2437 run.set_imprint(true);
2438 assert!(run.r_pr.as_ref().unwrap().imprint.is_some());
2439 }
2440
2441 #[test]
2442 #[cfg(feature = "wml-styling")]
2443 fn test_run_set_small_caps() {
2444 let mut run = types::Run::default();
2445 run.set_small_caps(true);
2446 assert!(run.r_pr.as_ref().unwrap().small_caps.is_some());
2447 run.set_small_caps(false);
2448 assert!(run.r_pr.as_ref().unwrap().small_caps.is_none());
2449 }
2450
2451 #[test]
2452 #[cfg(feature = "wml-styling")]
2453 fn test_run_set_all_caps() {
2454 let mut run = types::Run::default();
2455 run.set_all_caps(true);
2456 assert!(run.r_pr.as_ref().unwrap().caps.is_some());
2457 run.set_all_caps(false);
2458 assert!(run.r_pr.as_ref().unwrap().caps.is_none());
2459 }
2460
2461 #[test]
2462 #[cfg(feature = "wml-styling")]
2463 fn test_run_set_vanish() {
2464 let mut run = types::Run::default();
2465 run.set_vanish(true);
2466 assert!(run.r_pr.as_ref().unwrap().vanish.is_some());
2467 run.set_vanish(false);
2468 assert!(run.r_pr.as_ref().unwrap().vanish.is_none());
2469 }
2470
2471 #[test]
2472 #[cfg(feature = "wml-styling")]
2473 fn test_run_set_double_strike() {
2474 let mut run = types::Run::default();
2475 run.set_double_strike(true);
2476 assert!(run.r_pr.as_ref().unwrap().dstrike.is_some());
2477 run.set_double_strike(false);
2478 assert!(run.r_pr.as_ref().unwrap().dstrike.is_none());
2479 }
2480
2481 #[test]
2486 #[cfg(feature = "wml-tables")]
2487 fn test_cell_set_grid_span() {
2488 let mut cell = types::TableCell::default();
2489 cell.set_grid_span(3);
2490
2491 let tcpr = cell.cell_properties.as_ref().unwrap();
2492 let gs = tcpr.grid_span.as_ref().unwrap();
2493 assert_eq!(gs.value, 3);
2494 }
2495
2496 #[test]
2497 #[cfg(feature = "wml-tables")]
2498 fn test_cell_set_vertical_merge_restart() {
2499 let mut cell = types::TableCell::default();
2500 cell.set_vertical_merge(VMergeType::Restart);
2501
2502 let tcpr = cell.cell_properties.as_ref().unwrap();
2503 let vm = tcpr.vertical_merge.as_ref().unwrap();
2504 assert_eq!(vm.value, Some(types::STMerge::Restart));
2505 }
2506
2507 #[test]
2508 #[cfg(feature = "wml-tables")]
2509 fn test_cell_set_vertical_merge_continue() {
2510 let mut cell = types::TableCell::default();
2511 cell.set_vertical_merge(VMergeType::Continue);
2512
2513 let tcpr = cell.cell_properties.as_ref().unwrap();
2514 let vm = tcpr.vertical_merge.as_ref().unwrap();
2515 assert_eq!(vm.value, None);
2516 }
2517
2518 #[test]
2523 #[cfg(all(
2524 feature = "wml-styling",
2525 feature = "extra-attrs",
2526 feature = "extra-children"
2527 ))]
2528 fn test_roundtrip_page_break() {
2529 use crate::Document;
2530 use crate::writer::DocumentBuilder;
2531 use std::io::Cursor;
2532
2533 let mut builder = DocumentBuilder::new();
2534 let body = builder.body_mut();
2535 let para = body.add_paragraph();
2536 para.add_page_break();
2537
2538 let mut buf = Cursor::new(Vec::new());
2539 builder.write(&mut buf).unwrap();
2540
2541 buf.set_position(0);
2542 let doc = Document::from_reader(buf).unwrap();
2543 let body_ref = doc.body();
2544 assert!(!body_ref.block_content.is_empty());
2545 }
2546
2547 #[test]
2548 #[cfg(all(
2549 feature = "wml-styling",
2550 feature = "extra-attrs",
2551 feature = "extra-children"
2552 ))]
2553 fn test_roundtrip_spacing_and_indent() {
2554 use crate::Document;
2555 use crate::ext::BodyExt;
2556 use crate::writer::DocumentBuilder;
2557 use std::io::Cursor;
2558
2559 let mut builder = DocumentBuilder::new();
2560 {
2561 let body = builder.body_mut();
2562 let para = body.add_paragraph();
2563 para.set_space_before(240);
2564 para.set_space_after(120);
2565 para.set_line_spacing(360);
2566 para.set_indent_left(720);
2567 para.set_outline_level(0);
2568 para.add_run().set_text("test spacing");
2569 }
2570
2571 let mut buf = Cursor::new(Vec::new());
2572 builder.write(&mut buf).unwrap();
2573
2574 buf.set_position(0);
2575 let doc = Document::from_reader(buf).unwrap();
2576 let body_ref = doc.body();
2577 assert_eq!(body_ref.paragraphs().len(), 1);
2578
2579 let p = body_ref.paragraphs()[0];
2580 let ppr = p.p_pr.as_ref().unwrap();
2581 let spacing = ppr.spacing.as_ref().unwrap();
2582 assert_eq!(spacing.before.as_deref(), Some("240"));
2583 assert_eq!(spacing.after.as_deref(), Some("120"));
2584 }
2585
2586 #[test]
2587 #[cfg(all(
2588 feature = "wml-tables",
2589 feature = "extra-attrs",
2590 feature = "extra-children"
2591 ))]
2592 fn test_roundtrip_grid_span_and_vmerge() {
2593 use crate::Document;
2594 use crate::ext::BodyExt;
2595 use crate::writer::DocumentBuilder;
2596 use std::io::Cursor;
2597
2598 let mut builder = DocumentBuilder::new();
2599 {
2600 let body = builder.body_mut();
2601 let tbl = body.add_table();
2602 let row = tbl.add_row();
2603 let cell = row.add_cell();
2604 cell.set_grid_span(2);
2605 cell.set_vertical_merge(VMergeType::Restart);
2606 cell.add_paragraph().add_run().set_text("merged");
2607 }
2608
2609 let mut buf = Cursor::new(Vec::new());
2610 builder.write(&mut buf).unwrap();
2611
2612 buf.set_position(0);
2613 let doc = Document::from_reader(buf).unwrap();
2614 let body_ref = doc.body();
2615 assert!(!body_ref.tables().is_empty());
2616
2617 let tbl = body_ref.tables()[0];
2618 let row = match &tbl.rows[0] {
2619 crate::types::RowContent::Tr(r) => r,
2620 _ => panic!("expected Tr"),
2621 };
2622 let cell = match &row.cells[0] {
2623 crate::types::CellContent::Tc(c) => c,
2624 _ => panic!("expected Tc"),
2625 };
2626 let tcpr = cell.cell_properties.as_ref().unwrap();
2627 assert_eq!(tcpr.grid_span.as_ref().unwrap().value, 2);
2628 assert_eq!(
2629 tcpr.vertical_merge.as_ref().unwrap().value,
2630 Some(crate::types::STMerge::Restart)
2631 );
2632 }
2633
2634 #[test]
2639 fn test_add_bookmark_start_u32() {
2640 let mut para = types::Paragraph::default();
2641 para.add_bookmark_start_u32(1, "myBookmark");
2642
2643 assert_eq!(para.paragraph_content.len(), 1);
2644 match ¶.paragraph_content[0] {
2645 types::ParagraphContent::BookmarkStart(bm) => {
2646 assert_eq!(bm.id, 1);
2647 assert_eq!(bm.name, "myBookmark");
2648 }
2649 _ => panic!("expected BookmarkStart"),
2650 }
2651 }
2652
2653 #[test]
2654 fn test_add_bookmark_end_u32() {
2655 let mut para = types::Paragraph::default();
2656 para.add_bookmark_end_u32(42);
2657
2658 assert_eq!(para.paragraph_content.len(), 1);
2659 match ¶.paragraph_content[0] {
2660 types::ParagraphContent::BookmarkEnd(bm) => {
2661 assert_eq!(bm.id, 42);
2662 }
2663 _ => panic!("expected BookmarkEnd"),
2664 }
2665 }
2666
2667 #[test]
2672 #[cfg(feature = "wml-tables")]
2673 fn test_row_set_height() {
2674 let mut row = types::CTRow::default();
2675 row.set_height(720);
2676
2677 let row_pr = row.row_properties.as_ref().unwrap();
2678 let height = row_pr.tr_height.as_ref().unwrap();
2679 assert_eq!(height.value.as_deref(), Some("720"));
2680 assert_eq!(height.h_rule, Some(types::STHeightRule::Exact));
2681 }
2682
2683 #[test]
2688 #[cfg(feature = "wml-tables")]
2689 fn test_cell_set_background_color() {
2690 let mut cell = types::TableCell::default();
2691 cell.set_background_color("FF0000");
2692
2693 let tcpr = cell.cell_properties.as_ref().unwrap();
2694 let shd = tcpr.shading.as_ref().unwrap();
2695 assert_eq!(shd.value, types::STShd::Clear);
2696 #[cfg(feature = "wml-styling")]
2697 assert_eq!(shd.fill.as_deref(), Some("FF0000"));
2698 }
2699
2700 #[test]
2705 #[cfg(feature = "wml-tables")]
2706 fn test_cell_set_borders() {
2707 let mut cell = types::TableCell::default();
2708 cell.set_borders(BorderStyle::Single, 4, "000000");
2709
2710 let tcpr = cell.cell_properties.as_ref().unwrap();
2711 let borders = tcpr.tc_borders.as_ref().unwrap();
2712 assert!(borders.top.is_some());
2713 assert!(borders.bottom.is_some());
2714 assert!(borders.left.is_some());
2715 assert!(borders.right.is_some());
2716 let top = borders.top.as_ref().unwrap();
2717 assert_eq!(top.value, types::STBorder::Single);
2718 #[cfg(feature = "wml-styling")]
2719 assert_eq!(top.size, Some(4u64));
2720 }
2721
2722 #[test]
2723 #[cfg(feature = "wml-tables")]
2724 fn test_cell_set_border_top_only() {
2725 let mut cell = types::TableCell::default();
2726 cell.set_border_top(BorderStyle::Dashed, 8, "AABBCC");
2727
2728 let tcpr = cell.cell_properties.as_ref().unwrap();
2729 let borders = tcpr.tc_borders.as_ref().unwrap();
2730 assert!(borders.top.is_some());
2731 assert!(borders.bottom.is_none());
2732 assert!(borders.left.is_none());
2733 assert!(borders.right.is_none());
2734 }
2735
2736 #[test]
2741 #[cfg(feature = "wml-tables")]
2742 fn test_cell_set_padding() {
2743 let mut cell = types::TableCell::default();
2744 cell.set_padding(100, 100, 200, 200);
2745
2746 let tcpr = cell.cell_properties.as_ref().unwrap();
2747 let mar = tcpr.tc_mar.as_ref().unwrap();
2748 let top = mar.top.as_ref().unwrap();
2749 assert_eq!(top.width.as_deref(), Some("100"));
2750 assert_eq!(top.r#type, Some(types::STTblWidth::Dxa));
2751 let left = mar.left.as_ref().unwrap();
2752 assert_eq!(left.width.as_deref(), Some("200"));
2753 }
2754
2755 #[test]
2760 #[cfg(feature = "wml-tables")]
2761 fn test_table_set_width_dxa() {
2762 let table = types::Table {
2763 range_markup: Vec::new(),
2764 table_properties: Box::new(types::TableProperties::default()),
2765 tbl_grid: Box::new(types::TableGrid::default()),
2766 rows: Vec::new(),
2767 #[cfg(feature = "extra-children")]
2768 extra_children: Vec::new(),
2769 };
2770 let mut body = types::Body::default();
2771 body.block_content
2772 .push(types::BlockContent::Tbl(Box::new(table)));
2773 let tbl = match body.block_content.last_mut().unwrap() {
2774 types::BlockContent::Tbl(t) => t.as_mut(),
2775 _ => unreachable!(),
2776 };
2777 tbl.set_width(9360, TableWidthUnit::Dxa); let tbl_w = tbl.table_properties.tbl_w.as_ref().unwrap();
2780 assert_eq!(tbl_w.width.as_deref(), Some("9360"));
2781 assert_eq!(tbl_w.r#type, Some(types::STTblWidth::Dxa));
2782 }
2783
2784 #[test]
2785 #[cfg(feature = "wml-tables")]
2786 fn test_table_set_width_pct() {
2787 let table = types::Table {
2788 range_markup: Vec::new(),
2789 table_properties: Box::new(types::TableProperties::default()),
2790 tbl_grid: Box::new(types::TableGrid::default()),
2791 rows: Vec::new(),
2792 #[cfg(feature = "extra-children")]
2793 extra_children: Vec::new(),
2794 };
2795 let mut body = types::Body::default();
2796 body.block_content
2797 .push(types::BlockContent::Tbl(Box::new(table)));
2798 let tbl = match body.block_content.last_mut().unwrap() {
2799 types::BlockContent::Tbl(t) => t.as_mut(),
2800 _ => unreachable!(),
2801 };
2802 tbl.set_width(5000, TableWidthUnit::Pct); let tbl_w = tbl.table_properties.tbl_w.as_ref().unwrap();
2805 assert_eq!(tbl_w.width.as_deref(), Some("5000"));
2806 assert_eq!(tbl_w.r#type, Some(types::STTblWidth::Pct));
2807 }
2808
2809 #[test]
2814 #[cfg(all(
2815 feature = "wml-tables",
2816 feature = "wml-styling",
2817 feature = "extra-attrs",
2818 feature = "extra-children"
2819 ))]
2820 fn test_roundtrip_cell_background_and_borders() {
2821 use crate::Document;
2822 use crate::ext::BodyExt;
2823 use crate::writer::DocumentBuilder;
2824 use std::io::Cursor;
2825
2826 let mut builder = DocumentBuilder::new();
2827 {
2828 let body = builder.body_mut();
2829 let tbl = body.add_table();
2830 let row = tbl.add_row();
2831 let cell = row.add_cell();
2832 cell.set_background_color("FFFF00");
2833 cell.set_borders(BorderStyle::Single, 4, "000000");
2834 cell.set_padding(72, 72, 144, 144);
2835 cell.add_paragraph().add_run().set_text("styled");
2836 }
2837
2838 let mut buf = Cursor::new(Vec::new());
2839 builder.write(&mut buf).unwrap();
2840
2841 buf.set_position(0);
2842 let doc = Document::from_reader(buf).unwrap();
2843 let body_ref = doc.body();
2844 assert!(!body_ref.tables().is_empty());
2845
2846 let tbl = body_ref.tables()[0];
2847 let row = match &tbl.rows[0] {
2848 crate::types::RowContent::Tr(r) => r,
2849 _ => panic!("expected Tr"),
2850 };
2851 let cell = match &row.cells[0] {
2852 crate::types::CellContent::Tc(c) => c,
2853 _ => panic!("expected Tc"),
2854 };
2855 let tcpr = cell.cell_properties.as_ref().unwrap();
2856 let shd = tcpr.shading.as_ref().unwrap();
2857 assert_eq!(shd.value, crate::types::STShd::Clear);
2858 assert_eq!(shd.fill.as_deref(), Some("FFFF00"));
2859
2860 let borders = tcpr.tc_borders.as_ref().unwrap();
2861 assert!(borders.top.is_some());
2862 let top = borders.top.as_ref().unwrap();
2863 assert_eq!(top.value, crate::types::STBorder::Single);
2864
2865 let mar = tcpr.tc_mar.as_ref().unwrap();
2866 let left = mar.left.as_ref().unwrap();
2867 assert_eq!(left.width.as_deref(), Some("144"));
2868 }
2869}