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