swc_css_compat/compiler/
color_hwb.rs

1use swc_atoms::atom;
2use swc_css_ast::{
3    AbsoluteColorBase, AlphaValue, Angle, ComponentValue, Delimiter, DelimiterValue, FunctionName,
4    Hue, Ident, Number, Percentage,
5};
6use swc_css_utils::{angle_to_deg, hwb_to_rgb, to_rgb255};
7
8use crate::compiler::Compiler;
9
10impl Compiler {
11    fn get_hue(&self, hue: Option<&ComponentValue>) -> Option<f64> {
12        match hue {
13            Some(ComponentValue::Hue(hue)) => {
14                let mut value = match &**hue {
15                    Hue::Number(Number { value, .. }) => *value,
16                    Hue::Angle(Angle {
17                        value: Number { value, .. },
18                        unit: Ident { value: unit, .. },
19                        ..
20                    }) => angle_to_deg(*value, unit),
21                };
22
23                value %= 360.0;
24
25                if value < 0.0 {
26                    value += 360.0;
27                }
28
29                Some(value)
30            }
31            Some(ComponentValue::Ident(ident)) if ident.value.eq_ignore_ascii_case("none") => {
32                Some(0.0)
33            }
34            _ => None,
35        }
36    }
37
38    fn get_percentage(&self, percentage: Option<&ComponentValue>) -> Option<f64> {
39        match percentage {
40            Some(ComponentValue::Percentage(percentage)) => {
41                let Number { value, .. } = &percentage.value;
42                if *value > 100.0 {
43                    return Some(1.0);
44                } else if *value < 0.0 {
45                    return Some(0.0);
46                }
47
48                Some(*value / 100.0)
49            }
50            Some(ComponentValue::Ident(ident)) if ident.value.eq_ignore_ascii_case("none") => {
51                Some(0.0)
52            }
53            _ => None,
54        }
55    }
56
57    fn get_alpha_value(&self, alpha_value: Option<&ComponentValue>) -> Option<f64> {
58        let Some(alpha_value) = alpha_value else {
59            return Some(1.0);
60        };
61
62        match &alpha_value {
63            ComponentValue::AlphaValue(alpha_value) => match &**alpha_value {
64                AlphaValue::Number(Number { value, .. }) => {
65                    if *value > 1.0 {
66                        return Some(1.0);
67                    } else if *value < 0.0 {
68                        return Some(0.0);
69                    }
70
71                    Some(*value)
72                }
73                AlphaValue::Percentage(Percentage {
74                    value: Number { value, .. },
75                    ..
76                }) => {
77                    if *value > 100.0 {
78                        return Some(1.0);
79                    } else if *value < 0.0 {
80                        return Some(0.0);
81                    }
82
83                    Some(*value / 100.0)
84                }
85            },
86            ComponentValue::Ident(ident) if ident.value.eq_ignore_ascii_case("none") => Some(0.0),
87            _ => None,
88        }
89    }
90
91    pub(crate) fn process_color_hwb(&mut self, n: &mut AbsoluteColorBase) {
92        if let AbsoluteColorBase::Function(function) = n {
93            if function.name != "hwb" {
94                return;
95            }
96
97            let h = match self.get_hue(function.value.first()) {
98                Some(value) => value,
99                _ => return,
100            };
101            let w = match self.get_percentage(function.value.get(1)) {
102                Some(value) => value,
103                _ => return,
104            };
105            let b = match self.get_percentage(function.value.get(2)) {
106                Some(value) => value,
107                _ => return,
108            };
109            let a = match self.get_alpha_value(function.value.get(4)) {
110                Some(value) => value,
111                _ => return,
112            };
113
114            let rgb = to_rgb255(hwb_to_rgb([h, w, b]));
115
116            if a == 1.0 {
117                *n = AbsoluteColorBase::Function(swc_css_ast::Function {
118                    name: FunctionName::Ident(Ident {
119                        value: atom!("rgb"),
120                        span: Default::default(),
121                        raw: None,
122                    }),
123                    value: vec![
124                        ComponentValue::Number(Box::new(Number {
125                            value: rgb[0].round(),
126                            span: Default::default(),
127                            raw: None,
128                        })),
129                        ComponentValue::Delimiter(Box::new(Delimiter {
130                            value: DelimiterValue::Comma,
131                            span: Default::default(),
132                        })),
133                        ComponentValue::Number(Box::new(Number {
134                            value: rgb[1].round(),
135                            span: Default::default(),
136                            raw: None,
137                        })),
138                        ComponentValue::Delimiter(Box::new(Delimiter {
139                            value: DelimiterValue::Comma,
140                            span: Default::default(),
141                        })),
142                        ComponentValue::Number(Box::new(Number {
143                            value: rgb[2].round(),
144                            span: Default::default(),
145                            raw: None,
146                        })),
147                    ],
148                    span: Default::default(),
149                });
150            } else {
151                *n = AbsoluteColorBase::Function(swc_css_ast::Function {
152                    name: FunctionName::Ident(Ident {
153                        value: atom!("rgba"),
154                        span: Default::default(),
155                        raw: None,
156                    }),
157                    value: vec![
158                        ComponentValue::Number(Box::new(Number {
159                            value: rgb[0].round(),
160                            span: Default::default(),
161                            raw: None,
162                        })),
163                        ComponentValue::Delimiter(Box::new(Delimiter {
164                            value: DelimiterValue::Comma,
165                            span: Default::default(),
166                        })),
167                        ComponentValue::Number(Box::new(Number {
168                            value: rgb[1].round(),
169                            span: Default::default(),
170                            raw: None,
171                        })),
172                        ComponentValue::Delimiter(Box::new(Delimiter {
173                            value: DelimiterValue::Comma,
174                            span: Default::default(),
175                        })),
176                        ComponentValue::Number(Box::new(Number {
177                            value: rgb[2].round(),
178                            span: Default::default(),
179                            raw: None,
180                        })),
181                        ComponentValue::Delimiter(Box::new(Delimiter {
182                            value: DelimiterValue::Comma,
183                            span: Default::default(),
184                        })),
185                        ComponentValue::AlphaValue(Box::new(AlphaValue::Number(Number {
186                            value: a,
187                            span: Default::default(),
188                            raw: None,
189                        }))),
190                    ],
191                    span: Default::default(),
192                });
193            }
194        }
195    }
196}