1pub(crate) use cssnumeric::CSSParseError;
9use cssnumeric::{parse_css_number, CSSNumeric};
10
11fn parse_rgb_num(num: &str) -> Result<u8, CSSParseError> {
15 let parsed_num = parse_css_number(num)?;
16 match parsed_num {
17 CSSNumeric::Integer(val) => {
19 if val >= 255 {
20 Ok(255u8)
21 } else if val <= 0 {
22 Ok(0u8)
23 } else {
24 Ok(val as u8)
25 }
26 }
27 CSSNumeric::Float(val) => {
28 let clamped = if val <= 0. {
30 0.
31 } else if val >= 1. {
32 1.
33 } else {
34 val
35 };
36 Ok((clamped * 255. - 0.000001).round() as u8)
39 }
40 CSSNumeric::Percentage(val) => {
41 let clamped = if val <= 0 {
43 0
44 } else if val >= 100 {
45 100
46 } else {
47 val
48 };
49 Ok((clamped as f64 * 2.55).round() as u8)
51 }
52 }
53}
54
55pub(crate) fn parse_rgb_str(num: &str) -> Result<(u8, u8, u8), CSSParseError> {
58 if !num.starts_with("rgb(") || num.len() < 10 {
61 return Err(CSSParseError::InvalidColorSyntax);
62 }
63 let mut chars: Vec<char> = num.chars().skip(4).collect();
65 if chars.iter().last().unwrap() != &')' {
67 return Err(CSSParseError::InvalidColorSyntax);
68 }
69 chars.pop();
70
71 if chars.iter().any(|&c| !"0123456789+-,. %".contains(c)) {
73 println!("hi");
74 return Err(CSSParseError::InvalidColorSyntax);
75 }
76 let split_iter = chars.split(|c| c == &',');
81 let mut nums: Vec<u8> = vec![];
83 for split in split_iter {
84 nums.push(parse_rgb_num(split.iter().collect::<String>().trim())?);
85 }
86 if nums.len() != 3 {
87 return Err(CSSParseError::InvalidColorSyntax);
88 }
89 Ok((nums[0], nums[1], nums[2]))
90}
91
92pub(crate) fn parse_hsl_hsv_tuple(tup: &str) -> Result<(f64, f64, f64), CSSParseError> {
96 if !tup.starts_with('(') || !tup.ends_with(')') {
98 return Err(CSSParseError::InvalidColorSyntax);
99 }
100 let mut chars: Vec<char> = tup.chars().skip(1).collect();
101 chars.pop();
102
103 let split_iter = chars.split(|c| c == &',');
105 let mut numerics: Vec<CSSNumeric> = vec![];
106 for split in split_iter {
107 numerics.push(parse_css_number(split.iter().collect::<String>().trim())?);
108 }
109 if numerics.len() != 3 {
110 return Err(CSSParseError::InvalidColorSyntax);
111 }
112 let hue: f64 = match numerics[0] {
114 CSSNumeric::Integer(val) => {
115 let mut clamped = val;
116 while clamped < 0 {
117 clamped += 360;
118 }
119 while clamped >= 360 {
120 clamped -= 360;
121 }
122 clamped as f64
123 }
124 CSSNumeric::Float(val) => {
125 let mut clamped = val;
126 while clamped < 0. {
127 clamped += 360.;
128 }
129 while clamped >= 360. {
130 clamped -= 360.;
131 }
132 clamped
133 }
134 _ => return Err(CSSParseError::InvalidColorSyntax),
135 };
136 let sat: f64 = match numerics[1] {
139 CSSNumeric::Percentage(val) => {
140 if val < 0 {
141 0.
142 } else if val > 100 {
143 1.
144 } else {
145 (val as f64) / 100.
146 }
147 }
148 _ => return Err(CSSParseError::InvalidColorSyntax),
149 };
150 let l_or_v: f64 = match numerics[2] {
151 CSSNumeric::Percentage(val) => {
152 if val < 0 {
153 0.
154 } else if val > 100 {
155 1.
156 } else {
157 (val as f64) / 100.
158 }
159 }
160 _ => return Err(CSSParseError::InvalidColorSyntax),
161 };
162 Ok((hue, sat, l_or_v))
164}
165
166#[cfg(test)]
167mod tests {
168 #[allow(unused_imports)]
169 use super::*;
170
171 #[test]
172 fn test_rgb_num_parsing() {
173 assert_eq!(104u8, parse_rgb_num("104").unwrap());
175 assert_eq!(255u8, parse_rgb_num("234923").unwrap());
176 assert_eq!(123u8, parse_rgb_num(".48235").unwrap());
178 assert_eq!(255u8, parse_rgb_num("1.04").unwrap());
179 assert_eq!(122u8, parse_rgb_num("48%").unwrap());
181 assert_eq!(255u8, parse_rgb_num("115%").unwrap());
182 assert_eq!(
184 Err(CSSParseError::InvalidNumericCharacters),
185 parse_rgb_num("abc")
186 );
187 assert_eq!(
188 Err(CSSParseError::InvalidNumericSyntax),
189 parse_rgb_num("123%%")
190 );
191 }
192
193 #[test]
194 fn test_rgb_str_parsing() {
195 let rgb = parse_rgb_str("rgb(125, 20%, 0.5)").unwrap();
197 assert_eq!(rgb, (125, 51, 127));
198 let rgb = parse_rgb_str("rgb(-125, -20%, 10.5)").unwrap();
200 assert_eq!(rgb, (0, 0, 255));
201 assert_eq!(
203 Err(CSSParseError::InvalidColorSyntax),
204 parse_rgb_str("rgB(123, 33, 2)")
205 );
206 assert_eq!(
207 Err(CSSParseError::InvalidColorSyntax),
208 parse_rgb_str("rgb(123, 123, 41, 22)")
209 );
210 assert_eq!(
211 Err(CSSParseError::InvalidColorSyntax),
212 parse_rgb_str("rgB(())")
213 );
214 }
215
216 #[test]
217 fn test_hslv_str_parsing() {
218 let hsl = parse_hsl_hsv_tuple("(123, 40%, 40%)").unwrap();
220 assert_eq!(hsl.0.round() as u8, 123u8);
221 assert_eq!((hsl.1 * 100.).round() as u8, 40u8);
222 assert_eq!((hsl.2 * 100.).round() as u8, 40u8);
223 let hsl = parse_hsl_hsv_tuple("(-597, 40%, 40%)").unwrap();
225 assert_eq!(hsl.0.round() as u8, 123u8);
226 assert_eq!((hsl.1 * 100.).round() as u8, 40u8);
227 assert_eq!((hsl.2 * 100.).round() as u8, 40u8);
228 let hsl = parse_hsl_hsv_tuple("(1203, 40%, 40%)").unwrap();
229 assert_eq!(hsl.0.round() as u8, 123u8);
230 assert_eq!((hsl.1 * 100.).round() as u8, 40u8);
231 assert_eq!((hsl.2 * 100.).round() as u8, 40u8);
232 let hsl = parse_hsl_hsv_tuple("(123, 140%, -40%)").unwrap();
234 assert_eq!(hsl.0.round() as u8, 123u8);
235 assert_eq!((hsl.1 * 100.).round() as u8, 100u8);
236 assert_eq!((hsl.2 * 100.).round() as u8, 0u8);
237 assert_eq!(
239 parse_hsl_hsv_tuple("(14%, 140%, 12%)"),
240 Err(CSSParseError::InvalidColorSyntax)
241 );
242 }
243}