ppt_rs/generator/slide_content/
print_settings.rs1#[derive(Clone, Debug, Copy, PartialEq, Eq, Default)]
8pub enum HandoutLayout {
9 SlidesPerPage1,
10 SlidesPerPage2,
11 #[default]
12 SlidesPerPage3,
13 SlidesPerPage4,
14 SlidesPerPage6,
15 SlidesPerPage9,
16}
17
18impl HandoutLayout {
19 pub fn slides_per_page(&self) -> u32 {
20 match self {
21 HandoutLayout::SlidesPerPage1 => 1,
22 HandoutLayout::SlidesPerPage2 => 2,
23 HandoutLayout::SlidesPerPage3 => 3,
24 HandoutLayout::SlidesPerPage4 => 4,
25 HandoutLayout::SlidesPerPage6 => 6,
26 HandoutLayout::SlidesPerPage9 => 9,
27 }
28 }
29
30 pub fn to_xml_value(&self) -> &'static str {
31 match self {
32 HandoutLayout::SlidesPerPage1 => "handout1",
33 HandoutLayout::SlidesPerPage2 => "handout2",
34 HandoutLayout::SlidesPerPage3 => "handout3",
35 HandoutLayout::SlidesPerPage4 => "handout4",
36 HandoutLayout::SlidesPerPage6 => "handout6",
37 HandoutLayout::SlidesPerPage9 => "handout9",
38 }
39 }
40}
41
42#[derive(Clone, Debug, Copy, PartialEq, Eq, Default)]
44pub enum PrintColorMode {
45 #[default]
46 Color,
47 Grayscale,
48 BlackAndWhite,
49}
50
51impl PrintColorMode {
52 pub fn to_xml_value(&self) -> &'static str {
53 match self {
54 PrintColorMode::Color => "clr",
55 PrintColorMode::Grayscale => "gray",
56 PrintColorMode::BlackAndWhite => "bw",
57 }
58 }
59}
60
61#[derive(Clone, Debug, Copy, PartialEq, Eq, Default)]
63pub enum PrintWhat {
64 #[default]
65 Slides,
66 Handouts,
67 Notes,
68 Outline,
69}
70
71impl PrintWhat {
72 pub fn to_xml_value(&self) -> &'static str {
73 match self {
74 PrintWhat::Slides => "slides",
75 PrintWhat::Handouts => "handouts",
76 PrintWhat::Notes => "notes",
77 PrintWhat::Outline => "outline",
78 }
79 }
80}
81
82#[derive(Clone, Debug, Copy, PartialEq, Eq, Default)]
84pub enum Orientation {
85 #[default]
86 Landscape,
87 Portrait,
88}
89
90impl Orientation {
91 pub fn to_xml_value(&self) -> &'static str {
92 match self {
93 Orientation::Landscape => "landscape",
94 Orientation::Portrait => "portrait",
95 }
96 }
97}
98
99#[derive(Clone, Debug, Default)]
101pub struct PrintSettings {
102 pub print_what: PrintWhat,
103 pub color_mode: PrintColorMode,
104 pub handout_layout: HandoutLayout,
105 pub frame_slides: bool,
106 pub scale_to_fit: bool,
107 pub include_hidden_slides: bool,
108 pub orientation: Orientation,
109 pub header: Option<String>,
110 pub footer: Option<String>,
111 pub print_date: bool,
112 pub print_page_numbers: bool,
113}
114
115impl PrintSettings {
116 pub fn new() -> Self {
117 Self {
118 scale_to_fit: true,
119 ..Default::default()
120 }
121 }
122
123 pub fn print_what(mut self, what: PrintWhat) -> Self {
124 self.print_what = what;
125 self
126 }
127
128 pub fn color_mode(mut self, mode: PrintColorMode) -> Self {
129 self.color_mode = mode;
130 self
131 }
132
133 pub fn handout_layout(mut self, layout: HandoutLayout) -> Self {
134 self.handout_layout = layout;
135 self
136 }
137
138 pub fn frame_slides(mut self, frame: bool) -> Self {
139 self.frame_slides = frame;
140 self
141 }
142
143 pub fn scale_to_fit(mut self, scale: bool) -> Self {
144 self.scale_to_fit = scale;
145 self
146 }
147
148 pub fn include_hidden_slides(mut self, include: bool) -> Self {
149 self.include_hidden_slides = include;
150 self
151 }
152
153 pub fn orientation(mut self, orientation: Orientation) -> Self {
154 self.orientation = orientation;
155 self
156 }
157
158 pub fn header(mut self, header: &str) -> Self {
159 self.header = Some(header.to_string());
160 self
161 }
162
163 pub fn footer(mut self, footer: &str) -> Self {
164 self.footer = Some(footer.to_string());
165 self
166 }
167
168 pub fn print_date(mut self, val: bool) -> Self {
169 self.print_date = val;
170 self
171 }
172
173 pub fn print_page_numbers(mut self, val: bool) -> Self {
174 self.print_page_numbers = val;
175 self
176 }
177
178 pub fn to_prnpr_xml(&self) -> String {
180 let mut attrs = Vec::new();
181 attrs.push(format!(r#"prnWhat="{}""#, self.print_what.to_xml_value()));
182 attrs.push(format!(r#"clrMode="{}""#, self.color_mode.to_xml_value()));
183
184 if self.frame_slides {
185 attrs.push("frameSlides=\"1\"".to_string());
186 }
187 if self.include_hidden_slides {
188 attrs.push("hiddenSlides=\"1\"".to_string());
189 }
190 if self.scale_to_fit {
191 attrs.push("scaleToFitPaper=\"1\"".to_string());
192 }
193
194 format!(r#"<p:prnPr {}/>"#, attrs.join(" "))
195 }
196
197 pub fn to_handout_master_xml(&self) -> String {
199 let mut xml = String::from(
200 r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?><p:handoutMaster 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"><p:cSld><p:bg><p:bgRef idx="1001"><a:schemeClr val="bg1"/></p:bgRef></p:bg><p:spTree><p:nvGrpSpPr><p:cNvPr id="1" name=""/><p:cNvGrpSpPr/><p:nvPr/></p:nvGrpSpPr><p:grpSpPr><a:xfrm><a:off x="0" y="0"/><a:ext cx="0" cy="0"/><a:chOff x="0" y="0"/><a:chExt cx="0" cy="0"/></a:xfrm></p:grpSpPr>"#,
201 );
202
203 if let Some(ref header) = self.header {
204 xml.push_str(&format!(
205 r#"<p:sp><p:nvSpPr><p:cNvPr id="2" name="Header"/><p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr><p:nvPr><p:ph type="hdr"/></p:nvPr></p:nvSpPr><p:spPr/><p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:r><a:t>{}</a:t></a:r></a:p></p:txBody></p:sp>"#,
206 xml_escape(header)
207 ));
208 }
209
210 if let Some(ref footer) = self.footer {
211 xml.push_str(&format!(
212 r#"<p:sp><p:nvSpPr><p:cNvPr id="3" name="Footer"/><p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr><p:nvPr><p:ph type="ftr"/></p:nvPr></p:nvSpPr><p:spPr/><p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:r><a:t>{}</a:t></a:r></a:p></p:txBody></p:sp>"#,
213 xml_escape(footer)
214 ));
215 }
216
217 if self.print_date {
218 xml.push_str(
219 r#"<p:sp><p:nvSpPr><p:cNvPr id="4" name="Date"/><p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr><p:nvPr><p:ph type="dt"/></p:nvPr></p:nvSpPr><p:spPr/><p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:fld id="{B6F15528-F159-4107-2D14-000000000000}" type="datetimeFigureOut"><a:t/></a:fld><a:endParaRPr/></a:p></p:txBody></p:sp>"#,
220 );
221 }
222
223 if self.print_page_numbers {
224 xml.push_str(
225 r#"<p:sp><p:nvSpPr><p:cNvPr id="5" name="Slide Number"/><p:cNvSpPr><a:spLocks noGrp="1"/></p:cNvSpPr><p:nvPr><p:ph type="sldNum"/></p:nvPr></p:nvSpPr><p:spPr/><p:txBody><a:bodyPr/><a:lstStyle/><a:p><a:fld id="{B6F15528-F159-4107-2D14-000000000001}" type="slidenum"><a:t>‹#›</a:t></a:fld><a:endParaRPr/></a:p></p:txBody></p:sp>"#,
226 );
227 }
228
229 xml.push_str(r#"</p:spTree></p:cSld><p:clrMap bg1="lt1" tx1="dk1" bg2="lt2" tx2="dk2" accent1="accent1" accent2="accent2" accent3="accent3" accent4="accent4" accent5="accent5" accent6="accent6" hlink="hlink" folHlink="folHlink"/></p:handoutMaster>"#);
230 xml
231 }
232}
233
234fn xml_escape(s: &str) -> String {
235 s.replace('&', "&")
236 .replace('<', "<")
237 .replace('>', ">")
238 .replace('"', """)
239 .replace('\'', "'")
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245
246 #[test]
247 fn test_handout_layout_default() {
248 assert_eq!(HandoutLayout::default(), HandoutLayout::SlidesPerPage3);
249 assert_eq!(HandoutLayout::SlidesPerPage3.slides_per_page(), 3);
250 }
251
252 #[test]
253 fn test_handout_layout_variants() {
254 assert_eq!(HandoutLayout::SlidesPerPage1.slides_per_page(), 1);
255 assert_eq!(HandoutLayout::SlidesPerPage2.slides_per_page(), 2);
256 assert_eq!(HandoutLayout::SlidesPerPage4.slides_per_page(), 4);
257 assert_eq!(HandoutLayout::SlidesPerPage6.slides_per_page(), 6);
258 assert_eq!(HandoutLayout::SlidesPerPage9.slides_per_page(), 9);
259 }
260
261 #[test]
262 fn test_handout_layout_xml() {
263 assert_eq!(HandoutLayout::SlidesPerPage1.to_xml_value(), "handout1");
264 assert_eq!(HandoutLayout::SlidesPerPage6.to_xml_value(), "handout6");
265 }
266
267 #[test]
268 fn test_print_color_mode() {
269 assert_eq!(PrintColorMode::default(), PrintColorMode::Color);
270 assert_eq!(PrintColorMode::Color.to_xml_value(), "clr");
271 assert_eq!(PrintColorMode::Grayscale.to_xml_value(), "gray");
272 assert_eq!(PrintColorMode::BlackAndWhite.to_xml_value(), "bw");
273 }
274
275 #[test]
276 fn test_print_what() {
277 assert_eq!(PrintWhat::default(), PrintWhat::Slides);
278 assert_eq!(PrintWhat::Slides.to_xml_value(), "slides");
279 assert_eq!(PrintWhat::Handouts.to_xml_value(), "handouts");
280 assert_eq!(PrintWhat::Notes.to_xml_value(), "notes");
281 assert_eq!(PrintWhat::Outline.to_xml_value(), "outline");
282 }
283
284 #[test]
285 fn test_orientation() {
286 assert_eq!(Orientation::default(), Orientation::Landscape);
287 assert_eq!(Orientation::Landscape.to_xml_value(), "landscape");
288 assert_eq!(Orientation::Portrait.to_xml_value(), "portrait");
289 }
290
291 #[test]
292 fn test_print_settings_new() {
293 let s = PrintSettings::new();
294 assert_eq!(s.print_what, PrintWhat::Slides);
295 assert_eq!(s.color_mode, PrintColorMode::Color);
296 assert!(s.scale_to_fit);
297 assert!(!s.frame_slides);
298 }
299
300 #[test]
301 fn test_print_settings_builder() {
302 let s = PrintSettings::new()
303 .print_what(PrintWhat::Handouts)
304 .color_mode(PrintColorMode::Grayscale)
305 .handout_layout(HandoutLayout::SlidesPerPage6)
306 .frame_slides(true)
307 .scale_to_fit(false)
308 .include_hidden_slides(true)
309 .orientation(Orientation::Portrait)
310 .header("My Header")
311 .footer("Page Footer")
312 .print_date(true)
313 .print_page_numbers(true);
314 assert_eq!(s.print_what, PrintWhat::Handouts);
315 assert_eq!(s.color_mode, PrintColorMode::Grayscale);
316 assert_eq!(s.handout_layout, HandoutLayout::SlidesPerPage6);
317 assert!(s.frame_slides);
318 assert!(!s.scale_to_fit);
319 assert!(s.include_hidden_slides);
320 assert_eq!(s.header.as_deref(), Some("My Header"));
321 assert_eq!(s.footer.as_deref(), Some("Page Footer"));
322 assert!(s.print_date);
323 assert!(s.print_page_numbers);
324 }
325
326 #[test]
327 fn test_prnpr_xml() {
328 let s = PrintSettings::new();
329 let xml = s.to_prnpr_xml();
330 assert!(xml.contains("<p:prnPr"));
331 assert!(xml.contains(r#"prnWhat="slides""#));
332 assert!(xml.contains(r#"clrMode="clr""#));
333 }
334
335 #[test]
336 fn test_prnpr_xml_handouts() {
337 let s = PrintSettings::new()
338 .print_what(PrintWhat::Handouts)
339 .color_mode(PrintColorMode::BlackAndWhite)
340 .frame_slides(true);
341 let xml = s.to_prnpr_xml();
342 assert!(xml.contains(r#"prnWhat="handouts""#));
343 assert!(xml.contains(r#"clrMode="bw""#));
344 assert!(xml.contains("frameSlides=\"1\""));
345 }
346
347 #[test]
348 fn test_handout_master_xml_basic() {
349 let s = PrintSettings::new().header("Title");
350 let xml = s.to_handout_master_xml();
351 assert!(xml.contains("<p:handoutMaster"));
352 assert!(xml.contains("</p:handoutMaster>"));
353 assert!(xml.contains("<p:bg>"));
354 assert!(xml.contains("<a:spLocks noGrp=\"1\"/>"));
355 assert!(xml.contains("<p:spTree>"));
356 }
357
358 #[test]
359 fn test_handout_master_xml_with_header_footer() {
360 let s = PrintSettings::new()
361 .header("Report")
362 .footer("Confidential")
363 .print_date(true)
364 .print_page_numbers(true);
365 let xml = s.to_handout_master_xml();
366 assert!(xml.contains("Report"));
367 assert!(xml.contains("Confidential"));
368 assert!(xml.contains(r#"type="hdr""#));
369 assert!(xml.contains(r#"type="ftr""#));
370 assert!(xml.contains(r#"type="dt""#));
371 assert!(xml.contains(r#"type="sldNum""#));
372 }
373
374 #[test]
375 fn test_prnpr_xml_wellformed() {
376 let s = PrintSettings::new()
378 .print_what(PrintWhat::Handouts)
379 .color_mode(PrintColorMode::Grayscale)
380 .frame_slides(true)
381 .include_hidden_slides(true)
382 .scale_to_fit(true);
383 let xml = s.to_prnpr_xml();
384 assert!(xml.contains("frameSlides=\"1\""));
386 assert!(xml.contains("hiddenSlides=\"1\""));
387 assert!(xml.contains("scaleToFitPaper=\"1\""));
388 assert!(xml.ends_with("/>"));
390 let wrapped = format!("<root {}/>", &xml[9..xml.len()-2]);
392 assert!(!wrapped.contains("=\"1 "), "attribute value missing closing quote");
393 }
394
395 #[test]
396 fn test_handout_master_xml_escaping() {
397 let s = PrintSettings::new().header("Q&A <Report>");
398 let xml = s.to_handout_master_xml();
399 assert!(xml.contains("Q&A <Report>"));
400 }
401}