1use 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), 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), };
104
105static COLOR_STR_TO_INDEX: phf::Map<&'static str, u32> = phf_map! {
106 "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, };
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 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 #[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 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 #[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 #[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 #[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 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 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 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 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}