Skip to main content

yew_ansi/
graphic_rendition.rs

1use crate::style::{ClassStyle, StyleBuilder};
2use std::{
3    borrow::Borrow,
4    convert::TryFrom,
5    fmt::{self, Display, Formatter},
6    iter,
7};
8
9/// The 8 colors defined by the original specification.
10#[derive(Clone, Copy, Debug, Eq, PartialEq)]
11pub enum ColorName {
12    Black,
13    Red,
14    Green,
15    Yellow,
16    Blue,
17    Magenta,
18    Cyan,
19    White,
20}
21impl ColorName {
22    fn from_code(code: usize) -> Option<Self> {
23        use ColorName::*;
24        [Black, Red, Green, Yellow, Blue, Magenta, Cyan, White]
25            .get(code % 10)
26            .copied()
27    }
28
29    /// Get the 24-bit colour code.
30    pub fn rgb(self, bright: bool) -> u32 {
31        use ColorName::*;
32
33        macro_rules! rgb {
34            ($r:literal, $g:literal, $b:literal) => {
35                (($r << 16) + ($g << 8) + $b) as u32
36            };
37        }
38
39        if bright {
40            match self {
41                Black => rgb!(1, 1, 1),
42                Red => rgb!(222, 56, 43),
43                Green => rgb!(57, 181, 74),
44                Yellow => rgb!(255, 199, 6),
45                Blue => rgb!(0, 111, 184),
46                Magenta => rgb!(118, 38, 113),
47                Cyan => rgb!(44, 181, 233),
48                White => rgb!(204, 204, 204),
49            }
50        } else {
51            match self {
52                Black => rgb!(128, 128, 128),
53                Red => rgb!(255, 0, 0),
54                Green => rgb!(0, 255, 0),
55                Yellow => rgb!(255, 255, 0),
56                Blue => rgb!(0, 0, 255),
57                Magenta => rgb!(255, 0, 255),
58                Cyan => rgb!(0, 255, 255),
59                White => rgb!(255, 255, 255),
60            }
61        }
62    }
63}
64impl Display for ColorName {
65    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
66        use ColorName::*;
67        let name = match self {
68            Black => "black",
69            Red => "red",
70            Green => "green",
71            Yellow => "yellow",
72            Blue => "blue",
73            Magenta => "magenta",
74            Cyan => "cyan",
75            White => "white",
76        };
77        f.write_str(name)
78    }
79}
80
81/// Select Graphic Rendition parameter.
82#[derive(Clone, Debug, Eq, PartialEq)]
83#[non_exhaustive]
84pub enum Sgr {
85    Reset,
86    Bold,
87    BoldOff,
88    Italic,
89    ItalicOff,
90    Underline,
91    UnderlineOff,
92    ColorFgRgb(u32),
93    ColorFgName(ColorName),
94    ColorFgNameBright(ColorName),
95    ResetColorFg,
96    ColorBgRgb(u32),
97    ColorBgName(ColorName),
98    ColorBgNameBright(ColorName),
99    ResetColorBg,
100}
101impl Sgr {
102    fn from_color_code(code: usize, background: bool, bright: bool) -> Option<Self> {
103        use Sgr::*;
104        let color = ColorName::from_code(code)?;
105        let sgr = match (background, bright) {
106            (false, false) => ColorFgName(color),
107            (false, true) => ColorFgNameBright(color),
108            (true, false) => ColorBgName(color),
109            (true, true) => ColorBgNameBright(color),
110        };
111        Some(sgr)
112    }
113
114    fn from_rgb(r: usize, g: usize, b: usize, background: bool) -> Option<Self> {
115        let rgb = u32::try_from((r << 16) + (g << 8) + b).ok()?;
116        let sgr = if background {
117            Self::ColorBgRgb(rgb)
118        } else {
119            Self::ColorFgRgb(rgb)
120        };
121        Some(sgr)
122    }
123
124    fn color_rgb(mut params: impl Iterator<Item = usize>, background: bool) -> Option<Self> {
125        match params.next()? {
126            2 => {
127                let (r, g, b) = (params.next()?, params.next()?, params.next()?);
128                Self::from_rgb(r, g, b, background)
129            }
130            5 => {
131                let n = params.next()?;
132                match n {
133                    0..=7 => Self::from_color_code(n, background, false),
134                    8..=15 => Self::from_color_code(n - 8, background, true),
135                    16..=231 => {
136                        // palette represents a 6 * 6 * 6 cube where the three
137                        // dimensions represent r, g, and b.
138                        // Comments here assume a 2D representation of the cube.
139                        // See: https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit
140                        const ROWS: usize = 6;
141                        const COLUMNS: usize = 36;
142                        const STEP_SIZE: usize = 0xFF / 6;
143
144                        let n = n - 16;
145                        // increases with each row
146                        let r = (n / COLUMNS) * STEP_SIZE;
147                        // g is constant for each 6 * 6 block
148                        let g = ((n % COLUMNS) / ROWS) * STEP_SIZE;
149                        // increases with each column but resets every 6.
150                        let b = (n % ROWS) * STEP_SIZE;
151                        Self::from_rgb(r, g, b, background)
152                    }
153                    232..=255 => {
154                        const STEP_SIZE: usize = 0xFF / 24;
155                        let n = n - 232;
156                        Self::from_rgb(n * STEP_SIZE, n * STEP_SIZE, n * STEP_SIZE, background)
157                    }
158                    _ => None,
159                }
160            }
161            _ => None,
162        }
163    }
164
165    /// Parse a single SGR parameter from the parameters.
166    /// This will only consume as many items from `params` as required to complete the SGR.
167    fn from_params(mut params: impl Iterator<Item = usize>) -> Option<Self> {
168        use Sgr::*;
169        let code = params.next()?;
170        Some(match code {
171            0 => Reset,
172            1 => Bold,
173            3 => Italic,
174            4 => Underline,
175            22 => BoldOff,
176            23 => ItalicOff,
177            24 => UnderlineOff,
178            30..=37 => ColorFgName(ColorName::from_code(code)?),
179            38 => Self::color_rgb(params, false)?,
180            39 => ResetColorFg,
181            40..=47 => ColorBgName(ColorName::from_code(code)?),
182            48 => Self::color_rgb(params, true)?,
183            49 => ResetColorBg,
184            90..=97 => ColorFgNameBright(ColorName::from_code(code)?),
185            100..=107 => ColorBgNameBright(ColorName::from_code(code)?),
186            _ => return None,
187        })
188    }
189}
190
191/// Parse all SGR parameters in the given parameters.
192/// This only consumes as many items from `params` as can be parsed by [`Sgr`].
193pub(crate) fn parse_sgrs(mut params: impl Iterator<Item = usize>) -> Vec<Sgr> {
194    iter::from_fn(|| Sgr::from_params(&mut params)).collect()
195}
196
197/// Describes the color effect of multiple SGR parameters.
198#[derive(Clone, Debug, Eq, PartialEq)]
199pub enum ColorEffect {
200    None,
201    Name(ColorName),
202    NameBright(ColorName),
203    Rgb(u32),
204}
205impl ColorEffect {
206    /// Get the 24-bit colour code.
207    pub fn rgb(&self) -> Option<u32> {
208        match self {
209            Self::None => None,
210            Self::Name(named) => Some(named.rgb(false)),
211            Self::NameBright(named) => Some(named.rgb(true)),
212            Self::Rgb(rgb) => Some(*rgb),
213        }
214    }
215}
216impl Default for ColorEffect {
217    fn default() -> Self {
218        Self::None
219    }
220}
221
222impl From<&Sgr> for ColorEffect {
223    fn from(sgr: &Sgr) -> Self {
224        use Sgr::*;
225        match sgr {
226            ColorFgRgb(rgb) | ColorBgRgb(rgb) => Self::Rgb(*rgb),
227            ColorFgName(name) | ColorBgName(name) => Self::Name(*name),
228            ColorFgNameBright(name) | ColorBgNameBright(name) => Self::NameBright(*name),
229            _ => Self::None,
230        }
231    }
232}
233
234/// Describes the effect that multiple SGR parameters have on text.
235#[derive(Clone, Debug, Default, Eq, PartialEq)]
236pub struct SgrEffect {
237    pub bold: bool,
238    pub italic: bool,
239    pub underline: bool,
240    /// Foreground colour
241    pub fg: ColorEffect,
242    /// Background colour
243    pub bg: ColorEffect,
244}
245impl SgrEffect {
246    fn reset(&mut self) {
247        *self = Self::default();
248    }
249
250    /// Apply a SGR parameter to this effect.
251    pub fn apply_sgr(&mut self, sgr: impl Borrow<Sgr>) {
252        use Sgr::*;
253        let sgr = sgr.borrow();
254        match sgr {
255            Reset => self.reset(),
256            Bold => self.bold = true,
257            BoldOff => self.bold = false,
258            Italic => self.italic = true,
259            ItalicOff => self.italic = false,
260            Underline => self.underline = true,
261            UnderlineOff => self.underline = false,
262            ColorFgRgb(_) | ColorFgName(_) | ColorFgNameBright(_) | ResetColorFg => {
263                self.fg = ColorEffect::from(sgr);
264            }
265            ColorBgRgb(_) | ColorBgName(_) | ColorBgNameBright(_) | ResetColorBg => {
266                self.bg = ColorEffect::from(sgr);
267            }
268        }
269    }
270
271    /// Apply multiple SGR parameters to this effect.
272    pub fn apply_sgrs<T: Borrow<Sgr>>(&mut self, sgrs: impl IntoIterator<Item = T>) {
273        for sgr in sgrs {
274            self.apply_sgr(sgr.borrow());
275        }
276    }
277
278    pub fn to_class_style<B: StyleBuilder>(&self) -> ClassStyle {
279        let mut builder = B::default();
280        if self.bold {
281            builder.bold();
282        }
283        if self.italic {
284            builder.italic();
285        }
286        if self.underline {
287            builder.underline();
288        }
289        builder.fg_color(&self.fg);
290        builder.bg_color(&self.bg);
291
292        builder.finish()
293    }
294}