Get ANSI color for character by index

I have developed couleurs

an NPM package
that can be installed to add a method rgb

to String.prototype

:

> console.log("Hello World!".rgb(255, 0, 0)) // "Hello World!" in red
Hello World!
undefined
> "Hello World!".rgb(255, 0, 0)
'\u001b[38;5;196mHello World!\u001b[0m'

      

This works great. What is the correct way to get ANSI color / style in the index i

?

Maybe this could be hacked by some regex, but I'm not sure if this is really good (however, if a correct implementation is available, I don't mind) ... I would prefer a native way to get the color / style by referring to the character interpreted by tty ...

> function getStyle (input, i) { /* get style at index `i` */ return style; }

> getStyle("Hello World!".rgb(255, 0, 0), 0); // Get style of the first char
{
   start: "\u001b[38;5;196m",
   end: "\u001b[0m",
   char: "H"
}
> getStyle("Hello " + "World!".rgb(255, 0, 0), 0); // Get style of the first char
{
   start: "",
   end: "",
   char: "H"
}

      

Things get more complicated when we have several combined styles:

> console.log("Green and Italic".rgb(0, 255, 0).italic())
Green and Italic
undefined
> getStyle("Green and Italic".rgb(0, 255, 0).italic(), 0);
{
   start: "\u001b[3m\u001b[38;5;46m",
   end: "\u001b[0m\u001b[23m",
   char: "G"
}
> getStyle(("Bold & Red".bold() + " but this one is only red").rgb(255, 0, 0), 0);
{
   start: "\u001b[38;5;196m\u001b[1m",
   end: "\u001b[22m\u001b[0m",
   char: "B"
}
> getStyle(("Bold & Red".bold() + " but this one is only red").rgb(255, 0, 0), 11);
{
   start: "\u001b[38;5;196m",
   end: "\u001b[0m",
   char: "u"
}
> ("Bold & Red".bold() + " but this one is only red").rgb(255, 0, 0)
'\u001b[38;5;196m\u001b[1mBold & Red\u001b[22m but this one is only red\u001b[0m'

      

As I said, I am looking for the native path (possibly using a child process).

So how do I get the full ANSI style for a character in the index i

?

+3


source to share


2 answers


There are several ways to "format" text to text, and this is one of them. The problem is that you are mixing text and style into the same object - a text string. It looks like RTF

Here is some \b bold\b0 and {\i italic} text\par

      

but different from, say, the native Word.DOC file format, which works with text launches:

(text) Here is some bold and italic text\r
(chp)  13 None
       4  sprmCFBold
       5  None
       6  sprmCFItalic
       6  None

      

- the number on the left is the number of characters with specific formatting.



The latter format is what you are looking for, since you want to index characters in plain text. Subtracting the length of the formatting, you can see which one you are interested in. Depending on how many times you want to request formatting, you can do one-off runs, or cache the formatted text somewhere.

A one-time run should check each element of the encoded string, incrementing the "text" index if it is not inside the color string, and updating the "last seen" color string if it is. I have added a compatible function getCharAt

for debugging purposes.

var str = '\u001b[38;5;196m\u001b[1mBo\x1B[22mld & Red\u001b[22m but this one is only red\u001b[0m';

const map = {
    bold: ["\x1B[1m", "\x1B[22m" ]
  , italic: ["\x1B[3m", "\x1B[23m" ]
  , underline: ["\x1B[4m", "\x1B[24m" ]
  , inverse: ["\x1B[7m", "\x1B[27m" ]
  , strikethrough: ["\x1B[9m", "\x1B[29m" ]
};

String.prototype.getColorAt = function(index)
{
    var strindex=0, color=[], cmatch, i,j;

    while (strindex < this.length)
    {
        cmatch = this.substr(strindex).match(/^(\u001B\[[^m]*m)/);
        if (cmatch)
        {
            // Global reset?
            if (cmatch[0] == '\x1B[0m')
            {
                color = [];
            } else
            {
                // Off code?
                for (i=0; i<map.length; i++)
                {
                    if (map[i][1] == cmatch[0])
                    {
                        // Remove On code?
                        for (j=color.length-1; j>=0; j--)
                        {
                            if (color[j] == map[i][0])
                                color.splice (j,1);
                        }
                        break;
                    }
                }
                if (j==map.length)
                    color.push (cmatch[0]);
            }
            strindex += cmatch[0].length;
        } else
        {
            /* a regular character! */
            if (!index)
                break;
            strindex++;
            index--;
        }
    }
    return color.join('');
}

String.prototype.getCharAt = function(index)
{
    var strindex=0, cmatch;

    while (strindex < this.length)
    {
        cmatch = this.substr(strindex).match(/^(\u001B\[[^m]*m)/);
        if (cmatch)
        {
            strindex += cmatch[0].length;
        } else
        {
            /* a regular character! */
            if (!index)
                return this.substr(strindex,1);
            strindex++;
            index--;
        }
    }
    return '';
}

console.log (str);

color = str.getColorAt (1);
text = str.getCharAt (1);
console.log ('color is '+color+color.length+', char is '+text);

      

The returned color

one is still in the original escaped encoding. You can make it return a constant of some type by adding them to the original array map

.

+2


source


I can't provide you with a complete solution, but here's a sketch:

  • maintain a stack that accumulates the current format
  • split the string into chunks espace sequence | just a character

  • repeat this chunk list
  • if it's just a char, store its index + current stack state
  • if it is an escape, either push the appropriate format onto the stack, or select a format from it

You can also use this algorithm to convert escaped string to html and use XML methods to process the result tree.



By the way, the latter would be nice the other way around, how about this:

console.log("<font color='red'>hi <b>there</b></font>".toANSI())

      

+1


source







All Articles