1use std::fmt::Write;
12
13use crate::errors::LexError;
14use crate::lexer::{EmphasisType, TagType, Token, tokenize};
15
16#[derive(Debug, PartialEq, Clone)]
18pub enum Ground {
19 Foreground,
21 Background,
23}
24
25#[derive(Default, Clone, Debug)]
27pub struct Style {
28 pub fg: Option<Color>,
30 pub bg: Option<Color>,
32 pub bold: bool,
34 pub dim: bool,
36 pub italic: bool,
38 pub underline: bool,
40 pub strikethrough: bool,
42 pub blink: bool,
44 pub reset: bool,
46 pub prefix: Option<String>,
48}
49
50#[derive(Debug, PartialEq, Clone)]
52pub enum NamedColor {
53 Black,
54 Red,
55 Green,
56 Yellow,
57 Blue,
58 Magenta,
59 Cyan,
60 White,
61 BrightBlack,
62 BrightRed,
63 BrightGreen,
64 BrightYellow,
65 BrightBlue,
66 BrightMagenta,
67 BrightCyan,
68 BrightWhite,
69}
70
71#[derive(Debug, PartialEq, Clone)]
73pub enum Color {
74 Named(NamedColor),
75 Ansi256(u8),
76 Rgb(u8, u8, u8),
77}
78
79impl Style {
80 pub fn parse(markup: impl Into<String>) -> Result<Self, LexError> {
98 let mut res = Self {
99 ..Default::default()
100 };
101 for tok in tokenize(markup.into())? {
102 match tok {
103 Token::Text(_) => continue,
104 Token::Tag(tag) => match tag {
105 TagType::ResetAll | TagType::ResetOne(_) => res.reset = true,
106 TagType::Emphasis(emphasis) => match emphasis {
107 EmphasisType::Dim => res.dim = true,
108 EmphasisType::Blink => res.blink = true,
109 EmphasisType::Bold => res.bold = true,
110 EmphasisType::Italic => res.italic = true,
111 EmphasisType::Strikethrough => res.strikethrough = true,
112 EmphasisType::Underline => res.underline = true,
113 },
114 TagType::Color { color, ground } => match ground {
115 Ground::Background => res.bg = Some(color),
116 Ground::Foreground => res.fg = Some(color),
117 },
118 TagType::Prefix(_) => continue,
119 },
120 }
121 }
122
123 Ok(res)
124 }
125}
126
127impl NamedColor {
128 pub(crate) fn from_str(input: &str) -> Option<Self> {
133 match input {
134 "black" => Some(Self::Black),
135 "red" => Some(Self::Red),
136 "green" => Some(Self::Green),
137 "yellow" => Some(Self::Yellow),
138 "blue" => Some(Self::Blue),
139 "magenta" => Some(Self::Magenta),
140 "cyan" => Some(Self::Cyan),
141 "white" => Some(Self::White),
142 "bright-black" => Some(Self::BrightBlack),
143 "bright-red" => Some(Self::BrightRed),
144 "bright-green" => Some(Self::BrightGreen),
145 "bright-yellow" => Some(Self::BrightYellow),
146 "bright-blue" => Some(Self::BrightBlue),
147 "bright-magenta" => Some(Self::BrightMagenta),
148 "bright-cyan" => Some(Self::BrightCyan),
149 "bright-white" => Some(Self::BrightWhite),
150 _ => None,
151 }
152 }
153}
154
155fn vec_to_ansi_seq(vec: Vec<u8>) -> String {
159 let mut seq = String::from("\x1b[");
160
161 for (i, n) in vec.iter().enumerate() {
162 if i != 0 {
163 seq.push(';');
164 }
165 write!(seq, "{n}").unwrap();
166 }
167
168 seq.push('m');
169 seq
170}
171
172fn encode_color_sgr(ansi: &mut Vec<u8>, param: Ground, color: &Color) {
175 let addend: u8 = match param {
176 Ground::Background => 10,
177 Ground::Foreground => 0,
178 };
179 match color {
180 Color::Named(named) => {
181 ansi.push(match named {
182 NamedColor::Black => 30 + addend,
183 NamedColor::Red => 31 + addend,
184 NamedColor::Green => 32 + addend,
185 NamedColor::Yellow => 33 + addend,
186 NamedColor::Blue => 34 + addend,
187 NamedColor::Magenta => 35 + addend,
188 NamedColor::Cyan => 36 + addend,
189 NamedColor::White => 37 + addend,
190 NamedColor::BrightBlack => 90 + addend,
191 NamedColor::BrightRed => 91 + addend,
192 NamedColor::BrightGreen => 92 + addend,
193 NamedColor::BrightYellow => 93 + addend,
194 NamedColor::BrightBlue => 94 + addend,
195 NamedColor::BrightMagenta => 95 + addend,
196 NamedColor::BrightCyan => 96 + addend,
197 NamedColor::BrightWhite => 97 + addend,
198 });
199 }
200 Color::Ansi256(v) => {
201 ansi.extend_from_slice(&[38 + addend, 5, *v]);
202 }
203 Color::Rgb(r, g, b) => {
204 ansi.extend_from_slice(&[38 + addend, 2, *r, *g, *b]);
205 }
206 }
207}
208
209const fn named_sgr(color: &NamedColor) -> u8 {
211 match color {
212 NamedColor::Black => 30,
213 NamedColor::Red => 31,
214 NamedColor::Green => 32,
215 NamedColor::Yellow => 33,
216 NamedColor::Blue => 34,
217 NamedColor::Magenta => 35,
218 NamedColor::Cyan => 36,
219 NamedColor::White => 37,
220 NamedColor::BrightBlack => 90,
221 NamedColor::BrightRed => 91,
222 NamedColor::BrightGreen => 92,
223 NamedColor::BrightYellow => 93,
224 NamedColor::BrightBlue => 94,
225 NamedColor::BrightMagenta => 95,
226 NamedColor::BrightCyan => 96,
227 NamedColor::BrightWhite => 97,
228 }
229}
230
231pub fn color_to_ansi(color: &Color, ground: Ground) -> String {
239 let add: u8 = match ground {
240 Ground::Background => 10,
241 Ground::Foreground => 0,
242 };
243 match color {
244 Color::Named(n) => format!("\x1b[{}m", named_sgr(n) + add),
245 Color::Ansi256(v) => format!("\x1b[{};5;{}m", 38 + add, v),
246 Color::Rgb(r, g, b) => format!("\x1b[{};2;{};{};{}m", 38 + add, r, g, b),
247 }
248}
249
250pub fn emphasis_to_ansi(emphasis: &EmphasisType) -> String {
252 let code: u8 = match emphasis {
253 EmphasisType::Bold => 1,
254 EmphasisType::Dim => 2,
255 EmphasisType::Italic => 3,
256 EmphasisType::Underline => 4,
257 EmphasisType::Blink => 5,
258 EmphasisType::Strikethrough => 9,
259 };
260 format!("\x1b[{}m", code)
261}
262
263pub fn style_to_ansi(style: &Style) -> String {
283 let mut ansi: Vec<u8> = Vec::new();
284
285 if style.reset {
286 return String::from("\x1b[0m");
287 }
288
289 for (enabled, code) in [
290 (style.bold, 1),
291 (style.dim, 2),
292 (style.italic, 3),
293 (style.underline, 4),
294 (style.blink, 5),
295 (style.strikethrough, 9),
296 ] {
297 if enabled {
298 ansi.push(code);
299 }
300 }
301
302 if let Some(fg) = &style.fg {
303 encode_color_sgr(&mut ansi, Ground::Foreground, fg);
304 }
305 if let Some(bg) = &style.bg {
306 encode_color_sgr(&mut ansi, Ground::Background, bg);
307 }
308
309 if ansi.is_empty() {
310 return String::new();
311 }
312
313 vec_to_ansi_seq(ansi)
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319 use crate::lexer::EmphasisType;
320
321 #[test]
324 fn test_named_color_from_str_known_colors() {
325 assert_eq!(NamedColor::from_str("black"), Some(NamedColor::Black));
326 assert_eq!(NamedColor::from_str("red"), Some(NamedColor::Red));
327 assert_eq!(NamedColor::from_str("green"), Some(NamedColor::Green));
328 assert_eq!(NamedColor::from_str("yellow"), Some(NamedColor::Yellow));
329 assert_eq!(NamedColor::from_str("blue"), Some(NamedColor::Blue));
330 assert_eq!(NamedColor::from_str("magenta"), Some(NamedColor::Magenta));
331 assert_eq!(NamedColor::from_str("cyan"), Some(NamedColor::Cyan));
332 assert_eq!(NamedColor::from_str("white"), Some(NamedColor::White));
333 }
334
335 #[test]
336 fn test_named_color_from_str_unknown_returns_none() {
337 assert_eq!(NamedColor::from_str("purple"), None);
338 }
339
340 #[test]
341 fn test_named_color_from_str_case_sensitive() {
342 assert_eq!(NamedColor::from_str("Red"), None);
343 assert_eq!(NamedColor::from_str("RED"), None);
344 }
345
346 #[test]
347 fn test_named_color_from_str_empty_returns_none() {
348 assert_eq!(NamedColor::from_str(""), None);
349 }
350
351 #[test]
354 fn test_vec_to_ansi_seq_single_param() {
355 let result = vec_to_ansi_seq(vec![1]);
356 assert_eq!(result, "\x1b[1m");
357 }
358
359 #[test]
360 fn test_vec_to_ansi_seq_multiple_params() {
361 let result = vec_to_ansi_seq(vec![1, 31]);
362 assert_eq!(result, "\x1b[1;31m");
363 }
364
365 #[test]
366 fn test_vec_to_ansi_seq_empty_produces_bare_sequence() {
367 let result = vec_to_ansi_seq(vec![]);
368 assert_eq!(result, "\x1b[m");
369 }
370
371 #[test]
374 fn test_color_to_ansi_named_foreground() {
375 let result = color_to_ansi(&Color::Named(NamedColor::Red), Ground::Foreground);
376 assert_eq!(result, "\x1b[31m");
377 }
378
379 #[test]
380 fn test_color_to_ansi_named_background() {
381 let result = color_to_ansi(&Color::Named(NamedColor::Red), Ground::Background);
382 assert_eq!(result, "\x1b[41m");
383 }
384
385 #[test]
386 fn test_color_to_ansi_ansi256_foreground() {
387 let result = color_to_ansi(&Color::Ansi256(200), Ground::Foreground);
388 assert_eq!(result, "\x1b[38;5;200m");
389 }
390
391 #[test]
392 fn test_color_to_ansi_ansi256_background() {
393 let result = color_to_ansi(&Color::Ansi256(100), Ground::Background);
394 assert_eq!(result, "\x1b[48;5;100m");
395 }
396
397 #[test]
398 fn test_color_to_ansi_rgb_foreground() {
399 let result = color_to_ansi(&Color::Rgb(255, 128, 0), Ground::Foreground);
400 assert_eq!(result, "\x1b[38;2;255;128;0m");
401 }
402
403 #[test]
404 fn test_color_to_ansi_rgb_background() {
405 let result = color_to_ansi(&Color::Rgb(0, 0, 255), Ground::Background);
406 assert_eq!(result, "\x1b[48;2;0;0;255m");
407 }
408
409 #[test]
410 fn test_color_to_ansi_rgb_zero_values() {
411 let result = color_to_ansi(&Color::Rgb(0, 0, 0), Ground::Foreground);
412 assert_eq!(result, "\x1b[38;2;0;0;0m");
413 }
414
415 #[test]
418 fn test_emphasis_to_ansi_bold() {
419 assert_eq!(emphasis_to_ansi(&EmphasisType::Bold), "\x1b[1m");
420 }
421
422 #[test]
423 fn test_emphasis_to_ansi_dim() {
424 assert_eq!(emphasis_to_ansi(&EmphasisType::Dim), "\x1b[2m");
425 }
426
427 #[test]
428 fn test_emphasis_to_ansi_italic() {
429 assert_eq!(emphasis_to_ansi(&EmphasisType::Italic), "\x1b[3m");
430 }
431
432 #[test]
433 fn test_emphasis_to_ansi_underline() {
434 assert_eq!(emphasis_to_ansi(&EmphasisType::Underline), "\x1b[4m");
435 }
436
437 #[test]
438 fn test_emphasis_to_ansi_blink() {
439 assert_eq!(emphasis_to_ansi(&EmphasisType::Blink), "\x1b[5m");
440 }
441
442 #[test]
443 fn test_emphasis_to_ansi_strikethrough() {
444 assert_eq!(emphasis_to_ansi(&EmphasisType::Strikethrough), "\x1b[9m");
445 }
446
447 #[test]
450 fn test_style_to_ansi_empty_style_returns_empty_string() {
451 let style = Style {
452 fg: None,
453 bg: None,
454 bold: false,
455 dim: false,
456 italic: false,
457 underline: false,
458 strikethrough: false,
459 blink: false,
460 ..Default::default()
461 };
462 assert_eq!(style_to_ansi(&style), "");
463 }
464
465 #[test]
466 fn test_style_to_ansi_bold_only() {
467 let style = Style {
468 fg: None,
469 bg: None,
470 bold: true,
471 dim: false,
472 italic: false,
473 underline: false,
474 strikethrough: false,
475 blink: false,
476 ..Default::default()
477 };
478 assert_eq!(style_to_ansi(&style), "\x1b[1m");
479 }
480
481 #[test]
482 fn test_style_to_ansi_bold_with_foreground_color() {
483 let style = Style {
484 fg: Some(Color::Named(NamedColor::Green)),
485 bg: None,
486 bold: true,
487 dim: false,
488 italic: false,
489 underline: false,
490 strikethrough: false,
491 blink: false,
492 ..Default::default()
493 };
494 assert_eq!(style_to_ansi(&style), "\x1b[1;32m");
495 }
496
497 #[test]
498 fn test_style_to_ansi_fg_and_bg() {
499 let style = Style {
500 fg: Some(Color::Named(NamedColor::White)),
501 bg: Some(Color::Named(NamedColor::Blue)),
502 bold: false,
503 dim: false,
504 italic: false,
505 underline: false,
506 strikethrough: false,
507 blink: false,
508 ..Default::default()
509 };
510 assert_eq!(style_to_ansi(&style), "\x1b[37;44m");
511 }
512
513 #[test]
514 fn test_style_to_ansi_all_emphasis_flags() {
515 let style = Style {
516 fg: None,
517 bg: None,
518 bold: true,
519 dim: true,
520 italic: true,
521 underline: true,
522 strikethrough: true,
523 blink: true,
524 ..Default::default()
525 };
526 assert_eq!(style_to_ansi(&style), "\x1b[1;2;3;4;5;9m");
527 }
528}
529
530