1use swc_atoms::{atom, Atom};
2use swc_common::DUMMY_SP;
3use swc_css_ast::*;
4use swc_css_utils::{angle_to_deg, hsl_to_rgb, hwb_to_rgb, to_rgb255, NAMED_COLORS};
5
6use super::Compressor;
7use crate::compressor::alpha_value::compress_alpha_value;
8
9fn compress_alpha_in_hex(value: &Atom) -> Option<&str> {
10 let length = value.len();
11
12 if length == 3 || length == 6 {
13 return None;
14 }
15
16 let chars = value.as_bytes();
17
18 if length == 8
19 && (chars[6] == b'f' || chars[6] == b'F')
20 && (chars[7] == b'f' || chars[7] == b'F')
21 {
22 return Some(&value[0..6]);
23 } else if length == 4 && chars[3] == b'f' || chars[3] == b'F' {
24 return Some(&value[0..3]);
25 }
26
27 None
28}
29
30fn get_short_hex(v: u32) -> u32 {
31 ((v & 0x0ff00000) >> 12) | ((v & 0x00000ff0) >> 4)
32}
33
34fn get_long_hex(v: u32) -> u32 {
35 ((v & 0xf000) << 16)
36 | ((v & 0xff00) << 12)
37 | ((v & 0x0ff0) << 8)
38 | ((v & 0x00ff) << 4)
39 | (v & 0x000f)
40}
41
42fn get_named_color_by_hex(v: u32) -> Option<&'static str> {
43 let s = match v {
45 0x000080 => "navy",
46 0x008000 => "green",
47 0x008080 => "teal",
48 0x4b0082 => "indigo",
49 0x800000 => "maroon",
50 0x800080 => "purple",
51 0x808000 => "olive",
52 0x808080 => "gray",
53 0xa0522d => "sienna",
54 0xa52a2a => "brown",
55 0xc0c0c0 => "silver",
56 0xcd853f => "peru",
57 0xd2b48c => "tan",
58 0xda70d6 => "orchid",
59 0xdda0dd => "plum",
60 0xee82ee => "violet",
61 0xf0e68c => "khaki",
62 0xf0ffff => "azure",
63 0xf5deb3 => "wheat",
64 0xf5f5dc => "beige",
65 0xfa8072 => "salmon",
66 0xfaf0e6 => "linen",
67 0xff0000 => "red",
68 0xff6347 => "tomato",
69 0xff7f50 => "coral",
70 0xffa500 => "orange",
71 0xffc0cb => "pink",
72 0xffd700 => "gold",
73 0xffe4c4 => "bisque",
74 0xfffafa => "snow",
75 0xfffff0 => "ivory",
76 _ => return None,
77 };
78
79 Some(s)
80}
81
82macro_rules! make_color {
83 ($span:expr,$r:expr,$g:expr,$b:expr, $a:expr) => {{
84 let need_alpha_value = $a != 1.0;
85
86 let r = $r.round();
87 let g = $g.round();
88 let b = $b.round();
89
90 if need_alpha_value {
91 let is_alpha_hex_supported = false;
93
94 if is_alpha_hex_supported {
95 let alpha = (($a * 255.0) as f64).round().max(0.0).min(255.0) as u8;
96 let hex: u32 =
97 ((r as u32) << 24) | ((g as u32) << 16) | ((b as u32) << 8) | (alpha as u32);
98
99 let compact = get_short_hex(hex);
100 let value = if hex == get_long_hex(compact) {
101 format!("{:04x}", compact)
102 } else {
103 format!("{:08x}", hex)
104 };
105
106 Color::AbsoluteColorBase(AbsoluteColorBase::HexColor(HexColor {
107 span: $span,
108 value: value.into(),
109 raw: None,
110 }))
111 } else {
112 let mut alpha_value = AlphaValue::Number(Number {
113 span: DUMMY_SP,
114 value: $a,
115 raw: None,
116 });
117
118 compress_alpha_value(&mut alpha_value);
119
120 Color::AbsoluteColorBase(AbsoluteColorBase::Function(Function {
121 span: $span,
122 name: FunctionName::Ident(Ident {
123 span: DUMMY_SP,
124 value: atom!("rgba"),
125 raw: None,
126 }),
127 value: vec![
128 ComponentValue::Number(Box::new(Number {
129 span: DUMMY_SP,
130 value: r,
131 raw: None,
132 })),
133 ComponentValue::Delimiter(Box::new(Delimiter {
134 span: DUMMY_SP,
135 value: DelimiterValue::Comma,
136 })),
137 ComponentValue::Number(Box::new(Number {
138 span: DUMMY_SP,
139 value: g,
140 raw: None,
141 })),
142 ComponentValue::Delimiter(Box::new(Delimiter {
143 span: DUMMY_SP,
144 value: DelimiterValue::Comma,
145 })),
146 ComponentValue::Number(Box::new(Number {
147 span: DUMMY_SP,
148 value: b,
149 raw: None,
150 })),
151 ComponentValue::Delimiter(Box::new(Delimiter {
152 span: DUMMY_SP,
153 value: DelimiterValue::Comma,
154 })),
155 ComponentValue::AlphaValue(Box::new(alpha_value)),
156 ],
157 }))
158 }
159 } else {
160 let hex: u32 = ((r as u32) << 16) | ((g as u32) << 8) | (b as u32);
161
162 if let Some(name) = get_named_color_by_hex(hex) {
163 Color::AbsoluteColorBase(AbsoluteColorBase::NamedColorOrTransparent(Ident {
164 span: $span,
165 value: name.into(),
166 raw: None,
167 }))
168 } else {
169 let compact = get_short_hex(hex);
170 let value = if hex == get_long_hex(compact) {
171 format!("{:03x}", compact)
172 } else {
173 format!("{:06x}", hex)
174 };
175
176 Color::AbsoluteColorBase(AbsoluteColorBase::HexColor(HexColor {
177 span: $span,
178 value: value.into(),
179 raw: None,
180 }))
181 }
182 }
183 }};
184}
185
186impl Compressor {
187 fn get_named_color_by_hex(&self, hex: &str) -> Option<&'static str> {
188 let name = match hex {
189 "000080" => "navy",
190 "008000" => "green",
191 "008080" => "teal",
192 "4b0082" => "indigo",
193 "800000" => "maroon",
194 "800080" => "purple",
195 "808000" => "olive",
196 "808080" => "gray",
197 "a0522d" => "sienna",
198 "a52a2a" => "brown",
199 "c0c0c0" => "silver",
200 "cd853f" => "peru",
201 "d2b48c" => "tan",
202 "da70d6" => "orchid",
203 "dda0dd" => "plum",
204 "ee82ee" => "violet",
205 "f0e68c" => "khaki",
206 "f0ffff" => "azure",
207 "f5deb3" => "wheat",
208 "f5f5dc" => "beige",
209 "fa8072" => "salmon",
210 "faf0e6" => "linen",
211 "ff0000" => "red",
212 "ff6347" => "tomato",
213 "ff7f50" => "coral",
214 "ffa500" => "orange",
215 "ffc0cb" => "pink",
216 "ffd700" => "gold",
217 "ffe4c4" => "bisque",
218 "fffafa" => "snow",
219 "fffff0" => "ivory",
220 _ => return None,
221 };
222
223 Some(name)
224 }
225
226 fn get_alpha_value(&self, alpha_value: Option<&&ComponentValue>) -> Option<f64> {
227 let Some(alpha_value) = alpha_value else {
228 return Some(1.0);
229 };
230
231 match &alpha_value {
232 ComponentValue::AlphaValue(alpha_value) => match &**alpha_value {
233 AlphaValue::Number(Number { value, .. }) => {
234 if *value > 1.0 {
235 return Some(1.0);
236 } else if *value < 0.0 {
237 return Some(0.0);
238 }
239
240 Some(*value)
241 }
242 AlphaValue::Percentage(Percentage {
243 value: Number { value, .. },
244 ..
245 }) => {
246 if *value > 100.0 {
247 return Some(1.0);
248 } else if *value < 0.0 {
249 return Some(0.0);
250 }
251
252 Some(*value / 100.0)
253 }
254 },
255 ComponentValue::Ident(ident) if ident.value.eq_ignore_ascii_case("none") => Some(0.0),
256 _ => None,
257 }
258 }
259
260 fn get_hue(&self, hue: Option<&&ComponentValue>) -> Option<f64> {
261 match hue {
262 Some(ComponentValue::Hue(hue)) => {
263 let mut value = match &**hue {
264 Hue::Number(Number { value, .. }) => *value,
265 Hue::Angle(Angle {
266 value: Number { value, .. },
267 unit: Ident { value: unit, .. },
268 ..
269 }) => angle_to_deg(*value, unit),
270 };
271
272 value %= 360.0;
273
274 if value < 0.0 {
275 value += 360.0;
276 }
277
278 Some(value)
279 }
280 Some(ComponentValue::Ident(ident)) if ident.value.eq_ignore_ascii_case("none") => {
281 Some(0.0)
282 }
283 _ => None,
284 }
285 }
286
287 fn get_percentage(&self, percentage: Option<&&ComponentValue>) -> Option<f64> {
288 match percentage {
289 Some(ComponentValue::Percentage(percentage)) => {
290 let Number { value, .. } = &percentage.value;
291 if *value > 100.0 {
292 return Some(1.0);
293 } else if *value < 0.0 {
294 return Some(0.0);
295 }
296
297 Some(*value / 100.0)
298 }
299 Some(ComponentValue::Ident(ident)) if ident.value.eq_ignore_ascii_case("none") => {
300 Some(0.0)
301 }
302 _ => None,
303 }
304 }
305
306 fn get_number_or_percentage(
307 &self,
308 number_or_percentage: Option<&&ComponentValue>,
309 ) -> Option<f64> {
310 match number_or_percentage {
311 Some(ComponentValue::Number(number)) => {
312 if number.value > 255.0 {
313 return Some(255.0);
314 } else if number.value < 0.0 {
315 return Some(0.0);
316 }
317
318 Some(number.value)
319 }
320 Some(ComponentValue::Percentage(percentage)) => {
321 if percentage.value.value > 100.0 {
322 return Some(255.0);
323 } else if percentage.value.value < 0.0 {
324 return Some(0.0);
325 }
326
327 Some((2.55 * percentage.value.value).round())
328 }
329 Some(ComponentValue::Ident(ident)) if ident.value.eq_ignore_ascii_case("none") => {
330 Some(0.0)
331 }
332 _ => None,
333 }
334 }
335}
336
337impl Compressor {
338 pub(super) fn compress_color(&self, color: &mut Color) {
339 match color {
340 Color::AbsoluteColorBase(AbsoluteColorBase::NamedColorOrTransparent(Ident {
341 value,
342 span,
343 ..
344 })) => match value.to_ascii_lowercase() {
345 ref s if *s == "transparent" => {
346 *color = make_color!(*span, 0.0_f64, 0.0_f64, 0.0_f64, 0.0_f64);
347 }
348 name => {
349 if let Some(value) = NAMED_COLORS.get(&name) {
350 *color = make_color!(
351 *span,
352 value.rgb[0] as f64,
353 value.rgb[1] as f64,
354 value.rgb[2] as f64,
355 1.0
356 )
357 }
358 }
359 },
360 Color::AbsoluteColorBase(AbsoluteColorBase::HexColor(HexColor {
361 span, value, ..
362 })) => {
363 if let Some(value) = self.get_named_color_by_hex(value) {
364 *color = Color::AbsoluteColorBase(AbsoluteColorBase::NamedColorOrTransparent(
365 Ident {
366 span: *span,
367 value: value.into(),
368 raw: None,
369 },
370 ));
371 } else if let Some(new_value) = compress_alpha_in_hex(value) {
372 *value = new_value.into();
373 }
374 }
375 Color::AbsoluteColorBase(AbsoluteColorBase::Function(Function {
376 span,
377 name,
378 value,
379 ..
380 })) if name == "rgb" || name == "rgba" => {
381 let rgba: Vec<_> = value
382 .iter()
383 .filter(|n| {
384 !n.as_delimiter()
385 .map(|delimiter| delimiter.value)
386 .map(|v| matches!(v, DelimiterValue::Comma | DelimiterValue::Solidus))
387 .unwrap_or_default()
388 })
389 .collect();
390
391 let r = match self.get_number_or_percentage(rgba.first()) {
392 Some(value) => value,
393 _ => return,
394 };
395 let g = match self.get_number_or_percentage(rgba.get(1)) {
396 Some(value) => value,
397 _ => return,
398 };
399 let b = match self.get_number_or_percentage(rgba.get(2)) {
400 Some(value) => value,
401 _ => return,
402 };
403 let a = match self.get_alpha_value(rgba.get(3)) {
404 Some(value) => value,
405 _ => return,
406 };
407
408 *color = make_color!(*span, r, g, b, a);
409 }
410 Color::AbsoluteColorBase(AbsoluteColorBase::Function(Function {
411 span,
412 name,
413 value,
414 ..
415 })) if name == "hsl" || name == "hsla" => {
416 let hsla: Vec<_> = value
417 .iter()
418 .filter(|n| {
419 !n.as_delimiter()
420 .map(|delimiter| delimiter.value)
421 .map(|v| matches!(v, DelimiterValue::Comma | DelimiterValue::Solidus))
422 .unwrap_or_default()
423 })
424 .collect();
425
426 let h = match self.get_hue(hsla.first()) {
427 Some(value) => value,
428 _ => return,
429 };
430 let s = match self.get_percentage(hsla.get(1)) {
431 Some(value) => value,
432 _ => return,
433 };
434 let l = match self.get_percentage(hsla.get(2)) {
435 Some(value) => value,
436 _ => return,
437 };
438 let a = match self.get_alpha_value(hsla.get(3)) {
439 Some(value) => value,
440 _ => return,
441 };
442
443 let rgb = to_rgb255(hsl_to_rgb([h, s, l]));
444
445 *color = make_color!(*span, rgb[0], rgb[1], rgb[2], a);
446 }
447 Color::AbsoluteColorBase(AbsoluteColorBase::Function(Function {
448 span,
449 name,
450 value,
451 ..
452 })) if name == "hwb" => {
453 let h = match self.get_hue(value.first().as_ref()) {
454 Some(value) => value,
455 _ => return,
456 };
457 let w = match self.get_percentage(value.get(1).as_ref()) {
458 Some(value) => value,
459 _ => return,
460 };
461 let b = match self.get_percentage(value.get(2).as_ref()) {
462 Some(value) => value,
463 _ => return,
464 };
465 let a = match self.get_alpha_value(value.get(4).as_ref()) {
466 Some(value) => value,
467 _ => return,
468 };
469
470 let rgb = to_rgb255(hwb_to_rgb([h, w, b]));
471
472 *color = make_color!(*span, rgb[0], rgb[1], rgb[2], a);
473 }
474 _ => {}
475 }
476 }
477}