term_transcript/svg/
palette.rs

1//! `Palette` and other color-related types.
2
3use std::{error, fmt, str::FromStr};
4
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6
7use crate::utils::RgbColor;
8
9/// Palette of [16 standard terminal colors][colors] (8 ordinary colors + 8 intense variations).
10///
11/// [colors]: https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit
12#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
13pub struct Palette {
14    /// Ordinary colors.
15    pub colors: TermColors,
16    /// Intense colors.
17    pub intense_colors: TermColors,
18}
19
20/// Returns the palette specified by [`NamedPalette::Gjm8`].
21impl Default for Palette {
22    fn default() -> Self {
23        Self::gjm8()
24    }
25}
26
27impl Palette {
28    const fn dracula() -> Self {
29        Self {
30            colors: TermColors {
31                black: RgbColor(0x28, 0x29, 0x36),
32                red: RgbColor(0xea, 0x51, 0xb2),
33                green: RgbColor(0xeb, 0xff, 0x87),
34                yellow: RgbColor(0x00, 0xf7, 0x69),
35                blue: RgbColor(0x62, 0xd6, 0xe8),
36                magenta: RgbColor(0xb4, 0x5b, 0xcf),
37                cyan: RgbColor(0xa1, 0xef, 0xe4),
38                white: RgbColor(0xe9, 0xe9, 0xf4),
39            },
40            intense_colors: TermColors {
41                black: RgbColor(0x62, 0x64, 0x83),
42                red: RgbColor(0xb4, 0x5b, 0xcf),
43                green: RgbColor(0x3a, 0x3c, 0x4e),
44                yellow: RgbColor(0x4d, 0x4f, 0x68),
45                blue: RgbColor(0x62, 0xd6, 0xe8),
46                magenta: RgbColor(0xf1, 0xf2, 0xf8),
47                cyan: RgbColor(0x00, 0xf7, 0x69),
48                white: RgbColor(0xf7, 0xf7, 0xfb),
49            },
50        }
51    }
52
53    const fn powershell() -> Self {
54        Self {
55            colors: TermColors {
56                black: RgbColor(0x0c, 0x0c, 0x0c),
57                red: RgbColor(0xc5, 0x0f, 0x1f),
58                green: RgbColor(0x13, 0xa1, 0x0e),
59                yellow: RgbColor(0xc1, 0x9c, 0x00),
60                blue: RgbColor(0x00, 0x37, 0xda),
61                magenta: RgbColor(0x88, 0x17, 0x98),
62                cyan: RgbColor(0x3a, 0x96, 0xdd),
63                white: RgbColor(0xcc, 0xcc, 0xcc),
64            },
65            intense_colors: TermColors {
66                black: RgbColor(0x76, 0x76, 0x76),
67                red: RgbColor(0xe7, 0x48, 0x56),
68                green: RgbColor(0x16, 0xc6, 0x0c),
69                yellow: RgbColor(0xf9, 0xf1, 0xa5),
70                blue: RgbColor(0x3b, 0x78, 0xff),
71                magenta: RgbColor(0xb4, 0x00, 0x9e),
72                cyan: RgbColor(0x61, 0xd6, 0xd6),
73                white: RgbColor(0xf2, 0xf2, 0xf2),
74            },
75        }
76    }
77
78    const fn xterm() -> Self {
79        Self {
80            colors: TermColors {
81                black: RgbColor(0, 0, 0),
82                red: RgbColor(0xcd, 0, 0),
83                green: RgbColor(0, 0xcd, 0),
84                yellow: RgbColor(0xcd, 0xcd, 0),
85                blue: RgbColor(0, 0, 0xee),
86                magenta: RgbColor(0xcd, 0, 0xcd),
87                cyan: RgbColor(0, 0xcd, 0xcd),
88                white: RgbColor(0xe5, 0xe5, 0xe5),
89            },
90            intense_colors: TermColors {
91                black: RgbColor(0x7f, 0x7f, 0x7f),
92                red: RgbColor(0xff, 0, 0),
93                green: RgbColor(0, 0xff, 0),
94                yellow: RgbColor(0xff, 0xff, 0),
95                blue: RgbColor(0x5c, 0x5c, 0xff),
96                magenta: RgbColor(0xff, 0, 0xff),
97                cyan: RgbColor(0, 0xff, 0xff),
98                white: RgbColor(0xff, 0xff, 0xff),
99            },
100        }
101    }
102
103    const fn ubuntu() -> Self {
104        Self {
105            colors: TermColors {
106                black: RgbColor(0x01, 0x01, 0x01),
107                red: RgbColor(0xde, 0x38, 0x2b),
108                green: RgbColor(0x38, 0xb5, 0x4a),
109                yellow: RgbColor(0xff, 0xc7, 0x06),
110                blue: RgbColor(0, 0x6f, 0xb8),
111                magenta: RgbColor(0x76, 0x26, 0x71),
112                cyan: RgbColor(0x2c, 0xb5, 0xe9),
113                white: RgbColor(0xcc, 0xcc, 0xcc),
114            },
115            intense_colors: TermColors {
116                black: RgbColor(0x80, 0x80, 0x80),
117                red: RgbColor(0xff, 0, 0),
118                green: RgbColor(0, 0xff, 0),
119                yellow: RgbColor(0xff, 0xff, 0),
120                blue: RgbColor(0, 0, 0xff),
121                magenta: RgbColor(0xff, 0, 0xff),
122                cyan: RgbColor(0, 0xff, 0xff),
123                white: RgbColor(0xff, 0xff, 0xff),
124            },
125        }
126    }
127
128    const fn gjm8() -> Self {
129        Self {
130            colors: TermColors {
131                black: RgbColor(0x1c, 0x1c, 0x1c),
132                red: RgbColor(0xff, 0x00, 0x5b),
133                green: RgbColor(0xce, 0xe3, 0x18),
134                yellow: RgbColor(0xff, 0xe7, 0x55),
135                blue: RgbColor(0x04, 0x8a, 0xc7),
136                magenta: RgbColor(0x83, 0x3c, 0x9f),
137                cyan: RgbColor(0x0a, 0xc1, 0xcd),
138                white: RgbColor(0xe5, 0xe5, 0xe5),
139            },
140            intense_colors: TermColors {
141                black: RgbColor(0x66, 0x66, 0x66),
142                red: RgbColor(0xff, 0x00, 0xa0),
143                green: RgbColor(0xcc, 0xff, 0x00),
144                yellow: RgbColor(0xff, 0x9f, 0x00),
145                blue: RgbColor(0x48, 0xc6, 0xff),
146                magenta: RgbColor(0xbe, 0x67, 0xe1),
147                cyan: RgbColor(0x63, 0xe7, 0xf0),
148                white: RgbColor(0xf3, 0xf3, 0xf3),
149            },
150        }
151    }
152}
153
154/// Values of [8 base terminal colors][colors].
155///
156/// [colors]: https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit
157#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
158pub struct TermColors {
159    /// Black color.
160    pub black: RgbColor,
161    /// Red color.
162    pub red: RgbColor,
163    /// Green color.
164    pub green: RgbColor,
165    /// Yellow color.
166    pub yellow: RgbColor,
167    /// Blue color.
168    pub blue: RgbColor,
169    /// Magenta color.
170    pub magenta: RgbColor,
171    /// Cyan color.
172    pub cyan: RgbColor,
173    /// White color.
174    pub white: RgbColor,
175}
176
177impl Serialize for RgbColor {
178    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
179        serializer.serialize_str(&format!("{self:x}"))
180    }
181}
182
183impl<'de> Deserialize<'de> for RgbColor {
184    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
185        use serde::de;
186
187        #[derive(Debug)]
188        struct ColorVisitor;
189
190        impl de::Visitor<'_> for ColorVisitor {
191            type Value = RgbColor;
192
193            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
194                formatter.write_str("hex color, such as #fed or #a757ff")
195            }
196
197            fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
198                value.parse().map_err(E::custom)
199            }
200        }
201
202        deserializer.deserialize_str(ColorVisitor)
203    }
204}
205
206/// Named [`Palette`].
207#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
208#[non_exhaustive]
209pub enum NamedPalette {
210    /// Dracula color scheme. This is the [`Default`] value.
211    Dracula,
212    /// PowerShell 6 / Windows 10 console color scheme.
213    PowerShell,
214    /// `xterm` color scheme.
215    Xterm,
216    /// Ubuntu terminal color scheme.
217    Ubuntu,
218    /// [gjm8 color scheme](https://terminal.sexy/).
219    Gjm8,
220}
221
222impl Default for NamedPalette {
223    fn default() -> Self {
224        Self::Gjm8
225    }
226}
227
228impl From<NamedPalette> for Palette {
229    fn from(value: NamedPalette) -> Self {
230        match value {
231            NamedPalette::Dracula => Self::dracula(),
232            NamedPalette::PowerShell => Self::powershell(),
233            NamedPalette::Xterm => Self::xterm(),
234            NamedPalette::Ubuntu => Self::ubuntu(),
235            NamedPalette::Gjm8 => Self::gjm8(),
236        }
237    }
238}
239
240impl FromStr for NamedPalette {
241    type Err = NamedPaletteParseError;
242
243    fn from_str(s: &str) -> Result<Self, Self::Err> {
244        match s {
245            "dracula" => Ok(Self::Dracula),
246            "powershell" => Ok(Self::PowerShell),
247            "xterm" => Ok(Self::Xterm),
248            "ubuntu" => Ok(Self::Ubuntu),
249            "gjm8" => Ok(Self::Gjm8),
250            _ => Err(NamedPaletteParseError(())),
251        }
252    }
253}
254
255/// Errors that can occur when [parsing](FromStr) [`NamedPalette`] from a string.
256#[derive(Debug)]
257pub struct NamedPaletteParseError(());
258
259impl fmt::Display for NamedPaletteParseError {
260    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
261        formatter.write_str(
262            "Invalid palette name; allowed names are `dracula`, `powershell`, `xterm`, \
263             `ubuntu` and `gjm8`",
264        )
265    }
266}
267
268impl error::Error for NamedPaletteParseError {}