static CSS_NAMED: &[&str] = &[
"aliceblue",
"antiquewhite",
"aqua",
"aquamarine",
"azure",
"beige",
"bisque",
"black",
"blanchedalmond",
"blue",
"blueviolet",
"brown",
"burlywood",
"cadetblue",
"chartreuse",
"chocolate",
"coral",
"cornflowerblue",
"cornsilk",
"crimson",
"cyan",
"darkblue",
"darkcyan",
"darkgoldenrod",
"darkgray",
"darkgreen",
"darkgrey",
"darkkhaki",
"darkmagenta",
"darkolivegreen",
"darkorange",
"darkorchid",
"darkred",
"darksalmon",
"darkseagreen",
"darkslateblue",
"darkslategray",
"darkslategrey",
"darkturquoise",
"darkviolet",
"deeppink",
"deepskyblue",
"dimgray",
"dimgrey",
"dodgerblue",
"firebrick",
"floralwhite",
"forestgreen",
"fuchsia",
"gainsboro",
"ghostwhite",
"gold",
"goldenrod",
"gray",
"green",
"greenyellow",
"grey",
"honeydew",
"hotpink",
"indianred",
"indigo",
"ivory",
"khaki",
"lavender",
"lavenderblush",
"lawngreen",
"lemonchiffon",
"lightblue",
"lightcoral",
"lightcyan",
"lightgoldenrodyellow",
"lightgray",
"lightgreen",
"lightgrey",
"lightpink",
"lightsalmon",
"lightseagreen",
"lightskyblue",
"lightslategray",
"lightslategrey",
"lightsteelblue",
"lightyellow",
"lime",
"limegreen",
"linen",
"magenta",
"maroon",
"mediumaquamarine",
"mediumblue",
"mediumorchid",
"mediumpurple",
"mediumseagreen",
"mediumslateblue",
"mediumspringgreen",
"mediumturquoise",
"mediumvioletred",
"midnightblue",
"mintcream",
"mistyrose",
"moccasin",
"navajowhite",
"navy",
"oldlace",
"olive",
"olivedrab",
"orange",
"orangered",
"orchid",
"palegoldenrod",
"palegreen",
"paleturquoise",
"palevioletred",
"papayawhip",
"peachpuff",
"peru",
"pink",
"plum",
"powderblue",
"purple",
"rebeccapurple",
"red",
"rosybrown",
"royalblue",
"saddlebrown",
"salmon",
"sandybrown",
"seagreen",
"seashell",
"sienna",
"silver",
"skyblue",
"slateblue",
"slategray",
"slategrey",
"snow",
"springgreen",
"steelblue",
"tan",
"teal",
"thistle",
"tomato",
"turquoise",
"violet",
"wheat",
"white",
"whitesmoke",
"yellow",
"yellowgreen",
];
static XCOLOR_NAMED: &[&str] = &[
"Apricot",
"Aquamarine",
"Bittersweet",
"Black",
"Blue",
"BlueGreen",
"BlueViolet",
"BrickRed",
"Brown",
"BurntOrange",
"CadetBlue",
"CarnationPink",
"Cerulean",
"CornflowerBlue",
"Cyan",
"Dandelion",
"DarkOrchid",
"Emerald",
"ForestGreen",
"Fuchsia",
"Goldenrod",
"Gray",
"Green",
"GreenYellow",
"JungleGreen",
"Lavender",
"LimeGreen",
"Magenta",
"Mahogany",
"Maroon",
"Melon",
"MidnightBlue",
"Mulberry",
"NavyBlue",
"OliveGreen",
"Orange",
"OrangeRed",
"Orchid",
"Peach",
"Periwinkle",
"PineGreen",
"Plum",
"ProcessBlue",
"Purple",
"RawSienna",
"Red",
"RedOrange",
"RedViolet",
"Rhodamine",
"RoyalBlue",
"RoyalPurple",
"RubineRed",
"Salmon",
"SeaGreen",
"Sepia",
"SkyBlue",
"SpringGreen",
"Tan",
"TealBlue",
"Thistle",
"Turquoise",
"Violet",
"VioletRed",
"White",
"WildStrawberry",
"Yellow",
"YellowGreen",
"YellowOrange",
];
static XCOLOR_HEX: &[(&str, &str)] = &[
("Apricot", "#ffad7a"),
("Bittersweet", "#c20300"),
("BlueGreen", "#26ffab"),
("BrickRed", "#b80000"),
("BurntOrange", "#ff7d00"),
("CarnationPink", "#ff5eff"),
("Cerulean", "#0fe3ff"),
("Dandelion", "#ffb529"),
("Emerald", "#00ff80"),
("JungleGreen", "#03ff7a"),
("Mahogany", "#a60000"),
("Melon", "#ff8a80"),
("Mulberry", "#a314fa"),
("NavyBlue", "#0f75ff"),
("OliveGreen", "#009900"),
("Peach", "#ff804d"),
("Periwinkle", "#6e73ff"),
("PineGreen", "#00bf29"),
("ProcessBlue", "#0affff"),
("RawSienna", "#8c0000"),
("RedOrange", "#ff3b21"),
("RedViolet", "#9600a8"),
("Rhodamine", "#ff2eff"),
("RoyalPurple", "#4019ff"),
("RubineRed", "#ff00de"),
("Sepia", "#4d0000"),
("TealBlue", "#1ffaa3"),
("VioletRed", "#ff30ff"),
("WildStrawberry", "#ff0a9c"),
("YellowOrange", "#ff9400"),
];
pub fn xcolor_hex(name: &str) -> Option<&'static str> {
XCOLOR_HEX
.binary_search_by_key(&name, |(n, _)| n)
.ok()
.map(|i| XCOLOR_HEX[i].1)
}
pub fn is_valid_color(s: &str) -> bool {
let t = s.trim();
if t.is_empty() {
return false;
}
if is_hex(t) || is_functional(t) {
return true;
}
if t.eq_ignore_ascii_case("none")
|| t.eq_ignore_ascii_case("transparent")
|| t.eq_ignore_ascii_case("currentcolor")
{
return true;
}
let lower = t.to_ascii_lowercase();
CSS_NAMED.binary_search(&lower.as_str()).is_ok() || XCOLOR_NAMED.contains(&t)
}
pub fn suggest(s: &str) -> Option<&'static str> {
crate::diagnostic::closest(&s.to_ascii_lowercase(), CSS_NAMED)
.or_else(|| crate::diagnostic::closest(s, XCOLOR_NAMED))
}
fn is_hex(s: &str) -> bool {
let Some(hex) = s.strip_prefix('#') else {
return false;
};
matches!(hex.len(), 3 | 4 | 6 | 8) && hex.bytes().all(|b| b.is_ascii_hexdigit())
}
fn is_functional(s: &str) -> bool {
let lower = s.to_ascii_lowercase();
["rgb(", "rgba(", "hsl(", "hsla(", "color("]
.iter()
.any(|p| lower.starts_with(p))
&& lower.ends_with(')')
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn css_list_is_sorted_for_binary_search() {
let mut sorted = CSS_NAMED.to_vec();
sorted.sort_unstable();
assert_eq!(CSS_NAMED, sorted.as_slice(), "CSS_NAMED must stay sorted");
}
#[test]
fn xcolor_hex_table_is_sorted_and_consistent() {
let mut sorted = XCOLOR_HEX.to_vec();
sorted.sort_unstable_by_key(|(n, _)| *n);
assert_eq!(XCOLOR_HEX, sorted.as_slice(), "XCOLOR_HEX must stay sorted");
for (name, hex) in XCOLOR_HEX {
assert!(XCOLOR_NAMED.contains(name), "{name} not in XCOLOR_NAMED");
assert!(
CSS_NAMED
.binary_search(&name.to_ascii_lowercase().as_str())
.is_err(),
"{name} is a CSS keyword and must not be remapped"
);
assert!(is_hex(hex), "bad hex for {name}: {hex}");
}
}
#[test]
fn every_valid_xcolor_name_actually_renders() {
for name in XCOLOR_NAMED {
let is_css = CSS_NAMED
.binary_search(&name.to_ascii_lowercase().as_str())
.is_ok();
assert!(
is_css || xcolor_hex(name).is_some(),
"{name} is a valid xcolor name but neither a CSS keyword nor remapped to hex — it would render blank"
);
}
}
#[test]
fn xcolor_hex_maps_non_css_names_only() {
assert_eq!(xcolor_hex("Dandelion"), Some("#ffb529"));
assert_eq!(xcolor_hex("Goldenrod"), None);
assert_eq!(xcolor_hex("dandelion"), None);
assert_eq!(xcolor_hex("notacolor"), None);
}
#[test]
fn suggest_covers_css_and_xcolor() {
assert_eq!(suggest("crimsom"), Some("crimson")); assert_eq!(suggest("Dandelio"), Some("Dandelion")); assert_eq!(suggest("zzzzzzzz"), None); }
#[test]
fn accepts_valid_forms() {
for c in [
"red",
"Red",
"REBECCAPURPLE",
"cornflowerblue",
"#1b5e20",
"#abc",
"#12345678",
"#abcd",
"rgb(1,2,3)",
"rgba(1,2,3,0.5)",
"hsl(120, 50%, 50%)",
"none",
"transparent",
"currentColor",
"Dandelion",
"Goldenrod",
] {
assert!(is_valid_color(c), "should be valid: {c}");
}
}
#[test]
fn rejects_invalid_forms() {
for c in [
"notacolor",
"crimsom",
"#12g456",
"#ab",
"#abcde",
"0xff0000",
"dandelion",
"",
"rgb(1,2,3",
] {
assert!(!is_valid_color(c), "should be invalid: {c}");
}
}
}