use std::fmt::{Display, Formatter};
#[cfg(feature = "wasm")]
use serde::{Deserialize, Serialize};
use crate::color::{error::ColorError, RGB};
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "wasm", derive(Serialize, Deserialize))]
pub struct Ansi16 {
pub(crate) code: u8,
}
impl Ansi16 {
pub fn new(code: u8) -> Result<Self, ColorError> {
match code {
30..=37 | 90..=97 => Ok(Self { code }),
_ => Err(ColorError::InvalidColorCode(code)),
}
}
#[inline]
#[must_use]
pub fn foreground(&self) -> u8 {
self.code
}
#[inline]
#[must_use]
pub fn background(&self) -> u8 {
self.code + 10
}
#[inline]
#[must_use]
pub fn black() -> Self {
Self { code: 30 }
}
#[inline]
#[must_use]
pub fn red() -> Self {
Self { code: 31 }
}
#[inline]
#[must_use]
pub fn green() -> Self {
Self { code: 32 }
}
#[inline]
#[must_use]
pub fn yellow() -> Self {
Self { code: 33 }
}
#[inline]
#[must_use]
pub fn blue() -> Self {
Self { code: 34 }
}
#[inline]
#[must_use]
pub fn magenta() -> Self {
Self { code: 35 }
}
#[inline]
#[must_use]
pub fn cyan() -> Self {
Self { code: 36 }
}
#[inline]
#[must_use]
pub fn white() -> Self {
Self { code: 37 }
}
#[inline]
#[must_use]
pub fn bright_black() -> Self {
Self { code: 90 }
}
#[inline]
#[must_use]
pub fn bright_red() -> Self {
Self { code: 91 }
}
#[inline]
#[must_use]
pub fn bright_green() -> Self {
Self { code: 92 }
}
#[inline]
#[must_use]
pub fn bright_yellow() -> Self {
Self { code: 93 }
}
#[inline]
#[must_use]
pub fn bright_blue() -> Self {
Self { code: 94 }
}
#[inline]
#[must_use]
pub fn bright_magenta() -> Self {
Self { code: 95 }
}
#[inline]
#[must_use]
pub fn bright_cyan() -> Self {
Self { code: 96 }
}
#[inline]
#[must_use]
pub fn bright_white() -> Self {
Self { code: 97 }
}
}
impl Display for Ansi16 {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "ANSI16({})", self.code)
}
}
impl From<&RGB> for Ansi16 {
fn from(rgb: &RGB) -> Self {
let code = from_rgb(rgb.r, rgb.g, rgb.b);
Self { code }
}
}
#[inline]
fn from_rgb(r: u8, g: u8, b: u8) -> u8 {
let max = r.max(g).max(b);
let brightness = if max > 128 { 60 } else { 0 };
let color = (((b > 127) as u8) << 2) | (((g > 127) as u8) << 1) | ((r > 127) as u8);
color + brightness + 30
}
#[cfg(test)]
mod tests {
use rstest::rstest;
#[cfg(feature = "wasm")]
use serde_test::{assert_de_tokens, assert_ser_tokens, Token};
use super::*;
#[test]
fn test_new() {
let actual = Ansi16::new(30).unwrap();
assert_eq!(actual, Ansi16 { code: 30 });
assert_eq!(actual.foreground(), 30);
assert_eq!(actual.background(), 40);
}
#[test]
fn test_new_bright() {
let actual = Ansi16::new(90).unwrap();
assert_eq!(actual, Ansi16 { code: 90 });
assert_eq!(actual.foreground(), 90);
assert_eq!(actual.background(), 100);
}
#[test]
fn test_new_invalid() {
let actual = Ansi16::new(29);
assert!(actual.is_err());
assert_eq!(actual.unwrap_err(), ColorError::InvalidColorCode(29));
}
#[test]
#[cfg(feature = "wasm")]
fn test_serialize() {
let ansi16 = Ansi16::new(30).unwrap();
assert_ser_tokens(
&ansi16,
&[
Token::Struct {
name: "Ansi16",
len: 1,
},
Token::Str("code"),
Token::U8(30),
Token::StructEnd,
],
);
}
#[test]
#[cfg(feature = "wasm")]
fn test_deserialize() {
let ansi16 = Ansi16::new(30).unwrap();
assert_de_tokens(
&ansi16,
&[
Token::Struct {
name: "Ansi16",
len: 1,
},
Token::Str("code"),
Token::U8(30),
Token::StructEnd,
],
);
}
#[test]
fn test_fmt() {
let ansi16 = Ansi16::bright_black();
let actual = format!("{}", ansi16);
assert_eq!(actual, "ANSI16(90)");
}
#[rstest]
#[case::black((0, 0, 0), Ansi16::black())]
#[case::red((128, 0, 0), Ansi16::red())]
#[case::green((0, 128, 0), Ansi16::green())]
#[case::yellow((128, 128, 0), Ansi16::yellow())]
#[case::blue((0, 0, 128), Ansi16::blue())]
#[case::magenta((128, 0, 128), Ansi16::magenta())]
#[case::cyan((0, 128, 128), Ansi16::cyan())]
#[case::white((128, 128, 128), Ansi16::white())]
#[case::bright_red((255, 0, 0), Ansi16::bright_red())]
#[case::bright_green((0, 255, 0), Ansi16::bright_green())]
#[case::bright_yellow((255, 255, 0), Ansi16::bright_yellow())]
#[case::bright_blue((0, 0, 255), Ansi16::bright_blue())]
#[case::bright_magenta((255, 0, 255), Ansi16::bright_magenta())]
#[case::bright_cyan((0, 255, 255), Ansi16::bright_cyan())]
#[case::bright_white((255, 255, 255), Ansi16::bright_white())]
fn test_from_rgb(#[case] rgb: (u8, u8, u8), #[case] expected: Ansi16) {
let rgb = RGB::new(rgb.0, rgb.1, rgb.2);
let actual = Ansi16::from(&rgb);
assert_eq!(actual, expected);
}
}