1use std::fmt;
7use std::hash::{Hash, Hasher};
8use std::sync::atomic::{AtomicU32, Ordering};
9
10use crate::color::Color;
11
12static NEXT_ID: AtomicU32 = AtomicU32::new(0);
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20pub struct Attributes(u32);
21
22impl Attributes {
23 pub const BOLD: u32 = 1 << 0;
24 pub const DIM: u32 = 1 << 1;
25 pub const ITALIC: u32 = 1 << 2;
26 pub const UNDERLINE: u32 = 1 << 3;
27 pub const BLINK: u32 = 1 << 4;
28 pub const REVERSE: u32 = 1 << 5;
29 pub const STRIKE: u32 = 1 << 6;
30 pub const UNDERLINE2: u32 = 1 << 7;
31 pub const FRAME: u32 = 1 << 8;
32 pub const ENCIRCLE: u32 = 1 << 9;
33 pub const OVERLINE: u32 = 1 << 10;
34 pub const BLINK2: u32 = 1 << 11;
35 pub const CONCEAL: u32 = 1 << 12;
36
37 pub const fn empty() -> Self {
38 Self(0)
39 }
40
41 pub fn set(&mut self, bit: u32, value: bool) {
42 if value {
43 self.0 |= bit;
44 } else {
45 self.0 &= !bit;
46 }
47 }
48
49 pub fn get(&self, bit: u32) -> bool {
50 self.0 & bit != 0
51 }
52
53 pub const fn bits(&self) -> u32 {
54 self.0
55 }
56}
57
58pub const STYLE_BITS: &[u32] = &[
60 Attributes::BOLD, Attributes::DIM, Attributes::ITALIC,
61 Attributes::UNDERLINE, Attributes::BLINK, Attributes::REVERSE,
62 Attributes::STRIKE, Attributes::UNDERLINE2, Attributes::FRAME,
63 Attributes::ENCIRCLE, Attributes::OVERLINE, Attributes::BLINK2,
64 Attributes::CONCEAL,
65];
66
67impl fmt::Display for Attributes {
68 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69 let mut parts: Vec<&str> = Vec::new();
70 if self.get(Self::BOLD) { parts.push("bold"); }
71 if self.get(Self::DIM) { parts.push("dim"); }
72 if self.get(Self::ITALIC) { parts.push("italic"); }
73 if self.get(Self::UNDERLINE) { parts.push("underline"); }
74 if self.get(Self::BLINK) { parts.push("blink"); }
75 if self.get(Self::REVERSE) { parts.push("reverse"); }
76 if self.get(Self::CONCEAL) { parts.push("conceal"); }
77 if self.get(Self::STRIKE) { parts.push("strike"); }
78 if self.get(Self::OVERLINE) { parts.push("overline"); }
79 if parts.is_empty() {
80 write!(f, "none")
81 } else {
82 write!(f, "{}", parts.join(" "))
83 }
84 }
85}
86
87#[derive(Debug, Clone)]
97pub struct Style {
98 pub(crate) color: Option<Color>,
99 pub(crate) bgcolor: Option<Color>,
100 pub(crate) attributes: Attributes,
101 pub(crate) set_attributes: u32,
103 pub(crate) link: Option<String>,
104 pub(crate) link_id: u32,
105 pub(crate) is_null: bool,
106 pub(crate) meta: Option<Vec<u8>>,
108}
109
110impl Style {
111 pub fn null() -> Self {
115 Self {
116 color: None,
117 bgcolor: None,
118 attributes: Attributes::empty(),
119 set_attributes: 0,
120 link: None,
121 link_id: 0,
122 is_null: true,
123 meta: None,
124 }
125 }
126
127 pub fn new() -> Self {
129 Self {
130 color: None,
131 bgcolor: None,
132 attributes: Attributes::empty(),
133 set_attributes: 0,
134 link: None,
135 link_id: NEXT_ID.fetch_add(1, Ordering::Relaxed),
136 is_null: false,
137 meta: None,
138 }
139 }
140
141 pub fn color(mut self, color: impl Into<Option<Color>>) -> Self {
143 self.color = color.into();
144 self
145 }
146
147 pub fn bgcolor(mut self, bgcolor: impl Into<Option<Color>>) -> Self {
149 self.bgcolor = bgcolor.into();
150 self
151 }
152
153 pub fn bold(mut self, value: bool) -> Self {
155 self.set_attributes |= Attributes::BOLD;
156 self.attributes.set(Attributes::BOLD, value);
157 self
158 }
159
160 pub fn dim(mut self, value: bool) -> Self {
162 self.set_attributes |= Attributes::DIM;
163 self.attributes.set(Attributes::DIM, value);
164 self
165 }
166
167 pub fn italic(mut self, value: bool) -> Self {
169 self.set_attributes |= Attributes::ITALIC;
170 self.attributes.set(Attributes::ITALIC, value);
171 self
172 }
173
174 pub fn underline(mut self, value: bool) -> Self {
176 self.set_attributes |= Attributes::UNDERLINE;
177 self.attributes.set(Attributes::UNDERLINE, value);
178 self
179 }
180
181 pub fn blink(mut self, value: bool) -> Self {
183 self.set_attributes |= Attributes::BLINK;
184 self.attributes.set(Attributes::BLINK, value);
185 self
186 }
187
188 pub fn reverse(mut self, value: bool) -> Self {
190 self.set_attributes |= Attributes::REVERSE;
191 self.attributes.set(Attributes::REVERSE, value);
192 self
193 }
194
195 pub fn strike(mut self, value: bool) -> Self {
197 self.set_attributes |= Attributes::STRIKE;
198 self.attributes.set(Attributes::STRIKE, value);
199 self
200 }
201
202 pub fn blink2(mut self, value: bool) -> Self {
204 self.set_attributes |= Attributes::BLINK2;
205 self.attributes.set(Attributes::BLINK2, value);
206 self
207 }
208
209 pub fn conceal(mut self, value: bool) -> Self {
211 self.set_attributes |= Attributes::CONCEAL;
212 self.attributes.set(Attributes::CONCEAL, value);
213 self
214 }
215
216 pub fn underline2(mut self, value: bool) -> Self {
218 self.set_attributes |= Attributes::UNDERLINE2;
219 self.attributes.set(Attributes::UNDERLINE2, value);
220 self
221 }
222
223 pub fn frame(mut self, value: bool) -> Self {
225 self.set_attributes |= Attributes::FRAME;
226 self.attributes.set(Attributes::FRAME, value);
227 self
228 }
229
230 pub fn encircle(mut self, value: bool) -> Self {
232 self.set_attributes |= Attributes::ENCIRCLE;
233 self.attributes.set(Attributes::ENCIRCLE, value);
234 self
235 }
236
237 pub fn overline(mut self, value: bool) -> Self {
239 self.set_attributes |= Attributes::OVERLINE;
240 self.attributes.set(Attributes::OVERLINE, value);
241 self
242 }
243
244 pub fn without_color(&self) -> Self {
246 let mut s = self.clone();
247 s.color = None;
248 s.bgcolor = None;
249 s
250 }
251
252 pub fn background_style(&self) -> Self {
255 let mut s = Self::new();
256 s.bgcolor = self.color.clone();
257 s
258 }
259
260 pub fn transparent_background(&self) -> bool {
262 self.bgcolor.is_none()
263 }
264
265 pub fn link(mut self, url: impl Into<String>) -> Self {
267 self.link = Some(url.into());
268 self.link_id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
269 self
270 }
271
272 pub fn from_str(definition: &str) -> Self {
274 let mut style = Self::new();
275 for part in definition.split_whitespace() {
276 match part {
277 "bold" | "b" => { style.set_attributes |= Attributes::BOLD; style.attributes.set(Attributes::BOLD, true); }
278 "dim" | "d" => { style.set_attributes |= Attributes::DIM; style.attributes.set(Attributes::DIM, true); }
279 "italic" | "i" => { style.set_attributes |= Attributes::ITALIC; style.attributes.set(Attributes::ITALIC, true); }
280 "underline" | "u" => { style.set_attributes |= Attributes::UNDERLINE; style.attributes.set(Attributes::UNDERLINE, true); }
281 "blink" => { style.set_attributes |= Attributes::BLINK; style.attributes.set(Attributes::BLINK, true); }
282 "reverse" | "r" => { style.set_attributes |= Attributes::REVERSE; style.attributes.set(Attributes::REVERSE, true); }
283 "strike" | "s" => { style.set_attributes |= Attributes::STRIKE; style.attributes.set(Attributes::STRIKE, true); }
284 "not bold" | "!bold" | "nobold" => { style.set_attributes |= Attributes::BOLD; style.attributes.set(Attributes::BOLD, false); }
285 "not italic" | "!italic" | "noitalic" => { style.set_attributes |= Attributes::ITALIC; style.attributes.set(Attributes::ITALIC, false); }
286 "not underline" | "!underline" | "nounderline" => { style.set_attributes |= Attributes::UNDERLINE; style.attributes.set(Attributes::UNDERLINE, false); }
287 "none" | "default" => {}
288 "on" => { }
289 part if part.starts_with("on ") => {
290 if let Ok(c) = Color::parse(&part[3..]) {
291 style.bgcolor = Some(c);
292 }
293 }
294 part if part.starts_with("link=") => {
295 style.link = Some(part[5..].to_string());
296 }
297 part => {
298 if let Ok(c) = Color::parse(part) {
300 if style.bgcolor.is_some() && style.color.is_none() {
301 } else {
303 style.color = Some(c);
304 }
305 }
306 }
307 }
308 }
309 style
310 }
311
312 pub fn is_null(&self) -> bool {
315 self.is_null
316 }
317
318 pub fn is_plain(&self) -> bool {
319 self.color.is_none()
320 && self.bgcolor.is_none()
321 && self.set_attributes == 0
322 && self.link.is_none()
323 }
324
325 pub fn get_bold(&self) -> Option<bool> {
327 if self.set_attributes & Attributes::BOLD != 0 {
328 Some(self.attributes.get(Attributes::BOLD))
329 } else {
330 None
331 }
332 }
333
334 pub fn combine(&self, other: &Style) -> Style {
336 if other.is_null {
337 return self.clone();
338 }
339 if self.is_null {
340 return other.clone();
341 }
342
343 let mut combined = self.clone();
344 if other.color.is_some() {
345 combined.color = other.color.clone();
346 }
347 if other.bgcolor.is_some() {
348 combined.bgcolor = other.bgcolor.clone();
349 }
350 for &bit in STYLE_BITS {
352 if other.set_attributes & bit != 0 {
353 combined.set_attributes |= bit;
354 combined.attributes.set(bit, other.attributes.get(bit));
355 }
356 }
357 if other.link.is_some() {
358 combined.link = other.link.clone();
359 combined.link_id = other.link_id;
360 }
361 if other.meta.is_some() {
362 combined.meta = other.meta.clone();
363 }
364 combined.is_null = false;
365 combined
366 }
367
368 pub fn to_ansi(&self) -> String {
370 if self.is_null {
371 return String::new();
372 }
373 let mut codes: Vec<String> = Vec::new();
374
375 if let Some(ref c) = self.color {
377 match c.color_type {
378 crate::color::ColorType::Default => codes.push("39".into()),
379 crate::color::ColorType::Standard => {
380 if let Some(n) = c.number {
381 if n < 8 {
382 codes.push((30 + n).to_string());
383 } else {
384 codes.push((82 + n).to_string()); }
386 }
387 }
388 crate::color::ColorType::EightBit => {
389 if let Some(n) = c.number {
390 codes.push(format!("38;5;{n}"));
391 }
392 }
393 crate::color::ColorType::TrueColor => {
394 if let Some((r, g, b)) = c.triplet {
395 codes.push(format!("38;2;{r};{g};{b}"));
396 }
397 }
398 }
399 }
400
401 if let Some(ref c) = self.bgcolor {
403 match c.color_type {
404 crate::color::ColorType::Default => codes.push("49".into()),
405 crate::color::ColorType::Standard => {
406 if let Some(n) = c.number {
407 if n < 8 {
408 codes.push((40 + n).to_string());
409 } else {
410 codes.push((92 + n).to_string()); }
412 }
413 }
414 crate::color::ColorType::EightBit => {
415 if let Some(n) = c.number {
416 codes.push(format!("48;5;{n}"));
417 }
418 }
419 crate::color::ColorType::TrueColor => {
420 if let Some((r, g, b)) = c.triplet {
421 codes.push(format!("48;2;{r};{g};{b}"));
422 }
423 }
424 }
425 }
426
427 if self.set_attributes & Attributes::BOLD != 0 {
429 codes.push(if self.attributes.get(Attributes::BOLD) { "1" } else { "22" }.into());
430 }
431 if self.set_attributes & Attributes::DIM != 0 {
432 codes.push(if self.attributes.get(Attributes::DIM) { "2" } else { "22" }.into());
433 }
434 if self.set_attributes & Attributes::ITALIC != 0 {
435 codes.push(if self.attributes.get(Attributes::ITALIC) { "3" } else { "23" }.into());
436 }
437 if self.set_attributes & Attributes::UNDERLINE != 0 {
438 codes.push(if self.attributes.get(Attributes::UNDERLINE) { "4" } else { "24" }.into());
439 }
440 if self.set_attributes & Attributes::BLINK != 0 {
441 codes.push(if self.attributes.get(Attributes::BLINK) { "5" } else { "25" }.into());
442 }
443 if self.set_attributes & Attributes::REVERSE != 0 {
444 codes.push(if self.attributes.get(Attributes::REVERSE) { "7" } else { "27" }.into());
445 }
446 if self.set_attributes & Attributes::CONCEAL != 0 {
447 codes.push(if self.attributes.get(Attributes::CONCEAL) { "8" } else { "28" }.into());
448 }
449 if self.set_attributes & Attributes::STRIKE != 0 {
450 codes.push(if self.attributes.get(Attributes::STRIKE) { "9" } else { "29" }.into());
451 }
452 if self.set_attributes & Attributes::CONCEAL != 0 {
453 codes.push(if self.attributes.get(Attributes::CONCEAL) { "8" } else { "28" }.into());
454 }
455 if self.set_attributes & Attributes::UNDERLINE2 != 0 {
456 codes.push(if self.attributes.get(Attributes::UNDERLINE2) { "21" } else { "24" }.into());
457 }
458 if self.set_attributes & Attributes::BLINK2 != 0 {
459 codes.push(if self.attributes.get(Attributes::BLINK2) { "6" } else { "25" }.into());
460 }
461 if self.set_attributes & Attributes::FRAME != 0 {
462 codes.push(if self.attributes.get(Attributes::FRAME) { "51" } else { "54" }.into());
463 }
464 if self.set_attributes & Attributes::ENCIRCLE != 0 {
465 codes.push(if self.attributes.get(Attributes::ENCIRCLE) { "52" } else { "54" }.into());
466 }
467 if self.set_attributes & Attributes::OVERLINE != 0 {
468 codes.push(if self.attributes.get(Attributes::OVERLINE) { "53" } else { "55" }.into());
469 }
470
471 if codes.is_empty() {
472 String::new()
473 } else {
474 format!("\x1b[{}m", codes.join(";"))
475 }
476 }
477
478 pub fn reset_ansi(&self) -> &'static str {
480 "\x1b[0m"
481 }
482}
483
484impl Default for Style {
485 fn default() -> Self {
486 Self::new()
487 }
488}
489
490impl PartialEq for Style {
491 fn eq(&self, other: &Self) -> bool {
492 self.color == other.color
493 && self.bgcolor == other.bgcolor
494 && self.attributes == other.attributes
495 && self.set_attributes == other.set_attributes
496 && self.link == other.link
497 }
498}
499
500impl Eq for Style {}
501
502impl Hash for Style {
503 fn hash<H: Hasher>(&self, state: &mut H) {
504 self.color.hash(state);
505 self.bgcolor.hash(state);
506 self.attributes.hash(state);
507 self.set_attributes.hash(state);
508 self.link.hash(state);
509 }
510}
511
512impl fmt::Display for Style {
513 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
514 if self.is_null {
515 return write!(f, "null");
516 }
517 let mut parts: Vec<String> = Vec::new();
518 if let Some(ref c) = self.color {
519 parts.push(c.to_string());
520 }
521 if let Some(ref c) = self.bgcolor {
522 parts.push(format!("on {}", c));
523 }
524 let attrs = self.attributes.to_string();
525 if attrs != "none" {
526 parts.push(attrs);
527 }
528 if parts.is_empty() {
529 write!(f, "none")
530 } else {
531 write!(f, "{}", parts.join(" "))
532 }
533 }
534}
535
536pub type StyleType = Style;
538
539#[derive(Debug, Clone)]
545pub struct StyleStack {
546 stack: Vec<Style>,
547 default_style: Style,
548}
549
550impl StyleStack {
551 pub fn new(default_style: Style) -> Self {
552 Self {
553 stack: Vec::new(),
554 default_style,
555 }
556 }
557
558 pub fn current(&self) -> Style {
560 let mut combined = self.default_style.clone();
561 for s in &self.stack {
562 combined = combined.combine(s);
563 }
564 combined
565 }
566
567 pub fn push(&mut self, style: Style) {
569 self.stack.push(style);
570 }
571
572 pub fn pop(&mut self) -> Option<Style> {
574 self.stack.pop()
575 }
576
577 pub fn len(&self) -> usize {
579 self.stack.len()
580 }
581
582 pub fn is_empty(&self) -> bool {
583 self.stack.is_empty()
584 }
585}
586
587#[cfg(test)]
588mod tests {
589 use super::*;
590
591 #[test]
592 fn test_style_parse() {
593 let s = Style::from_str("bold red");
594 assert_eq!(s.get_bold(), Some(true));
595 assert!(s.color.is_some());
596 }
597
598 #[test]
599 fn test_style_combine() {
600 let base = Style::from_str("red");
601 let over = Style::from_str("bold");
602 let combined = base.combine(&over);
603 assert_eq!(combined.get_bold(), Some(true));
604 assert!(combined.color.is_some());
605 }
606
607 #[test]
608 fn test_ansi_output() {
609 let s = Style::new().color(Color::parse("red").unwrap()).bold(true);
610 let ansi = s.to_ansi();
611 assert!(ansi.contains("31")); assert!(ansi.contains("1")); }
614}