1use std::fmt;
34use std::hash::{Hash, Hasher};
35use std::sync::atomic::{AtomicU32, Ordering};
36
37use crate::color::Color;
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
111impl fmt::Display for Attributes {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 let mut parts: Vec<&str> = Vec::new();
114 if self.get(Self::BOLD) { parts.push("bold"); }
115 if self.get(Self::DIM) { parts.push("dim"); }
116 if self.get(Self::ITALIC) { parts.push("italic"); }
117 if self.get(Self::UNDERLINE) { parts.push("underline"); }
118 if self.get(Self::BLINK) { parts.push("blink"); }
119 if self.get(Self::REVERSE) { parts.push("reverse"); }
120 if self.get(Self::CONCEAL) { parts.push("conceal"); }
121 if self.get(Self::STRIKE) { parts.push("strike"); }
122 if self.get(Self::OVERLINE) { parts.push("overline"); }
123 if parts.is_empty() {
124 write!(f, "none")
125 } else {
126 write!(f, "{}", parts.join(" "))
127 }
128 }
129}
130
131#[derive(Debug, Clone)]
141pub struct Style {
142 pub(crate) color: Option<Color>,
143 pub(crate) bgcolor: Option<Color>,
144 pub(crate) attributes: Attributes,
145 pub(crate) set_attributes: u32,
147 pub(crate) link: Option<String>,
148 pub(crate) link_id: u32,
149 pub(crate) is_null: bool,
150 pub(crate) meta: Option<Vec<u8>>,
152}
153
154impl Style {
155 pub fn null() -> Self {
159 Self {
160 color: None,
161 bgcolor: None,
162 attributes: Attributes::empty(),
163 set_attributes: 0,
164 link: None,
165 link_id: 0,
166 is_null: true,
167 meta: None,
168 }
169 }
170
171 pub fn new() -> Self {
173 Self {
174 color: None,
175 bgcolor: None,
176 attributes: Attributes::empty(),
177 set_attributes: 0,
178 link: None,
179 link_id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
180 is_null: false,
181 meta: None,
182 }
183 }
184
185 pub fn color(mut self, color: impl Into<Option<Color>>) -> Self {
187 self.color = color.into();
188 self
189 }
190
191 pub fn bgcolor(mut self, bgcolor: impl Into<Option<Color>>) -> Self {
193 self.bgcolor = bgcolor.into();
194 self
195 }
196
197 pub fn bold(mut self, value: bool) -> Self {
199 self.set_attributes |= Attributes::BOLD;
200 self.attributes.set(Attributes::BOLD, value);
201 self
202 }
203
204 pub fn dim(mut self, value: bool) -> Self {
206 self.set_attributes |= Attributes::DIM;
207 self.attributes.set(Attributes::DIM, value);
208 self
209 }
210
211 pub fn italic(mut self, value: bool) -> Self {
213 self.set_attributes |= Attributes::ITALIC;
214 self.attributes.set(Attributes::ITALIC, value);
215 self
216 }
217
218 pub fn underline(mut self, value: bool) -> Self {
220 self.set_attributes |= Attributes::UNDERLINE;
221 self.attributes.set(Attributes::UNDERLINE, value);
222 self
223 }
224
225 pub fn blink(mut self, value: bool) -> Self {
227 self.set_attributes |= Attributes::BLINK;
228 self.attributes.set(Attributes::BLINK, value);
229 self
230 }
231
232 pub fn reverse(mut self, value: bool) -> Self {
234 self.set_attributes |= Attributes::REVERSE;
235 self.attributes.set(Attributes::REVERSE, value);
236 self
237 }
238
239 pub fn strike(mut self, value: bool) -> Self {
241 self.set_attributes |= Attributes::STRIKE;
242 self.attributes.set(Attributes::STRIKE, value);
243 self
244 }
245
246 pub fn blink2(mut self, value: bool) -> Self {
248 self.set_attributes |= Attributes::BLINK2;
249 self.attributes.set(Attributes::BLINK2, value);
250 self
251 }
252
253 pub fn conceal(mut self, value: bool) -> Self {
255 self.set_attributes |= Attributes::CONCEAL;
256 self.attributes.set(Attributes::CONCEAL, value);
257 self
258 }
259
260 pub fn underline2(mut self, value: bool) -> Self {
262 self.set_attributes |= Attributes::UNDERLINE2;
263 self.attributes.set(Attributes::UNDERLINE2, value);
264 self
265 }
266
267 pub fn frame(mut self, value: bool) -> Self {
269 self.set_attributes |= Attributes::FRAME;
270 self.attributes.set(Attributes::FRAME, value);
271 self
272 }
273
274 pub fn encircle(mut self, value: bool) -> Self {
276 self.set_attributes |= Attributes::ENCIRCLE;
277 self.attributes.set(Attributes::ENCIRCLE, value);
278 self
279 }
280
281 pub fn overline(mut self, value: bool) -> Self {
283 self.set_attributes |= Attributes::OVERLINE;
284 self.attributes.set(Attributes::OVERLINE, value);
285 self
286 }
287
288 pub fn without_color(&self) -> Self {
290 let mut s = self.clone();
291 s.color = None;
292 s.bgcolor = None;
293 s
294 }
295
296 pub fn background_style(&self) -> Self {
299 let mut s = Self::new();
300 s.bgcolor = self.color.clone();
301 s
302 }
303
304 pub fn transparent_background(&self) -> bool {
306 self.bgcolor.is_none()
307 }
308
309 pub fn link(mut self, url: impl Into<String>) -> Self {
311 self.link = Some(url.into());
312 self.link_id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
313 self
314 }
315
316 pub fn from_str(definition: &str) -> Self {
318 let mut style = Self::new();
319 for part in definition.split_whitespace() {
320 match part {
321 "bold" | "b" => { style.set_attributes |= Attributes::BOLD; style.attributes.set(Attributes::BOLD, true); }
322 "dim" | "d" => { style.set_attributes |= Attributes::DIM; style.attributes.set(Attributes::DIM, true); }
323 "italic" | "i" => { style.set_attributes |= Attributes::ITALIC; style.attributes.set(Attributes::ITALIC, true); }
324 "underline" | "u" => { style.set_attributes |= Attributes::UNDERLINE; style.attributes.set(Attributes::UNDERLINE, true); }
325 "blink" => { style.set_attributes |= Attributes::BLINK; style.attributes.set(Attributes::BLINK, true); }
326 "reverse" | "r" => { style.set_attributes |= Attributes::REVERSE; style.attributes.set(Attributes::REVERSE, true); }
327 "strike" | "s" => { style.set_attributes |= Attributes::STRIKE; style.attributes.set(Attributes::STRIKE, true); }
328 "not bold" | "!bold" | "nobold" => { style.set_attributes |= Attributes::BOLD; style.attributes.set(Attributes::BOLD, false); }
329 "not italic" | "!italic" | "noitalic" => { style.set_attributes |= Attributes::ITALIC; style.attributes.set(Attributes::ITALIC, false); }
330 "not underline" | "!underline" | "nounderline" => { style.set_attributes |= Attributes::UNDERLINE; style.attributes.set(Attributes::UNDERLINE, false); }
331 "none" | "default" => {}
332 "on" => { }
333 part if part.starts_with("on ") => {
334 if let Ok(c) = Color::parse(&part[3..]) {
335 style.bgcolor = Some(c);
336 }
337 }
338 part if part.starts_with("link=") => {
339 style.link = Some(part[5..].to_string());
340 }
341 part => {
342 if let Ok(c) = Color::parse(part) {
344 if style.bgcolor.is_some() && style.color.is_none() {
345 } else {
347 style.color = Some(c);
348 }
349 }
350 }
351 }
352 }
353 style
354 }
355
356 pub fn is_null(&self) -> bool {
360 self.is_null
361 }
362
363 pub fn is_plain(&self) -> bool {
365 self.color.is_none()
366 && self.bgcolor.is_none()
367 && self.set_attributes == 0
368 && self.link.is_none()
369 }
370
371 pub fn get_bold(&self) -> Option<bool> {
373 if self.set_attributes & Attributes::BOLD != 0 {
374 Some(self.attributes.get(Attributes::BOLD))
375 } else {
376 None
377 }
378 }
379
380 pub fn combine(&self, other: &Style) -> Style {
382 if other.is_null {
383 return self.clone();
384 }
385 if self.is_null {
386 return other.clone();
387 }
388
389 let mut combined = self.clone();
390 if other.color.is_some() {
391 combined.color = other.color.clone();
392 }
393 if other.bgcolor.is_some() {
394 combined.bgcolor = other.bgcolor.clone();
395 }
396 for &bit in STYLE_BITS {
398 if other.set_attributes & bit != 0 {
399 combined.set_attributes |= bit;
400 combined.attributes.set(bit, other.attributes.get(bit));
401 }
402 }
403 if other.link.is_some() {
404 combined.link = other.link.clone();
405 combined.link_id = other.link_id;
406 }
407 if other.meta.is_some() {
408 combined.meta = other.meta.clone();
409 }
410 combined.is_null = false;
411 combined
412 }
413
414 pub fn to_ansi(&self) -> String {
416 if self.is_null {
417 return String::new();
418 }
419 let mut codes: Vec<String> = Vec::new();
420
421 if let Some(ref c) = self.color {
423 match c.color_type {
424 crate::color::ColorType::Default => codes.push("39".into()),
425 crate::color::ColorType::Standard => {
426 if let Some(n) = c.number {
427 if n < 8 {
428 codes.push((30 + n).to_string());
429 } else {
430 codes.push((82 + n).to_string()); }
432 }
433 }
434 crate::color::ColorType::EightBit => {
435 if let Some(n) = c.number {
436 codes.push(format!("38;5;{n}"));
437 }
438 }
439 crate::color::ColorType::TrueColor => {
440 if let Some((r, g, b)) = c.triplet {
441 codes.push(format!("38;2;{r};{g};{b}"));
442 }
443 }
444 }
445 }
446
447 if let Some(ref c) = self.bgcolor {
449 match c.color_type {
450 crate::color::ColorType::Default => codes.push("49".into()),
451 crate::color::ColorType::Standard => {
452 if let Some(n) = c.number {
453 if n < 8 {
454 codes.push((40 + n).to_string());
455 } else {
456 codes.push((92 + n).to_string()); }
458 }
459 }
460 crate::color::ColorType::EightBit => {
461 if let Some(n) = c.number {
462 codes.push(format!("48;5;{n}"));
463 }
464 }
465 crate::color::ColorType::TrueColor => {
466 if let Some((r, g, b)) = c.triplet {
467 codes.push(format!("48;2;{r};{g};{b}"));
468 }
469 }
470 }
471 }
472
473 if self.set_attributes & Attributes::BOLD != 0 {
475 codes.push(if self.attributes.get(Attributes::BOLD) { "1" } else { "22" }.into());
476 }
477 if self.set_attributes & Attributes::DIM != 0 {
478 codes.push(if self.attributes.get(Attributes::DIM) { "2" } else { "22" }.into());
479 }
480 if self.set_attributes & Attributes::ITALIC != 0 {
481 codes.push(if self.attributes.get(Attributes::ITALIC) { "3" } else { "23" }.into());
482 }
483 if self.set_attributes & Attributes::UNDERLINE != 0 {
484 codes.push(if self.attributes.get(Attributes::UNDERLINE) { "4" } else { "24" }.into());
485 }
486 if self.set_attributes & Attributes::BLINK != 0 {
487 codes.push(if self.attributes.get(Attributes::BLINK) { "5" } else { "25" }.into());
488 }
489 if self.set_attributes & Attributes::REVERSE != 0 {
490 codes.push(if self.attributes.get(Attributes::REVERSE) { "7" } else { "27" }.into());
491 }
492 if self.set_attributes & Attributes::CONCEAL != 0 {
493 codes.push(if self.attributes.get(Attributes::CONCEAL) { "8" } else { "28" }.into());
494 }
495 if self.set_attributes & Attributes::STRIKE != 0 {
496 codes.push(if self.attributes.get(Attributes::STRIKE) { "9" } else { "29" }.into());
497 }
498 if self.set_attributes & Attributes::CONCEAL != 0 {
499 codes.push(if self.attributes.get(Attributes::CONCEAL) { "8" } else { "28" }.into());
500 }
501 if self.set_attributes & Attributes::UNDERLINE2 != 0 {
502 codes.push(if self.attributes.get(Attributes::UNDERLINE2) { "21" } else { "24" }.into());
503 }
504 if self.set_attributes & Attributes::BLINK2 != 0 {
505 codes.push(if self.attributes.get(Attributes::BLINK2) { "6" } else { "25" }.into());
506 }
507 if self.set_attributes & Attributes::FRAME != 0 {
508 codes.push(if self.attributes.get(Attributes::FRAME) { "51" } else { "54" }.into());
509 }
510 if self.set_attributes & Attributes::ENCIRCLE != 0 {
511 codes.push(if self.attributes.get(Attributes::ENCIRCLE) { "52" } else { "54" }.into());
512 }
513 if self.set_attributes & Attributes::OVERLINE != 0 {
514 codes.push(if self.attributes.get(Attributes::OVERLINE) { "53" } else { "55" }.into());
515 }
516
517 if codes.is_empty() {
518 String::new()
519 } else {
520 format!("\x1b[{}m", codes.join(";"))
521 }
522 }
523
524 pub fn reset_ansi(&self) -> &'static str {
526 "\x1b[0m"
527 }
528}
529
530impl Default for Style {
531 fn default() -> Self {
532 Self::new()
533 }
534}
535
536impl PartialEq for Style {
537 fn eq(&self, other: &Self) -> bool {
538 self.color == other.color
539 && self.bgcolor == other.bgcolor
540 && self.attributes == other.attributes
541 && self.set_attributes == other.set_attributes
542 && self.link == other.link
543 }
544}
545
546impl Eq for Style {}
547
548impl Hash for Style {
549 fn hash<H: Hasher>(&self, state: &mut H) {
550 self.color.hash(state);
551 self.bgcolor.hash(state);
552 self.attributes.hash(state);
553 self.set_attributes.hash(state);
554 self.link.hash(state);
555 }
556}
557
558impl fmt::Display for Style {
559 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
560 if self.is_null {
561 return write!(f, "null");
562 }
563 let mut parts: Vec<String> = Vec::new();
564 if let Some(ref c) = self.color {
565 parts.push(c.to_string());
566 }
567 if let Some(ref c) = self.bgcolor {
568 parts.push(format!("on {}", c));
569 }
570 let attrs = self.attributes.to_string();
571 if attrs != "none" {
572 parts.push(attrs);
573 }
574 if parts.is_empty() {
575 write!(f, "none")
576 } else {
577 write!(f, "{}", parts.join(" "))
578 }
579 }
580}
581
582pub type StyleType = Style;
584
585#[derive(Debug, Clone)]
591pub struct StyleStack {
592 stack: Vec<Style>,
593 default_style: Style,
594}
595
596impl StyleStack {
597 pub fn new(default_style: Style) -> Self {
599 Self {
600 stack: Vec::new(),
601 default_style,
602 }
603 }
604
605 pub fn current(&self) -> Style {
607 let mut combined = self.default_style.clone();
608 for s in &self.stack {
609 combined = combined.combine(s);
610 }
611 combined
612 }
613
614 pub fn push(&mut self, style: Style) {
616 self.stack.push(style);
617 }
618
619 pub fn pop(&mut self) -> Option<Style> {
621 self.stack.pop()
622 }
623
624 pub fn len(&self) -> usize {
626 self.stack.len()
627 }
628
629 pub fn is_empty(&self) -> bool {
631 self.stack.is_empty()
632 }
633}
634
635#[cfg(test)]
636mod tests {
637 use super::*;
638
639 #[test]
640 fn test_style_parse() {
641 let s = Style::from_str("bold red");
642 assert_eq!(s.get_bold(), Some(true));
643 assert!(s.color.is_some());
644 }
645
646 #[test]
647 fn test_style_combine() {
648 let base = Style::from_str("red");
649 let over = Style::from_str("bold");
650 let combined = base.combine(&over);
651 assert_eq!(combined.get_bold(), Some(true));
652 assert!(combined.color.is_some());
653 }
654
655 #[test]
656 fn test_ansi_output() {
657 let s = Style::new().color(Color::parse("red").unwrap()).bold(true);
658 let ansi = s.to_ansi();
659 assert!(ansi.contains("31")); assert!(ansi.contains("1")); }
662}