pub(crate) use cssnumeric::CSSParseError;
use cssnumeric::{parse_css_number, CSSNumeric};
fn parse_rgb_num(num: &str) -> Result<u8, CSSParseError> {
let parsed_num = parse_css_number(num)?;
match parsed_num {
CSSNumeric::Integer(val) => {
if val >= 255 {
Ok(255u8)
} else if val <= 0 {
Ok(0u8)
} else {
Ok(val as u8)
}
}
CSSNumeric::Float(val) => {
let clamped = if val <= 0. {
0.
} else if val >= 1. {
1.
} else {
val
};
Ok((clamped * 255. - 0.000001).round() as u8)
}
CSSNumeric::Percentage(val) => {
let clamped = if val <= 0 {
0
} else if val >= 100 {
100
} else {
val
};
Ok((clamped as f64 * 2.55).round() as u8)
}
}
}
pub(crate) fn parse_rgb_str(num: &str) -> Result<(u8, u8, u8), CSSParseError> {
if !num.starts_with("rgb(") || num.len() < 10 {
return Err(CSSParseError::InvalidColorSyntax);
}
let mut chars: Vec<char> = num.chars().skip(4).collect();
if chars.iter().last().unwrap() != &')' {
return Err(CSSParseError::InvalidColorSyntax);
}
chars.pop();
if chars.iter().any(|&c| !"0123456789+-,. %".contains(c)) {
println!("hi");
return Err(CSSParseError::InvalidColorSyntax);
}
let split_iter = chars.split(|c| c == &',');
let mut nums: Vec<u8> = vec![];
for split in split_iter {
nums.push(parse_rgb_num(split.iter().collect::<String>().trim())?);
}
if nums.len() != 3 {
return Err(CSSParseError::InvalidColorSyntax);
}
Ok((nums[0], nums[1], nums[2]))
}
pub(crate) fn parse_hsl_hsv_tuple(tup: &str) -> Result<(f64, f64, f64), CSSParseError> {
if !tup.starts_with('(') || !tup.ends_with(')') {
return Err(CSSParseError::InvalidColorSyntax);
}
let mut chars: Vec<char> = tup.chars().skip(1).collect();
chars.pop();
let split_iter = chars.split(|c| c == &',');
let mut numerics: Vec<CSSNumeric> = vec![];
for split in split_iter {
numerics.push(parse_css_number(split.iter().collect::<String>().trim())?);
}
if numerics.len() != 3 {
return Err(CSSParseError::InvalidColorSyntax);
}
let hue: f64 = match numerics[0] {
CSSNumeric::Integer(val) => {
let mut clamped = val;
while clamped < 0 {
clamped += 360;
}
while clamped >= 360 {
clamped -= 360;
}
clamped as f64
}
CSSNumeric::Float(val) => {
let mut clamped = val;
while clamped < 0. {
clamped += 360.;
}
while clamped >= 360. {
clamped -= 360.;
}
clamped
}
_ => return Err(CSSParseError::InvalidColorSyntax),
};
let sat: f64 = match numerics[1] {
CSSNumeric::Percentage(val) => {
if val < 0 {
0.
} else if val > 100 {
1.
} else {
(val as f64) / 100.
}
}
_ => return Err(CSSParseError::InvalidColorSyntax),
};
let l_or_v: f64 = match numerics[2] {
CSSNumeric::Percentage(val) => {
if val < 0 {
0.
} else if val > 100 {
1.
} else {
(val as f64) / 100.
}
}
_ => return Err(CSSParseError::InvalidColorSyntax),
};
Ok((hue, sat, l_or_v))
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
#[test]
fn test_rgb_num_parsing() {
assert_eq!(104u8, parse_rgb_num("104").unwrap());
assert_eq!(255u8, parse_rgb_num("234923").unwrap());
assert_eq!(123u8, parse_rgb_num(".48235").unwrap());
assert_eq!(255u8, parse_rgb_num("1.04").unwrap());
assert_eq!(122u8, parse_rgb_num("48%").unwrap());
assert_eq!(255u8, parse_rgb_num("115%").unwrap());
assert_eq!(
Err(CSSParseError::InvalidNumericCharacters),
parse_rgb_num("abc")
);
assert_eq!(
Err(CSSParseError::InvalidNumericSyntax),
parse_rgb_num("123%%")
);
}
#[test]
fn test_rgb_str_parsing() {
let rgb = parse_rgb_str("rgb(125, 20%, 0.5)").unwrap();
assert_eq!(rgb, (125, 51, 127));
let rgb = parse_rgb_str("rgb(-125, -20%, 10.5)").unwrap();
assert_eq!(rgb, (0, 0, 255));
assert_eq!(
Err(CSSParseError::InvalidColorSyntax),
parse_rgb_str("rgB(123, 33, 2)")
);
assert_eq!(
Err(CSSParseError::InvalidColorSyntax),
parse_rgb_str("rgb(123, 123, 41, 22)")
);
assert_eq!(
Err(CSSParseError::InvalidColorSyntax),
parse_rgb_str("rgB(())")
);
}
#[test]
fn test_hslv_str_parsing() {
let hsl = parse_hsl_hsv_tuple("(123, 40%, 40%)").unwrap();
assert_eq!(hsl.0.round() as u8, 123u8);
assert_eq!((hsl.1 * 100.).round() as u8, 40u8);
assert_eq!((hsl.2 * 100.).round() as u8, 40u8);
let hsl = parse_hsl_hsv_tuple("(-597, 40%, 40%)").unwrap();
assert_eq!(hsl.0.round() as u8, 123u8);
assert_eq!((hsl.1 * 100.).round() as u8, 40u8);
assert_eq!((hsl.2 * 100.).round() as u8, 40u8);
let hsl = parse_hsl_hsv_tuple("(1203, 40%, 40%)").unwrap();
assert_eq!(hsl.0.round() as u8, 123u8);
assert_eq!((hsl.1 * 100.).round() as u8, 40u8);
assert_eq!((hsl.2 * 100.).round() as u8, 40u8);
let hsl = parse_hsl_hsv_tuple("(123, 140%, -40%)").unwrap();
assert_eq!(hsl.0.round() as u8, 123u8);
assert_eq!((hsl.1 * 100.).round() as u8, 100u8);
assert_eq!((hsl.2 * 100.).round() as u8, 0u8);
assert_eq!(
parse_hsl_hsv_tuple("(14%, 140%, 12%)"),
Err(CSSParseError::InvalidColorSyntax)
);
}
}