#include #include #include // For debugging, can be removed #include // For debugging, can be removed #include // For malloc, realloc, free #include // For strcmp, strncmp #include // For isalnum, isdigit, isalpha, isspace #include // For errno static inline size_t min( size_t a, size_t b ) { return ( b < a ? b : a ); } static inline size_t max( size_t a, size_t b ) { return ( b > a ? b : a ); } // const std = @import( "std" ); // // const p = std.debug.print; // const assert = std.debug.assert; // const testing = std.testing; // # ansi controls & escapes // https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape-sequences // https://gist.github.com/ConnerWill/d4b6c776b509add763e17f9f113fd25b // https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797 // "\r" move cursor to beginning of line // "\x1b" is the escape code '^[' // "\033" is the escape code '^[' // TODO NO_COLOR // https://no-color.org/ // XXX nocolor.org // 403 Forbidden // https://ziglang.org/documentation/master/std/#std.posix.getenv // 3 options // - writer api - writer_xyz( writer, ... ) !void // - comptime fmt - fmt_xyz( comptime ... ) !*const [_:0]u8 // - runtime alloc - alloc_xyz( allocator, ... ) ![:0]u8 // # clearing /// clear end of line const char CLEAR_EOL[] = "\x1b[0K"; // "\x1b[K" /// clear beginning of line const char CLEAR_BOL[] = "\x1b[1K"; /// clear line const char CLEAR_LINE[] = "\x1b[2K"; /// clear end of screen const char CLEAR_EOS[] = "\x1b[0J"; // "\x1b[J" /// clear beginning of screen const char CLEAR_BOS[] = "\x1b[1J"; /// clear screen const char CLEAR_SCREEN[] = "\x1b[2J"; // pub fn writer_clear_end_of_line ( writer :anytype ) void { try writer.print( "\x1b[K" , .{} ); } // cleareol EL0 Clear line from cursor right "^[[K" void clear_end_of_line ( FILE *f ) { fprintf( f, "\x1b[0K" ); } // cleareol EL0 Clear line from cursor right "^[[0K" void clear_begin_of_line ( FILE *f ) { fprintf( f, "\x1b[1K" ); } // clearbol EL1 Clear line from cursor left "^[[1K" void clear_line ( FILE *f ) { fprintf( f, "\x1b[2K" ); } // clearline EL2 Clear entire line "^[[2K" // pub fn writer_clear_end_of_screen ( writer :anytype ) void { try writer.print( "\x1b[J" , .{} ); } // cleareos ED0 Clear screen from cursor down "^[[J" void clear_end_of_screen ( FILE *f ) { fprintf( f, "\x1b[0J" ); } // cleareos ED0 Clear screen from cursor down "^[[0J" void clear_begin_of_screen ( FILE *f ) { fprintf( f, "\x1b[1J" ); } // clearbos ED1 Clear screen from cursor up "^[[1J" void clear_screen ( FILE *f ) { fprintf( f, "\x1b[2J" ); } // clearscreen ED2 Clear entire screen "^[[2J" // # cursor movement const char CURSOR_ONE_UP [] = "\x1b[1A"; const char CURSOR_ONE_DOWN [] = "\x1b[1B"; const char CURSOR_ONE_RIGHT [] = "\x1b[1C"; const char CURSOR_ONE_LEFT [] = "\x1b[1D"; // note: '1' can be changed to move more than one tile typedef enum { UP = 'A', DOWN = 'B', LEFT = 'D', RIGHT = 'C', } direction_t; /// direction format string /// steps(int) then direction(A,B,C,D) const char go_fmt[] = "\x1b[%d%c"; // decimal steps, character direction // printf( go_fmt, 10, RIGHT ); void fgo( FILE *f, direction_t direction, size_t distance ) { fprintf( f, go_fmt, (int)distance, direction ); } const char fmt_distance_direction[] = "\x1b[%d%c"; void f_move_cursor( FILE *f, char direction, size_t distance ) { fprintf( f, fmt_distance_direction, (int)distance, direction ); } // BK void f_move_cursor_up ( FILE *f, size_t distance ) { f_move_cursor( f, UP , distance ); } // BK void f_move_cursor_down ( FILE *f, size_t distance ) { f_move_cursor( f, DOWN , distance ); } // BK void f_move_cursor_right ( FILE *f, size_t distance ) { f_move_cursor( f, RIGHT , distance ); } // BK void f_move_cursor_left ( FILE *f, size_t distance ) { f_move_cursor( f, LEFT , distance ); } // # colors typedef enum { TRUECOLOR = 2, PALETTE = 5, // TODO investigate other values.. } color_t; const char color_clear[] = "\x1b[0m"; // # foreground and background // - FOREGROUND = 38, BACKGROUND = 48, typedef enum { FOREGROUND = 38, BACKGROUND = 48, // XXX why these values specifically ? } ground_t; // # palette const char fmt_palette[] = "\x1b[%d;5;%dm"; // ground_t; type=palette; value // - value: // - 0 <= basic < 16 // 0 = BLACK , RED , GREEN , ORANGE , // BLUE , PURPLE , CYAN , WHITE = 7 // 0 <= dark < 8 // 8 <= light < 16 // - 16 <= rgb < 232 // rgb: ( 0 <= val < 6 ) // value: ( 36*r + 6*g + b ) // - 232 <= monochrome < 256 // 232 dark-to-light 255 linear typedef enum { DARK_BLACK = 0, LIGHT_BLACK = 8, DARK_RED = 1, LIGHT_RED = 9, DARK_GREEN = 2, LIGHT_GREEN = 10, DARK_ORANGE = 3, LIGHT_ORANGE = 11, DARK_BLUE = 4, LIGHT_BLUE = 12, DARK_PURPLE = 5, LIGHT_PURPLE = 13, DARK_CYAN = 6, LIGHT_CYAN = 14, DARK_WHITE = 7, LIGHT_WHITE = 15, RGB_BLACK = 16, RGB_WHITE = 231, MONO_BLACK = 232, MONO_WHITE = 255 } palette_t; typedef enum { DARK = 0, LIGHT = 8 } shade_t; typedef enum { BLACK = 0, // light_black = 8, RED = 1, // light_red = 9, GREEN = 2, // light_green = 10, ORANGE = 3, // light_orange = 11, BLUE = 4, // light_blue = 12, PURPLE = 5, // light_purple = 13, CYAN = 6, // light_cyan = 14, WHITE = 7, // light_white = 15, } color_palette_t; unsigned char color_rgb( size_t r, size_t g, size_t b ) { assert( 0 <= r && r < 6 ); assert( 0 <= g && g < 6 ); assert( 0 <= b && b < 6 ); size_t rgb = RGB_BLACK + ( b + 6*g + 6*6*r ); assert( RGB_BLACK <= rgb && rgb <= RGB_WHITE ); return (unsigned char)rgb; } const char _fmt_g_rgb[] = "\x1b[{d};2;{d};{d};{d}m"; // .{ r, g, b } // pub const _fmt_palette_fg = "\x1b[38;5;{d}m"; // pub const _fmt_palette_bg = "\x1b[48;5;{d}m"; int truecolor_to_rgb( size_t r, size_t g, size_t b ) { return color_rgb( r * 6 / 256, g * 6 / 256, b * 6 / 256 ); } // # truecolor const char fmt_truecolor[] = "\x1b[%d;2;%d;%d;%dm"; // ground_t; r; g; b // # 0 <= r,g,b < 256 unsigned char shade( color_palette_t palette, shade_t shade ) { return palette + shade; } // BK const char _fmt_palette_fg[] = "\x1b[38;5;%dm"; // BK const char _fmt_palette_bg[] = "\x1b[48;5;%dm"; // BK // const char _fmt_palette_fg_bg[] = _fmt_palette_fg ++ _fmt_palette_bg; // BK const char _fmt_palette_fg_bg[] = "\x1b[38;5;%d;48;5;%dm"; unsigned char six_to_byte( size_t six ) { assert( 0 <= six && six < 6 ); return six * 51; } size_t byte_to_six( unsigned char byte ) { return byte / 51; } unsigned char color_monochrome( size_t white ) { assert( 0 <= white && white < 24 ); size_t val = MONO_BLACK + white; assert( MONO_BLACK <= val && val <= MONO_WHITE ); return (unsigned char)val; } // # printing colors // if( fg ) |c| p( "\x1b[38;5;{d}m", .{ c } ); // set foreground color // if( bg ) |c| p( "\x1b[48;5;{d}m", .{ c } ); // set background color // p( "{s}\x1b[0m", .{ text } ); // clear color formatting // BK void f_print_color_text( FILE *f, const char text[], fg :?u8, bg :?u8 ) !void { // BK if( fg ) |c| try writer_set_color( c, .foreground, writer ); // BK if( bg ) |c| try writer_set_color( c, .background, writer ); // BK try writer.print( "{s}", .{ text } ); // BK try writer_clear_color( writer ); // BK } // BK pub fn writer_set_foreground_color( writer :anytype, color :u8 ) !void { // BK try writer_set_color( writer, color, .foreground ); // BK } // BK pub fn writer_set_background_color( writer :anytype, color :u8 ) void { // BK try writer_set_color( writer, color, .background ); // BK } // BK const char _fmt_g_c[] = "\x1b[%d;5;%dm"; // BK void f_set_color( FILE *f, size_t color, ground_t ground ) { // BK fprintf( f, _fmt_g_c, (int)ground, (int)color ); // BK } // BK const char _fmt_clear_color[] = "\x1b[0m"; // see `color_clear` // BK void f_clear_color( FILE *f ) { // BK fprintf( f, _fmt_clear_color ); // BK } void f_color_clear( FILE *f ) { fprintf( f, color_clear ); } void f_color_palette( FILE *f, ground_t ground, unsigned char index ) { fprintf( f, fmt_palette, ground, index ); } void f_color_palette_rgb( FILE *f, ground_t ground, size_t r, size_t g, size_t b ) { f_color_palette( f, ground, color_rgb( r,g,b ) ); } void f_color_palette_mono( FILE *f, ground_t ground, size_t white ) { f_color_palette( f, ground, color_monochrome( white ) ); } void f_color_truecolor( FILE *f, ground_t ground, unsigned char r, unsigned char g, unsigned char b ) { fprintf( f, fmt_truecolor, ground, r, g, b ); } // ... TODO swap names of // writer_set_xxxxground_color() and // writer_set_xxxxground() // XXX the truecolor rgb style escape codes don't work... // \033[38;2;;;m #Select RGB foreground color // \033[48;2;;;m #Select RGB background color // BK pub fn writer_set_foreground( writer :anytype, r :u8, g :u8, b :u8 ) !void { // BK try writer.print( "\x1b[38;2;{d};{d};{d}m", .{ r, g, b } ); // BK } // BK pub fn writer_set_background( writer :anytype, r :u8, g :u8, b :u8 ) !void { // BK try writer.print( "\x1b[48;2;{d};{d};{d}m", .{ r, g, b } ); // BK } /// tests void test_direction() { // ^ // < > // v fprintf ( stdout, "cursor movement:\n" ); fprintf ( stdout, "\n\n\n" ); fgo ( stdout, UP , 3 ); fgo ( stdout, RIGHT , 2 ); fprintf ( stdout, "^" ); fgo ( stdout, LEFT , 2 ); fgo ( stdout, DOWN , 1 ); fprintf ( stdout, "<" ); fgo ( stdout, RIGHT , 1 ); fprintf ( stdout, ">" ); fgo ( stdout, LEFT, 2 ); fgo ( stdout, DOWN, 1 ), fprintf ( stdout, "v\n" ); } void test_color_basic() { printf( "\x1b[48;2;000;000;000m" ); printf( "\\x1b[48;2;000;000;000m // background truecolor black" ); printf( "\x1b[0m\n" ); printf( "\x1b[38;2;000;255;001m" ); printf( "\\x1b[38;2;000;255;001m // foreground truecolor green" ); printf( "\x1b[0m\n" ); printf( "\\x1b[0m // clear color\n" ); } void test_color_palette_indexed() { fprintf( stdout, "palette:\n" ); fprintf( stdout, "\x1b[30m" " 30 " "\x1b[0;40m" " 40 " "\x1b[0;%d;5;%d;%d;5;%dm" " %s " "\x1b[%d;5;%d;%d;5;%dm" " = %d " "\x1b[0m" "\n", FOREGROUND, LIGHT_BLACK , BACKGROUND, DARK_BLACK, "BLACK ", FOREGROUND, DARK_BLACK , BACKGROUND, LIGHT_WHITE, BLACK ); fprintf( stdout, "\x1b[31m" " 31 " "\x1b[0;41m" " 41 " "\x1b[0;%d;5;%d;%d;5;%dm" " %s " "\x1b[%d;5;%d;%d;5;%dm" " = %d " "\x1b[0m" "\n", FOREGROUND, LIGHT_RED , BACKGROUND, DARK_BLACK, "RED ", FOREGROUND, DARK_RED , BACKGROUND, LIGHT_WHITE, RED ); fprintf( stdout, "\x1b[32m" " 32 " "\x1b[0;42m" " 42 " "\x1b[0;%d;5;%d;%d;5;%dm" " %s " "\x1b[%d;5;%d;%d;5;%dm" " = %d " "\x1b[0m" "\n", FOREGROUND, LIGHT_GREEN , BACKGROUND, DARK_BLACK, "GREEN ", FOREGROUND, DARK_GREEN , BACKGROUND, LIGHT_WHITE, GREEN ); fprintf( stdout, "\x1b[33m" " 33 " "\x1b[0;43m" " 43 " "\x1b[0;%d;5;%d;%d;5;%dm" " %s " "\x1b[%d;5;%d;%d;5;%dm" " = %d " "\x1b[0m" "\n", FOREGROUND, LIGHT_ORANGE , BACKGROUND, DARK_BLACK, "ORANGE ", FOREGROUND, DARK_ORANGE , BACKGROUND, LIGHT_WHITE, ORANGE ); fprintf( stdout, "\x1b[34m" " 34 " "\x1b[0;44m" " 44 " "\x1b[0;%d;5;%d;%d;5;%dm" " %s " "\x1b[%d;5;%d;%d;5;%dm" " = %d " "\x1b[0m" "\n", FOREGROUND, LIGHT_BLUE , BACKGROUND, DARK_BLACK, "BLUE ", FOREGROUND, DARK_BLUE , BACKGROUND, LIGHT_WHITE, BLUE ); fprintf( stdout, "\x1b[35m" " 35 " "\x1b[0;45m" " 45 " "\x1b[0;%d;5;%d;%d;5;%dm" " %s " "\x1b[%d;5;%d;%d;5;%dm" " = %d " "\x1b[0m" "\n", FOREGROUND, LIGHT_PURPLE , BACKGROUND, DARK_BLACK, "PURPLE ", FOREGROUND, DARK_PURPLE , BACKGROUND, LIGHT_WHITE, PURPLE ); fprintf( stdout, "\x1b[36m" " 36 " "\x1b[0;46m" " 46 " "\x1b[0;%d;5;%d;%d;5;%dm" " %s " "\x1b[%d;5;%d;%d;5;%dm" " = %d " "\x1b[0m" "\n", FOREGROUND, LIGHT_CYAN , BACKGROUND, DARK_BLACK, "CYAN ", FOREGROUND, DARK_CYAN , BACKGROUND, LIGHT_WHITE, CYAN ); fprintf( stdout, "\x1b[37m" " 37 " "\x1b[0;47m" " 47 " "\x1b[0;%d;5;%d;%d;5;%dm" " %s " "\x1b[%d;5;%d;%d;5;%dm" " = %d " "\x1b[0m" "\n", FOREGROUND, LIGHT_WHITE , BACKGROUND, DARK_BLACK, "WHITE ", FOREGROUND, DARK_WHITE , BACKGROUND, LIGHT_WHITE, WHITE ); const char *color_names[] = { "BLACK ", "RED ", "GREEN ", "ORANGE ", "BLUE ", "PURPLE ", "CYAN ", "WHITE " }; for( int i = 0; i < 8; i++ ) { fprintf( stdout, "\x1b[0;%02.dm" " %02.d " "\x1b[0;%02.dm" " %02.d " "\x1b[0;38;5;%dm" " dark " "\x1b[0m" "/" "\x1b[0;38;5;%dm" " light " "\x1b[0;38;5;%d;48;5;%dm" " dark-on-light " "\x1b[0;38;5;%d;48;5;%dm" " light-on-dark " "\x1b[0;38;5;%dm" " %02.d = %s " "\x1b[0;38;5;15;48;5;%dm" " dark bg " "\x1b[0;38;5;7;48;5;%dm" " dark bg " "\x1b[0;38;5;8;48;5;%dm" " light bg " "\x1b[0;38;5;00;48;5;%dm" " light bg " "\x1b[0;38;5;%d;48;5;15m" " dark fg " "\x1b[0;38;5;%d;48;5;7m" " dark fg " "\x1b[0;38;5;%d;48;5;8m" " light fg " "\x1b[0;38;5;%d;48;5;00m" " light fg " "\x1b[0m\n", 30 + i, 8 + i, 40 + i, 0 + i, 0 + i, 8 + i, 0 + i, 8 + i, 8 + i, 0 + i, 8 + i, 6 + i, color_names[i], 0 + i, 0 + i, 8 + i, 8 + i, 0 + i, 0 + i, 8 + i, 8 + i ); } } void test_color_palette_math() { // misc, ansi terminal escape codes const char esc = 27; assert( esc == "\x1b"[0] ); // black, white, blue, red assert( 0 == BLACK + DARK ); assert( 15 == WHITE + LIGHT ); assert( 4 == BLUE + DARK ); assert( 9 == RED + LIGHT ); // black, white, dull-blueish-green assert( 16 == color_rgb( 0, 0, 0 ) ); assert( 231 == color_rgb( 5, 5, 5 ) ); assert( 110 == color_rgb( 2, 3, 4 ) ); // black, white, grey assert( 232 == color_monochrome( 0 ) ); assert( 255 == color_monochrome( 23 ) ); assert( 242 == color_monochrome( 10 ) ); } void test_color_palette_rgb() { fprintf( stdout, "rgb:\n" ); for( int i = 0; i < 6; i += 2 ) { for( int j = 0; j < 6; j += 2 ) { for( int k = 0; k < 6; k += 2 ) { fprintf( stdout, "\x1b[%d;5;%d;%d;5;%dm" " palette " "\x1b[%d;2;%d;%d;%d;%d;2;%d;%d;%dm" " r=%d,g=%d,b=%d " "\x1b[0m" " and " "\x1b[%d;5;%d;%d;5;%dm" " palette " "\x1b[%d;2;%d;%d;%d;%d;2;%d;%d;%dm" " r=%d,g=%d,b=%d " "\x1b[0m" "\n", FOREGROUND, color_rgb( 5-i , 5-j , 5-k ), BACKGROUND, color_rgb( i, j, k ), FOREGROUND, 51*( 5-i ),51*( 5-j ),51*( 5-k ) , BACKGROUND, 51*i,51*j,51*k , 5-i , 5-j , 5-k , FOREGROUND, color_rgb( (i+3)%6 , (j+3)%6 , (k+3)%6 ), BACKGROUND, color_rgb( i, j, k ), FOREGROUND, 51*((i+3)%6),51*((j+3)%6),51*((k+3)%6) , BACKGROUND, 51*i,51*j,51*k , (i+3)%6 , (j+3)%6 , (k+3)%6 ); int x = i + 1; int y = j + 1; int z = k + 1; fprintf( stdout, "\x1b[%d;5;%d;%d;5;%dm" " palette " "\x1b[%d;2;%d;%d;%d;%d;2;%d;%d;%dm" " r=%d,g=%d,b=%d " "\x1b[0m" " and " "\x1b[%d;5;%d;%d;5;%dm" " palette " "\x1b[%d;2;%d;%d;%d;%d;2;%d;%d;%dm" " r=%d,g=%d,b=%d " "\x1b[0m" "\n", FOREGROUND, color_rgb( 5-x , 5-y , 5-z ), BACKGROUND, color_rgb( x, y, z ), FOREGROUND, 51*( 5-x ),51*( 5-y ),51*( 5-z ) , BACKGROUND, 51*x,51*y,51*z , 5-x , 5-y , 5-z , FOREGROUND, color_rgb( (x+3)%6 , (y+3)%6 , (z+3)%6 ), BACKGROUND, color_rgb( x, y, z ), FOREGROUND, 51*((x+3)%6),51*((y+3)%6),51*((z+3)%6) , BACKGROUND, 51*x,51*y,51*z , (x+3)%6 , (y+3)%6 , (z+3)%6 ); } } } } void test_color_palette_monochrome() { fprintf( stdout, "monochrome:\n" ); for( int i = 0; i < 24; i++ ) { fprintf( stdout, "\x1b[%d;5;%d;%d;5;%dm" " white = %02.d " "\x1b[0m" "\n", FOREGROUND, MONO_BLACK + i, BACKGROUND, ( i < 12 ? MONO_WHITE : MONO_BLACK ), i ); } } void print_truecolor_and_palette( FILE *f, ground_t bg, int r, int g, int b ) { fprintf( stdout, "\x1b[%d;2;%d;%d;%d;%d;2;%d;%d;%dm" " r=%0.3d,g=%0.3d,b=%0.3d " "\x1b[%d;5;%d;%d;5;%dm" " palette " "\x1b[0m", FOREGROUND, r,g,b , BACKGROUND, bg,bg,bg , r,g,b, FOREGROUND, truecolor_to_rgb( r,g,b ), BACKGROUND, truecolor_to_rgb( bg,bg,bg ) ); } void test_truecolor() { // demonstrate that truecolor is more fine-grained than palette fprintf( stdout, "truecolor:\n" ); for( int ijk = 0; ijk < 3; ijk++ ) { for( int x = 0; x < 16; x++ ) { { int i = ( ijk==0 ? 1 : 0 ) * x * 17; int j = ( ijk==1 ? 1 : 0 ) * x * 17; int k = ( ijk==2 ? 1 : 0 ) * x * 17; print_truecolor_and_palette( stdout, 255, i,j,k ); } { int i = ( ijk==0 ? 15 : x ) * 17; int j = ( ijk==1 ? 15 : x ) * 17; int k = ( ijk==2 ? 15 : x ) * 17; print_truecolor_and_palette( stdout, 0, i,j,k ); } fprintf( stdout, "\n" ); } } } void test_truecolor_rebeccapurple() { // rebeccapurple // #663399 size_t red = 0x66; size_t green = 0x33; size_t blue = 0x99; fprintf( stdout, "rebeccapurple:\n" ); // palette rgb fprintf( stdout, "\x1b[48;5;%dm" " palette style, 16 <= _ < 232 %s\n", truecolor_to_rgb( red, green, blue ), color_clear ); // truecolor manual fprintf( stdout, "\x1b[48;2;%d;%d;%dm" " truecolor style, 0 <= _ < 16777216 %s\n", (int)red, (int)green, (int)blue, color_clear ); f_color_truecolor( stdout, BACKGROUND, red, green, blue ); f_color_palette( stdout, FOREGROUND, LIGHT_GREEN ); fprintf( stdout, " rebecca purple " ); f_color_clear( stdout ); fprintf( stdout, "\n" ); } // cc -DTEST waterbow.c && ./a.out int #ifdef TEST main #else test #endif () { test_direction(); test_color_basic(); test_color_palette_indexed(); test_color_palette_math(); test_color_palette_rgb(); test_color_palette_monochrome(); test_truecolor(); test_truecolor_rebeccapurple(); return 0; }