Skip to main content

pptx_text_formatting/
pptx_text_formatting.rs

1//! PowerPoint text-in-shape formatting (from the shared `drawing` crate):
2//! paragraph spacing/indent, bullets and auto-numbering, autofit and
3//! centered text, casing/spacing/highlight/superscript, and a dynamic
4//! text field. One slide per feature.
5//!
6//! Run with: `cargo run -p office-toolkit --example pptx_text_formatting`
7
8use std::path::{Path, PathBuf};
9
10use office_toolkit::SaveToFile;
11use office_toolkit::drawing::{
12    BulletKind, Color, ShapeProperties, TextAnchor, TextAutofit, TextBody, TextBodyProperties,
13    TextCaps, TextParagraph, TextParagraphProperties, TextRun, TextRunProperties, TextSpacing,
14    Transform2D,
15};
16use office_toolkit::powerpoint::{AutoShape, Shape};
17use office_toolkit::prelude::*;
18
19fn main() -> office_toolkit::Result<()> {
20    let path = output_path("pptx_text_formatting.pptx");
21
22    let spacing_shape = AutoShape::new(2, "Spacing")
23        .with_properties(
24            ShapeProperties::new().with_transform(
25                Transform2D::new()
26                    .with_offset(838_200, 838_200)
27                    .with_extent(6_000_000, 1_500_000),
28            ),
29        )
30        .with_text_body(
31            TextBody::new().with_paragraph(
32                TextParagraph::new()
33                    .with_properties(
34                        TextParagraphProperties::new()
35                            .with_line_spacing(TextSpacing::Percent(150_000))
36                            .with_space_before(TextSpacing::Points(1200))
37                            .with_indent_emu(457_200)
38                            .with_level(1),
39                    )
40                    .with_run(TextRun::text(
41                        "Indented, 150% line spacing, 12pt space before.",
42                    )),
43            ),
44        );
45
46    let bullets_shape = AutoShape::new(2, "Bullets")
47        .with_properties(
48            ShapeProperties::new().with_transform(
49                Transform2D::new()
50                    .with_offset(838_200, 838_200)
51                    .with_extent(6_000_000, 1_800_000),
52            ),
53        )
54        .with_text_body(
55            TextBody::new()
56                .with_paragraph(
57                    TextParagraph::new()
58                        .with_properties(
59                            TextParagraphProperties::new()
60                                .with_bullet(BulletKind::Character("\u{2022}".to_string())),
61                        )
62                        .with_run(TextRun::text("Bulleted item")),
63                )
64                .with_paragraph(
65                    TextParagraph::new()
66                        .with_properties(TextParagraphProperties::new().with_bullet(
67                            BulletKind::AutoNumber {
68                                scheme: "arabicPeriod".to_string(),
69                                start_at: None,
70                            },
71                        ))
72                        .with_run(TextRun::text("Auto-numbered item")),
73                ),
74        );
75
76    let autofit_shape = AutoShape::new(2, "Autofit")
77        .with_text_box(true)
78        .with_properties(
79            ShapeProperties::new().with_transform(
80                Transform2D::new()
81                    .with_offset(838_200, 838_200)
82                    .with_extent(4_000_000, 1_000_000),
83            ),
84        )
85        .with_text_body(
86            TextBody::new()
87                .with_properties(
88                    TextBodyProperties::new()
89                        .with_autofit(TextAutofit::Shape)
90                        .with_anchor(TextAnchor::Center)
91                        .with_anchor_center(true),
92                )
93                .with_paragraph(TextParagraph::new().with_run(TextRun::text(
94                    "This shape resizes to fit its text, centered both ways.",
95                ))),
96        );
97
98    let casing_shape = AutoShape::new(2, "Casing")
99        .with_properties(
100            ShapeProperties::new().with_transform(
101                Transform2D::new()
102                    .with_offset(838_200, 838_200)
103                    .with_extent(6_000_000, 1_500_000),
104            ),
105        )
106        .with_text_body(
107            TextBody::new().with_paragraph(
108                TextParagraph::new()
109                    .with_run(TextRun::text_with_properties(
110                        "rendered in all caps, ",
111                        TextRunProperties::new().with_text_caps(TextCaps::All),
112                    ))
113                    .with_run(TextRun::text_with_properties(
114                        "widely spaced, ",
115                        TextRunProperties::new().with_character_spacing_points(3.0),
116                    ))
117                    .with_run(TextRun::text_with_properties(
118                        "highlighted",
119                        TextRunProperties::new().with_highlight(Color::Rgb("FFFF00".to_string())),
120                    ))
121                    .with_run(TextRun::text_with_properties(
122                        "2",
123                        TextRunProperties::new().with_baseline_percent(30.0),
124                    )),
125            ),
126        );
127
128    let field_shape = AutoShape::new(2, "Field")
129        .with_properties(
130            ShapeProperties::new().with_transform(
131                Transform2D::new()
132                    .with_offset(838_200, 838_200)
133                    .with_extent(3_000_000, 500_000),
134            ),
135        )
136        .with_text_body(
137            TextBody::new().with_paragraph(
138                TextParagraph::new()
139                    .with_run(TextRun::text("Slide "))
140                    .with_run(TextRun::field(
141                        "{7A9E3F10-4B6C-4D2E-9A1F-8C5D6E7F0123}",
142                        Some("slidenum".to_string()),
143                        "1",
144                    )),
145            ),
146        );
147
148    let presentation = Presentation::new()
149        .with_slide(Slide::new().with_shape(Shape::AutoShape(spacing_shape)))
150        .with_slide(Slide::new().with_shape(Shape::AutoShape(bullets_shape)))
151        .with_slide(Slide::new().with_shape(Shape::AutoShape(autofit_shape)))
152        .with_slide(Slide::new().with_shape(Shape::AutoShape(casing_shape)))
153        .with_slide(Slide::new().with_shape(Shape::AutoShape(field_shape)));
154
155    presentation.save_to_file(&path)?;
156    println!("Wrote {}", path.display());
157    Ok(())
158}
159
160/// Every example in this directory writes its output under
161/// `tests-data/output/`, resolved relative to this crate's own manifest so
162/// it works no matter what directory `cargo run` was invoked from.
163fn output_path(filename: &str) -> PathBuf {
164    Path::new(env!("CARGO_MANIFEST_DIR"))
165        .join("../../tests-data/output")
166        .join(filename)
167}