shortest_color/
lib.rs

1use std::collections::HashMap;
2use std::sync::LazyLock;
3
4#[repr(C)]
5#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
6struct C {
7    r: u8,
8    g: u8,
9    b: u8,
10    a: u8,
11}
12
13#[inline(always)]
14const fn h(b: u8) -> u8 {
15    match b {
16        b'0'..=b'9' => b - b'0',
17        b'A'..=b'F' => b - b'A' + 10,
18        b'a'..=b'f' => b - b'a' + 10,
19        _ => 255,
20    }
21}
22
23#[inline(always)]
24const fn p(b: &[u8], i: usize) -> Option<u8> {
25    let h1 = h(b[i]);
26    let l = h(b[i + 1]);
27    if h1 == 255 || l == 255 {
28        None
29    } else {
30        Some((h1 << 4) | l)
31    }
32}
33
34#[inline(always)]
35const fn sg(b: u8) -> Option<u8> {
36    let v = h(b);
37    if v == 255 {
38        None
39    } else {
40        Some((v << 4) | v)
41    }
42}
43
44#[inline(always)]
45fn n(b: &[u8], m: f32, a: bool) -> Option<f32> {
46    let l = b.len();
47    if l == 0 || l > 8 {
48        return None;
49    }
50
51    let mut r = 0.0f32;
52    let mut d = false;
53    let mut dv = 10.0f32;
54    let ng = a && b[0] == b'-';
55    let st = if ng {
56        if l == 1 {
57            return None;
58        }
59        1
60    } else {
61        if !a && b[0] == b'-' {
62            return None;
63        }
64        0
65    };
66
67    for &c in &b[st..] {
68        match c {
69            b'0'..=b'9' => {
70                let dt = (c - b'0') as f32;
71                if d {
72                    r += dt / dv;
73                    dv *= 10.0;
74                    if dv > 10000.0 {
75                        break;
76                    }
77                } else {
78                    r = r * 10.0 + dt;
79                    if r > m && !ng {
80                        return None;
81                    }
82                }
83            }
84            b'.' => {
85                if d {
86                    return None;
87                }
88                d = true;
89            }
90            _ => return None,
91        }
92    }
93
94    let f = if ng { -r } else { r };
95    if f > m || (!a && f < 0.0) {
96        None
97    } else {
98        Some(f)
99    }
100}
101
102#[inline(always)]
103fn rt(b: &[u8]) -> Option<u8> {
104    let l = b.len();
105    if l == 0 || l > 6 {
106        return None;
107    }
108
109    if !b.contains(&b'.') {
110        let mut rs = 0u32;
111        for &c in b {
112            if !c.is_ascii_digit() {
113                return None;
114            }
115            rs = rs * 10 + (c - b'0') as u32;
116            if rs > 255 {
117                return None;
118            }
119        }
120        return Some(rs as u8);
121    }
122
123    let rt = n(b, 255.9, false)?;
124    Some((rt + 0.5) as u8)
125}
126
127#[inline(always)]
128fn pr(b: &[u8]) -> Option<f32> {
129    let l = b.len();
130    if !(2..=6).contains(&l) || b[l - 1] != b'%' {
131        return None;
132    }
133
134    let np = &b[..l - 1];
135    let rs = n(np, 100.0, true)?;
136    if !(0.0..=100.0).contains(&rs) {
137        return None;
138    }
139    Some(rs)
140}
141
142#[inline(always)]
143fn al(b: &[u8]) -> Option<u8> {
144    if b.is_empty() {
145        return None;
146    }
147
148    if b[b.len() - 1] == b'%' {
149        let pc = pr(b)?;
150        return Some((pc * 2.55 + 0.5) as u8);
151    }
152
153    let rs = n(b, 1.0, false)?;
154    Some((rs * 255.0 + 0.5) as u8)
155}
156
157#[inline(always)]
158fn ew(sl: &[u8], sf: &[u8]) -> bool {
159    let sl_l = sl.len();
160    let sf_l = sf.len();
161    sl_l >= sf_l && sl[sl_l - sf_l..].eq_ignore_ascii_case(sf)
162}
163
164#[inline(always)]
165fn an(s: &str) -> Option<f32> {
166    let b = s.as_bytes();
167    let l = b.len();
168    if l == 0 {
169        return None;
170    }
171
172    let (ns, mu) = if ew(b, b"grad") {
173        (&s[..l - 4], 0.9)
174    } else if ew(b, b"turn") {
175        (&s[..l - 4], 360.0)
176    } else if ew(b, b"deg") {
177        (&s[..l - 3], 1.0)
178    } else if ew(b, b"rad") {
179        (&s[..l - 3], 57.29578)
180    } else {
181        (s, 1.0)
182    };
183
184    let hu = n(ns.as_bytes(), f32::MAX, true)?;
185    Some(((hu * mu % 360.0) + 360.0) % 360.0)
186}
187
188#[inline(always)]
189fn hs(h: f32, s: f32, l: f32) -> (u8, u8, u8) {
190    let sn = s * 0.01;
191    let ln = l * 0.01;
192    let ch = (1.0 - (2.0 * ln - 1.0).abs()) * sn;
193    let hc = h / 60.0;
194    let x = ch * (1.0 - ((hc % 2.0) - 1.0).abs());
195    let m = ln - ch * 0.5;
196
197    let (r, g, b) = match hc as u8 {
198        0 => (ch, x, 0.0),
199        1 => (x, ch, 0.0),
200        2 => (0.0, ch, x),
201        3 => (0.0, x, ch),
202        4 => (x, 0.0, ch),
203        _ => (ch, 0.0, x),
204    };
205
206    (
207        ((r + m) * 255.0 + 0.5) as u8,
208        ((g + m) * 255.0 + 0.5) as u8,
209        ((b + m) * 255.0 + 0.5) as u8,
210    )
211}
212
213#[inline(always)]
214fn hc(s: &[u8]) -> Option<C> {
215    let hp = &s[1..];
216    match hp.len() {
217        3 => Some(C {
218            r: sg(hp[0])?,
219            g: sg(hp[1])?,
220            b: sg(hp[2])?,
221            a: 255,
222        }),
223        4 => Some(C {
224            r: sg(hp[0])?,
225            g: sg(hp[1])?,
226            b: sg(hp[2])?,
227            a: sg(hp[3])?,
228        }),
229        6 => Some(C {
230            r: p(hp, 0)?,
231            g: p(hp, 2)?,
232            b: p(hp, 4)?,
233            a: 255,
234        }),
235        8 => Some(C {
236            r: p(hp, 0)?,
237            g: p(hp, 2)?,
238            b: p(hp, 4)?,
239            a: p(hp, 6)?,
240        }),
241        _ => None,
242    }
243}
244
245static NAME_MAP: LazyLock<HashMap<Vec<u8>, C>> = LazyLock::new(|| {
246    let mut map = HashMap::new();
247    for &(nm, hx) in K {
248        if let Some(c) = hc(hx) {
249            let lower_name = nm.iter().map(|&b| b | 0x20).collect();
250            map.insert(lower_name, c);
251        }
252    }
253    map
254});
255
256#[inline(always)]
257fn lk(nm: &[u8]) -> Option<C> {
258    let lower_nm: Vec<u8> = nm.iter().map(|&b| b | 0x20).collect();
259    NAME_MAP.get(&lower_nm).copied()
260}
261
262#[inline(always)]
263fn ar(ag: &[u8]) -> Option<([&[u8]; 4], usize)> {
264    let mut pt = [&[][..]; 4];
265    let mut c = 0;
266    let mut st = 0;
267    let mut ia = false;
268
269    for i in 0..ag.len() {
270        let b = ag[i];
271        let is = matches!(b, b' ' | b'\t' | b',');
272
273        if !is && !ia {
274            st = i;
275            ia = true;
276        } else if is && ia {
277            if c >= 4 {
278                return None;
279            }
280            pt[c] = &ag[st..i];
281            c += 1;
282            ia = false;
283        }
284    }
285
286    if ia {
287        if c >= 4 {
288            return None;
289        }
290        pt[c] = &ag[st..];
291        c += 1;
292    }
293
294    if (2..=4).contains(&c) {
295        Some((pt, c))
296    } else {
297        None
298    }
299}
300
301#[inline(always)]
302fn pc(s: &[u8]) -> Option<C> {
303    let l = s.len();
304
305    if s[0] == b'#' {
306        return hc(s);
307    }
308
309    if s[3] != b'(' && s[4] != b'(' {
310        return lk(s);
311    }
312
313    if s[l - 1] != b')' {
314        return None;
315    }
316
317    let (f, ha, st) = match &s[..3] {
318        px if px.eq_ignore_ascii_case(b"rgb") => match s[3] {
319            b'(' => (0, false, 4),
320            b'a' => (0, true, 5),
321            _ => return None,
322        },
323        px if px.eq_ignore_ascii_case(b"hsl") => match s[3] {
324            b'(' => (1, false, 4),
325            b'a' => (1, true, 5),
326            _ => return None,
327        },
328        _ => return None,
329    };
330
331    let (pt, c) = ar(&s[st..l - 1])?;
332
333    if ha && c == 2 {
334        if let Some(bc) = lk(pt[0]) {
335            let a = al(pt[1])?;
336            return Some(C { a, ..bc });
337        }
338        return None;
339    }
340
341    let ep = if ha { 4 } else { 3 };
342    if c != ep {
343        return None;
344    }
345
346    let a = if ha { al(pt[3])? } else { 255 };
347
348    match f {
349        0 => {
350            let rg = rt(pt[0])?;
351            let g = rt(pt[1])?;
352            let b = rt(pt[2])?;
353            Some(C { r: rg, g, b, a })
354        }
355        _ => {
356            let hs_str = std::str::from_utf8(pt[0]).ok()?;
357            let hu = an(hs_str)?;
358            let sa = pr(pt[1])?;
359            let li = pr(pt[2])?;
360            let (r, g, b) = hs(hu, sa, li);
361            Some(C { r, g, b, a })
362        }
363    }
364}
365
366#[inline(always)]
367const fn sh(r: u8, g: u8, b: u8, a: u8) -> bool {
368    (r & 0x0F) * 0x11 == r
369        && (g & 0x0F) * 0x11 == g
370        && (b & 0x0F) * 0x11 == b
371        && (a & 0x0F) * 0x11 == a
372}
373
374static L: LazyLock<Vec<(u32, &'static [u8])>> = LazyLock::new(|| {
375    let v: Vec<_> = K
376        .iter()
377        .filter_map(|&(nm, hx)| {
378            hc(hx).and_then(|cl| {
379                if cl.a == 255 {
380                    let rg = ((cl.r as u32) << 16) | ((cl.g as u32) << 8) | (cl.b as u32);
381                    Some((rg, nm))
382                } else {
383                    None
384                }
385            })
386        })
387        .collect();
388
389    let mut m = HashMap::new();
390    for (rg, nm) in v {
391        m.entry(rg).or_insert(nm);
392    }
393
394    let mut result: Vec<_> = m.into_iter().collect();
395    result.sort_unstable_by_key(|&(rg, _)| rg);
396    result
397});
398
399#[inline(always)]
400fn fn1(rg: u32) -> Option<&'static [u8]> {
401    L.binary_search_by_key(&rg, |&(r, _)| r)
402        .ok()
403        .map(|i| L[i].1)
404}
405
406#[inline(always)]
407const fn hd(n: u8) -> u8 {
408    match n {
409        0..=9 => b'0' + n,
410        _ => b'a' + (n - 10),
411    }
412}
413
414#[inline(always)]
415fn cs(cl: &C) -> String {
416    if cl.a != 255 {
417        let sh1 = sh(cl.r, cl.g, cl.b, cl.a);
418        let mut bf = [0u8; 9];
419        let sl = if sh1 {
420            bf[0] = b'#';
421            bf[1] = hd(cl.r >> 4);
422            bf[2] = hd(cl.g >> 4);
423            bf[3] = hd(cl.b >> 4);
424            bf[4] = hd(cl.a >> 4);
425            &bf[..5]
426        } else {
427            bf[0] = b'#';
428            bf[1] = hd(cl.r >> 4);
429            bf[2] = hd(cl.r & 0xF);
430            bf[3] = hd(cl.g >> 4);
431            bf[4] = hd(cl.g & 0xF);
432            bf[5] = hd(cl.b >> 4);
433            bf[6] = hd(cl.b & 0xF);
434            bf[7] = hd(cl.a >> 4);
435            bf[8] = hd(cl.a & 0xF);
436            &bf[..9]
437        };
438        return unsafe { std::str::from_utf8_unchecked(sl) }.to_string();
439    }
440
441    let rg = ((cl.r as u32) << 16) | ((cl.g as u32) << 8) | (cl.b as u32);
442
443    if let Some(nm) = fn1(rg) {
444        let sh1 = sh(cl.r, cl.g, cl.b, 255);
445        let ml = if sh1 { 4 } else { 7 };
446        if nm.len() < ml {
447            return unsafe { std::str::from_utf8_unchecked(nm) }.to_string();
448        }
449    }
450
451    let sh1 = sh(cl.r, cl.g, cl.b, 255);
452    let mut bf = [0u8; 7];
453    let sl = if sh1 {
454        bf[0] = b'#';
455        bf[1] = hd(cl.r >> 4);
456        bf[2] = hd(cl.g >> 4);
457        bf[3] = hd(cl.b >> 4);
458        &bf[..4]
459    } else {
460        bf[0] = b'#';
461        bf[1] = hd(cl.r >> 4);
462        bf[2] = hd(cl.r & 0xF);
463        bf[3] = hd(cl.g >> 4);
464        bf[4] = hd(cl.g & 0xF);
465        bf[5] = hd(cl.b >> 4);
466        bf[6] = hd(cl.b & 0xF);
467        &bf[..7]
468    };
469    unsafe { std::str::from_utf8_unchecked(sl) }.to_string()
470}
471
472#[inline(always)]
473fn tw(s: &[u8]) -> &[u8] {
474    let a = s
475        .iter()
476        .position(|&b| !matches!(b, b' ' | b'\t' | b'\n' | b'\r'))
477        .unwrap_or(s.len());
478    let z = s
479        .iter()
480        .rposition(|&b| !matches!(b, b' ' | b'\t' | b'\n' | b'\r'))
481        .map_or(a, |i| i + 1);
482    &s[a..z]
483}
484
485#[inline(always)]
486fn tlf(s: &[u8]) -> String {
487    let mut r = String::with_capacity(s.len());
488    unsafe {
489        let b = r.as_mut_vec();
490        b.extend(s.iter().map(|&b| b | 0x20));
491    }
492    r
493}
494
495pub fn shorten_css_color(i: impl AsRef<str>) -> String {
496    let s = i.as_ref().as_bytes();
497    if s.is_empty() {
498        return String::new();
499    }
500
501    let tr = tw(s);
502
503    if tr.len() < 5 {
504        if tr.eq_ignore_ascii_case(b"#f00") {
505            return String::from("red");
506        }
507        return tlf(tr);
508    }
509
510    pc(tr).map_or_else(|| tlf(tr), |x| cs(&x))
511}
512
513const K: &[(&[u8], &[u8])] = &[
514    (b"aliceblue", b"#f0f8ff"),
515    (b"antiquewhite", b"#faebd7"),
516    (b"aqua", b"#0ff"),
517    (b"aquamarine", b"#7fffd4"),
518    (b"azure", b"#f0ffff"),
519    (b"beige", b"#f5f5dc"),
520    (b"bisque", b"#ffe4c4"),
521    (b"black", b"#000"),
522    (b"blanchedalmond", b"#ffebcd"),
523    (b"blue", b"#00f"),
524    (b"blueviolet", b"#8a2be2"),
525    (b"brown", b"#a52a2a"),
526    (b"burlywood", b"#deb887"),
527    (b"cadetblue", b"#5f9ea0"),
528    (b"chartreuse", b"#7fff00"),
529    (b"chocolate", b"#d2691e"),
530    (b"coral", b"#ff7f50"),
531    (b"cornflowerblue", b"#6495ed"),
532    (b"cornsilk", b"#fff8dc"),
533    (b"crimson", b"#dc143c"),
534    (b"cyan", b"#0ff"),
535    (b"darkblue", b"#00008b"),
536    (b"darkcyan", b"#008b8b"),
537    (b"darkgoldenrod", b"#b8860b"),
538    (b"darkgray", b"#a9a9a9"),
539    (b"darkgreen", b"#006400"),
540    (b"darkgrey", b"#a9a9a9"),
541    (b"darkkhaki", b"#bdb76b"),
542    (b"darkmagenta", b"#8b008b"),
543    (b"darkolivegreen", b"#556b2f"),
544    (b"darkorange", b"#ff8c00"),
545    (b"darkorchid", b"#9932cc"),
546    (b"darkred", b"#8b0000"),
547    (b"darksalmon", b"#e9967a"),
548    (b"darkseagreen", b"#8fbc8f"),
549    (b"darkslateblue", b"#483d8b"),
550    (b"darkslategray", b"#2f4f4f"),
551    (b"darkslategrey", b"#2f4f4f"),
552    (b"darkturquoise", b"#00ced1"),
553    (b"darkviolet", b"#9400d3"),
554    (b"deeppink", b"#ff1493"),
555    (b"deepskyblue", b"#00bfff"),
556    (b"dimgray", b"#696969"),
557    (b"dimgrey", b"#696969"),
558    (b"dodgerblue", b"#1e90ff"),
559    (b"firebrick", b"#b22222"),
560    (b"floralwhite", b"#fffaf0"),
561    (b"forestgreen", b"#228b22"),
562    (b"fuchsia", b"#f0f"),
563    (b"gainsboro", b"#dcdcdc"),
564    (b"ghostwhite", b"#f8f8ff"),
565    (b"gold", b"#ffd700"),
566    (b"goldenrod", b"#daa520"),
567    (b"gray", b"#808080"),
568    (b"green", b"#008000"),
569    (b"greenyellow", b"#adff2f"),
570    (b"grey", b"#808080"),
571    (b"honeydew", b"#f0fff0"),
572    (b"hotpink", b"#ff69b4"),
573    (b"indianred", b"#cd5c5c"),
574    (b"indigo", b"#4b0082"),
575    (b"ivory", b"#fffff0"),
576    (b"khaki", b"#f0e68c"),
577    (b"lavender", b"#e6e6fa"),
578    (b"lavenderblush", b"#fff0f5"),
579    (b"lawngreen", b"#7cfc00"),
580    (b"lemonchiffon", b"#fffacd"),
581    (b"lightblue", b"#add8e6"),
582    (b"lightcoral", b"#f08080"),
583    (b"lightcyan", b"#e0ffff"),
584    (b"lightgoldenrodyellow", b"#fafad2"),
585    (b"lightgray", b"#d3d3d3"),
586    (b"lightgreen", b"#90ee90"),
587    (b"lightgrey", b"#d3d3d3"),
588    (b"lightpink", b"#ffb6c1"),
589    (b"lightsalmon", b"#ffa07a"),
590    (b"lightseagreen", b"#20b2aa"),
591    (b"lightskyblue", b"#87cefa"),
592    (b"lightslategray", b"#778899"),
593    (b"lightslategrey", b"#778899"),
594    (b"lightsteelblue", b"#b0c4de"),
595    (b"lightyellow", b"#ffffe0"),
596    (b"lime", b"#0f0"),
597    (b"limegreen", b"#32cd32"),
598    (b"linen", b"#faf0e6"),
599    (b"magenta", b"#f0f"),
600    (b"maroon", b"#800000"),
601    (b"mediumaquamarine", b"#66cdaa"),
602    (b"mediumblue", b"#0000cd"),
603    (b"mediumorchid", b"#ba55d3"),
604    (b"mediumpurple", b"#9370db"),
605    (b"mediumseagreen", b"#3cb371"),
606    (b"mediumslateblue", b"#7b68ee"),
607    (b"mediumspringgreen", b"#00fa9a"),
608    (b"mediumturquoise", b"#48d1cc"),
609    (b"mediumvioletred", b"#c71585"),
610    (b"midnightblue", b"#191970"),
611    (b"mintcream", b"#f5fffa"),
612    (b"mistyrose", b"#ffe4e1"),
613    (b"moccasin", b"#ffe4b5"),
614    (b"navajowhite", b"#ffdead"),
615    (b"navy", b"#000080"),
616    (b"oldlace", b"#fdf5e6"),
617    (b"olive", b"#808000"),
618    (b"olivedrab", b"#6b8e23"),
619    (b"orange", b"#ffa500"),
620    (b"orangered", b"#ff4500"),
621    (b"orchid", b"#da70d6"),
622    (b"palegoldenrod", b"#eee8aa"),
623    (b"palegreen", b"#98fb98"),
624    (b"paleturquoise", b"#afeeee"),
625    (b"palevioletred", b"#db7093"),
626    (b"papayawhip", b"#ffefd5"),
627    (b"peachpuff", b"#ffdab9"),
628    (b"peru", b"#cd853f"),
629    (b"pink", b"#ffc0cb"),
630    (b"plum", b"#dda0dd"),
631    (b"powderblue", b"#b0e0e6"),
632    (b"purple", b"#800080"),
633    (b"rebeccapurple", b"#639"),
634    (b"red", b"#f00"),
635    (b"rosybrown", b"#bc8f8f"),
636    (b"royalblue", b"#4169e1"),
637    (b"saddlebrown", b"#8b4513"),
638    (b"salmon", b"#fa8072"),
639    (b"sandybrown", b"#f4a460"),
640    (b"seagreen", b"#2e8b57"),
641    (b"seashell", b"#fff5ee"),
642    (b"sienna", b"#a0522d"),
643    (b"silver", b"#c0c0c0"),
644    (b"skyblue", b"#87ceeb"),
645    (b"slateblue", b"#6a5acd"),
646    (b"slategray", b"#708090"),
647    (b"slategrey", b"#708090"),
648    (b"snow", b"#fffafa"),
649    (b"springgreen", b"#00ff7f"),
650    (b"steelblue", b"#4682b4"),
651    (b"tan", b"#d2b48c"),
652    (b"teal", b"#008080"),
653    (b"thistle", b"#d8bfd8"),
654    (b"tomato", b"#ff6347"),
655    (b"transparent", b"#0000"),
656    (b"turquoise", b"#40e0d0"),
657    (b"violet", b"#ee82ee"),
658    (b"wheat", b"#f5deb3"),
659    (b"white", b"#fff"),
660    (b"whitesmoke", b"#f5f5f5"),
661    (b"yellow", b"#ff0"),
662    (b"yellowgreen", b"#9acd32"),
663];