50% colors i'll do rainbow and whatever else i'm missing from the rust version later too lseepy rn
ci / unix (macos-15) (push) Canceled after 0s
ci / unix (macos-15-intel) (push) Canceled after 0s
ci / unix (ubuntu-24.04) (push) Canceled after 0s
ci / unix (ubuntu-24.04-arm) (push) Canceled after 0s
ci / windows (push) Canceled after 0s
ci / freebsd (push) Canceled after 0s

This commit is contained in:
david committed 2026-10-13 07:43:48 -03:00
1 parent 3b16b2141e
commit f0f954d463
7 files changed
+697 -28

No files matched your search

+1 -1
View File
@@ -1,5 +1,5 @@
AC_PREREQ([2.69])
AC_INIT([mikusays], [0.2.0], [], [mikusays], [https://github.com/davideldios7/mikusays])
AC_INIT([mikusays], [0.3.0], [], [mikusays], [https://github.com/davideldios7/mikusays])
AC_CONFIG_SRCDIR([src/main.c])
AC_CONFIG_AUX_DIR([build-aux])
AM_INIT_AUTOMAKE([foreign subdir-objects -Wall])
+1
View File
@@ -7,6 +7,7 @@
#include <string.h>
#include <locale.h>
#include <stdbool.h>
#include <ctype.h>
#include "helper.h"
+27 -6
View File
@@ -1,7 +1,7 @@
#ifndef COLORS_H
#define COLORS_H
#include <stdlib.h>
#include <stddef.h>
#include <stdbool.h>
typedef enum color_mode {
@@ -31,9 +31,9 @@ typedef enum NamedColor {
}NamedColor;
typedef struct{
int red;
int green;
int blue;
unsigned char red;
unsigned char green;
unsigned char blue;
}rgbcolor;
typedef struct colorconfig {
@@ -43,8 +43,29 @@ typedef struct colorconfig {
bool rainbow_enabled; //flag
int saturation; //default 100
int brightness; //default 50
char **rainbow_cache; //array of rgb_color malloced
rgbcolor *rainbow_cache; //array of rgbcolor malloced
size_t rainbow_count;
}colorconfig;
#endif
typedef enum ColorParseError {
COLOR_OK,
COLOR_ERR_FORMAT,
COLOR_ERR_HEX,
COLOR_ERR_RGB,
COLOR_ERR_HSL,
COLOR_ERR_UNKNOWN_NAME
}ColorParseError;
void colorconfig_init(colorconfig *config);
enum color_mode detect_color_mode(void);
bool enable_windows_ansi(void);
int parse_namedcolors(const char *name, rgbcolor *out_rgb);
ColorParseError parse_color(const char *s, rgbcolor *out);
const char *color_parse_error_message(ColorParseError error);
unsigned char rgb_to_ansi16(rgbcolor rgb);
unsigned char rgb_to_ansi256(rgbcolor rgb);
rgbcolor hsl_to_rgb(unsigned short h, unsigned char s, unsigned char l);
rgbcolor lerp_color_hsv(rgbcolor start, rgbcolor end, float t);
void rgb_to_fg_escape(rgbcolor rgb, color_mode mode, char *out, size_t size);
#endif
+1
View File
@@ -2,5 +2,6 @@
#define helper_h
int displaywidth(const char *text);
int utf8_decode(const unsigned char *bytes, unsigned long *codepoint);
#endif
+550 -17
View File
@@ -1,19 +1,76 @@
#include "../defs/all.h"
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <ctype.h>
void colorconfig_init(colorconfig config){
config.saturation = 100;
config.brightness = 50;
#ifdef _WIN32
#include <windows.h>
#endif
//try to enable ansi (virtual terminal processing) on windows consoles.
//returns true if enabeling succeded or isn't necesary, false otherwise.
bool enable_windows_ansi(void){
#ifdef _WIN32
#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004
#endif
HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD mode = 0;
if(out == INVALID_HANDLE_VALUE)
return false;
if(GetConsoleMode(out, &mode) == 0)
return false;
if(SetConsoleMode(out, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0)
return false;
#endif
return true;
}
#ifdef _WIN32
//returns true if str ends with suffix, ignorning case
static bool ends_with_ignore_case(const char *str, const char *suffix){
size_t str_len = strlen(str);
size_t suffix_len = strlen(suffix);
size_t i;
if(suffix_len > str_len)
return false;
str += str_len - suffix_len;
for(i = 0; i < suffix_len; i++){
if(tolower((unsigned char)str[i]) != tolower((unsigned char)suffix[i]))
return false;
}
return true;
}
//detect the parent shell name on windows, using enviroment hints.
//returns null if nothing matched
static const char *detect_windows_shell(void){
const char *comspec;
//heuristic checks using common env vars
if(getenv("PSModulePath"))
return "powershell";
if(getenv("PWshPROFILE") || getenv("PWSH_VERSION"))
return "pwsh";
comspec = getenv("COMSPEC");
if(comspec && ends_with_ignore_case(comspec, "cmd.exe"))
return "cmd.exe";
if(getenv("WT_SESSION"))
return "windows_terminal";
return NULL;
}
#endif
enum color_mode detect_color_mode(void){
const char *colorterm, *term;
enum color_mode mode = MODE_ANSI16;
if(getenv("NO_COLOR"))
return MODE_NONE;
//early checks
colorterm = getenv("COLORTERM");
if(colorterm && (strstr(colorterm, "truecolor") || strstr(colorterm, "24bit")))
return MODE_TRUECOLOR;
@@ -21,23 +78,499 @@ enum color_mode detect_color_mode(void){
term = getenv("TERM");
if(term && strstr(term, "256color"))
return MODE_ANSI256;
if(term && strstr(term, "color"))
return MODE_ANSI16;
return MODE_ANSI16;
}
//default fallback is mode_ansi16 (set above)
#ifdef _WIN32
//i'll do this later
//if running on windows, try to enable ansi (virtual terminal processing)
if(enable_windows_ansi()){
//re-check for a better mode
if(colorterm && (strstr(colorterm, "truecolor") || strstr(colorterm, "24bit")))
mode = MODE_TRUECOLOR;
if(term && strstr(term, "256color"))
mode = MODE_ANSI256;
}else{
const char *shell = detect_windows_shell();
if(shell){
if(strstr(shell, "cmd.exe")){
//cmd historically doesn't support ansi; use 16 colors fallback
mode = MODE_ANSI16;
}else if(strstr(shell, "powershell") || strstr(shell, "pwsh")){
if(term && strstr(term, "256color"))
mode = MODE_ANSI256;
}else{
mode = MODE_ANSI16;
}
}
}
#endif
char parse_namedcolors(rgbcolor name, char *outrgb){
/*
table = array of { name_string, rgb } for the 16 colors
normalized = lowercase copy of name, with "_" removed // so that it handles bright_red and brightred
for each entry in the table:
if normalized equals entry.name -> out_rgb = entry.rgb, return success
return fail
*/
return mode;
}
void colorconfig_init(colorconfig *config){
memset(config, 0, sizeof(*config));
config->saturation = 100;
config->brightness = 50;
config->mode = detect_color_mode();
}
unsigned char rgb_to_ansi16(rgbcolor rgb){
unsigned char r = rgb.red;
unsigned char g = rgb.green;
unsigned char b = rgb.blue;
unsigned char brightness = (unsigned char)((r * 299 + g * 587 + b * 114 + 500) / 1000); //avoid using math.h because i don't want to use it
int is_bright = brightness > 128;
//determine base color using simple thresholding on each channel, producing an index 0..7
int r_on = r > 128;
int g_on = g > 128;
int b_on = b > 128;
unsigned char base_idx = (unsigned char)r_on + (unsigned char)g_on * 2 + (unsigned char)b_on * 4; // 0..7
//sgr color codes: 30..37 for normal, 90..97 for bright
if(is_bright)
return (unsigned char)(90 + base_idx);
return (unsigned char)(30 + base_idx);
}
//round half away from zero for positive values only
static int round_pos(float x){
return (int)(x + 0.5f);
}
unsigned char rgb_to_ansi256(rgbcolor rgb){
unsigned char r = rgb.red;
unsigned char g = rgb.green;
unsigned char b = rgb.blue;
if(r == g && g == b){
//calulate the grayscale value
unsigned char gray = (unsigned char)round_pos(r * 0.299f + g * 0.587f + b * 0.114f);
//ramp (232-255)
if(gray <= 8)
return 16;
if(gray >= 248)
return 231;
return (unsigned char)(round_pos(((float)gray - 8.0f) / 247.0f * 24.0f) + 232);
}
//color cube (16-231)
{
int ri = round_pos(r / 255.0f * 5.0f);
int gi = round_pos(g / 255.0f * 5.0f);
int bi = round_pos(b / 255.0f * 5.0f);
return (unsigned char)(16 + ri * 36 + gi * 6 + bi);
}
}
//convert ansi color code to rgb
static rgbcolor ansi_to_rgb(unsigned char code){
//standard 16 colors
static const rgbcolor standard[16] = {
{0, 0, 0}, //black
{128, 0, 0}, //red
{0, 128, 0}, //green
{128, 128, 0}, //yellow
{0, 0, 128}, //blue
{128, 0, 128}, // magenta
{0, 128, 128}, //cyan
{192, 192, 192}, //white
{128, 128, 128}, //bright blakc whatever that is
{255, 0, 0}, //bright red
{0, 255, 0}, //brightgreen
{255, 255, 0},//bright yellow
{0, 0, 255}, //bright blue
{255, 0, 255}, //bright magenta
{0, 255, 255}, //bright cyan
{255, 255, 255}, //Brightwhite
};
rgbcolor out;
if(code < 16)
return standard[code];
//256-color mode
if(code <= 231){
//color cube
int cube = code - 16;
out.red = (unsigned char)(((cube / 36) % 6) * 51);
out.green = (unsigned char)(((cube / 6) % 6) * 51);
out.blue = (unsigned char)((cube % 6) * 51);
return out;
}
//grayscale ramp
out.red = out.green = out.blue = (unsigned char)((code - 232) * 10 + 8);
return out;
}
//helper for hsl_to_rgb
static float hue_to_rgb(float p, float q, float t){
if(t < 0.0f)
t += 1.0f;
if(t > 1.0f)
t -= 1.0f;
if(t < 1.0f / 6.0f)
return p + (q - p) * 6.0f * t;
if(t < 1.0f / 2.0f)
return q;
if(t < 2.0f / 3.0f)
return p + (q - p) * (2.0f / 3.0f - t) * 6.0f;
return p;
}
//convert hsl to rgb
//h: 0-360, s: 0-100, l: 0-100
rgbcolor hsl_to_rgb(unsigned short h, unsigned char s, unsigned char l){
float hue = (float)h / 360.0f;
float sat = (float)s / 100.0f;
float lig = (float)l / 100.0f;
float r, g, b;
rgbcolor out;
if(sat == 0.0f){
r = g = b = lig;
}else{
float q = lig < 0.5f ? lig * (1.0f + sat) : lig + sat - lig * sat;
float p = 2.0f * lig - q;
r = hue_to_rgb(p, q, hue + 1.0f / 3.0f);
g = hue_to_rgb(p, q, hue);
b = hue_to_rgb(p, q, hue - 1.0f / 3.0f);
}
out.red = (unsigned char)round_pos(r * 255.0f);
out.green = (unsigned char)round_pos(g * 255.0f);
out.blue = (unsigned char)round_pos(b * 255.0f);
return out;
}
//convert color to ansi escape code for foreground
//out gets an empty string for mode_none
void rgb_to_fg_escape(rgbcolor rgb, color_mode mode, char *out, size_t size){
switch(mode){
case MODE_TRUECOLOR:
snprintf(out, size, "\x1b[38;2;%d;%d;%dm", rgb.red, rgb.green, rgb.blue);
break;
case MODE_ANSI256:
snprintf(out, size, "\x1b[38;5;%dm", rgb_to_ansi256(rgb));
break;
case MODE_ANSI16:
snprintf(out, size, "\x1b[%dm", rgb_to_ansi16(rgb));
break;
case MODE_NONE:
default:
if(size > 0)
out[0] = '\0';
break;
}
}
static float float_mod(float a, float b){
return a - b * (float)(long)(a / b);
}
static float float_abs(float x){
return x < 0.0f ? -x : x;
}
//converts rgb to hsv
static void rgb_to_hsv(rgbcolor rgb, float *h_out, float *s_out, float *v_out){
float r = rgb.red / 255.0f;
float g = rgb.green / 255.0f;
float b = rgb.blue / 255.0f;
float max = r > g ? r : g;
float min = r < g ? r : g;
float delta, h;
if(b > max) max = b;
if(b < min) min = b;
delta = max - min;
if(delta == 0.0f)
h = 0.0f;
else if(max == r)
h = 60.0f * float_mod((g - b) / delta, 6.0f);
else if(max == g)
h = 60.0f * ((b - r) / delta + 2.0f);
else
h = 60.0f * ((r - g) / delta + 4.0f);
if(h < 0.0f)
h += 360.0f;
*h_out = h;
*s_out = max == 0.0f ? 0.0f : delta / max;
*v_out = max;
}
//convert hsv to rgb
static rgbcolor hsv_to_rgb(float h, float s, float v){
float c = v * s;
float x = c * (1.0f - float_abs(float_mod(h / 60.0f, 2.0f) - 1.0f));
float m = v - c;
float r, g, b;
rgbcolor out;
if(h >= 0.0f && h < 60.0f)
{r = c; g = x; b = 0.0f;}
else if(h >= 60.0f && h < 120.0f)
{r = x; g = c; b = 0.0f;}
else if(h >= 120.0f && h < 180.0f)
{r = 0.0f; g = c;b = x;}
else if(h >= 180.0f && h < 240.0f)
{r = 0.0f; g = x; b = c;}
else if(h >= 240.0f && h < 300.0f)
{r = x; g = 0.0f; b = c; }
else {r = c; g = 0.0f; b = x;}
out.red = (unsigned char)round_pos((r + m) * 255.0f);
out.green = (unsigned char)round_pos((g + m) * 255.0f);
out.blue = (unsigned char)round_pos((b + m) * 255.0f);
return out;
}
//linear interpolation between two colors in hsv space
rgbcolor lerp_color_hsv(rgbcolor start, rgbcolor end, float t){
float h1, s1, v1, h2, s2, v2;
float h_diff, h, s, v;
rgb_to_hsv(start, &h1, &s1, &v1);
rgb_to_hsv(end, &h2, &s2, &v2);
//handle hue interpolation correctly (shortest path around the color wheel)
h_diff = float_mod(h2 - h1 + 180.0f, 360.0f) - 180.0f;
h = float_mod(h1 + h_diff * t + 360.0f, 360.0f);
s = s1 + (s2 - s1) * t;
v = v1 + (v2 - v1) * t;
return hsv_to_rgb(h, s, v);
}
//parsing helpers
//returns the numeric value of a hex digit,, or -1
static int hex_value(char c){
if(c >= '0' && c <= '9') return c - '0';
if(c >= 'a' && c <= 'f') return c - 'a' + 10;
if(c >= 'A' && c <= 'F') return c - 'A' + 10;
return -1;
}
//skip spaces at both ends of [*start, *end)
static void trim_range(const char **start, const char **end){
while(*start < *end && isspace((unsigned char)**start))
(*start)++;
while(*end > *start && isspace((unsigned char)(*end)[-1]))
(*end)--;
}
//parse [start, end) as a decimal number from 0 to max. 1 good 0 bad
static int parse_uint(const char *start, const char *end, unsigned long max, unsigned long *out){
unsigned long value = 0;
if(start >= end)
return 0;
for(; start < end; start++){
if(!isdigit((unsigned char)*start))
return 0;
value = value * 10 + (unsigned long)(*start - '0');
if(value > max)
return 0;
}
*out = value;
return 1;
}
//returns 1 if str starts with prefix ignoring case
static int starts_with_ignore_case(const char *str, const char *prefix){
for(; *prefix != '\0'; str++, prefix++){
if(*str == '\0' || tolower((unsigned char)*str) != tolower((unsigned char)*prefix))
return 0;
}
return 1;
}
static ColorParseError parse_hex_color(const char *s, rgbcolor *out){
const char *hex = s;
int value[6];
size_t length, i;
while(*hex == '#')
hex++;
length = strlen(hex);
if(length != 3 && length != 6)
return COLOR_ERR_HEX;
for(i = 0; i < length; i++){
value[i] = hex_value(hex[i]);
if(value[i] < 0)
return COLOR_ERR_HEX;
}
if(length == 3){
//#rgb format
out->red = (unsigned char)(value[0] * 17);
out->green = (unsigned char)(value[1] * 17);
out->blue = (unsigned char)(value[2] * 17);
}else{
//#rrggbb format
out->red = (unsigned char)(value[0] * 16 + value[1]);
out->green = (unsigned char)(value[2] * 16 + value[3]);
out->blue = (unsigned char)(value[4] * 16 + value[5]);
}
return COLOR_OK;
}
//splits [start, end) on exactly two commas into three trimmed ranges. 1 good 0 bad
static int split_three(const char *start, const char *end, const char *range[3][2]){
const char *comma1 = NULL, *comma2 = NULL, *p;
int commas = 0;
for(p = start; p < end; p++){
if(*p != ',')
continue;
commas++;
if(commas == 1) comma1 = p;
else if(commas == 2) comma2 = p;
}
if(commas != 2)
return 0;
range[0][0] = start; range[0][1] = comma1;
range[1][0] = comma1 + 1; range[1][1] = comma2;
range[2][0] = comma2 + 1; range[2][1] = end;
for(commas = 0; commas < 3; commas++){
trim_range(&range[commas][0], &range[commas][1]);
}
return 1;
}
static ColorParseError parse_rgb_color(const char *s, rgbcolor *out){
const char *range[3][2];
unsigned long r, g, b;
if(!split_three(s, s + strlen(s), range))
return COLOR_ERR_RGB;
if(!parse_uint(range[0][0], range[0][1], 255, &r)|| !parse_uint(range[1][0], range[1][1], 255, &g)|| !parse_uint(range[2][0], range[2][1], 255, &b))
return COLOR_ERR_RGB;
out->red = (unsigned char)r;
out->green = (unsigned char)g;
out->blue = (unsigned char)b;
return COLOR_OK;
}
static ColorParseError parse_hsl_color(const char *s, rgbcolor *out){
const char *start = s;
const char *end = s + strlen(s);
const char *range[3][2];
unsigned long h, sat, l;
//strip the "hsl(" prefix and the ")" suffix
while(strncmp(start, "hsl(", 4) == 0)
start += 4;
while(end > start && end[-1] == ')')
end--;
if(!split_three(start, end, range))
return COLOR_ERR_HSL;
//hue can have a trailing unit like "180deg", so drop trailing non-digits
while(range[0][1] > range[0][0] && !isdigit((unsigned char)range[0][1][-1]))
range[0][1]--;
//saturation and lightness can have trailing '%'
while(range[1][1] > range[1][0] && range[1][1][-1] == '%')
range[1][1]--;
while(range[2][1] > range[2][0] && range[2][1][-1] == '%')
range[2][1]--;
if(!parse_uint(range[0][0], range[0][1], 65535, &h)
|| !parse_uint(range[1][0], range[1][1], 255, &sat)
|| !parse_uint(range[2][0], range[2][1], 255, &l))
return COLOR_ERR_HSL;
if(h > 360 || sat > 100 || l > 100)
return COLOR_ERR_HSL;
*out = hsl_to_rgb((unsigned short)h, (unsigned char)sat, (unsigned char)l);
return COLOR_OK;
}
static int parse_named_color(const char *s, rgbcolor *out){
static const struct { const char *name; rgbcolor rgb; } table[] = {
{"black", {0,0,0}}, {"red", {197,15,31}}, {"green", {19,161,14}},
{"yellow", {193,156,0}}, {"blue", {0,55,218}}, {"magenta", {136,23,152}},
{"cyan", {58,150,221}}, {"white", {204,204,204}},
{"brightblack", {118,118,118}}, {"brightred", {231,72,86}},
{"brightgreen", {22,198,12}}, {"brightyellow", {249,241,165}},
{"brightblue", {59,120,255}}, {"brightmagenta", {180,0,158}},
{"brightcyan", {97,214,214}}, {"brightwhite", {242,242,242}},
};
char norm[32];
size_t n = 0, i;
for(; *s && n < sizeof(norm) - 1; s++) //lowercase, drop '_'
if(*s != '_') norm[n++] = (char)tolower((unsigned char)*s);
norm[n] = '\0';
for(i = 0; i < sizeof(table) / sizeof(table[0]); i++){
if(strcmp(norm, table[i].name) == 0){
*out = table[i].rgb;
return 1;
}
}
return 0;
}
//parse any supported color format into rgb:
//#rrggbb, #rgb, hsl(h,s%,l%), r,g,b or an ansi code 0-255
ColorParseError parse_color(const char *s, rgbcolor *out){
if(parse_named_color(s, out)) return COLOR_OK;
size_t length;
unsigned long code;
//try hex color (#rrggbb or #rgb)
if(s[0] == '#')
return parse_hex_color(s, out);
//try hsl (hsl(180,100%,50%))
length = strlen(s);
if(starts_with_ignore_case(s, "hsl(") && length > 0 && s[length - 1] == ')')
return parse_hsl_color(s, out);
//try rgb tuple (255,0,128)
if(strchr(s, ','))
return parse_rgb_color(s, out);
//try ansi code (number between 0-255)
if(parse_uint(s, s + length, 255, &code)){
*out = ansi_to_rgb((unsigned char)code);
return COLOR_OK;
}
return COLOR_ERR_FORMAT;
}
const char *color_parse_error_message(ColorParseError error){
switch(error){
case COLOR_ERR_FORMAT:
return "Invaliid color format";
case COLOR_ERR_HEX:
return "Invaliid hex color code";
case COLOR_ERR_RGB:
return "Invaliid RGB format. Expected format: R,G,B";
case COLOR_ERR_HSL:
return "Invaliid HSL format. Expected format: hsl(H,S%,L%)";
case COLOR_ERR_UNKNOWN_NAME:
return "Unkown named color";
case COLOR_OK:
default:
return "";
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
/* this reads one utf81 character from the start of bytes
it puts the character's number in *codepoint and returns how many bytes it used (its always 1 or more)
invalid input it stores U+FFFD (�) and returns 1, so the caller always moves forward. */
static int utf8_decode(const unsigned char *bytes, unsigned long *codepoint){
int utf8_decode(const unsigned char *bytes, unsigned long *codepoint){
unsigned long value = bytes[0];
unsigned long minallowed = 0; //rejects overlong encodings
int sequence_length;
+116 -3
View File
@@ -160,7 +160,12 @@ static void print_help(void){
" -s, --style <STYLE> Style of the Miku art. A random one is chosen if not specified\n"
" -l, --list List all available art styles with their indices\n"
" -h, --help Print help\n"
" -V, --version Print version\n");
" -V, --version Print version\n"
" --rainbow Apply a smooth rainbow gradient across the art\n"
" --saturation <0-100> Rainbow saturation (default: 100)\n"
" --brightness <0-100> Rainbow brightness (default: 50)\n"
" --color <COLOR> Set a single solid color\n"
" --no-color Disable all coloring\n");
}
static void list_all_art_styles(void){
@@ -177,6 +182,61 @@ static void list_all_art_styles(void){
printf("\nUse --style <number> to select a specific style, or omit for random selection.\n");
}
static struct colorconfig colors;
static int rainbow = 0, has_solid = 0;
static rgbcolor solid;
static int is_art_space(unsigned long codepoint){
return (codepoint >= 0x09 && codepoint <= 0x0D) || codepoint == 0x20 || codepoint == 0x85 || codepoint == 0xA0
|| codepoint == 0x1680 || (codepoint >= 0x2000 && codepoint <= 0x200A) || codepoint == 0x2028
|| codepoint == 0x2029 || codepoint == 0x202F || codepoint == 0x205F || codepoint == 0x3000;
}
static size_t count_colored_chars(char **art){
size_t total = 0;
for(int i = 0; art[i] != NULL; i++){
const unsigned char *p = (const unsigned char *)art[i];
while(*p){
unsigned long codepoint;
p += utf8_decode(p, &codepoint);
if(!is_art_space(codepoint)) total++;
}
}
return total;
}
static void print_art_line(const char *line, size_t *index, size_t total){
char esc[32];
const unsigned char *p = (const unsigned char *)line;
if(colors.mode == MODE_NONE || (!rainbow && !has_solid)){
fputs(line, stdout);
return;
}
if(!rainbow){//solid: one color for the whole line
rgb_to_fg_escape(solid, colors.mode, esc, sizeof(esc));
printf("%s%s\x1b[0m", esc, line);
return;
}
while(*p){//rainbow: one color per non-space char
unsigned long codepoint;
int len = utf8_decode(p, &codepoint);
if(is_art_space(codepoint)){
fwrite(p, 1, (size_t)len, stdout);
}else{
unsigned short hue = (unsigned short)((float)*index / (float)total * 360.0f + 0.5f);
rgbcolor c = hsl_to_rgb(hue, colors.saturation, colors.brightness);
rgb_to_fg_escape(c, colors.mode, esc, sizeof(esc));
printf("%s", esc);
fwrite(p, 1, (size_t)len, stdout);
fputs("\x1b[0m", stdout);
(*index)++;
}
p += len;
}
}
void draw_miku_says(char *text, int style){
char **speech_bubble_lines = getbubblelines(text);
if(speech_bubble_lines == NULL)
@@ -255,6 +315,8 @@ void draw_miku_says(char *text, int style){
for(int i = 0; i < top_padding; i++){
printf("\n");
}
size_t color_index = 0;
size_t colored_total = count_colored_chars(miku_art);
for(int i = 0; all_content_lines[i] != NULL; i++){
int is_bubble_line = i < bubble_line_count;
@@ -268,7 +330,14 @@ void draw_miku_says(char *text, int style){
else
padding = overall_left_padding;
printf("%*s%s\n", padding, "", all_content_lines[i]);
if(is_bubble_line){
printf("%*s%s\n", padding, "", all_content_lines[i]);
}else{
printf("%*s", padding, "");
print_art_line(all_content_lines[i], &color_index, colored_total);
putchar('\n');
}
}
for(int i = 0; i < top_padding; i++){
@@ -277,11 +346,13 @@ void draw_miku_says(char *text, int style){
freelines(speech_bubble_lines);
free(all_content_lines);
}
int main(int argc, char **argv){
srand(time(NULL));
getterminalsize();
colorconfig_init(&colors);
long style = -1; //-1 means random
int list = 0;
int ret = 0;
@@ -317,12 +388,54 @@ int main(int argc, char **argv){
goto out;
}
style = v < 0 ? -1 : v; //negative = random
} else if(strcmp(argv[i], "--no-color") == 0){
colors.mode = MODE_NONE;
} else if(strcmp(argv[i], "--rainbow") == 0){
rainbow = 1;
} else if(strcmp(argv[i], "--color") == 0){
if(i + 1 >= argc){
fprintf(stderr, "error: a value is required for '--color <COLOR>' but none was supplied\n");
ret = 2;
goto out;
}
i++;
ColorParseError err = parse_color(argv[i], &solid);
if(err != COLOR_OK){
fprintf(stderr, "error: invalid color '%s': %s; expected a named color, #RRGGBB, #RGB, R,G,B, hsl(H,S%%,L%%), or ANSI value 0-255\n",
argv[i], color_parse_error_message(err));
ret = 2;
goto out;
}
has_solid = 1;
} else if(strcmp(argv[i], "--saturation") == 0 || strcmp(argv[i], "--brightness") == 0){
const char *flag = argv[i];
if(i + 1 >= argc){
fprintf(stderr, "error: a value is required for '%s <0-100>' but none was supplied\n", flag);
ret = 2;
goto out;
}
i++;
char *end;
long v = strtol(argv[i], &end, 10);
if(*argv[i] == '\0' || *end != '\0' || v < 0 || v > 100){
fprintf(stderr, "error: invalid value '%s' for '%s <0-100>'\n", argv[i], flag);
ret = 2;
goto out;
}
if(flag[2] == 's') colors.saturation = (unsigned char)v;
else colors.brightness = (unsigned char)v;
} else {
strcat(text, argv[i]);
strcat(text, " ");
}
}
if(rainbow && has_solid){
fprintf(stderr, "error: --rainbow cannot be used with --color\n");
ret = 2;
goto out;
}
if(list){
list_all_art_styles();
} else if(text[0] == '\0'){