'use strict'; const colorConverter = (() => { return { parse, format, formatAlpha, RGBtoHSV, HSVtoRGB, HSLtoHSV, HSVtoHSL, constrainHue, snapToInt, ALPHA_DIGITS: 3, // NAMED_COLORS is added below }; function format(color = '', type = color.type, hexUppercase, usoMode) { if (!color || !type) return typeof color === 'string' ? color : ''; const {a} = color; let aStr = formatAlpha(a); if (aStr) aStr = ', ' + aStr; if (type !== 'hsl' && color.type === 'hsl') { color = HSVtoRGB(HSLtoHSV(color)); } const {r, g, b, h, s, l} = color; switch (type) { case 'hex': { let res = '#' + hex2(r) + hex2(g) + hex2(b) + (aStr ? hex2(Math.round(a * 255)) : ''); if (!usoMode) res = res.replace(/^#(.)\1(.)\2(.)\3(?:(.)\4)?$/, '#$1$2$3$4'); return hexUppercase ? res.toUpperCase() : res; } case 'rgb': { const rgb = [r, g, b].map(Math.round).join(', '); return usoMode ? rgb : `rgb${aStr ? 'a' : ''}(${rgb}${aStr})`; } case 'hsl': return `hsl${aStr ? 'a' : ''}(${h}, ${s}%, ${l}%${aStr})`; } } // Copied from _hexcolor() in parserlib.js function validateHex(color) { return /^#[a-f\d]+$/i.test(color) && [4, 5, 7, 9].some(n => color.length === n); } function validateRGB(nums) { const isPercentage = nums[0].endsWith('%'); const valid = isPercentage ? validatePercentage : validateNum; return nums.slice(0, 3).every(valid); } function validatePercentage(s) { if (!s.endsWith('%')) return false; const n = Number(s.slice(0, -1)); return n >= 0 && n <= 100; } function validateNum(s) { const n = Number(s); return n >= 0 && n <= 255; } function validateHSL(nums) { return validateAngle(nums[0]) && nums.slice(1, 3).every(validatePercentage); } function validateAngle(s) { return /^-?(\d+|\d*\.\d+)(deg|grad|rad|turn)?$/i.test(s); } function validateAlpha(alpha) { if (alpha.endsWith('%')) { return validatePercentage(alpha); } const n = Number(alpha); return n >= 0 && n <= 1; } function parse(str) { if (typeof str !== 'string') return; str = str.trim(); if (!str) return; if (str[0] !== '#' && !str.includes('(')) { // eslint-disable-next-line no-use-before-define str = colorConverter.NAMED_COLORS.get(str); if (!str) return; } if (str[0] === '#') { if (!validateHex(str)) { return null; } str = str.slice(1); const [r, g, b, a = 255] = str.length <= 4 ? str.match(/(.)/g).map(c => parseInt(c + c, 16)) : str.match(/(..)/g).map(c => parseInt(c, 16)); return {type: 'hex', r, g, b, a: a === 255 ? undefined : a / 255}; } const [, type, value] = str.match(/^(rgb|hsl)a?\((.*?)\)|$/i); if (!type) return; const comma = value.includes(',') && !value.includes('/'); const num = value.trim().split(comma ? /\s*,\s*/ : /\s+(?!\/)|\s*\/\s*/); if (num.length < 3 || num.length > 4) return; if (num[3] && !validateAlpha(num[3])) return null; let a = !num[3] ? 1 : parseFloat(num[3]) / (num[3].endsWith('%') ? 100 : 1); if (isNaN(a)) a = 1; const first = num[0]; if (/rgb/i.test(type)) { if (!validateRGB(num)) { return null; } const k = first.endsWith('%') ? 2.55 : 1; const [r, g, b] = num.map(s => Math.round(parseFloat(s) * k)); return {type: 'rgb', r, g, b, a}; } else { if (!validateHSL(num)) { return null; } let h = parseFloat(first); if (first.endsWith('grad')) h *= 360 / 400; else if (first.endsWith('rad')) h *= 180 / Math.PI; else if (first.endsWith('turn')) h *= 360; const s = parseFloat(num[1]); const l = parseFloat(num[2]); return {type: 'hsl', h, s, l, a}; } } function formatAlpha(a) { return isNaN(a) ? '' : (a + .5 * Math.pow(10, -colorConverter.ALPHA_DIGITS)) .toFixed(colorConverter.ALPHA_DIGITS + 1) .slice(0, -1) .replace(/^0(?=\.[1-9])|^1\.0+?$|\.?0+$/g, ''); } function RGBtoHSV({r, g, b, a}) { r /= 255; g /= 255; b /= 255; const MaxC = Math.max(r, g, b); const MinC = Math.min(r, g, b); const DeltaC = MaxC - MinC; let h = DeltaC === 0 ? 0 : MaxC === r ? 60 * (((g - b) / DeltaC) % 6) : MaxC === g ? 60 * (((b - r) / DeltaC) + 2) : MaxC === b ? 60 * (((r - g) / DeltaC) + 4) : 0; h = constrainHue(h); return { h, s: MaxC === 0 ? 0 : DeltaC / MaxC, v: MaxC, a, }; } function HSVtoRGB({h, s, v}) { h = constrainHue(h) % 360; const C = s * v; const X = C * (1 - Math.abs((h / 60) % 2 - 1)); const m = v - C; const [r, g, b] = h >= 0 && h < 60 ? [C, X, 0] : h >= 60 && h < 120 ? [X, C, 0] : h >= 120 && h < 180 ? [0, C, X] : h >= 180 && h < 240 ? [0, X, C] : h >= 240 && h < 300 ? [X, 0, C] : h >= 300 && h < 360 ? [C, 0, X] : []; return { r: snapToInt(Math.round((r + m) * 255)), g: snapToInt(Math.round((g + m) * 255)), b: snapToInt(Math.round((b + m) * 255)), }; } function HSLtoHSV({h, s, l, a}) { const t = s * (l < 50 ? l : 100 - l) / 100; return { h: constrainHue(h), s: t + l ? 200 * t / (t + l) / 100 : 0, v: (t + l) / 100, a, }; } function HSVtoHSL({h, s, v}) { const l = (2 - s) * v / 2; const t = l < .5 ? l * 2 : 2 - l * 2; return { h: Math.round(constrainHue(h)), s: Math.round(t ? s * v / t * 100 : 0), l: Math.round(l * 100), }; } function constrainHue(h) { return h < 0 ? h % 360 + 360 : h > 360 ? h % 360 : h; } function snapToInt(num) { const int = Math.round(num); return Math.abs(int - num) < 1e-3 ? int : num; } function hex2(val) { return (val < 16 ? '0' : '') + (val >> 0).toString(16); } })(); colorConverter.NAMED_COLORS = new Map([ ['transparent', 'rgba(0, 0, 0, 0)'], // CSS4 named colors ['aliceblue', '#f0f8ff'], ['antiquewhite', '#faebd7'], ['aqua', '#00ffff'], ['aquamarine', '#7fffd4'], ['azure', '#f0ffff'], ['beige', '#f5f5dc'], ['bisque', '#ffe4c4'], ['black', '#000000'], ['blanchedalmond', '#ffebcd'], ['blue', '#0000ff'], ['blueviolet', '#8a2be2'], ['brown', '#a52a2a'], ['burlywood', '#deb887'], ['cadetblue', '#5f9ea0'], ['chartreuse', '#7fff00'], ['chocolate', '#d2691e'], ['coral', '#ff7f50'], ['cornflowerblue', '#6495ed'], ['cornsilk', '#fff8dc'], ['crimson', '#dc143c'], ['cyan', '#00ffff'], ['darkblue', '#00008b'], ['darkcyan', '#008b8b'], ['darkgoldenrod', '#b8860b'], ['darkgray', '#a9a9a9'], ['darkgrey', '#a9a9a9'], ['darkgreen', '#006400'], ['darkkhaki', '#bdb76b'], ['darkmagenta', '#8b008b'], ['darkolivegreen', '#556b2f'], ['darkorange', '#ff8c00'], ['darkorchid', '#9932cc'], ['darkred', '#8b0000'], ['darksalmon', '#e9967a'], ['darkseagreen', '#8fbc8f'], ['darkslateblue', '#483d8b'], ['darkslategray', '#2f4f4f'], ['darkslategrey', '#2f4f4f'], ['darkturquoise', '#00ced1'], ['darkviolet', '#9400d3'], ['deeppink', '#ff1493'], ['deepskyblue', '#00bfff'], ['dimgray', '#696969'], ['dimgrey', '#696969'], ['dodgerblue', '#1e90ff'], ['firebrick', '#b22222'], ['floralwhite', '#fffaf0'], ['forestgreen', '#228b22'], ['fuchsia', '#ff00ff'], ['gainsboro', '#dcdcdc'], ['ghostwhite', '#f8f8ff'], ['gold', '#ffd700'], ['goldenrod', '#daa520'], ['gray', '#808080'], ['grey', '#808080'], ['green', '#008000'], ['greenyellow', '#adff2f'], ['honeydew', '#f0fff0'], ['hotpink', '#ff69b4'], ['indianred', '#cd5c5c'], ['indigo', '#4b0082'], ['ivory', '#fffff0'], ['khaki', '#f0e68c'], ['lavender', '#e6e6fa'], ['lavenderblush', '#fff0f5'], ['lawngreen', '#7cfc00'], ['lemonchiffon', '#fffacd'], ['lightblue', '#add8e6'], ['lightcoral', '#f08080'], ['lightcyan', '#e0ffff'], ['lightgoldenrodyellow', '#fafad2'], ['lightgray', '#d3d3d3'], ['lightgrey', '#d3d3d3'], ['lightgreen', '#90ee90'], ['lightpink', '#ffb6c1'], ['lightsalmon', '#ffa07a'], ['lightseagreen', '#20b2aa'], ['lightskyblue', '#87cefa'], ['lightslategray', '#778899'], ['lightslategrey', '#778899'], ['lightsteelblue', '#b0c4de'], ['lightyellow', '#ffffe0'], ['lime', '#00ff00'], ['limegreen', '#32cd32'], ['linen', '#faf0e6'], ['magenta', '#ff00ff'], ['maroon', '#800000'], ['mediumaquamarine', '#66cdaa'], ['mediumblue', '#0000cd'], ['mediumorchid', '#ba55d3'], ['mediumpurple', '#9370db'], ['mediumseagreen', '#3cb371'], ['mediumslateblue', '#7b68ee'], ['mediumspringgreen', '#00fa9a'], ['mediumturquoise', '#48d1cc'], ['mediumvioletred', '#c71585'], ['midnightblue', '#191970'], ['mintcream', '#f5fffa'], ['mistyrose', '#ffe4e1'], ['moccasin', '#ffe4b5'], ['navajowhite', '#ffdead'], ['navy', '#000080'], ['oldlace', '#fdf5e6'], ['olive', '#808000'], ['olivedrab', '#6b8e23'], ['orange', '#ffa500'], ['orangered', '#ff4500'], ['orchid', '#da70d6'], ['palegoldenrod', '#eee8aa'], ['palegreen', '#98fb98'], ['paleturquoise', '#afeeee'], ['palevioletred', '#db7093'], ['papayawhip', '#ffefd5'], ['peachpuff', '#ffdab9'], ['peru', '#cd853f'], ['pink', '#ffc0cb'], ['plum', '#dda0dd'], ['powderblue', '#b0e0e6'], ['purple', '#800080'], ['rebeccapurple', '#663399'], ['red', '#ff0000'], ['rosybrown', '#bc8f8f'], ['royalblue', '#4169e1'], ['saddlebrown', '#8b4513'], ['salmon', '#fa8072'], ['sandybrown', '#f4a460'], ['seagreen', '#2e8b57'], ['seashell', '#fff5ee'], ['sienna', '#a0522d'], ['silver', '#c0c0c0'], ['skyblue', '#87ceeb'], ['slateblue', '#6a5acd'], ['slategray', '#708090'], ['slategrey', '#708090'], ['snow', '#fffafa'], ['springgreen', '#00ff7f'], ['steelblue', '#4682b4'], ['tan', '#d2b48c'], ['teal', '#008080'], ['thistle', '#d8bfd8'], ['tomato', '#ff6347'], ['turquoise', '#40e0d0'], ['violet', '#ee82ee'], ['wheat', '#f5deb3'], ['white', '#ffffff'], ['whitesmoke', '#f5f5f5'], ['yellow', '#ffff00'], ['yellowgreen', '#9acd32'], ]);