3 Commits
Author SHA1 Message Date
david b5e3328f64 finally finished this. it should be 1:1 with the original rust version...
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
release / dist (push) Canceled after 0s
release / build (, macos-arm64, macos-15) (push) Canceled after 0s
release / build (, macos-x86_64, macos-15-intel) (push) Canceled after 0s
release / build (-static, linux-aarch64, ubuntu-24.04-arm) (push) Canceled after 0s
release / build (-static, linux-x86_64, ubuntu-24.04) (push) Canceled after 0s
release / windows (push) Canceled after 0s
release / release (push) Canceled after 0s
2026-10-26 18:27:07 -03:00
david 7aff48a428 split text
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
2026-10-13 23:04:06 -03:00
david f0f954d463 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
2026-10-13 07:43:48 -03:00
11 changed files with 1035 additions and 131 deletions

No files matched your search

+1
View File
@@ -11,3 +11,4 @@ mikusays
*.tar.gz *.tar.gz
.dirstamp .dirstamp
configure~
+3 -1
View File
@@ -6,7 +6,9 @@ mikusays_SOURCES = \
src/helper.c \ src/helper.c \
defs/all.h \ defs/all.h \
defs/mikuart.h \ defs/mikuart.h \
defs/helper.h defs/helper.h \
defs/colors.h \
defs/widechar.h
AM_CFLAGS = -Wall -Wextra AM_CFLAGS = -Wall -Wextra
Generated
+3 -1
View File
@@ -296,7 +296,9 @@ mikusays_SOURCES = \
src/helper.c \ src/helper.c \
defs/all.h \ defs/all.h \
defs/mikuart.h \ defs/mikuart.h \
defs/helper.h defs/helper.h \
defs/colors.h \
defs/widechar.h
AM_CFLAGS = -Wall -Wextra AM_CFLAGS = -Wall -Wextra
EXTRA_DIST = LICENSE README.md EXTRA_DIST = LICENSE README.md
Generated Vendored
+13 -13
View File
@@ -1,6 +1,6 @@
#! /bin/sh #! /bin/sh
# Guess values for system-dependent variables and create Makefiles. # Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.73 for mikusays 0.2.0. # Generated by GNU Autoconf 2.73 for mikusays 1.0.0.
# #
# #
# Copyright (C) 1992-1996, 1998-2017, 2020-2026 Free Software Foundation, # Copyright (C) 1992-1996, 1998-2017, 2020-2026 Free Software Foundation,
@@ -584,10 +584,10 @@ MAKEFLAGS=
# Identity of this package. # Identity of this package.
PACKAGE_NAME='mikusays' PACKAGE_NAME='mikusays'
PACKAGE_TARNAME='mikusays' PACKAGE_TARNAME='mikusays'
PACKAGE_VERSION='0.2.0' PACKAGE_VERSION='1.0.0'
PACKAGE_STRING='mikusays 0.2.0' PACKAGE_STRING='mikusays 1.0.0'
PACKAGE_BUGREPORT='' PACKAGE_BUGREPORT=''
PACKAGE_URL='https://github.com/davideldios7/mikusays' PACKAGE_URL='https://git.squeakyrat.moe/mikusays-c'
ac_unique_file="src/main.c" ac_unique_file="src/main.c"
# Factoring default headers for most tests. # Factoring default headers for most tests.
@@ -1281,7 +1281,7 @@ if test "$ac_init_help" = "long"; then
# Omit some internal or obsolete options to make the list less imposing. # Omit some internal or obsolete options to make the list less imposing.
# This message is too long to be a string in the A/UX 3.1 sh. # This message is too long to be a string in the A/UX 3.1 sh.
cat <<_ACEOF cat <<_ACEOF
'configure' configures mikusays 0.2.0 to adapt to many kinds of systems. 'configure' configures mikusays 1.0.0 to adapt to many kinds of systems.
Usage: $0 [OPTION]... [VAR=VALUE]... Usage: $0 [OPTION]... [VAR=VALUE]...
@@ -1348,7 +1348,7 @@ fi
if test -n "$ac_init_help"; then if test -n "$ac_init_help"; then
case $ac_init_help in case $ac_init_help in
short | recursive ) echo "Configuration of mikusays 0.2.0:";; short | recursive ) echo "Configuration of mikusays 1.0.0:";;
esac esac
cat <<\_ACEOF cat <<\_ACEOF
@@ -1379,7 +1379,7 @@ Use these variables to override the choices made by 'configure' or to help
it to find libraries and programs with nonstandard names/locations. it to find libraries and programs with nonstandard names/locations.
Report bugs to the package provider. Report bugs to the package provider.
mikusays home page: <https://github.com/davideldios7/mikusays>. mikusays home page: <https://git.squeakyrat.moe/mikusays-c>.
_ACEOF _ACEOF
ac_status=$? ac_status=$?
fi fi
@@ -1443,7 +1443,7 @@ fi
test -n "$ac_init_help" && exit $ac_status test -n "$ac_init_help" && exit $ac_status
if $ac_init_version; then if $ac_init_version; then
cat <<\_ACEOF cat <<\_ACEOF
mikusays configure 0.2.0 mikusays configure 1.0.0
generated by GNU Autoconf 2.73 generated by GNU Autoconf 2.73
Copyright (C) 2026 Free Software Foundation, Inc. Copyright (C) 2026 Free Software Foundation, Inc.
@@ -1555,7 +1555,7 @@ cat >config.log <<_ACEOF
This file contains any messages produced by compilers while This file contains any messages produced by compilers while
running configure, to aid debugging if configure makes a mistake. running configure, to aid debugging if configure makes a mistake.
It was created by mikusays $as_me 0.2.0, which was It was created by mikusays $as_me 1.0.0, which was
generated by GNU Autoconf 2.73. Invocation command line was generated by GNU Autoconf 2.73. Invocation command line was
$ $0$ac_configure_args_raw $ $0$ac_configure_args_raw
@@ -3171,7 +3171,7 @@ fi
# Define the identity of the package. # Define the identity of the package.
PACKAGE='mikusays' PACKAGE='mikusays'
VERSION='0.2.0' VERSION='1.0.0'
printf '%s\n' "#define PACKAGE \"$PACKAGE\"" >>confdefs.h printf '%s\n' "#define PACKAGE \"$PACKAGE\"" >>confdefs.h
@@ -5437,7 +5437,7 @@ cat >>"$CONFIG_STATUS" <<\_ACEOF || ac_write_fail=1
# report actual input values of CONFIG_FILES etc. instead of their # report actual input values of CONFIG_FILES etc. instead of their
# values after options handling. # values after options handling.
ac_log=" ac_log="
This file was extended by mikusays $as_me 0.2.0, which was This file was extended by mikusays $as_me 1.0.0, which was
generated by GNU Autoconf 2.73. Invocation command line was generated by GNU Autoconf 2.73. Invocation command line was
CONFIG_FILES = $CONFIG_FILES CONFIG_FILES = $CONFIG_FILES
@@ -5489,7 +5489,7 @@ Configuration commands:
$config_commands $config_commands
Report bugs to the package provider. Report bugs to the package provider.
mikusays home page: <https://github.com/davideldios7/mikusays>." mikusays home page: <https://git.squeakyrat.moe/mikusays-c>."
_ACEOF _ACEOF
ac_cs_config=`printf '%s\n' "$ac_configure_args" | sed "$ac_safe_unquote"` ac_cs_config=`printf '%s\n' "$ac_configure_args" | sed "$ac_safe_unquote"`
@@ -5497,7 +5497,7 @@ ac_cs_config_escaped=`printf '%s\n' "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\
cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1 cat >>"$CONFIG_STATUS" <<_ACEOF || ac_write_fail=1
ac_cs_config='$ac_cs_config_escaped' ac_cs_config='$ac_cs_config_escaped'
ac_cs_version="\\ ac_cs_version="\\
mikusays config.status 0.2.0 mikusays config.status 1.0.0
configured by $0, generated by GNU Autoconf 2.73, configured by $0, generated by GNU Autoconf 2.73,
with options \\"\$ac_cs_config\\" with options \\"\$ac_cs_config\\"
+1 -1
View File
@@ -1,5 +1,5 @@
AC_PREREQ([2.69]) AC_PREREQ([2.69])
AC_INIT([mikusays], [0.2.0], [], [mikusays], [https://github.com/davideldios7/mikusays]) AC_INIT([mikusays], [1.0.0], [], [mikusays], [https://git.squeakyrat.moe/mikusays-c])
AC_CONFIG_SRCDIR([src/main.c]) AC_CONFIG_SRCDIR([src/main.c])
AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_AUX_DIR([build-aux])
AM_INIT_AUTOMAKE([foreign subdir-objects -Wall]) AM_INIT_AUTOMAKE([foreign subdir-objects -Wall])
+2
View File
@@ -7,6 +7,8 @@
#include <string.h> #include <string.h>
#include <locale.h> #include <locale.h>
#include <stdbool.h> #include <stdbool.h>
#include <ctype.h>
#include <stdarg.h>
#include "helper.h" #include "helper.h"
+29 -24
View File
@@ -1,7 +1,7 @@
#ifndef COLORS_H #ifndef COLORS_H
#define COLORS_H #define COLORS_H
#include <stdlib.h> #include <stddef.h>
#include <stdbool.h> #include <stdbool.h>
typedef enum color_mode { typedef enum color_mode {
@@ -11,29 +11,11 @@ typedef enum color_mode {
MODE_TRUECOLOR MODE_TRUECOLOR
}color_mode; }color_mode;
typedef enum NamedColor {
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
BrightBlack,
BrightRed,
BrightGreen,
BrightYellow,
BrightBlue,
BrightMagenta,
BrightCyan,
BrightWhite,
}NamedColor;
typedef struct{ typedef struct{
int red; unsigned char red;
int green; unsigned char green;
int blue; unsigned char blue;
}rgbcolor; }rgbcolor;
typedef struct colorconfig { typedef struct colorconfig {
@@ -43,8 +25,31 @@ typedef struct colorconfig {
bool rainbow_enabled; //flag bool rainbow_enabled; //flag
int saturation; //default 100 int saturation; //default 100
int brightness; //default 50 int brightness; //default 50
char **rainbow_cache; //array of rgb_color malloced rgbcolor *rainbow_cache; //array of rgbcolor malloced
size_t rainbow_count; size_t rainbow_count;
bool has_gradient;
rgbcolor gradient_start;
rgbcolor gradient_end;
}colorconfig; }colorconfig;
#endif typedef enum ColorParseError {
COLOR_OK,
COLOR_ERR_FORMAT,
COLOR_ERR_HEX,
COLOR_ERR_RGB,
COLOR_ERR_HSL,
}ColorParseError;
void colorconfig_init(colorconfig *config);
enum color_mode detect_color_mode(void);
bool enable_windows_ansi(void);
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);
void print_color_debug(const colorconfig *config, bool ansi_enabled);
#endif
+2
View File
@@ -2,5 +2,7 @@
#define helper_h #define helper_h
int displaywidth(const char *text); int displaywidth(const char *text);
int utf8_decode(const unsigned char *bytes, unsigned long *codepoint);
int codepoint_width(unsigned long codepoint);
#endif #endif
+582 -17
View File
@@ -1,19 +1,76 @@
#include "../defs/all.h" #include "../defs/all.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <strings.h> #include <ctype.h>
void colorconfig_init(colorconfig config){ #ifdef _WIN32
config.saturation = 100; #include <windows.h>
config.brightness = 50; #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){ enum color_mode detect_color_mode(void){
const char *colorterm, *term; const char *colorterm, *term;
enum color_mode mode = MODE_ANSI16;
if(getenv("NO_COLOR")) if(getenv("NO_COLOR"))
return MODE_NONE; return MODE_NONE;
//early checks
colorterm = getenv("COLORTERM"); colorterm = getenv("COLORTERM");
if(colorterm && (strstr(colorterm, "truecolor") || strstr(colorterm, "24bit"))) if(colorterm && (strstr(colorterm, "truecolor") || strstr(colorterm, "24bit")))
return MODE_TRUECOLOR; return MODE_TRUECOLOR;
@@ -21,23 +78,531 @@ enum color_mode detect_color_mode(void){
term = getenv("TERM"); term = getenv("TERM");
if(term && strstr(term, "256color")) if(term && strstr(term, "256color"))
return MODE_ANSI256; return MODE_ANSI256;
if(term && strstr(term, "color"))
return MODE_ANSI16;
return MODE_ANSI16; //default fallback is mode_ansi16 (set above)
}
#ifdef _WIN32 #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 #endif
char parse_namedcolors(rgbcolor name, char *outrgb){ return mode;
/*
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
*/
} }
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 "Invalid color format";
case COLOR_ERR_HEX:
return "Invalid hex color code";
case COLOR_ERR_RGB:
return "Invalid RGB format. Expected format: R,G,B";
case COLOR_ERR_HSL:
return "Invalid HSL format. Expected format: hsl(H,S%,L%)";
case COLOR_OK:
default:
return "";
}
}
static const char *color_mode_name(color_mode mode){
switch(mode){
case MODE_TRUECOLOR: return "Truecolor";
case MODE_ANSI256: return "Ansi256";
case MODE_ANSI16: return "Ansi16";
case MODE_NONE:
default: return "NoColor";
}
}
static void print_env_debug(const char *name){
const char *value = getenv(name);
if(value)
printf("%s: Some(\"%s\")\n", name, value);
else
printf("%s: None\n", name);
}
void print_color_debug(const colorconfig *config, bool ansi_enabled){
printf("Detected color mode: %s\n", color_mode_name(config->mode));
printf("ANSI enabled: %s\n", ansi_enabled ? "true" : "false");
print_env_debug("NO_COLOR");
print_env_debug("COLORTERM");
print_env_debug("TERM");
#ifdef _WIN32
{
const char *shell = detect_windows_shell();
if(shell)
printf("Windows shell hint: Some(\"%s\")\n", shell);
else
printf("Windows shell hint: None\n");
}
#endif
}
+2 -2
View File
@@ -4,7 +4,7 @@
/* this reads one utf81 character from the start of bytes /* 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) 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. */ 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 value = bytes[0];
unsigned long minallowed = 0; //rejects overlong encodings unsigned long minallowed = 0; //rejects overlong encodings
int sequence_length; int sequence_length;
@@ -48,7 +48,7 @@ static int utf8_decode(const unsigned char *bytes, unsigned long *codepoint){
} }
/* this turns the library's return value into a column count so 0 1 or 2 */ /* this turns the library's return value into a column count so 0 1 or 2 */
static int codepoint_width(unsigned long codepoint){ int codepoint_width(unsigned long codepoint){
int width = widechar_wcwidth(codepoint); int width = widechar_wcwidth(codepoint);
if(width > 0) if(width > 0)
return width; return width;
+397 -72
View File
@@ -1,7 +1,5 @@
#include "../defs/all.h" #include "../defs/all.h"
#include "../defs/mikuart.h" #include "../defs/mikuart.h"
#include <stdlib.h>
#include <string.h>
char **getmikuart(int style){ char **getmikuart(int style){
if(style < 0) if(style < 0)
@@ -18,28 +16,46 @@ static struct win term = {80, 24}; //if the function fails this is the defualt
#ifdef _WIN32 #ifdef _WIN32
#include <windows.h> #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) { static void getterminalsize(void) {
CONSOLE_SCREEN_BUFFER_INFO info;
HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE);
int found = GetConsoleScreenBufferInfo(console, &info);
SetConsoleOutputCP(CP_UTF8); SetConsoleOutputCP(CP_UTF8);
CONSOLE_SCREEN_BUFFER_INFO csbi; if(!found){
if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) { HANDLE fallback = CreateFileA("CONOUT$", GENERIC_READ | GENERIC_WRITE,
term.winwidth = csbi.srWindow.Right - csbi.srWindow.Left + 1; FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
term.winheight = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; 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 <sys/ioctl.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h>
static void getterminalsize(void) { static void getterminalsize(void) {
struct winsize ws; struct winsize size_info;
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) { int tty_descriptor = -1;
if(ws.ws_col > 0) int status = ioctl(STDOUT_FILENO, TIOCGWINSZ, &size_info);
term.winwidth = ws.ws_col;
if(ws.ws_row > 0) if(status != 0){
term.winheight = ws.ws_row; 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 #endif
@@ -55,6 +71,43 @@ static void freelines(char **lines){
free(lines); free(lines);
} }
static char **push_chunk(char **chunks, int *count, const char *start, size_t len){
chunks = realloc(chunks, (*count + 2) * sizeof(char *));
chunks[*count] = malloc(len + 1);
memcpy(chunks[*count], start, len);
chunks[*count][len] = '\0';
(*count)++;
chunks[*count] = NULL;
return chunks;
}
char **split_word_by_width(const char *word, int max_width){
char **chunks = NULL;
int count = 0;
size_t length = strlen(word);
size_t start = 0, i = 0;
int chunk_width = 0;
while(i < length){
unsigned long codepoint;
int bytes = utf8_decode((const unsigned char *)word + i, &codepoint);
int width = codepoint_width(codepoint);
if(i > start && chunk_width + width > max_width){
chunks = push_chunk(chunks, &count, word + start, i - start);
start = i;
chunk_width = 0;
}
chunk_width += width;
i += bytes;
}
if(i > start)
chunks = push_chunk(chunks, &count, word + start, i - start);
return chunks;
}
char **wraptext(const char *text, int max, int *count){ char **wraptext(const char *text, int max, int *count){
char *copy = strdup(text); char *copy = strdup(text);
char *current_line = calloc(strlen(text)+1, sizeof(char)); char *current_line = calloc(strlen(text)+1, sizeof(char));
@@ -69,17 +122,40 @@ char **wraptext(const char *text, int max, int *count){
} }
strcat(current_line, tok); strcat(current_line, tok);
} else{ } else{
if(current_line[0] != '\0'){ if(displaywidth(tok) > max){
lines = realloc(lines, (*count + 2) * sizeof(char *)); if(current_line[0] != '\0'){
lines[*count] = strdup(current_line); lines = realloc(lines, (*count + 2) * sizeof(char *));
(*count)++; lines[*count] = strdup(current_line);
lines[*count] = NULL; (*count)++;
} lines[*count] = NULL;
strcpy(current_line, tok); }
char **chunks = split_word_by_width(tok, max);
int n = 0;
while(chunks[n] != NULL) n++;
for(int i = 0; i < n - 1; i++){
lines = realloc(lines, (*count + 2) * sizeof(char *));
lines[*count] = chunks[i];
(*count)++;
lines[*count] = NULL;
}
strcpy(current_line, chunks[n - 1]);
free(chunks[n - 1]);
free(chunks);
} else{
if(current_line[0] != '\0'){
lines = realloc(lines, (*count + 2) * sizeof(char *));
lines[*count] = strdup(current_line);
(*count)++;
lines[*count] = NULL;
}
strcpy(current_line, tok);
}
} }
tok = strtok(NULL, " \t\n"); tok = strtok(NULL, " \t\n");
} }
if(current_line[0] != '\0'){ if(current_line[0] != '\0'){
lines = realloc(lines, (*count + 2) * sizeof(char *)); lines = realloc(lines, (*count + 2) * sizeof(char *));
lines[*count] = strdup(current_line); lines[*count] = strdup(current_line);
@@ -96,11 +172,9 @@ char **wraptext(const char *text, int max, int *count){
#define MIN_BUBBLE_WIDTH 20 #define MIN_BUBBLE_WIDTH 20
char **getbubblelines(char *text){ char **getbubblelines(char *text){
char **templines = wraptext(text, MAX_BUBBLE_WIDTH - 4, &count); char **templines = wraptext(text, MAX_BUBBLE_WIDTH - 4, &count);
if(templines == NULL)
return NULL;
size_t content_width = 0; 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]); size_t current_len = displaywidth(templines[i]);
if (current_len > content_width) if (current_len > content_width)
content_width = current_len; content_width = current_len;
@@ -127,7 +201,7 @@ char **getbubblelines(char *text){
strcat(bubblelines[0], horizontal); strcat(bubblelines[0], horizontal);
strcat(bubblelines[0], top_right); 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 line_width = displaywidth(lines[i]);
int padding_needed = (bubble_width - 2) - line_width; int padding_needed = (bubble_width - 2) - line_width;
if (padding_needed < 0) padding_needed = 0; if (padding_needed < 0) padding_needed = 0;
@@ -157,10 +231,17 @@ static void print_help(void){
"Arguments:\n" "Arguments:\n"
" [TEXT] Text to display in the speech bubble\n\n" " [TEXT] Text to display in the speech bubble\n\n"
"Options:\n" "Options:\n"
" -s, --style <STYLE> Style of the Miku art. A random one is chosen if not specified\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" " -l, --list List all available art styles with their indices\n"
" -h, --help Print help\n" " --rainbow Apply a smooth rainbow gradient across the ASCII art\n"
" -V, --version Print version\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){ static void list_all_art_styles(void){
@@ -175,8 +256,84 @@ static void list_all_art_styles(void){
} }
printf("\nUse --style <number> to select a specific style, or omit for random selection.\n"); 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 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 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 escape[32];
const unsigned char *cursor = (const unsigned char *)line;
int per_character = colors.rainbow_enabled || colors.has_gradient;
if(colors.mode == MODE_NONE || (!per_character && !colors.has_solid_color)){
fputs(line, stdout);
return;
}
if(!per_character){
rgb_to_fg_escape(colors.solid_color, colors.mode, escape, sizeof(escape));
printf("%s%s\x1b[0m", escape, line);
return;
}
while(*cursor){
unsigned long codepoint;
int byte_count = utf8_decode(cursor, &codepoint);
if(is_art_space(codepoint)){
fwrite(cursor, 1, (size_t)byte_count, stdout);
}else{
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)++;
}
cursor += byte_count;
}
}
void draw_miku_says(char *text, int style){ void draw_miku_says(char *text, int style){
char **speech_bubble_lines = getbubblelines(text); char **speech_bubble_lines = getbubblelines(text);
if(speech_bubble_lines == NULL) if(speech_bubble_lines == NULL)
@@ -255,6 +412,8 @@ void draw_miku_says(char *text, int style){
for(int i = 0; i < top_padding; i++){ for(int i = 0; i < top_padding; i++){
printf("\n"); 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++){ for(int i = 0; all_content_lines[i] != NULL; i++){
int is_bubble_line = i < bubble_line_count; int is_bubble_line = i < bubble_line_count;
@@ -268,7 +427,14 @@ void draw_miku_says(char *text, int style){
else else
padding = overall_left_padding; 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++){ for(int i = 0; i < top_padding; i++){
@@ -278,64 +444,223 @@ void draw_miku_says(char *text, int style){
free(all_content_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){ 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)); srand(time(NULL));
getterminalsize(); getterminalsize();
colorconfig_init(&colors);
long style = -1; //-1 means random for(index = 1; index < argc; index++)
int list = 0; length += strlen(argv[index]);
int ret = 0; text = malloc(length + (size_t)argc);
size_t length = 0;
for(int i = 1; i < argc; i++)
length += strlen(argv[i]);
char *text = malloc(length + argc);
if(!text) return 1; if(!text) return 1;
*text = '\0'; *text = '\0';
for(int i = 1; i < argc; i++){ for(index = 1; index < argc; index++){
if(strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0){ 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(); print_help();
goto out; 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"); printf("mikusays " VERSION "\n");
goto out; goto out;
} else if(strcmp(argv[i], "-l") == 0 || strcmp(argv[i], "--list") == 0){ } else if(strcmp(argument, "-l") == 0 || strcmp(argument, "--list") == 0){
list = 1; list_requested = 1;
} else if(strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--style") == 0){ } else if(strcmp(argument, "--no-color") == 0){
if(i + 1 >= argc){ no_color = 1;
fprintf(stderr, "error: a value is required for '--style <STYLE>' but none was supplied\n"); colors.mode = MODE_NONE;
ret = 2; }else if(strcmp(argument, "--rainbow") == 0){
goto out; colors.rainbow_enabled = true;
} } else if(strcmp(argument, "--color-debug") == 0){
i++; color_debug = 1;
} else if(match_value_option(argc, argv, &index, "--style", 's', &value)){
char *end; char *end;
long v = strtol(argv[i], &end, 10); long parsed;
if(*argv[i] == '\0' || *end != '\0'){ if(value == NULL){
fprintf(stderr, "error: invalid value '%s' for '--style <STYLE>': not a number\n", argv[i]); result = usage_error("a value is required for '--style <STYLE>' but none was supplied");
ret = 2;
goto out; goto out;
} }
style = v < 0 ? -1 : v; //negative = random 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;
}
if(parsed < 0){
result = usage_error("invalid value '%s' for '--style <STYLE>': %ld is not in 0..2147483647", value, parsed);
goto out;
}
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;
}
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;
}
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 { } else {
strcat(text, argv[i]); result = usage_error("unexpected argument '%s' found", argument);
strcat(text, " "); goto out;
} }
} }
if(list){ if(colors.rainbow_enabled && color_text){
list_all_art_styles(); result = usage_error("the argument '--rainbow' cannot be used with '--color <COLOR>'");
} else if(text[0] == '\0'){ goto out;
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);
} }
//i didn't know you could do labels like in assembly (nasm) this is very useful knowledge 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_requested){
list_all_art_styles();
goto out;
}
if(!has_text){
fprintf(stderr, "Error: Text is required when not using --list\n");
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;
}
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: out:
free(text); free(text);
return ret; return result;
} } //ooooo 666 lines spookyyy evillll