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