cfonts
Version:
Sexy ANSI fonts for the console
576 lines (557 loc) • 15.8 kB
JavaScript
/***************************************************************************************************************************************************************
*
* cfonts
*
* Sexy fonts for the console. (CLI output)
*
* @license https://github.com/dominikwilkowski/cfonts/blob/released/LICENSE GNU GPL-3.0-or-later
* @author Dominik Wilkowski hi@dominik-wilkowski.com
* @repository https://github.com/dominikwilkowski/cfonts
*
* HEXTEST - Regex to see if a string is a hex color
* Rgb2hsv - Converts an RGB color value to HSV
* Hsv2rgb - Converts an HSV color value to RGB
* Rgb2hex - Converts RGB to HEX
* Hex2rgb - Convert HEX to RGB
* Hsv2hsvRad - Convert HSV coordinate to HSVrad (degree to radian)
* HsvRad2hsv - Convert HSVrad color to HSV (radian to degree)
* Hex2hsvRad - Convert HEX to HSVrad
* HsvRad2hex - Convert HSVrad to HEX
* rgb2ansi_16m - - Convert RGB values to ANSI16 million colors - truecolor
* rgb2ansi256Code - Convert RGB values to ANSI256 escape code
* rgb2ansi_256 - Convert RGB values to ANSI256
* ansi_2562ansi_16 - Convert ANSI256 code values to ANSI16
* get_term_color_support - Detect the ANSI support for the current terminal taking into account env vars NO_COLOR and FORCE_COLOR
* Color - Abstraction for coloring hex-, keyword- and background-colors
*
**************************************************************************************************************************************************************/
'use strict';
const {
supportsColor
} = require('supports-color');
const {
Options
} = require('./Options.js');
/**
* Regex to see if a string is a hex color
*
* @type {RegExp}
*/
const HEXTEST = RegExp('^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$');
/**
* Converts an RGB color value to HSV
*
* @author https://github.com/Gavin-YYC/colorconvert
*
* @param {object} options - Arguments
* @param {number} options.r - The red color value
* @param {number} options.g - The green color value
* @param {number} options.b - The blue color value
*
* @return {array} - The HSV representation
*/
function Rgb2hsv({
r,
g,
b
}) {
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
const diff = max - min;
let h = 0;
let v = max;
let s = max === 0 ? 0 : diff / max;
// h
if (max === min) {
h = 0;
} else if (max === r && g >= b) {
h = 60 * ((g - b) / diff);
} else if (max === r && g < b) {
h = 60 * ((g - b) / diff) + 360;
} else if (max === g) {
h = 60 * ((b - r) / diff) + 120;
} else {
// if( max === b ) {
h = 60 * ((r - g) / diff) + 240;
}
return [h, s * 100, v * 100];
}
/**
* Converts an HSV color value to RGB
*
* @author https://github.com/Gavin-YYC/colorconvert
*
* @param {number} h - The hue
* @param {number} s - The saturation
* @param {number} v - The value
*
* @typedef {object} Hsv2rgbReturnObject
* @property {number} r - The red value
* @property {number} g - The green value
* @property {number} b - The blue value
*
* @return {Hsv2rgbReturnObject} - The RGB representation
*/
function Hsv2rgb(h, s, v) {
h /= 60;
s /= 100;
v /= 100;
const hi = Math.floor(h) % 6;
const f = h - Math.floor(h);
const p = 255 * v * (1 - s);
const q = 255 * v * (1 - s * f);
const t = 255 * v * (1 - s * (1 - f));
v *= 255;
switch (hi) {
case 0:
return {
r: v,
g: t,
b: p
};
case 1:
return {
r: q,
g: v,
b: p
};
case 2:
return {
r: p,
g: v,
b: t
};
case 3:
return {
r: p,
g: q,
b: v
};
case 4:
return {
r: t,
g: p,
b: v
};
case 5:
return {
r: v,
g: p,
b: q
};
}
}
/**
* Converts RGB to HEX
*
* @param {number} r - The Red value
* @param {number} g - The Green value
* @param {number} b - The Blue value
*
* @return {string} - A HEX color
*/
function Rgb2hex(r, g, b) {
const val = (b | g << 8 | r << 16 | 1 << 24).toString(16).slice(1);
return '#' + val.toLowerCase();
}
/**
* Convert HEX to RGB
*
* @param {string} hex - The HEX color
*
* @return {array} - An object with RGB values
*/
function Hex2rgb(hex) {
hex = hex.replace(/^#/, '');
if (hex.length > 6) {
hex = hex.slice(0, 6);
}
if (hex.length === 4) {
hex = hex.slice(0, 3);
}
if (hex.length === 3) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
const num = parseInt(hex, 16);
const r = num >> 16;
const g = num >> 8 & 255;
const b = num & 255;
const rgb = [r, g, b];
return rgb;
}
/**
* Convert HSV coordinate to HSVrad (degree to radian)
*
* @param {array} argument - The HSV representation of a color
*
* @return {array} - The HSVrad color
*/
function Hsv2hsvRad([h, s, v]) {
return [h * Math.PI / 180, s, v];
}
/**
* Convert HSVrad color to HSV (radian to degree)
*
* @param {number} hRad - H in rad
* @param {number} s - S
* @param {number} v - V
*
* @return {array} - The HSV color
*/
function HsvRad2hsv(hRad, s, v) {
const precision = 1000000000000;
const h = Math.round(hRad * 180 / Math.PI * precision) / precision;
return [h, s, v];
}
/**
* Convert HEX to HSVrad
*
* @param {string} hex - A HEX color
*
* @return {array} - The HSVrad color
*/
function Hex2hsvRad(hex) {
const [r, g, b] = Hex2rgb(hex);
const hsv = Rgb2hsv({
r,
g,
b
});
const hsvRad = Hsv2hsvRad(hsv);
return hsvRad;
}
/**
* Convert HSVrad to HEX
*
* @param {number} hRad - The hue in rad
* @param {number} s - The saturation
* @param {number} v - The value
*
* @return {string} - The HEX color
*/
function HsvRad2hex(hRad, s, v) {
const [h] = HsvRad2hsv(hRad, s, v);
const {
r,
g,
b
} = Hsv2rgb(h, s, v);
const hex = Rgb2hex(r, g, b);
return hex;
}
/**
* Convert RGB values to ANSI16 million colors - truecolor
*
* @param {number} r - Red value
* @param {number} g - Green value
* @param {number} b - Blue value
* @param {boolean} bg - Is this a background color; default: false
*
* @return {string} - The opening ANSI escape sequence for the given color
*/
function rgb2ansi_16m(r, g, b, bg = false) {
const layer_code = bg ? 48 : 38;
return `\u001b[${layer_code};2;${r};${g};${b}m`;
}
/**
* Convert RGB values to ANSI256 escape code
*
* @param {number} red - Red value
* @param {number} green - Green value
* @param {number} blue - Blue value
*
* @return {number} - The ANSI escape code for the given color
*/
function rgb2ansi256Code(red, green, blue) {
if (red === green && green === blue) {
if (red < 8) {
return 16;
}
if (red > 248) {
return 231;
}
return Math.round((red - 8) / 247 * 24) + 232;
}
return 16 + 36 * Math.round(red / 255 * 5) + 6 * Math.round(green / 255 * 5) + Math.round(blue / 255 * 5);
}
/**
* Convert RGB values to ANSI256
*
* @param {number} r - Red value
* @param {number} g - Green value
* @param {number} b - Blue value
* @param {boolean} bg - Is this a background color; default: false
*
* @return {string} - The opening ANSI escape sequence for the given color
*/
function rgb2ansi_256(r, g, b, bg = false) {
const layer_code = bg ? 48 : 38;
const code = rgb2ansi256Code(r, g, b);
return `\u001b[${layer_code};5;${code}m`;
}
/**
* Convert ANSI256 code values to ANSI16
*
* @param {number} code - The code of the ANSI256 color
* @param {boolean} bg - Is this a background color; default: false
*
* @return {string} - The opening ANSI escape sequence for the given color
*/
function ansi_2562ansi_16(code, bg = false) {
let ansi_16_code;
if (code <= 7) {
ansi_16_code = code + 10;
}
if (code >= 8 && code <= 15) {
ansi_16_code = code + 82;
}
if (code === 16) {
ansi_16_code = 0;
}
if (code >= 17 && code <= 19) {
ansi_16_code = 34;
}
if (code >= 20 && code <= 21 || code >= 25 && code <= 27) {
ansi_16_code = 94;
}
if (code >= 22 && code <= 24 || code >= 58 && code <= 60 || code >= 64 && code <= 66 || code >= 94 && code <= 95 || code >= 100 && code <= 102 || code >= 106 && code <= 108 || code >= 130 && code <= 131 || code >= 136 && code <= 138 || code >= 142 && code <= 144 || code >= 148 && code <= 151 || code >= 172 && code <= 174 || code >= 178 && code <= 181 || code >= 184 && code <= 189) {
ansi_16_code = 33;
}
if (code >= 28 && code <= 30 || code >= 34 && code <= 36 || code >= 70 && code <= 72 || code >= 76 && code <= 79 || code >= 112 && code <= 114) {
ansi_16_code = 32;
}
if (code >= 31 && code <= 33 || code >= 37 && code <= 39 || code >= 44 && code <= 45 || code >= 61 && code <= 63 || code >= 67 && code <= 69 || code >= 73 && code <= 75 || code >= 80 && code <= 81 || code >= 103 && code <= 111 || code >= 115 && code <= 117 || code >= 152 && code <= 153) {
ansi_16_code = 36;
}
if (code >= 40 && code <= 43 || code >= 46 && code <= 49 || code >= 82 && code <= 85 || code >= 118 && code <= 120 || code >= 154 && code <= 157) {
ansi_16_code = 92;
}
if (code >= 50 && code <= 51 || code >= 86 && code <= 87 || code >= 121 && code <= 123 || code >= 158 && code <= 159) {
ansi_16_code = 96;
}
if (code >= 52 && code <= 54 || code >= 88 && code <= 90 || code >= 124 && code <= 126 || code >= 166 && code <= 168) {
ansi_16_code = 31;
}
if (code >= 55 && code <= 57 || code >= 91 && code <= 93 || code >= 96 && code <= 99 || code >= 127 && code <= 129 || code >= 132 && code <= 135 || code >= 139 && code <= 141 || code >= 145 && code <= 147 || code >= 169 && code <= 171 || code >= 175 && code <= 177) {
ansi_16_code = 35;
}
if (code >= 160 && code <= 163 || code >= 196 && code <= 199 || code >= 202 && code <= 213) {
ansi_16_code = 91;
}
if (code >= 164 && code <= 165 || code >= 182 && code <= 183 || code >= 200 && code <= 201 || code >= 218 && code <= 219) {
ansi_16_code = 95;
}
if (code >= 190 && code <= 193 || code >= 214 && code <= 217 || code >= 220 && code <= 228) {
ansi_16_code = 93;
}
if (code >= 194 && code <= 195 || code >= 229 && code <= 231 || code >= 253 && code <= 255) {
ansi_16_code = 97;
}
if (code >= 232 && code <= 239) {
ansi_16_code = 30;
}
if (code >= 240 && code <= 246) {
ansi_16_code = 90;
}
if (code >= 247 && code <= 252) {
ansi_16_code = 37;
}
if (bg) {
ansi_16_code = ansi_16_code + 10;
}
return `\u001b[${ansi_16_code}m`;
}
/**
* Detect the ANSI support for the current terminal taking into account env vars NO_COLOR and FORCE_COLOR
*
* @return {number} - 0 = no color support; 1 = 16 colors support; 2 = 256 colors support; 3 = 16 million colors support
*/
function get_term_color_support() {
let term_support = supportsColor().level || 3;
if ('NO_COLOR' in process.env) {
term_support = 0;
}
if (process.env['FORCE_COLOR'] === '0') {
term_support = 0;
}
if (process.env['FORCE_COLOR'] === '1') {
term_support = 1;
}
if (process.env['FORCE_COLOR'] === '2') {
term_support = 2;
}
if (process.env['FORCE_COLOR'] === '3') {
term_support = 3;
}
return term_support;
}
/**
* Abstraction for coloring hex-, keyword- and background-colors
*
* @param {string} color - The color to be used
* @param {boolean} bg - Whether this is a background or not
*
* @typedef {object} ColorReturnObject
* @property {string} open - The open ansi code
* @property {string} close - The close ansi code
*
* @return {ColorReturnObject} - An object with open and close ansi codes
*/
const Color = (color, bg = false) => {
const COLORS = {
black: '#000',
red: '#ea3223',
green: '#377d22',
yellow: '#fffd54',
blue: '#0020f5',
magenta: '#ea3df7',
cyan: '#74fbfd',
white: '#fff',
gray: '#808080',
redbright: '#ee776d',
greenbright: '#8cf57b',
yellowbright: '#fffb7f',
bluebright: '#6974f6',
magentabright: '#ee82f8',
cyanbright: '#8dfafd',
whitebright: '#fff'
};
const support = get_term_color_support();
// bail early if we use system color
if (color === 'system' || support === 0) {
return {
open: '',
close: ''
};
}
const OPTIONS = Options.get;
if (OPTIONS.env === 'node') {
let open;
let close = bg ? '\u001b[49m' : '\u001b[39m';
switch (color.toLowerCase()) {
case 'transparent':
open = '\u001b[49m';
break;
case 'black':
open = bg ? '\u001b[40m' : '\u001b[30m';
break;
case 'red':
open = bg ? '\u001b[41m' : '\u001b[31m';
break;
case 'green':
open = bg ? '\u001b[42m' : '\u001b[32m';
break;
case 'yellow':
open = bg ? '\u001b[43m' : '\u001b[33m';
break;
case 'blue':
open = bg ? '\u001b[44m' : '\u001b[34m';
break;
case 'magenta':
open = bg ? '\u001b[45m' : '\u001b[35m';
break;
case 'cyan':
open = bg ? '\u001b[46m' : '\u001b[36m';
break;
case 'white':
open = bg ? '\u001b[47m' : '\u001b[37m';
break;
case 'gray':
open = bg ? '\u001b[100m' : '\u001b[90m';
break;
case 'redbright':
open = bg ? '\u001b[101m' : '\u001b[91m';
break;
case 'greenbright':
open = bg ? '\u001b[102m' : '\u001b[92m';
break;
case 'yellowbright':
open = bg ? '\u001b[103m' : '\u001b[93m';
break;
case 'bluebright':
open = bg ? '\u001b[104m' : '\u001b[94m';
break;
case 'magentabright':
open = bg ? '\u001b[105m' : '\u001b[95m';
break;
case 'cyanbright':
open = bg ? '\u001b[106m' : '\u001b[96m';
break;
case 'whitebright':
open = bg ? '\u001b[107m' : '\u001b[97m';
break;
case 'candy':
open = ['\u001b[31m', '\u001b[32m', '\u001b[33m', '\u001b[35m', '\u001b[36m', '\u001b[91m', '\u001b[92m', '\u001b[93m', '\u001b[94m', '\u001b[95m', '\u001b[96m'][Math.floor(Math.random() * 11)];
break;
default:
{
let hex = color;
if (!HEXTEST.test(color)) {
return {
open: '',
close: ''
};
}
const rgb = Hex2rgb(hex);
if (support === 1) {
open = ansi_2562ansi_16(rgb2ansi256Code(rgb[0], rgb[1], rgb[2]), bg);
}
if (support === 2) {
open = rgb2ansi_256(rgb[0], rgb[1], rgb[2], bg);
}
if (support === 3) {
open = rgb2ansi_16m(rgb[0], rgb[1], rgb[2], bg);
}
}
}
return {
open,
close
};
} else if (!OPTIONS.env) {
return {
open: '',
close: ''
};
} else {
if (!HEXTEST.test(color)) {
color = COLORS[color.toLowerCase()];
if (!color) {
return {
open: '',
close: ''
};
}
}
if (bg) {
return {
open: color,
close: ''
};
}
return {
open: `<span style="color:${color}">`,
close: '</span>'
};
}
};
module.exports = exports = {
HEXTEST,
Rgb2hsv,
Hsv2rgb,
Rgb2hex,
Hex2rgb,
Hsv2hsvRad,
HsvRad2hsv,
Hex2hsvRad,
HsvRad2hex,
rgb2ansi_16m,
rgb2ansi256Code,
rgb2ansi_256,
ansi_2562ansi_16,
get_term_color_support,
Color
};