import configureMeasurements, { allMeasures } from 'convert-units'; export const convert = configureMeasurements(allMeasures); export const bases: {[key: number]: string[]} = { 2: ['bin', 'binary'], 8: ['oct', 'octal'], 10: ['dec', 'decimal'], 16: ['hex', 'hexadecimal'] }; export function getBaseNum(base: string): number | null { let result = null; for (const i in bases) { const defs = bases[i]; if (defs.indexOf(base) === -1) { continue; } result = parseInt(i, 10); } if (result) { return result; } if (base.indexOf('b') !== 0) { return null; } const matcher = base.match(/b(?:ase)?-?(\d+)/); if (!matcher || !matcher[1] || isNaN(parseInt(matcher[1], 10))) { return null; } return parseInt(matcher[1], 10); } export function rgbToHex(r: number, g: number, b: number): string { // tslint:disable-next-line: no-bitwise return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); } export function RGBToHSL(r: number, g: number, b: number): {[key: string]: number} { // Make r, g, and b fractions of 1 r /= 255; g /= 255; b /= 255; // Find greatest and smallest channel values const cmin = Math.min(r, g, b); const cmax = Math.max(r, g, b); const delta = cmax - cmin; let h = 0; let s = 0; let l = 0; // Calculate hue // No difference if (delta === 0) { h = 0; } // Red is max else if (cmax === r) { h = ((g - b) / delta) % 6; } // Green is max else if (cmax === g) { h = (b - r) / delta + 2; } // Blue is max else { h = (r - g) / delta + 4; } h = Math.round(h * 60); // Make negative hues positive behind 360° if (h < 0) { h += 360; } // Calculate lightness l = (cmax + cmin) / 2; // Calculate saturation s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1)); // Multiply l and s by 100 s = +(s * 100).toFixed(1); l = +(l * 100).toFixed(1); return { h, s, l }; } export function hexToRgb(hex: string): {[key: string]: number} | null { // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; hex = hex.replace(shorthandRegex, (m, r, g, b) => { return r + r + g + g + b + b; }); const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); return result ? { r: parseInt(result[1], 16), g: parseInt(result[2], 16), b: parseInt(result[3], 16) } : null; } // Create an object to efficiently translate unit names to their abbreviations let unitIndex: { [x: string]: string[] } = {}; let backupLowercaseUnitTable: { [x: string]: string } = {}; export function createUnitIndex(): void { const fullList = convert().list(); fullList.forEach((unit) => { const { abbr, singular, plural } = unit; if (!unitIndex[abbr]) { unitIndex[abbr] = [ abbr, singular.toLowerCase(), plural.toLowerCase() ]; // liter <-> litres if (abbr === 'l') { unitIndex[abbr].push('liter', 'liters'); } // meter -> metre if (singular.includes('meter')) { unitIndex[abbr].push( singular.replace('meter', 'metre').toLowerCase(), plural.replace('meter', 'metre').toLowerCase(), ); } // metre -> meter if (singular.includes('metre')) { unitIndex[abbr].push( singular.replace('metre', 'meter').toLowerCase(), plural.replace('metre', 'meter').toLowerCase(), ); } // degree(s) Celsius -> celsius if (singular.startsWith('degree ')) { unitIndex[abbr].push( singular.split(' ')[1].toLowerCase(), ); } // " per " -> "/" const appendages: string[] = []; unitIndex[abbr].forEach((entry) => { if (entry.includes(' per ')) { appendages.push(entry.replace(' per ', '/')); } }); unitIndex[abbr].push(...appendages); backupLowercaseUnitTable[abbr.toLowerCase()] = abbr; } }); } // Get a unit by it's full name or abbreviation export function getUnitAbbreviation(fullText: string): string | null { // Regular abbreviation if (unitIndex[fullText]) { return fullText; } // Lowercase abbreviation const lowerText = fullText.toLowerCase(); if (backupLowercaseUnitTable[lowerText]) { return backupLowercaseUnitTable[lowerText]; } // Text search const found = Object.keys(unitIndex).reduce((previous, current) => { const arrayList = unitIndex[current]; if (arrayList.includes(lowerText)) { return current; } return previous; }, ''); return found || null; } export function wipeCaches(): void { unitIndex = {}; backupLowercaseUnitTable = {}; } export function ASCIIBinaryConverter(input: string, simplified: string[]): string { let response = ''; let strArr; let i; let text = input.split(' ').slice(2).join(' '); switch (simplified[0] ? simplified[0].toUpperCase() : null) { case 'ENCODE': strArr = text.split(''); for (i in strArr) { response += ('0000000' + parseInt(Buffer.from(strArr[i].toString(), 'utf8').toString('hex'), 16).toString(2)).slice(-8); } response = response.substring(1); break; case 'DECODE': text = text.split(' ').join(''); i = 0; while (8 * (i + 1) <= text.length) { response += Buffer.from(parseInt(text.substring(8 * i, 8), 2).toString(16), 'hex').toString('utf8'); i++; } response = response.replace(/\n/g, '\\n').replace(/\r/g, '\\r'); } return response; } export function ASCIIHexConverter(input: string, simplified: string[]): string { let response = ''; let i; let text = input.split(' ').slice(2).join(' '); switch (simplified[0] ? simplified[0].toUpperCase() : null) { case 'DECODE': text = text.replace(/\s/g, ''); for (i = 0; i < text.length; i += 2) { response += String.fromCharCode(parseInt(text.substring(i, 2), 16)); } response = response.replace(/\n/g, '\\n').replace(/\r/g, '\\r'); break; case 'ENCODE': for (i = 0; i < text.length; i++) { response += text.charCodeAt(i).toString(16) + ' '; } break; } return response; } export function base64Converter(input: string, simplified: string[]): string { let response = ''; const text = input.split(' ').slice(2).join(' '); switch (simplified[0] ? simplified[0].toUpperCase() : null) { case 'DECODE': response = (Buffer.from(text, 'base64').toString('ascii')).replace(/\n/g, '\\n').replace(/\r/g, '\\r'); break; case 'ENCODE': response = Buffer.from(text).toString('base64'); break; } return response; } export function baseConverter(simplified: string[]): string { if (simplified.length < 3) { throw new Error('Too few arguments!'); } const input = simplified[0]; const src = simplified[1].toLowerCase(); const dst = simplified[2].toLowerCase(); const srcBase = getBaseNum(src); const dstBase = getBaseNum(dst); if (!srcBase || !dstBase || dstBase > 36 || dstBase < 2 || srcBase > 36 || srcBase < 2) { throw new Error('Invalid conversion.'); } const decimal = parseInt(input, srcBase); return decimal.toString(dstBase); }