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 {
40        Self::new(0, 0, 0)
41    }
42    pub fn white() -> Self {
43        Self::new(255, 255, 255)
44    }
45    pub fn red() -> Self {
46        Self::new(255, 0, 0)
47    }
48    pub fn green() -> Self {
49        Self::new(0, 255, 0)
50    }
51    pub fn blue() -> Self {
52        Self::new(0, 0, 255)
53    }
54}
55
56impl ToXml for RgbColor {
57    fn to_xml(&self) -> String {
58        format!(r#"<a:srgbClr val="{}"/>"#, self.to_hex())
59    }
60}
61
62impl Default for RgbColor {
63    fn default() -> Self {
64        Self::black()
65    }
66}
67
68/// Scheme color (theme-based)
69#[derive(Clone, Debug, PartialEq, Eq)]
70pub enum SchemeColor {
71    Accent1,
72    Accent2,
73    Accent3,
74    Accent4,
75    Accent5,
76    Accent6,
77    Dark1,
78    Dark2,
79    Light1,
80    Light2,
81    Hyperlink,
82    FollowedHyperlink,
83    Background1,
84    Background2,
85    Text1,
86    Text2,
87}
88
89impl SchemeColor {
90    pub fn as_str(&self) -> &'static str {
91        match self {
92            SchemeColor::Accent1 => "accent1",
93            SchemeColor::Accent2 => "accent2",
94            SchemeColor::Accent3 => "accent3",
95            SchemeColor::Accent4 => "accent4",
96            SchemeColor::Accent5 => "accent5",
97            SchemeColor::Accent6 => "accent6",
98            SchemeColor::Dark1 => "dk1",
99            SchemeColor::Dark2 => "dk2",
100            SchemeColor::Light1 => "lt1",
101            SchemeColor::Light2 => "lt2",
102            SchemeColor::Hyperlink => "hlink",
103            SchemeColor::FollowedHyperlink => "folHlink",
104            SchemeColor::Background1 => "bg1",
105            SchemeColor::Background2 => "bg2",
106            SchemeColor::Text1 => "tx1",
107            SchemeColor::Text2 => "tx2",
108        }
109    }
110}
111
112impl ToXml for SchemeColor {
113    fn to_xml(&self) -> String {
114        format!(r#"<a:schemeClr val="{}"/>"#, self.as_str())
115    }
116}
117
118/// Unified color type
119#[derive(Clone, Debug, PartialEq)]
120pub enum Color {
121    Rgb(RgbColor),
122    Scheme(SchemeColor),
123}
124
125impl Color {
126    /// Create from RGB values
127    pub fn rgb(r: u8, g: u8, b: u8) -> Self {
128        Color::Rgb(RgbColor::new(r, g, b))
129    }
130
131    /// Create from hex string
132    pub fn from_hex(hex: &str) -> Option<Self> {
133        RgbColor::from_hex(hex).map(Color::Rgb)
134    }
135
136    /// Create from scheme color
137    pub fn scheme(color: SchemeColor) -> Self {
138        Color::Scheme(color)
139    }
140}
141
142impl ToXml for Color {
143    fn to_xml(&self) -> String {
144        match self {
145            Color::Rgb(rgb) => rgb.to_xml(),
146            Color::Scheme(scheme) => scheme.to_xml(),
147        }
148    }
149}
150
151impl Default for Color {
152    fn default() -> Self {
153        Color::Rgb(RgbColor::black())
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn test_rgb_from_hex() {
163        let color = RgbColor::from_hex("FF0000").unwrap();
164        assert_eq!(color.r, 255);
165        assert_eq!(color.g, 0);
166        assert_eq!(color.b, 0);
167
168        let color = RgbColor::from_hex("#00FF00").unwrap();
169        assert_eq!(color.to_hex(), "00FF00");
170    }
171
172    #[test]
173    fn test_rgb_to_xml() {
174        let color = RgbColor::new(255, 0, 0);
175        assert_eq!(color.to_xml(), r#"<a:srgbClr val="FF0000"/>"#);
176    }
177
178    #[test]
179    fn test_scheme_color() {
180        let color = SchemeColor::Accent1;
181        assert_eq!(color.to_xml(), r#"<a:schemeClr val="accent1"/>"#);
182    }
183
184    #[test]
185    fn test_color_enum() {
186        let rgb = Color::rgb(255, 0, 0);
187        assert_eq!(rgb.to_xml(), r#"<a:srgbClr val="FF0000"/>"#);
188
189        let scheme = Color::scheme(SchemeColor::Dark1);
190        assert_eq!(scheme.to_xml(), r#"<a:schemeClr val="dk1"/>"#);
191    }
192
193    #[test]
194    fn test_common_colors() {
195        assert_eq!(RgbColor::black().to_hex(), "000000");
196        assert_eq!(RgbColor::white().to_hex(), "FFFFFF");
197        assert_eq!(RgbColor::red().to_hex(), "FF0000");
198    }
199}