Skip to main content

ppt_rs/generator/
template.rs

1//! Load theme / master / layout parts from an existing PPTX template.
2
3use std::collections::HashMap;
4use std::path::Path;
5
6use crate::exc::{PptxError, Result};
7use crate::opc::Package;
8
9/// Theme + master + layout parts cloned from an existing `.pptx` file.
10#[derive(Clone, Debug, Default)]
11pub struct PptxTemplate {
12    parts: HashMap<String, Vec<u8>>,
13    layout_count: usize,
14}
15
16impl PptxTemplate {
17    /// Load template parts from a `.pptx` on disk.
18    pub fn load(path: impl AsRef<Path>) -> Result<Self> {
19        let pkg = Package::open(path)?;
20        Self::from_package(&pkg)
21    }
22
23    /// Extract template parts from an opened package.
24    pub fn from_package(pkg: &Package) -> Result<Self> {
25        let mut parts = HashMap::new();
26        for path in pkg.part_paths() {
27            if path.starts_with("ppt/theme/")
28                || path.starts_with("ppt/slideMasters/")
29                || path.starts_with("ppt/slideLayouts/")
30                || path == "ppt/tableStyles.xml"
31            {
32                if let Some(data) = pkg.get_part(path) {
33                    parts.insert(path.to_string(), data.to_vec());
34                }
35            }
36        }
37
38        if !parts.keys().any(|p| p.starts_with("ppt/slideMasters/")) {
39            return Err(PptxError::InvalidValue(
40                "template missing ppt/slideMasters/".into(),
41            ));
42        }
43
44        let layout_count = parts
45            .keys()
46            .filter(|p| {
47                p.starts_with("ppt/slideLayouts/slideLayout")
48                    && p.ends_with(".xml")
49                    && !p.contains("_rels")
50            })
51            .count()
52            .max(1);
53
54        Ok(PptxTemplate { parts, layout_count })
55    }
56
57    pub fn layout_count(&self) -> usize {
58        self.layout_count
59    }
60
61    pub fn parts(&self) -> &HashMap<String, Vec<u8>> {
62        &self.parts
63    }
64
65    pub fn has_layout(&self, n: usize) -> bool {
66        self.parts
67            .contains_key(&format!("ppt/slideLayouts/slideLayout{n}.xml"))
68    }
69
70    /// Resolve layout index for a slide, capped to layouts available in the template.
71    pub fn resolve_layout_number(&self, requested: usize) -> usize {
72        if self.has_layout(requested) {
73            requested
74        } else {
75            1
76        }
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    use crate::generator::create_pptx;
84
85    #[test]
86    fn load_template_from_generated_deck() {
87        let bytes = create_pptx("Tpl", 1).unwrap();
88        let dir = std::env::temp_dir().join("ppt_rs_template_test.pptx");
89        std::fs::write(&dir, &bytes).unwrap();
90        let tpl = PptxTemplate::load(&dir).unwrap();
91        assert!(tpl.layout_count() >= 1);
92        assert!(tpl.has_layout(1));
93        std::fs::remove_file(dir).ok();
94    }
95}