Skip to main content

pptx_theme_and_metadata/
pptx_theme_and_metadata.rs

1//! PowerPoint theme, embedded fonts, and document metadata. Theme and
2//! metadata both apply to a whole presentation, so this example writes
3//! two small files rather than two slides of a single one.
4//!
5//! Run with: `cargo run -p office-toolkit --example pptx_theme_and_metadata`
6
7use std::path::{Path, PathBuf};
8
9use office_toolkit::SaveToFile;
10use office_toolkit::drawing::{
11    Color, Fill, ShapeProperties, TextBody, TextParagraph, TextRun, TextRunProperties, Transform2D,
12};
13use office_toolkit::powerpoint::{
14    AutoShape, ColorScheme, CustomPropertyValue, DocumentProperties, EmbeddedFont, FontScheme,
15    Shape, Theme,
16};
17use office_toolkit::prelude::*;
18
19fn main() -> office_toolkit::Result<()> {
20    // A custom theme and an embedded font. `Theme`/`ColorScheme`/
21    // `FontScheme` have no `with_*` builders in this crate — only
22    // `office_default()` or plain struct literals.
23    let custom_theme = Theme {
24        name: "Custom Brand".to_string(),
25        colors: ColorScheme {
26            dark1: "1B1B1B".to_string(),
27            light1: "FFFFFF".to_string(),
28            dark2: "0A2540".to_string(),
29            light2: "F5F5F5".to_string(),
30            accent1: "0A2540".to_string(),
31            accent2: "E8622C".to_string(),
32            accent3: "2E86AB".to_string(),
33            accent4: "6FB98F".to_string(),
34            accent5: "F4A259".to_string(),
35            accent6: "8E4585".to_string(),
36            hyperlink: "1155CC".to_string(),
37            followed_hyperlink: "6B3FA0".to_string(),
38        },
39        fonts: FontScheme {
40            major_latin: "Georgia".to_string(),
41            minor_latin: "Verdana".to_string(),
42        },
43    };
44
45    // Placeholder bytes — a real .pptx would carry genuine TrueType/
46    // OpenType font data here.
47    let embedded_font = EmbeddedFont::new("Custom Sans")
48        .with_regular(vec![0u8; 16])
49        .with_bold(vec![0u8; 16]);
50
51    // A slide with no shapes at all renders as a blank page — nothing shows
52    // the custom theme actually took effect. `drawing::Color` has no
53    // scheme-relative variant yet (`<a:schemeClr>` isn't modeled, see that
54    // enum's own doc comment), so this can't reference the theme's accent1
55    // symbolically; it uses the same literal hex values the theme itself
56    // was built from, purely so the slide visibly matches the palette above.
57    let title_shape = AutoShape::new(2, "Title")
58        .with_properties(
59            ShapeProperties::new()
60                .with_transform(
61                    Transform2D::new()
62                        .with_offset(838_200, 838_200)
63                        .with_extent(6_000_000, 1_500_000),
64                )
65                .with_fill(Fill::Solid(Color::Rgb(custom_theme.colors.accent1.clone()))),
66        )
67        .with_text_body(
68            TextBody::new().with_paragraph(
69                TextParagraph::new().with_run(TextRun::text_with_properties(
70                    "Custom Brand theme — Georgia / Verdana, accent1 = #0A2540",
71                    TextRunProperties::new()
72                        .with_font_family(custom_theme.fonts.major_latin.clone())
73                        .with_font_size_points(24.0)
74                        .with_fill(Fill::Solid(Color::Rgb(custom_theme.colors.light1.clone()))),
75                )),
76            ),
77        );
78
79    let themed_presentation = Presentation::new()
80        .with_theme(custom_theme)
81        .with_embedded_font(embedded_font)
82        .with_slide(Slide::new().with_shape(Shape::AutoShape(title_shape)));
83    let theme_path = output_path("pptx_theme.pptx");
84    themed_presentation.save_to_file(&theme_path)?;
85    println!("Wrote {}", theme_path.display());
86
87    // Document metadata (docProps/core.xml, app.xml, and custom.xml).
88    let properties = DocumentProperties::new()
89        .with_title("Metadata example")
90        .with_author("Example Author")
91        .with_subject("Presentation-level features")
92        .with_custom_property(
93            "ProjectCode",
94            CustomPropertyValue::Text("PPTX-META".to_string()),
95        );
96    // Same reasoning as `title_shape` above: an empty slide would render
97    // blank, so a text shape spells out where the metadata actually lives.
98    let metadata_shape = AutoShape::new(2, "Note")
99        .with_properties(ShapeProperties::new().with_transform(Transform2D::new().with_offset(838_200, 838_200).with_extent(7_000_000, 1_000_000)))
100        .with_text_body(TextBody::new().with_paragraph(TextParagraph::new().with_run(TextRun::text(
101            "This presentation's metadata is in File > Info (title, author, subject, and a custom \"ProjectCode\" property) — not on this slide.",
102        ))));
103    let metadata_presentation = Presentation::new()
104        .with_properties(properties)
105        .with_slide(Slide::new().with_shape(Shape::AutoShape(metadata_shape)));
106    let metadata_path = output_path("pptx_metadata.pptx");
107    metadata_presentation.save_to_file(&metadata_path)?;
108    println!("Wrote {}", metadata_path.display());
109
110    Ok(())
111}
112
113/// Every example in this directory writes its output under
114/// `tests-data/output/`, resolved relative to this crate's own manifest so
115/// it works no matter what directory `cargo run` was invoked from.
116fn output_path(filename: &str) -> PathBuf {
117    Path::new(env!("CARGO_MANIFEST_DIR"))
118        .join("../../tests-data/output")
119        .join(filename)
120}