Skip to main content

ppt_rs/
templates.rs

1//! Template module for common presentation types
2//!
3//! Provides pre-built presentation structures for common use cases.
4//!
5//! # Examples
6//!
7//! ```rust
8//! use ppt_rs::templates::{self, ProposalContent};
9//!
10//! // Create a business proposal
11//! let pptx = templates::business_proposal(
12//!     "Q4 Budget Proposal",
13//!     "Finance Team",
14//!     ProposalContent {
15//!         executive_summary: vec!["Key insight 1", "Key insight 2"],
16//!         problem: vec!["Current challenge"],
17//!         solution: vec!["Our approach"],
18//!         timeline: vec![("Phase 1", "Week 1-2"), ("Phase 2", "Week 3-4")],
19//!         budget: vec![("Item", "$10,000")],
20//!         next_steps: vec!["Action 1", "Action 2"],
21//!     },
22//! ).expect("Failed to create presentation");
23//!
24//! assert!(!pptx.is_empty());
25//! ```
26
27use crate::generator::{SlideContent, SlideLayout, create_pptx_with_content};
28use crate::exc::Result;
29
30/// Content for a business proposal presentation
31#[derive(Debug, Clone)]
32pub struct ProposalContent<'a> {
33    pub executive_summary: Vec<&'a str>,
34    pub problem: Vec<&'a str>,
35    pub solution: Vec<&'a str>,
36    pub timeline: Vec<(&'a str, &'a str)>,
37    pub budget: Vec<(&'a str, &'a str)>,
38    pub next_steps: Vec<&'a str>,
39}
40
41impl<'a> Default for ProposalContent<'a> {
42    fn default() -> Self {
43        Self {
44            executive_summary: vec!["Add executive summary points"],
45            problem: vec!["Define the problem"],
46            solution: vec!["Present your solution"],
47            timeline: vec![("Phase 1", "Description")],
48            budget: vec![("Item", "Amount")],
49            next_steps: vec!["Define next steps"],
50        }
51    }
52}
53
54/// Content for a training material presentation
55#[derive(Debug, Clone)]
56pub struct TrainingContent<'a> {
57    pub objectives: Vec<&'a str>,
58    pub modules: Vec<(&'a str, Vec<&'a str>)>,
59    pub exercises: Vec<&'a str>,
60    pub summary: Vec<&'a str>,
61}
62
63impl<'a> Default for TrainingContent<'a> {
64    fn default() -> Self {
65        Self {
66            objectives: vec!["Learning objective 1"],
67            modules: vec![("Module 1", vec!["Topic 1", "Topic 2"])],
68            exercises: vec!["Practice exercise"],
69            summary: vec!["Key takeaway"],
70        }
71    }
72}
73
74/// Content for a status report presentation
75#[derive(Debug, Clone)]
76pub struct StatusContent<'a> {
77    pub summary: Vec<&'a str>,
78    pub completed: Vec<&'a str>,
79    pub in_progress: Vec<&'a str>,
80    pub blocked: Vec<&'a str>,
81    pub next_week: Vec<&'a str>,
82    pub metrics: Vec<(&'a str, &'a str)>,
83}
84
85impl<'a> Default for StatusContent<'a> {
86    fn default() -> Self {
87        Self {
88            summary: vec!["High-level status"],
89            completed: vec!["Completed item"],
90            in_progress: vec!["In progress item"],
91            blocked: vec!["Blocked item"],
92            next_week: vec!["Planned item"],
93            metrics: vec![("Metric", "Value")],
94        }
95    }
96}
97
98/// Content for a technical documentation presentation
99#[derive(Debug, Clone)]
100pub struct TechnicalContent<'a> {
101    pub overview: Vec<&'a str>,
102    pub architecture: Vec<&'a str>,
103    pub components: Vec<(&'a str, Vec<&'a str>)>,
104    pub api_examples: Vec<(&'a str, &'a str)>,
105    pub best_practices: Vec<&'a str>,
106}
107
108impl<'a> Default for TechnicalContent<'a> {
109    fn default() -> Self {
110        Self {
111            overview: vec!["System overview"],
112            architecture: vec!["Architecture description"],
113            components: vec![("Component", vec!["Feature 1"])],
114            api_examples: vec![("Method", "Description")],
115            best_practices: vec!["Best practice"],
116        }
117    }
118}
119
120/// Create a business proposal presentation
121///
122/// Structure:
123/// 1. Title slide
124/// 2. Agenda
125/// 3. Executive Summary
126/// 4. Problem Statement
127/// 5. Proposed Solution
128/// 6. Timeline
129/// 7. Budget
130/// 8. Next Steps
131/// 9. Questions/Discussion
132pub fn business_proposal<'a>(
133    title: &str,
134    author: &str,
135    content: ProposalContent<'a>,
136) -> Result<Vec<u8>> {
137    let mut slides = Vec::new();
138
139    // Title slide
140    slides.push(
141        SlideContent::new(title)
142            .add_bullet(author)
143            .layout(SlideLayout::CenteredTitle)
144    );
145
146    // Agenda
147    slides.push(
148        SlideContent::new("Agenda")
149            .add_bullet("Executive Summary")
150            .add_bullet("Problem Statement")
151            .add_bullet("Proposed Solution")
152            .add_bullet("Timeline & Budget")
153            .add_bullet("Next Steps")
154    );
155
156    // Executive Summary
157    let mut summary = SlideContent::new("Executive Summary");
158    for point in &content.executive_summary {
159        summary = summary.add_bullet(*point);
160    }
161    slides.push(summary);
162
163    // Problem Statement
164    let mut problem = SlideContent::new("Problem Statement");
165    for point in &content.problem {
166        problem = problem.add_bullet(*point);
167    }
168    slides.push(problem);
169
170    // Solution
171    let mut solution = SlideContent::new("Proposed Solution");
172    for point in &content.solution {
173        solution = solution.add_bullet(*point);
174    }
175    slides.push(solution);
176
177    // Timeline
178    let mut timeline = SlideContent::new("Timeline");
179    for (phase, desc) in &content.timeline {
180        timeline = timeline.add_bullet(&format!("{}: {}", phase, desc));
181    }
182    slides.push(timeline);
183
184    // Budget
185    let mut budget = SlideContent::new("Budget");
186    for (item, amount) in &content.budget {
187        budget = budget.add_bullet(&format!("{}: {}", item, amount));
188    }
189    slides.push(budget);
190
191    // Next Steps
192    let mut next = SlideContent::new("Next Steps");
193    for step in &content.next_steps {
194        next = next.add_bullet(*step);
195    }
196    slides.push(next);
197
198    // Q&A
199    slides.push(
200        SlideContent::new("Questions?")
201            .add_bullet("Discussion and Q&A")
202            .layout(SlideLayout::CenteredTitle)
203    );
204
205    create_pptx_with_content(title, slides).map_err(|e| crate::exc::PptxError::InvalidOperation(e.to_string()))
206}
207
208/// Create a training material presentation
209///
210/// Structure:
211/// 1. Title slide
212/// 2. Learning Objectives
213/// 3. Module slides (one per module)
214/// 4. Exercises
215/// 5. Summary
216/// 6. Q&A
217pub fn training_material<'a>(
218    title: &str,
219    author: &str,
220    content: TrainingContent<'a>,
221) -> Result<Vec<u8>> {
222    let mut slides = Vec::new();
223
224    // Title
225    slides.push(
226        SlideContent::new(title)
227            .add_bullet(author)
228            .layout(SlideLayout::CenteredTitle)
229    );
230
231    // Objectives
232    let mut objectives = SlideContent::new("Learning Objectives");
233    for obj in &content.objectives {
234        objectives = objectives.add_bullet(*obj);
235    }
236    slides.push(objectives);
237
238    // Modules
239    for (module_name, topics) in &content.modules {
240        let mut module = SlideContent::new(*module_name);
241        for topic in topics {
242            module = module.add_bullet(*topic);
243        }
244        slides.push(module);
245    }
246
247    // Exercises
248    let mut exercises = SlideContent::new("Exercises");
249    for ex in &content.exercises {
250        exercises = exercises.add_bullet(*ex);
251    }
252    slides.push(exercises);
253
254    // Summary
255    let mut summary = SlideContent::new("Summary");
256    for point in &content.summary {
257        summary = summary.add_bullet(*point);
258    }
259    slides.push(summary);
260
261    // Q&A
262    slides.push(
263        SlideContent::new("Questions?")
264            .layout(SlideLayout::CenteredTitle)
265    );
266
267    create_pptx_with_content(title, slides).map_err(|e| crate::exc::PptxError::InvalidOperation(e.to_string()))
268}
269
270/// Create a status report presentation
271///
272/// Structure:
273/// 1. Title slide
274/// 2. Executive Summary
275/// 3. Completed
276/// 4. In Progress
277/// 5. Blocked/Risks
278/// 6. Next Week
279/// 7. Metrics
280pub fn status_report<'a>(
281    title: &str,
282    date: &str,
283    content: StatusContent<'a>,
284) -> Result<Vec<u8>> {
285    let mut slides = Vec::new();
286
287    // Title
288    slides.push(
289        SlideContent::new(title)
290            .add_bullet(date)
291            .layout(SlideLayout::CenteredTitle)
292    );
293
294    // Summary
295    let mut summary = SlideContent::new("Executive Summary");
296    for point in &content.summary {
297        summary = summary.add_bullet(*point);
298    }
299    slides.push(summary);
300
301    // Completed
302    let mut completed = SlideContent::new("Completed ✓");
303    for item in &content.completed {
304        completed = completed.add_bullet(*item);
305    }
306    slides.push(completed);
307
308    // In Progress
309    let mut progress = SlideContent::new("In Progress");
310    for item in &content.in_progress {
311        progress = progress.add_bullet(*item);
312    }
313    slides.push(progress);
314
315    // Blocked
316    if !content.blocked.is_empty() {
317        let mut blocked = SlideContent::new("Blocked / Risks");
318        for item in &content.blocked {
319            blocked = blocked.add_bullet(*item);
320        }
321        slides.push(blocked);
322    }
323
324    // Next Week
325    let mut next = SlideContent::new("Next Week");
326    for item in &content.next_week {
327        next = next.add_bullet(*item);
328    }
329    slides.push(next);
330
331    // Metrics
332    if !content.metrics.is_empty() {
333        let mut metrics = SlideContent::new("Key Metrics");
334        for (metric, value) in &content.metrics {
335            metrics = metrics.add_bullet(&format!("{}: {}", metric, value));
336        }
337        slides.push(metrics);
338    }
339
340    create_pptx_with_content(title, slides).map_err(|e| crate::exc::PptxError::InvalidOperation(e.to_string()))
341}
342
343/// Create a technical documentation presentation
344///
345/// Structure:
346/// 1. Title slide
347/// 2. Overview
348/// 3. Architecture
349/// 4. Component slides
350/// 5. API Examples
351/// 6. Best Practices
352pub fn technical_doc<'a>(
353    title: &str,
354    version: &str,
355    content: TechnicalContent<'a>,
356) -> Result<Vec<u8>> {
357    let mut slides = Vec::new();
358
359    // Title
360    slides.push(
361        SlideContent::new(title)
362            .add_bullet(&format!("Version {}", version))
363            .layout(SlideLayout::CenteredTitle)
364    );
365
366    // Overview
367    let mut overview = SlideContent::new("Overview");
368    for point in &content.overview {
369        overview = overview.add_bullet(*point);
370    }
371    slides.push(overview);
372
373    // Architecture
374    let mut arch = SlideContent::new("Architecture");
375    for point in &content.architecture {
376        arch = arch.add_bullet(*point);
377    }
378    slides.push(arch);
379
380    // Components
381    for (name, features) in &content.components {
382        let mut comp = SlideContent::new(*name);
383        for feature in features {
384            comp = comp.add_bullet(*feature);
385        }
386        slides.push(comp);
387    }
388
389    // API Examples
390    if !content.api_examples.is_empty() {
391        let mut api = SlideContent::new("API Reference");
392        for (method, desc) in &content.api_examples {
393            api = api.add_bullet(&format!("{} - {}", method, desc));
394        }
395        slides.push(api);
396    }
397
398    // Best Practices
399    let mut practices = SlideContent::new("Best Practices");
400    for practice in &content.best_practices {
401        practices = practices.add_bullet(*practice);
402    }
403    slides.push(practices);
404
405    create_pptx_with_content(title, slides).map_err(|e| crate::exc::PptxError::InvalidOperation(e.to_string()))
406}
407
408/// Create a simple presentation with just title and bullet slides
409pub fn simple(title: &str, slides: &[(&str, &[&str])]) -> Result<Vec<u8>> {
410    let slide_contents: Vec<SlideContent> = slides.iter().map(|(slide_title, bullets)| {
411        let mut slide = SlideContent::new(*slide_title);
412        for bullet in *bullets {
413            slide = slide.add_bullet(*bullet);
414        }
415        slide
416    }).collect();
417
418    create_pptx_with_content(title, slide_contents).map_err(|e| crate::exc::PptxError::InvalidOperation(e.to_string()))
419}
420
421#[cfg(test)]
422mod tests {
423    use super::*;
424
425    #[test]
426    fn test_business_proposal() {
427        let content = ProposalContent::default();
428        let result = business_proposal("Test Proposal", "Author", content);
429        assert!(result.is_ok());
430        let data = result.unwrap();
431        assert!(data.len() > 1000);
432    }
433
434    #[test]
435    fn test_training_material() {
436        let content = TrainingContent::default();
437        let result = training_material("Test Training", "Trainer", content);
438        assert!(result.is_ok());
439    }
440
441    #[test]
442    fn test_status_report() {
443        let content = StatusContent::default();
444        let result = status_report("Weekly Status", "2025-01-01", content);
445        assert!(result.is_ok());
446    }
447
448    #[test]
449    fn test_technical_doc() {
450        let content = TechnicalContent::default();
451        let result = technical_doc("API Documentation", "1.0.0", content);
452        assert!(result.is_ok());
453    }
454
455    #[test]
456    fn test_simple() {
457        let slides = [
458            ("Introduction", &["Point 1", "Point 2"][..]),
459            ("Conclusion", &["Summary"][..]),
460        ];
461        let result = simple("Simple Presentation", &slides);
462        assert!(result.is_ok());
463    }
464
465    #[test]
466    fn test_custom_proposal_content() {
467        let content = ProposalContent {
468            executive_summary: vec!["We need more budget", "Market opportunity exists"],
469            problem: vec!["Current system is outdated", "Losing customers"],
470            solution: vec!["New platform", "Modern technology"],
471            timeline: vec![("Q1", "Design"), ("Q2", "Build"), ("Q3", "Launch")],
472            budget: vec![("Development", "$100,000"), ("Marketing", "$50,000")],
473            next_steps: vec!["Approve budget", "Hire team", "Start development"],
474        };
475        let result = business_proposal("Q1 Budget Proposal", "Finance", content);
476        assert!(result.is_ok());
477    }
478}
479