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, Attributes::DIM, Attributes::ITALIC,
105 Attributes::UNDERLINE, Attributes::BLINK, Attributes::REVERSE,
106 Attributes::STRIKE, Attributes::UNDERLINE2, Attributes::FRAME,
107 Attributes::ENCIRCLE, Attributes::OVERLINE, Attributes::BLINK2,
108 Attributes::CONCEAL,
109];
110
111pub const STYLE_ATTRIBUTES: &[(&str, u32)] = &[
113 ("bold", Attributes::BOLD),
114 ("dim", Attributes::DIM),
115 ("italic", Attributes::ITALIC),
116 ("underline", Attributes::UNDERLINE),
117 ("blink", Attributes::BLINK),
118 ("reverse", Attributes::REVERSE),
119 ("strike", Attributes::STRIKE),
120 ("underline2", Attributes::UNDERLINE2),
121 ("frame", Attributes::FRAME),
122 ("encircle", Attributes::ENCIRCLE),
123 ("overline", Attributes::OVERLINE),
124 ("blink2", Attributes::BLINK2),
125 ("conceal", Attributes::CONCEAL),
126];
127
128impl fmt::Display for Attributes {
129 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130 let mut parts: Vec<&str> = Vec::new();
131 if self.get(Self::BOLD) { parts.push("bold"); }
132 if self.get(Self::DIM) { parts.push("dim"); }
133 if self.get(Self::ITALIC) { parts.push("italic"); }
134 if self.get(Self::UNDERLINE) { parts.push("underline"); }
135 if self.get(Self::BLINK) { parts.push("blink"); }
136 if self.get(Self::REVERSE) { parts.push("reverse"); }
137 if self.get(Self::CONCEAL) { parts.push("conceal"); }
138 if self.get(Self::STRIKE) { parts.push("strike"); }
139 if self.get(Self::OVERLINE) { parts.push("overline"); }
140 if parts.is_empty() {
141 write!(f, "none")
142 } else {
143 write!(f, "{}", parts.join(" "))
144 }
145 }
146}
147
148#[derive(Debug, Clone)]
158pub struct Style {
159 pub(crate) color: Option<Color>,
160 pub(crate) bgcolor: Option<Color>,
161 pub(crate) attributes: Attributes,
162 pub(crate) set_attributes: u32,
164 pub(crate) link: Option<String>,
165 pub(crate) link_id: u32,
166 pub(crate) is_null: bool,
167 pub(crate) meta: Option<Vec<u8>>,
169}
170
171impl Style {
172 pub fn null() -> Self {
176 Self {
177 color: None,
178 bgcolor: None,
179 attributes: Attributes::empty(),
180 set_attributes: 0,
181 link: None,
182 link_id: 0,
183 is_null: true,
184 meta: None,
185 }
186 }
187
188 pub fn new() -> Self {
190 Self {
191 color: None,
192 bgcolor: None,
193 attributes: Attributes::empty(),
194 set_attributes: 0,
195 link: None,
196 link_id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
197 is_null: false,
198 meta: None,
199 }
200 }
201
202 pub fn color(mut self, color: impl Into<Option<Color>>) -> Self {
204 self.color = color.into();
205 self
206 }
207
208 pub fn bgcolor(mut self, bgcolor: impl Into<Option<Color>>) -> Self {
210 self.bgcolor = bgcolor.into();
211 self
212 }
213
214 pub fn bold(mut self, value: bool) -> Self {
216 self.set_attributes |= Attributes::BOLD;
217 self.attributes.set(Attributes::BOLD, value);
218 self
219 }
220
221 pub fn dim(mut self, value: bool) -> Self {
223 self.set_attributes |= Attributes::DIM;
224 self.attributes.set(Attributes::DIM, value);
225 self
226 }
227
228 pub fn italic(mut self, value: bool) -> Self {
230 self.set_attributes |= Attributes::ITALIC;
231 self.attributes.set(Attributes::ITALIC, value);
232 self
233 }
234
235 pub fn underline(mut self, value: bool) -> Self {
237 self.set_attributes |= Attributes::UNDERLINE;
238 self.attributes.set(Attributes::UNDERLINE, value);
239 self
240 }
241
242 pub fn blink(mut self, value: bool) -> Self {
244 self.set_attributes |= Attributes::BLINK;
245 self.attributes.set(Attributes::BLINK, value);
246 self
247 }
248
249 pub fn reverse(mut self, value: bool) -> Self {
251 self.set_attributes |= Attributes::REVERSE;
252 self.attributes.set(Attributes::REVERSE, value);
253 self
254 }
255
256 pub fn strike(mut self, value: bool) -> Self {
258 self.set_attributes |= Attributes::STRIKE;
259 self.attributes.set(Attributes::STRIKE, value);
260 self
261 }
262
263 pub fn blink2(mut self, value: bool) -> Self {
265 self.set_attributes |= Attributes::BLINK2;
266 self.attributes.set(Attributes::BLINK2, value);
267 self
268 }
269
270 pub fn conceal(mut self, value: bool) -> Self {
272 self.set_attributes |= Attributes::CONCEAL;
273 self.attributes.set(Attributes::CONCEAL, value);
274 self
275 }
276
277 pub fn underline2(mut self, value: bool) -> Self {
279 self.set_attributes |= Attributes::UNDERLINE2;
280 self.attributes.set(Attributes::UNDERLINE2, value);
281 self
282 }
283
284 pub fn frame(mut self, value: bool) -> Self {
286 self.set_attributes |= Attributes::FRAME;
287 self.attributes.set(Attributes::FRAME, value);
288 self
289 }
290
291 pub fn encircle(mut self, value: bool) -> Self {
293 self.set_attributes |= Attributes::ENCIRCLE;
294 self.attributes.set(Attributes::ENCIRCLE, value);
295 self
296 }
297
298 pub fn overline(mut self, value: bool) -> Self {
300 self.set_attributes |= Attributes::OVERLINE;
301 self.attributes.set(Attributes::OVERLINE, value);
302 self
303 }
304
305 pub fn without_color(&self) -> Self {
307 let mut s = self.clone();
308 s.color = None;
309 s.bgcolor = None;
310 s
311 }
312
313 pub fn background_style(&self) -> Self {
316 let mut s = Self::new();
317 s.bgcolor = self.color.clone();
318 s
319 }
320
321 pub fn transparent_background(&self) -> bool {
323 self.bgcolor.is_none()
324 }
325
326 pub fn link(mut self, url: impl Into<String>) -> Self {
328 self.link = Some(url.into());
329 self.link_id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
330 self
331 }
332
333 pub fn from_str(definition: &str) -> Self {
335 let mut style = Self::new();
336 for part in definition.split_whitespace() {
337 match part {
338 "bold" | "b" => { style.set_attributes |= Attributes::BOLD; style.attributes.set(Attributes::BOLD, true); }
339 "dim" | "d" => { style.set_attributes |= Attributes::DIM; style.attributes.set(Attributes::DIM, true); }
340 "italic" | "i" => { style.set_attributes |= Attributes::ITALIC; style.attributes.set(Attributes::ITALIC, true); }
341 "underline" | "u" => { style.set_attributes |= Attributes::UNDERLINE; style.attributes.set(Attributes::UNDERLINE, true); }
342 "blink" => { style.set_attributes |= Attributes::BLINK; style.attributes.set(Attributes::BLINK, true); }
343 "reverse" | "r" => { style.set_attributes |= Attributes::REVERSE; style.attributes.set(Attributes::REVERSE, true); }
344 "strike" | "s" => { style.set_attributes |= Attributes::STRIKE; style.attributes.set(Attributes::STRIKE, true); }
345 "not bold" | "!bold" | "nobold" => { style.set_attributes |= Attributes::BOLD; style.attributes.set(Attributes::BOLD, false); }
346 "not italic" | "!italic" | "noitalic" => { style.set_attributes |= Attributes::ITALIC; style.attributes.set(Attributes::ITALIC, false); }
347 "not underline" | "!underline" | "nounderline" => { style.set_attributes |= Attributes::UNDERLINE; style.attributes.set(Attributes::UNDERLINE, false); }
348 "none" | "default" => {}
349 "on" => { }
350 part if part.starts_with("on ") => {
351 if let Ok(c) = Color::parse(&part[3..]) {
352 style.bgcolor = Some(c);
353 }
354 }
355 part if part.starts_with("link=") => {
356 style.link = Some(part[5..].to_string());
357 }
358 part => {
359 if let Ok(c) = Color::parse(part) {
361 if style.bgcolor.is_some() && style.color.is_none() {
362 } else {
364 style.color = Some(c);
365 }
366 }
367 }
368 }
369 }
370 style
371 }
372
373 pub fn is_null(&self) -> bool {
377 self.is_null
378 }
379
380 pub fn is_plain(&self) -> bool {
382 self.color.is_none()
383 && self.bgcolor.is_none()
384 && self.set_attributes == 0
385 && self.link.is_none()
386 }
387
388 pub fn get_bold(&self) -> Option<bool> {
390 if self.set_attributes & Attributes::BOLD != 0 {
391 Some(self.attributes.get(Attributes::BOLD))
392 } else {
393 None
394 }
395 }
396
397 pub fn get_italic(&self) -> Option<bool> {
399 if self.set_attributes & Attributes::ITALIC != 0 {
400 Some(self.attributes.get(Attributes::ITALIC))
401 } else {
402 None
403 }
404 }
405
406 pub fn combine(&self, other: &Style) -> Style {
408 if other.is_null {
409 return self.clone();
410 }
411 if self.is_null {
412 return other.clone();
413 }
414
415 let mut combined = self.clone();
416 if other.color.is_some() {
417 combined.color = other.color.clone();
418 }
419 if other.bgcolor.is_some() {
420 combined.bgcolor = other.bgcolor.clone();
421 }
422 for &bit in STYLE_BITS {
424 if other.set_attributes & bit != 0 {
425 combined.set_attributes |= bit;
426 combined.attributes.set(bit, other.attributes.get(bit));
427 }
428 }
429 if other.link.is_some() {
430 combined.link = other.link.clone();
431 combined.link_id = other.link_id;
432 }
433 if other.meta.is_some() {
434 combined.meta = other.meta.clone();
435 }
436 combined.is_null = false;
437 combined
438 }
439
440 pub fn to_ansi(&self) -> String {
442 if self.is_null {
443 return String::new();
444 }
445 let mut codes: Vec<String> = Vec::new();
446
447 if let Some(ref c) = self.color {
449 match c.color_type {
450 crate::color::ColorType::Default => codes.push("39".into()),
451 crate::color::ColorType::Standard => {
452 if let Some(n) = c.number {
453 if n < 8 {
454 codes.push((30 + n).to_string());
455 } else {
456 codes.push((82 + n).to_string()); }
458 }
459 }
460 crate::color::ColorType::EightBit => {
461 if let Some(n) = c.number {
462 codes.push(format!("38;5;{n}"));
463 }
464 }
465 crate::color::ColorType::TrueColor => {
466 if let Some((r, g, b)) = c.triplet {
467 codes.push(format!("38;2;{r};{g};{b}"));
468 }
469 }
470 }
471 }
472
473 if let Some(ref c) = self.bgcolor {
475 match c.color_type {
476 crate::color::ColorType::Default => codes.push("49".into()),
477 crate::color::ColorType::Standard => {
478 if let Some(n) = c.number {
479 if n < 8 {
480 codes.push((40 + n).to_string());
481 } else {
482 codes.push((92 + n).to_string()); }
484 }
485 }
486 crate::color::ColorType::EightBit => {
487 if let Some(n) = c.number {
488 codes.push(format!("48;5;{n}"));
489 }
490 }
491 crate::color::ColorType::TrueColor => {
492 if let Some((r, g, b)) = c.triplet {
493 codes.push(format!("48;2;{r};{g};{b}"));
494 }
495 }
496 }
497 }
498
499 if self.set_attributes & Attributes::BOLD != 0 {
501 codes.push(if self.attributes.get(Attributes::BOLD) { "1" } else { "22" }.into());
502 }
503 if self.set_attributes & Attributes::DIM != 0 {
504 codes.push(if self.attributes.get(Attributes::DIM) { "2" } else { "22" }.into());
505 }
506 if self.set_attributes & Attributes::ITALIC != 0 {
507 codes.push(if self.attributes.get(Attributes::ITALIC) { "3" } else { "23" }.into());
508 }
509 if self.set_attributes & Attributes::UNDERLINE != 0 {
510 codes.push(if self.attributes.get(Attributes::UNDERLINE) { "4" } else { "24" }.into());
511 }
512 if self.set_attributes & Attributes::BLINK != 0 {
513 codes.push(if self.attributes.get(Attributes::BLINK) { "5" } else { "25" }.into());
514 }
515 if self.set_attributes & Attributes::REVERSE != 0 {
516 codes.push(if self.attributes.get(Attributes::REVERSE) { "7" } else { "27" }.into());
517 }
518 if self.set_attributes & Attributes::CONCEAL != 0 {
519 codes.push(if self.attributes.get(Attributes::CONCEAL) { "8" } else { "28" }.into());
520 }
521 if self.set_attributes & Attributes::STRIKE != 0 {
522 codes.push(if self.attributes.get(Attributes::STRIKE) { "9" } else { "29" }.into());
523 }
524 if self.set_attributes & Attributes::CONCEAL != 0 {
525 codes.push(if self.attributes.get(Attributes::CONCEAL) { "8" } else { "28" }.into());
526 }
527 if self.set_attributes & Attributes::UNDERLINE2 != 0 {
528 codes.push(if self.attributes.get(Attributes::UNDERLINE2) { "21" } else { "24" }.into());
529 }
530 if self.set_attributes & Attributes::BLINK2 != 0 {
531 codes.push(if self.attributes.get(Attributes::BLINK2) { "6" } else { "25" }.into());
532 }
533 if self.set_attributes & Attributes::FRAME != 0 {
534 codes.push(if self.attributes.get(Attributes::FRAME) { "51" } else { "54" }.into());
535 }
536 if self.set_attributes & Attributes::ENCIRCLE != 0 {
537 codes.push(if self.attributes.get(Attributes::ENCIRCLE) { "52" } else { "54" }.into());
538 }
539 if self.set_attributes & Attributes::OVERLINE != 0 {
540 codes.push(if self.attributes.get(Attributes::OVERLINE) { "53" } else { "55" }.into());
541 }
542
543 if codes.is_empty() {
544 String::new()
545 } else {
546 format!("\x1b[{}m", codes.join(";"))
547 }
548 }
549
550 pub fn reset_ansi(&self) -> &'static str {
552 "\x1b[0m"
553 }
554
555 pub fn chain(&self, other: &Style) -> Style {
560 let mut result = Style::new();
561 result.color = self.color.clone().or_else(|| other.color.clone());
562 result.bgcolor = self.bgcolor.clone().or_else(|| other.bgcolor.clone());
563 result.link = self.link.clone().or_else(|| other.link.clone());
564 result.meta = self.meta.clone().or_else(|| other.meta.clone());
565 for &bit in STYLE_BITS {
566 if self.set_attributes & bit != 0 {
567 result.set_attributes |= bit;
568 result.attributes.set(bit, self.attributes.get(bit));
569 } else if other.set_attributes & bit != 0 {
570 result.set_attributes |= bit;
571 result.attributes.set(bit, other.attributes.get(bit));
572 }
573 }
574 result
575 }
576
577 pub fn copy(&self) -> Style {
581 self.clone()
582 }
583
584 pub fn clear_meta_and_links(&mut self) -> &mut Self {
586 self.meta = None;
587 self.link = None;
588 self
589 }
590
591 pub fn from_color(color: Color) -> Self {
595 Self::new().color(color)
596 }
597
598 pub fn from_meta(meta: Vec<u8>) -> Self {
600 let mut s = Self::new();
601 s.meta = Some(meta);
602 s
603 }
604
605 pub fn get_html_style(&self, _theme: Option<&crate::export::ExportTheme>) -> String {
609 if self.is_null {
610 return String::new();
611 }
612 let mut parts: Vec<String> = Vec::new();
613
614 if let Some(ref c) = self.color {
615 let hex = color_to_css_hex(c);
616 if !hex.is_empty() {
617 parts.push(format!("color: {}", hex));
618 }
619 }
620 if let Some(ref c) = self.bgcolor {
621 let hex = color_to_css_hex(c);
622 if !hex.is_empty() {
623 parts.push(format!("background-color: {}", hex));
624 }
625 }
626 if self.set_attributes & Attributes::BOLD != 0 && self.attributes.get(Attributes::BOLD) {
627 parts.push("font-weight: bold".into());
628 }
629 if self.set_attributes & Attributes::ITALIC != 0 && self.attributes.get(Attributes::ITALIC) {
630 parts.push("font-style: italic".into());
631 }
632
633 let mut decor: Vec<&str> = Vec::new();
635 if self.set_attributes & Attributes::UNDERLINE != 0
636 && self.attributes.get(Attributes::UNDERLINE)
637 {
638 decor.push("underline");
639 }
640 if self.set_attributes & Attributes::UNDERLINE2 != 0
641 && self.attributes.get(Attributes::UNDERLINE2)
642 {
643 decor.push("underline");
644 }
645 if self.set_attributes & Attributes::STRIKE != 0
646 && self.attributes.get(Attributes::STRIKE)
647 {
648 decor.push("line-through");
649 }
650 if !decor.is_empty() {
651 parts.push(format!("text-decoration: {}", decor.join(" ")));
652 }
653
654 if parts.is_empty() {
655 String::new()
656 } else {
657 parts.join("; ")
658 }
659 }
660
661 pub fn normalize(&self) -> Style {
667 let mut s = Style::new();
668 s.color = self.color.clone();
669 s.bgcolor = self.bgcolor.clone();
670 s.link = self.link.clone();
671 s.link_id = self.link_id;
672 s.meta = self.meta.clone();
673 for &bit in STYLE_BITS {
674 if self.set_attributes & bit != 0 && self.attributes.get(bit) {
675 s.set_attributes |= bit;
676 s.attributes.set(bit, true);
677 }
678 }
679 s
680 }
681
682 pub fn pick_first(&self) -> Option<&'static str> {
687 if let Some(ref c) = self.color {
688 if let Some(name) = color_to_name(c) {
689 return Some(name);
690 }
691 }
692 if let Some(ref c) = self.bgcolor {
693 if let Some(name) = color_to_name(c) {
694 return Some(name);
695 }
696 }
697 None
698 }
699
700 pub fn render(&self, text: &str) -> String {
702 format!("{}{}{}", self.to_ansi(), text, self.reset_ansi())
703 }
704
705 pub fn test(&self, text: Option<&str>) -> String {
707 let t = text.unwrap_or("Lorem ipsum");
708 self.render(t)
709 }
710
711 pub fn update_link(&mut self, url: Option<String>) -> &mut Self {
713 self.link = url;
714 if self.link.is_some() {
715 self.link_id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
716 }
717 self
718 }
719
720 pub fn meta(&self) -> Option<&Vec<u8>> {
724 self.meta.as_ref()
725 }
726
727 pub fn meta_mut(&mut self) -> Option<&mut Vec<u8>> {
729 self.meta.as_mut()
730 }
731
732 pub fn set_meta(&mut self, meta: Option<Vec<u8>>) -> &mut Self {
734 self.meta = meta;
735 self
736 }
737
738 pub fn link_id(&self) -> u32 {
740 self.link_id
741 }
742
743 pub fn on(self, color: impl Into<Option<Color>>) -> Self {
745 self.bgcolor(color)
746 }
747
748 pub fn color_ref(&self) -> Option<&Color> {
750 self.color.as_ref()
751 }
752
753 pub fn bgcolor_ref(&self) -> Option<&Color> {
755 self.bgcolor.as_ref()
756 }
757}
758
759impl Default for Style {
760 fn default() -> Self {
761 Self::new()
762 }
763}
764
765impl PartialEq for Style {
766 fn eq(&self, other: &Self) -> bool {
767 self.color == other.color
768 && self.bgcolor == other.bgcolor
769 && self.attributes == other.attributes
770 && self.set_attributes == other.set_attributes
771 && self.link == other.link
772 }
773}
774
775impl Eq for Style {}
776
777impl Hash for Style {
778 fn hash<H: Hasher>(&self, state: &mut H) {
779 self.color.hash(state);
780 self.bgcolor.hash(state);
781 self.attributes.hash(state);
782 self.set_attributes.hash(state);
783 self.link.hash(state);
784 }
785}
786
787impl fmt::Display for Style {
788 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
789 if self.is_null {
790 return write!(f, "null");
791 }
792 let mut parts: Vec<String> = Vec::new();
793 if let Some(ref c) = self.color {
794 parts.push(c.to_string());
795 }
796 if let Some(ref c) = self.bgcolor {
797 parts.push(format!("on {}", c));
798 }
799 let attrs = self.attributes.to_string();
800 if attrs != "none" {
801 parts.push(attrs);
802 }
803 if parts.is_empty() {
804 write!(f, "none")
805 } else {
806 write!(f, "{}", parts.join(" "))
807 }
808 }
809}
810
811pub type StyleType = Style;
813
814fn color_to_css_hex(c: &Color) -> String {
818 match c.color_type {
819 ColorType::Default => String::new(),
820 ColorType::Standard => {
821 if let Some(n) = c.number {
822 let (r, g, b) = STANDARD_PALETTE[n as usize];
823 format!("#{:02x}{:02x}{:02x}", r, g, b)
824 } else {
825 String::new()
826 }
827 }
828 ColorType::EightBit => {
829 if let Some(n) = c.number {
830 let [r, g, b] = EIGHT_BIT_PALETTE[n as usize];
831 format!("#{:02x}{:02x}{:02x}", r, g, b)
832 } else {
833 String::new()
834 }
835 }
836 ColorType::TrueColor => {
837 if let Some((r, g, b)) = c.triplet {
838 format!("#{:02x}{:02x}{:02x}", r, g, b)
839 } else {
840 String::new()
841 }
842 }
843 }
844}
845
846fn color_to_name(c: &Color) -> Option<&'static str> {
848 match c.color_type {
849 ColorType::Standard => {
850 if let Some(n) = c.number {
851 Some(STANDARD_COLOR_NAMES[n as usize])
852 } else {
853 None
854 }
855 }
856 _ => None,
857 }
858}
859
860#[derive(Debug, Clone)]
866pub struct StyleStack {
867 stack: Vec<Style>,
868 default_style: Style,
869}
870
871impl StyleStack {
872 pub fn new(default_style: Style) -> Self {
874 Self {
875 stack: Vec::new(),
876 default_style,
877 }
878 }
879
880 pub fn current(&self) -> Style {
882 let mut combined = self.default_style.clone();
883 for s in &self.stack {
884 combined = combined.combine(s);
885 }
886 combined
887 }
888
889 pub fn push(&mut self, style: Style) {
891 self.stack.push(style);
892 }
893
894 pub fn pop(&mut self) -> Option<Style> {
896 self.stack.pop()
897 }
898
899 pub fn len(&self) -> usize {
901 self.stack.len()
902 }
903
904 pub fn is_empty(&self) -> bool {
906 self.stack.is_empty()
907 }
908}
909
910#[cfg(test)]
911mod tests {
912 use super::*;
913
914 #[test]
915 fn test_style_parse() {
916 let s = Style::from_str("bold red");
917 assert_eq!(s.get_bold(), Some(true));
918 assert!(s.color.is_some());
919 }
920
921 #[test]
922 fn test_style_combine() {
923 let base = Style::from_str("red");
924 let over = Style::from_str("bold");
925 let combined = base.combine(&over);
926 assert_eq!(combined.get_bold(), Some(true));
927 assert!(combined.color.is_some());
928 }
929
930 #[test]
931 fn test_ansi_output() {
932 let s = Style::new().color(Color::parse("red").unwrap()).bold(true);
933 let ansi = s.to_ansi();
934 assert!(ansi.contains("31")); assert!(ansi.contains("1")); }
937
938 #[test]
939 fn test_chain() {
940 let a = Style::new().bold(true);
941 let b = Style::new().color(Color::parse("red").unwrap()).italic(true);
942 let chained = a.chain(&b);
943 assert_eq!(chained.get_bold(), Some(true));
944 assert!(chained.attributes.get(Attributes::ITALIC));
945 assert!(chained.set_attributes & Attributes::ITALIC != 0);
946 assert!(chained.color.is_some());
947 }
948
949 #[test]
950 fn test_chain_precedence() {
951 let a = Style::new().bold(true).color(Color::parse("red").unwrap());
952 let b = Style::new().bold(false).color(Color::parse("blue").unwrap());
953 let chained = a.chain(&b);
954 assert_eq!(chained.get_bold(), Some(true));
957 let c = chained.color.as_ref().unwrap();
958 let name = color_to_name(c);
959 assert_eq!(name, Some("red"));
960 }
961
962 #[test]
963 fn test_copy() {
964 let s = Style::new().bold(true).color(Color::parse("red").unwrap());
965 let c = s.copy();
966 assert_eq!(s, c);
967 }
968
969 #[test]
970 fn test_clear_meta_and_links() {
971 let mut s = Style::new().link("https://example.com");
972 s.meta = Some(vec![1, 2, 3]);
973 s.clear_meta_and_links();
974 assert!(s.link.is_none());
975 assert!(s.meta.is_none());
976 }
977
978 #[test]
979 fn test_from_color() {
980 let s = Style::from_color(Color::parse("red").unwrap());
981 assert!(s.color.is_some());
982 assert!(s.bgcolor.is_none());
983 }
984
985 #[test]
986 fn test_from_meta() {
987 let s = Style::from_meta(vec![10, 20, 30]);
988 assert_eq!(s.meta(), Some(&vec![10, 20, 30]));
989 }
990
991 #[test]
992 fn test_get_html_style() {
993 let s = Style::new()
994 .color(Color::parse("red").unwrap())
995 .bold(true)
996 .italic(true);
997 let css = s.get_html_style(None);
998 assert!(css.contains("color:"));
999 assert!(css.contains("font-weight: bold"));
1000 assert!(css.contains("font-style: italic"));
1001 }
1002
1003 #[test]
1004 fn test_get_html_style_underline_strike() {
1005 let s = Style::new()
1006 .color(Color::parse("red").unwrap())
1007 .underline(true)
1008 .strike(true);
1009 let css = s.get_html_style(None);
1010 assert!(css.contains("text-decoration:"));
1011 assert!(css.contains("underline"));
1012 assert!(css.contains("line-through"));
1013 }
1014
1015 #[test]
1016 fn test_get_html_style_null() {
1017 let s = Style::null();
1018 let css = s.get_html_style(None);
1019 assert!(css.is_empty());
1020 }
1021
1022 #[test]
1023 fn test_normalize() {
1024 let s = Style::new().bold(true).italic(false);
1025 let n = s.normalize();
1026 assert_eq!(n.get_bold(), Some(true));
1027 assert!(n.set_attributes & Attributes::ITALIC == 0);
1029 }
1030
1031 #[test]
1032 fn test_pick_first() {
1033 let s = Style::new().color(Color::parse("red").unwrap());
1034 assert_eq!(s.pick_first(), Some("red"));
1035 }
1036
1037 #[test]
1038 fn test_pick_first_fallback() {
1039 let s = Style::new().bgcolor(Color::parse("blue").unwrap());
1040 assert_eq!(s.pick_first(), Some("blue"));
1041 }
1042
1043 #[test]
1044 fn test_pick_first_none() {
1045 let s = Style::new();
1046 assert_eq!(s.pick_first(), None);
1047 }
1048
1049 #[test]
1050 fn test_render() {
1051 let s = Style::new().bold(true).color(Color::parse("red").unwrap());
1052 let rendered = s.render("hello");
1053 assert!(rendered.starts_with("\x1b["));
1054 assert!(rendered.contains("hello"));
1055 assert!(rendered.ends_with("\x1b[0m"));
1056 }
1057
1058 #[test]
1059 fn test_test_with_text() {
1060 let s = Style::new().bold(true);
1061 let out = s.test(Some("custom"));
1062 assert!(out.contains("custom"));
1063 }
1064
1065 #[test]
1066 fn test_test_default() {
1067 let s = Style::new().bold(true);
1068 let out = s.test(None);
1069 assert!(out.contains("Lorem ipsum"));
1070 }
1071
1072 #[test]
1073 fn test_update_link() {
1074 let mut s = Style::new();
1075 s.update_link(Some("https://example.com".into()));
1076 assert!(s.link.is_some());
1077 let first_id = s.link_id;
1078 s.update_link(None);
1079 assert!(s.link.is_none());
1080 assert_eq!(s.link_id, first_id);
1081 }
1082
1083 #[test]
1084 fn test_link_id() {
1085 let s = Style::new().link("https://example.com");
1086 assert!(s.link_id() > 0);
1087 }
1088
1089 #[test]
1090 fn test_meta_methods() {
1091 let mut s = Style::new();
1092 s.set_meta(Some(vec![1, 2, 3]));
1093 assert_eq!(s.meta(), Some(&vec![1, 2, 3]));
1094 if let Some(m) = s.meta_mut() {
1095 m.push(4);
1096 }
1097 assert_eq!(s.meta(), Some(&vec![1, 2, 3, 4]));
1098 }
1099
1100 #[test]
1101 fn test_on() {
1102 let s = Style::new().on(Color::parse("red").unwrap());
1103 assert!(s.bgcolor.is_some());
1104 let b = Color::parse("red").unwrap();
1105 assert_eq!(s.bgcolor.unwrap(), b);
1106 }
1107
1108 #[test]
1109 fn test_references() {
1110 let s = Style::new()
1111 .color(Color::parse("red").unwrap())
1112 .bgcolor(Color::parse("blue").unwrap());
1113 assert!(s.color_ref().is_some());
1114 assert!(s.bgcolor_ref().is_some());
1115 }
1116
1117 #[test]
1118 fn test_color_to_css_hex() {
1119 let c = Color::parse("red").unwrap();
1120 let hex = color_to_css_hex(&c);
1121 assert_eq!(hex, "#800000"); }
1123
1124 #[test]
1125 fn test_color_to_css_hex_truecolor() {
1126 let c = Color::from_rgb(255, 0, 128);
1127 let hex = color_to_css_hex(&c);
1128 assert_eq!(hex, "#ff0080");
1129 }
1130
1131 #[test]
1132 fn test_static_attributes() {
1133 assert!(!STYLE_ATTRIBUTES.is_empty());
1134 let names: Vec<&str> = STYLE_ATTRIBUTES.iter().map(|(n, _)| *n).collect();
1135 assert!(names.contains(&"bold"));
1136 assert!(names.contains(&"italic"));
1137 assert!(names.contains(&"underline"));
1138 assert!(!names.contains(&"notexist"));
1139 }
1140}