[全语言][Python]让你的CIL多一点色彩:ANSI转义序列定制渲染方式

大家肯定都熟悉\n \t这一类转义字符,它们可以进行输出一些我们平时不太容易打出来的字符,与终端配合就可以做到一些特殊的操作。与之类似,\033代表了ASCII码为27的字符ESCESC专门用于终端控制(所以被称作ansi escape code,ANSI转义字符),可以大量操作(在此不做展开)。

通常ESC会与[字符连接进行控制(当然还有其他许多),而ESC [则被称为CSI(Control Sequence Introducer)。当然,由于CSI可以进行的操作也可多,本文仅就其控制文本样式这一点进行说明。

下文均以\033表示ESC(以及\033[表示SCI),使用python进行举例

让我们先看一段简单的例子:

容易总结出以下规律:

使用\033[#m来设置样式,其中#代表具体的样式代码(一些数字),对于多样式使用;将样式数字隔开。

以3开头的两位数代表前景色,以4开头的两位数代表背景色;单独数字代表特殊样式(比如4代表下划线)

如上文所言,对于颜色有以下对应代码:

代码 颜色
0 黑色
1 紅色
2 綠色
3 黃色
4 藍色
5 紫色
6 青色
7 白色

对于特殊样式,则有:

代码 意义
0 默认
1 高亮
4 下划线
5 闪烁
7 反色
8 不可见

其实有more and more,但是懒得写了

对于上面的内容,一般的终端都支持(除了win家族比较老的终端以及idle一类的并非主要用于终端的东西)。其中有八种颜色,对应二进制需要3bit来标记。现在的大部分支持3bit的终端也支持4bit,即16种颜色。与3bit使用 30~37 来表示前景色以及使用 40~47 表示背景色类似,4bit在其基础上添加 90~97 与 100~107 来表示更亮的颜色。如下:

对于再新一些的终端,还支持8bit的256色,在linux中可以使用下面的命令测试自己的终端是否支持256色(只要正常显示了256种颜色就是支持):

(x=`tput op` y=`printf %76s`;for i in {0..256};do o=00$i;echo -e ${o:${#o}-3:3} `tput setaf $i;tput setab $i`${y// /=}$x;done)

对于256色的终端,使用\033[38;5;#m\033[48;5;#m来表示前景色与背景色,#可以是0~255的任意数,对照如下:

示例如下:

再新一些的终端甚至支持256bit的全彩色,再linux下可以使用下面的代码进行测试:

awk 'BEGIN{
    s="/\\/\\/\\/\\/\\"; s=s s s s s s s s;
    for (colnum = 0; colnum<77; colnum++) {
        r = 255-(colnum*255/76);
        g = (colnum*510/76);
        b = (colnum*255/76);
        if (g>255) g = 510-g;
        printf "\033[48;2;%d;%d;%dm", r,g,b;
        printf "\033[38;2;%d;%d;%dm", 255-r,255-g,255-b;
        printf "%s\033[0m", substr(s,colnum+1,1);
    }
    printf "\n";
}'

支持的将有以下输出:

对于全彩色,使用\033[38;2;r;g;bm\033[48;2;r;g;bm进行设置。


综上所述,让我们写一个简单的printc()函数来进行简便的带样式输出吧:

def printc(*args, fg: str | int | tuple[int] = '', bg: str | int | tuple[int] = '', light: bool = False, style: tuple[int | str] | str | int = (), sep=' ', end='\n', file=None, flush=False):
    def prints(arg): return print(arg, end='', file=file, flush=flush)

    def decoder(data):
        if type(data) is str:
            com_table = {'black': 0, 'red': 1, 'green': 2, 'yellow': 3,
                         'blue': 4, 'magenta': 5, 'cyan': 6, 'white': 7}
            return com_table[data]
        elif type(data) is int:
            return f'8;5;{data}'
        elif type(data) is tuple:
            if len(data) == 3:
                return f'8;2;{data[0]};{data[1]};{data[2]}'
    if not light or type(fg) is not str or type(bg) is not str:
        if fg:
            prints(f"\033[3{decoder(fg)}m")
        if bg:
            prints(f"\033[4{decoder(bg)}m")
    else:
        if fg:
            prints(f"\033[9{decoder(fg)}m")
        if bg:
            prints(f"\033[10{decoder(bg)}m")
    if type(style) is tuple:
        for s in style:
            if type(s) is int:
                prints(f'\033[{s}m')
            elif type(s) is str:
                com_table = {'reset': 0, 'bold': 1, 'faint': 2, 'italic': 3,
                             'underline': 4, 'invert': 7, 'hide': 8, 'delete': 9}
                prints(f'\033[{com_table[s]}m')
    elif type(style) is str:
        com_table = {'reset': 0, 'bold': 1, 'faint': 2, 'italic': 3,
                     'underline': 4, 'invert': 7, 'hide': 8, 'delete': 9}
        prints(f'\033[{com_table[style]}m')
    elif type(style) is int:
        prints(f'\033[{style}m')
    print(*args, sep=sep, end=end, file=file, flush=flush)
    prints('\033[0m')

写一下简单的测试:

if __name__ == '__main__':
    printc('''Copyright(c) 2022 Expector
            _       _
 _ __  _ __(_)_ __ | |_ ___
| '_ \| '__| | '_ \| __/ __|
| |_) | |  | | | | | || (__
| .__/|_|  |_|_| |_|\__\___|
|_|''', fg=(0x66, 0xcc, 0xff), bg='black', style='bold')
    input('Press enter to see the palette...')
    print('\n4-bit 16-color')
    for color in ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white']:
        printc(f'    {color}    ', bg=color, end='')
    print()
    for color in ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white']:
        printc(f' light {color} ', bg=color, light=True, end='')
    print('\n8-bit 256-color')
    for color in range(0, 256):
        printc(f' {color} ', bg=color, end='')
    print('\n24-bit true color')
    for color in range(0, 77):
        printc(' ', bg=(255-(color*255//76), color*510//76 if color <
               38 else 510-color*510//76, color*255//76), end='')

跑起来!

源代码在这里

本文使用CC-BY-NC-ND进行许可

cil·ansi·python
113 views
Comments
登录后评论
Sign In