#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::str::FromStr;
#[derive(Copy, Clone, Debug)]
pub enum ColorParsingError {
InvalidChar(char),
InvalidLength(usize),
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Color {
pub r: u8,
pub b: u8,
pub g: u8,
pub a: u8,
}
impl Color {
pub const WHITE: Self = Self {
r: 255,
g: 255,
b: 255,
a: 255,
};
pub const BLACK: Self = Self {
r: 0,
g: 0,
b: 0,
a: 255,
};
pub const RED: Self = Self {
r: 255,
g: 0,
b: 0,
a: 255,
};
pub const GREEN: Self = Self {
r: 0,
g: 255,
b: 0,
a: 255,
};
pub const BLUE: Self = Self {
r: 0,
g: 0,
b: 255,
a: 255,
};
pub const CYAN: Self = Self {
r: 0,
g: 255,
b: 255,
a: 255,
};
pub const MAGENTA: Self = Self {
r: 255,
g: 0,
b: 255,
a: 255,
};
pub const YELLOW: Self = Self {
r: 255,
g: 255,
b: 0,
a: 255,
};
pub const fn gray(shade: u8) -> Self {
Self {
r: shade,
g: shade,
b: shade,
a: 255,
}
}
pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
Self { r, g, b, a: 255 }
}
pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
Self { r, g, b, a }
}
pub const fn r(self, r: u8) -> Self {
Self { r, ..self }
}
pub const fn g(self, g: u8) -> Self {
Self { g, ..self }
}
pub const fn b(self, b: u8) -> Self {
Self { b, ..self }
}
pub const fn a(self, a: u8) -> Self {
Self { a, ..self }
}
}
fn hex_from_char(c: char) -> Option<u8> {
Some(match c {
'0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => c as u8 - b'0',
'a' | 'b' | 'c' | 'd' | 'e' | 'f' => c as u8 - b'a' + 10,
'A' | 'B' | 'C' | 'D' | 'E' | 'F' => c as u8 - b'A' + 10,
_ => return None,
})
}
#[inline]
fn component_from_char(c: char) -> Result<u8, ColorParsingError> {
component_from_chars(c, c)
}
fn component_from_chars(cc: char, c: char) -> Result<u8, ColorParsingError> {
match (hex_from_char(cc), hex_from_char(c)) {
(Some(nn), Some(n)) => Ok(nn * 0x10 + n),
(None, _) => Err(ColorParsingError::InvalidChar(cc)),
(_, None) => Err(ColorParsingError::InvalidChar(c)),
}
}
impl FromStr for Color {
type Err = ColorParsingError;
fn from_str(name: &str) -> Result<Self, Self::Err> {
let len = name.len();
let mut chars = name.chars();
Ok(unsafe {
match len {
0 => Self::WHITE,
1 => Self::gray(component_from_char(chars.next().unwrap_unchecked())?),
2 => Self::gray(component_from_chars(
chars.next().unwrap_unchecked(),
chars.next().unwrap_unchecked(),
)?),
3 => Self::rgb(
component_from_char(chars.next().unwrap_unchecked())?,
component_from_char(chars.next().unwrap_unchecked())?,
component_from_char(chars.next().unwrap_unchecked())?,
),
4 => Self::rgba(
component_from_char(chars.next().unwrap_unchecked())?,
component_from_char(chars.next().unwrap_unchecked())?,
component_from_char(chars.next().unwrap_unchecked())?,
component_from_char(chars.next().unwrap_unchecked())?,
),
6 => Self::rgb(
component_from_chars(
chars.next().unwrap_unchecked(),
chars.next().unwrap_unchecked(),
)?,
component_from_chars(
chars.next().unwrap_unchecked(),
chars.next().unwrap_unchecked(),
)?,
component_from_chars(
chars.next().unwrap_unchecked(),
chars.next().unwrap_unchecked(),
)?,
),
8 => Self::rgba(
component_from_chars(
chars.next().unwrap_unchecked(),
chars.next().unwrap_unchecked(),
)?,
component_from_chars(
chars.next().unwrap_unchecked(),
chars.next().unwrap_unchecked(),
)?,
component_from_chars(
chars.next().unwrap_unchecked(),
chars.next().unwrap_unchecked(),
)?,
component_from_chars(
chars.next().unwrap_unchecked(),
chars.next().unwrap_unchecked(),
)?,
),
_ => return Err(ColorParsingError::InvalidLength(len)),
}
})
}
}
#[cfg(feature = "data-stream")]
mod data_stream {
use crate::Color;
use data_stream::{from_stream, numbers::LittleEndian, to_stream, FromStream, ToStream};
use std::io::{Read, Result, Write};
impl<S> ToStream<S> for Color {
fn to_stream<W: Write>(&self, writer: &mut W) -> Result<()> {
to_stream::<LittleEndian, _, _>(&self.r, writer)?;
to_stream::<LittleEndian, _, _>(&self.g, writer)?;
to_stream::<LittleEndian, _, _>(&self.b, writer)?;
to_stream::<LittleEndian, _, _>(&self.a, writer)?;
Ok(())
}
}
impl<S> FromStream<S> for Color {
fn from_stream<R: Read>(reader: &mut R) -> Result<Self> {
Ok(Self {
r: from_stream::<LittleEndian, _, _>(reader)?,
g: from_stream::<LittleEndian, _, _>(reader)?,
b: from_stream::<LittleEndian, _, _>(reader)?,
a: from_stream::<LittleEndian, _, _>(reader)?,
})
}
}
}
#[cfg(feature = "parser")]
mod parser {
use crate::Color;
use token_parser::{Context, Error, Parsable, Parser, Result};
impl<C: Context> Parsable<C> for Color {
fn parse_symbol(name: String, _context: &C) -> Result<Self> {
if let Ok(color) = name.parse() {
Ok(color)
} else {
Err(Error::StringParsing)
}
}
fn parse_list(parser: &mut Parser, context: &C) -> Result<Self> {
let args: Vec<u8> = parser.parse_rest(context)?;
Ok(match args.len() {
0 => Color::WHITE,
1 => Color::gray(args[0]),
2 => return Err(Error::NotEnoughElements(2)),
3 => Color::rgb(args[0], args[1], args[2]),
4 => Color::rgba(args[0], args[1], args[2], args[3]),
n => return Err(Error::TooManyElements(n - 4)),
})
}
}
}