1use std::fmt;
13
14use gpui::{Hsla, Rgba};
15
16#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct CssColorError(String);
19
20impl fmt::Display for CssColorError {
21 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22 write!(f, "guise: invalid CSS color `{}`", self.0)
23 }
24}
25
26impl std::error::Error for CssColorError {}
27
28fn rgba_f(r: f32, g: f32, b: f32, a: f32) -> Hsla {
31 Rgba {
32 r: (r / 255.0).clamp(0.0, 1.0),
33 g: (g / 255.0).clamp(0.0, 1.0),
34 b: (b / 255.0).clamp(0.0, 1.0),
35 a: a.clamp(0.0, 1.0),
36 }
37 .into()
38}
39
40pub fn rgb(r: u8, g: u8, b: u8) -> Hsla {
42 rgba(r, g, b, 1.0)
43}
44
45pub fn rgba(r: u8, g: u8, b: u8, a: f32) -> Hsla {
47 rgba_f(r as f32, g as f32, b as f32, a)
48}
49
50pub fn hsl(h: f32, s: f32, l: f32) -> Hsla {
52 hsla(h, s, l, 1.0)
53}
54
55pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
57 Hsla {
58 h: (h / 360.0).rem_euclid(1.0),
59 s: (s / 100.0).clamp(0.0, 1.0),
60 l: (l / 100.0).clamp(0.0, 1.0),
61 a: a.clamp(0.0, 1.0),
62 }
63}
64
65pub fn css(input: &str) -> Result<Hsla, CssColorError> {
69 let err = || CssColorError(input.to_string());
70 let s = input.trim();
71
72 if let Some(hex) = s.strip_prefix('#') {
73 return parse_hex(hex).ok_or_else(err);
74 }
75 let lower = s.to_ascii_lowercase();
76 if let Some(inner) = wrapped(&lower, "rgba").or_else(|| wrapped(&lower, "rgb")) {
77 return parse_rgb(inner).ok_or_else(err);
78 }
79 if let Some(inner) = wrapped(&lower, "hsla").or_else(|| wrapped(&lower, "hsl")) {
80 return parse_hsl(inner).ok_or_else(err);
81 }
82 named(&lower).map(|(r, g, b)| rgb(r, g, b)).ok_or_else(err)
83}
84
85fn wrapped<'a>(s: &'a str, name: &str) -> Option<&'a str> {
87 let rest = s.strip_prefix(name)?.trim_start();
88 let rest = rest.strip_prefix('(')?;
89 rest.strip_suffix(')')
90}
91
92fn nibble(b: u8) -> Option<u8> {
93 match b {
94 b'0'..=b'9' => Some(b - b'0'),
95 b'a'..=b'f' => Some(b - b'a' + 10),
96 b'A'..=b'F' => Some(b - b'A' + 10),
97 _ => None,
98 }
99}
100
101fn hex2(bytes: &[u8], i: usize) -> Option<f32> {
102 Some((nibble(bytes[i])? * 16 + nibble(bytes[i + 1])?) as f32)
103}
104
105fn parse_hex(hex: &str) -> Option<Hsla> {
106 let b = hex.as_bytes();
107 match b.len() {
108 3 => Some(rgba_f(
109 (nibble(b[0])? * 17) as f32,
110 (nibble(b[1])? * 17) as f32,
111 (nibble(b[2])? * 17) as f32,
112 1.0,
113 )),
114 4 => Some(rgba_f(
115 (nibble(b[0])? * 17) as f32,
116 (nibble(b[1])? * 17) as f32,
117 (nibble(b[2])? * 17) as f32,
118 (nibble(b[3])? * 17) as f32 / 255.0,
119 )),
120 6 => Some(rgba_f(hex2(b, 0)?, hex2(b, 2)?, hex2(b, 4)?, 1.0)),
121 8 => Some(rgba_f(
122 hex2(b, 0)?,
123 hex2(b, 2)?,
124 hex2(b, 4)?,
125 hex2(b, 6)? / 255.0,
126 )),
127 _ => None,
128 }
129}
130
131fn tokens(inner: &str) -> Vec<&str> {
132 inner
133 .split(|c| c == ',' || c == ' ' || c == '/')
134 .filter(|t| !t.is_empty())
135 .collect()
136}
137
138fn num(tok: &str) -> Option<f32> {
139 tok.trim().parse().ok()
140}
141
142fn channel(tok: &str) -> Option<f32> {
144 let t = tok.trim();
145 match t.strip_suffix('%') {
146 Some(p) => Some(num(p)? / 100.0 * 255.0),
147 None => num(t),
148 }
149}
150
151fn alpha(tok: &str) -> Option<f32> {
153 let t = tok.trim();
154 match t.strip_suffix('%') {
155 Some(p) => Some(num(p)? / 100.0),
156 None => num(t),
157 }
158}
159
160fn parse_rgb(inner: &str) -> Option<Hsla> {
161 let parts = tokens(inner);
162 let a = match parts.len() {
163 3 => 1.0,
164 4 => alpha(parts[3])?,
165 _ => return None,
166 };
167 Some(rgba_f(
168 channel(parts[0])?,
169 channel(parts[1])?,
170 channel(parts[2])?,
171 a,
172 ))
173}
174
175fn parse_hsl(inner: &str) -> Option<Hsla> {
176 let parts = tokens(inner);
177 let a = match parts.len() {
178 3 => 1.0,
179 4 => alpha(parts[3])?,
180 _ => return None,
181 };
182 let h = num(parts[0].trim().trim_end_matches("deg"))?;
183 let s = num(parts[1].trim().trim_end_matches('%'))?;
184 let l = num(parts[2].trim().trim_end_matches('%'))?;
185 Some(hsla(h, s, l, a))
186}
187
188fn named(name: &str) -> Option<(u8, u8, u8)> {
190 let rgb = match name {
191 "transparent" => return Some((0, 0, 0)),
192 "aliceblue" => (240, 248, 255),
193 "antiquewhite" => (250, 235, 215),
194 "aqua" | "cyan" => (0, 255, 255),
195 "aquamarine" => (127, 255, 212),
196 "azure" => (240, 255, 255),
197 "beige" => (245, 245, 220),
198 "bisque" => (255, 228, 196),
199 "black" => (0, 0, 0),
200 "blanchedalmond" => (255, 235, 205),
201 "blue" => (0, 0, 255),
202 "blueviolet" => (138, 43, 226),
203 "brown" => (165, 42, 42),
204 "burlywood" => (222, 184, 135),
205 "cadetblue" => (95, 158, 160),
206 "chartreuse" => (127, 255, 0),
207 "chocolate" => (210, 105, 30),
208 "coral" => (255, 127, 80),
209 "cornflowerblue" => (100, 149, 237),
210 "cornsilk" => (255, 248, 220),
211 "crimson" => (220, 20, 60),
212 "darkblue" => (0, 0, 139),
213 "darkcyan" => (0, 139, 139),
214 "darkgoldenrod" => (184, 134, 11),
215 "darkgray" | "darkgrey" => (169, 169, 169),
216 "darkgreen" => (0, 100, 0),
217 "darkkhaki" => (189, 183, 107),
218 "darkmagenta" => (139, 0, 139),
219 "darkolivegreen" => (85, 107, 47),
220 "darkorange" => (255, 140, 0),
221 "darkorchid" => (153, 50, 204),
222 "darkred" => (139, 0, 0),
223 "darksalmon" => (233, 150, 122),
224 "darkseagreen" => (143, 188, 143),
225 "darkslateblue" => (72, 61, 139),
226 "darkslategray" | "darkslategrey" => (47, 79, 79),
227 "darkturquoise" => (0, 206, 209),
228 "darkviolet" => (148, 0, 211),
229 "deeppink" => (255, 20, 147),
230 "deepskyblue" => (0, 191, 255),
231 "dimgray" | "dimgrey" => (105, 105, 105),
232 "dodgerblue" => (30, 144, 255),
233 "firebrick" => (178, 34, 34),
234 "floralwhite" => (255, 250, 240),
235 "forestgreen" => (34, 139, 34),
236 "fuchsia" | "magenta" => (255, 0, 255),
237 "gainsboro" => (220, 220, 220),
238 "ghostwhite" => (248, 248, 255),
239 "gold" => (255, 215, 0),
240 "goldenrod" => (218, 165, 32),
241 "gray" | "grey" => (128, 128, 128),
242 "green" => (0, 128, 0),
243 "greenyellow" => (173, 255, 47),
244 "honeydew" => (240, 255, 240),
245 "hotpink" => (255, 105, 180),
246 "indianred" => (205, 92, 92),
247 "indigo" => (75, 0, 130),
248 "ivory" => (255, 255, 240),
249 "khaki" => (240, 230, 140),
250 "lavender" => (230, 230, 250),
251 "lavenderblush" => (255, 240, 245),
252 "lawngreen" => (124, 252, 0),
253 "lemonchiffon" => (255, 250, 205),
254 "lightblue" => (173, 216, 230),
255 "lightcoral" => (240, 128, 128),
256 "lightcyan" => (224, 255, 255),
257 "lightgoldenrodyellow" => (250, 250, 210),
258 "lightgray" | "lightgrey" => (211, 211, 211),
259 "lightgreen" => (144, 238, 144),
260 "lightpink" => (255, 182, 193),
261 "lightsalmon" => (255, 160, 122),
262 "lightseagreen" => (32, 178, 170),
263 "lightskyblue" => (135, 206, 250),
264 "lightslategray" | "lightslategrey" => (119, 136, 153),
265 "lightsteelblue" => (176, 196, 222),
266 "lightyellow" => (255, 255, 224),
267 "lime" => (0, 255, 0),
268 "limegreen" => (50, 205, 50),
269 "linen" => (250, 240, 230),
270 "maroon" => (128, 0, 0),
271 "mediumaquamarine" => (102, 205, 170),
272 "mediumblue" => (0, 0, 205),
273 "mediumorchid" => (186, 85, 211),
274 "mediumpurple" => (147, 112, 219),
275 "mediumseagreen" => (60, 179, 113),
276 "mediumslateblue" => (123, 104, 238),
277 "mediumspringgreen" => (0, 250, 154),
278 "mediumturquoise" => (72, 209, 204),
279 "mediumvioletred" => (199, 21, 133),
280 "midnightblue" => (25, 25, 112),
281 "mintcream" => (245, 255, 250),
282 "mistyrose" => (255, 228, 225),
283 "moccasin" => (255, 228, 181),
284 "navajowhite" => (255, 222, 173),
285 "navy" => (0, 0, 128),
286 "oldlace" => (253, 245, 230),
287 "olive" => (128, 128, 0),
288 "olivedrab" => (107, 142, 35),
289 "orange" => (255, 165, 0),
290 "orangered" => (255, 69, 0),
291 "orchid" => (218, 112, 214),
292 "palegoldenrod" => (238, 232, 170),
293 "palegreen" => (152, 251, 152),
294 "paleturquoise" => (175, 238, 238),
295 "palevioletred" => (219, 112, 147),
296 "papayawhip" => (255, 239, 213),
297 "peachpuff" => (255, 218, 185),
298 "peru" => (205, 133, 63),
299 "pink" => (255, 192, 203),
300 "plum" => (221, 160, 221),
301 "powderblue" => (176, 224, 230),
302 "purple" => (128, 0, 128),
303 "rebeccapurple" => (102, 51, 153),
304 "red" => (255, 0, 0),
305 "rosybrown" => (188, 143, 143),
306 "royalblue" => (65, 105, 225),
307 "saddlebrown" => (139, 69, 19),
308 "salmon" => (250, 128, 114),
309 "sandybrown" => (244, 164, 96),
310 "seagreen" => (46, 139, 87),
311 "seashell" => (255, 245, 238),
312 "sienna" => (160, 82, 45),
313 "silver" => (192, 192, 192),
314 "skyblue" => (135, 206, 235),
315 "slateblue" => (106, 90, 205),
316 "slategray" | "slategrey" => (112, 128, 144),
317 "snow" => (255, 250, 250),
318 "springgreen" => (0, 255, 127),
319 "steelblue" => (70, 130, 180),
320 "tan" => (210, 180, 140),
321 "teal" => (0, 128, 128),
322 "thistle" => (216, 191, 216),
323 "tomato" => (255, 99, 71),
324 "turquoise" => (64, 224, 208),
325 "violet" => (238, 130, 238),
326 "wheat" => (245, 222, 179),
327 "white" => (255, 255, 255),
328 "whitesmoke" => (245, 245, 245),
329 "yellow" => (255, 255, 0),
330 "yellowgreen" => (154, 205, 50),
331 _ => return None,
332 };
333 Some(rgb)
334}
335
336#[cfg(test)]
337mod tests {
338 use super::*;
339
340 #[test]
341 fn hex_forms() {
342 assert_eq!(css("#228be6").unwrap(), rgb(0x22, 0x8b, 0xe6));
343 assert_eq!(css("228be6").is_err(), true); assert_eq!(css("#fff").unwrap(), rgb(255, 255, 255));
345 assert_eq!(
346 css("#ff000080").unwrap(),
347 rgba(255, 0, 0, 0x80 as f32 / 255.0)
348 );
349 }
350
351 #[test]
352 fn functional_forms() {
353 assert_eq!(css("rgb(34, 139, 230)").unwrap(), rgb(34, 139, 230));
354 assert_eq!(css("rgba(0,0,0,0.5)").unwrap(), rgba(0, 0, 0, 0.5));
355 assert_eq!(css("hsl(210, 80%, 52%)").unwrap(), hsl(210.0, 80.0, 52.0));
356 assert_eq!(
357 css("hsla(210,80%,52%,0.5)").unwrap(),
358 hsla(210.0, 80.0, 52.0, 0.5)
359 );
360 }
361
362 #[test]
363 fn named_colors() {
364 assert_eq!(css("teal").unwrap(), rgb(0, 128, 128));
365 assert_eq!(css("RebeccaPurple").unwrap(), rgb(102, 51, 153));
366 assert!(css("notacolor").is_err());
367 }
368}