fugafacere
Version:
A pure-JS implementation of the W3C's Canvas-2D Context API that can run on top of either Expo Graphics or a browser WebGL context.
354 lines (342 loc) • 13.2 kB
JavaScript
import convert from 'color-convert';
const x11ColorTable = {
aliceblue: [0.941176, 0.972549, 1.0, 1.0],
antiquewhite: [0.980392, 0.921569, 0.843137, 1.0],
aqua: [0.0, 1.0, 1.0, 1.0],
aquamarine: [0.498039, 1.0, 0.831373, 1.0],
azure: [0.941176, 1.0, 1.0, 1.0],
beige: [0.960784, 0.960784, 0.862745, 1.0],
bisque: [1.0, 0.894118, 0.768627, 1.0],
black: [0.0, 0.0, 0.0, 1.0],
blanchedalmond: [1.0, 0.921569, 0.803922, 1.0],
blue: [0.0, 0.0, 1.0, 1.0],
blueviolet: [0.541176, 0.168627, 0.886275, 1.0],
brown: [0.647059, 0.164706, 0.164706, 1.0],
burlywood: [0.870588, 0.721569, 0.529412, 1.0],
cadetblue: [0.372549, 0.619608, 0.627451, 1.0],
chartreuse: [0.498039, 1.0, 0.0, 1.0],
chocolate: [0.823529, 0.411765, 0.117647, 1.0],
coral: [1.0, 0.498039, 0.313725, 1.0],
cornflowerblue: [0.392157, 0.584314, 0.929412, 1.0],
cornsilk: [1.0, 0.972549, 0.862745, 1.0],
crimson: [0.862745, 0.078431, 0.235294, 1.0],
cyan: [0.0, 1.0, 1.0, 1.0],
darkblue: [0.0, 0.0, 0.545098, 1.0],
darkcyan: [0.0, 0.545098, 0.545098, 1.0],
darkgoldenrod: [0.721569, 0.52549, 0.043137, 1.0],
darkgray: [0.662745, 0.662745, 0.662745, 1.0],
darkgreen: [0.0, 0.392157, 0.0, 1.0],
darkgrey: [0.662745, 0.662745, 0.662745, 1.0],
darkkhaki: [0.741176, 0.717647, 0.419608, 1.0],
darkmagenta: [0.545098, 0.0, 0.545098, 1.0],
darkolivegreen: [0.333333, 0.419608, 0.184314, 1.0],
darkorange: [1.0, 0.54902, 0.0, 1.0],
darkorchid: [0.6, 0.196078, 0.8, 1.0],
darkred: [0.545098, 0.0, 0.0, 1.0],
darksalmon: [0.913725, 0.588235, 0.478431, 1.0],
darkseagreen: [0.560784, 0.737255, 0.560784, 1.0],
darkslateblue: [0.282353, 0.239216, 0.545098, 1.0],
darkslategray: [0.184314, 0.309804, 0.309804, 1.0],
darkslategrey: [0.184314, 0.309804, 0.309804, 1.0],
darkturquoise: [0.0, 0.807843, 0.819608, 1.0],
darkviolet: [0.580392, 0.0, 0.827451, 1.0],
deeppink: [1.0, 0.078431, 0.576471, 1.0],
deepskyblue: [0.0, 0.74902, 1.0, 1.0],
dimgray: [0.411765, 0.411765, 0.411765, 1.0],
dimgrey: [0.411765, 0.411765, 0.411765, 1.0],
dodgerblue: [0.117647, 0.564706, 1.0, 1.0],
firebrick: [0.698039, 0.133333, 0.133333, 1.0],
floralwhite: [1.0, 0.980392, 0.941176, 1.0],
forestgreen: [0.133333, 0.545098, 0.133333, 1.0],
fuchsia: [1.0, 0.0, 1.0, 1.0],
gainsboro: [0.862745, 0.862745, 0.862745, 1.0],
ghostwhite: [0.972549, 0.972549, 1.0, 1.0],
gold: [1.0, 0.843137, 0.0, 1.0],
goldenrod: [0.854902, 0.647059, 0.12549, 1.0],
gray: [0.501961, 0.501961, 0.501961, 1.0],
green: [0.0, 0.501961, 0.0, 1.0],
greenyellow: [0.678431, 1.0, 0.184314, 1.0],
grey: [0.501961, 0.501961, 0.501961, 1.0],
honeydew: [0.941176, 1.0, 0.941176, 1.0],
hotpink: [1.0, 0.411765, 0.705882, 1.0],
indianred: [0.803922, 0.360784, 0.360784, 1.0],
indigo: [0.294118, 0.0, 0.509804, 1.0],
ivory: [1.0, 1.0, 0.941176, 1.0],
khaki: [0.941176, 0.901961, 0.54902, 1.0],
lavender: [0.901961, 0.901961, 0.980392, 1.0],
lavenderblush: [1.0, 0.941176, 0.960784, 1.0],
lawngreen: [0.486275, 0.988235, 0.0, 1.0],
lemonchiffon: [1.0, 0.980392, 0.803922, 1.0],
lightblue: [0.678431, 0.847059, 0.901961, 1.0],
lightcoral: [0.941176, 0.501961, 0.501961, 1.0],
lightcyan: [0.878431, 1.0, 1.0, 1.0],
lightgoldenrodyellow: [0.980392, 0.980392, 0.823529, 1.0],
lightgray: [0.827451, 0.827451, 0.827451, 1.0],
lightgreen: [0.564706, 0.933333, 0.564706, 1.0],
lightgrey: [0.827451, 0.827451, 0.827451, 1.0],
lightpink: [1.0, 0.713725, 0.756863, 1.0],
lightsalmon: [1.0, 0.627451, 0.478431, 1.0],
lightseagreen: [0.12549, 0.698039, 0.666667, 1.0],
lightskyblue: [0.529412, 0.807843, 0.980392, 1.0],
lightslategray: [0.466667, 0.533333, 0.6, 1.0],
lightslategrey: [0.466667, 0.533333, 0.6, 1.0],
lightsteelblue: [0.690196, 0.768627, 0.870588, 1.0],
lightyellow: [1.0, 1.0, 0.878431, 1.0],
lime: [0.0, 1.0, 0.0, 1.0],
limegreen: [0.196078, 0.803922, 0.196078, 1.0],
linen: [0.980392, 0.941176, 0.901961, 1.0],
magenta: [1.0, 0.0, 1.0, 1.0],
maroon: [0.501961, 0.0, 0.0, 1.0],
mediumaquamarine: [0.4, 0.803922, 0.666667, 1.0],
mediumblue: [0.0, 0.0, 0.803922, 1.0],
mediumorchid: [0.729412, 0.333333, 0.827451, 1.0],
mediumpurple: [0.576471, 0.439216, 0.858824, 1.0],
mediumseagreen: [0.235294, 0.701961, 0.443137, 1.0],
mediumslateblue: [0.482353, 0.407843, 0.933333, 1.0],
mediumspringgreen: [0.0, 0.980392, 0.603922, 1.0],
mediumturquoise: [0.282353, 0.819608, 0.8, 1.0],
mediumvioletred: [0.780392, 0.082353, 0.521569, 1.0],
midnightblue: [0.098039, 0.098039, 0.439216, 1.0],
mintcream: [0.960784, 1.0, 0.980392, 1.0],
mistyrose: [1.0, 0.894118, 0.882353, 1.0],
moccasin: [1.0, 0.894118, 0.709804, 1.0],
navajowhite: [1.0, 0.870588, 0.678431, 1.0],
navy: [0.0, 0.0, 0.501961, 1.0],
oldlace: [0.992157, 0.960784, 0.901961, 1.0],
olive: [0.501961, 0.501961, 0.0, 1.0],
olivedrab: [0.419608, 0.556863, 0.137255, 1.0],
orange: [1.0, 0.647059, 0.0, 1.0],
orangered: [1.0, 0.270588, 0.0, 1.0],
orchid: [0.854902, 0.439216, 0.839216, 1.0],
palegoldenrod: [0.933333, 0.909804, 0.666667, 1.0],
palegreen: [0.596078, 0.984314, 0.596078, 1.0],
paleturquoise: [0.686275, 0.933333, 0.933333, 1.0],
palevioletred: [0.858824, 0.439216, 0.576471, 1.0],
papayawhip: [1.0, 0.937255, 0.835294, 1.0],
peachpuff: [1.0, 0.854902, 0.72549, 1.0],
peru: [0.803922, 0.521569, 0.247059, 1.0],
pink: [1.0, 0.752941, 0.796078, 1.0],
plum: [0.866667, 0.627451, 0.866667, 1.0],
powderblue: [0.690196, 0.878431, 0.901961, 1.0],
purple: [0.501961, 0.0, 0.501961, 1.0],
rebeccapurple: [0.4, 0.2, 0.6, 1.0],
red: [1.0, 0.0, 0.0, 1.0],
rosybrown: [0.737255, 0.560784, 0.560784, 1.0],
royalblue: [0.254902, 0.411765, 0.882353, 1.0],
saddlebrown: [0.545098, 0.270588, 0.07451, 1.0],
salmon: [0.980392, 0.501961, 0.447059, 1.0],
sandybrown: [0.956863, 0.643137, 0.376471, 1.0],
seagreen: [0.180392, 0.545098, 0.341176, 1.0],
seashell: [1.0, 0.960784, 0.933333, 1.0],
sienna: [0.627451, 0.321569, 0.176471, 1.0],
silver: [0.752941, 0.752941, 0.752941, 1.0],
skyblue: [0.529412, 0.807843, 0.921569, 1.0],
slateblue: [0.415686, 0.352941, 0.803922, 1.0],
slategray: [0.439216, 0.501961, 0.564706, 1.0],
slategrey: [0.439216, 0.501961, 0.564706, 1.0],
snow: [1.0, 0.980392, 0.980392, 1.0],
springgreen: [0.0, 1.0, 0.498039, 1.0],
steelblue: [0.27451, 0.509804, 0.705882, 1.0],
tan: [0.823529, 0.705882, 0.54902, 1.0],
teal: [0.0, 0.501961, 0.501961, 1.0],
thistle: [0.847059, 0.74902, 0.847059, 1.0],
tomato: [1.0, 0.388235, 0.278431, 1.0],
turquoise: [0.25098, 0.878431, 0.815686, 1.0],
violet: [0.933333, 0.509804, 0.933333, 1.0],
wheat: [0.960784, 0.870588, 0.701961, 1.0],
white: [1.0, 1.0, 1.0, 1.0],
whitesmoke: [0.960784, 0.960784, 0.960784, 1.0],
yellow: [1.0, 1.0, 0.0, 1.0],
yellowgreen: [0.603922, 0.803922, 0.196078, 1.0],
transparent: [0.0, 0.0, 0.0, 0.0],
};
const numberRegex = /(-?[0-9]*\.?[0-9]+([eE][0-9]+)?)/;
function parseAngleArg(arg) {
const matches = new RegExp(
numberRegex.source + /([dD][eE][gG]|[gG][rR][aA][dD]|[rR][aA][dD]|[tT][uU][rR][nN])?$/.source
).exec(arg);
if (!matches) {
throw new Error('Bad Color');
}
const value = Number(matches[1]);
if (!isFinite(value)) {
throw new Error('Bad Color');
}
const unit = (matches[3] || 'DEG').toUpperCase();
if (unit === 'DEG') {
return value;
} else if (unit === 'GRAD') {
return value * 0.9;
} else if (unit === 'RAD') {
return (value * 180) / Math.PI;
} else if (unit === 'TURN') {
return value * 360;
} else {
throw new Error('Bad Color');
}
}
function parseNumberPercentageArg(arg, opts) {
opts.min = isFinite(opts.min) ? opts.min : NaN;
opts.max = isFinite(opts.max) ? opts.max : 255;
opts.percentageRequired = opts.percentageRequired || false;
const matches = new RegExp(numberRegex.source + /(%)?$/.source).exec(arg);
if (!matches) {
throw new Error('Bad Color');
}
let value = Number(matches[1]);
if (isNaN(value)) {
throw new Error('Bad Color');
}
if (matches[3]) {
value = opts.max * value * 0.01;
} else {
if (opts.percentageRequired) {
throw new Error('Bad Color');
}
}
if (isFinite(opts.min) && value < opts.min) {
value = opts.min;
} else if (value > opts.max) {
value = opts.max;
}
return value;
}
function parseComponentFunctionArgs(args) {
let parsedArgs = /^\s*([^\s,]+)\s+([^\s,]+)\s+([^\s,]+)\s*(\/\s*([^\s]+)\s*)?$/.exec(args);
if (!parsedArgs) {
parsedArgs = /^\s*([^\s,]+)\s*,\s*([^\s,]+)\s*,\s*([^\s,]+)\s*(,\s*([^\s]+)\s*)?$/.exec(args);
}
if (!parsedArgs) {
throw new Error('Bad Color');
}
return [parsedArgs[1], parsedArgs[2], parsedArgs[3], parsedArgs[5]];
}
export default function cssToGlColor(cssStr) {
if (cssStr === '' || cssStr === undefined) {
throw new Error('Bad Color');
}
cssStr = cssStr.trim().toLowerCase();
if (cssStr.charAt(0) === '#') {
// Hex
const hexdigits = cssStr.substring(1);
if (/^[a-f0-9]+$/.exec(hexdigits) == null) {
throw new Error('Bad Color');
}
if (hexdigits.length === 3) {
return [
parseInt(hexdigits.substring(0, 1), 16) / 15,
parseInt(hexdigits.substring(1, 2), 16) / 15,
parseInt(hexdigits.substring(2, 3), 16) / 15,
1.0,
];
} else if (hexdigits.length === 4) {
return [
parseInt(hexdigits.substring(0, 1), 16) / 15,
parseInt(hexdigits.substring(1, 2), 16) / 15,
parseInt(hexdigits.substring(2, 3), 16) / 15,
parseInt(hexdigits.substring(3, 4), 16) / 15,
];
} else if (hexdigits.length === 6) {
return [
parseInt(hexdigits.substring(0, 2), 16) / 255,
parseInt(hexdigits.substring(2, 4), 16) / 255,
parseInt(hexdigits.substring(4, 6), 16) / 255,
1.0,
];
} else if (hexdigits.length === 8) {
return [
parseInt(hexdigits.substring(0, 2), 16) / 255,
parseInt(hexdigits.substring(2, 4), 16) / 255,
parseInt(hexdigits.substring(4, 6), 16) / 255,
parseInt(hexdigits.substring(6, 8), 16) / 255,
];
} else {
throw new Error('Bad Color');
}
} else {
const matches = /\s*([a-z-]+)\(([^)]+)\)?\s*$/.exec(cssStr);
if (matches == null) {
// Named color
if (cssStr in x11ColorTable) {
return x11ColorTable[cssStr];
} else {
throw new Error('Bad Color');
}
} else {
// Color function
const func = matches[1];
const args = matches[2];
if (func === 'rgb' || func === 'rgba') {
const parsedArgs = parseComponentFunctionArgs(args);
// Enforce either all are percentages or none are
const isPercentage = parsedArgs.map((v) => (v || '').includes('%'));
if (!(isPercentage[0] === isPercentage[1] && isPercentage[1] === isPercentage[2])) {
throw new Error('Bad Color');
}
const r = parseNumberPercentageArg(parsedArgs[0], { min: 0, max: 255 }) / 255;
const g = parseNumberPercentageArg(parsedArgs[1], { min: 0, max: 255 }) / 255;
const b = parseNumberPercentageArg(parsedArgs[2], { min: 0, max: 255 }) / 255;
const a = parseNumberPercentageArg(parsedArgs[3] || 1.0, {
min: 0,
max: 1,
});
return [r, g, b, a];
} else if (func === 'hsl' || func === 'hsla') {
const parsedArgs = parseComponentFunctionArgs(args);
const h = parseAngleArg(parsedArgs[0]) % 360;
const s = parseNumberPercentageArg(parsedArgs[1], {
min: 0,
max: 1,
percentageRequired: true,
});
const l = parseNumberPercentageArg(parsedArgs[2], {
min: 0,
max: 1,
percentageRequired: true,
});
const a = parseNumberPercentageArg(parsedArgs[3] || 1.0, {
min: 0,
max: 1,
});
const converted = convert.hsl.rgb(h, s * 100, l * 100);
return [converted[0] / 255, converted[1] / 255, converted[2] / 255, a];
} else if (func === 'hwb') {
// TODO: disallow comma notation?
const parsedArgs = parseComponentFunctionArgs(args);
const h = parseAngleArg(parsedArgs[0]) % 360;
let w = parseNumberPercentageArg(parsedArgs[1], {
min: 0,
max: 1,
percentageRequired: true,
});
let b = parseNumberPercentageArg(parsedArgs[2], {
min: 0,
max: 1,
percentageRequired: true,
});
const a = parseNumberPercentageArg(parsedArgs[3] || 1.0, {
min: 0,
max: 1,
});
if (w + b > 1) {
const ratio = w / b;
b = 1 / (1 + ratio);
w = 1 - b;
}
const converted = convert.hsl.rgb(h, 100, 50);
for (let i = 0; i < 3; i++) {
converted[i] *= (1 - w - b) / 255;
converted[i] += w;
}
converted.push(a);
return converted;
} else if (func === 'gray') {
throw new Error('Bad Color');
} else if (func === 'device-cmyk') {
throw new Error('Bad Color');
} else {
throw new Error('Bad Color');
}
}
}
}