/
parse-css.ts
83 lines (79 loc) · 2.92 KB
/
parse-css.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
import { clamp01 } from "@thi.ng/math/interval";
import { maybeParseFloat, maybeParseInt } from "@thi.ng/strings/parse";
import { Color, ColorMode, INV8BIT } from "./api";
import { convert } from "./convert";
import { CSS_NAMES } from "./names";
import { IDeref } from "@thi.ng/api/api";
const RE_HEX = /^#?([0-9a-f]{3,8})$/i;
const RE_CSS = /^(rgb|hsl)a?\(\s*([0-9.]+?),\s*([0-9.]+%?),\s*([0-9.]+%?),?\s*([0-9.]+)?\s*\)$/;
export const parseCSS =
(col: string | IDeref<string>, mode = ColorMode.RGBA) => {
col = typeof col === "string" ? col : col.deref();
let res: Color | number;
let resMode: ColorMode;
if (col.charAt(0) === "#") {
resMode = ColorMode.INT32;
res = parseHex(col);
} else {
const match = RE_CSS.exec(col);
if (match) {
if (match[1] === "rgb" || match[1] === "rgba") {
resMode = ColorMode.RGBA;
res = [
parseChannel(match[2]),
parseChannel(match[3]),
parseChannel(match[4]),
maybeParseFloat(match[5], 1)
];
} else {
resMode = ColorMode.HSLA;
res = [
maybeParseFloat(match[2]) / 360,
parseChannel(match[3]),
parseChannel(match[4]),
maybeParseFloat(match[5], 1)
];
}
} else {
const c = CSS_NAMES[col];
!c && illegalArgs(`invalid color: "${col}"`);
resMode = ColorMode.INT32;
res = parseHex(c);
}
}
if (res && resMode != mode) {
return convert(res, mode, resMode);
}
return res;
};
export const parseHex =
(src: string): number => {
const match = RE_HEX.exec(src);
if (match) {
const hex = match[1];
switch (hex.length) {
case 3: {
const [r, g, b] = hex;
return (maybeParseInt(`${r}${r}${g}${g}${b}${b}`, 0, 16) | 0xff000000) >>> 0;
}
case 4: {
const [a, r, g, b] = hex;
return (maybeParseInt(`${a}${a}${r}${r}${g}${g}${b}${b}`, 0, 16)) >>> 0;
}
case 6:
return (maybeParseInt(hex, 0, 16) | 0xff000000) >>> 0;
case 8:
return maybeParseInt(hex, 0, 16) >>> 0;
default:
}
}
illegalArgs(`invalid hex color: "${src}"`);
};
const parseChannel =
(c: string) =>
clamp01(
c.indexOf("%") > 0 ?
maybeParseFloat(c) * 0.01 :
maybeParseFloat(c) * INV8BIT
);