ansi_gfx/
lib.rs

1/*!
2Simple ANSI graphics code formatter.
3
4# Examples
5
6Set attributes:
7
8```
9println!(
10	"{}Hello {}world!{}",
11	ansi_gfx::BOLD, ansi_gfx::UNDERLINE, ansi_gfx::RESET);
12```
13
14<pre style="background-color: black; color: lightgray;"><span style="font-weight: bold;">Hello <span style="text-decoration: underline;">world!</span></span></pre>
15
16Change text foreground and background color:
17
18```
19println!(
20	concat!(
21		"Foreground {}Red {}Green {}and {}Blue{}!\n",
22		"Background {}Red {}Green {}and {}Blue{}!"),
23	ansi_gfx::RED, ansi_gfx::GREEN, ansi_gfx::RESET, ansi_gfx::BLUE, ansi_gfx::RESET,
24	ansi_gfx::RED_BG, ansi_gfx::GREEN_BG, ansi_gfx::RESET, ansi_gfx::BLUE_BG, ansi_gfx::RESET);
25```
26
27<pre style="background-color: black; color: lightgray;">Foreground <span style="color: red;">Red </span><span style="color: green;">Green </span>and <span style="color: blue;">Blue</span>!
28Foreground <span style="background-color: red;">Red </span><span style="background-color: green;">Green </span>and <span style="background-color: blue;">Blue</span>!</pre>
29
30Combine attributes, colors and extended colors using the [`mode!`] macro:
31
32```
33println!(
34	"{}Here comes the sun!{}",
35	ansi_gfx::mode!(UNDERLINE; FG RGB 243, 159, 24; BG PAL 28),
36	ansi_gfx::RESET);
37```
38
39<pre style="background-color: black; color: lightgrey;"><span style="background-color: rgb(0, 135, 0); color: rgb(243, 159, 24); text-decoration: underline;">Hello world!</span></pre>
40
41*/
42
43#![cfg_attr(not(test), no_std)]
44
45
46use core::{fmt, slice, str};
47
48/// ANSI graphics mode builder for complex codes.
49///
50/// Returns an instance of [`Print`].
51///
52/// The macro accepts any number of arguments separated by a semicolon `;`.
53///
54/// A single argument can be:
55/// * A code name identifier (e.g. `BOLD`). See [`codes`] for a list of all codes.
56/// * A runtime [`Code`] value (e.g. `{ansi_gfx::BOLD}`).
57/// * A foreground palette color (e.g. `FG PAL 28`).
58/// * A background palette color (e.g. `BG PAL 28`).
59/// * A foreground RGB color (e.g. `FG RGB 255, 0, 0`).
60/// * A background RGB color (e.g. `BG RGB 255, 0, 0`).
61///
62/// # Examples
63///
64/// ```
65/// println!("{}Bold and underlined{}\n", ansi_gfx::mode!(BOLD; UNDERLINE), ansi_gfx::RESET);
66/// println!("{}Red on yellow{}\n", ansi_gfx::mode!(FG PAL 9; BG PAL 11), ansi_gfx::RESET);
67/// let style = ansi_gfx::INVERSE;
68/// println!("{}Inverted{}\n", ansi_gfx::mode!({style}; BOLD), ansi_gfx::RESET);
69/// ```
70///
71/// <pre style="background-color: black; color: lightgray;"><span style="font-weight: bold; text-decoration: underline;">Bold and underlined</span>
72/// <span style="color: rgb(239, 41, 41); background-color: rgb(252, 233, 79);">Red on yellow</span>
73/// <span style="background-color: lightgray; color: black;">Inverted</span></pre>
74#[macro_export]
75macro_rules! mode {
76	($($tt:tt)*) => {
77		$crate::__mode!([] $($tt)*)
78	};
79}
80
81#[doc(hidden)]
82#[macro_export]
83macro_rules! __mode {
84	// Palette
85	([$($code:expr,)*] $ground:ident $space:ident $index:expr; $($tail:tt)*) => {
86		$crate::__mode!([
87			$($code,)*
88			$crate::__FG_or_BG::$ground.__byte,
89			$crate::__RGB_or_PAL::$space.__byte,
90			{ let index: u8 = $index; index },
91		] $($tail)*)
92	};
93	([$($code:expr,)*] $ground:ident $space:ident $index:expr) => {
94		$crate::__mode!([
95			$($code,)*
96			$crate::__FG_or_BG::$ground.__byte,
97			$crate::__RGB_or_PAL::$space.__byte,
98			{ let index: u8 = $index; index },
99		])
100	};
101
102	// RGB
103	([$($code:expr,)*] $ground:ident $space:ident $red:expr, $green:expr, $blue:expr; $($tail:tt)*) => {
104		$crate::__mode!([
105			$($code,)*
106			$crate::__FG_or_BG::$ground.__byte,
107			$crate::__RGB_or_PAL::$space.__byte,
108			{ let red: u8 = $red; red },
109			{ let green: u8 = $green; green },
110			{ let blue: u8 = $blue; blue },
111		] $($tail)*)
112	};
113	([$($code:expr,)*] $ground:ident $space:ident $red:expr, $green:expr, $blue:expr) => {
114		$crate::__mode!([
115			$($code,)*
116			$crate::__FG_or_BG::$ground.__byte,
117			$crate::__RGB_or_PAL::$space.__byte,
118			{ let red: u8 = $red; red },
119			{ let green: u8 = $green; green },
120			{ let blue: u8 = $blue; blue },
121		])
122	};
123
124	// Identifier
125	([$($code:expr,)*] $name:ident; $($tail:tt)*) => {
126		$crate::__mode!([
127			$($code,)*
128			$crate::codes::$name.__byte,
129		] $($tail)*)
130	};
131	([$($code:expr,)*] $name:ident) => {
132		$crate::__mode!([
133			$($code,)*
134			$crate::codes::$name.__byte,
135		])
136	};
137
138	// Runtime value
139	([$($code:expr,)*] {$v:expr}; $($tail:tt)*) => {
140		$crate::__mode!([
141			$($code,)*
142			{ let v: $crate::Code = $v; v.__byte },
143		] $($tail)*)
144	};
145	([$($code:expr,)*] {$v:expr}) => {
146		$crate::__mode!([
147			$($code,)*
148			{ let v: $crate::Code = $v; v.__byte },
149		])
150	};
151
152	// Term
153	([$($code:expr,)*]) => {
154		$crate::Print { __codes: [$($code),*] }
155	};
156}
157
158/// Single ANSI graphics code.
159#[derive(Copy, Clone, Eq, PartialEq)]
160#[repr(transparent)]
161pub struct Code {
162	#[doc(hidden)]
163	pub __byte: u8,
164}
165
166#[doc(hidden)]
167#[allow(non_snake_case)]
168pub mod __FG_or_BG {
169	use super::Code;
170
171	/// Sets the extended foreground color.
172	pub const FG: Code = Code { __byte: 38 };
173	/// Sets the extended background color.
174	pub const BG: Code = Code { __byte: 48 };
175}
176
177#[doc(hidden)]
178#[allow(non_snake_case)]
179pub mod __RGB_or_PAL {
180	use super::Code;
181
182	/// Use the 256-bit color palette.
183	pub const PAL: Code = Code { __byte: 5 };
184	/// Use the true color mode.
185	pub const RGB: Code = Code { __byte: 2 };
186}
187
188/// Color codes and attributes.
189///
190/// These constants are re-exported at the crate root for convenience.
191pub mod codes {
192	use super::Code;
193
194	/// Set bold mode.
195	pub const BOLD: Code = Code { __byte: 1 };
196	/// Set dim/faint mode.
197	pub const DIM: Code = Code { __byte: 2 };
198	/// Set italic mode.
199	pub const ITALIC: Code = Code { __byte: 3 };
200	/// Set underline mode.
201	pub const UNDERLINE: Code = Code { __byte: 4 };
202	/// Set blinking mode.
203	pub const BLINK: Code = Code { __byte: 5 };
204	/// Flip foreground and background colors.
205	pub const INVERSE: Code = Code { __byte: 7 };
206	/// Set hidden/invisible mode.
207	pub const HIDDEN: Code = Code { __byte: 8 };
208	/// Set strikethrough mode.
209	pub const STRIKE: Code = Code { __byte: 9 };
210
211	/// Reset all attributes.
212	pub const RESET: Code = Code { __byte: 0 };
213	/// Reset bold/dim mode.
214	pub const RESET_WEIGHT: Code = Code { __byte: 22 };
215	/// Reset italic mode.
216	pub const RESET_ITALIC: Code = Code { __byte: 23 };
217	/// Reset underline mode.
218	pub const RESET_UNDERLINE: Code = Code { __byte: 24 };
219	/// Reset blinking mode.
220	pub const RESET_BLINK: Code = Code { __byte: 25 };
221	/// Reset inverse mode.
222	pub const RESET_INVERSE: Code = Code { __byte: 27 };
223	/// Reset hidden mode.
224	pub const RESET_HIDDEN: Code = Code { __byte: 28 };
225	/// Reset strikethrough mode.
226	pub const RESET_STRIKE: Code = Code { __byte: 29 };
227
228	/// Black foreground color.
229	pub const BLACK: Code = Code { __byte: 30 };
230	/// Red foreground color.
231	pub const RED: Code = Code { __byte: 31 };
232	/// Green foreground color.
233	pub const GREEN: Code = Code { __byte: 32 };
234	/// Yellow foreground color.
235	pub const YELLOW: Code = Code { __byte: 33 };
236	/// Blue foreground color.
237	pub const BLUE: Code = Code { __byte: 34 };
238	/// Magenta foreground color.
239	pub const MAGENTA: Code = Code { __byte: 35 };
240	/// Cyan foreground color.
241	pub const CYAN: Code = Code { __byte: 36 };
242	/// White foreground color.
243	pub const WHITE: Code = Code { __byte: 37 };
244	/// Default foreground color.
245	pub const DEFAULT: Code = Code { __byte: 39 };
246
247	/// Black background color.
248	pub const BLACK_BG: Code = Code { __byte: 40 };
249	/// Red background color.
250	pub const RED_BG: Code = Code { __byte: 41 };
251	/// Green background color.
252	pub const GREEN_BG: Code = Code { __byte: 42 };
253	/// Yellow background color.
254	pub const YELLOW_BG: Code = Code { __byte: 43 };
255	/// Blue background color.
256	pub const BLUE_BG: Code = Code { __byte: 44 };
257	/// Magenta background color.
258	pub const MAGENTA_BG: Code = Code { __byte: 45 };
259	/// Cyan background color.
260	pub const CYAN_BG: Code = Code { __byte: 46 };
261	/// White background color.
262	pub const WHITE_BG: Code = Code { __byte: 47 };
263	/// Default background color.
264	pub const DEFAULT_BG: Code = Code { __byte: 49 };
265
266	/// Bright black foreground color.
267	pub const BRIGHT_BLACK: Code = Code { __byte: 90 };
268	/// Bright red foreground color.
269	pub const BRIGHT_RED: Code = Code { __byte: 91 };
270	/// Bright green foreground color.
271	pub const BRIGHT_GREEN: Code = Code { __byte: 92 };
272	/// Bright yellow foreground color.
273	pub const BRIGHT_YELLOW: Code = Code { __byte: 93 };
274	/// Bright blue foreground color.
275	pub const BRIGHT_BLUE: Code = Code { __byte: 94 };
276	/// Bright magenta foreground color.
277	pub const BRIGHT_MAGENTA: Code = Code { __byte: 95 };
278	/// Bright cyan foreground color.
279	pub const BRIGHT_CYAN: Code = Code { __byte: 96 };
280	/// Bright white foreground color.
281	pub const BRIGHT_WHITE: Code = Code { __byte: 97 };
282
283	/// Bright black background color.
284	pub const BRIGHT_BLACK_BG: Code = Code { __byte: 100 };
285	/// Bright red background color.
286	pub const BRIGHT_RED_BG: Code = Code { __byte: 101 };
287	/// Bright green background color.
288	pub const BRIGHT_GREEN_BG: Code = Code { __byte: 102 };
289	/// Bright yellow background color.
290	pub const BRIGHT_YELLOW_BG: Code = Code { __byte: 103 };
291	/// Bright blue background color.
292	pub const BRIGHT_BLUE_BG: Code = Code { __byte: 104 };
293	/// Bright magenta background color.
294	pub const BRIGHT_MAGENTA_BG: Code = Code { __byte: 105 };
295	/// Bright cyan background color.
296	pub const BRIGHT_CYAN_BG: Code = Code { __byte: 106 };
297	/// Bright white background color.
298	pub const BRIGHT_WHITE_BG: Code = Code { __byte: 107 };
299}
300
301pub use self::codes::*;
302
303impl fmt::Display for Code {
304	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
305		let mut buf = [0u8; 8];
306		f.write_str(display(slice::from_ref(&self.__byte), &mut buf).ok_or(fmt::Error)?)?;
307		Ok(())
308	}
309}
310
311impl fmt::Debug for Code {
312	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
313		write!(f, "\"\\x1b[{}m\"", self.__byte)
314	}
315}
316
317/// Format graphics codes as an ANSI escape sequence.
318///
319/// Create an instance using the [`mode!`] macro.
320pub struct Print<T: AsRef<[u8]>> {
321	#[doc(hidden)]
322	pub __codes: T,
323}
324
325impl<T: AsRef<[u8]>> Print<T> {
326	/// Normalizes the generic type to `&[u8]`.
327	pub fn erase<'a>(&'a self) -> Print<&'a [u8]> {
328		Print { __codes: self.__codes.as_ref() }
329	}
330}
331
332impl<T: AsRef<[u8]>> fmt::Display for Print<T> {
333	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
334		let codes = self.__codes.as_ref();
335		if codes.len() > 0 {
336			let mut buf = [0u8; 64];
337			f.write_str(display(codes, &mut buf).ok_or(fmt::Error)?)?;
338		}
339		Ok(())
340	}
341}
342
343impl<T: AsRef<[u8]>> fmt::Debug for Print<T> {
344	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
345		debug(self.__codes.as_ref(), f)
346	}
347}
348
349#[inline(never)]
350fn debug(codes: &[u8], f: &mut fmt::Formatter) -> fmt::Result {
351	if codes.len() > 0 {
352		write!(f, "\"\\x1b[")?;
353		for i in 0..codes.len() {
354			let suffix = if i + 1 == codes.len() { 'm' } else { ';' };
355			write!(f, "{}{}", codes[i], suffix)?;
356		}
357		write!(f, "\"")?;
358	}
359	Ok(())
360}
361
362#[inline]
363fn display_code(mut code: u8, suffix: u8, buf: &mut [u8]) -> usize {
364	if buf.len() < 4 {
365		return 0;
366	}
367
368	let mut i = 0;
369	if code >= 100 {
370		buf[i] = b'0' + code / 100;
371		code = code % 100;
372		i += 1;
373	}
374	if code >= 10 {
375		buf[i] = b'0' + code / 10;
376		code = code % 10;
377		i += 1;
378	}
379	buf[i] = b'0' + code;
380	i += 1;
381	buf[i] = suffix;
382	i += 1;
383	return i;
384}
385
386#[inline(never)]
387fn display<'a>(codes: &[u8], buf: &'a mut [u8]) -> Option<&'a str> {
388	if buf.len() < 3 {
389		return None;
390	}
391	buf[0] = 0x1b;
392	buf[1] = b'[';
393	let mut total = 2;
394	{
395		let mut buf = &mut buf[2..];
396		for i in 0..codes.len() {
397			let suffix = if i + 1 == codes.len() { b'm' } else { b';' };
398			let skip = display_code(codes[i], suffix, buf);
399			if skip == 0 {
400				return None;
401			}
402			total += skip;
403			buf = &mut buf[skip..];
404		}
405	}
406	let buf = &buf.get(..total)?;
407	unsafe { Some(str::from_utf8_unchecked(buf)) }
408}
409
410#[cfg(test)]
411mod tests;