#[derive(Debug, Clone)]
pub enum Color {
Rgb(u8, u8, u8),
None,
Css(Box<str>),
}
impl Color {
#[inline]
pub fn write_svg(&self, buf: &mut String) {
match self {
Color::Rgb(r, g, b) => {
const HEX: &[u8; 16] = b"0123456789abcdef";
let bytes = [
b'#',
HEX[(*r >> 4) as usize],
HEX[(*r & 0xf) as usize],
HEX[(*g >> 4) as usize],
HEX[(*g & 0xf) as usize],
HEX[(*b >> 4) as usize],
HEX[(*b & 0xf) as usize],
];
buf.push_str(unsafe { std::str::from_utf8_unchecked(&bytes) });
}
Color::None => buf.push_str("none"),
Color::Css(s) => buf.push_str(s),
}
}
pub fn to_svg_string(&self) -> String {
let mut s = String::with_capacity(7);
self.write_svg(&mut s);
s
}
}
impl From<&str> for Color {
#[inline]
fn from(s: &str) -> Self {
parse_color_str(s)
}
}
impl From<String> for Color {
#[inline]
fn from(s: String) -> Self {
parse_color_str(&s)
}
}
impl From<&String> for Color {
#[inline]
fn from(s: &String) -> Self {
parse_color_str(s.as_str())
}
}
fn parse_color_str(s: &str) -> Color {
if s.is_empty() || s.eq_ignore_ascii_case("none") || s.eq_ignore_ascii_case("transparent") {
return Color::None;
}
if s.len() == 7 && s.as_bytes()[0] == b'#' {
if let (Ok(r), Ok(g), Ok(b)) = (
u8::from_str_radix(&s[1..3], 16),
u8::from_str_radix(&s[3..5], 16),
u8::from_str_radix(&s[5..7], 16),
) {
return Color::Rgb(r, g, b);
}
}
if s.len() == 4 && s.as_bytes()[0] == b'#' {
if let (Ok(r), Ok(g), Ok(b)) = (
u8::from_str_radix(&s[1..2], 16),
u8::from_str_radix(&s[2..3], 16),
u8::from_str_radix(&s[3..4], 16),
) {
return Color::Rgb(r * 17, g * 17, b * 17);
}
}
if let Some(inner) = s.strip_prefix("rgb(").and_then(|t| t.strip_suffix(')')) {
let parts: Vec<&str> = inner.split(',').collect();
if parts.len() == 3 {
if let (Ok(r), Ok(g), Ok(b)) = (
parts[0].trim().parse::<f64>(),
parts[1].trim().parse::<f64>(),
parts[2].trim().parse::<f64>(),
) {
return Color::Rgb(r.round() as u8, g.round() as u8, b.round() as u8);
}
}
}
match s.to_ascii_lowercase().as_str() {
"black" => Color::Rgb(0, 0, 0),
"white" => Color::Rgb(255, 255, 255),
"red" => Color::Rgb(255, 0, 0),
"green" => Color::Rgb(0, 128, 0),
"blue" => Color::Rgb(0, 0, 255),
"steelblue" => Color::Rgb(70, 130, 180),
"gray" | "grey" => Color::Rgb(128, 128, 128),
"lightgray" | "lightgrey" => Color::Rgb(211, 211, 211),
"darkgray" | "darkgrey" => Color::Rgb(169, 169, 169),
"orange" => Color::Rgb(255, 165, 0),
"yellow" => Color::Rgb(255, 255, 0),
"purple" => Color::Rgb(128, 0, 128),
"pink" => Color::Rgb(255, 192, 203),
"brown" => Color::Rgb(165, 42, 42),
"cyan" => Color::Rgb(0, 255, 255),
"magenta" => Color::Rgb(255, 0, 255),
"coral" => Color::Rgb(255, 127, 80),
"salmon" => Color::Rgb(250, 128, 114),
"navy" => Color::Rgb(0, 0, 128),
"teal" => Color::Rgb(0, 128, 128),
"olive" => Color::Rgb(128, 128, 0),
"maroon" => Color::Rgb(128, 0, 0),
"gold" => Color::Rgb(255, 215, 0),
"tomato" => Color::Rgb(255, 99, 71),
"crimson" => Color::Rgb(220, 20, 60),
"dodgerblue" => Color::Rgb(30, 144, 255),
"limegreen" => Color::Rgb(50, 205, 50),
"orangered" => Color::Rgb(255, 69, 0),
"darkred" => Color::Rgb(139, 0, 0),
"darkblue" => Color::Rgb(0, 0, 139),
"darkgreen" => Color::Rgb(0, 100, 0),
"firebrick" => Color::Rgb(178, 34, 34),
"royalblue" => Color::Rgb(65, 105, 225),
"indianred" => Color::Rgb(205, 92, 92),
"forestgreen" => Color::Rgb(34, 139, 34),
"sienna" => Color::Rgb(160, 82, 45),
"chocolate" => Color::Rgb(210, 105, 30),
"peru" => Color::Rgb(205, 133, 63),
"violet" => Color::Rgb(238, 130, 238),
"turquoise" => Color::Rgb(64, 224, 208),
"cornflowerblue" => Color::Rgb(100, 149, 237),
"darkorange" => Color::Rgb(255, 140, 0),
"deeppink" => Color::Rgb(255, 20, 147),
"hotpink" => Color::Rgb(255, 105, 180),
"silver" => Color::Rgb(192, 192, 192),
_ => Color::Css(s.into()),
}
}
impl PartialEq for Color {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Color::Rgb(r1, g1, b1), Color::Rgb(r2, g2, b2)) => r1 == r2 && g1 == g2 && b1 == b2,
(Color::None, Color::None) => true,
(Color::Css(a), Color::Css(b)) => a == b,
_ => false,
}
}
}