1#![deny(clippy::all)]
2
3use std::{borrow::Cow, f64::consts::PI, str};
4
5use once_cell::sync::Lazy;
6use rustc_hash::FxHashMap;
7use serde::{Deserialize, Serialize};
8use swc_atoms::{Atom, StaticString};
9use swc_css_ast::*;
10use swc_css_visit::{VisitMut, VisitMutWith};
11
12pub struct IdentReplacer<'a> {
13 from: &'a str,
14 to: &'a str,
15}
16
17impl VisitMut for IdentReplacer<'_> {
18 fn visit_mut_ident(&mut self, n: &mut Ident) {
19 n.visit_mut_children_with(self);
20
21 if n.value.eq_ignore_ascii_case(self.from) {
22 n.value = self.to.into();
23 n.raw = None;
24 }
25 }
26}
27
28pub fn replace_ident<N>(node: &mut N, from: &str, to: &str)
29where
30 N: for<'aa> VisitMutWith<IdentReplacer<'aa>>,
31{
32 node.visit_mut_with(&mut IdentReplacer { from, to });
33}
34
35pub struct FunctionNameReplacer<'a> {
36 from: &'a str,
37 to: &'a str,
38}
39
40impl VisitMut for FunctionNameReplacer<'_> {
41 fn visit_mut_function(&mut self, n: &mut Function) {
42 n.visit_mut_children_with(self);
43
44 match &mut n.name {
45 FunctionName::Ident(name) if name.value.eq_ignore_ascii_case(self.from) => {
46 name.value = self.to.into();
47 name.raw = None;
48 }
49 FunctionName::DashedIdent(name) if name.value.eq_ignore_ascii_case(self.from) => {
50 name.value = self.to.into();
51 name.raw = None;
52 }
53 _ => {}
54 }
55 }
56}
57
58pub fn replace_function_name<N>(node: &mut N, from: &str, to: &str)
59where
60 N: for<'aa> VisitMutWith<FunctionNameReplacer<'aa>>,
61{
62 node.visit_mut_with(&mut FunctionNameReplacer { from, to });
63}
64
65pub struct PseudoClassSelectorNameReplacer<'a> {
66 from: &'a str,
67 to: &'a str,
68}
69
70impl VisitMut for PseudoClassSelectorNameReplacer<'_> {
71 fn visit_mut_pseudo_class_selector(&mut self, n: &mut PseudoClassSelector) {
72 n.visit_mut_children_with(self);
73
74 if &*n.name.value == self.from {
75 n.name.value = self.to.into();
76 n.name.raw = None;
77 }
78 }
79}
80
81pub fn replace_pseudo_class_selector_name<N>(node: &mut N, from: &str, to: &str)
82where
83 N: for<'aa> VisitMutWith<PseudoClassSelectorNameReplacer<'aa>>,
84{
85 node.visit_mut_with(&mut PseudoClassSelectorNameReplacer { from, to });
86}
87
88pub struct PseudoElementSelectorNameReplacer<'a> {
89 from: &'a str,
90 to: &'a str,
91}
92
93impl VisitMut for PseudoElementSelectorNameReplacer<'_> {
94 fn visit_mut_pseudo_element_selector(&mut self, n: &mut PseudoElementSelector) {
95 n.visit_mut_children_with(self);
96
97 if &*n.name.value == self.from {
98 n.name.value = self.to.into();
99 n.name.raw = None;
100 }
101 }
102}
103
104pub fn replace_pseudo_element_selector_name<N>(node: &mut N, from: &str, to: &str)
105where
106 N: for<'aa> VisitMutWith<PseudoElementSelectorNameReplacer<'aa>>,
107{
108 node.visit_mut_with(&mut PseudoElementSelectorNameReplacer { from, to });
109}
110
111pub struct PseudoElementOnPseudoClassReplacer<'a> {
112 from: &'a str,
113 to: &'a str,
114}
115
116impl VisitMut for PseudoElementOnPseudoClassReplacer<'_> {
117 fn visit_mut_subclass_selector(&mut self, n: &mut SubclassSelector) {
118 n.visit_mut_children_with(self);
119
120 match n {
121 SubclassSelector::PseudoElement(PseudoElementSelector { name, span, .. })
122 if &*name.value == self.from =>
123 {
124 *n = SubclassSelector::PseudoClass(PseudoClassSelector {
125 span: *span,
126 name: Ident {
127 span: name.span,
128 value: self.to.into(),
129 raw: None,
130 },
131 children: None,
132 })
133 }
134 _ => {}
135 }
136 }
137}
138
139pub fn replace_pseudo_class_selector_on_pseudo_element_selector<N>(
140 node: &mut N,
141 from: &str,
142 to: &str,
143) where
144 N: for<'aa> VisitMutWith<PseudoElementOnPseudoClassReplacer<'aa>>,
145{
146 node.visit_mut_with(&mut PseudoElementOnPseudoClassReplacer { from, to });
147}
148
149#[derive(Serialize, Deserialize, Debug)]
150pub struct NamedColor {
151 pub hex: String,
152 pub rgb: Vec<u8>,
153}
154
155pub static NAMED_COLORS: Lazy<FxHashMap<StaticString, NamedColor>> = Lazy::new(|| {
156 serde_json::from_str(include_str!("./named-colors.json"))
157 .expect("failed to parse named-colors.json for html entities")
158});
159
160#[inline]
161fn is_escape_not_required(value: &str) -> bool {
162 if value.is_empty() {
163 return true;
164 }
165
166 if value.as_bytes()[0].is_ascii_digit() {
167 return false;
168 }
169
170 if value.len() == 1 && value.as_bytes()[0] == b'-' {
171 return false;
172 }
173
174 if value.len() >= 2 && value.as_bytes()[0] == b'-' && value.as_bytes()[1].is_ascii_digit() {
175 return false;
176 }
177
178 value.chars().all(|c| {
179 match c {
180 '\x00' => false,
181 '\x01'..='\x1f' | '\x7F' => false,
182 '-' | '_' => true,
183 _ if !c.is_ascii()
184 || c.is_ascii_digit()
185 || c.is_ascii_uppercase()
186 || c.is_ascii_lowercase() =>
187 {
188 true
189 }
190 _ => false,
192 }
193 })
194}
195
196pub fn serialize_ident(value: &str, minify: bool) -> Cow<'_, str> {
198 if is_escape_not_required(value) {
200 return Cow::Borrowed(value);
201 }
202
203 let mut result = String::with_capacity(value.len());
204
205 for (i, c) in value.chars().enumerate() {
218 match c {
219 '\x00' => {
221 result.push(char::REPLACEMENT_CHARACTER);
222 }
223 '\x01'..='\x1f' | '\x7F' => {
226 result.push_str(&hex_escape(c as u8, minify));
227 }
228 '0'..='9' if i == 0 => {
231 result.push_str(&hex_escape(c as u8, minify));
232 }
233 '0'..='9' if i == 1 && &value[0..1] == "-" => {
236 result.push_str(&hex_escape(c as u8, minify));
237 }
238 '-' if i == 0 && value.len() == 1 => {
241 result.push_str(&hex_escape(c as u8, minify));
242 }
243 _ if !c.is_ascii()
248 || c == '-'
249 || c == '_'
250 || c.is_ascii_digit()
251 || c.is_ascii_uppercase()
252 || c.is_ascii_lowercase() =>
253 {
254 result.push(c);
255 }
256 _ => {
258 let bytes = [b'\\', c as u8];
259
260 result.push_str(unsafe { str::from_utf8_unchecked(&bytes) });
263 }
264 }
265 }
266
267 Cow::Owned(result)
268}
269
270fn hex_escape(ascii_byte: u8, _minify: bool) -> String {
272 static HEX_DIGITS: &[u8; 16] = b"0123456789abcdef";
273
274 if ascii_byte > 0x0f {
275 let high = (ascii_byte >> 4) as usize;
276 let low = (ascii_byte & 0x0f) as usize;
277 unsafe { str::from_utf8_unchecked(&[b'\\', HEX_DIGITS[high], HEX_DIGITS[low], b' ']) }
278 .to_string()
279 } else {
280 unsafe { str::from_utf8_unchecked(&[b'\\', HEX_DIGITS[ascii_byte as usize], b' ']) }
281 .to_string()
282 }
283}
284
285pub fn hwb_to_rgb(hwb: [f64; 3]) -> [f64; 3] {
286 let [h, w, b] = hwb;
287
288 if w + b >= 1.0 {
289 let gray = w / (w + b);
290
291 return [gray, gray, gray];
292 }
293
294 let mut rgb = hsl_to_rgb([h, 1.0, 0.5]);
295
296 for item in &mut rgb {
297 *item *= 1.0 - w - b;
298 *item += w;
299 }
300
301 [rgb[0], rgb[1], rgb[2]]
302}
303
304pub fn hsl_to_rgb(hsl: [f64; 3]) -> [f64; 3] {
305 let [h, s, l] = hsl;
306
307 let r;
308 let g;
309 let b;
310
311 if s == 0.0 {
312 r = l;
313 g = l;
314 b = l;
315 } else {
316 let f = |n: f64| -> f64 {
317 let k = (n + h / 30.0) % 12.0;
318 let a = s * f64::min(l, 1.0 - l);
319
320 l - a * f64::min(k - 3.0, 9.0 - k).clamp(-1.0, 1.0)
321 };
322
323 r = f(0.0);
324 g = f(8.0);
325 b = f(4.0);
326 }
327
328 [r, g, b]
329}
330
331pub fn to_rgb255(abc: [f64; 3]) -> [f64; 3] {
332 let mut abc255 = abc;
333
334 for item in &mut abc255 {
335 *item *= 255.0;
336 }
337
338 abc255
339}
340
341pub fn clamp_unit_f64(val: f64) -> u8 {
342 (val * 255.).round().clamp(0., 255.) as u8
343}
344
345pub fn round_alpha(alpha: f64) -> f64 {
346 let mut rounded_alpha = (alpha * 100.).round() / 100.;
347
348 if clamp_unit_f64(rounded_alpha) != clamp_unit_f64(alpha) {
349 rounded_alpha = (alpha * 1000.).round() / 1000.;
350 }
351
352 rounded_alpha
353}
354
355#[inline]
356fn from_hex(c: u8) -> u8 {
357 match c {
358 b'0'..=b'9' => c - b'0',
359 b'a'..=b'f' => c - b'a' + 10,
360 b'A'..=b'F' => c - b'A' + 10,
361 _ => {
362 unreachable!();
363 }
364 }
365}
366
367pub fn hex_to_rgba(hex: &str) -> (u8, u8, u8, f64) {
368 let hex = hex.as_bytes();
369
370 match hex.len() {
371 8 => {
372 let r = from_hex(hex[0]) * 16 + from_hex(hex[1]);
373 let g = from_hex(hex[2]) * 16 + from_hex(hex[3]);
374 let b = from_hex(hex[4]) * 16 + from_hex(hex[5]);
375 let a = (from_hex(hex[6]) * 16 + from_hex(hex[7])) as f64 / 255.0;
376
377 (r, g, b, a)
378 }
379 4 => {
380 let r = from_hex(hex[0]) * 17;
381 let g = from_hex(hex[1]) * 17;
382 let b = from_hex(hex[2]) * 17;
383 let a = (from_hex(hex[3]) * 17) as f64 / 255.0;
384
385 (r, g, b, a)
386 }
387
388 _ => {
389 unreachable!()
390 }
391 }
392}
393
394pub fn angle_to_deg(value: f64, from: &Atom) -> f64 {
395 match &*from.to_ascii_lowercase() {
396 "deg" => value,
397 "grad" => value * 180.0 / 200.0,
398 "turn" => value * 360.0,
399 "rad" => value * 180.0 / PI,
400 _ => {
401 unreachable!("Unknown angle type: {:?}", from);
402 }
403 }
404}