ppt_rs/parts/
slide_layout.rs

1//! Slide layout part
2//!
3//! Represents a slide layout template (ppt/slideLayouts/slideLayoutN.xml).
4
5use super::base::{Part, PartType, ContentType};
6use crate::exc::PptxError;
7
8/// Slide layout types
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum LayoutType {
11    Title,
12    TitleAndContent,
13    SectionHeader,
14    TwoContent,
15    Comparison,
16    TitleOnly,
17    Blank,
18    ContentWithCaption,
19    PictureWithCaption,
20    TitleAndVerticalText,
21    VerticalTitleAndText,
22    Custom,
23}
24
25impl LayoutType {
26    /// Get the layout name
27    pub fn name(&self) -> &'static str {
28        match self {
29            LayoutType::Title => "Title Slide",
30            LayoutType::TitleAndContent => "Title and Content",
31            LayoutType::SectionHeader => "Section Header",
32            LayoutType::TwoContent => "Two Content",
33            LayoutType::Comparison => "Comparison",
34            LayoutType::TitleOnly => "Title Only",
35            LayoutType::Blank => "Blank",
36            LayoutType::ContentWithCaption => "Content with Caption",
37            LayoutType::PictureWithCaption => "Picture with Caption",
38            LayoutType::TitleAndVerticalText => "Title and Vertical Text",
39            LayoutType::VerticalTitleAndText => "Vertical Title and Text",
40            LayoutType::Custom => "Custom",
41        }
42    }
43
44    /// Get the layout type attribute value
45    pub fn type_value(&self) -> &'static str {
46        match self {
47            LayoutType::Title => "title",
48            LayoutType::TitleAndContent => "obj",
49            LayoutType::SectionHeader => "secHead",
50            LayoutType::TwoContent => "twoObj",
51            LayoutType::Comparison => "twoTxTwoObj",
52            LayoutType::TitleOnly => "titleOnly",
53            LayoutType::Blank => "blank",
54            LayoutType::ContentWithCaption => "objTx",
55            LayoutType::PictureWithCaption => "picTx",
56            LayoutType::TitleAndVerticalText => "vertTx",
57            LayoutType::VerticalTitleAndText => "vertTitleAndTx",
58            LayoutType::Custom => "cust",
59        }
60    }
61}
62
63/// Slide layout part (ppt/slideLayouts/slideLayoutN.xml)
64#[derive(Debug, Clone)]
65pub struct SlideLayoutPart {
66    path: String,
67    layout_number: usize,
68    layout_type: LayoutType,
69    name: String,
70    master_rel_id: String,
71    xml_content: Option<String>,
72}
73
74impl SlideLayoutPart {
75    /// Create a new slide layout part
76    pub fn new(layout_number: usize, layout_type: LayoutType) -> Self {
77        SlideLayoutPart {
78            path: format!("ppt/slideLayouts/slideLayout{}.xml", layout_number),
79            layout_number,
80            layout_type,
81            name: layout_type.name().to_string(),
82            master_rel_id: "rId1".to_string(),
83            xml_content: None,
84        }
85    }
86
87    /// Get layout number
88    pub fn layout_number(&self) -> usize {
89        self.layout_number
90    }
91
92    /// Get layout type
93    pub fn layout_type(&self) -> LayoutType {
94        self.layout_type
95    }
96
97    /// Get layout name
98    pub fn name(&self) -> &str {
99        &self.name
100    }
101
102    /// Set custom name
103    pub fn set_name(&mut self, name: impl Into<String>) {
104        self.name = name.into();
105    }
106
107    /// Set master relationship ID
108    pub fn set_master_rel_id(&mut self, rel_id: impl Into<String>) {
109        self.master_rel_id = rel_id.into();
110    }
111
112    /// Get relative path for relationships
113    pub fn rel_target(&self) -> String {
114        format!("slideLayouts/slideLayout{}.xml", self.layout_number)
115    }
116
117    fn generate_xml(&self) -> String {
118        format!(
119            r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
120<p:sldLayout xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main" type="{}" preserve="1">
121  <p:cSld name="{}">
122    <p:spTree>
123      <p:nvGrpSpPr>
124        <p:cNvPr id="1" name=""/>
125        <p:cNvGrpSpPr/>
126        <p:nvPr/>
127      </p:nvGrpSpPr>
128      <p:grpSpPr>
129        <a:xfrm>
130          <a:off x="0" y="0"/>
131          <a:ext cx="0" cy="0"/>
132          <a:chOff x="0" y="0"/>
133          <a:chExt cx="0" cy="0"/>
134        </a:xfrm>
135      </p:grpSpPr>
136    </p:spTree>
137  </p:cSld>
138  <p:clrMapOvr>
139    <a:masterClrMapping/>
140  </p:clrMapOvr>
141</p:sldLayout>"#,
142            self.layout_type.type_value(),
143            self.name
144        )
145    }
146}
147
148impl Part for SlideLayoutPart {
149    fn path(&self) -> &str {
150        &self.path
151    }
152
153    fn part_type(&self) -> PartType {
154        PartType::SlideLayout
155    }
156
157    fn content_type(&self) -> ContentType {
158        ContentType::SlideLayout
159    }
160
161    fn to_xml(&self) -> Result<String, PptxError> {
162        if let Some(ref xml) = self.xml_content {
163            return Ok(xml.clone());
164        }
165        Ok(self.generate_xml())
166    }
167
168    fn from_xml(xml: &str) -> Result<Self, PptxError> {
169        Ok(SlideLayoutPart {
170            path: "ppt/slideLayouts/slideLayout1.xml".to_string(),
171            layout_number: 1,
172            layout_type: LayoutType::TitleAndContent,
173            name: "Layout".to_string(),
174            master_rel_id: "rId1".to_string(),
175            xml_content: Some(xml.to_string()),
176        })
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use super::*;
183
184    #[test]
185    fn test_slide_layout_new() {
186        let layout = SlideLayoutPart::new(1, LayoutType::Title);
187        assert_eq!(layout.layout_number(), 1);
188        assert_eq!(layout.layout_type(), LayoutType::Title);
189        assert_eq!(layout.path(), "ppt/slideLayouts/slideLayout1.xml");
190    }
191
192    #[test]
193    fn test_layout_type_name() {
194        assert_eq!(LayoutType::Title.name(), "Title Slide");
195        assert_eq!(LayoutType::Blank.name(), "Blank");
196        assert_eq!(LayoutType::TwoContent.name(), "Two Content");
197    }
198
199    #[test]
200    fn test_layout_type_value() {
201        assert_eq!(LayoutType::Title.type_value(), "title");
202        assert_eq!(LayoutType::Blank.type_value(), "blank");
203        assert_eq!(LayoutType::TitleAndContent.type_value(), "obj");
204    }
205
206    #[test]
207    fn test_slide_layout_to_xml() {
208        let layout = SlideLayoutPart::new(1, LayoutType::Title);
209        let xml = layout.to_xml().unwrap();
210        assert!(xml.contains("p:sldLayout"));
211        assert!(xml.contains("type=\"title\""));
212    }
213
214    #[test]
215    fn test_slide_layout_rel_target() {
216        let layout = SlideLayoutPart::new(3, LayoutType::Blank);
217        assert_eq!(layout.rel_target(), "slideLayouts/slideLayout3.xml");
218    }
219}