anstyle/
color.rs

1/// Any ANSI color code scheme
2#[allow(clippy::exhaustive_enums)]
3#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
4pub enum Color {
5    /// Available 4-bit ANSI color palette codes
6    ///
7    /// The user's terminal defines the meaning of the each palette code.
8    Ansi(AnsiColor),
9    /// 256 (8-bit) color support
10    ///
11    /// - `0..16` are [`AnsiColor`] palette codes
12    /// - `0..232` map to [`RgbColor`] color values
13    /// - `232..` map to [`RgbColor`] gray-scale values
14    Ansi256(Ansi256Color),
15    /// 24-bit ANSI RGB color codes
16    Rgb(RgbColor),
17}
18
19impl Color {
20    /// Create a [`Style`][crate::Style] with this as the foreground
21    #[inline]
22    pub fn on(self, background: impl Into<Color>) -> crate::Style {
23        crate::Style::new()
24            .fg_color(Some(self))
25            .bg_color(Some(background.into()))
26    }
27
28    /// Create a [`Style`][crate::Style] with this as the foreground
29    #[inline]
30    pub const fn on_default(self) -> crate::Style {
31        crate::Style::new().fg_color(Some(self))
32    }
33
34    /// Render the ANSI code for a foreground color
35    #[inline]
36    pub fn render_fg(self) -> impl core::fmt::Display + Copy {
37        match self {
38            Self::Ansi(color) => color.as_fg_buffer(),
39            Self::Ansi256(color) => color.as_fg_buffer(),
40            Self::Rgb(color) => color.as_fg_buffer(),
41        }
42    }
43
44    #[inline]
45    #[cfg(feature = "std")]
46    pub(crate) fn write_fg_to(self, write: &mut dyn std::io::Write) -> std::io::Result<()> {
47        let buffer = match self {
48            Self::Ansi(color) => color.as_fg_buffer(),
49            Self::Ansi256(color) => color.as_fg_buffer(),
50            Self::Rgb(color) => color.as_fg_buffer(),
51        };
52        buffer.write_to(write)
53    }
54
55    /// Render the ANSI code for a background color
56    #[inline]
57    pub fn render_bg(self) -> impl core::fmt::Display + Copy {
58        match self {
59            Self::Ansi(color) => color.as_bg_buffer(),
60            Self::Ansi256(color) => color.as_bg_buffer(),
61            Self::Rgb(color) => color.as_bg_buffer(),
62        }
63    }
64
65    #[inline]
66    #[cfg(feature = "std")]
67    pub(crate) fn write_bg_to(self, write: &mut dyn std::io::Write) -> std::io::Result<()> {
68        let buffer = match self {
69            Self::Ansi(color) => color.as_bg_buffer(),
70            Self::Ansi256(color) => color.as_bg_buffer(),
71            Self::Rgb(color) => color.as_bg_buffer(),
72        };
73        buffer.write_to(write)
74    }
75
76    #[inline]
77    pub(crate) fn render_underline(self) -> impl core::fmt::Display + Copy {
78        match self {
79            Self::Ansi(color) => color.as_underline_buffer(),
80            Self::Ansi256(color) => color.as_underline_buffer(),
81            Self::Rgb(color) => color.as_underline_buffer(),
82        }
83    }
84
85    #[inline]
86    #[cfg(feature = "std")]
87    pub(crate) fn write_underline_to(self, write: &mut dyn std::io::Write) -> std::io::Result<()> {
88        let buffer = match self {
89            Self::Ansi(color) => color.as_underline_buffer(),
90            Self::Ansi256(color) => color.as_underline_buffer(),
91            Self::Rgb(color) => color.as_underline_buffer(),
92        };
93        buffer.write_to(write)
94    }
95}
96
97impl From<AnsiColor> for Color {
98    #[inline]
99    fn from(inner: AnsiColor) -> Self {
100        Self::Ansi(inner)
101    }
102}
103
104impl From<Ansi256Color> for Color {
105    #[inline]
106    fn from(inner: Ansi256Color) -> Self {
107        Self::Ansi256(inner)
108    }
109}
110
111impl From<RgbColor> for Color {
112    #[inline]
113    fn from(inner: RgbColor) -> Self {
114        Self::Rgb(inner)
115    }
116}
117
118impl From<u8> for Color {
119    #[inline]
120    fn from(inner: u8) -> Self {
121        Self::Ansi256(inner.into())
122    }
123}
124
125impl From<(u8, u8, u8)> for Color {
126    #[inline]
127    fn from(inner: (u8, u8, u8)) -> Self {
128        Self::Rgb(inner.into())
129    }
130}
131
132/// Available 4-bit ANSI color palette codes
133///
134/// The user's terminal defines the meaning of the each palette code.
135#[allow(clippy::exhaustive_enums)]
136#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
137#[repr(u8)]
138pub enum AnsiColor {
139    /// Black: #0 (foreground code `30`, background code `40`).
140    Black,
141
142    /// Red: #1 (foreground code `31`, background code `41`).
143    Red,
144
145    /// Green: #2 (foreground code `32`, background code `42`).
146    Green,
147
148    /// Yellow: #3 (foreground code `33`, background code `43`).
149    Yellow,
150
151    /// Blue: #4 (foreground code `34`, background code `44`).
152    Blue,
153
154    /// Magenta: #5 (foreground code `35`, background code `45`).
155    Magenta,
156
157    /// Cyan: #6 (foreground code `36`, background code `46`).
158    Cyan,
159
160    /// White: #7 (foreground code `37`, background code `47`).
161    White,
162
163    /// Bright black: #0 (foreground code `90`, background code `100`).
164    BrightBlack,
165
166    /// Bright red: #1 (foreground code `91`, background code `101`).
167    BrightRed,
168
169    /// Bright green: #2 (foreground code `92`, background code `102`).
170    BrightGreen,
171
172    /// Bright yellow: #3 (foreground code `93`, background code `103`).
173    BrightYellow,
174
175    /// Bright blue: #4 (foreground code `94`, background code `104`).
176    BrightBlue,
177
178    /// Bright magenta: #5 (foreground code `95`, background code `105`).
179    BrightMagenta,
180
181    /// Bright cyan: #6 (foreground code `96`, background code `106`).
182    BrightCyan,
183
184    /// Bright white: #7 (foreground code `97`, background code `107`).
185    BrightWhite,
186}
187
188impl AnsiColor {
189    /// Create a [`Style`][crate::Style] with this as the foreground
190    #[inline]
191    pub fn on(self, background: impl Into<Color>) -> crate::Style {
192        crate::Style::new()
193            .fg_color(Some(self.into()))
194            .bg_color(Some(background.into()))
195    }
196
197    /// Create a [`Style`][crate::Style] with this as the foreground
198    #[inline]
199    pub const fn on_default(self) -> crate::Style {
200        crate::Style::new().fg_color(Some(Color::Ansi(self)))
201    }
202
203    /// Render the ANSI code for a foreground color
204    #[inline]
205    pub fn render_fg(self) -> impl core::fmt::Display + Copy {
206        NullFormatter(self.as_fg_str())
207    }
208
209    #[inline]
210    fn as_fg_str(&self) -> &'static str {
211        match self {
212            Self::Black => escape!("3", "0"),
213            Self::Red => escape!("3", "1"),
214            Self::Green => escape!("3", "2"),
215            Self::Yellow => escape!("3", "3"),
216            Self::Blue => escape!("3", "4"),
217            Self::Magenta => escape!("3", "5"),
218            Self::Cyan => escape!("3", "6"),
219            Self::White => escape!("3", "7"),
220            Self::BrightBlack => escape!("9", "0"),
221            Self::BrightRed => escape!("9", "1"),
222            Self::BrightGreen => escape!("9", "2"),
223            Self::BrightYellow => escape!("9", "3"),
224            Self::BrightBlue => escape!("9", "4"),
225            Self::BrightMagenta => escape!("9", "5"),
226            Self::BrightCyan => escape!("9", "6"),
227            Self::BrightWhite => escape!("9", "7"),
228        }
229    }
230
231    #[inline]
232    fn as_fg_buffer(&self) -> DisplayBuffer {
233        DisplayBuffer::default().write_str(self.as_fg_str())
234    }
235
236    /// Render the ANSI code for a background color
237    #[inline]
238    pub fn render_bg(self) -> impl core::fmt::Display + Copy {
239        NullFormatter(self.as_bg_str())
240    }
241
242    #[inline]
243    fn as_bg_str(&self) -> &'static str {
244        match self {
245            Self::Black => escape!("4", "0"),
246            Self::Red => escape!("4", "1"),
247            Self::Green => escape!("4", "2"),
248            Self::Yellow => escape!("4", "3"),
249            Self::Blue => escape!("4", "4"),
250            Self::Magenta => escape!("4", "5"),
251            Self::Cyan => escape!("4", "6"),
252            Self::White => escape!("4", "7"),
253            Self::BrightBlack => escape!("10", "0"),
254            Self::BrightRed => escape!("10", "1"),
255            Self::BrightGreen => escape!("10", "2"),
256            Self::BrightYellow => escape!("10", "3"),
257            Self::BrightBlue => escape!("10", "4"),
258            Self::BrightMagenta => escape!("10", "5"),
259            Self::BrightCyan => escape!("10", "6"),
260            Self::BrightWhite => escape!("10", "7"),
261        }
262    }
263
264    #[inline]
265    fn as_bg_buffer(&self) -> DisplayBuffer {
266        DisplayBuffer::default().write_str(self.as_bg_str())
267    }
268
269    #[inline]
270    fn as_underline_buffer(&self) -> DisplayBuffer {
271        // No per-color codes; must delegate to `Ansi256Color`
272        Ansi256Color::from(*self).as_underline_buffer()
273    }
274
275    /// Change the color to/from bright
276    #[must_use]
277    #[inline]
278    pub fn bright(self, yes: bool) -> Self {
279        if yes {
280            match self {
281                Self::Black => Self::BrightBlack,
282                Self::Red => Self::BrightRed,
283                Self::Green => Self::BrightGreen,
284                Self::Yellow => Self::BrightYellow,
285                Self::Blue => Self::BrightBlue,
286                Self::Magenta => Self::BrightMagenta,
287                Self::Cyan => Self::BrightCyan,
288                Self::White => Self::BrightWhite,
289                Self::BrightBlack => self,
290                Self::BrightRed => self,
291                Self::BrightGreen => self,
292                Self::BrightYellow => self,
293                Self::BrightBlue => self,
294                Self::BrightMagenta => self,
295                Self::BrightCyan => self,
296                Self::BrightWhite => self,
297            }
298        } else {
299            match self {
300                Self::Black => self,
301                Self::Red => self,
302                Self::Green => self,
303                Self::Yellow => self,
304                Self::Blue => self,
305                Self::Magenta => self,
306                Self::Cyan => self,
307                Self::White => self,
308                Self::BrightBlack => Self::Black,
309                Self::BrightRed => Self::Red,
310                Self::BrightGreen => Self::Green,
311                Self::BrightYellow => Self::Yellow,
312                Self::BrightBlue => Self::Blue,
313                Self::BrightMagenta => Self::Magenta,
314                Self::BrightCyan => Self::Cyan,
315                Self::BrightWhite => Self::White,
316            }
317        }
318    }
319
320    /// Report whether the color is bright
321    #[inline]
322    pub fn is_bright(self) -> bool {
323        match self {
324            Self::Black => false,
325            Self::Red => false,
326            Self::Green => false,
327            Self::Yellow => false,
328            Self::Blue => false,
329            Self::Magenta => false,
330            Self::Cyan => false,
331            Self::White => false,
332            Self::BrightBlack => true,
333            Self::BrightRed => true,
334            Self::BrightGreen => true,
335            Self::BrightYellow => true,
336            Self::BrightBlue => true,
337            Self::BrightMagenta => true,
338            Self::BrightCyan => true,
339            Self::BrightWhite => true,
340        }
341    }
342}
343
344/// 256 (8-bit) color support
345///
346/// - `0..16` are [`AnsiColor`] palette codes
347/// - `0..232` map to [`RgbColor`] color values
348/// - `232..` map to [`RgbColor`] gray-scale values
349#[allow(clippy::exhaustive_structs)]
350#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
351#[repr(transparent)]
352pub struct Ansi256Color(pub u8);
353
354impl Ansi256Color {
355    /// Create a [`Style`][crate::Style] with this as the foreground
356    #[inline]
357    pub fn on(self, background: impl Into<Color>) -> crate::Style {
358        crate::Style::new()
359            .fg_color(Some(self.into()))
360            .bg_color(Some(background.into()))
361    }
362
363    /// Create a [`Style`][crate::Style] with this as the foreground
364    #[inline]
365    pub const fn on_default(self) -> crate::Style {
366        crate::Style::new().fg_color(Some(Color::Ansi256(self)))
367    }
368
369    /// Get the raw value
370    #[inline]
371    pub const fn index(self) -> u8 {
372        self.0
373    }
374
375    /// Convert to [`AnsiColor`] when there is a 1:1 mapping
376    #[inline]
377    pub const fn into_ansi(self) -> Option<AnsiColor> {
378        match self.index() {
379            0 => Some(AnsiColor::Black),
380            1 => Some(AnsiColor::Red),
381            2 => Some(AnsiColor::Green),
382            3 => Some(AnsiColor::Yellow),
383            4 => Some(AnsiColor::Blue),
384            5 => Some(AnsiColor::Magenta),
385            6 => Some(AnsiColor::Cyan),
386            7 => Some(AnsiColor::White),
387            8 => Some(AnsiColor::BrightBlack),
388            9 => Some(AnsiColor::BrightRed),
389            10 => Some(AnsiColor::BrightGreen),
390            11 => Some(AnsiColor::BrightYellow),
391            12 => Some(AnsiColor::BrightBlue),
392            13 => Some(AnsiColor::BrightMagenta),
393            14 => Some(AnsiColor::BrightCyan),
394            15 => Some(AnsiColor::BrightWhite),
395            _ => None,
396        }
397    }
398
399    /// Losslessly convert from [`AnsiColor`]
400    #[inline]
401    pub const fn from_ansi(color: AnsiColor) -> Self {
402        match color {
403            AnsiColor::Black => Self(0),
404            AnsiColor::Red => Self(1),
405            AnsiColor::Green => Self(2),
406            AnsiColor::Yellow => Self(3),
407            AnsiColor::Blue => Self(4),
408            AnsiColor::Magenta => Self(5),
409            AnsiColor::Cyan => Self(6),
410            AnsiColor::White => Self(7),
411            AnsiColor::BrightBlack => Self(8),
412            AnsiColor::BrightRed => Self(9),
413            AnsiColor::BrightGreen => Self(10),
414            AnsiColor::BrightYellow => Self(11),
415            AnsiColor::BrightBlue => Self(12),
416            AnsiColor::BrightMagenta => Self(13),
417            AnsiColor::BrightCyan => Self(14),
418            AnsiColor::BrightWhite => Self(15),
419        }
420    }
421
422    /// Render the ANSI code for a foreground color
423    #[inline]
424    pub fn render_fg(self) -> impl core::fmt::Display + Copy {
425        self.as_fg_buffer()
426    }
427
428    #[inline]
429    fn as_fg_buffer(&self) -> DisplayBuffer {
430        DisplayBuffer::default()
431            .write_str("\x1B[38;5;")
432            .write_code(self.index())
433            .write_str("m")
434    }
435
436    /// Render the ANSI code for a background color
437    #[inline]
438    pub fn render_bg(self) -> impl core::fmt::Display + Copy {
439        self.as_bg_buffer()
440    }
441
442    #[inline]
443    fn as_bg_buffer(&self) -> DisplayBuffer {
444        DisplayBuffer::default()
445            .write_str("\x1B[48;5;")
446            .write_code(self.index())
447            .write_str("m")
448    }
449
450    #[inline]
451    fn as_underline_buffer(&self) -> DisplayBuffer {
452        DisplayBuffer::default()
453            .write_str("\x1B[58;5;")
454            .write_code(self.index())
455            .write_str("m")
456    }
457}
458
459impl From<u8> for Ansi256Color {
460    #[inline]
461    fn from(inner: u8) -> Self {
462        Self(inner)
463    }
464}
465
466impl From<AnsiColor> for Ansi256Color {
467    #[inline]
468    fn from(inner: AnsiColor) -> Self {
469        Self::from_ansi(inner)
470    }
471}
472
473/// 24-bit ANSI RGB color codes
474#[allow(clippy::exhaustive_structs)]
475#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
476pub struct RgbColor(pub u8, pub u8, pub u8);
477
478impl RgbColor {
479    /// Create a [`Style`][crate::Style] with this as the foreground
480    #[inline]
481    pub fn on(self, background: impl Into<Color>) -> crate::Style {
482        crate::Style::new()
483            .fg_color(Some(self.into()))
484            .bg_color(Some(background.into()))
485    }
486
487    /// Create a [`Style`][crate::Style] with this as the foreground
488    #[inline]
489    pub const fn on_default(self) -> crate::Style {
490        crate::Style::new().fg_color(Some(Color::Rgb(self)))
491    }
492
493    /// Red
494    #[inline]
495    pub const fn r(self) -> u8 {
496        self.0
497    }
498
499    /// Green
500    #[inline]
501    pub const fn g(self) -> u8 {
502        self.1
503    }
504
505    /// Blue
506    #[inline]
507    pub const fn b(self) -> u8 {
508        self.2
509    }
510
511    /// Render the ANSI code for a foreground color
512    #[inline]
513    pub fn render_fg(self) -> impl core::fmt::Display + Copy {
514        self.as_fg_buffer()
515    }
516
517    #[inline]
518    fn as_fg_buffer(&self) -> DisplayBuffer {
519        DisplayBuffer::default()
520            .write_str("\x1B[38;2;")
521            .write_code(self.r())
522            .write_str(";")
523            .write_code(self.g())
524            .write_str(";")
525            .write_code(self.b())
526            .write_str("m")
527    }
528
529    /// Render the ANSI code for a background color
530    #[inline]
531    pub fn render_bg(self) -> impl core::fmt::Display + Copy {
532        self.as_bg_buffer()
533    }
534
535    #[inline]
536    fn as_bg_buffer(&self) -> DisplayBuffer {
537        DisplayBuffer::default()
538            .write_str("\x1B[48;2;")
539            .write_code(self.r())
540            .write_str(";")
541            .write_code(self.g())
542            .write_str(";")
543            .write_code(self.b())
544            .write_str("m")
545    }
546
547    #[inline]
548    fn as_underline_buffer(&self) -> DisplayBuffer {
549        DisplayBuffer::default()
550            .write_str("\x1B[58;2;")
551            .write_code(self.r())
552            .write_str(";")
553            .write_code(self.g())
554            .write_str(";")
555            .write_code(self.b())
556            .write_str("m")
557    }
558}
559
560impl From<(u8, u8, u8)> for RgbColor {
561    #[inline]
562    fn from(inner: (u8, u8, u8)) -> Self {
563        let (r, g, b) = inner;
564        Self(r, g, b)
565    }
566}
567
568const DISPLAY_BUFFER_CAPACITY: usize = 19;
569
570#[derive(Copy, Clone, Default, Debug)]
571struct DisplayBuffer {
572    buffer: [u8; DISPLAY_BUFFER_CAPACITY],
573    len: usize,
574}
575
576impl DisplayBuffer {
577    #[must_use]
578    #[inline(never)]
579    fn write_str(mut self, part: &'static str) -> Self {
580        for (i, b) in part.as_bytes().iter().enumerate() {
581            self.buffer[self.len + i] = *b;
582        }
583        self.len += part.len();
584        self
585    }
586
587    #[must_use]
588    #[inline(never)]
589    fn write_code(mut self, code: u8) -> Self {
590        let c1: u8 = (code / 100) % 10;
591        let c2: u8 = (code / 10) % 10;
592        let c3: u8 = code % 10;
593
594        let mut printed = true;
595        if c1 != 0 {
596            printed = true;
597            self.buffer[self.len] = b'0' + c1;
598            self.len += 1;
599        }
600        if c2 != 0 || printed {
601            self.buffer[self.len] = b'0' + c2;
602            self.len += 1;
603        }
604        // If we received a zero value we must still print a value.
605        self.buffer[self.len] = b'0' + c3;
606        self.len += 1;
607
608        self
609    }
610
611    #[inline]
612    fn as_str(&self) -> &str {
613        // SAFETY: Only `&str` can be written to the buffer
614        #[allow(unsafe_code)]
615        unsafe {
616            core::str::from_utf8_unchecked(&self.buffer[0..self.len])
617        }
618    }
619
620    #[inline]
621    #[cfg(feature = "std")]
622    fn write_to(self, write: &mut dyn std::io::Write) -> std::io::Result<()> {
623        write.write_all(self.as_str().as_bytes())
624    }
625}
626
627impl core::fmt::Display for DisplayBuffer {
628    #[inline]
629    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
630        f.write_str(self.as_str())
631    }
632}
633
634#[derive(Copy, Clone, Default, Debug)]
635struct NullFormatter(&'static str);
636
637impl core::fmt::Display for NullFormatter {
638    #[inline]
639    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
640        f.write_str(self.0)
641    }
642}
643
644#[cfg(test)]
645#[cfg(feature = "std")]
646mod test {
647    use super::*;
648
649    #[test]
650    fn max_display_buffer() {
651        let c = RgbColor(255, 255, 255);
652        let actual = c.render_fg().to_string();
653        assert_eq!(actual, "\u{1b}[38;2;255;255;255m");
654        assert_eq!(actual.len(), DISPLAY_BUFFER_CAPACITY);
655    }
656
657    #[test]
658    fn print_size_of() {
659        use std::mem::size_of;
660        dbg!(size_of::<Color>());
661        dbg!(size_of::<AnsiColor>());
662        dbg!(size_of::<Ansi256Color>());
663        dbg!(size_of::<RgbColor>());
664        dbg!(size_of::<DisplayBuffer>());
665    }
666
667    #[test]
668    fn no_align() {
669        #[track_caller]
670        fn assert_no_align(d: impl core::fmt::Display) {
671            let expected = format!("{d}");
672            let actual = format!("{d:<10}");
673            assert_eq!(expected, actual);
674        }
675
676        assert_no_align(AnsiColor::White.render_fg());
677        assert_no_align(AnsiColor::White.render_bg());
678        assert_no_align(Ansi256Color(0).render_fg());
679        assert_no_align(Ansi256Color(0).render_bg());
680        assert_no_align(RgbColor(0, 0, 0).render_fg());
681        assert_no_align(RgbColor(0, 0, 0).render_bg());
682        assert_no_align(Color::Ansi(AnsiColor::White).render_fg());
683        assert_no_align(Color::Ansi(AnsiColor::White).render_bg());
684    }
685}