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