#![allow(clippy::unreadable_literal)]
use std::{fmt, str::FromStr};
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct Style {
pub color_mode: ColorMode,
pub fg: Option<Color>,
pub bg: Option<Color>,
#[cfg(feature = "underline-color")]
pub underline_color: Option<Color>,
pub add_modifier: Modifier,
pub sub_modifier: Modifier,
}
impl Style {
pub const fn new() -> Self {
Self {
color_mode: ColorMode::overwrite(),
fg: None,
bg: None,
#[cfg(feature = "underline-color")]
underline_color: None,
add_modifier: Modifier::empty(),
sub_modifier: Modifier::empty(),
}
}
pub const fn fg(mut self, color: Color) -> Self {
self.fg = Some(color);
self
}
pub const fn bg(mut self, color: Color) -> Self {
self.bg = Some(color);
self
}
pub const fn color_mode(mut self, color_mode: ColorMode) -> Self {
self.color_mode = color_mode;
self
}
pub const fn add_modifier(mut self, modifier: Modifier) -> Self {
self.sub_modifier = self.sub_modifier.difference(modifier);
self.add_modifier = self.add_modifier.union(modifier);
self
}
pub const fn remove_modifier(mut self, modifier: Modifier) -> Self {
self.add_modifier = self.add_modifier.difference(modifier);
self.sub_modifier = self.sub_modifier.union(modifier);
self
}
}
impl Style {
pub fn patch<S: Into<Self>>(mut self, other: S) -> Self {
let other: Style = other.into();
match other.color_mode {
ColorMode::Overwrite => {
self.fg = other.fg.or(self.fg);
self.bg = other.bg.or(self.bg);
}
ColorMode::Additive => {
if let Some(other_fg) = other.fg {
if let Some(self_fg) = self.fg {
let other_rgb = other_fg.as_rgb();
let self_rgb = self_fg.as_rgb();
let r = other_rgb.0.saturating_add(self_rgb.0);
let g = other_rgb.1.saturating_add(self_rgb.1);
let b = other_rgb.2.saturating_add(self_rgb.2);
self.fg = Some(Color::Rgb(r, g, b));
} else {
self.fg = Some(other_fg);
}
}
if let Some(other_bg) = other.bg {
if let Some(self_bg) = self.bg {
let other_rgb = other_bg.as_rgb();
let self_rgb = self_bg.as_rgb();
let r = other_rgb.0.saturating_add(self_rgb.0);
let g = other_rgb.1.saturating_add(self_rgb.1);
let b = other_rgb.2.saturating_add(self_rgb.2);
self.bg = Some(Color::Rgb(r, g, b));
} else {
self.bg = Some(other_bg);
}
}
}
ColorMode::Subtractive => {
if let Some(other_fg) = other.fg {
if let Some(self_fg) = self.fg {
let other_rgb = other_fg.as_rgb();
let self_rgb = self_fg.as_rgb();
let r = other_rgb.0.saturating_sub(self_rgb.0);
let g = other_rgb.1.saturating_sub(self_rgb.1);
let b = other_rgb.2.saturating_sub(self_rgb.2);
self.fg = Some(Color::Rgb(r, g, b));
} else {
self.fg = Some(other_fg);
}
}
if let Some(other_bg) = other.bg {
if let Some(self_bg) = self.bg {
let other_rgb = other_bg.as_rgb();
let self_rgb = self_bg.as_rgb();
let r = other_rgb.0.saturating_sub(self_rgb.0);
let g = other_rgb.1.saturating_sub(self_rgb.1);
let b = other_rgb.2.saturating_sub(self_rgb.2);
self.bg = Some(Color::Rgb(r, g, b));
} else {
self.bg = Some(other_bg);
}
}
}
ColorMode::Blend => {
if let Some(other_fg) = other.fg {
if let Some(self_fg) = self.fg {
let other_rgb = other_fg.as_rgb();
let self_rgb = self_fg.as_rgb();
let r = other_rgb.0.saturating_add(self_rgb.0.saturating_sub(other_rgb.0));
let g = other_rgb.1.saturating_add(self_rgb.1.saturating_sub(other_rgb.1));
let b = other_rgb.2.saturating_add(self_rgb.2.saturating_sub(other_rgb.2));
self.fg = Some(Color::Rgb(r, g, b));
} else {
self.fg = Some(other_fg);
}
}
if let Some(other_bg) = other.bg {
if let Some(self_bg) = self.bg {
let other_rgb = other_bg.as_rgb();
let self_rgb = self_bg.as_rgb();
let r = other_rgb.0.saturating_add(self_rgb.0.saturating_sub(other_rgb.0));
let g = other_rgb.1.saturating_add(self_rgb.1.saturating_sub(other_rgb.1));
let b = other_rgb.2.saturating_add(self_rgb.2.saturating_sub(other_rgb.2));
self.bg = Some(Color::Rgb(r, g, b));
} else {
self.bg = Some(other_bg);
}
}
}
ColorMode::Mix => {
if let Some(other_fg) = other.fg {
if let Some(self_fg) = self.fg {
let other_rgb = other_fg.as_rgb();
let self_rgb = self_fg.as_rgb();
let r = self_rgb.0.saturating_add(other_rgb.0.saturating_sub(self_rgb.0));
let g = self_rgb.1.saturating_add(other_rgb.1.saturating_sub(self_rgb.1));
let b = self_rgb.2.saturating_add(other_rgb.2.saturating_sub(self_rgb.2));
self.fg = Some(Color::Rgb(r, g, b));
} else {
self.fg = Some(other_fg);
}
}
if let Some(other_bg) = other.bg {
if let Some(self_bg) = self.bg {
let other_rgb = other_bg.as_rgb();
let self_rgb = self_bg.as_rgb();
let r = self_rgb.0.saturating_add(other_rgb.0.saturating_sub(self_rgb.0));
let g = self_rgb.1.saturating_add(other_rgb.1.saturating_sub(self_rgb.1));
let b = self_rgb.2.saturating_add(other_rgb.2.saturating_sub(self_rgb.2));
self.bg = Some(Color::Rgb(r, g, b));
} else {
self.bg = Some(other_bg);
}
}
}
}
#[cfg(feature = "underline-color")]
{
self.underline_color = other.underline_color.or(self.underline_color);
}
self.add_modifier.remove(other.sub_modifier);
self.add_modifier.insert(other.add_modifier);
self.sub_modifier.remove(other.add_modifier);
self.sub_modifier.insert(other.sub_modifier);
self
}
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub enum ColorMode {
#[default]
Overwrite,
Additive,
Subtractive,
Blend,
Mix,
}
impl ColorMode {
pub const fn overwrite() -> Self {
Self::Overwrite
}
pub const fn additive() -> Self {
Self::Additive
}
pub const fn subtractive() -> Self {
Self::Subtractive
}
pub const fn blend() -> Self {
Self::Blend
}
pub const fn mix() -> Self {
Self::Mix
}
}
bitflags::bitflags! {
#[derive(Clone, Copy, Default, Eq, Hash, PartialEq)]
pub struct Modifier: u16 {
const BOLD = 0b0000_0000_0001;
const DIM = 0b0000_0000_0010;
const ITALIC = 0b0000_0000_0100;
const UNDERLINED = 0b0000_0000_1000;
const SLOW_BLINK = 0b0000_0001_0000;
const RAPID_BLINK = 0b0000_0010_0000;
const REVERSED = 0b0000_0100_0000;
const HIDDEN = 0b0000_1000_0000;
const CROSSED_OUT = 0b0001_0000_0000;
}
}
impl fmt::Debug for Modifier {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
if self.is_empty() {
return write!(f, "NONE");
}
write!(f, "{}", self.0)
}
}
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash)]
pub enum Color {
#[default]
Reset,
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
Gray,
DarkGray,
LightRed,
LightGreen,
LightYellow,
LightBlue,
LightMagenta,
LightCyan,
White,
Rgb(u8, u8, u8),
Indexed(u8),
}
impl Color {
pub const fn from_u32(u: u32) -> Self {
let r = (u >> 16) as u8;
let g = (u >> 8) as u8;
let b = u as u8;
Self::Rgb(r, g, b)
}
pub fn as_rgb(&self) -> (u8, u8, u8) {
match self {
Self::Rgb(r, g, b) => (*r, *g, *b),
_ => (0, 0, 0),
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct ParseColorError;
impl fmt::Display for ParseColorError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Failed to parse Colors")
}
}
impl std::error::Error for ParseColorError {}
impl FromStr for Color {
type Err = ParseColorError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(
match s
.to_lowercase()
.replace([' ', '-', '_'], "")
.replace("bright", "light")
.replace("grey", "gray")
.replace("silver", "gray")
.replace("lightblack", "darkgray")
.replace("lightwhite", "white")
.replace("lightgray", "white")
.as_ref()
{
"reset" => Self::Reset,
"black" => Self::Black,
"red" => Self::Red,
"green" => Self::Green,
"yellow" => Self::Yellow,
"blue" => Self::Blue,
"magenta" => Self::Magenta,
"cyan" => Self::Cyan,
"gray" => Self::Gray,
"darkgray" => Self::DarkGray,
"lightred" => Self::LightRed,
"lightgreen" => Self::LightGreen,
"lightyellow" => Self::LightYellow,
"lightblue" => Self::LightBlue,
"lightmagenta" => Self::LightMagenta,
"lightcyan" => Self::LightCyan,
"white" => Self::White,
_ => {
if let Ok(index) = s.parse::<u8>() {
Self::Indexed(index)
} else if let Some((r, g, b)) = parse_hex_color(s) {
Self::Rgb(r, g, b)
} else {
return Err(ParseColorError);
}
}
},
)
}
}
fn parse_hex_color(input: &str) -> Option<(u8, u8, u8)> {
if !input.starts_with('#') || input.len() != 7 {
return None;
}
let r = u8::from_str_radix(input.get(1..3)?, 16).ok()?;
let g = u8::from_str_radix(input.get(3..5)?, 16).ok()?;
let b = u8::from_str_radix(input.get(5..7)?, 16).ok()?;
Some((r, g, b))
}
impl fmt::Display for Color {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Reset => write!(f, "Reset"),
Self::Black => write!(f, "Black"),
Self::Red => write!(f, "Red"),
Self::Green => write!(f, "Green"),
Self::Yellow => write!(f, "Yellow"),
Self::Blue => write!(f, "Blue"),
Self::Magenta => write!(f, "Magenta"),
Self::Cyan => write!(f, "Cyan"),
Self::Gray => write!(f, "Gray"),
Self::DarkGray => write!(f, "DarkGray"),
Self::LightRed => write!(f, "LightRed"),
Self::LightGreen => write!(f, "LightGreen"),
Self::LightYellow => write!(f, "LightYellow"),
Self::LightBlue => write!(f, "LightBlue"),
Self::LightMagenta => write!(f, "LightMagenta"),
Self::LightCyan => write!(f, "LightCyan"),
Self::White => write!(f, "White"),
Self::Rgb(r, g, b) => write!(f, "#{r:02X}{g:02X}{b:02X}"),
Self::Indexed(i) => write!(f, "{i}"),
}
}
}
impl Color {
pub fn from_hsl(h: f64, s: f64, l: f64) -> Self {
let h = h.clamp(0.0, 360.0);
let s = s.clamp(0.0, 100.0);
let l = l.clamp(0.0, 100.0);
normalized_hsl_to_rgb(h / 360.0, s / 100.0, l / 100.0)
}
}
fn normalized_hsl_to_rgb(hue: f64, saturation: f64, lightness: f64) -> Color {
let red: f64;
let green: f64;
let blue: f64;
if saturation == 0.0 {
red = lightness;
green = lightness;
blue = lightness;
} else {
let q = if lightness < 0.5 {
lightness * (1.0 + saturation)
} else {
lightness + saturation - lightness * saturation
};
let p = 2.0 * lightness - q;
red = hue_to_rgb(p, q, hue + 1.0 / 3.0);
green = hue_to_rgb(p, q, hue);
blue = hue_to_rgb(p, q, hue - 1.0 / 3.0);
}
Color::Rgb(
(red * 255.0).round() as u8,
(green * 255.0).round() as u8,
(blue * 255.0).round() as u8,
)
}
fn hue_to_rgb(p: f64, q: f64, t: f64) -> f64 {
let mut t = t;
if t < 0.0 {
t += 1.0;
}
if t > 1.0 {
t -= 1.0;
}
if t < 1.0 / 6.0 {
p + (q - p) * 6.0 * t
} else if t < 1.0 / 2.0 {
q
} else if t < 2.0 / 3.0 {
p + (q - p) * (2.0 / 3.0 - t) * 6.0
} else {
p
}
}