Skip to main content

ppt_rs/elements/
color.rs

1//! Color types for PPTX elements
2//!
3//! Provides unified color handling for all PPTX elements.
4
5use crate::core::ToXml;
6
7/// RGB color (6-digit hex)
8#[derive(Clone, Debug, PartialEq, Eq)]
9pub struct RgbColor {
10    pub r: u8,
11    pub g: u8,
12    pub b: u8,
13}
14
15impl RgbColor {
16    /// Create a new RGB color
17    pub fn new(r: u8, g: u8, b: u8) -> Self {
18        Self { r, g, b }
19    }
20
21    /// Parse from hex string (e.g., "FF0000" or "#FF0000")
22    pub fn from_hex(hex: &str) -> Option<Self> {
23        let hex = hex.trim_start_matches('#');
24        if hex.len() != 6 {
25            return None;
26        }
27        let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
28        let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
29        let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
30        Some(Self { r, g, b })
31    }
32
33    /// Convert to hex string (uppercase, no #)
34    pub fn to_hex(&self) -> String {
35        format!("{:02X}{:02X}{:02X}", self.r, self.g, self.b)
36    }
37
38    /// Common colors
39    pub fn black() -> Self { Self::new(0, 0, 0) }
40    pub fn white() -> Self { Self::new(255, 255, 255) }
41    pub fn red() -> Self { Self::new(255, 0, 0) }
42    pub fn green() -> Self { Self::new(0, 255, 0) }
43    pub fn blue() -> Self { Self::new(0, 0, 255) }
44}
45
46impl ToXml for RgbColor {
47    fn to_xml(&self) -> String {
48        format!(r#"<a:srgbClr val="{}"/>"#, self.to_hex())
49    }
50}
51
52impl Default for RgbColor {
53    fn default() -> Self {
54        Self::black()
55    }
56}
57
58/// Scheme color (theme-based)
59#[derive(Clone, Debug, PartialEq, Eq)]
60pub enum SchemeColor {
61    Accent1,
62    Accent2,
63    Accent3,
64    Accent4,
65    Accent5,
66    Accent6,
67    Dark1,
68    Dark2,
69    Light1,
70    Light2,
71    Hyperlink,
72    FollowedHyperlink,
73    Background1,
74    Background2,
75    Text1,
76    Text2,
77}
78
79impl SchemeColor {
80    pub fn as_str(&self) -> &'static str {
81        match self {
82            SchemeColor::Accent1 => "accent1",
83            SchemeColor::Accent2 => "accent2",
84            SchemeColor::Accent3 => "accent3",
85            SchemeColor::Accent4 => "accent4",
86            SchemeColor::Accent5 => "accent5",
87            SchemeColor::Accent6 => "accent6",
88            SchemeColor::Dark1 => "dk1",
89            SchemeColor::Dark2 => "dk2",
90            SchemeColor::Light1 => "lt1",
91            SchemeColor::Light2 => "lt2",
92            SchemeColor::Hyperlink => "hlink",
93            SchemeColor::FollowedHyperlink => "folHlink",
94            SchemeColor::Background1 => "bg1",
95            SchemeColor::Background2 => "bg2",
96            SchemeColor::Text1 => "tx1",
97            SchemeColor::Text2 => "tx2",
98        }
99    }
100}
101
102impl ToXml for SchemeColor {
103    fn to_xml(&self) -> String {
104        format!(r#"<a:schemeClr val="{}"/>"#, self.as_str())
105    }
106}
107
108/// Unified color type
109#[derive(Clone, Debug, PartialEq)]
110pub enum Color {
111    Rgb(RgbColor),
112    Scheme(SchemeColor),
113}
114
115impl Color {
116    /// Create from RGB values
117    pub fn rgb(r: u8, g: u8, b: u8) -> Self {
118        Color::Rgb(RgbColor::new(r, g, b))
119    }
120
121    /// Create from hex string
122    pub fn from_hex(hex: &str) -> Option<Self> {
123        RgbColor::from_hex(hex).map(Color::Rgb)
124    }
125
126    /// Create from scheme color
127    pub fn scheme(color: SchemeColor) -> Self {
128        Color::Scheme(color)
129    }
130}
131
132impl ToXml for Color {
133    fn to_xml(&self) -> String {
134        match self {
135            Color::Rgb(rgb) => rgb.to_xml(),
136            Color::Scheme(scheme) => scheme.to_xml(),
137        }
138    }
139}
140
141impl Default for Color {
142    fn default() -> Self {
143        Color::Rgb(RgbColor::black())
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn test_rgb_from_hex() {
153        let color = RgbColor::from_hex("FF0000").unwrap();
154        assert_eq!(color.r, 255);
155        assert_eq!(color.g, 0);
156        assert_eq!(color.b, 0);
157
158        let color = RgbColor::from_hex("#00FF00").unwrap();
159        assert_eq!(color.to_hex(), "00FF00");
160    }
161
162    #[test]
163    fn test_rgb_to_xml() {
164        let color = RgbColor::new(255, 0, 0);
165        assert_eq!(color.to_xml(), r#"<a:srgbClr val="FF0000"/>"#);
166    }
167
168    #[test]
169    fn test_scheme_color() {
170        let color = SchemeColor::Accent1;
171        assert_eq!(color.to_xml(), r#"<a:schemeClr val="accent1"/>"#);
172    }
173
174    #[test]
175    fn test_color_enum() {
176        let rgb = Color::rgb(255, 0, 0);
177        assert_eq!(rgb.to_xml(), r#"<a:srgbClr val="FF0000"/>"#);
178
179        let scheme = Color::scheme(SchemeColor::Dark1);
180        assert_eq!(scheme.to_xml(), r#"<a:schemeClr val="dk1"/>"#);
181    }
182
183    #[test]
184    fn test_common_colors() {
185        assert_eq!(RgbColor::black().to_hex(), "000000");
186        assert_eq!(RgbColor::white().to_hex(), "FFFFFF");
187        assert_eq!(RgbColor::red().to_hex(), "FF0000");
188    }
189}