1use std::fmt;
34use std::hash::{Hash, Hasher};
35use std::sync::atomic::{AtomicU32, Ordering};
36
37use crate::color::{Color, ColorType, EIGHT_BIT_PALETTE, STANDARD_COLOR_NAMES, STANDARD_PALETTE};
38
39static NEXT_ID: AtomicU32 = AtomicU32::new(0);
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
47pub struct Attributes(u32);
48
49impl Attributes {
50 pub const BOLD: u32 = 1 << 0;
52 pub const DIM: u32 = 1 << 1;
54 pub const ITALIC: u32 = 1 << 2;
56 pub const UNDERLINE: u32 = 1 << 3;
58 pub const BLINK: u32 = 1 << 4;
60 pub const REVERSE: u32 = 1 << 5;
62 pub const STRIKE: u32 = 1 << 6;
64 pub const UNDERLINE2: u32 = 1 << 7;
66 pub const FRAME: u32 = 1 << 8;
68 pub const ENCIRCLE: u32 = 1 << 9;
70 pub const OVERLINE: u32 = 1 << 10;
72 pub const BLINK2: u32 = 1 << 11;
74 pub const CONCEAL: u32 = 1 << 12;
76
77 pub const fn empty() -> Self {
79 Self(0)
80 }
81
82 pub fn set(&mut self, bit: u32, value: bool) {
84 if value {
85 self.0 |= bit;
86 } else {
87 self.0 &= !bit;
88 }
89 }
90
91 pub fn get(&self, bit: u32) -> bool {
93 self.0 & bit != 0
94 }
95
96 pub const fn bits(&self) -> u32 {
98 self.0
99 }
100}
101
102pub const STYLE_BITS: &[u32] = &[
104 Attributes::BOLD,
105 Attributes::DIM,
106 Attributes::ITALIC,
107 Attributes::UNDERLINE,
108 Attributes::BLINK,
109 Attributes::REVERSE,
110 Attributes::STRIKE,
111 Attributes::UNDERLINE2,
112 Attributes::FRAME,
113 Attributes::ENCIRCLE,
114 Attributes::OVERLINE,
115 Attributes::BLINK2,
116 Attributes::CONCEAL,
117];
118
119pub const STYLE_ATTRIBUTES: &[(&str, u32)] = &[
121 ("bold", Attributes::BOLD),
122 ("dim", Attributes::DIM),
123 ("italic", Attributes::ITALIC),
124 ("underline", Attributes::UNDERLINE),
125 ("blink", Attributes::BLINK),
126 ("reverse", Attributes::REVERSE),
127 ("strike", Attributes::STRIKE),
128 ("underline2", Attributes::UNDERLINE2),
129 ("frame", Attributes::FRAME),
130 ("encircle", Attributes::ENCIRCLE),
131 ("overline", Attributes::OVERLINE),
132 ("blink2", Attributes::BLINK2),
133 ("conceal", Attributes::CONCEAL),
134];
135
136impl fmt::Display for Attributes {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 let mut parts: Vec<&str> = Vec::new();
139 if self.get(Self::BOLD) {
140 parts.push("bold");
141 }
142 if self.get(Self::DIM) {
143 parts.push("dim");
144 }
145 if self.get(Self::ITALIC) {
146 parts.push("italic");
147 }
148 if self.get(Self::UNDERLINE) {
149 parts.push("underline");
150 }
151 if self.get(Self::BLINK) {
152 parts.push("blink");
153 }
154 if self.get(Self::REVERSE) {
155 parts.push("reverse");
156 }
157 if self.get(Self::CONCEAL) {
158 parts.push("conceal");
159 }
160 if self.get(Self::STRIKE) {
161 parts.push("strike");
162 }
163 if self.get(Self::OVERLINE) {
164 parts.push("overline");
165 }
166 if parts.is_empty() {
167 write!(f, "none")
168 } else {
169 write!(f, "{}", parts.join(" "))
170 }
171 }
172}
173
174pub const NULL_STYLE: Style = Style {
181 color: None,
182 bgcolor: None,
183 attributes: Attributes(0),
184 set_attributes: 0,
185 link: None,
186 link_id: 0,
187 is_null: true,
188 meta: None,
189};
190
191#[derive(Debug, Clone)]
197pub struct Style {
198 pub(crate) color: Option<Color>,
199 pub(crate) bgcolor: Option<Color>,
200 pub(crate) attributes: Attributes,
201 pub(crate) set_attributes: u32,
203 pub(crate) link: Option<String>,
204 pub(crate) link_id: u32,
205 pub(crate) is_null: bool,
206 pub(crate) meta: Option<Vec<u8>>,
208}
209
210impl Style {
211 pub fn null() -> Self {
218 NULL_STYLE.clone()
219 }
220
221 pub fn new() -> Self {
223 Self {
224 color: None,
225 bgcolor: None,
226 attributes: Attributes::empty(),
227 set_attributes: 0,
228 link: None,
229 link_id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
230 is_null: false,
231 meta: None,
232 }
233 }
234
235 pub fn color(mut self, color: impl Into<Option<Color>>) -> Self {
237 self.color = color.into();
238 self
239 }
240
241 pub fn bgcolor(mut self, bgcolor: impl Into<Option<Color>>) -> Self {
243 self.bgcolor = bgcolor.into();
244 self
245 }
246
247 pub fn bold(mut self, value: bool) -> Self {
249 self.set_attributes |= Attributes::BOLD;
250 self.attributes.set(Attributes::BOLD, value);
251 self
252 }
253
254 pub fn dim(mut self, value: bool) -> Self {
256 self.set_attributes |= Attributes::DIM;
257 self.attributes.set(Attributes::DIM, value);
258 self
259 }
260
261 pub fn italic(mut self, value: bool) -> Self {
263 self.set_attributes |= Attributes::ITALIC;
264 self.attributes.set(Attributes::ITALIC, value);
265 self
266 }
267
268 pub fn underline(mut self, value: bool) -> Self {
270 self.set_attributes |= Attributes::UNDERLINE;
271 self.attributes.set(Attributes::UNDERLINE, value);
272 self
273 }
274
275 pub fn blink(mut self, value: bool) -> Self {
277 self.set_attributes |= Attributes::BLINK;
278 self.attributes.set(Attributes::BLINK, value);
279 self
280 }
281
282 pub fn reverse(mut self, value: bool) -> Self {
284 self.set_attributes |= Attributes::REVERSE;
285 self.attributes.set(Attributes::REVERSE, value);
286 self
287 }
288
289 pub fn strike(mut self, value: bool) -> Self {
291 self.set_attributes |= Attributes::STRIKE;
292 self.attributes.set(Attributes::STRIKE, value);
293 self
294 }
295
296 pub fn blink2(mut self, value: bool) -> Self {
298 self.set_attributes |= Attributes::BLINK2;
299 self.attributes.set(Attributes::BLINK2, value);
300 self
301 }
302
303 pub fn conceal(mut self, value: bool) -> Self {
305 self.set_attributes |= Attributes::CONCEAL;
306 self.attributes.set(Attributes::CONCEAL, value);
307 self
308 }
309
310 pub fn underline2(mut self, value: bool) -> Self {
312 self.set_attributes |= Attributes::UNDERLINE2;
313 self.attributes.set(Attributes::UNDERLINE2, value);
314 self
315 }
316
317 pub fn frame(mut self, value: bool) -> Self {
319 self.set_attributes |= Attributes::FRAME;
320 self.attributes.set(Attributes::FRAME, value);
321 self
322 }
323
324 pub fn encircle(mut self, value: bool) -> Self {
326 self.set_attributes |= Attributes::ENCIRCLE;
327 self.attributes.set(Attributes::ENCIRCLE, value);
328 self
329 }
330
331 pub fn overline(mut self, value: bool) -> Self {
333 self.set_attributes |= Attributes::OVERLINE;
334 self.attributes.set(Attributes::OVERLINE, value);
335 self
336 }
337
338 pub fn without_color(&self) -> Self {
340 let mut s = self.clone();
341 s.color = None;
342 s.bgcolor = None;
343 s
344 }
345
346 pub fn background_style(&self) -> Self {
349 let mut s = Self::new();
350 s.bgcolor = self.color;
351 s
352 }
353
354 pub fn transparent_background(&self) -> bool {
356 self.bgcolor.is_none()
357 }
358
359 pub fn link(mut self, url: impl Into<String>) -> Self {
361 self.link = Some(url.into());
362 self.link_id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
363 self
364 }
365
366 #[allow(clippy::should_implement_trait)]
373 pub fn from_str(definition: &str) -> Self {
374 let mut style = Self::new();
375 let parts: Vec<&str> = definition.split_whitespace().collect();
376 let mut i = 0;
377 let mut negate = false;
378 let mut saw_on = false;
379
380 while i < parts.len() {
381 let part = parts[i];
382
383 if part == "not" {
385 negate = true;
386 i += 1;
387 continue;
388 }
389
390 match part {
391 "bold" | "b" => {
392 style.set_attributes |= Attributes::BOLD;
393 style.attributes.set(Attributes::BOLD, !negate);
394 }
395 "dim" | "d" => {
396 style.set_attributes |= Attributes::DIM;
397 style.attributes.set(Attributes::DIM, !negate);
398 }
399 "italic" | "i" => {
400 style.set_attributes |= Attributes::ITALIC;
401 style.attributes.set(Attributes::ITALIC, !negate);
402 }
403 "underline" | "u" => {
404 style.set_attributes |= Attributes::UNDERLINE;
405 style.attributes.set(Attributes::UNDERLINE, !negate);
406 }
407 "blink" => {
408 style.set_attributes |= Attributes::BLINK;
409 style.attributes.set(Attributes::BLINK, !negate);
410 }
411 "reverse" | "r" => {
412 style.set_attributes |= Attributes::REVERSE;
413 style.attributes.set(Attributes::REVERSE, !negate);
414 }
415 "strike" | "s" => {
416 style.set_attributes |= Attributes::STRIKE;
417 style.attributes.set(Attributes::STRIKE, !negate);
418 }
419 "none" | "default" => {}
420 "on" => {
421 saw_on = true;
422 if i + 1 < parts.len() {
424 if let Ok(c) = Color::parse(parts[i + 1]) {
425 style.bgcolor = Some(c);
426 i += 1; }
428 }
429 }
430 part if part.starts_with('!') => {
431 let inner = &part[1..];
433 let (bit, _name) = match inner {
434 "bold" | "b" => (Attributes::BOLD, "bold"),
435 "dim" | "d" => (Attributes::DIM, "dim"),
436 "italic" | "i" => (Attributes::ITALIC, "italic"),
437 "underline" | "u" => (Attributes::UNDERLINE, "underline"),
438 "blink" => (Attributes::BLINK, "blink"),
439 "reverse" | "r" => (Attributes::REVERSE, "reverse"),
440 "strike" | "s" => (Attributes::STRIKE, "strike"),
441 _ => {
442 i += 1;
444 negate = false;
445 continue;
446 }
447 };
448 style.set_attributes |= bit;
449 style.attributes.set(bit, false);
450 }
451 part if part.starts_with("no") && part.len() > 2 => {
452 let inner = &part[2..];
454 let (bit, _name) = match inner {
455 "bold" => (Attributes::BOLD, "bold"),
456 "italic" => (Attributes::ITALIC, "italic"),
457 "underline" => (Attributes::UNDERLINE, "underline"),
458 _ => {
459 i += 1;
461 negate = false;
462 continue;
463 }
464 };
465 style.set_attributes |= bit;
466 style.attributes.set(bit, false);
467 }
468 part if part.starts_with("link=") => {
469 style.link = Some(part[5..].to_string());
470 }
471 part if part.starts_with("on ") => {
472 if let Ok(c) = Color::parse(&part[3..]) {
473 style.bgcolor = Some(c);
474 }
475 }
476 part => {
477 if let Ok(c) = Color::parse(part) {
479 if saw_on {
480 style.bgcolor = Some(c);
481 saw_on = false;
482 } else {
483 style.color = Some(c);
484 }
485 }
486 }
487 }
488 negate = false;
489 i += 1;
490 }
491 style
492 }
493
494 pub fn is_null(&self) -> bool {
498 self.is_null
499 }
500
501 pub fn is_plain(&self) -> bool {
503 self.color.is_none()
504 && self.bgcolor.is_none()
505 && self.set_attributes == 0
506 && self.link.is_none()
507 }
508
509 pub fn get_bold(&self) -> Option<bool> {
511 if self.set_attributes & Attributes::BOLD != 0 {
512 Some(self.attributes.get(Attributes::BOLD))
513 } else {
514 None
515 }
516 }
517
518 pub fn get_italic(&self) -> Option<bool> {
520 if self.set_attributes & Attributes::ITALIC != 0 {
521 Some(self.attributes.get(Attributes::ITALIC))
522 } else {
523 None
524 }
525 }
526
527 pub fn combine(&self, other: &Style) -> Style {
529 if other.is_null {
530 return self.clone();
531 }
532 if self.is_null {
533 return other.clone();
534 }
535
536 let mut combined = self.clone();
537 if other.color.is_some() {
538 combined.color = other.color;
539 }
540 if other.bgcolor.is_some() {
541 combined.bgcolor = other.bgcolor;
542 }
543 for &bit in STYLE_BITS {
545 if other.set_attributes & bit != 0 {
546 combined.set_attributes |= bit;
547 combined.attributes.set(bit, other.attributes.get(bit));
548 }
549 }
550 if other.link.is_some() {
551 combined.link = other.link.clone();
552 combined.link_id = other.link_id;
553 }
554 if other.meta.is_some() {
555 combined.meta = other.meta.clone();
556 }
557 combined.is_null = false;
558 combined
559 }
560
561 pub fn to_ansi(&self) -> String {
567 if self.is_null {
568 return String::new();
569 }
570 let mut out = String::with_capacity(48);
571 let mut first = true;
572
573 macro_rules! push_code {
575 ($code:expr) => {{
576 if first {
577 out.push_str("\x1b[");
578 first = false;
579 } else {
580 out.push(';');
581 }
582 out.push_str($code);
583 }};
584 }
585
586 if let Some(ref c) = self.color {
588 match c.color_type {
589 crate::color::ColorType::Default => push_code!("39"),
590 crate::color::ColorType::Standard => {
591 if let Some(n) = c.number {
592 let code = if n < 8 { 30 + n } else { 82 + n };
593 push_code!(&code.to_string());
594 }
595 }
596 crate::color::ColorType::EightBit => {
597 if let Some(n) = c.number {
598 out.push_str(if first { "\x1b[38;5;" } else { ";38;5;" });
599 first = false;
600 out.push_str(&n.to_string());
601 }
602 }
603 crate::color::ColorType::TrueColor => {
604 if let Some((r, g, b)) = c.triplet {
605 out.push_str(if first { "\x1b[38;2;" } else { ";38;2;" });
606 first = false;
607 out.push_str(&format!("{r};{g};{b}"));
608 }
609 }
610 }
611 }
612
613 if let Some(ref c) = self.bgcolor {
615 match c.color_type {
616 crate::color::ColorType::Default => push_code!("49"),
617 crate::color::ColorType::Standard => {
618 if let Some(n) = c.number {
619 let code = if n < 8 { 40 + n } else { 92 + n };
620 push_code!(&code.to_string());
621 }
622 }
623 crate::color::ColorType::EightBit => {
624 if let Some(n) = c.number {
625 out.push_str(if first { "\x1b[48;5;" } else { ";48;5;" });
626 first = false;
627 out.push_str(&n.to_string());
628 }
629 }
630 crate::color::ColorType::TrueColor => {
631 if let Some((r, g, b)) = c.triplet {
632 out.push_str(if first { "\x1b[48;2;" } else { ";48;2;" });
633 first = false;
634 out.push_str(&format!("{r};{g};{b}"));
635 }
636 }
637 }
638 }
639
640 if self.set_attributes & Attributes::BOLD != 0 {
642 push_code!(if self.attributes.get(Attributes::BOLD) {
643 "1"
644 } else {
645 "22"
646 });
647 }
648 if self.set_attributes & Attributes::DIM != 0 {
649 push_code!(if self.attributes.get(Attributes::DIM) {
650 "2"
651 } else {
652 "22"
653 });
654 }
655 if self.set_attributes & Attributes::ITALIC != 0 {
656 push_code!(if self.attributes.get(Attributes::ITALIC) {
657 "3"
658 } else {
659 "23"
660 });
661 }
662 if self.set_attributes & Attributes::UNDERLINE != 0 {
663 push_code!(if self.attributes.get(Attributes::UNDERLINE) {
664 "4"
665 } else {
666 "24"
667 });
668 }
669 if self.set_attributes & Attributes::BLINK != 0 {
670 push_code!(if self.attributes.get(Attributes::BLINK) {
671 "5"
672 } else {
673 "25"
674 });
675 }
676 if self.set_attributes & Attributes::REVERSE != 0 {
677 push_code!(if self.attributes.get(Attributes::REVERSE) {
678 "7"
679 } else {
680 "27"
681 });
682 }
683 if self.set_attributes & Attributes::CONCEAL != 0 {
684 push_code!(if self.attributes.get(Attributes::CONCEAL) {
685 "8"
686 } else {
687 "28"
688 });
689 }
690 if self.set_attributes & Attributes::STRIKE != 0 {
691 push_code!(if self.attributes.get(Attributes::STRIKE) {
692 "9"
693 } else {
694 "29"
695 });
696 }
697 if self.set_attributes & Attributes::UNDERLINE2 != 0 {
698 push_code!(if self.attributes.get(Attributes::UNDERLINE2) {
699 "21"
700 } else {
701 "24"
702 });
703 }
704 if self.set_attributes & Attributes::BLINK2 != 0 {
705 push_code!(if self.attributes.get(Attributes::BLINK2) {
706 "6"
707 } else {
708 "25"
709 });
710 }
711 if self.set_attributes & Attributes::FRAME != 0 {
712 push_code!(if self.attributes.get(Attributes::FRAME) {
713 "51"
714 } else {
715 "54"
716 });
717 }
718 if self.set_attributes & Attributes::ENCIRCLE != 0 {
719 push_code!(if self.attributes.get(Attributes::ENCIRCLE) {
720 "52"
721 } else {
722 "54"
723 });
724 }
725 if self.set_attributes & Attributes::OVERLINE != 0 {
726 push_code!(if self.attributes.get(Attributes::OVERLINE) {
727 "53"
728 } else {
729 "55"
730 });
731 }
732
733 if !first {
734 out.push('m');
735 }
736 out
737 }
738
739 pub fn reset_ansi(&self) -> &'static str {
741 "\x1b[0m"
742 }
743
744 pub fn chain(&self, other: &Style) -> Style {
749 let mut result = Style::new();
750 result.color = self.color.or(other.color);
751 result.bgcolor = self.bgcolor.or(other.bgcolor);
752 result.link = self.link.clone().or_else(|| other.link.clone());
753 result.meta = self.meta.clone().or_else(|| other.meta.clone());
754 for &bit in STYLE_BITS {
755 if self.set_attributes & bit != 0 {
756 result.set_attributes |= bit;
757 result.attributes.set(bit, self.attributes.get(bit));
758 } else if other.set_attributes & bit != 0 {
759 result.set_attributes |= bit;
760 result.attributes.set(bit, other.attributes.get(bit));
761 }
762 }
763 result
764 }
765
766 pub fn copy(&self) -> Style {
770 self.clone()
771 }
772
773 pub fn clear_meta_and_links(&mut self) -> &mut Self {
775 self.meta = None;
776 self.link = None;
777 self
778 }
779
780 pub fn from_color(color: Color) -> Self {
784 Self::new().color(color)
785 }
786
787 pub fn from_meta(meta: Vec<u8>) -> Self {
789 let mut s = Self::new();
790 s.meta = Some(meta);
791 s
792 }
793
794 pub fn get_html_style(&self, _theme: Option<&crate::export::ExportTheme>) -> String {
798 if self.is_null {
799 return String::new();
800 }
801 let mut parts: Vec<String> = Vec::new();
802
803 if let Some(ref c) = self.color {
804 let hex = color_to_css_hex(c);
805 if !hex.is_empty() {
806 parts.push(format!("color: {}", hex));
807 }
808 }
809 if let Some(ref c) = self.bgcolor {
810 let hex = color_to_css_hex(c);
811 if !hex.is_empty() {
812 parts.push(format!("background-color: {}", hex));
813 }
814 }
815 if self.set_attributes & Attributes::BOLD != 0 && self.attributes.get(Attributes::BOLD) {
816 parts.push("font-weight: bold".into());
817 }
818 if self.set_attributes & Attributes::ITALIC != 0 && self.attributes.get(Attributes::ITALIC)
819 {
820 parts.push("font-style: italic".into());
821 }
822
823 let mut decor: Vec<&str> = Vec::new();
825 if self.set_attributes & Attributes::UNDERLINE != 0
826 && self.attributes.get(Attributes::UNDERLINE)
827 {
828 decor.push("underline");
829 }
830 if self.set_attributes & Attributes::UNDERLINE2 != 0
831 && self.attributes.get(Attributes::UNDERLINE2)
832 {
833 decor.push("underline");
834 }
835 if self.set_attributes & Attributes::STRIKE != 0 && self.attributes.get(Attributes::STRIKE)
836 {
837 decor.push("line-through");
838 }
839 if (self.set_attributes & Attributes::BLINK != 0 && self.attributes.get(Attributes::BLINK))
840 || (self.set_attributes & Attributes::BLINK2 != 0
841 && self.attributes.get(Attributes::BLINK2))
842 {
843 decor.push("blink");
844 }
845 if !decor.is_empty() {
846 parts.push(format!("text-decoration: {}", decor.join(" ")));
847 }
848
849 if self.set_attributes & Attributes::REVERSE != 0
851 && self.attributes.get(Attributes::REVERSE)
852 {
853 parts.push("filter: invert(100%)".into());
854 }
855
856 if self.set_attributes & Attributes::CONCEAL != 0
858 && self.attributes.get(Attributes::CONCEAL)
859 {
860 parts.push("visibility: hidden".into());
861 }
862
863 if parts.is_empty() {
864 String::new()
865 } else {
866 parts.join("; ")
867 }
868 }
869
870 pub fn normalize(&self) -> Style {
876 let mut s = Style::new();
877 s.color = self.color;
878 s.bgcolor = self.bgcolor;
879 s.link = self.link.clone();
880 s.link_id = self.link_id;
881 s.meta = self.meta.clone();
882 for &bit in STYLE_BITS {
883 if self.set_attributes & bit != 0 && self.attributes.get(bit) {
884 s.set_attributes |= bit;
885 s.attributes.set(bit, true);
886 }
887 }
888 s
889 }
890
891 pub fn pick_first(&self) -> Option<&'static str> {
896 if let Some(ref c) = self.color {
897 if let Some(name) = color_to_name(c) {
898 return Some(name);
899 }
900 }
901 if let Some(ref c) = self.bgcolor {
902 if let Some(name) = color_to_name(c) {
903 return Some(name);
904 }
905 }
906 None
907 }
908
909 pub fn render(&self, text: &str) -> String {
911 format!("{}{}{}", self.to_ansi(), text, self.reset_ansi())
912 }
913
914 pub fn test(&self, text: Option<&str>) -> String {
916 let t = text.unwrap_or("Lorem ipsum");
917 self.render(t)
918 }
919
920 pub fn update_link(&mut self, url: Option<String>) -> &mut Self {
922 self.link = url;
923 if self.link.is_some() {
924 self.link_id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
925 }
926 self
927 }
928
929 pub fn meta(&self) -> Option<&Vec<u8>> {
933 self.meta.as_ref()
934 }
935
936 pub fn meta_mut(&mut self) -> Option<&mut Vec<u8>> {
938 self.meta.as_mut()
939 }
940
941 pub fn set_meta(&mut self, meta: Option<Vec<u8>>) -> &mut Self {
943 self.meta = meta;
944 self
945 }
946
947 pub fn link_id(&self) -> u32 {
949 self.link_id
950 }
951
952 pub fn on(self, color: impl Into<Option<Color>>) -> Self {
954 self.bgcolor(color)
955 }
956
957 pub fn color_ref(&self) -> Option<&Color> {
959 self.color.as_ref()
960 }
961
962 pub fn bgcolor_ref(&self) -> Option<&Color> {
964 self.bgcolor.as_ref()
965 }
966}
967
968impl Default for Style {
969 fn default() -> Self {
970 Self::new()
971 }
972}
973
974impl PartialEq for Style {
975 fn eq(&self, other: &Self) -> bool {
976 self.color == other.color
977 && self.bgcolor == other.bgcolor
978 && self.attributes == other.attributes
979 && self.set_attributes == other.set_attributes
980 && self.link == other.link
981 }
982}
983
984impl Eq for Style {}
985
986impl Hash for Style {
987 fn hash<H: Hasher>(&self, state: &mut H) {
988 self.color.hash(state);
989 self.bgcolor.hash(state);
990 self.attributes.hash(state);
991 self.set_attributes.hash(state);
992 self.link.hash(state);
993 }
994}
995
996impl fmt::Display for Style {
997 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
998 if self.is_null {
999 return write!(f, "null");
1000 }
1001 let mut parts: Vec<String> = Vec::new();
1002 if let Some(ref c) = self.color {
1003 parts.push(c.to_string());
1004 }
1005 if let Some(ref c) = self.bgcolor {
1006 parts.push(format!("on {}", c));
1007 }
1008 let attrs = self.attributes.to_string();
1009 if attrs != "none" {
1010 parts.push(attrs);
1011 }
1012 if parts.is_empty() {
1013 write!(f, "none")
1014 } else {
1015 write!(f, "{}", parts.join(" "))
1016 }
1017 }
1018}
1019
1020pub type StyleType = Style;
1022
1023fn color_to_css_hex(c: &Color) -> String {
1027 match c.color_type {
1028 ColorType::Default => String::new(),
1029 ColorType::Standard => {
1030 if let Some(n) = c.number {
1031 let (r, g, b) = STANDARD_PALETTE[n as usize];
1032 format!("#{:02x}{:02x}{:02x}", r, g, b)
1033 } else {
1034 String::new()
1035 }
1036 }
1037 ColorType::EightBit => {
1038 if let Some(n) = c.number {
1039 let [r, g, b] = EIGHT_BIT_PALETTE[n as usize];
1040 format!("#{:02x}{:02x}{:02x}", r, g, b)
1041 } else {
1042 String::new()
1043 }
1044 }
1045 ColorType::TrueColor => {
1046 if let Some((r, g, b)) = c.triplet {
1047 format!("#{:02x}{:02x}{:02x}", r, g, b)
1048 } else {
1049 String::new()
1050 }
1051 }
1052 }
1053}
1054
1055fn color_to_name(c: &Color) -> Option<&'static str> {
1057 match c.color_type {
1058 ColorType::Standard => {
1059 if let Some(n) = c.number {
1060 Some(STANDARD_COLOR_NAMES[n as usize])
1061 } else {
1062 None
1063 }
1064 }
1065 _ => None,
1066 }
1067}
1068
1069#[derive(Debug, Clone)]
1079pub struct StyleStack {
1080 stack: Vec<Style>,
1081 tag_names: Vec<String>,
1084 default_style: Style,
1085}
1086
1087impl StyleStack {
1088 pub fn new(default_style: Style) -> Self {
1090 Self {
1091 stack: Vec::new(),
1092 tag_names: Vec::new(),
1093 default_style,
1094 }
1095 }
1096
1097 pub fn current(&self) -> Style {
1099 let mut combined = self.default_style.clone();
1100 for s in &self.stack {
1101 combined = combined.combine(s);
1102 }
1103 combined
1104 }
1105
1106 pub fn push(&mut self, style: Style) {
1108 self.tag_names.push(String::new());
1109 self.stack.push(style);
1110 }
1111
1112 pub fn push_named(&mut self, name: String, style: Style) {
1114 self.tag_names.push(name);
1115 self.stack.push(style);
1116 }
1117
1118 pub fn pop(&mut self) -> Option<Style> {
1120 self.tag_names.pop();
1121 self.stack.pop()
1122 }
1123
1124 pub fn pop_to(&mut self, name: &str) {
1130 if let Some(pos) = self.tag_names.iter().rposition(|n| n == name) {
1131 self.stack.truncate(pos);
1132 self.tag_names.truncate(pos);
1133 } else {
1134 self.stack.pop();
1136 self.tag_names.pop();
1137 }
1138 }
1139
1140 pub fn len(&self) -> usize {
1142 self.stack.len()
1143 }
1144
1145 pub fn is_empty(&self) -> bool {
1147 self.stack.is_empty()
1148 }
1149}
1150
1151#[cfg(test)]
1152mod tests {
1153 use super::*;
1154
1155 #[test]
1156 fn test_style_parse() {
1157 let s = Style::from_str("bold red");
1158 assert_eq!(s.get_bold(), Some(true));
1159 assert!(s.color.is_some());
1160 }
1161
1162 #[test]
1163 fn test_style_combine() {
1164 let base = Style::from_str("red");
1165 let over = Style::from_str("bold");
1166 let combined = base.combine(&over);
1167 assert_eq!(combined.get_bold(), Some(true));
1168 assert!(combined.color.is_some());
1169 }
1170
1171 #[test]
1172 fn test_ansi_output() {
1173 let s = Style::new().color(Color::parse("red").unwrap()).bold(true);
1174 let ansi = s.to_ansi();
1175 assert!(ansi.contains("31")); assert!(ansi.contains("1")); }
1178
1179 #[test]
1180 fn test_chain() {
1181 let a = Style::new().bold(true);
1182 let b = Style::new()
1183 .color(Color::parse("red").unwrap())
1184 .italic(true);
1185 let chained = a.chain(&b);
1186 assert_eq!(chained.get_bold(), Some(true));
1187 assert!(chained.attributes.get(Attributes::ITALIC));
1188 assert!(chained.set_attributes & Attributes::ITALIC != 0);
1189 assert!(chained.color.is_some());
1190 }
1191
1192 #[test]
1193 fn test_chain_precedence() {
1194 let a = Style::new().bold(true).color(Color::parse("red").unwrap());
1195 let b = Style::new()
1196 .bold(false)
1197 .color(Color::parse("blue").unwrap());
1198 let chained = a.chain(&b);
1199 assert_eq!(chained.get_bold(), Some(true));
1202 let c = chained.color.as_ref().unwrap();
1203 let name = color_to_name(c);
1204 assert_eq!(name, Some("red"));
1205 }
1206
1207 #[test]
1208 fn test_copy() {
1209 let s = Style::new().bold(true).color(Color::parse("red").unwrap());
1210 let c = s.copy();
1211 assert_eq!(s, c);
1212 }
1213
1214 #[test]
1215 fn test_clear_meta_and_links() {
1216 let mut s = Style::new().link("https://example.com");
1217 s.meta = Some(vec![1, 2, 3]);
1218 s.clear_meta_and_links();
1219 assert!(s.link.is_none());
1220 assert!(s.meta.is_none());
1221 }
1222
1223 #[test]
1224 fn test_from_color() {
1225 let s = Style::from_color(Color::parse("red").unwrap());
1226 assert!(s.color.is_some());
1227 assert!(s.bgcolor.is_none());
1228 }
1229
1230 #[test]
1231 fn test_from_meta() {
1232 let s = Style::from_meta(vec![10, 20, 30]);
1233 assert_eq!(s.meta(), Some(&vec![10, 20, 30]));
1234 }
1235
1236 #[test]
1237 fn test_get_html_style() {
1238 let s = Style::new()
1239 .color(Color::parse("red").unwrap())
1240 .bold(true)
1241 .italic(true);
1242 let css = s.get_html_style(None);
1243 assert!(css.contains("color:"));
1244 assert!(css.contains("font-weight: bold"));
1245 assert!(css.contains("font-style: italic"));
1246 }
1247
1248 #[test]
1249 fn test_get_html_style_underline_strike() {
1250 let s = Style::new()
1251 .color(Color::parse("red").unwrap())
1252 .underline(true)
1253 .strike(true);
1254 let css = s.get_html_style(None);
1255 assert!(css.contains("text-decoration:"));
1256 assert!(css.contains("underline"));
1257 assert!(css.contains("line-through"));
1258 }
1259
1260 #[test]
1261 fn test_get_html_style_null() {
1262 let s = Style::null();
1263 let css = s.get_html_style(None);
1264 assert!(css.is_empty());
1265 }
1266
1267 #[test]
1268 fn test_normalize() {
1269 let s = Style::new().bold(true).italic(false);
1270 let n = s.normalize();
1271 assert_eq!(n.get_bold(), Some(true));
1272 assert!(n.set_attributes & Attributes::ITALIC == 0);
1274 }
1275
1276 #[test]
1277 fn test_pick_first() {
1278 let s = Style::new().color(Color::parse("red").unwrap());
1279 assert_eq!(s.pick_first(), Some("red"));
1280 }
1281
1282 #[test]
1283 fn test_pick_first_fallback() {
1284 let s = Style::new().bgcolor(Color::parse("blue").unwrap());
1285 assert_eq!(s.pick_first(), Some("blue"));
1286 }
1287
1288 #[test]
1289 fn test_pick_first_none() {
1290 let s = Style::new();
1291 assert_eq!(s.pick_first(), None);
1292 }
1293
1294 #[test]
1295 fn test_render() {
1296 let s = Style::new().bold(true).color(Color::parse("red").unwrap());
1297 let rendered = s.render("hello");
1298 assert!(rendered.starts_with("\x1b["));
1299 assert!(rendered.contains("hello"));
1300 assert!(rendered.ends_with("\x1b[0m"));
1301 }
1302
1303 #[test]
1304 fn test_test_with_text() {
1305 let s = Style::new().bold(true);
1306 let out = s.test(Some("custom"));
1307 assert!(out.contains("custom"));
1308 }
1309
1310 #[test]
1311 fn test_test_default() {
1312 let s = Style::new().bold(true);
1313 let out = s.test(None);
1314 assert!(out.contains("Lorem ipsum"));
1315 }
1316
1317 #[test]
1318 fn test_update_link() {
1319 let mut s = Style::new();
1320 s.update_link(Some("https://example.com".into()));
1321 assert!(s.link.is_some());
1322 let first_id = s.link_id;
1323 s.update_link(None);
1324 assert!(s.link.is_none());
1325 assert_eq!(s.link_id, first_id);
1326 }
1327
1328 #[test]
1329 fn test_link_id() {
1330 let s = Style::new().link("https://example.com");
1331 assert!(s.link_id() > 0);
1332 }
1333
1334 #[test]
1335 fn test_meta_methods() {
1336 let mut s = Style::new();
1337 s.set_meta(Some(vec![1, 2, 3]));
1338 assert_eq!(s.meta(), Some(&vec![1, 2, 3]));
1339 if let Some(m) = s.meta_mut() {
1340 m.push(4);
1341 }
1342 assert_eq!(s.meta(), Some(&vec![1, 2, 3, 4]));
1343 }
1344
1345 #[test]
1346 fn test_on() {
1347 let s = Style::new().on(Color::parse("red").unwrap());
1348 assert!(s.bgcolor.is_some());
1349 let b = Color::parse("red").unwrap();
1350 assert_eq!(s.bgcolor.unwrap(), b);
1351 }
1352
1353 #[test]
1354 fn test_references() {
1355 let s = Style::new()
1356 .color(Color::parse("red").unwrap())
1357 .bgcolor(Color::parse("blue").unwrap());
1358 assert!(s.color_ref().is_some());
1359 assert!(s.bgcolor_ref().is_some());
1360 }
1361
1362 #[test]
1363 fn test_color_to_css_hex() {
1364 let c = Color::parse("red").unwrap();
1365 let hex = color_to_css_hex(&c);
1366 assert_eq!(hex, "#800000"); }
1368
1369 #[test]
1370 fn test_color_to_css_hex_truecolor() {
1371 let c = Color::from_rgb(255, 0, 128);
1372 let hex = color_to_css_hex(&c);
1373 assert_eq!(hex, "#ff0080");
1374 }
1375
1376 #[test]
1377 fn test_null_style_constant() {
1378 assert!(NULL_STYLE.is_null());
1379 assert!(NULL_STYLE.color.is_none());
1380 assert!(NULL_STYLE.bgcolor.is_none());
1381 assert_eq!(NULL_STYLE.set_attributes, 0);
1382 assert_eq!(Style::null(), NULL_STYLE);
1384 }
1385
1386 #[test]
1387 fn test_get_html_style_blink() {
1388 let s = Style::new().blink(true);
1389 let css = s.get_html_style(None);
1390 assert!(css.contains("blink"));
1391 }
1392
1393 #[test]
1394 fn test_get_html_style_reverse() {
1395 let s = Style::new().reverse(true);
1396 let css = s.get_html_style(None);
1397 assert!(css.contains("invert(100%)"));
1398 }
1399
1400 #[test]
1401 fn test_get_html_style_conceal() {
1402 let s = Style::new().conceal(true);
1403 let css = s.get_html_style(None);
1404 assert!(css.contains("visibility: hidden"));
1405 }
1406
1407 #[test]
1408 fn test_static_attributes() {
1409 assert!(!STYLE_ATTRIBUTES.is_empty());
1410 let names: Vec<&str> = STYLE_ATTRIBUTES.iter().map(|(n, _)| *n).collect();
1411 assert!(names.contains(&"bold"));
1412 assert!(names.contains(&"italic"));
1413 assert!(names.contains(&"underline"));
1414 assert!(!names.contains(&"notexist"));
1415 }
1416
1417 #[test]
1422 fn test_not_bold_with_space() {
1423 let s = Style::from_str("not bold");
1425 assert_eq!(s.get_bold(), Some(false));
1426 }
1427
1428 #[test]
1429 fn test_not_italic_with_space() {
1430 let s = Style::from_str("not italic");
1431 assert_eq!(s.get_italic(), Some(false));
1432 }
1433
1434 #[test]
1435 fn test_not_underline_with_space() {
1436 let s = Style::from_str("not underline");
1437 assert!(s.set_attributes & Attributes::UNDERLINE != 0);
1439 assert!(!s.attributes.get(Attributes::UNDERLINE));
1440 }
1441
1442 #[test]
1443 fn test_bang_negation_bold() {
1444 let s = Style::from_str("!bold");
1445 assert_eq!(s.get_bold(), Some(false));
1446 }
1447
1448 #[test]
1449 fn test_nobold_prefix() {
1450 let s = Style::from_str("nobold");
1451 assert_eq!(s.get_bold(), Some(false));
1452 }
1453
1454 #[test]
1455 fn test_not_bold_red() {
1456 let s = Style::from_str("not bold red");
1458 assert_eq!(s.get_bold(), Some(false));
1459 assert!(s.color.is_some());
1460 }
1461
1462 #[test]
1463 fn test_not_multiple() {
1464 let s = Style::from_str("not bold not italic");
1466 assert_eq!(s.get_bold(), Some(false));
1467 assert_eq!(s.get_italic(), Some(false));
1468 }
1469
1470 #[test]
1471 fn test_on_next_color() {
1472 let s = Style::from_str("on red");
1474 assert!(s.bgcolor.is_some());
1475 }
1476}