#![deny(missing_docs)]
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use thiserror::Error;
#[derive(Copy, Clone, PartialEq, Eq, Debug, Error)]
pub enum ColorParsingError {
#[error("Invalid hex character '{0}'")]
InvalidChar(char),
#[error("Invalid length {0} - expected 1, 2, 3, 4, 6 or 8")]
InvalidLength(usize),
}
#[must_use]
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Color {
pub r: u8,
pub g: u8,
pub b: u8,
pub a: u8,
}
impl Color {
pub const WHITE: Self = Self {
r: 0xFF,
g: 0xFF,
b: 0xFF,
a: 0xFF,
};
pub const BLACK: Self = Self {
r: 0,
g: 0,
b: 0,
a: 0xFF,
};
pub const RED: Self = Self {
r: 0xFF,
g: 0,
b: 0,
a: 0xFF,
};
pub const GREEN: Self = Self {
r: 0,
g: 0xFF,
b: 0,
a: 0xFF,
};
pub const BLUE: Self = Self {
r: 0,
g: 0,
b: 0xFF,
a: 0xFF,
};
pub const CYAN: Self = Self {
r: 0,
g: 0xFF,
b: 0xFF,
a: 0xFF,
};
pub const MAGENTA: Self = Self {
r: 0xFF,
g: 0,
b: 0xFF,
a: 0xFF,
};
pub const YELLOW: Self = Self {
r: 0xFF,
g: 0xFF,
b: 0,
a: 0xFF,
};
pub const fn gray(shade: u8) -> Self {
Self {
r: shade,
g: shade,
b: shade,
a: 0xFF,
}
}
pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
Self { r, g, b, a: 0xFF }
}
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 }
}
}
const fn hex_from_byte(byte: u8) -> Option<u8> {
Some(match byte {
b'0'..=b'9' => byte - b'0',
b'a'..=b'f' => byte - b'a' + 10,
b'A'..=b'F' => byte - b'A' + 10,
_ => return None,
})
}
const fn component_from_byte(byte: u8) -> Result<u8, ColorParsingError> {
component_from_bytes(byte, byte)
}
const fn component_from_bytes(high: u8, low: u8) -> Result<u8, ColorParsingError> {
match [hex_from_byte(high), hex_from_byte(low)] {
[Some(h), Some(l)] => Ok(h * 0x10 + l),
[None, _] => Err(ColorParsingError::InvalidChar(high as char)),
[_, _] => Err(ColorParsingError::InvalidChar(low as char)),
}
}
impl FromStr for Color {
type Err = ColorParsingError;
fn from_str(name: &str) -> Result<Self, Self::Err> {
let bytes = name.as_bytes();
Ok(match *bytes {
[g] => Self::gray(component_from_byte(g)?),
[gh, gl] => Self::gray(component_from_bytes(gh, gl)?),
[r, g, b] => Self::rgb(
component_from_byte(r)?,
component_from_byte(g)?,
component_from_byte(b)?,
),
[r, g, b, a] => Self::rgba(
component_from_byte(r)?,
component_from_byte(g)?,
component_from_byte(b)?,
component_from_byte(a)?,
),
[rh, rl, gh, gl, bh, bl] => Self::rgb(
component_from_bytes(rh, rl)?,
component_from_bytes(gh, gl)?,
component_from_bytes(bh, bl)?,
),
[rh, rl, gh, gl, bh, bl, ah, al] => Self::rgba(
component_from_bytes(rh, rl)?,
component_from_bytes(gh, gl)?,
component_from_bytes(bh, bl)?,
component_from_bytes(ah, al)?,
),
_ => return Err(ColorParsingError::InvalidLength(bytes.len())),
})
}
}
#[cfg(feature = "data-stream")]
mod data_stream {
use crate::Color;
use data_stream::{FromStream, ToStream, from_stream, numbers::EndianSettings, to_stream};
use std::io::{Read, Result, Write};
impl<S: EndianSettings> ToStream<S> for Color {
fn to_stream<W: Write>(&self, writer: &mut W) -> Result<()> {
to_stream::<S, _, _>(&self.r, writer)?;
to_stream::<S, _, _>(&self.g, writer)?;
to_stream::<S, _, _>(&self.b, writer)?;
to_stream::<S, _, _>(&self.a, writer)?;
Ok(())
}
}
impl<S: EndianSettings> FromStream<S> for Color {
fn from_stream<R: Read>(reader: &mut R) -> Result<Self> {
Ok(Self {
r: from_stream::<S, _, _>(reader)?,
g: from_stream::<S, _, _>(reader)?,
b: from_stream::<S, _, _>(reader)?,
a: from_stream::<S, _, _>(reader)?,
})
}
}
}
#[cfg(feature = "parser")]
mod parser {
use crate::Color;
use token_parser::{Context, ErrorKind, Parsable, Parser, Result, Span};
impl<C: Context> Parsable<C> for Color {
fn parse_symbol(name: Box<str>, _span: Span, _context: &C) -> Result<Self> {
name.parse()
.map_err(|_| ErrorKind::StringParsing("Color").into())
}
fn parse_list(parser: &mut Parser, context: &C) -> Result<Self> {
let args: Vec<u8> = parser.parse_rest(context)?;
Ok(match args.len() {
0 => return Err(ErrorKind::NotEnoughElements(1).into()),
1 => Self::gray(args[0]),
2 => return Err(ErrorKind::NotEnoughElements(2).into()),
3 => Self::rgb(args[0], args[1], args[2]),
4 => Self::rgba(args[0], args[1], args[2], args[3]),
n => return Err(ErrorKind::TooManyElements(n - 4).into()),
})
}
}
}