Skip to main content

umya_spreadsheet/structs/
color.rs

1// color
2use std::{
3    borrow::Cow,
4    io::Cursor,
5};
6
7use phf::phf_map;
8use quick_xml::{
9    Reader,
10    Writer,
11    events::{
12        BytesStart,
13        Event,
14    },
15};
16use rgb::Argb;
17
18use crate::{
19    helper::color::calc_tint,
20    reader::driver::get_attribute_value,
21    structs::drawing::Theme,
22    writer::driver::write_start_tag,
23};
24
25pub type ARGB8 = Argb<u8>;
26
27macro_rules! argb {
28    ($a:expr, $r:expr, $g:expr, $b:expr) => {
29        ARGB8 {
30            a: $a,
31            r: $r,
32            g: $g,
33            b: $b,
34        }
35    };
36}
37
38static INDEX_TO_COLOR: phf::Map<u32, ARGB8> = phf_map! {
39    0u32 => argb!(0xFF, 0x00, 0x00, 0x00), // System Colour #1 - Black
40    1u32 => argb!(0xFF, 0xFF, 0xFF, 0xFF), // System Colour #2 - White
41    2u32 => argb!(0xFF, 0xFF, 0x00, 0x00), // System Colour #3 - Red
42    3u32 => argb!(0xFF, 0x00, 0xFF, 0x00), // System Colour #4 - Green
43    4u32 => argb!(0xFF, 0x00, 0x00, 0xFF), // System Colour #5 - Blue
44    5u32 => argb!(0xFF, 0xFF, 0xFF, 0x00), // System Colour #6 - Yellow
45    6u32 => argb!(0xFF, 0xFF, 0x00, 0xFF), // System Colour #7- Magenta
46    7u32 => argb!(0xFF, 0x00, 0xFF, 0xFF), // System Colour #8- Cyan
47    8u32 => argb!(0xFF, 0x00, 0x00, 0x00), // System Colour #1 - Black
48    9u32 => argb!(0xFF, 0xFF, 0xFF, 0xFF), // System Colour #2 - White
49    10u32 => argb!(0xFF, 0xFF, 0x00, 0x00), // System Colour #3 - Red
50    11u32 => argb!(0xFF, 0x00, 0xFF, 0x00), // System Colour #4 - Green
51    12u32 => argb!(0xFF, 0x00, 0x00, 0xFF), // System Colour #5 - Blue
52    13u32 => argb!(0xFF, 0xFF, 0xFF, 0x00), // System Colour #6 - Yellow
53    14u32 => argb!(0xFF, 0xFF, 0x00, 0xFF), // System Colour #7- Magenta
54    15u32 => argb!(0xFF, 0x00, 0xFF, 0xFF), // System Colour #8- Cyan
55    16u32 => argb!(0xFF, 0x80, 0x00, 0x00), // Standard Colour #9
56    17u32 => argb!(0xFF, 0x00, 0x80, 0x00), // Standard Colour #10
57    18u32 => argb!(0xFF, 0x00, 0x00, 0x80), // Standard Colour #11
58    19u32 => argb!(0xFF, 0x80, 0x80, 0x00), // Standard Colour #12
59    20u32 => argb!(0xFF, 0x80, 0x00, 0x80), // Standard Colour #13
60    21u32 => argb!(0xFF, 0x00, 0x80, 0x80), // Standard Colour #14
61    22u32 => argb!(0xFF, 0xC0, 0xC0, 0xC0), // Standard Colour #15
62    23u32 => argb!(0xFF, 0x80, 0x80, 0x80), // Standard Colour #16
63    24u32 => argb!(0xFF, 0x99, 0x99, 0xFF), // Chart Fill Colour #17
64    25u32 => argb!(0xFF, 0x99, 0x33, 0x66), // Chart Fill Colour #18
65    26u32 => argb!(0xFF, 0xFF, 0xFF, 0xCC), // Chart Fill Colour #19
66    27u32 => argb!(0xFF, 0xCC, 0xFF, 0xFF), // Chart Fill Colour #20
67    28u32 => argb!(0xFF, 0x66, 0x00, 0x66), // Chart Fill Colour #21
68    29u32 => argb!(0xFF, 0xFF, 0x80, 0x80), // Chart Fill Colour #22
69    30u32 => argb!(0xFF, 0x00, 0x66, 0xCC), // Chart Fill Colour #23
70    31u32 => argb!(0xFF, 0xCC, 0xCC, 0xFF), // Chart Fill Colour #24
71    32u32 => argb!(0xFF, 0x00, 0x00, 0x80), // Chart Line Colour #25
72    33u32 => argb!(0xFF, 0xFF, 0x00, 0xFF), // Chart Line Colour #26
73    34u32 => argb!(0xFF, 0xFF, 0xFF, 0x00), // Chart Line Colour #27
74    35u32 => argb!(0xFF, 0x00, 0xFF, 0xFF), // Chart Line Colour #28
75    36u32 => argb!(0xFF, 0x80, 0x00, 0x80), // Chart Line Colour #29
76    37u32 => argb!(0xFF, 0x80, 0x00, 0x00), // Chart Line Colour #30
77    38u32 => argb!(0xFF, 0x00, 0x80, 0x80), // Chart Line Colour #31
78    39u32 => argb!(0xFF, 0x00, 0x00, 0xFF), // Chart Line Colour #32
79    40u32 => argb!(0xFF, 0x00, 0xCC, 0xFF), // Standard Colour #33
80    41u32 => argb!(0xFF, 0xCC, 0xFF, 0xFF), // Standard Colour #34
81    42u32 => argb!(0xFF, 0xCC, 0xFF, 0xCC), // Standard Colour #35
82    43u32 => argb!(0xFF, 0xFF, 0xFF, 0x99), // Standard Colour #36
83    44u32 => argb!(0xFF, 0x99, 0xCC, 0xFF), // Standard Colour #37
84    45u32 => argb!(0xFF, 0xFF, 0x99, 0xCC), // Standard Colour #38
85    46u32 => argb!(0xFF, 0xCC, 0x99, 0xFF), // Standard Colour #39
86    47u32 => argb!(0xFF, 0xFF, 0xCC, 0x99), // Standard Colour #40
87    48u32 => argb!(0xFF, 0x33, 0x66, 0xFF), // Standard Colour #41
88    49u32 => argb!(0xFF, 0x33, 0xCC, 0xCC), // Standard Colour #42
89    50u32 => argb!(0xFF, 0x99, 0xCC, 0x00), // Standard Colour #43
90    51u32 => argb!(0xFF, 0xFF, 0xCC, 0x00), // Standard Colour #44
91    52u32 => argb!(0xFF, 0xFF, 0x99, 0x00), // Standard Colour #45
92    53u32 => argb!(0xFF, 0xFF, 0x66, 0x00), // Standard Colour #46
93    54u32 => argb!(0xFF, 0x66, 0x66, 0x99), // Standard Colour #47
94    55u32 => argb!(0xFF, 0x96, 0x96, 0x96), // Standard Colour #48
95    56u32 => argb!(0xFF, 0x00, 0x33, 0x66), // Standard Colour #49
96    57u32 => argb!(0xFF, 0x33, 0x99, 0x66), // Standard Colour #50
97    58u32 => argb!(0xFF, 0x00, 0x33, 0x00), // Standard Colour #51
98    59u32 => argb!(0xFF, 0x33, 0x33, 0x00), // Standard Colour #52
99    60u32 => argb!(0xFF, 0x99, 0x33, 0x00), // Standard Colour #53
100    61u32 => argb!(0xFF, 0x99, 0x33, 0x66), // Standard Colour #54
101    62u32 => argb!(0xFF, 0x33, 0x33, 0x99), // Standard Colour #55
102    63u32 => argb!(0xFF, 0x33, 0x33, 0x33), // Standard Colour #56
103};
104
105static COLOR_STR_TO_INDEX: phf::Map<&'static str, u32> = phf_map! {
106    "FF000000" => 0u32, // System Colour #1 - Black
107    "FFFFFFFF" => 1u32, // System Colour #2 - White
108    "FFFF0000" => 2u32, // System Colour #3 - Red
109    "FF00FF00" => 3u32, // System Colour #4 - Green
110    "FF0000FF" => 4u32, // System Colour #5 - Blue
111    "FFFFFF00" => 5u32, // System Colour #6 - Yellow
112    "FFFF00FF" => 6u32, // System Colour #7- Magenta
113    "FF00FFFF" => 7u32, // System Colour #8- Cyan
114//    "FF000000" => 8u32, // System Colour #1 - Black - Duplicate Key !
115//    "FFFFFFFF" => 9u32, // System Colour #2 - White - Duplicate Key !
116//    "FFFF0000" => 10u32, // System Colour #3 - Red - Duplicate Key !
117//    "FF00FF00" => 11u32, // System Colour #4 - Green - Duplicate Key !
118//    "FF0000FF" => 12u32, // System Colour #5 - Blue - Duplicate Key !
119//    "FFFFFF00" => 13u32, // System Colour #6 - Yellow - Duplicate Key !
120//    "FFFF00FF" => 14u32, // System Colour #7- Magenta - Duplicate Key !
121//    "FF00FFFF" => 15u32, // System Colour #8- Cyan - Duplicate Key !
122    "FF800000" => 16u32, // Standard Colour #9
123    "FF008000" => 17u32, // Standard Colour #10
124    "FF000080" => 18u32, // Standard Colour #11
125    "FF808000" => 19u32, // Standard Colour #12
126    "FF800080" => 20u32, // Standard Colour #13
127    "FF008080" => 21u32, // Standard Colour #14
128    "FFC0C0C0" => 22u32, // Standard Colour #15
129    "FF808080" => 23u32, // Standard Colour #16
130    "FF9999FF" => 24u32, // Chart Fill Colour #17
131    "FF993366" => 25u32, // Chart Fill Colour #18
132    "FFFFFFCC" => 26u32, // Chart Fill Colour #19
133    "FFCCFFFF" => 27u32, // Chart Fill Colour #20
134    "FF660066" => 28u32, // Chart Fill Colour #21
135    "FFFF8080" => 29u32, // Chart Fill Colour #22
136    "FF0066CC" => 30u32, // Chart Fill Colour #23
137    "FFCCCCFF" => 31u32, // Chart Fill Colour #24
138//   "FF000080" => 32u32, // Chart Line Colour #25 - Duplicate Key !
139//   "FFFF00FF" => 33u32, // Chart Line Colour #26 - Duplicate Key !
140//   "FFFFFF00" => 34u32, // Chart Line Colour #27 - Duplicate Key !
141//   "FF00FFFF" => 35u32, // Chart Line Colour #28 - Duplicate Key !
142//   "FF800080" => 36u32, // Chart Line Colour #29 - Duplicate Key !
143//   "FF800000" => 37u32, // Chart Line Colour #30 - Duplicate Key !
144//   "FF008080" => 38u32, // Chart Line Colour #31 - Duplicate Key !
145//   "FF0000FF" => 39u32, // Chart Line Colour #32 - Duplicate Key !
146    "FF00CCFF" => 40u32, // Standard Colour #33
147//   "FFCCFFFF" => 41u32, // Standard Colour #34 - Duplicate Key !
148    "FFCCFFCC" => 42u32, // Standard Colour #35
149    "FFFFFF99" => 43u32, // Standard Colour #36
150    "FF99CCFF" => 44u32, // Standard Colour #37
151    "FFFF99CC" => 45u32, // Standard Colour #38
152    "FFCC99FF" => 46u32, // Standard Colour #39
153    "FFFFCC99" => 47u32, // Standard Colour #40
154    "FF3366FF" => 48u32, // Standard Colour #41
155    "FF33CCCC" => 49u32, // Standard Colour #42
156    "FF99CC00" => 50u32, // Standard Colour #43
157    "FFFFCC00" => 51u32, // Standard Colour #44
158    "FFFF9900" => 52u32, // Standard Colour #45
159    "FFFF6600" => 53u32, // Standard Colour #46
160    "FF666699" => 54u32, // Standard Colour #47
161    "FF969696" => 55u32, // Standard Colour #48
162    "FF003366" => 56u32, // Standard Colour #49
163    "FF339966" => 57u32, // Standard Colour #50
164    "FF003300" => 58u32, // Standard Colour #51
165    "FF333300" => 59u32, // Standard Colour #52
166    "FF993300" => 60u32, // Standard Colour #53
167//    "FF993366" => 61u32, // Standard Colour #54 - Duplicate Key !
168    "FF333399" => 62u32, // Standard Colour #55
169    "FF333333" => 63u32, // Standard Colour #56
170};
171
172#[derive(Default, Debug, Clone, PartialEq, PartialOrd)]
173pub struct Color {
174    indexed:     Option<u32>,
175    theme_index: Option<u32>,
176    argb:        Option<ARGB8>,
177    tint:        Option<f64>,
178}
179
180impl Color {
181    // Colors
182    pub const COLOR_BLACK: ARGB8 = ARGB8 {
183        a: 0xFF,
184        r: 0x00,
185        g: 0x00,
186        b: 0x00,
187    };
188    pub const COLOR_BLACK_STR: &'static str = "FF000000";
189    pub const COLOR_BLUE: ARGB8 = ARGB8 {
190        a: 0xFF,
191        r: 0x00,
192        g: 0x00,
193        b: 0xFF,
194    };
195    pub const COLOR_BLUE_STR: &'static str = "FF0000FF";
196    pub const COLOR_DARKBLUE: ARGB8 = ARGB8 {
197        a: 0xFF,
198        r: 0x00,
199        g: 0x00,
200        b: 0x80,
201    };
202    pub const COLOR_DARKBLUE_STR: &'static str = "FF000080";
203    pub const COLOR_DARKGREEN: ARGB8 = ARGB8 {
204        a: 0xFF,
205        r: 0x00,
206        g: 0x80,
207        b: 0x00,
208    };
209    pub const COLOR_DARKGREEN_STR: &'static str = "FF008000";
210    pub const COLOR_DARKRED: ARGB8 = ARGB8 {
211        a: 0xFF,
212        r: 0x80,
213        g: 0x00,
214        b: 0x00,
215    };
216    pub const COLOR_DARKRED_STR: &'static str = "FF800000";
217    pub const COLOR_DARKYELLOW: ARGB8 = ARGB8 {
218        a: 0xFF,
219        r: 0x80,
220        g: 0x80,
221        b: 0x00,
222    };
223    pub const COLOR_DARKYELLOW_STR: &'static str = "FF808000";
224    pub const COLOR_GREEN: ARGB8 = ARGB8 {
225        a: 0xFF,
226        r: 0x00,
227        g: 0xFF,
228        b: 0x00,
229    };
230    pub const COLOR_GREEN_STR: &'static str = "FF00FF00";
231    pub const COLOR_RED: ARGB8 = ARGB8 {
232        a: 0xFF,
233        r: 0xFF,
234        g: 0x00,
235        b: 0x00,
236    };
237    pub const COLOR_RED_STR: &'static str = "FFFF0000";
238    pub const COLOR_WHITE: ARGB8 = ARGB8 {
239        a: 0xFF,
240        r: 0xFF,
241        g: 0xFF,
242        b: 0xFF,
243    };
244    pub const COLOR_WHITE_STR: &'static str = "FFFFFFFF";
245    pub const COLOR_YELLOW: ARGB8 = ARGB8 {
246        a: 0xFF,
247        r: 0xFF,
248        g: 0xFF,
249        b: 0x00,
250    };
251    pub const COLOR_YELLOW_STR: &'static str = "FFFFFF00";
252    pub const NAMED_COLORS: [&'static str; 8] = [
253        "Black", "White", "Red", "Green", "Blue", "Yellow", "Magenta", "Cyan",
254    ];
255
256    /// Convert hex string to ARGB8
257    #[must_use]
258    pub fn hex_to_argb8(hex: &str) -> Option<ARGB8> {
259        if hex.len() == 9 && hex.starts_with('#') {
260            let a = u8::from_str_radix(&hex[1..3], 16).ok()?;
261            let r = u8::from_str_radix(&hex[3..5], 16).ok()?;
262            let g = u8::from_str_radix(&hex[5..7], 16).ok()?;
263            let b = u8::from_str_radix(&hex[7..9], 16).ok()?;
264
265            Some(ARGB8 { a, r, g, b })
266        } else if hex.len() == 8 {
267            let a = u8::from_str_radix(&hex[0..2], 16).ok()?;
268            let r = u8::from_str_radix(&hex[2..4], 16).ok()?;
269            let g = u8::from_str_radix(&hex[4..6], 16).ok()?;
270            let b = u8::from_str_radix(&hex[6..8], 16).ok()?;
271
272            Some(ARGB8 { a, r, g, b })
273        } else if hex.len() == 7 && hex.starts_with('#') {
274            let a = 0xFF;
275            let r = u8::from_str_radix(&hex[1..3], 16).ok()?;
276            let g = u8::from_str_radix(&hex[3..5], 16).ok()?;
277            let b = u8::from_str_radix(&hex[5..7], 16).ok()?;
278
279            Some(ARGB8 { a, r, g, b })
280        } else if hex.len() == 6 {
281            let a = 0xFF;
282            let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
283            let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
284            let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
285
286            Some(ARGB8 { a, r, g, b })
287        } else if (hex.len() == 4 && hex.starts_with('#')) || hex.len() == 3 {
288            // To pass an integration test where the hex string is "#333".
289            // https://github.com/MathNya/umya-spreadsheet/pull/113
290            // https://github.com/MathNya/umya-spreadsheet/pull/250#issuecomment-2566258423
291            // https://www.w3schools.com/css/css_colors_hex.asp
292
293            let padded_hex = hex
294                .replace('#', "")
295                .chars()
296                .map(|c| c.to_string().repeat(2))
297                .collect::<String>();
298            let a = 0xFF;
299            let r = u8::from_str_radix(&padded_hex[0..2], 16).ok()?;
300            let g = u8::from_str_radix(&padded_hex[2..4], 16).ok()?;
301            let b = u8::from_str_radix(&padded_hex[4..6], 16).ok()?;
302
303            Some(ARGB8 { a, r, g, b })
304        } else {
305            None
306        }
307    }
308
309    /// Convert ARGB8 to hex string
310    #[must_use]
311    pub fn argb8_to_hex(argb: ARGB8) -> String {
312        format!("{:02X}{:02X}{:02X}{:02X}", argb.a, argb.r, argb.g, argb.b)
313    }
314
315    #[must_use]
316    pub fn argb_str(&self) -> String {
317        Self::argb8_to_hex(self.argb())
318    }
319
320    #[must_use]
321    #[deprecated(since = "3.0.0", note = "Use argb_str()")]
322    pub fn get_argb_str(&self) -> String {
323        Self::argb8_to_hex(self.argb())
324    }
325
326    #[must_use]
327    pub fn argb(&self) -> ARGB8 {
328        if let Some(idx) = self.indexed {
329            if let Some(v) = INDEX_TO_COLOR.get(&idx) {
330                return *v;
331            }
332        }
333        self.argb.unwrap_or_default()
334    }
335
336    #[must_use]
337    #[deprecated(since = "3.0.0", note = "Use argb()")]
338    pub fn get_argb(&self) -> ARGB8 {
339        self.argb()
340    }
341
342    /// Get Argb.
343    /// Color information based on the theme can also be obtained.
344    /// # Examples
345    /// ```
346    /// let mut book = umya_spreadsheet::new_file();
347    /// let theme = book.theme();
348    /// ```
349    #[must_use]
350    pub fn argb_with_theme(&self, theme: &Theme) -> Cow<'static, str> {
351        if self.indexed.is_some() {
352            return self.argb_str().into();
353        }
354        if let Some(key) = self.theme_index {
355            if let Some(v) = theme
356                .theme_elements()
357                .color_scheme()
358                .color_map()
359                .get(key as usize)
360            {
361                if let Some(tint) = self.tint {
362                    return calc_tint(v, tint).into();
363                }
364                return v.clone().into();
365            }
366        }
367        self.argb_str().clone().into()
368    }
369
370    #[must_use]
371    #[deprecated(since = "3.0.0", note = "Use argb_with_theme()")]
372    pub fn get_argb_with_theme(&self, theme: &Theme) -> Cow<'static, str> {
373        self.argb_with_theme(theme)
374    }
375
376    pub fn set_argb<S: Into<ARGB8>>(&mut self, value: S) -> &mut Self {
377        let argb = value.into();
378        let indexed = COLOR_STR_TO_INDEX.get(Self::argb8_to_hex(argb).as_ref());
379
380        if let Some(v) = indexed {
381            self.indexed = Some(*v);
382            self.argb = None;
383        } else {
384            self.indexed = None;
385            self.argb = Some(argb);
386        }
387        self.theme_index = None;
388        self
389    }
390
391    pub fn set_argb_str<S: AsRef<str>>(&mut self, value: S) -> &mut Self {
392        let argb = Self::hex_to_argb8(value.as_ref()).unwrap();
393        let indexed = COLOR_STR_TO_INDEX.get(value.as_ref());
394
395        if let Some(v) = indexed {
396            self.indexed = Some(*v);
397            self.argb = None;
398        } else {
399            self.indexed = None;
400            self.argb = Some(argb);
401        }
402        self.theme_index = None;
403        self
404    }
405
406    #[inline]
407    #[must_use]
408    pub fn indexed(&self) -> u32 {
409        self.indexed.unwrap_or(0)
410    }
411
412    #[inline]
413    #[must_use]
414    #[deprecated(since = "3.0.0", note = "Use indexed()")]
415    pub fn get_indexed(&self) -> u32 {
416        self.indexed()
417    }
418
419    #[inline]
420    pub fn set_indexed(&mut self, index: u32) -> &mut Self {
421        self.indexed = Some(index);
422        self.theme_index = None;
423        self.argb = None;
424        self
425    }
426
427    #[inline]
428    #[must_use]
429    pub fn theme_index(&self) -> u32 {
430        self.theme_index.unwrap_or(0)
431    }
432
433    #[inline]
434    #[must_use]
435    #[deprecated(since = "3.0.0", note = "Use theme_index()")]
436    pub fn get_theme_index(&self) -> u32 {
437        self.theme_index()
438    }
439
440    #[inline]
441    pub fn set_theme_index(&mut self, index: u32) -> &mut Self {
442        self.indexed = None;
443        self.theme_index = Some(index);
444        self.argb = None;
445        self
446    }
447
448    #[inline]
449    #[must_use]
450    pub fn tint(&self) -> f64 {
451        self.tint.unwrap_or(0.0)
452    }
453
454    #[inline]
455    #[must_use]
456    #[deprecated(since = "3.0.0", note = "Use tint()")]
457    pub fn get_tint(&self) -> f64 {
458        self.tint()
459    }
460
461    #[inline]
462    pub fn set_tint(&mut self, value: f64) -> &mut Color {
463        self.tint = Some(value);
464        self
465    }
466
467    #[inline]
468    pub(crate) fn has_value(&self) -> bool {
469        self.theme_index.is_some()
470            || self.indexed.is_some()
471            || self.argb.is_some()
472            || self.tint.is_some()
473    }
474
475    #[inline]
476    pub(crate) fn hash_code(&self) -> String {
477        crate::helper::utils::md5_hash(format!(
478            "{}{}{}{}",
479            self.indexed.map_or(String::new(), |v| v.to_string()),
480            self.theme_index.map_or(String::new(), |v| v.to_string()),
481            self.argb.map_or(String::new(), Self::argb8_to_hex),
482            self.tint.map_or(String::new(), |v| v.to_string())
483        ))
484    }
485
486    #[inline]
487    #[deprecated(since = "3.0.0", note = "Use tint()")]
488    pub(crate) fn get_hash_code(&self) -> String {
489        self.hash_code()
490    }
491
492    // When opened in software such as Excel, it is visually blank.
493    #[inline]
494    pub(crate) fn is_visually_empty(&self) -> bool {
495        !self.has_value()
496    }
497
498    pub(crate) fn set_attributes<R: std::io::BufRead>(
499        &mut self,
500        reader: &mut Reader<R>,
501        e: &BytesStart,
502        empty_flg: bool,
503    ) {
504        for attr in e.attributes().with_checks(false).flatten() {
505            match attr.key.0 {
506                b"indexed" => {
507                    if let Ok(v) = get_attribute_value(&attr) {
508                        if let Ok(num) = v.parse() {
509                            self.indexed = Some(num);
510                        }
511                    }
512                }
513                b"theme" => {
514                    if let Ok(v) = get_attribute_value(&attr) {
515                        if let Ok(num) = v.parse() {
516                            self.theme_index = Some(num);
517                        }
518                    }
519                }
520                b"rgb" => {
521                    if let Ok(v) = get_attribute_value(&attr) {
522                        self.argb = Self::hex_to_argb8(&v);
523                    }
524                }
525                b"tint" => {
526                    if let Ok(v) = get_attribute_value(&attr) {
527                        if let Ok(num) = v.parse() {
528                            self.tint = Some(num);
529                        }
530                    }
531                }
532                _ => {}
533            }
534        }
535
536        if empty_flg {
537            return;
538        }
539
540        let mut buf = Vec::new();
541        loop {
542            match reader.read_event_into(&mut buf) {
543                Ok(Event::End(ref e)) => match e.name().into_inner() {
544                    b"color" | b"fgColor" | b"bgColor" | b"tabColor" => return,
545                    _ => (),
546                },
547                Ok(Event::Eof) => panic!(
548                    "Error: Could not find {} end element",
549                    "color,fgColor,bgColor,tabColor"
550                ),
551                Err(e) => panic!("Error at position {}: {:?}", reader.buffer_position(), e),
552                _ => (),
553            }
554            buf.clear();
555        }
556    }
557
558    #[inline]
559    pub(crate) fn write_to_color(&self, writer: &mut Writer<Cursor<Vec<u8>>>) {
560        // color
561        self.write_to(writer, "color");
562    }
563
564    #[inline]
565    pub(crate) fn write_to_fg_color(&self, writer: &mut Writer<Cursor<Vec<u8>>>) {
566        // fgColor
567        self.write_to(writer, "fgColor");
568    }
569
570    #[inline]
571    pub(crate) fn write_to_bg_color(&self, writer: &mut Writer<Cursor<Vec<u8>>>) {
572        // bgColor
573        self.write_to(writer, "bgColor");
574    }
575
576    #[inline]
577    pub(crate) fn write_to_tab_color(&self, writer: &mut Writer<Cursor<Vec<u8>>>) {
578        // tabColor
579        self.write_to(writer, "tabColor");
580    }
581
582    fn write_to(&self, writer: &mut Writer<Cursor<Vec<u8>>>, tag_name: &str) {
583        let mut attributes: crate::structs::AttrCollection = Vec::new();
584
585        if let Some(theme_index) = self.theme_index {
586            attributes.push(("theme", theme_index.to_string()).into());
587        } else if let Some(indexed) = self.indexed {
588            attributes.push(("indexed", indexed.to_string()).into());
589        } else if let Some(argb) = self.argb {
590            attributes.push(("rgb", Self::argb8_to_hex(argb)).into());
591        }
592
593        if let Some(tint) = self.tint {
594            attributes.push(("tint", tint.to_string()).into());
595        }
596
597        if !attributes.is_empty() {
598            write_start_tag(writer, tag_name, attributes, true);
599        }
600    }
601}
602
603#[cfg(test)]
604mod tests {
605    use super::*;
606
607    #[test]
608    fn test_hex_conversion() {
609        let hex = "FF123456";
610        let argb = Color::hex_to_argb8(hex).unwrap();
611        assert_eq!(Color::argb8_to_hex(argb), hex);
612    }
613
614    #[test]
615    fn set_value() {
616        let mut obj = Color::default();
617        obj.set_argb_str("F34F8080");
618        assert_eq!(obj.argb_str(), "F34F8080");
619
620        let mut obj = Color::default();
621        obj.set_argb_str("FFFF8080");
622        assert_eq!(obj.indexed(), 29);
623        assert_eq!(obj.argb_str(), "FFFF8080");
624
625        let mut obj = Color::default();
626        let theme = Theme::default_value();
627        obj.set_theme_index(1);
628        assert_eq!(obj.argb_with_theme(&theme), "000000");
629    }
630}