Skip to main content

ppt_rs/
prelude.rs

1//! Prelude module for easy imports
2//!
3//! This module provides a simplified API for common use cases.
4//!
5//! # Quick Start
6//!
7//! ```rust,no_run
8//! use ppt_rs::prelude::*;
9//! use ppt_rs::pptx;
10//!
11//! // Create a simple presentation
12//! let pptx_data = pptx!("My Presentation")
13//!     .slide("Welcome", &["Point 1", "Point 2"])
14//!     .slide("Details", &["More info"])
15//!     .build()
16//!     .unwrap();
17//!
18//! std::fs::write("output.pptx", pptx_data).unwrap();
19//! ```
20
21// Re-export commonly used types
22pub use crate::generator::{
23    SlideContent, SlideLayout,
24    Shape, ShapeType, ShapeFill, ShapeLine,
25    Image,
26    Connector, ConnectorType, ArrowType,
27    create_pptx, create_pptx_with_content,
28    BulletStyle, BulletPoint,
29    TextFormat, FormattedText,
30};
31
32pub use crate::generator::shapes::{
33    GradientFill, GradientDirection, GradientStop,
34};
35
36pub use crate::elements::{Color, RgbColor, Position, Size};
37pub use crate::core::{Dimension, FlexPosition, FlexSize};
38pub use crate::exc::Result;
39
40/// Font size module with common presets (in points)
41/// 
42/// These values can be used directly with `content_size()` and `title_size()`:
43/// ```
44/// use ppt_rs::prelude::{SlideContent, font_sizes};
45/// 
46/// let slide = SlideContent::new("Title")
47///     .title_size(font_sizes::TITLE)
48///     .content_size(font_sizes::BODY);
49/// 
50/// assert_eq!(font_sizes::TITLE, 44);
51/// assert_eq!(font_sizes::BODY, 18);
52/// ```
53pub mod font_sizes {
54    /// Title font size (44pt)
55    pub const TITLE: u32 = 44;
56    /// Subtitle font size (32pt)
57    pub const SUBTITLE: u32 = 32;
58    /// Heading font size (28pt)
59    pub const HEADING: u32 = 28;
60    /// Body font size (18pt)
61    pub const BODY: u32 = 18;
62    /// Small font size (14pt)
63    pub const SMALL: u32 = 14;
64    /// Caption font size (12pt)
65    pub const CAPTION: u32 = 12;
66    /// Code font size (14pt)
67    pub const CODE: u32 = 14;
68    /// Large font size (36pt)
69    pub const LARGE: u32 = 36;
70    /// Extra large font size (48pt)
71    pub const XLARGE: u32 = 48;
72    
73    /// Convert points to OOXML size units (hundredths of a point)
74    pub fn to_emu(pt: u32) -> u32 {
75        pt * 100
76    }
77}
78
79/// Quick presentation builder macro
80#[macro_export]
81macro_rules! pptx {
82    ($title:expr) => {
83        $crate::prelude::QuickPptx::new($title)
84    };
85}
86
87/// Quick shape creation
88#[macro_export]
89macro_rules! shape {
90    // Rectangle with position and size (in inches)
91    (rect $x:expr, $y:expr, $w:expr, $h:expr) => {
92        $crate::prelude::Shape::new(
93            $crate::prelude::ShapeType::Rectangle,
94            $crate::prelude::inches($x),
95            $crate::prelude::inches($y),
96            $crate::prelude::inches($w),
97            $crate::prelude::inches($h),
98        )
99    };
100    // Circle with position and size (in inches)
101    (circle $x:expr, $y:expr, $size:expr) => {
102        $crate::prelude::Shape::new(
103            $crate::prelude::ShapeType::Circle,
104            $crate::prelude::inches($x),
105            $crate::prelude::inches($y),
106            $crate::prelude::inches($size),
107            $crate::prelude::inches($size),
108        )
109    };
110}
111
112/// Convert inches to EMU (English Metric Units)
113pub fn inches(val: f64) -> u32 {
114    (val * 914400.0) as u32
115}
116
117/// Convert centimeters to EMU
118pub fn cm(val: f64) -> u32 {
119    (val * 360000.0) as u32
120}
121
122/// Convert points to EMU
123pub fn pt(val: f64) -> u32 {
124    (val * 12700.0) as u32
125}
126
127/// Quick presentation builder for simple use cases
128pub struct QuickPptx {
129    title: String,
130    slides: Vec<SlideContent>,
131}
132
133impl QuickPptx {
134    /// Create a new presentation with a title
135    pub fn new(title: &str) -> Self {
136        QuickPptx {
137            title: title.to_string(),
138            slides: Vec::new(),
139        }
140    }
141    
142    /// Add a slide with title and bullet points
143    pub fn slide(mut self, title: &str, bullets: &[&str]) -> Self {
144        let mut slide = SlideContent::new(title);
145        for bullet in bullets {
146            slide = slide.add_bullet(*bullet);
147        }
148        self.slides.push(slide);
149        self
150    }
151    
152    /// Add a slide with just a title
153    pub fn title_slide(mut self, title: &str) -> Self {
154        self.slides.push(SlideContent::new(title));
155        self
156    }
157    
158    /// Add a slide with title and custom content
159    pub fn content_slide(mut self, slide: SlideContent) -> Self {
160        self.slides.push(slide);
161        self
162    }
163    
164    /// Add a slide with shapes
165    pub fn shapes_slide(mut self, title: &str, shapes: Vec<Shape>) -> Self {
166        let slide = SlideContent::new(title).with_shapes(shapes);
167        self.slides.push(slide);
168        self
169    }
170    
171    /// Build the presentation and return the PPTX data
172    pub fn build(self) -> std::result::Result<Vec<u8>, Box<dyn std::error::Error>> {
173        if self.slides.is_empty() {
174            // Create at least one slide
175            create_pptx(&self.title, 1)
176        } else {
177            create_pptx_with_content(&self.title, self.slides)
178        }
179    }
180    
181    /// Build and save to a file
182    pub fn save(self, path: &str) -> std::result::Result<(), Box<dyn std::error::Error>> {
183        let data = self.build()?;
184        std::fs::write(path, data)?;
185        Ok(())
186    }
187}
188
189
190/// Quick shape builders
191pub mod shapes {
192    use super::*;
193    
194    /// Create a rectangle
195    pub fn rect(x: f64, y: f64, width: f64, height: f64) -> Shape {
196        Shape::new(ShapeType::Rectangle, inches(x), inches(y), inches(width), inches(height))
197    }
198    
199    /// Create a rectangle at EMU coordinates
200    pub fn rect_emu(x: u32, y: u32, width: u32, height: u32) -> Shape {
201        Shape::new(ShapeType::Rectangle, x, y, width, height)
202    }
203    
204    /// Create a circle
205    pub fn circle(x: f64, y: f64, diameter: f64) -> Shape {
206        Shape::new(ShapeType::Circle, inches(x), inches(y), inches(diameter), inches(diameter))
207    }
208    
209    /// Create a circle at EMU coordinates
210    pub fn circle_emu(x: u32, y: u32, diameter: u32) -> Shape {
211        Shape::new(ShapeType::Circle, x, y, diameter, diameter)
212    }
213    
214    /// Create a rounded rectangle
215    pub fn rounded_rect(x: f64, y: f64, width: f64, height: f64) -> Shape {
216        Shape::new(ShapeType::RoundedRectangle, inches(x), inches(y), inches(width), inches(height))
217    }
218    
219    /// Create a text box (rectangle with text)
220    pub fn text_box(x: f64, y: f64, width: f64, height: f64, text: &str) -> Shape {
221        Shape::new(ShapeType::Rectangle, inches(x), inches(y), inches(width), inches(height))
222            .with_text(text)
223    }
224    
225    /// Create a colored shape
226    pub fn colored(shape: Shape, fill: &str, line: Option<&str>) -> Shape {
227        let mut s = shape.with_fill(ShapeFill::new(fill));
228        if let Some(l) = line {
229            s = s.with_line(ShapeLine::new(l, 12700));
230        }
231        s
232    }
233    
234    /// Create a gradient shape
235    pub fn gradient(shape: Shape, start: &str, end: &str, direction: GradientDirection) -> Shape {
236        shape.with_gradient(GradientFill::linear(start, end, direction))
237    }
238    
239    /// Create an arrow shape pointing right
240    pub fn arrow_right(x: f64, y: f64, width: f64, height: f64) -> Shape {
241        Shape::new(ShapeType::RightArrow, inches(x), inches(y), inches(width), inches(height))
242    }
243    
244    /// Create an arrow shape pointing left
245    pub fn arrow_left(x: f64, y: f64, width: f64, height: f64) -> Shape {
246        Shape::new(ShapeType::LeftArrow, inches(x), inches(y), inches(width), inches(height))
247    }
248    
249    /// Create an arrow shape pointing up
250    pub fn arrow_up(x: f64, y: f64, width: f64, height: f64) -> Shape {
251        Shape::new(ShapeType::UpArrow, inches(x), inches(y), inches(width), inches(height))
252    }
253    
254    /// Create an arrow shape pointing down
255    pub fn arrow_down(x: f64, y: f64, width: f64, height: f64) -> Shape {
256        Shape::new(ShapeType::DownArrow, inches(x), inches(y), inches(width), inches(height))
257    }
258    
259    /// Create a diamond shape
260    pub fn diamond(x: f64, y: f64, size: f64) -> Shape {
261        Shape::new(ShapeType::Diamond, inches(x), inches(y), inches(size), inches(size))
262    }
263    
264    /// Create a triangle shape
265    pub fn triangle(x: f64, y: f64, width: f64, height: f64) -> Shape {
266        Shape::new(ShapeType::Triangle, inches(x), inches(y), inches(width), inches(height))
267    }
268    
269    /// Create a star shape (5-pointed)
270    pub fn star(x: f64, y: f64, size: f64) -> Shape {
271        Shape::new(ShapeType::Star5, inches(x), inches(y), inches(size), inches(size))
272    }
273    
274    /// Create a heart shape
275    pub fn heart(x: f64, y: f64, size: f64) -> Shape {
276        Shape::new(ShapeType::Heart, inches(x), inches(y), inches(size), inches(size))
277    }
278    
279    /// Create a cloud shape
280    pub fn cloud(x: f64, y: f64, width: f64, height: f64) -> Shape {
281        Shape::new(ShapeType::Cloud, inches(x), inches(y), inches(width), inches(height))
282    }
283    
284    /// Create a callout shape with text
285    pub fn callout(x: f64, y: f64, width: f64, height: f64, text: &str) -> Shape {
286        Shape::new(ShapeType::WedgeRectCallout, inches(x), inches(y), inches(width), inches(height))
287            .with_text(text)
288    }
289    
290    /// Create a badge (colored rounded rectangle with text)
291    pub fn badge(x: f64, y: f64, text: &str, fill_color: &str) -> Shape {
292        Shape::new(ShapeType::RoundedRectangle, inches(x), inches(y), inches(1.5), inches(0.4))
293            .with_fill(ShapeFill::new(fill_color))
294            .with_text(text)
295    }
296    
297    /// Create a process box (flowchart)
298    pub fn process(x: f64, y: f64, width: f64, height: f64, text: &str) -> Shape {
299        Shape::new(ShapeType::FlowChartProcess, inches(x), inches(y), inches(width), inches(height))
300            .with_text(text)
301    }
302    
303    /// Create a decision diamond (flowchart)
304    pub fn decision(x: f64, y: f64, size: f64, text: &str) -> Shape {
305        Shape::new(ShapeType::FlowChartDecision, inches(x), inches(y), inches(size), inches(size))
306            .with_text(text)
307    }
308    
309    /// Create a document shape (flowchart)
310    pub fn document(x: f64, y: f64, width: f64, height: f64, text: &str) -> Shape {
311        Shape::new(ShapeType::FlowChartDocument, inches(x), inches(y), inches(width), inches(height))
312            .with_text(text)
313    }
314    
315    /// Create a data shape (parallelogram, flowchart)
316    pub fn data(x: f64, y: f64, width: f64, height: f64, text: &str) -> Shape {
317        Shape::new(ShapeType::FlowChartData, inches(x), inches(y), inches(width), inches(height))
318            .with_text(text)
319    }
320    
321    /// Create a terminator shape (flowchart)
322    pub fn terminator(x: f64, y: f64, width: f64, height: f64, text: &str) -> Shape {
323        Shape::new(ShapeType::FlowChartTerminator, inches(x), inches(y), inches(width), inches(height))
324            .with_text(text)
325    }
326
327    /// Create any shape using flexible Dimension units.
328    ///
329    /// ```
330    /// use ppt_rs::prelude::*;
331    ///
332    /// // 10% from left, 20% from top, 80% wide, 2 inches tall
333    /// let shape = shapes::dim(
334    ///     ShapeType::Rectangle,
335    ///     Dimension::Ratio(0.1), Dimension::Ratio(0.2),
336    ///     Dimension::Ratio(0.8), Dimension::Inches(2.0),
337    /// );
338    /// ```
339    pub fn dim(shape_type: ShapeType, x: Dimension, y: Dimension, width: Dimension, height: Dimension) -> Shape {
340        Shape::from_dimensions(shape_type, x, y, width, height)
341    }
342
343    /// Create a rectangle using ratio (0.0–1.0) of slide dimensions.
344    ///
345    /// ```
346    /// use ppt_rs::prelude::*;
347    ///
348    /// // Centered rectangle: 10% margin on each side, 20% from top, 30% tall
349    /// let shape = shapes::rect_ratio(0.1, 0.2, 0.8, 0.3);
350    /// ```
351    pub fn rect_ratio(x: f64, y: f64, width: f64, height: f64) -> Shape {
352        Shape::from_dimensions(
353            ShapeType::Rectangle,
354            Dimension::Ratio(x), Dimension::Ratio(y),
355            Dimension::Ratio(width), Dimension::Ratio(height),
356        )
357    }
358
359    /// Create a text box using ratio (0.0–1.0) of slide dimensions.
360    pub fn text_box_ratio(x: f64, y: f64, width: f64, height: f64, text: &str) -> Shape {
361        rect_ratio(x, y, width, height).with_text(text)
362    }
363}
364
365/// Color constants for convenience
366pub mod colors {
367    pub const RED: &str = "FF0000";
368    pub const GREEN: &str = "00FF00";
369    pub const BLUE: &str = "0000FF";
370    pub const WHITE: &str = "FFFFFF";
371    pub const BLACK: &str = "000000";
372    pub const GRAY: &str = "808080";
373    pub const LIGHT_GRAY: &str = "D3D3D3";
374    pub const DARK_GRAY: &str = "404040";
375    pub const YELLOW: &str = "FFFF00";
376    pub const ORANGE: &str = "FFA500";
377    pub const PURPLE: &str = "800080";
378    pub const CYAN: &str = "00FFFF";
379    pub const MAGENTA: &str = "FF00FF";
380    pub const NAVY: &str = "000080";
381    pub const TEAL: &str = "008080";
382    pub const OLIVE: &str = "808000";
383    
384    // Corporate colors
385    pub const CORPORATE_BLUE: &str = "1565C0";
386    pub const CORPORATE_GREEN: &str = "2E7D32";
387    pub const CORPORATE_RED: &str = "C62828";
388    pub const CORPORATE_ORANGE: &str = "EF6C00";
389    
390    // Material Design colors
391    pub const MATERIAL_RED: &str = "F44336";
392    pub const MATERIAL_PINK: &str = "E91E63";
393    pub const MATERIAL_PURPLE: &str = "9C27B0";
394    pub const MATERIAL_INDIGO: &str = "3F51B5";
395    pub const MATERIAL_BLUE: &str = "2196F3";
396    pub const MATERIAL_CYAN: &str = "00BCD4";
397    pub const MATERIAL_TEAL: &str = "009688";
398    pub const MATERIAL_GREEN: &str = "4CAF50";
399    pub const MATERIAL_LIME: &str = "CDDC39";
400    pub const MATERIAL_AMBER: &str = "FFC107";
401    pub const MATERIAL_ORANGE: &str = "FF9800";
402    pub const MATERIAL_BROWN: &str = "795548";
403    pub const MATERIAL_GRAY: &str = "9E9E9E";
404    
405    // IBM Carbon Design colors
406    pub const CARBON_BLUE_60: &str = "0043CE";
407    pub const CARBON_BLUE_40: &str = "4589FF";
408    pub const CARBON_GRAY_100: &str = "161616";
409    pub const CARBON_GRAY_80: &str = "393939";
410    pub const CARBON_GRAY_20: &str = "E0E0E0";
411    pub const CARBON_GREEN_50: &str = "24A148";
412    pub const CARBON_RED_60: &str = "DA1E28";
413    pub const CARBON_PURPLE_60: &str = "8A3FFC";
414}
415
416/// Theme presets for presentations
417pub mod themes {
418    /// Theme definition with color palette
419    #[derive(Debug, Clone)]
420    pub struct Theme {
421        pub name: &'static str,
422        pub primary: &'static str,
423        pub secondary: &'static str,
424        pub accent: &'static str,
425        pub background: &'static str,
426        pub text: &'static str,
427        pub light: &'static str,
428        pub dark: &'static str,
429    }
430
431    /// Corporate blue theme - Professional and trustworthy
432    pub const CORPORATE: Theme = Theme {
433        name: "Corporate",
434        primary: "1565C0",
435        secondary: "1976D2",
436        accent: "FF6F00",
437        background: "FFFFFF",
438        text: "212121",
439        light: "E3F2FD",
440        dark: "0D47A1",
441    };
442
443    /// Modern minimalist theme - Clean and simple
444    pub const MODERN: Theme = Theme {
445        name: "Modern",
446        primary: "212121",
447        secondary: "757575",
448        accent: "00BCD4",
449        background: "FAFAFA",
450        text: "212121",
451        light: "F5F5F5",
452        dark: "424242",
453    };
454
455    /// Vibrant creative theme - Bold and colorful
456    pub const VIBRANT: Theme = Theme {
457        name: "Vibrant",
458        primary: "E91E63",
459        secondary: "9C27B0",
460        accent: "FF9800",
461        background: "FFFFFF",
462        text: "212121",
463        light: "FCE4EC",
464        dark: "880E4F",
465    };
466
467    /// Dark mode theme - Easy on the eyes
468    pub const DARK: Theme = Theme {
469        name: "Dark",
470        primary: "BB86FC",
471        secondary: "03DAC6",
472        accent: "CF6679",
473        background: "121212",
474        text: "FFFFFF",
475        light: "1E1E1E",
476        dark: "000000",
477    };
478
479    /// Nature green theme - Fresh and organic
480    pub const NATURE: Theme = Theme {
481        name: "Nature",
482        primary: "2E7D32",
483        secondary: "4CAF50",
484        accent: "8BC34A",
485        background: "FFFFFF",
486        text: "1B5E20",
487        light: "E8F5E9",
488        dark: "1B5E20",
489    };
490
491    /// Tech blue theme - Modern technology feel
492    pub const TECH: Theme = Theme {
493        name: "Tech",
494        primary: "0D47A1",
495        secondary: "1976D2",
496        accent: "00E676",
497        background: "FAFAFA",
498        text: "263238",
499        light: "E3F2FD",
500        dark: "01579B",
501    };
502
503    /// Carbon Design theme - IBM's design system
504    pub const CARBON: Theme = Theme {
505        name: "Carbon",
506        primary: "0043CE",
507        secondary: "4589FF",
508        accent: "24A148",
509        background: "FFFFFF",
510        text: "161616",
511        light: "E0E0E0",
512        dark: "161616",
513    };
514
515    /// Get all available themes
516    pub fn all() -> Vec<Theme> {
517        vec![CORPORATE, MODERN, VIBRANT, DARK, NATURE, TECH, CARBON]
518    }
519}
520
521/// Layout helpers for positioning shapes
522pub mod layouts {
523    /// Slide dimensions in EMU
524    pub const SLIDE_WIDTH: u32 = 9144000;   // 10 inches
525    pub const SLIDE_HEIGHT: u32 = 6858000;  // 7.5 inches
526    
527    /// Common margins
528    pub const MARGIN: u32 = 457200;          // 0.5 inch
529    pub const MARGIN_SMALL: u32 = 228600;    // 0.25 inch
530    pub const MARGIN_LARGE: u32 = 914400;    // 1 inch
531
532    /// Center a shape on the slide (horizontal)
533    pub fn center_x(shape_width: u32) -> u32 {
534        (SLIDE_WIDTH - shape_width) / 2
535    }
536
537    /// Center a shape on the slide (vertical)
538    pub fn center_y(shape_height: u32) -> u32 {
539        (SLIDE_HEIGHT - shape_height) / 2
540    }
541
542    /// Get position for centering a shape both horizontally and vertically
543    pub fn center(shape_width: u32, shape_height: u32) -> (u32, u32) {
544        (center_x(shape_width), center_y(shape_height))
545    }
546
547    /// Calculate grid positions for arranging shapes
548    /// Returns Vec of (x, y) positions
549    pub fn grid(rows: usize, cols: usize, cell_width: u32, cell_height: u32) -> Vec<(u32, u32)> {
550        let mut positions = Vec::new();
551        let total_width = cell_width * cols as u32;
552        let total_height = cell_height * rows as u32;
553        let start_x = center_x(total_width);
554        let start_y = center_y(total_height);
555        
556        for row in 0..rows {
557            for col in 0..cols {
558                let x = start_x + (col as u32 * cell_width);
559                let y = start_y + (row as u32 * cell_height);
560                positions.push((x, y));
561            }
562        }
563        positions
564    }
565
566    /// Calculate positions for a horizontal stack of shapes
567    pub fn stack_horizontal(count: usize, shape_width: u32, spacing: u32, y: u32) -> Vec<(u32, u32)> {
568        let total_width = (shape_width * count as u32) + (spacing * (count - 1) as u32);
569        let start_x = center_x(total_width);
570        
571        (0..count)
572            .map(|i| (start_x + (i as u32 * (shape_width + spacing)), y))
573            .collect()
574    }
575
576    /// Calculate positions for a vertical stack of shapes
577    pub fn stack_vertical(count: usize, shape_height: u32, spacing: u32, x: u32) -> Vec<(u32, u32)> {
578        let total_height = (shape_height * count as u32) + (spacing * (count - 1) as u32);
579        let start_y = center_y(total_height);
580        
581        (0..count)
582            .map(|i| (x, start_y + (i as u32 * (shape_height + spacing))))
583            .collect()
584    }
585
586    /// Calculate positions to evenly distribute shapes across slide width
587    pub fn distribute_horizontal(count: usize, shape_width: u32, y: u32) -> Vec<(u32, u32)> {
588        if count == 0 {
589            return vec![];
590        }
591        if count == 1 {
592            return vec![(center_x(shape_width), y)];
593        }
594        
595        let usable_width = SLIDE_WIDTH - (2 * MARGIN);
596        let spacing = (usable_width - (shape_width * count as u32)) / (count as u32 - 1);
597        
598        (0..count)
599            .map(|i| (MARGIN + (i as u32 * (shape_width + spacing)), y))
600            .collect()
601    }
602}
603
604#[cfg(test)]
605mod tests {
606    use super::*;
607    
608    #[test]
609    fn test_quick_pptx() {
610        let result = QuickPptx::new("Test")
611            .slide("Slide 1", &["Point 1", "Point 2"])
612            .build();
613        assert!(result.is_ok());
614    }
615    
616    #[test]
617    fn test_inches_conversion() {
618        assert_eq!(inches(1.0), 914400);
619        assert_eq!(cm(2.54), 914400); // 1 inch = 2.54 cm
620    }
621    
622    #[test]
623    fn test_shape_builders() {
624        let rect = shapes::rect(1.0, 1.0, 2.0, 1.0);
625        assert_eq!(rect.width, inches(2.0));
626        
627        let circle = shapes::circle(1.0, 1.0, 1.0);
628        assert_eq!(circle.width, circle.height);
629    }
630    
631    #[test]
632    fn test_arrow_shapes() {
633        let arrow = shapes::arrow_right(1.0, 1.0, 2.0, 1.0);
634        assert_eq!(arrow.width, inches(2.0));
635        
636        let up = shapes::arrow_up(1.0, 1.0, 1.0, 2.0);
637        assert_eq!(up.height, inches(2.0));
638    }
639    
640    #[test]
641    fn test_flowchart_shapes() {
642        let process = shapes::process(1.0, 1.0, 2.0, 1.0, "Process");
643        assert!(process.text.is_some());
644        
645        let decision = shapes::decision(1.0, 1.0, 1.5, "Yes/No");
646        assert!(decision.text.is_some());
647    }
648    
649    #[test]
650    fn test_badge_shape() {
651        let badge = shapes::badge(1.0, 1.0, "NEW", colors::MATERIAL_GREEN);
652        assert!(badge.text.is_some());
653        assert!(badge.fill.is_some());
654    }
655    
656    #[test]
657    fn test_themes() {
658        let all_themes = crate::prelude::themes::all();
659        assert_eq!(all_themes.len(), 7);
660        
661        assert_eq!(crate::prelude::themes::CORPORATE.name, "Corporate");
662        assert_eq!(crate::prelude::themes::DARK.background, "121212");
663    }
664    
665    #[test]
666    fn test_layouts_center() {
667        let (x, y) = crate::prelude::layouts::center(1000000, 500000);
668        assert!(x > 0);
669        assert!(y > 0);
670        
671        // Should be centered
672        assert_eq!(x, (crate::prelude::layouts::SLIDE_WIDTH - 1000000) / 2);
673        assert_eq!(y, (crate::prelude::layouts::SLIDE_HEIGHT - 500000) / 2);
674    }
675    
676    #[test]
677    fn test_layouts_grid() {
678        let positions = crate::prelude::layouts::grid(2, 3, 1000000, 800000);
679        assert_eq!(positions.len(), 6);
680    }
681    
682    #[test]
683    fn test_layouts_stack_horizontal() {
684        let positions = crate::prelude::layouts::stack_horizontal(4, 500000, 100000, 2000000);
685        assert_eq!(positions.len(), 4);
686        
687        // Check that positions are evenly spaced
688        for i in 1..positions.len() {
689            let diff = positions[i].0 - positions[i-1].0;
690            assert_eq!(diff, 600000); // shape_width + spacing
691        }
692    }
693    
694    #[test]
695    fn test_layouts_distribute_horizontal() {
696        let positions = crate::prelude::layouts::distribute_horizontal(3, 500000, 2000000);
697        assert_eq!(positions.len(), 3);
698    }
699    
700    #[test]
701    fn test_material_colors() {
702        assert_eq!(colors::MATERIAL_RED, "F44336");
703        assert_eq!(colors::MATERIAL_BLUE, "2196F3");
704        assert_eq!(colors::MATERIAL_GREEN, "4CAF50");
705    }
706    
707    #[test]
708    fn test_carbon_colors() {
709        assert_eq!(colors::CARBON_BLUE_60, "0043CE");
710        assert_eq!(colors::CARBON_GRAY_100, "161616");
711    }
712}