diff options
Diffstat (limited to 'src/main.c')
| -rw-r--r-- | src/main.c | 382 |
1 files changed, 268 insertions, 114 deletions
@@ -16,28 +16,46 @@ static struct win term = {80, 24}; //if the function fails this is the defualt #ifdef _WIN32 #include <windows.h> - //i have no way to test this because i don't have any windows pc in my house qwq - //(i asked claude to write this for windows because i never looked at the windows api in my life) static void getterminalsize(void) { + CONSOLE_SCREEN_BUFFER_INFO info; + HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE); + int found = GetConsoleScreenBufferInfo(console, &info); + SetConsoleOutputCP(CP_UTF8); - CONSOLE_SCREEN_BUFFER_INFO csbi; - if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) { - term.winwidth = csbi.srWindow.Right - csbi.srWindow.Left + 1; - term.winheight = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; + if(!found){ + HANDLE fallback = CreateFileA("CONOUT$", GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if(fallback != INVALID_HANDLE_VALUE){ + found = GetConsoleScreenBufferInfo(fallback, &info); + CloseHandle(fallback); + } + } + if(found){ + term.winwidth = info.srWindow.Right - info.srWindow.Left + 1; + term.winheight = info.srWindow.Bottom - info.srWindow.Top + 1; } } -#else //or this if they're not on windows which should be posixish like linux or macos +#else #include <sys/ioctl.h> #include <unistd.h> + #include <fcntl.h> static void getterminalsize(void) { - struct winsize ws; - if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) { - if(ws.ws_col > 0) - term.winwidth = ws.ws_col; - if(ws.ws_row > 0) - term.winheight = ws.ws_row; + struct winsize size_info; + int tty_descriptor = -1; + int status = ioctl(STDOUT_FILENO, TIOCGWINSZ, &size_info); + + if(status != 0){ + tty_descriptor = open("/dev/tty", O_RDONLY); + if(tty_descriptor >= 0) + status = ioctl(tty_descriptor, TIOCGWINSZ, &size_info); } + if(status == 0){ + if(size_info.ws_col > 0) term.winwidth = size_info.ws_col; + if(size_info.ws_row > 0) term.winheight = size_info.ws_row; + } + if(tty_descriptor >= 0) + close(tty_descriptor); } #endif @@ -154,11 +172,9 @@ char **wraptext(const char *text, int max, int *count){ #define MIN_BUBBLE_WIDTH 20 char **getbubblelines(char *text){ char **templines = wraptext(text, MAX_BUBBLE_WIDTH - 4, &count); - if(templines == NULL) - return NULL; size_t content_width = 0; - for (int i = 0; templines[i] != NULL; i++) { + for (int i = 0; templines != NULL && templines[i] != NULL; i++) { size_t current_len = displaywidth(templines[i]); if (current_len > content_width) content_width = current_len; @@ -185,7 +201,7 @@ char **getbubblelines(char *text){ strcat(bubblelines[0], horizontal); strcat(bubblelines[0], top_right); - for (int i = 0; lines[i] != NULL; i++) { + for (int i = 0; lines != NULL && lines[i] != NULL; i++) { int line_width = displaywidth(lines[i]); int padding_needed = (bubble_width - 2) - line_width; if (padding_needed < 0) padding_needed = 0; @@ -215,15 +231,17 @@ static void print_help(void){ "Arguments:\n" " [TEXT] Text to display in the speech bubble\n\n" "Options:\n" - " -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" - " --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"); + " -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" + " --rainbow Apply a smooth rainbow gradient across the ASCII art\n" + " --saturation <SATURATION> Saturation level for rainbow gradient (0-100) [default: 100]\n" + " --brightness <BRIGHTNESS> Brightness level for rainbow gradient (0-100) [default: 50]\n" + " --color <COLOR> Override with a single solid color\n" + " --gradient <START:END> Define a custom two-color gradient (e.g., --gradient red:blue)\n" + " --no-color Disable all coloring (respects NO_COLOR environment variable)\n" + " --color-debug Print color detection/debug info and exit\n" + " -h, --help Print help\n" + " -V, --version Print version\n"); } static void list_all_art_styles(void){ @@ -238,11 +256,24 @@ static void list_all_art_styles(void){ } printf("\nUse --style <number> to select a specific style, or omit for random selection.\n"); + + printf("\nColor Options:\n" + " --rainbow Apply a smooth rainbow gradient\n" + " --saturation <0-100> Set rainbow saturation level (default: 100)\n" + " --brightness <0-100> Set rainbow brightness level (default: 50)\n" + " --color <COLOR> Set a single solid color (supports named colors, hex, rgb, hsl, ansi)\n" + " --gradient <START:END> Define a custom two-color gradient\n" + " --no-color Disable all coloring\n" + "\nColor Format Examples:\n" + " --color red Named color\n" + " --color #FF0000 Hex color\n" + " --color 255,0,128 RGB tuple\n" + " --color hsl(180,100%%,50%%) HSL value\n" + " --color 93 ANSI color code\n" + " --gradient red:blue Custom gradient\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 @@ -263,34 +294,42 @@ static size_t count_colored_chars(char **art){ return total; } +static rgbcolor color_for_index(size_t index, size_t total){ + if(colors.has_gradient){ + float position = total <= 1 ? 0.0f : (float)index / (float)(total - 1); + return lerp_color_hsv(colors.gradient_start, colors.gradient_end, position); + } + return hsl_to_rgb((unsigned short)((float)index / (float)total * 360.0f + 0.5f), + (unsigned char)colors.saturation, (unsigned char)colors.brightness); +} + 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; + char escape[32]; + const unsigned char *cursor = (const unsigned char *)line; + int per_character = colors.rainbow_enabled || colors.has_gradient; - if(colors.mode == MODE_NONE || (!rainbow && !has_solid)){ + if(colors.mode == MODE_NONE || (!per_character && !colors.has_solid_color)){ 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); + if(!per_character){ + rgb_to_fg_escape(colors.solid_color, colors.mode, escape, sizeof(escape)); + printf("%s%s\x1b[0m", escape, line); return; } - while(*p){//rainbow: one color per non-space char + while(*cursor){ unsigned long codepoint; - int len = utf8_decode(p, &codepoint); + int byte_count = utf8_decode(cursor, &codepoint); if(is_art_space(codepoint)){ - fwrite(p, 1, (size_t)len, stdout); + fwrite(cursor, 1, (size_t)byte_count, 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); + rgb_to_fg_escape(color_for_index(*index, total), colors.mode, escape, sizeof(escape)); + fputs(escape, stdout); + fwrite(cursor, 1, (size_t)byte_count, stdout); fputs("\x1b[0m", stdout); (*index)++; } - p += len; + cursor += byte_count; } } @@ -404,109 +443,224 @@ void draw_miku_says(char *text, int style){ freelines(speech_bubble_lines); free(all_content_lines); } + +#define COLOR_FORMAT_HELP "expected a named color, #RRGGBB, #RGB, R,G,B, hsl(H,S%%,L%%), or ANSI value 0-255" + +static int usage_error(const char *format, ...){ + va_list arguments; + va_start(arguments, format); + fputs("error: ", stderr); + vfprintf(stderr, format, arguments); + va_end(arguments); + fputs("\n\nUsage: mikusays [OPTIONS] [TEXT]\n\nFor more information, try '--help'.\n", stderr); + return 2; +} + +//returns 1 if argv[*index] is this option; *value stays NULL when the value is missing +static int match_value_option(int argc, char **argv, int *index, const char *long_name, + char short_name, const char **value){ + const char *argument = argv[*index]; + size_t long_length = strlen(long_name); + + *value = NULL; + if(strcmp(argument, long_name) == 0 + || (short_name != '\0' && argument[0] == '-' && argument[1] == short_name && argument[2] == '\0')){ + if(*index + 1 < argc){ + (*index)++; + *value = argv[*index]; + } + return 1; + } + if(strncmp(argument, long_name, long_length) == 0 && argument[long_length] == '='){ + *value = argument + long_length + 1; + return 1; + } + if(short_name != '\0' && argument[0] == '-' && argument[1] == short_name && argument[2] != '\0'){ + *value = argument + 2; + return 1; + } + return 0; +} + +static int parse_percent_option(const char *flag, const char *value, int *destination){ + char *end; + long parsed; + + if(value == NULL) + return usage_error("a value is required for '%s' but none was supplied", flag); + parsed = strtol(value, &end, 10); + if(*value == '\0' || *end != '\0' || parsed < 0 || parsed > 100) + return usage_error("invalid value '%s' for '%s'", value, flag); + *destination = (int)parsed; + return 0; +} + +static int parse_gradient(const char *specification, rgbcolor *start, rgbcolor *end){ + const char *separator = strchr(specification, ':'); + char start_text[64]; + size_t start_length; + ColorParseError error; + + if(separator == NULL) + return usage_error("invalid gradient: expected START:END (for example, red:blue)"); + start_length = (size_t)(separator - specification); + if(start_length == 0 || separator[1] == '\0' || strchr(separator + 1, ':') != NULL) + return usage_error("invalid gradient: expected exactly two colors as START:END"); + if(start_length >= sizeof(start_text)) + return usage_error("invalid gradient start: %s; " COLOR_FORMAT_HELP, + color_parse_error_message(COLOR_ERR_FORMAT)); + + memcpy(start_text, specification, start_length); + start_text[start_length] = '\0'; + error = parse_color(start_text, start); + if(error != COLOR_OK) + return usage_error("invalid gradient start '%s': %s; " COLOR_FORMAT_HELP, + start_text, color_parse_error_message(error)); + error = parse_color(separator + 1, end); + if(error != COLOR_OK) + return usage_error("invalid gradient end '%s': %s; " COLOR_FORMAT_HELP, + separator + 1, color_parse_error_message(error)); + return 0; +} int main(int argc, char **argv){ + long style = -1; //-1 means random + int index, result = 0, list_requested = 0, color_debug = 0, no_color = 0; + int has_text = 0, options_ended = 0, ansi_enabled = 0; + const char *value, *color_text = NULL, *gradient_text = NULL; + ColorParseError color_error; + size_t length = 0; + char *text; + srand(time(NULL)); getterminalsize(); + colorconfig_init(&colors); - colorconfig_init(&colors); - - long style = -1; //-1 means random - int list = 0; - int ret = 0; - - size_t length = 0; - for(int i = 1; i < argc; i++) - length += strlen(argv[i]); - char *text = malloc(length + argc); + for(index = 1; index < argc; index++) + length += strlen(argv[index]); + text = malloc(length + (size_t)argc); if(!text) return 1; *text = '\0'; - for(int i = 1; i < argc; i++){ - if(strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0){ + for(index = 1; index < argc; index++){ + const char *argument = argv[index]; + + if(!options_ended && strcmp(argument, "--") == 0){ + options_ended = 1; + } else if(options_ended || argument[0] != '-' || argument[1] == '\0'){ + strcat(text, argument); + strcat(text, " "); + has_text = 1; + } else if(strcmp(argument, "-h") == 0 || strcmp(argument, "--help") == 0){ print_help(); goto out; - } else if(strcmp(argv[i], "-V") == 0 || strcmp(argv[i], "--version") == 0){ + } else if(strcmp(argument, "-V") == 0 || strcmp(argument, "--version") == 0){ printf("mikusays " VERSION "\n"); goto out; - } else if(strcmp(argv[i], "-l") == 0 || strcmp(argv[i], "--list") == 0){ - list = 1; - } else if(strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--style") == 0){ - if(i + 1 >= argc){ - fprintf(stderr, "error: a value is required for '--style <STYLE>' but none was supplied\n"); - ret = 2; - goto out; - } - i++; + } else if(strcmp(argument, "-l") == 0 || strcmp(argument, "--list") == 0){ + list_requested = 1; + } else if(strcmp(argument, "--no-color") == 0){ + no_color = 1; + colors.mode = MODE_NONE; + }else if(strcmp(argument, "--rainbow") == 0){ + colors.rainbow_enabled = true; + } else if(strcmp(argument, "--color-debug") == 0){ + color_debug = 1; + } else if(match_value_option(argc, argv, &index, "--style", 's', &value)){ char *end; - long v = strtol(argv[i], &end, 10); - if(*argv[i] == '\0' || *end != '\0'){ - fprintf(stderr, "error: invalid value '%s' for '--style <STYLE>': not a number\n", argv[i]); - ret = 2; + long parsed; + if(value == NULL){ + result = usage_error("a value is required for '--style <STYLE>' but none was supplied"); 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; + parsed = strtol(value, &end, 10); + if(*value == '\0' || *end != '\0'){ + result = usage_error("invalid value '%s' for '--style <STYLE>': invalid digit found in string", value); 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; + if(parsed < 0){ + result = usage_error("invalid value '%s' for '--style <STYLE>': %ld is not in 0..2147483647", value, parsed); 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; + style = parsed; + } else if(match_value_option(argc, argv, &index, "--color", '\0', &value)){ + if(value == NULL){ + result = usage_error("a value is required for '--color <COLOR>' but none was supplied"); 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; + color_text = value; + } else if(match_value_option(argc, argv, &index, "--gradient", '\0', &value)){ + if(value == NULL){ + result = usage_error("a value is required for '--gradient <START:END>' but none was supplied"); goto out; } - if(flag[2] == 's') colors.saturation = (unsigned char)v; - else colors.brightness = (unsigned char)v; + gradient_text = value; + } else if(match_value_option(argc, argv, &index, "--saturation", '\0', &value)){ + result = parse_percent_option("--saturation <SATURATION>", value, &colors.saturation); + if(result) goto out; + } else if(match_value_option(argc, argv, &index, "--brightness", '\0', &value)){ + result = parse_percent_option("--brightness <BRIGHTNESS>", value, &colors.brightness); + if(result) goto out; } else { - strcat(text, argv[i]); - strcat(text, " "); + result = usage_error("unexpected argument '%s' found", argument); + goto out; } } - if(rainbow && has_solid){ - fprintf(stderr, "error: --rainbow cannot be used with --color\n"); - ret = 2; + if(colors.rainbow_enabled && color_text){ + result = usage_error("the argument '--rainbow' cannot be used with '--color <COLOR>'"); + goto out; + } + if(colors.rainbow_enabled && gradient_text){ + result = usage_error("the argument '--rainbow' cannot be used with '--gradient <START:END>'"); + goto out; + } + if(color_text && gradient_text){ + result = usage_error("the argument '--color <COLOR>' cannot be used with '--gradient <START:END>'"); goto out; } - if(list){ + if(list_requested){ list_all_art_styles(); - } else if(text[0] == '\0'){ + goto out; + } + if(!has_text){ fprintf(stderr, "Error: Text is required when not using --list\n"); - ret = 1; - } else if(style >= (long)migucount){ - fprintf(stderr, "Style %ld does not exist! Choose one between 0 and %zu\n", style, migucount - 1); - ret = 1; - } else { - draw_miku_says(text, (int)style); + result = 1; + goto out; + } + if(style >= (long)migucount){ + result = usage_error("style %ld does not exist; choose a value between 0 and %lu", + style, (unsigned long)(migucount - 1)); + goto out; + } + + if(color_text){ + color_error = parse_color(color_text, &colors.solid_color); + if(color_error != COLOR_OK){ + result = usage_error("invalid color '%s': %s; " COLOR_FORMAT_HELP, + color_text, color_parse_error_message(color_error)); + goto out; + } + colors.has_solid_color = true; } -//i didn't know you could do labels like in assembly (nasm) this is very useful knowledge + if(gradient_text){ + result = parse_gradient(gradient_text, &colors.gradient_start, &colors.gradient_end); + if(result) goto out; + colors.has_gradient = true; + } + +#ifdef _WIN32 + ansi_enabled = !no_color && enable_windows_ansi(); +#endif + if(color_debug){ + print_color_debug(&colors, ansi_enabled != 0); + goto out; + } + + draw_miku_says(text, (int)style); +//i didn't know you could do labels like in assembly (nasm) this is very useful knowledge out: free(text); - return ret; -} + return result; +} //ooooo 666 lines spookyyy evillll |
