use crate::{
Result,
styling::{Rgba, rgba},
};
pub fn parse_hex_color(color_str: &str) -> Result<Rgba>
{
if !color_str.starts_with('#')
{
return Err(anyhow::anyhow!("Color must start with '#'"));
}
let hex_str = &color_str[1..];
match hex_str.len()
{
6 => parse_6digit_hex(hex_str),
8 => parse_8digit_hex(hex_str),
_ => Err(anyhow::anyhow!(
"Color must be 6 or 8 hex digits, got {}",
hex_str.len()
)),
}
}
fn parse_6digit_hex(hex_str: &str) -> Result<Rgba>
{
let r = parse_hex_byte(&hex_str[0..2])?;
let g = parse_hex_byte(&hex_str[2..4])?;
let b = parse_hex_byte(&hex_str[4..6])?;
Ok(rgba(
r as f32 / 255.0,
g as f32 / 255.0,
b as f32 / 255.0,
1.0,
))
}
fn parse_8digit_hex(hex_str: &str) -> Result<Rgba>
{
let r = parse_hex_byte(&hex_str[0..2])?;
let g = parse_hex_byte(&hex_str[2..4])?;
let b = parse_hex_byte(&hex_str[4..6])?;
let a = parse_hex_byte(&hex_str[6..8])?;
Ok(rgba(
r as f32 / 255.0,
g as f32 / 255.0,
b as f32 / 255.0,
a as f32 / 255.0,
))
}
fn parse_hex_byte(hex_str: &str) -> Result<u8>
{
u8::from_str_radix(hex_str, 16).map_err(|_| anyhow::anyhow!("Invalid hex value: {}", hex_str))
}
#[cfg(test)]
mod tests
{
use super::*;
#[test]
fn test_parse_red()
{
let color = parse_hex_color("#ff0000").unwrap();
assert!((color.red() - 1.0).abs() < 0.01);
assert!((color.green() - 0.0).abs() < 0.01);
assert!((color.blue() - 0.0).abs() < 0.01);
assert!((color.alpha() - 1.0).abs() < 0.01);
}
#[test]
fn test_parse_black()
{
let color = parse_hex_color("#000000").unwrap();
assert!((color.red() - 0.0).abs() < 0.01);
assert!((color.green() - 0.0).abs() < 0.01);
assert!((color.blue() - 0.0).abs() < 0.01);
assert!((color.alpha() - 1.0).abs() < 0.01);
}
#[test]
fn test_parse_white()
{
let color = parse_hex_color("#ffffff").unwrap();
assert!((color.red() - 1.0).abs() < 0.01);
assert!((color.green() - 1.0).abs() < 0.01);
assert!((color.blue() - 1.0).abs() < 0.01);
assert!((color.alpha() - 1.0).abs() < 0.01);
}
#[test]
fn test_parse_with_alpha()
{
let color = parse_hex_color("#ff0000ff").unwrap();
assert!((color.red() - 1.0).abs() < 0.01);
assert!((color.green() - 0.0).abs() < 0.01);
assert!((color.blue() - 0.0).abs() < 0.01);
assert!((color.alpha() - 1.0).abs() < 0.01);
}
#[test]
fn test_parse_with_half_alpha()
{
let color = parse_hex_color("#ff000080").unwrap();
assert!((color.red() - 1.0).abs() < 0.01);
assert!((color.green() - 0.0).abs() < 0.01);
assert!((color.blue() - 0.0).abs() < 0.01);
let expected_alpha = 0x80 as f32 / 255.0;
assert!((color.alpha() - expected_alpha).abs() < 0.01);
}
#[test]
fn test_no_hash_prefix()
{
assert!(parse_hex_color("ff0000").is_err());
}
#[test]
fn test_invalid_length()
{
assert!(parse_hex_color("#fff").is_err());
assert!(parse_hex_color("#fffff").is_err());
assert!(parse_hex_color("#fffffffff").is_err());
}
#[test]
fn test_invalid_hex_digits()
{
assert!(parse_hex_color("#gggggg").is_err());
assert!(parse_hex_color("#zzzzzzz").is_err());
}
#[test]
fn test_case_insensitive()
{
let color1 = parse_hex_color("#FF0000").unwrap();
let color2 = parse_hex_color("#ff0000").unwrap();
assert!((color1.red() - color2.red()).abs() < 0.01);
assert!((color1.green() - color2.green()).abs() < 0.01);
assert!((color1.blue() - color2.blue()).abs() < 0.01);
}
}