Skip to content

Instantly share code, notes, and snippets.

@zeh
Last active March 26, 2017 23:34
Show Gist options
  • Save zeh/7295acd2cdb4d7f8252b9302672db51f to your computer and use it in GitHub Desktop.
Save zeh/7295acd2cdb4d7f8252b9302672db51f to your computer and use it in GitHub Desktop.
/*
Converts ANSi Art source text to console.log()-compatible color codes that work on Chrome and FireFox.
Usage:
console.log.apply(null, ANSIColorUtils.convertToConsole(ansiSource:string, numColumns:number));
Example:
console.log.apply(null, ANSIColorUtils.convertToConsole(`

  °
   °ܲÜÜÜÛ
 ÜÜܲ²Ý ÜÜܲÜÜÛÛÛÛܲÝ
  °²ÛÛÝ ÜÜÜÛÛÛÛÛÛÛÛÛÛÛ
  ÞÛÛÛÛÛÛÛÛÛÛÛÛÛ²ÛßÛÛ
  ÛÛÛÛÛÛÛßßßßÛÞ°ÛÛÛ
  ÛÛ²ÛÛßßß ÛÛÛÛÛ
   Û°Ý   ÜÜÛ°ßÛÛßß
-Ä--- - ß - ÞÛÛÛÛ þ - Ü --Ä--- --
 °°± ° ° °² ÛÛÞÝÝ ÜÜÜß  ° °± °°°
---Ä- --Ä-- ²ÛÛßÛ°²ßß - --Ä-- Ä-Ä--
 app started ßÛÛÛßß version x.y.z
 built @ 2017-01-02 - dev mode is ON
`
, 37));
*/
export default class ANSIColorUtils {
// TODO:
// * make this less verbose/repetitive
// * better char conversion
// * better seekahead
private static readonly COLORS = [
"rgb(0, 0, 0)",
"rgb(170, 0, 0)",
"rgb(0, 170, 0)",
"rgb(170, 85, 0)",
"rgb(0, 0, 170)",
"rgb(170, 0, 170)",
"rgb(0, 170, 170)",
"rgb(170, 170, 170)",
"rgb(85, 85, 85)",
"rgb(255, 85, 85)",
"rgb(85, 255, 85)",
"rgb(255, 255, 85)",
"rgb(85, 85, 255)",
"rgb(255, 85, 255)",
"rgb(85, 255, 255)",
"rgb(255, 255, 255)",
];
private static readonly CHARS = [
[ 223, "¯"],
[ 254, "¦"],
[ 220, "_"],
[ 221, "¦"],
[ 222, "¦"],
[ 176, "¦"],
[ 177, "¦"],
[ 178, "¦"],
[ 219, "¦"],
[ 196, "-"],
];
private static readonly ESC_START = "\x1B[";
private static readonly ESC_END_COLOR = "m";
private static readonly ESC_END_SPACES = "C";
private static readonly LINE_FEED = "\n";
public static convertToConsole(ansi:string, columns:number = 0):string[] {
let output:string = "";
let colors:string[] = [];
let fg = 7;
let bg = 0;
let flagHigh = false;
let flagBlink = false;
let flagUnderline = false;
let l = ansi.length;
let currLineColumns = 0;
let pos:number = 0;
do {
if (ansi.substr(pos, ANSIColorUtils.ESC_START.length) === ANSIColorUtils.ESC_START) {
// Color code
pos += ANSIColorUtils.ESC_START.length;
// Starting code
const endColorPos = ansi.indexOf(ANSIColorUtils.ESC_END_COLOR, pos);
const endSpacesPos = ansi.indexOf(ANSIColorUtils.ESC_END_SPACES, pos);
if (endColorPos > -1 && (endColorPos < endSpacesPos || endSpacesPos < 0)) {
// Color command
const colorCodes = ansi.substr(pos, endColorPos - pos).split(";");
pos = endColorPos + ANSIColorUtils.ESC_END_COLOR.length;
// Adds codes from colors
output += "%c";
for (let colorCode of colorCodes) {
switch (colorCode) {
case "0":
fg = 7;
bg = 0;
flagHigh = false;
flagBlink = false;
flagUnderline = false;
break;
case "1":
flagHigh = true;
break;
case "4":
flagUnderline = true;
break;
case "5":
flagBlink = true;
break;
case "7":
fg = 7;
bg = 0;
flagHigh = false;
break;
case "8":
fg = bg;
break;
case "30":
fg = 0;
break;
case "31":
fg = 1;
break;
case "32":
fg = 2;
break;
case "33":
fg = 3;
break;
case "34":
fg = 4;
break;
case "35":
fg = 5;
break;
case "36":
fg = 6;
break;
case "37":
fg = 7;
break;
case "40":
bg = 0;
break;
case "41":
bg = 1;
break;
case "42":
bg = 2;
break;
case "43":
bg = 3;
break;
case "44":
bg = 4;
break;
case "45":
bg = 5;
break;
case "46":
bg = 6;
break;
case "47":
bg = 7;
break;
default:
console.warn(`Error processing color code [${colorCode}].`);
break;
}
}
colors.push(`color: ${ANSIColorUtils.COLORS[fg + (flagHigh ? 8 : 0)]}; background-color: ${ANSIColorUtils.COLORS[bg]}`);
} else if (endSpacesPos > -1 && (endSpacesPos < endColorPos || endColorPos < 0)) {
// Spaces command
let spaces = parseInt(ansi.substr(pos, endSpacesPos - pos), 10);
output += (" ").repeat(spaces);
currLineColumns += spaces;
pos = endSpacesPos + ANSIColorUtils.ESC_END_SPACES.length;
}
} else if (ansi.substr(pos, ANSIColorUtils.LINE_FEED.length) === ANSIColorUtils.LINE_FEED) {
// New line
if (currLineColumns < columns) output += (" ").repeat(columns - currLineColumns);
output += "\n";
currLineColumns = 0;
pos += ANSIColorUtils.LINE_FEED.length;
} else {
// Normal character
let nextPosColors = ansi.indexOf(ANSIColorUtils.ESC_START, pos);
let nextPosLineFeed = ansi.indexOf(ANSIColorUtils.LINE_FEED, pos);
let nextPos;
if (nextPosColors < 0 && nextPosLineFeed < 0) {
// No more color codes or line feeds
nextPos = ansi.length;
} else if (nextPosColors < 0) {
nextPos = nextPosLineFeed;
} else if (nextPosLineFeed < 0) {
nextPos = nextPosColors;
} else {
nextPos = Math.min(nextPosColors, nextPosLineFeed);
}
output += ansi.substr(pos, nextPos - pos);
currLineColumns += nextPos - pos;
pos = nextPos;
}
} while (pos < l);
// Add spaces again
if (currLineColumns < columns) output += (" ").repeat(columns - currLineColumns);
for (let char of ANSIColorUtils.CHARS) {
output = output.replace(new RegExp(String.fromCharCode(char[0] as number), "g"), char[1] as string);
}
return [output].concat(colors);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment