From 0871ace481232eaf9d497a64fef91429b610effa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Breitbart?= Date: Mon, 25 Oct 2021 00:21:33 +0200 Subject: [PATCH] create minified color names module --- fixtures/x11-colornames/README.md | 14 + fixtures/x11-colornames/create_module.js | 447 ++++++++++++++ fixtures/x11-colornames/rgb.txt | 754 +++++++++++++++++++++++ src/common/InputHandler.ts | 21 +- src/common/data/ColorNames.ts | 91 +++ 5 files changed, 1318 insertions(+), 9 deletions(-) create mode 100644 fixtures/x11-colornames/README.md create mode 100644 fixtures/x11-colornames/create_module.js create mode 100644 fixtures/x11-colornames/rgb.txt create mode 100644 src/common/data/ColorNames.ts diff --git a/fixtures/x11-colornames/README.md b/fixtures/x11-colornames/README.md new file mode 100644 index 000000000..ee6d7b6db --- /dev/null +++ b/fixtures/x11-colornames/README.md @@ -0,0 +1,14 @@ +### Fixture for 11 color names + +`rgb.txt` contains X11's defined color names, copied over from `/etc/X11/rgb.txt` on Ubuntu 18. + +Run `create_module.js` to create a TS module containing the color definitions. The script performs these steps: +- extract color definitions from `rgb.txt` +- remove gray definitions (re-added programmatically later) +- create a perfect hash function +- calculate crc10 for basic collision prevention +- run several collision tests +- compress table and color data +- write data with loading shim to `ColorNames.ts` + +The final file is meant to be copied over to `../../src/common/data/`. diff --git a/fixtures/x11-colornames/create_module.js b/fixtures/x11-colornames/create_module.js new file mode 100644 index 000000000..edc1f3146 --- /dev/null +++ b/fixtures/x11-colornames/create_module.js @@ -0,0 +1,447 @@ +const fs = require('fs'); +const path = require('path'); + +/** + * For decoder side: + * - reconstruct grays with f = lambda x: (x * 256 - x + 50) / 100 (needs value rounding check) + * - apply nameFilter + * - build lookup & extract functions (runtime decoding?) + * - transfer hashtable and color table + */ + +// match color entry in rgb.txt +const rexFile = /^\s*(\d+)\s*(\d+)\s*(\d+)\s*?[\t]+(.*)$/; +// match greyXX|grayXX names +const rexGrey = /^gr[ae]y\d+/; +// name match +const rexName = /^\w?[A-Za-z0-9 ]+\w/; + +function parseX11Colors(filename) { + const fileData = fs.readFileSync(filename, {encoding: 'utf8'}); + const colors = []; + for (const line of fileData.split('\n')) { + const m = rexFile.exec(line); + if (m) { + colors.push({ + color: [parseInt(m[1]), parseInt(m[2]), parseInt(m[3])], + name: m[4] + }); + } + } + return colors; +} + +// Use the FNV algorithm from http://isthe.com/chongo/tech/comp/fnv/ +function hash(d, name) { + if (!d) d = 0x01000193; + for (const c of name) { + d = ( (d * 0x01000193) ^ c.charCodeAt(0) ) & 0xffffffff; + } + return d >>> 0; +} + +// inspired from http://stevehanov.ca/blog/?id=119 +function createMinimalPerfectHash(nameList) { + const nameMap = Object.create(null); + for (const [idx, name] of nameList.entries()) { + nameMap[name] = idx; + } + const size = nameList.length; + + const buckets = []; + for (let i = 0; i < size; ++i) buckets.push([]); + const g = new Array(size).fill(0); + const v = new Array(size).fill(null); + + for (const key of nameList) { + buckets[hash(0, key) % size].push(key); + } + + buckets.sort((a, b) => b.length - a.length); + let breakIdx = 0; + for (let i = 0; i < size; ++i) { + const bucket = buckets[i]; + if (bucket.length <= 1) { + breakIdx = i; + break; + } + let d = 1; + let item = 0; + let slots = []; + + while (item < bucket.length) { + const slot = hash(d, bucket[item]) % size; + if (v[slot] !== null || slots.includes(slot)) { + d++; + item = 0; + slots = []; + } else { + slots.push(slot); + item++; + } + } + + g[hash(0, bucket[0]) % size] = d; + for (let k = 0; k < bucket.length; ++k) { + v[slots[k]] = nameMap[bucket[k]]; + } + } + + const freeList = []; + for (let i = 0; i < size; ++i) { + if (v[i] === null) freeList.push(i); + } + + for (let i = breakIdx; i < size; ++i) { + const bucket = buckets[i]; + if (!bucket.length) break; + const slot = freeList.pop(); + g[hash(0, bucket[0]) % size] = -slot - 1; + v[slot] = nameMap[bucket[0]]; + } + + return [g, v]; +} + +function lookupPerfectHash(g, v, key) { + const d = g[hash(0, key) % g.length]; + if (d < 0) return v[-d - 1]; + return v[hash(d, key) % v.length]; +} + +function checkPerfectHashTables(g, v, nameList) { + let allPassed = true; + for (const [idx, name] of nameList.entries()) { + const lookup = lookupPerfectHash(g, v, name); + if (lookup !== idx) { + console.log(`\x1b[33mmismatch: '${name}' returns ${lookup} (orig: ${idx})\x1b[m`); + allPassed = false; + } + } + return allPassed; +} + +function crc10atm(name, crc) { + if (!crc) crc = 0; + for (const c of name) { + const v = c.charCodeAt(0); + crc ^= v << 2; + for (let k = 0; k < 8; k++) { + crc = crc & 0x200 ? (crc << 1) ^ 0x233 : crc << 1; + } + } + crc &= 0x3ff; + return crc >>> 0; +} + +function checkColorList(g, v, crc, colorNames) { + let match = []; + for (const word of colorNames) { + if (!nameFilter(word)) continue; + const idx = lookupPerfectHash(g, v, word); + const crc10 = crc10atm(word); + if (crc[idx] === crc10) { + match.push({idx, word, colorName: colorNames[idx]}); + } + } + return {tested: colorNames.length, match}; +} + +function checkWordlists(g, v, crc, colorNames, filename) { + const fileData = fs.readFileSync(filename, {encoding: 'utf8'}); + const words = fileData.split('\n').filter(el => lookupPerfectHash.length !== 0); + let collisions = []; + for (const word of words) { + if (!nameFilter(word)) continue; + const idx = lookupPerfectHash(g, v, word); + const crc10 = crc10atm(word); + if (crc[idx] === crc10 && word !== colorNames[idx]) { + collisions.push({idx, word, colorName: colorNames[idx]}); + } + } + return {tested: words.length, collisions}; +} + +function nameFilter(name) { + // length 3 - 22 + if (name.length < 3 || name.length > 22) return; + // chars only in [A-Za-z0-9 ] + if (!rexName.exec(name)) return; + return name; +} + +function compressTables(g, v, crc, al) { + // g | v | crc: all in 10 bit (<1024) + // --> 30 bit + // --> fits into 5 bytes of a 64-bit char alphabet + + // g needs an offset for proper zero alignment + const gOffset = -Math.min(...g); + g = g.map(el => el + gOffset); + + // assert we are in 0..2^10 + if (Math.min(...g) < 0 || Math.min(...v) < 0 || Math.min(...crc) < 0 + || Math.max(...g) > 1023 || Math.max(...v) > 1023 || Math.max(...crc) > 1023 + ) { + console.log('\x1b[31mTables out of compressible range, manual fix needed.\x1b[m'); + process.exit(1); + } + + // assert we have same length + if (g.length !== v.length || g.length !== crc.length) { + console.log('\x1b[31mTables length mismatch, manual fix needed.\x1b[m'); + process.exit(1); + } + + // construct compressed data string + let result = ''; + const length = g.length; + for (let i = 0; i < length; ++i) { + let value = (g[i] << 20) | (v[i] << 10) | crc[i]; + let bucket = ''; + for (let k = 0; k < 5; ++k) { + bucket += al[value % al.length]; + value = Math.floor(value / al.length); + } + result += bucket.split('').reverse().join(''); + } + + return [result, length, gOffset]; +} + +function compressColors(colors, al) { + let result = ''; + for (const color of colors) { + let value = (color[0] << 16) | (color[1] << 8) | color[2]; + let bucket = ''; + for (let k = 0; k < 4; ++k) { + bucket += al[value % al.length]; + value = Math.floor(value / al.length); + } + result += bucket.split('').reverse().join(''); + } + return result; +} + +function loadData(bucket, al) { + let value = 0; + for (const c of bucket) { + value *= al.length; + value += al.indexOf(c); + } + return [value >>> 20, (value >> 10) & 0x3FF, value & 0x3FF]; +} + +function loadColor(al, data, idx) { + // color buckets are hardcoded to 4 chars + let value = 0; + for (let i = idx * 4; i < idx * 4 + 4; ++i) { + value *= al.length; + value += al.indexOf(data[i]); + } + return [value >>> 16, (value >> 8) & 0xFF, value & 0xFF]; +} + +function lookupIdx(al, data, length, gOffset, name) { + const bl = data.length / length; + let offset = (hash(0, name) % length) * bl; + let [g, v, crc] = loadData(data.slice(offset, offset + bl), al); + offset = g < gOffset + ? (-(g - gOffset) - 1) * bl + : (hash(g - gOffset, name) % length) * bl; + [_, v, _] = loadData(data.slice(offset, offset + bl), al); + offset = v * bl; + [_, _, crc] = loadData(data.slice(offset, offset + bl), al); + return crc10atm(name) === crc ? v : -1; +} + +function lookup(al, data, colorData, length, gOffset, name) { + const idx = lookupIdx(al, data, length, gOffset, name); + if (idx === -1) return; + return loadColor(al, colorData, idx); +} + +function createModule(tableData, colorData, alphabet, gOffset, length) { + const TMPL = `/** + * Copyright (c) 2021 The xterm.js authors. All rights reserved. + * @license MIT + */ + +/** + * This module enables X11 color name lookups with the help of a perfect hash function + * to not penalize the package size too much. + * saving: ~70% (from 17kB down to 5kB) + */ + +// Note: module and table data created with fixtures/x11-colornames/create_module.js +const TABLE = '${tableData}'; +const COLORS = '${colorData}'; +const ALPHABET = '${alphabet}'; +const OFFSET = ${gOffset}; +const LENGTH = ${length}; +const BUCKET_LENGTH = TABLE.length / LENGTH; + +// match greyXX|grayXX names +const rexGrey = /^gr[ae]y(\\d+)/; +// name match +const rexName = /^\\w?[A-Za-z0-9 ]+\\w/; + +function gray(n: number): [number, number, number] | undefined { + if (0 <= n && n <= 100) { + const v = Math.floor((n * 256 - n + 50) / 100); + return [v, v, v]; + } + return; +} + +function hash(d: number, name: string): number { + if (!d) d = 0x01000193; + for (const c of name) { + d = ((d * 0x01000193) ^ c.charCodeAt(0)) & 0xffffffff; + } + return d >>> 0; +} + +function crc10(name: string, crc?: number): number { + if (!crc) crc = 0; + for (const c of name) { + crc ^= c.charCodeAt(0) << 2; + for (let k = 0; k < 8; k++) { + crc = crc & 0x200 ? (crc << 1) ^ 0x233 : crc << 1; + } + } + crc &= 0x3ff; + return crc >>> 0; +} + +function loadData(idx: number): [number, number, number] { + let value = 0; + for (let i = idx * BUCKET_LENGTH; i < idx * BUCKET_LENGTH + BUCKET_LENGTH; ++i) { + value *= ALPHABET.length; + value += ALPHABET.indexOf(TABLE[i]); + } + return [value >>> 20, (value >> 10) & 0x3FF, value & 0x3FF]; +} + +function loadColor(idx: number): [number, number, number] { + // color buckets are hardcoded to 4 chars + let v = 0; + for (let i = idx * 4; i < idx * 4 + 4; ++i) { + v *= ALPHABET.length; + v += ALPHABET.indexOf(COLORS[i]); + } + return [v >>> 16, (v >> 8) & 0xFF, v & 0xFF]; +} + +function lookupIdx(name: string): number { + let b = loadData(hash(0, name) % LENGTH); + b = loadData(b[0] < OFFSET ? (-(b[0] - OFFSET) - 1) : hash(b[0] - OFFSET, name) % LENGTH); + const [ , , crc] = loadData(b[1]); + return crc10(name) === crc ? b[1] : -1; +} + +export function getColorFromName(name: string): [number, number, number] | undefined { + // basic name filtering + if (name.length < 3 || name.length > 22 || !rexName.exec(name)) return; + + // handle grays special + const m = rexGrey.exec(name); + if (m) return gray(parseInt(m[1])); + + // grab crc checked idx from PHF + const idx = lookupIdx(name); + if (idx === -1) return; + return loadColor(idx); +} +`; + return TMPL; +} + + +function main() { + // parse definitions from X11 file + const colorList = parseX11Colors(path.join(__dirname, '/rgb.txt')) + .filter(el => !rexGrey.exec(el.name)); // remove greys, as we can reconstruct them later + const nameList = colorList.map(el => el.name); + + // PHF creation and test + const [g, v] = createMinimalPerfectHash(nameList); + if (!checkPerfectHashTables(g, v, nameList)) { + console.error('\x1b[31mPHF creation failed.\x1b[m'); + process.exit(1); + } else { + console.log('\x1b[32mPHF creation successful.\x1b[m'); + } + + // calc 10-bit CRCs to rule to avoid collisions at ~1:1024 + const crc = nameList.map(el => crc10atm(el)); + + // collision check against colorNames - must all collide + console.log('Self collision test:') + const collSelf = checkColorList(g, v, crc, nameList); + console.log('self:', { + entries: collSelf.tested, + matches: collSelf.match.length, + rate: collSelf.tested / collSelf.match.length + }); + if (collSelf.match.length !== collSelf.tested) { + console.error('\x1b[31mSelf collision tested failed.\x1b[m'); + process.exit(1); + } + + // basic collision checks against dictionaries (rates should be greater than 900:1) + console.log('Dictionary collision test:') + const collEnglish = checkWordlists(g, v, crc, nameList, '/usr/share/dict/words'); + console.log('english:', { + entries: collEnglish.tested, + collisions: collEnglish.collisions.length, + rate: collEnglish.tested / collEnglish.collisions.length + }); + //console.log(collEnglish.collisions); + const collnGerman = checkWordlists(g, v, crc, nameList, '/usr/share/dict/ngerman'); + console.log('ngerman:', { + entries: collnGerman.tested, + collisions: collnGerman.collisions.length, + rate: collnGerman.tested / collnGerman.collisions.length + }); + const colloGerman = checkWordlists(g, v, crc, nameList, '/usr/share/dict/ogerman'); + console.log('ogerman:', { + entries: colloGerman.tested, + collisions: colloGerman.collisions.length, + rate: colloGerman.tested / colloGerman.collisions.length + }); + + // compression with custom alphabet + const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!?'; + const [tableData, length, gOffset] = compressTables(g, v, crc, ALPHABET); + const colorData = compressColors(colorList.map(el => el.color), ALPHABET); + + // test loading of compressed table data + let failed = false; + for (const name of nameList) { + const orig = lookupPerfectHash(g, v, name); + const idx = lookupIdx(ALPHABET, tableData, length, gOffset, name); + if (idx !== orig) { + console.log('\x1b[31mcompressed loading failed for:\x1b[m', {name, idx, orig}); + failed = true; + } + } + if (failed) process.exit(1); + + // test full color decoding + for (const color of colorList) { + const loaded = lookup(ALPHABET, tableData, colorData, length, gOffset, color.name); + if (loaded[0] !== color.color[0] || loaded[1] !== color.color[1] || loaded[2] !== color.color[2]) { + console.log('\x1b[31mcolor loading failed for:\x1b[m', {color, loaded}); + failed = true; + } + } + if (failed) process.exit(1); + + // if we made it up to here, create decoding boilerplate + const moduleData = createModule(tableData, colorData, ALPHABET, gOffset, length); + fs.writeFileSync(path.join(__dirname, '/ColorNames.ts'), moduleData); + console.log(`${moduleData.length} bytes written to ${path.join(__dirname, '/ColorNames.ts')}`); +} + +main(); diff --git a/fixtures/x11-colornames/rgb.txt b/fixtures/x11-colornames/rgb.txt new file mode 100644 index 000000000..b9e56c602 --- /dev/null +++ b/fixtures/x11-colornames/rgb.txt @@ -0,0 +1,754 @@ +! $Xorg: rgb.txt,v 1.3 2000/08/17 19:54:00 cpqbld Exp $ +255 250 250 snow +248 248 255 ghost white +248 248 255 GhostWhite +245 245 245 white smoke +245 245 245 WhiteSmoke +220 220 220 gainsboro +255 250 240 floral white +255 250 240 FloralWhite +253 245 230 old lace +253 245 230 OldLace +250 240 230 linen +250 235 215 antique white +250 235 215 AntiqueWhite +255 239 213 papaya whip +255 239 213 PapayaWhip +255 235 205 blanched almond +255 235 205 BlanchedAlmond +255 228 196 bisque +255 218 185 peach puff +255 218 185 PeachPuff +255 222 173 navajo white +255 222 173 NavajoWhite +255 228 181 moccasin +255 248 220 cornsilk +255 255 240 ivory +255 250 205 lemon chiffon +255 250 205 LemonChiffon +255 245 238 seashell +240 255 240 honeydew +245 255 250 mint cream +245 255 250 MintCream +240 255 255 azure +240 248 255 alice blue +240 248 255 AliceBlue +230 230 250 lavender +255 240 245 lavender blush +255 240 245 LavenderBlush +255 228 225 misty rose +255 228 225 MistyRose +255 255 255 white + 0 0 0 black + 47 79 79 dark slate gray + 47 79 79 DarkSlateGray + 47 79 79 dark slate grey + 47 79 79 DarkSlateGrey +105 105 105 dim gray +105 105 105 DimGray +105 105 105 dim grey +105 105 105 DimGrey +112 128 144 slate gray +112 128 144 SlateGray +112 128 144 slate grey +112 128 144 SlateGrey +119 136 153 light slate gray +119 136 153 LightSlateGray +119 136 153 light slate grey +119 136 153 LightSlateGrey +190 190 190 gray +190 190 190 grey +211 211 211 light grey +211 211 211 LightGrey +211 211 211 light gray +211 211 211 LightGray + 25 25 112 midnight blue + 25 25 112 MidnightBlue + 0 0 128 navy + 0 0 128 navy blue + 0 0 128 NavyBlue +100 149 237 cornflower blue +100 149 237 CornflowerBlue + 72 61 139 dark slate blue + 72 61 139 DarkSlateBlue +106 90 205 slate blue +106 90 205 SlateBlue +123 104 238 medium slate blue +123 104 238 MediumSlateBlue +132 112 255 light slate blue +132 112 255 LightSlateBlue + 0 0 205 medium blue + 0 0 205 MediumBlue + 65 105 225 royal blue + 65 105 225 RoyalBlue + 0 0 255 blue + 30 144 255 dodger blue + 30 144 255 DodgerBlue + 0 191 255 deep sky blue + 0 191 255 DeepSkyBlue +135 206 235 sky blue +135 206 235 SkyBlue +135 206 250 light sky blue +135 206 250 LightSkyBlue + 70 130 180 steel blue + 70 130 180 SteelBlue +176 196 222 light steel blue +176 196 222 LightSteelBlue +173 216 230 light blue +173 216 230 LightBlue +176 224 230 powder blue +176 224 230 PowderBlue +175 238 238 pale turquoise +175 238 238 PaleTurquoise + 0 206 209 dark turquoise + 0 206 209 DarkTurquoise + 72 209 204 medium turquoise + 72 209 204 MediumTurquoise + 64 224 208 turquoise + 0 255 255 cyan +224 255 255 light cyan +224 255 255 LightCyan + 95 158 160 cadet blue + 95 158 160 CadetBlue +102 205 170 medium aquamarine +102 205 170 MediumAquamarine +127 255 212 aquamarine + 0 100 0 dark green + 0 100 0 DarkGreen + 85 107 47 dark olive green + 85 107 47 DarkOliveGreen +143 188 143 dark sea green +143 188 143 DarkSeaGreen + 46 139 87 sea green + 46 139 87 SeaGreen + 60 179 113 medium sea green + 60 179 113 MediumSeaGreen + 32 178 170 light sea green + 32 178 170 LightSeaGreen +152 251 152 pale green +152 251 152 PaleGreen + 0 255 127 spring green + 0 255 127 SpringGreen +124 252 0 lawn green +124 252 0 LawnGreen + 0 255 0 green +127 255 0 chartreuse + 0 250 154 medium spring green + 0 250 154 MediumSpringGreen +173 255 47 green yellow +173 255 47 GreenYellow + 50 205 50 lime green + 50 205 50 LimeGreen +154 205 50 yellow green +154 205 50 YellowGreen + 34 139 34 forest green + 34 139 34 ForestGreen +107 142 35 olive drab +107 142 35 OliveDrab +189 183 107 dark khaki +189 183 107 DarkKhaki +240 230 140 khaki +238 232 170 pale goldenrod +238 232 170 PaleGoldenrod +250 250 210 light goldenrod yellow +250 250 210 LightGoldenrodYellow +255 255 224 light yellow +255 255 224 LightYellow +255 255 0 yellow +255 215 0 gold +238 221 130 light goldenrod +238 221 130 LightGoldenrod +218 165 32 goldenrod +184 134 11 dark goldenrod +184 134 11 DarkGoldenrod +188 143 143 rosy brown +188 143 143 RosyBrown +205 92 92 indian red +205 92 92 IndianRed +139 69 19 saddle brown +139 69 19 SaddleBrown +160 82 45 sienna +205 133 63 peru +222 184 135 burlywood +245 245 220 beige +245 222 179 wheat +244 164 96 sandy brown +244 164 96 SandyBrown +210 180 140 tan +210 105 30 chocolate +178 34 34 firebrick +165 42 42 brown +233 150 122 dark salmon +233 150 122 DarkSalmon +250 128 114 salmon +255 160 122 light salmon +255 160 122 LightSalmon +255 165 0 orange +255 140 0 dark orange +255 140 0 DarkOrange +255 127 80 coral +240 128 128 light coral +240 128 128 LightCoral +255 99 71 tomato +255 69 0 orange red +255 69 0 OrangeRed +255 0 0 red +255 105 180 hot pink +255 105 180 HotPink +255 20 147 deep pink +255 20 147 DeepPink +255 192 203 pink +255 182 193 light pink +255 182 193 LightPink +219 112 147 pale violet red +219 112 147 PaleVioletRed +176 48 96 maroon +199 21 133 medium violet red +199 21 133 MediumVioletRed +208 32 144 violet red +208 32 144 VioletRed +255 0 255 magenta +238 130 238 violet +221 160 221 plum +218 112 214 orchid +186 85 211 medium orchid +186 85 211 MediumOrchid +153 50 204 dark orchid +153 50 204 DarkOrchid +148 0 211 dark violet +148 0 211 DarkViolet +138 43 226 blue violet +138 43 226 BlueViolet +160 32 240 purple +147 112 219 medium purple +147 112 219 MediumPurple +216 191 216 thistle +255 250 250 snow1 +238 233 233 snow2 +205 201 201 snow3 +139 137 137 snow4 +255 245 238 seashell1 +238 229 222 seashell2 +205 197 191 seashell3 +139 134 130 seashell4 +255 239 219 AntiqueWhite1 +238 223 204 AntiqueWhite2 +205 192 176 AntiqueWhite3 +139 131 120 AntiqueWhite4 +255 228 196 bisque1 +238 213 183 bisque2 +205 183 158 bisque3 +139 125 107 bisque4 +255 218 185 PeachPuff1 +238 203 173 PeachPuff2 +205 175 149 PeachPuff3 +139 119 101 PeachPuff4 +255 222 173 NavajoWhite1 +238 207 161 NavajoWhite2 +205 179 139 NavajoWhite3 +139 121 94 NavajoWhite4 +255 250 205 LemonChiffon1 +238 233 191 LemonChiffon2 +205 201 165 LemonChiffon3 +139 137 112 LemonChiffon4 +255 248 220 cornsilk1 +238 232 205 cornsilk2 +205 200 177 cornsilk3 +139 136 120 cornsilk4 +255 255 240 ivory1 +238 238 224 ivory2 +205 205 193 ivory3 +139 139 131 ivory4 +240 255 240 honeydew1 +224 238 224 honeydew2 +193 205 193 honeydew3 +131 139 131 honeydew4 +255 240 245 LavenderBlush1 +238 224 229 LavenderBlush2 +205 193 197 LavenderBlush3 +139 131 134 LavenderBlush4 +255 228 225 MistyRose1 +238 213 210 MistyRose2 +205 183 181 MistyRose3 +139 125 123 MistyRose4 +240 255 255 azure1 +224 238 238 azure2 +193 205 205 azure3 +131 139 139 azure4 +131 111 255 SlateBlue1 +122 103 238 SlateBlue2 +105 89 205 SlateBlue3 + 71 60 139 SlateBlue4 + 72 118 255 RoyalBlue1 + 67 110 238 RoyalBlue2 + 58 95 205 RoyalBlue3 + 39 64 139 RoyalBlue4 + 0 0 255 blue1 + 0 0 238 blue2 + 0 0 205 blue3 + 0 0 139 blue4 + 30 144 255 DodgerBlue1 + 28 134 238 DodgerBlue2 + 24 116 205 DodgerBlue3 + 16 78 139 DodgerBlue4 + 99 184 255 SteelBlue1 + 92 172 238 SteelBlue2 + 79 148 205 SteelBlue3 + 54 100 139 SteelBlue4 + 0 191 255 DeepSkyBlue1 + 0 178 238 DeepSkyBlue2 + 0 154 205 DeepSkyBlue3 + 0 104 139 DeepSkyBlue4 +135 206 255 SkyBlue1 +126 192 238 SkyBlue2 +108 166 205 SkyBlue3 + 74 112 139 SkyBlue4 +176 226 255 LightSkyBlue1 +164 211 238 LightSkyBlue2 +141 182 205 LightSkyBlue3 + 96 123 139 LightSkyBlue4 +198 226 255 SlateGray1 +185 211 238 SlateGray2 +159 182 205 SlateGray3 +108 123 139 SlateGray4 +202 225 255 LightSteelBlue1 +188 210 238 LightSteelBlue2 +162 181 205 LightSteelBlue3 +110 123 139 LightSteelBlue4 +191 239 255 LightBlue1 +178 223 238 LightBlue2 +154 192 205 LightBlue3 +104 131 139 LightBlue4 +224 255 255 LightCyan1 +209 238 238 LightCyan2 +180 205 205 LightCyan3 +122 139 139 LightCyan4 +187 255 255 PaleTurquoise1 +174 238 238 PaleTurquoise2 +150 205 205 PaleTurquoise3 +102 139 139 PaleTurquoise4 +152 245 255 CadetBlue1 +142 229 238 CadetBlue2 +122 197 205 CadetBlue3 + 83 134 139 CadetBlue4 + 0 245 255 turquoise1 + 0 229 238 turquoise2 + 0 197 205 turquoise3 + 0 134 139 turquoise4 + 0 255 255 cyan1 + 0 238 238 cyan2 + 0 205 205 cyan3 + 0 139 139 cyan4 +151 255 255 DarkSlateGray1 +141 238 238 DarkSlateGray2 +121 205 205 DarkSlateGray3 + 82 139 139 DarkSlateGray4 +127 255 212 aquamarine1 +118 238 198 aquamarine2 +102 205 170 aquamarine3 + 69 139 116 aquamarine4 +193 255 193 DarkSeaGreen1 +180 238 180 DarkSeaGreen2 +155 205 155 DarkSeaGreen3 +105 139 105 DarkSeaGreen4 + 84 255 159 SeaGreen1 + 78 238 148 SeaGreen2 + 67 205 128 SeaGreen3 + 46 139 87 SeaGreen4 +154 255 154 PaleGreen1 +144 238 144 PaleGreen2 +124 205 124 PaleGreen3 + 84 139 84 PaleGreen4 + 0 255 127 SpringGreen1 + 0 238 118 SpringGreen2 + 0 205 102 SpringGreen3 + 0 139 69 SpringGreen4 + 0 255 0 green1 + 0 238 0 green2 + 0 205 0 green3 + 0 139 0 green4 +127 255 0 chartreuse1 +118 238 0 chartreuse2 +102 205 0 chartreuse3 + 69 139 0 chartreuse4 +192 255 62 OliveDrab1 +179 238 58 OliveDrab2 +154 205 50 OliveDrab3 +105 139 34 OliveDrab4 +202 255 112 DarkOliveGreen1 +188 238 104 DarkOliveGreen2 +162 205 90 DarkOliveGreen3 +110 139 61 DarkOliveGreen4 +255 246 143 khaki1 +238 230 133 khaki2 +205 198 115 khaki3 +139 134 78 khaki4 +255 236 139 LightGoldenrod1 +238 220 130 LightGoldenrod2 +205 190 112 LightGoldenrod3 +139 129 76 LightGoldenrod4 +255 255 224 LightYellow1 +238 238 209 LightYellow2 +205 205 180 LightYellow3 +139 139 122 LightYellow4 +255 255 0 yellow1 +238 238 0 yellow2 +205 205 0 yellow3 +139 139 0 yellow4 +255 215 0 gold1 +238 201 0 gold2 +205 173 0 gold3 +139 117 0 gold4 +255 193 37 goldenrod1 +238 180 34 goldenrod2 +205 155 29 goldenrod3 +139 105 20 goldenrod4 +255 185 15 DarkGoldenrod1 +238 173 14 DarkGoldenrod2 +205 149 12 DarkGoldenrod3 +139 101 8 DarkGoldenrod4 +255 193 193 RosyBrown1 +238 180 180 RosyBrown2 +205 155 155 RosyBrown3 +139 105 105 RosyBrown4 +255 106 106 IndianRed1 +238 99 99 IndianRed2 +205 85 85 IndianRed3 +139 58 58 IndianRed4 +255 130 71 sienna1 +238 121 66 sienna2 +205 104 57 sienna3 +139 71 38 sienna4 +255 211 155 burlywood1 +238 197 145 burlywood2 +205 170 125 burlywood3 +139 115 85 burlywood4 +255 231 186 wheat1 +238 216 174 wheat2 +205 186 150 wheat3 +139 126 102 wheat4 +255 165 79 tan1 +238 154 73 tan2 +205 133 63 tan3 +139 90 43 tan4 +255 127 36 chocolate1 +238 118 33 chocolate2 +205 102 29 chocolate3 +139 69 19 chocolate4 +255 48 48 firebrick1 +238 44 44 firebrick2 +205 38 38 firebrick3 +139 26 26 firebrick4 +255 64 64 brown1 +238 59 59 brown2 +205 51 51 brown3 +139 35 35 brown4 +255 140 105 salmon1 +238 130 98 salmon2 +205 112 84 salmon3 +139 76 57 salmon4 +255 160 122 LightSalmon1 +238 149 114 LightSalmon2 +205 129 98 LightSalmon3 +139 87 66 LightSalmon4 +255 165 0 orange1 +238 154 0 orange2 +205 133 0 orange3 +139 90 0 orange4 +255 127 0 DarkOrange1 +238 118 0 DarkOrange2 +205 102 0 DarkOrange3 +139 69 0 DarkOrange4 +255 114 86 coral1 +238 106 80 coral2 +205 91 69 coral3 +139 62 47 coral4 +255 99 71 tomato1 +238 92 66 tomato2 +205 79 57 tomato3 +139 54 38 tomato4 +255 69 0 OrangeRed1 +238 64 0 OrangeRed2 +205 55 0 OrangeRed3 +139 37 0 OrangeRed4 +255 0 0 red1 +238 0 0 red2 +205 0 0 red3 +139 0 0 red4 +215 7 81 DebianRed +255 20 147 DeepPink1 +238 18 137 DeepPink2 +205 16 118 DeepPink3 +139 10 80 DeepPink4 +255 110 180 HotPink1 +238 106 167 HotPink2 +205 96 144 HotPink3 +139 58 98 HotPink4 +255 181 197 pink1 +238 169 184 pink2 +205 145 158 pink3 +139 99 108 pink4 +255 174 185 LightPink1 +238 162 173 LightPink2 +205 140 149 LightPink3 +139 95 101 LightPink4 +255 130 171 PaleVioletRed1 +238 121 159 PaleVioletRed2 +205 104 137 PaleVioletRed3 +139 71 93 PaleVioletRed4 +255 52 179 maroon1 +238 48 167 maroon2 +205 41 144 maroon3 +139 28 98 maroon4 +255 62 150 VioletRed1 +238 58 140 VioletRed2 +205 50 120 VioletRed3 +139 34 82 VioletRed4 +255 0 255 magenta1 +238 0 238 magenta2 +205 0 205 magenta3 +139 0 139 magenta4 +255 131 250 orchid1 +238 122 233 orchid2 +205 105 201 orchid3 +139 71 137 orchid4 +255 187 255 plum1 +238 174 238 plum2 +205 150 205 plum3 +139 102 139 plum4 +224 102 255 MediumOrchid1 +209 95 238 MediumOrchid2 +180 82 205 MediumOrchid3 +122 55 139 MediumOrchid4 +191 62 255 DarkOrchid1 +178 58 238 DarkOrchid2 +154 50 205 DarkOrchid3 +104 34 139 DarkOrchid4 +155 48 255 purple1 +145 44 238 purple2 +125 38 205 purple3 + 85 26 139 purple4 +171 130 255 MediumPurple1 +159 121 238 MediumPurple2 +137 104 205 MediumPurple3 + 93 71 139 MediumPurple4 +255 225 255 thistle1 +238 210 238 thistle2 +205 181 205 thistle3 +139 123 139 thistle4 + 0 0 0 gray0 + 0 0 0 grey0 + 3 3 3 gray1 + 3 3 3 grey1 + 5 5 5 gray2 + 5 5 5 grey2 + 8 8 8 gray3 + 8 8 8 grey3 + 10 10 10 gray4 + 10 10 10 grey4 + 13 13 13 gray5 + 13 13 13 grey5 + 15 15 15 gray6 + 15 15 15 grey6 + 18 18 18 gray7 + 18 18 18 grey7 + 20 20 20 gray8 + 20 20 20 grey8 + 23 23 23 gray9 + 23 23 23 grey9 + 26 26 26 gray10 + 26 26 26 grey10 + 28 28 28 gray11 + 28 28 28 grey11 + 31 31 31 gray12 + 31 31 31 grey12 + 33 33 33 gray13 + 33 33 33 grey13 + 36 36 36 gray14 + 36 36 36 grey14 + 38 38 38 gray15 + 38 38 38 grey15 + 41 41 41 gray16 + 41 41 41 grey16 + 43 43 43 gray17 + 43 43 43 grey17 + 46 46 46 gray18 + 46 46 46 grey18 + 48 48 48 gray19 + 48 48 48 grey19 + 51 51 51 gray20 + 51 51 51 grey20 + 54 54 54 gray21 + 54 54 54 grey21 + 56 56 56 gray22 + 56 56 56 grey22 + 59 59 59 gray23 + 59 59 59 grey23 + 61 61 61 gray24 + 61 61 61 grey24 + 64 64 64 gray25 + 64 64 64 grey25 + 66 66 66 gray26 + 66 66 66 grey26 + 69 69 69 gray27 + 69 69 69 grey27 + 71 71 71 gray28 + 71 71 71 grey28 + 74 74 74 gray29 + 74 74 74 grey29 + 77 77 77 gray30 + 77 77 77 grey30 + 79 79 79 gray31 + 79 79 79 grey31 + 82 82 82 gray32 + 82 82 82 grey32 + 84 84 84 gray33 + 84 84 84 grey33 + 87 87 87 gray34 + 87 87 87 grey34 + 89 89 89 gray35 + 89 89 89 grey35 + 92 92 92 gray36 + 92 92 92 grey36 + 94 94 94 gray37 + 94 94 94 grey37 + 97 97 97 gray38 + 97 97 97 grey38 + 99 99 99 gray39 + 99 99 99 grey39 +102 102 102 gray40 +102 102 102 grey40 +105 105 105 gray41 +105 105 105 grey41 +107 107 107 gray42 +107 107 107 grey42 +110 110 110 gray43 +110 110 110 grey43 +112 112 112 gray44 +112 112 112 grey44 +115 115 115 gray45 +115 115 115 grey45 +117 117 117 gray46 +117 117 117 grey46 +120 120 120 gray47 +120 120 120 grey47 +122 122 122 gray48 +122 122 122 grey48 +125 125 125 gray49 +125 125 125 grey49 +127 127 127 gray50 +127 127 127 grey50 +130 130 130 gray51 +130 130 130 grey51 +133 133 133 gray52 +133 133 133 grey52 +135 135 135 gray53 +135 135 135 grey53 +138 138 138 gray54 +138 138 138 grey54 +140 140 140 gray55 +140 140 140 grey55 +143 143 143 gray56 +143 143 143 grey56 +145 145 145 gray57 +145 145 145 grey57 +148 148 148 gray58 +148 148 148 grey58 +150 150 150 gray59 +150 150 150 grey59 +153 153 153 gray60 +153 153 153 grey60 +156 156 156 gray61 +156 156 156 grey61 +158 158 158 gray62 +158 158 158 grey62 +161 161 161 gray63 +161 161 161 grey63 +163 163 163 gray64 +163 163 163 grey64 +166 166 166 gray65 +166 166 166 grey65 +168 168 168 gray66 +168 168 168 grey66 +171 171 171 gray67 +171 171 171 grey67 +173 173 173 gray68 +173 173 173 grey68 +176 176 176 gray69 +176 176 176 grey69 +179 179 179 gray70 +179 179 179 grey70 +181 181 181 gray71 +181 181 181 grey71 +184 184 184 gray72 +184 184 184 grey72 +186 186 186 gray73 +186 186 186 grey73 +189 189 189 gray74 +189 189 189 grey74 +191 191 191 gray75 +191 191 191 grey75 +194 194 194 gray76 +194 194 194 grey76 +196 196 196 gray77 +196 196 196 grey77 +199 199 199 gray78 +199 199 199 grey78 +201 201 201 gray79 +201 201 201 grey79 +204 204 204 gray80 +204 204 204 grey80 +207 207 207 gray81 +207 207 207 grey81 +209 209 209 gray82 +209 209 209 grey82 +212 212 212 gray83 +212 212 212 grey83 +214 214 214 gray84 +214 214 214 grey84 +217 217 217 gray85 +217 217 217 grey85 +219 219 219 gray86 +219 219 219 grey86 +222 222 222 gray87 +222 222 222 grey87 +224 224 224 gray88 +224 224 224 grey88 +227 227 227 gray89 +227 227 227 grey89 +229 229 229 gray90 +229 229 229 grey90 +232 232 232 gray91 +232 232 232 grey91 +235 235 235 gray92 +235 235 235 grey92 +237 237 237 gray93 +237 237 237 grey93 +240 240 240 gray94 +240 240 240 grey94 +242 242 242 gray95 +242 242 242 grey95 +245 245 245 gray96 +245 245 245 grey96 +247 247 247 gray97 +247 247 247 grey97 +250 250 250 gray98 +250 250 250 grey98 +252 252 252 gray99 +252 252 252 grey99 +255 255 255 gray100 +255 255 255 grey100 +169 169 169 dark grey +169 169 169 DarkGrey +169 169 169 dark gray +169 169 169 DarkGray +0 0 139 dark blue +0 0 139 DarkBlue +0 139 139 dark cyan +0 139 139 DarkCyan +139 0 139 dark magenta +139 0 139 DarkMagenta +139 0 0 dark red +139 0 0 DarkRed +144 238 144 light green +144 238 144 LightGreen diff --git a/src/common/InputHandler.ts b/src/common/InputHandler.ts index f206ac8b4..9a56de6f6 100644 --- a/src/common/InputHandler.ts +++ b/src/common/InputHandler.ts @@ -21,6 +21,7 @@ import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowSe import { OscHandler } from 'common/parser/OscParser'; import { DcsHandler } from 'common/parser/DcsParser'; import { IBuffer } from 'common/buffer/Types'; +import { getColorFromName } from 'common/data/ColorNames'; /** * Map collect to glevel. Used in `selectCharset`. @@ -2877,13 +2878,14 @@ export class InputHandler extends Disposable implements IInputHandler { * with float numbering are not supported. */ protected _parseColorSpec(data: string): [number, number, number] | void { + if (!data) return; // also handle uppercases - data = data.toLowerCase(); - if (data.indexOf('rgb:') === 0) { + let low = data.toLowerCase(); + if (low.indexOf('rgb:') === 0) { // 'rgb:' specifier - data = data.slice(4); + low = low.slice(4); const rex = /^([\da-f]{1})\/([\da-f]{1})\/([\da-f]{1})$|^([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})$|^([\da-f]{3})\/([\da-f]{3})\/([\da-f]{3})$|^([\da-f]{4})\/([\da-f]{4})\/([\da-f]{4})$/; - const m = rex.exec(data); + const m = rex.exec(low); if (m) { const base = m[1] ? 15 : m[4] ? 255 : m[7] ? 4095 : 65535; return [ @@ -2892,20 +2894,21 @@ export class InputHandler extends Disposable implements IInputHandler { Math.round(parseInt(m[3] || m[6] || m[9] || m[12], 16) / base * 255) ]; } - } else if (data.indexOf('#') === 0) { + } else if (low.indexOf('#') === 0) { // '#' specifier - data = data.slice(1); + low = low.slice(1); const rex = /^[\da-f]+$/; - if (rex.exec(data) && [3, 6, 9, 12].includes(data.length)) { - const adv = data.length / 3; + if (rex.exec(low) && [3, 6, 9, 12].includes(low.length)) { + const adv = low.length / 3; const result: [number, number, number] = [0, 0, 0]; for (let i = 0; i < 3; ++i) { - const c = parseInt(data.slice(adv * i, adv * i + adv), 16); + const c = parseInt(low.slice(adv * i, adv * i + adv), 16); result[i] = adv === 1 ? c << 4 : adv === 2 ? c : adv === 3 ? c >> 4 : c >> 8; } return result; } } + return getColorFromName(data); } /** diff --git a/src/common/data/ColorNames.ts b/src/common/data/ColorNames.ts new file mode 100644 index 000000000..68dd06b61 --- /dev/null +++ b/src/common/data/ColorNames.ts @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2021 The xterm.js authors. All rights reserved. + * @license MIT + */ + +/** + * This module enables X11 color name lookups with the help of a perfect hash function + * to not penalize the package size too much. + * saving: ~70% (from 17kB down to 5kB) + */ + +// Note: module and table data created with fixtures/x11-colornames/create_module.js +const TABLE = 'AB!!!id!jsih!ZYigHp3AkceNiiImVihW3Uih?DFihrenihDEQiwgziieGJvAoMnsidOM3idOtQiciazAsFrtieFZyidKSgAx8odip818ieFiXieF97ic34eihOxpA4UPNidz51A8znjBAX?uBU9ynitOZdigRMNBYgIEBcaXPBk9LVB0xlOB9mcFihmlWCRq0yidmG7idqsHid7RmCV7pCClqRAimG5kCpsb4ipt78CtqLeig17aigJb7ioC4rihzLdigmoNigEDTihzZKCwIz1C4j5sih4QQihlg2igwsHDBFTuDESshDJFzIig1cbihZ3wipZphigiKcihZUxihZLODRm7RilG1Cid3tmDVGffDYJsPDc1JrixgxGidDn3Dli9uDsOl0imDQPiyDhbimDyZihU00icbZSEJs6VieEzpicF2YEqB?PicemNiduxiihEL9ieBjnEuI53icdLPiqBYWiduYFExunsFB3JqFEXPuihnyvFQkNAFVVjlic8SLicIeOFYvurFwrh5icQ4RicHUgidVbJidVA3idN3LGECqgGJiKSGRl8UidT9qipssVGZHNyidy3widTu8idgHsidgcgGdggRGkODXihH!8ilyhpihWj0ihWcailWHjGwO?OilD4BG888ZHJzmGHZtoyicqlkiwWfkicEbdHg3uEHtMkRH0TgkH5MBRIQE9VIUh5VIYeSyieFFgIlC6Qidvc5idv65icprGicNfhItjoJI4N3mJRFpIicLVGJgMY7J2GebKAPoGKyITuidFGWic2olicYLfLFuJDidnoticCc!icx2bieAcuLIv3UicoYuic594LRQ86LZc3NidcphicnOVic09yisjDiitRyFLc7NTicTaFicFbvic6jPLk3JtigqSiL1QJUjJQZpidQvJicUVVidYVsidNOjidNQfidNnLicyCAicHGeidY17icL3cL8DSKMcPzVMgc8dicEg6ig61OMwImmM06KXM6Hl!M96LNicQEbicUjBNAt2Aid4Hwix3w0Ndba5Nt5pqNwuVljBbJaicYjeigAxGN1b!LOEvTnisIIoORf1cOVV!TiczJbiddd5iddgpiclyxidfd8idfuEieACwid?zWih2rlOp2waid?r2OtUKQOxMyjihUpcigKWQO4y32PNAPFidAU6PZAhBPhjAni0PYUiceDridjwXPqEBxPsZ5Cicoi9icBWoieHXOP0B?9QF50CigWlyQJBkUQMs8nici3YQQ?MXidedxicjVCidhu9icqIFidhZjidhBQid0avicmIYQZLp!QdLBNQwFKyipSwfikPB5Q1SZKipSC1idL08Q44Vaic4tpikVWWQ9x5KikK4sig4Afit?bgihvhKRA?wsidxIfidGjgikw?ORRruoRYQWbRh2dkRk!EPRo!Vpig!taR92Glicf4gicxZGSCIB1jED6KSF0tyid0MUieEUnidLfYSRRoKSUk5sSczRfih06gSkpOgikVvGio5X1icp8Kio5NFik0sjix9EQSp8Pvide28icanaidyPpidekWSx71CTANkkicssXTE1nojEbGQTJp32ic?fFTcYU6icuDuicNCIT1B67T4DhEice8dihBd7ismVIUIRe3UVJeiUZBPEVNXn3idXcIj0jici5yT6VgGbJVlXA2idcRxicfAXicT4kicRjbVpnXZieEm?V4feMigLlzV95OGip4?git4nTWF3csicyWQWJGH2ikH?FWMo06ieCn5WSC2fieDOsWUm1Tidor2WZoaQidoCjicz5cWlxroicTKOW4dS9ido5CidnBwic0AWXJX4lXNdDaicS!nXkWPBXotnyYAgsNicrXhicnmHidHe0idHlLicaNvicG8Jida06YMOfFidTV!icY0YidDcrYRiXUYZivaigDO8ig8GPigGNwi88iCikuzkiharXicoAoihJPbihae9ihJmOihJ9xigV9UiwsMyidaEBichf!ic9ZEYk9oiigCARY9kLuidkWPidknpidk?aihh0likBkKZEJFsidjdfieJmgZIU5dieH47isgQIZNxb3ZQn7hidI6HZVIi0il9ZLix9nYih92!ihCeNZZClyZhISFi1IDjid6bQicwQvZl66Eid6riZoGjRicpYuieBC5ic5jfZwlrsicKATimA?cZ8ZO6icxGJieAt2iculVaA40zidYsAacin?aglNzalwMVi9d0mictfZascgqicbxMbABJ?ignSAjYM8?bIhNZigd1qig7eVieJNPbQq8pikKkaigVPligW!fjgR?5bWCHKidCM1bcrI5idppfidpRsi5P6TbkXgIbo2xucEAZdihvCiidPJXidPYxicAACcIXb9isr67cMtDzcgfiVctsKmc8khZigZjTilrC1ihpKGjQhh5dJr30igymSjKJehihMVeig3VrigwENipRc!idKnBdRRFodhKEOdxEc9idEnCidE8zic!9VidK1md18eZit!FHieGkhicJ8SieCXticlSNidbjridA7YeIdgneNtbMeVlKqeZlSZicbpmixtEVesAlzfU2dAfdwW?fsSYbf5w59gB1xOicMKxgN1q8icZbagZPjpidSoWid1Y0ic2JSid1Bhgo7qeidcLjilYKFiw6S2isa5Jgxwl9g00UbicSMohA73XhELGKhUQqNi8cGshgsaridfCIhkkePhtUeth0vCqileCnit5YaiE?plieHKPiJTPmiN7Kt'; +const COLORS = '??r6!Pj?!Pj?9fX19fX13Nzc??rw??rw?fXm?fXm!vDm!uvX!uvX?!?V?!?V?!vN?!vN?!TE?9q5?9q5?96t?96t?!S1??jc???w??rN??rN??Xu8P?w9f?69f?68P??8Pj?8Pj?5ub6??D1??D1?!Th?!Th????AAAAL09PL09PL09PL09PaWlpaWlpaWlpaWlpcICQcICQcICQcICQd4iZd4iZd4iZd4iZvr6!vr6!09PT09PT09PT09PTGRlwGRlwAACAAACAAACAZJXtZJXtSD2LSD2LalrNalrNe2jue2juhHD?hHD?AADNAADNQWnhQWnhAAD?HpD?HpD?AL??AL??h87rh87rh876h876RoK0RoK0sMTesMTerdjmrdjmsODmsODmr!7ur!7uAM7RAM7RSNHMSNHMQODQAP??4P??4P??X56gX56gZs2qZs2qf??UAGQAAGQAVWsvVWsvj7yPj7yPLotXLotXPLNxPLNxILKqILKqmPuYmPuYAP9?AP9?fPwAfPwAAP8Af?8AAPqaAPqarf8vrf8vMs0yMs0yms0yms0yIosiIosia44ja44jvbdrvbdr8OaM7uiq7uiq!vrS!vrS???g???g??8A?9cA7t2C7t2C2qUguIYLuIYLvI!PvI!PzVxczVxci0UTi0UToFItzYU?3riH9fXc9d6z9KRg9KRg0rSM0mkesiIipSoq6ZZ66ZZ6!oBy?6B6?6B6?6UA?4wA?4wA?39Q8ICA8ICA?2NH?0UA?0UA?wAA?2m0?2m0?xST?xST?8DL?7bB?7bB23CT23CTsDBgxxWFxxWF0CCQ0CCQ?wD?7oLu3aDd2nDWulXTulXTmTLMmTLMlADTlADTiiviiivioCDwk3Dbk3Db2L?Y??r67unpzcnJi4mJ??Xu7uXezcW?i4aC?!?b7t?MzcCwi4N4?!TE7tW3zbeei31r?9q57sutza!Vi3dl?96t7s!hzbOLi3le??rN7um?zcmli4lw??jc7ujNzcixi4h4???w7u7gzc3Bi4uD8P?w4O7gwc3Bg4uD??D17uDlzcHFi4OG?!Th7tXSzbe1i3178P??4O7uwc3Ng4uLg2??emfuaVnNRzyLSHb?Q27uOl?NJ0CLAAD?AADuAADNAACLHpD?HIbuGHTNEE6LY7j?XKzuT5TNNmSLAL??ALLuAJrNAGiLh87?fsDubKbNSnCLsOL?pNPujbbNYHuLxuL?udPun7bNbHuLyuH?vNLuorXNbnuLv!??st?umsDNaIOL4P??0e7utM3NeouLu???ru7uls3NZouLmPX?juXuesXNU4aLAPX?AOXuAMXNAIaLAP??AO7uAM3NAIuLl???je7uec3NUouLf??Udu7GZs2qRYt0wf?BtO60m82baYtpVP!fTu6UQ82ALotXmv!akO6QfM18VItUAP9?AO52AM1mAItFAP8AAO4AAM0AAIsAf?8Adu4AZs0ARYsAwP8!s!46ms0yaYsiyv9wvO5oos1abos9??aP7uaFzcZzi4ZO?!yL7tyCzb5wi4FM???g7u7Rzc20i4t6??8A7u4Azc0Ai4sA?9cA7skAza0Ai3UA?8El7rQizZsdi2kU?7kP7q0OzZUMi2UI?8HB7rS0zZubi2lp?2pq7mNjzVVVizo6?4JH7nlCzWg5i0cm?9Ob7sWRzap9i3NV?!e67tiuzbqWi35m?6VP7ppJzYU?i1or?38k7nYhzWYdi0UT?zAw7iwszSYmixoa?0BA7js7zTMziyMj?4xp7oJizXBUi0w5?6B67pVyzYFii1dC?6UA7poAzYUAi1oA?38A7nYAzWYAi0UA?3JW7mpQzVtFiz4v?2NH7lxCzU85izYm?0UA7kAAzTcAiyUA?wAA7gAAzQAAiwAA1wdR?xST7hKJzRB2iwpQ?2607mqnzWCQizpi?7XF7qm4zZGei2Ns?6657qKtzYyVi19l?4Kr7nmfzWiJi0dd?zSz7jCnzSmQixxi?z6W7jqMzTJ4iyJS?wD?7gDuzQDNiwCL?4P67nrpzWnJi0eJ?7v?7q7uzZbNi2aL4Gb?0V?utFLNejeLvz7?sjrumjLNaCKLmzD?kSzufSbNVRqLq4L?n3nuiWjNXUeL?!H?7tLuzbXNi3uLqampqampqampqampAACLAACLAIuLAIuLiwCLiwCLiwAAiwAAkO6QkO6Q'; +const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!?'; +const OFFSET = 551; +const LENGTH = 551; +const BUCKET_LENGTH = TABLE.length / LENGTH; + +// match greyXX|grayXX names +const rexGrey = /^gr[ae]y(\d+)/; +// name match +const rexName = /^\w?[A-Za-z0-9 ]+\w/; + +function gray(n: number): [number, number, number] | undefined { + if (0 <= n && n <= 100) { + const v = Math.floor((n * 256 - n + 50) / 100); + return [v, v, v]; + } + return; +} + +function hash(d: number, name: string): number { + if (!d) d = 0x01000193; + for (const c of name) { + d = ((d * 0x01000193) ^ c.charCodeAt(0)) & 0xffffffff; + } + return d >>> 0; +} + +function crc10(name: string, crc?: number): number { + if (!crc) crc = 0; + for (const c of name) { + crc ^= c.charCodeAt(0) << 2; + for (let k = 0; k < 8; k++) { + crc = crc & 0x200 ? (crc << 1) ^ 0x233 : crc << 1; + } + } + crc &= 0x3ff; + return crc >>> 0; +} + +function loadData(idx: number): [number, number, number] { + let value = 0; + for (let i = idx * BUCKET_LENGTH; i < idx * BUCKET_LENGTH + BUCKET_LENGTH; ++i) { + value *= ALPHABET.length; + value += ALPHABET.indexOf(TABLE[i]); + } + return [value >>> 20, (value >> 10) & 0x3FF, value & 0x3FF]; +} + +function loadColor(idx: number): [number, number, number] { + // color buckets are hardcoded to 4 chars + let v = 0; + for (let i = idx * 4; i < idx * 4 + 4; ++i) { + v *= ALPHABET.length; + v += ALPHABET.indexOf(COLORS[i]); + } + return [v >>> 16, (v >> 8) & 0xFF, v & 0xFF]; +} + +function lookupIdx(name: string): number { + let b = loadData(hash(0, name) % LENGTH); + b = loadData(b[0] < OFFSET ? (-(b[0] - OFFSET) - 1) : hash(b[0] - OFFSET, name) % LENGTH); + const [ , , crc] = loadData(b[1]); + return crc10(name) === crc ? b[1] : -1; +} + +export function getColorFromName(name: string): [number, number, number] | undefined { + // basic name filtering + if (name.length < 3 || name.length > 22 || !rexName.exec(name)) return; + + // handle grays special + const m = rexGrey.exec(name); + if (m) return gray(parseInt(m[1])); + + // grab crc checked idx from PHF + const idx = lookupIdx(name); + if (idx === -1) return; + return loadColor(idx); +}