use anyhow::{Context, Result};
const HEX_SHORTHAND_MULTIPLIER: u8 = 17;
pub type Color = [u8; 3];
pub type NormalizedColor = [f64; 3];
#[derive(Debug, Clone, PartialEq)]
pub enum ForegroundColorSpec {
Known(Color),
Unknown,
}
pub fn parse_hex_color(hex: &str) -> Result<Color> {
let hex = hex.trim_start_matches('#');
let (r, g, b) = match hex.len() {
3 => {
let r = u8::from_str_radix(&hex[0..1], 16).context("Invalid red component")?;
let g = u8::from_str_radix(&hex[1..2], 16).context("Invalid green component")?;
let b = u8::from_str_radix(&hex[2..3], 16).context("Invalid blue component")?;
(
r * HEX_SHORTHAND_MULTIPLIER,
g * HEX_SHORTHAND_MULTIPLIER,
b * HEX_SHORTHAND_MULTIPLIER,
)
}
6 => {
let r = u8::from_str_radix(&hex[0..2], 16).context("Invalid red component")?;
let g = u8::from_str_radix(&hex[2..4], 16).context("Invalid green component")?;
let b = u8::from_str_radix(&hex[4..6], 16).context("Invalid blue component")?;
(r, g, b)
}
_ => anyhow::bail!("Hex color must be 3 or 6 characters long (got: {})", hex),
};
Ok([r, g, b])
}
pub fn parse_foreground_spec(spec: &str) -> Result<ForegroundColorSpec> {
if spec == "auto" {
Ok(ForegroundColorSpec::Unknown)
} else {
parse_hex_color(spec).map(ForegroundColorSpec::Known)
}
}
pub fn normalize_color(color: Color) -> NormalizedColor {
[
color[0] as f64 / 255.0,
color[1] as f64 / 255.0,
color[2] as f64 / 255.0,
]
}
pub fn denormalize_color(color: NormalizedColor) -> Color {
[
(color[0] * 255.0).round().clamp(0.0, 255.0) as u8,
(color[1] * 255.0).round().clamp(0.0, 255.0) as u8,
(color[2] * 255.0).round().clamp(0.0, 255.0) as u8,
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_hex_color() {
assert_eq!(parse_hex_color("#ff0000").unwrap(), [255, 0, 0]);
assert_eq!(parse_hex_color("#00ff00").unwrap(), [0, 255, 0]);
assert_eq!(parse_hex_color("#0000ff").unwrap(), [0, 0, 255]);
assert_eq!(parse_hex_color("ff0000").unwrap(), [255, 0, 0]);
assert_eq!(parse_hex_color("00ff00").unwrap(), [0, 255, 0]);
assert_eq!(parse_hex_color("0000ff").unwrap(), [0, 0, 255]);
assert_eq!(parse_hex_color("#f00").unwrap(), [255, 0, 0]);
assert_eq!(parse_hex_color("#0f0").unwrap(), [0, 255, 0]);
assert_eq!(parse_hex_color("#00f").unwrap(), [0, 0, 255]);
assert_eq!(parse_hex_color("#fff").unwrap(), [255, 255, 255]);
assert_eq!(parse_hex_color("#000").unwrap(), [0, 0, 0]);
assert_eq!(parse_hex_color("#369").unwrap(), [51, 102, 153]);
assert_eq!(parse_hex_color("f00").unwrap(), [255, 0, 0]);
assert_eq!(parse_hex_color("0f0").unwrap(), [0, 255, 0]);
assert_eq!(parse_hex_color("00f").unwrap(), [0, 0, 255]);
assert!(parse_hex_color("ff").is_err()); assert!(parse_hex_color("ffff").is_err()); assert!(parse_hex_color("#gggggg").is_err()); assert!(parse_hex_color("#ggg").is_err()); }
#[test]
fn test_normalize_color() {
assert_eq!(normalize_color([255, 127, 0]), [1.0, 127.0 / 255.0, 0.0]);
}
#[test]
fn test_parse_foreground_spec() {
assert_eq!(
parse_foreground_spec("auto").unwrap(),
ForegroundColorSpec::Unknown
);
assert_eq!(
parse_foreground_spec("#ff0000").unwrap(),
ForegroundColorSpec::Known([255, 0, 0])
);
assert_eq!(
parse_foreground_spec("f00").unwrap(),
ForegroundColorSpec::Known([255, 0, 0])
);
assert!(parse_foreground_spec("invalid").is_err());
}
}