#[cfg(feature = "pyffi")]
use pyo3::{prelude::*, types::PyInt};
use crate::error::{HiResColorantError, OutOfBoundsError};
use crate::style::Layer;
use crate::{Color, ColorSpace};
#[cfg_attr(
feature = "pyffi",
doc = "In contrast, Python code uses the [`AnsiColor::try_from_8bit`] and
[`AnsiColor::to_8bit`] methods."
)]
#[cfg_attr(
feature = "pyffi",
pyclass(eq, eq_int, frozen, hash, ord, module = "prettypretty.color.termco")
)]
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum AnsiColor {
#[default]
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
BrightBlack,
BrightRed,
BrightGreen,
BrightYellow,
BrightBlue,
BrightMagenta,
BrightCyan,
BrightWhite,
}
#[cfg(not(feature = "pyffi"))]
impl AnsiColor {
pub fn all() -> AnsiColorIterator {
AnsiColorIterator::new()
}
}
#[cfg_attr(feature = "pyffi", pymethods)]
impl AnsiColor {
#[cfg(feature = "pyffi")]
#[staticmethod]
pub fn all() -> AnsiColorIterator {
AnsiColorIterator::new()
}
#[cfg(feature = "pyffi")]
#[staticmethod]
pub fn try_from_8bit(value: u8) -> Result<Self, OutOfBoundsError> {
Self::try_from(value)
}
#[cfg(feature = "pyffi")]
pub fn to_8bit(&self) -> u8 {
*self as u8
}
pub fn is_achromatic(&self) -> bool {
use AnsiColor::*;
matches!(*self, Black | White | BrightBlack | BrightWhite)
}
pub fn is_bright(&self) -> bool {
8 <= *self as u8
}
#[allow(clippy::missing_panics_doc)]
#[must_use = "the only reason to invoke method is to access the returned value"]
pub fn to_base(&self) -> Self {
let mut index = *self as u8;
if 8 <= index {
index -= 8;
}
AnsiColor::try_from(index)
.expect("valid index derrived from valid color must yield valid color")
}
#[allow(clippy::missing_panics_doc)]
#[must_use = "the only reason to invoke method is to access the returned value"]
pub fn to_bright(&self) -> Self {
let mut index = *self as u8;
if index < 8 {
index += 8;
}
AnsiColor::try_from(index)
.expect("valid index derrived from valid color must yield valid color")
}
pub fn name(&self) -> &'static str {
use AnsiColor::*;
match *self {
Black => "black",
Red => "red",
Green => "green",
Yellow => "yellow",
Blue => "blue",
Magenta => "magenta",
Cyan => "cyan",
White => "white",
BrightBlack => "bright black",
BrightRed => "bright red",
BrightGreen => "bright green",
BrightYellow => "bright yellow",
BrightBlue => "bright blue",
BrightMagenta => "bright magenta",
BrightCyan => "bright cyan",
BrightWhite => "bright white",
}
}
pub fn abbr(&self) -> &'static str {
use AnsiColor::*;
match *self {
Black => "bk",
Red => "rd",
Green => "gn",
Yellow => "yl",
Blue => "bu",
Magenta => "mg",
Cyan => "cn",
White => "wt",
BrightBlack => "BK",
BrightRed => "RD",
BrightGreen => "GN",
BrightYellow => "YL",
BrightBlue => "BU",
BrightMagenta => "MG",
BrightCyan => "CN",
BrightWhite => "WT",
}
}
}
impl TryFrom<u8> for AnsiColor {
type Error = OutOfBoundsError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
let ansi = match value {
0 => AnsiColor::Black,
1 => AnsiColor::Red,
2 => AnsiColor::Green,
3 => AnsiColor::Yellow,
4 => AnsiColor::Blue,
5 => AnsiColor::Magenta,
6 => AnsiColor::Cyan,
7 => AnsiColor::White,
8 => AnsiColor::BrightBlack,
9 => AnsiColor::BrightRed,
10 => AnsiColor::BrightGreen,
11 => AnsiColor::BrightYellow,
12 => AnsiColor::BrightBlue,
13 => AnsiColor::BrightMagenta,
14 => AnsiColor::BrightCyan,
15 => AnsiColor::BrightWhite,
_ => return Err(OutOfBoundsError::new(value, 0..=15)),
};
Ok(ansi)
}
}
impl From<AnsiColor> for u8 {
fn from(value: AnsiColor) -> u8 {
value as u8
}
}
#[cfg_attr(feature = "pyffi", pyclass(module = "prettypretty.color.termco"))]
#[derive(Debug)]
pub struct AnsiColorIterator {
index: usize,
}
impl AnsiColorIterator {
fn new() -> Self {
Self { index: 0 }
}
}
impl Iterator for AnsiColorIterator {
type Item = AnsiColor;
fn next(&mut self) -> Option<Self::Item> {
if 16 <= self.index {
None
} else {
let index = self.index;
self.index += 1;
Some(AnsiColor::try_from(index as u8).expect("valid index must yield valid color"))
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = 16 - self.index;
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for AnsiColorIterator {
fn len(&self) -> usize {
16 - self.index
}
}
impl core::iter::FusedIterator for AnsiColorIterator {}
#[cfg(feature = "pyffi")]
#[pymethods]
impl AnsiColorIterator {
pub fn __len__(&self) -> usize {
self.len()
}
pub fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
slf
}
pub fn __next__(mut slf: PyRefMut<'_, Self>) -> Option<AnsiColor> {
slf.next()
}
pub fn __repr__(&self) -> String {
format!("{:?}", self)
}
}
#[cfg_attr(
feature = "pyffi",
doc = "Since there is no Python feature equivalent to trait implementations in
Rust, the Python class for `EmbeddedRgb` provides equivalent functionality
through [`EmbeddedRgb::try_from_8bit`], [`EmbeddedRgb::to_8bit`],
[`EmbeddedRgb::to_24bit`], [`EmbeddedRgb::to_color`], [`EmbeddedRgb::coordinates`],
[`EmbeddedRgb::__len__`], [`EmbeddedRgb::__getitem__`], and
[`EmbeddedRgb::__repr__`]. These methods are not available in Rust."
)]
#[cfg_attr(
feature = "pyffi",
pyclass(eq, frozen, hash, sequence, module = "prettypretty.color.termco")
)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct EmbeddedRgb([u8; 3]);
#[cfg(feature = "pyffi")]
#[pymethods]
impl EmbeddedRgb {
#[new]
pub fn new(r: u8, g: u8, b: u8) -> Result<Self, OutOfBoundsError> {
if 6 <= r {
Err(OutOfBoundsError::new(r, 0..=5))
} else if 6 <= g {
Err(OutOfBoundsError::new(g, 0..=5))
} else if 6 <= b {
Err(OutOfBoundsError::new(b, 0..=5))
} else {
Ok(Self([r, g, b]))
}
}
#[staticmethod]
#[inline]
pub fn try_from_8bit(value: u8) -> Result<Self, OutOfBoundsError> {
Self::try_from(value)
}
#[inline]
pub fn to_8bit(&self) -> u8 {
u8::from(*self)
}
#[inline]
pub fn to_24bit(&self) -> [u8; 3] {
(*self).into()
}
#[inline]
pub fn to_color(&self) -> Color {
Color::from(*self)
}
#[inline]
pub fn coordinates(&self) -> [u8; 3] {
self.0
}
pub fn __len__(&self) -> usize {
3
}
pub fn __getitem__(&self, index: isize) -> PyResult<u8> {
match index {
-3..=-1 => Ok(self.0[(3 + index) as usize]),
0..=2 => Ok(self.0[index as usize]),
_ => Err(pyo3::exceptions::PyIndexError::new_err(
"Invalid coordinate index",
)),
}
}
pub fn __repr__(&self) -> String {
format!("EmbeddedRgb({}, {}, {})", self.0[0], self.0[1], self.0[2])
}
}
#[cfg(not(feature = "pyffi"))]
impl EmbeddedRgb {
pub fn new(r: u8, g: u8, b: u8) -> Result<Self, OutOfBoundsError> {
if 6 <= r {
Err(OutOfBoundsError::new(r, 0..=5))
} else if 6 <= g {
Err(OutOfBoundsError::new(g, 0..=5))
} else if 6 <= b {
Err(OutOfBoundsError::new(b, 0..=5))
} else {
Ok(Self([r, g, b]))
}
}
}
impl TryFrom<u8> for EmbeddedRgb {
type Error = OutOfBoundsError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
if !(16..=231).contains(&value) {
Err(OutOfBoundsError::new(value, 16..=231))
} else {
let mut b = value - 16;
let r = b / 36;
b -= r * 36;
let g = b / 6;
b -= g * 6;
Self::new(r, g, b)
}
}
}
impl AsRef<[u8; 3]> for EmbeddedRgb {
fn as_ref(&self) -> &[u8; 3] {
&self.0
}
}
impl core::ops::Index<usize> for EmbeddedRgb {
type Output = u8;
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
}
}
impl From<EmbeddedRgb> for u8 {
fn from(value: EmbeddedRgb) -> u8 {
let [r, g, b] = value.0;
16 + 36 * r + 6 * g + b
}
}
impl From<EmbeddedRgb> for [u8; 3] {
fn from(value: EmbeddedRgb) -> Self {
fn convert(value: u8) -> u8 {
if value == 0 {
0
} else {
55 + 40 * value
}
}
let [r, g, b] = *value.as_ref();
[convert(r), convert(g), convert(b)]
}
}
impl From<&EmbeddedRgb> for Color {
fn from(value: &EmbeddedRgb) -> Self {
Color::from(Rgb::from(*value))
}
}
impl From<EmbeddedRgb> for Color {
fn from(value: EmbeddedRgb) -> Self {
Color::from(Rgb::from(value))
}
}
#[cfg_attr(
feature = "pyffi",
doc = "Since there is no Python feature equivalent to trait implementations in
Rust, the Python class for `GrayGradient` provides equivalent functionality
through [`GrayGradient::try_from_8bit`], [`GrayGradient::__repr__`],
[`GrayGradient::to_8bit`], and [`GrayGradient::to_color`]. These methods are not
available in Rust."
)]
#[cfg_attr(
feature = "pyffi",
pyclass(eq, frozen, hash, ord, module = "prettypretty.color.termco")
)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct GrayGradient(u8);
#[cfg(feature = "pyffi")]
#[pymethods]
impl GrayGradient {
#[new]
pub fn new(value: u8) -> Result<Self, OutOfBoundsError> {
if 24 <= value {
Err(OutOfBoundsError::new(value, 0..=23))
} else {
Ok(Self(value))
}
}
#[staticmethod]
#[inline]
pub fn try_from_8bit(value: u8) -> Result<Self, OutOfBoundsError> {
Self::try_from(value)
}
#[inline]
pub fn to_8bit(&self) -> u8 {
u8::from(*self)
}
#[inline]
pub fn to_24bit(&self) -> [u8; 3] {
(*self).into()
}
#[inline]
pub fn to_color(&self) -> Color {
Color::from(*self)
}
#[inline]
pub const fn level(&self) -> u8 {
self.0
}
pub fn __repr__(&self) -> String {
format!("GrayGradient({})", self.0)
}
}
#[cfg(not(feature = "pyffi"))]
impl GrayGradient {
pub fn new(value: u8) -> Result<Self, OutOfBoundsError> {
if value <= 23 {
Ok(Self(value))
} else {
Err(OutOfBoundsError::new(value, 0..=23))
}
}
pub const fn level(&self) -> u8 {
self.0
}
}
impl TryFrom<u8> for GrayGradient {
type Error = OutOfBoundsError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
if value <= 231 {
Err(OutOfBoundsError::new(value, 232..=255))
} else {
Self::new(value - 232)
}
}
}
impl From<GrayGradient> for u8 {
fn from(value: GrayGradient) -> u8 {
232 + value.0
}
}
impl From<GrayGradient> for [u8; 3] {
fn from(value: GrayGradient) -> Self {
let level = 8 + 10 * value.level();
[level, level, level]
}
}
impl From<&GrayGradient> for Color {
fn from(value: &GrayGradient) -> Self {
Color::from(Rgb::from(*value))
}
}
impl From<GrayGradient> for Color {
fn from(value: GrayGradient) -> Self {
Color::from(Rgb::from(value))
}
}
#[cfg_attr(
feature = "pyffi",
doc = "Since there is no Python feature equivalent to trait implementations in
Rust, the Python class for `EightBitColor` provides equivalent functionality
through [`EightBitColor::from_8bit`], [`EightBitColor::to_8bit`], and
[`EightBitColor::__repr__`]. These methods are not available in Rust."
)]
#[cfg_attr(
feature = "pyffi",
pyclass(eq, frozen, hash, module = "prettypretty.color.termco")
)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum EightBitColor {
Ansi(AnsiColor),
Embedded(EmbeddedRgb),
Gray(GrayGradient),
}
#[cfg(feature = "pyffi")]
#[pymethods]
impl EightBitColor {
#[staticmethod]
pub fn from_8bit(byte: u8) -> Self {
Self::from(byte)
}
pub fn to_8bit(&self) -> u8 {
u8::from(*self)
}
pub fn __repr__(&self) -> String {
format!("{:?}", self)
}
}
impl From<AnsiColor> for EightBitColor {
fn from(value: AnsiColor) -> Self {
Self::Ansi(value)
}
}
impl From<EmbeddedRgb> for EightBitColor {
fn from(value: EmbeddedRgb) -> Self {
Self::Embedded(value)
}
}
impl From<GrayGradient> for EightBitColor {
fn from(value: GrayGradient) -> Self {
Self::Gray(value)
}
}
impl From<u8> for EightBitColor {
fn from(value: u8) -> Self {
if (0..=15).contains(&value) {
Self::Ansi(AnsiColor::try_from(value).expect("valid index must yield valid color"))
} else if (16..=231).contains(&value) {
Self::Embedded(
EmbeddedRgb::try_from(value).expect("valid index must yield valid color"),
)
} else {
Self::Gray(GrayGradient::try_from(value).expect("valid index must yield valid color"))
}
}
}
impl From<EightBitColor> for u8 {
fn from(value: EightBitColor) -> Self {
match value {
EightBitColor::Ansi(c) => c.into(),
EightBitColor::Embedded(c) => c.into(),
EightBitColor::Gray(c) => c.into(),
}
}
}
impl From<EightBitColor> for Colorant {
fn from(value: EightBitColor) -> Self {
match value {
EightBitColor::Ansi(c) => Colorant::Ansi(c),
EightBitColor::Embedded(c) => Colorant::Embedded(c),
EightBitColor::Gray(c) => Colorant::Gray(c),
}
}
}
#[cfg_attr(
feature = "pyffi",
doc = "Since there is no Python feature equivalent to trait implementations in
Rust, the Python class for `Rgb` provides equivalent functionality
through [`Rgb::from_color`], [`Rgb::to_color`], [`Rgb::coordinates`],
[`Rgb::__len__`], and [`Rgb::__getitem__`]. These methods are not available
in Rust."
)]
#[cfg_attr(
feature = "pyffi",
pyclass(eq, frozen, hash, sequence, module = "prettypretty.color.termco")
)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct Rgb([u8; 3]);
#[cfg(feature = "pyffi")]
#[pymethods]
impl Rgb {
#[new]
pub const fn new(r: u8, g: u8, b: u8) -> Self {
Self([r, g, b])
}
#[staticmethod]
pub fn from_color(color: &Color) -> Self {
let [r, g, b] = color.to_24bit();
Self::new(r, g, b)
}
pub fn to_color(&self) -> Color {
Color::from(*self)
}
pub fn coordinates(&self) -> [u8; 3] {
self.0
}
pub fn __len__(&self) -> usize {
3
}
pub fn __getitem__(&self, index: isize) -> PyResult<u8> {
match index {
-3..=-1 => Ok(self.0[(3 + index) as usize]),
0..=2 => Ok(self.0[index as usize]),
_ => Err(pyo3::exceptions::PyIndexError::new_err(
"Invalid coordinate index",
)),
}
}
pub fn weighted_euclidian_distance(&self, other: &Rgb) -> u32 {
self.do_weighted_euclidian_distance(other)
}
pub fn __repr__(&self) -> String {
format!("Rgb({}, {}, {})", self.0[0], self.0[1], self.0[2])
}
pub fn __str__(&self) -> String {
format!("{}", self)
}
}
#[cfg(not(feature = "pyffi"))]
impl Rgb {
pub const fn new(r: u8, g: u8, b: u8) -> Self {
Self([r, g, b])
}
pub fn weighted_euclidian_distance(&self, other: &Rgb) -> u32 {
self.do_weighted_euclidian_distance(other)
}
}
impl Rgb {
fn do_weighted_euclidian_distance(&self, other: &Rgb) -> u32 {
let r1 = self.0[0] as i32;
let g1 = self.0[1] as i32;
let b1 = self.0[2] as i32;
let r2 = other.0[0] as i32;
let g2 = other.0[1] as i32;
let b2 = other.0[2] as i32;
let r_sum = r1 + r2;
let r_delta = r1 - r2;
let g_delta = g1 - g2;
let b_delta = b1 - b2;
let r = (2 * 512 + r_sum) * r_delta * r_delta;
let g = 4 * g_delta * g_delta * (1 << 8);
let b = (2 * 767 - r_sum) * b_delta * b_delta;
(r + g + b) as u32
}
}
impl AsRef<[u8; 3]> for Rgb {
fn as_ref(&self) -> &[u8; 3] {
&self.0
}
}
impl core::ops::Index<usize> for Rgb {
type Output = u8;
fn index(&self, index: usize) -> &Self::Output {
&self.0[index]
}
}
impl From<EmbeddedRgb> for Rgb {
fn from(value: EmbeddedRgb) -> Self {
let [r, g, b] = Into::<[u8; 3]>::into(value);
Rgb::new(r, g, b)
}
}
impl From<GrayGradient> for Rgb {
fn from(value: GrayGradient) -> Rgb {
let [r, g, b] = Into::<[u8; 3]>::into(value);
Rgb::new(r, g, b)
}
}
impl From<[u8; 3]> for Rgb {
fn from(value: [u8; 3]) -> Self {
Rgb::new(value[0], value[1], value[2])
}
}
impl From<&Color> for Rgb {
fn from(value: &Color) -> Self {
let [r, g, b] = value.to(ColorSpace::Srgb).to_gamut().to_24bit();
Self::new(r, g, b)
}
}
impl From<Color> for Rgb {
fn from(value: Color) -> Self {
Rgb::from(&value)
}
}
impl From<&Rgb> for Color {
fn from(value: &Rgb) -> Self {
Self::from_24bit(value.0[0], value.0[1], value.0[2])
}
}
impl From<Rgb> for Color {
fn from(value: Rgb) -> Self {
Self::from_24bit(value.0[0], value.0[1], value.0[2])
}
}
impl core::fmt::Display for Rgb {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let [r, g, b] = *self.as_ref();
f.write_fmt(format_args!("#{:02x}{:02x}{:02x}", r, g, b))
}
}
#[cfg_attr(
feature = "pyffi",
pyclass(eq, frozen, hash, module = "prettypretty.color.termco")
)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Colorant {
Default(),
Ansi(AnsiColor),
Embedded(EmbeddedRgb),
Gray(GrayGradient),
Rgb(Rgb),
HiRes(Color),
}
#[cfg_attr(feature = "pyffi", pymethods)]
impl Colorant {
#[cfg(feature = "pyffi")]
#[staticmethod]
pub fn of(#[pyo3(from_py_with = crate::termco::into_colorant)] colorant: Colorant) -> Self {
colorant
}
pub fn is_default(&self) -> bool {
matches!(*self, Colorant::Default())
}
pub fn sgr_parameters(&self, layer: Layer) -> Option<Vec<u8>> {
match *self {
Self::Default() => Some(vec![39 + layer.offset()]),
Self::Ansi(c) => {
let base = if c.is_bright() { 90 } else { 30 } + layer.offset();
Some(vec![base + c.to_base() as u8])
}
Self::Embedded(c) => Some(vec![38 + layer.offset(), 5, u8::from(c)]),
Self::Gray(c) => Some(vec![38 + layer.offset(), 5, u8::from(c)]),
Self::Rgb(c) => Some(vec![38 + layer.offset(), 2, c[0], c[1], c[2]]),
Self::HiRes(_) => None,
}
}
#[cfg(feature = "pyffi")]
pub fn try_to_8bit(&self) -> PyResult<u8> {
u8::try_from(self).map_err(|_| {
pyo3::exceptions::PyValueError::new_err("unable to convert to 8-bit index")
})
}
#[cfg(feature = "pyffi")]
pub fn try_to_24bit(&self) -> PyResult<[u8; 3]> {
<[u8; 3]>::try_from(self).map_err(|_| {
pyo3::exceptions::PyValueError::new_err("unable to convert to 24-bit coordinates")
})
}
fn negate(&self) -> Option<Self> {
if self.is_default() {
None
} else {
Some(Colorant::Default())
}
}
#[cfg(feature = "pyffi")]
pub fn __neg__(&self) -> Option<Self> {
self.negate()
}
#[cfg(feature = "pyffi")]
pub fn __repr__(&self) -> String {
match *self {
Self::Default() => "Colorant(default)".to_string(),
Self::Ansi(c) => format!("Colorant({:?})", c),
Self::Embedded(c) => format!("Colorant({})", c.__repr__()),
Self::Gray(c) => format!("Colorant({})", c.__repr__()),
Self::Rgb(c) => format!("Colorant({})", c.__repr__()),
Self::HiRes(ref c) => format!("Colorant({})", c.__repr__()),
}
}
#[cfg(feature = "pyffi")]
#[pyo3(name = "display")]
pub fn py_display(&self, layer: Layer) -> PyResult<String> {
Ok(format!("{}", self.display(layer)?))
}
}
impl Colorant {
pub fn write_sgr_params(
&self,
layer: Layer,
f: &mut core::fmt::Formatter<'_>,
) -> core::fmt::Result {
match *self {
Self::Default() => write!(f, "{}", 39 + layer.offset()),
Self::Ansi(c) => {
let base = if c.is_bright() { 90 } else { 30 } + layer.offset();
write!(f, "{}", base + c.to_base() as u8)
}
Self::Embedded(c) => write!(f, "{};5;{}", 38 + layer.offset(), u8::from(c)),
Self::Gray(c) => write!(f, "{};5;{}", 38 + layer.offset(), u8::from(c)),
Self::Rgb(c) => write!(f, "{};2;{};{};{}", 38 + layer.offset(), c[0], c[1], c[2]),
Self::HiRes(_) => Ok(()),
}
}
pub fn display(
&self,
layer: Layer,
) -> Result<impl core::fmt::Display + use<'_>, HiResColorantError> {
if matches!(*self, Self::HiRes(_)) {
return Err(HiResColorantError);
}
Ok(LayeredColorant {
layer,
colorant: self,
})
}
}
impl core::ops::Neg for &Colorant {
type Output = Option<Colorant>;
fn neg(self) -> Self::Output {
self.negate()
}
}
impl core::ops::Neg for Colorant {
type Output = Option<Colorant>;
fn neg(self) -> Self::Output {
self.negate()
}
}
#[cfg(feature = "pyffi")]
pub(crate) fn into_colorant(obj: &Bound<'_, PyAny>) -> PyResult<Colorant> {
if obj.is_instance_of::<PyInt>() {
return obj.extract::<u8>().map(core::convert::Into::into);
}
obj.extract::<Colorant>()
.or_else(|_| obj.extract::<AnsiColor>().map(core::convert::Into::into))
.or_else(|_| obj.extract::<EmbeddedRgb>().map(core::convert::Into::into))
.or_else(|_| obj.extract::<GrayGradient>().map(core::convert::Into::into))
.or_else(|_| {
obj.extract::<EightBitColor>()
.map(core::convert::Into::into)
})
.or_else(|_| obj.extract::<Rgb>().map(core::convert::Into::into))
.or_else(|_| obj.extract::<Color>().map(core::convert::Into::into))
}
impl From<AnsiColor> for Colorant {
fn from(value: AnsiColor) -> Self {
Self::Ansi(value)
}
}
impl From<EmbeddedRgb> for Colorant {
fn from(value: EmbeddedRgb) -> Self {
Self::Embedded(value)
}
}
impl From<GrayGradient> for Colorant {
fn from(value: GrayGradient) -> Self {
Self::Gray(value)
}
}
impl From<u8> for Colorant {
fn from(value: u8) -> Self {
if (0..=15).contains(&value) {
Self::Ansi(AnsiColor::try_from(value).expect("valid index must yield valid color"))
} else if (16..=231).contains(&value) {
Self::Embedded(
EmbeddedRgb::try_from(value).expect("valid index must yield valid color"),
)
} else {
Self::Gray(GrayGradient::try_from(value).expect("valid index must yield valid color"))
}
}
}
impl From<[u8; 3]> for Colorant {
fn from(value: [u8; 3]) -> Self {
Self::Rgb(Rgb(value))
}
}
impl From<Rgb> for Colorant {
fn from(value: Rgb) -> Self {
Self::Rgb(value)
}
}
impl From<Color> for Colorant {
fn from(value: Color) -> Self {
Self::HiRes(value)
}
}
impl From<&Color> for Colorant {
fn from(value: &Color) -> Self {
Self::HiRes(value.clone())
}
}
impl TryFrom<&Colorant> for u8 {
type Error = Colorant;
fn try_from(value: &Colorant) -> Result<Self, Self::Error> {
match *value {
Colorant::Default() => Err(value.clone()),
Colorant::Ansi(c) => Ok(u8::from(c)),
Colorant::Embedded(c) => Ok(u8::from(c)),
Colorant::Gray(c) => Ok(u8::from(c)),
Colorant::Rgb(_) => Err(value.clone()),
Colorant::HiRes(_) => Err(value.clone()),
}
}
}
impl TryFrom<Colorant> for u8 {
type Error = Colorant;
fn try_from(value: Colorant) -> Result<Self, Self::Error> {
u8::try_from(&value)
}
}
impl TryFrom<&Colorant> for [u8; 3] {
type Error = Colorant;
fn try_from(value: &Colorant) -> Result<Self, Self::Error> {
match *value {
Colorant::Default() | Colorant::Ansi(_) => Err(value.clone()),
Colorant::Embedded(c) => Ok(c.into()),
Colorant::Gray(c) => Ok(c.into()),
Colorant::Rgb(c) => Ok(*c.as_ref()),
Colorant::HiRes(_) => Err(value.clone()),
}
}
}
impl TryFrom<Colorant> for [u8; 3] {
type Error = Colorant;
fn try_from(value: Colorant) -> Result<Self, Self::Error> {
<[u8; 3]>::try_from(&value)
}
}
impl TryFrom<Colorant> for Color {
type Error = Colorant;
fn try_from(value: Colorant) -> Result<Self, Self::Error> {
if let Colorant::HiRes(c) = value {
return Ok(c);
}
let [r, g, b] = value.try_into()?;
Ok(Color::from_24bit(r, g, b))
}
}
struct LayeredColorant<'a> {
layer: Layer,
colorant: &'a Colorant,
}
impl core::fmt::Display for LayeredColorant<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("\x1b[")?;
self.colorant.write_sgr_params(self.layer, f)?;
f.write_str("m")
}
}
#[cfg(test)]
mod test {
use super::{AnsiColor, Colorant, EmbeddedRgb, GrayGradient, OutOfBoundsError, Rgb};
use crate::Color;
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
#[test]
fn test_size() {
assert_eq!(std::mem::size_of::<AnsiColor>(), 1);
assert_eq!(std::mem::align_of::<AnsiColor>(), 1);
assert_eq!(std::mem::size_of::<EmbeddedRgb>(), 3);
assert_eq!(std::mem::align_of::<EmbeddedRgb>(), 1);
assert_eq!(std::mem::size_of::<Rgb>(), 3);
assert_eq!(std::mem::align_of::<Rgb>(), 1);
assert_eq!(std::mem::size_of::<Color>(), 32);
assert_eq!(std::mem::align_of::<Color>(), 8);
assert_eq!(std::mem::size_of::<Colorant>(), 32);
assert_eq!(std::mem::align_of::<Colorant>(), 8);
}
#[test]
fn test_conversion() -> Result<(), OutOfBoundsError> {
let magenta = AnsiColor::Magenta;
assert_eq!(magenta as u8, 5);
let green = EmbeddedRgb::new(0, 4, 0)?;
assert_eq!(green.as_ref(), &[0, 4, 0]);
assert_eq!(Rgb::from(green), Rgb::new(0, 215, 0));
let gray = GrayGradient::new(12)?;
assert_eq!(gray.level(), 12);
assert_eq!(Rgb::from(gray), Rgb::new(128, 128, 128));
let also_magenta = Colorant::Ansi(AnsiColor::Magenta);
let also_green = Colorant::Embedded(green);
let also_gray = Colorant::Gray(gray);
assert_eq!(also_magenta, Colorant::from(5));
assert_eq!(also_green, Colorant::from(40));
assert_eq!(also_gray, Colorant::from(244));
assert!(<[u8; 3]>::try_from(also_magenta).is_err());
assert_eq!(<[u8; 3]>::try_from(also_green), Ok([0_u8, 215, 0]));
assert_eq!(<[u8; 3]>::try_from(also_gray), Ok([128_u8, 128, 128]));
Ok(())
}
#[test]
fn test_limits() -> Result<(), OutOfBoundsError> {
let black_ansi = AnsiColor::try_from(0)?;
assert_eq!(black_ansi, AnsiColor::Black);
assert_eq!(u8::from(black_ansi), 0);
let white_ansi = AnsiColor::try_from(15)?;
assert_eq!(white_ansi, AnsiColor::BrightWhite);
assert_eq!(u8::from(white_ansi), 15);
let black_rgb = EmbeddedRgb::try_from(16)?;
assert_eq!(*black_rgb.as_ref(), [0_u8, 0_u8, 0_u8]);
assert_eq!(u8::from(black_rgb), 16);
let white_rgb = EmbeddedRgb::try_from(231)?;
assert_eq!(*white_rgb.as_ref(), [5_u8, 5_u8, 5_u8]);
assert_eq!(u8::from(white_rgb), 231);
let black_gray = GrayGradient::try_from(232)?;
assert_eq!(black_gray.level(), 0);
assert_eq!(u8::from(black_gray), 232);
let white_gray = GrayGradient::try_from(255)?;
assert_eq!(white_gray.level(), 23);
assert_eq!(u8::from(white_gray), 255);
Ok(())
}
}