Files
mikusays-c/src/main.c
T

355 lines
11 KiB
C

#include "../defs/all.h"
#include "../defs/mikuart.h"
#include <stdlib.h>
#include <string.h>
char **getmikuart(int style){
if(style < 0)
style = rand() % migucount;
return mikuart[style];
}
struct win {
int winwidth;
int winheight;
};
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) {
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;
}
}
#else //or this if they're not on windows which should be posixish like linux or macos
#include <sys/ioctl.h>
#include <unistd.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;
}
}
#endif
#define TERMINALWIDTH term.winwidth
#define TERMINALHEIGHT term.winheight
static int count;
static void freelines(char **lines){
if(!lines) return;
for(int i = 0; lines[i] != NULL; i++)
free(lines[i]);
free(lines);
}
char **wraptext(const char *text, int max, int *count){
char *copy = strdup(text);
char *current_line = calloc(strlen(text)+1, sizeof(char));
char **lines = NULL;
*count = 0;
char *tok = strtok(copy, " \t\n");
while(tok != NULL){
if(strlen(current_line)+strlen(tok) < (size_t)max){
if(current_line[0] != '\0'){
strcat(current_line, " ");
}
strcat(current_line, tok);
} 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");
}
if(current_line[0] != '\0'){
lines = realloc(lines, (*count + 2) * sizeof(char *));
lines[*count] = strdup(current_line);
(*count)++;
lines[*count] = NULL;
}
free(copy);
free(current_line);
return lines;
}
#define MAX_BUBBLE_WIDTH 50
#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++) {
size_t current_len = strlen(templines[i]);
if (current_len > content_width)
content_width = current_len;
}
freelines(templines);
int bubble_width = content_width + 4;
if(bubble_width < MIN_BUBBLE_WIDTH) bubble_width = MIN_BUBBLE_WIDTH;
if(bubble_width > MAX_BUBBLE_WIDTH) bubble_width = MAX_BUBBLE_WIDTH;
char **lines = wraptext(text, bubble_width - 4, &count);
const char *top_left = "╭";
const char *top_right = "╮";
const char *bottom_left = "╰";
const char *bottom_right = "╯";
const char *horizontal = "─";
const char *vertical = "│";
char **bubblelines = calloc(count + 5, sizeof(char *));
bubblelines[0] = malloc(strlen(top_left) + strlen(horizontal) * bubble_width + strlen(top_right) + 1);
strcpy(bubblelines[0], top_left);
for (int i = 0; i < bubble_width; i++)
strcat(bubblelines[0], horizontal);
strcat(bubblelines[0], top_right);
for (int i = 0; lines[i] != NULL; i++) {
int line_width = strlen(lines[i]);
int padding_needed = (bubble_width - 2) - line_width;
if (padding_needed < 0) padding_needed = 0;
int left_padding = padding_needed / 2;
int right_padding = padding_needed - left_padding;
bubblelines[i + 1] = malloc(strlen(vertical) * 2 + 2 + line_width + padding_needed + 1);
sprintf(bubblelines[i + 1], "%s %*s%s%*s %s",vertical, left_padding, "", lines[i], right_padding, "", vertical);
}
bubblelines[count+1] = malloc(strlen(bottom_left) + strlen(horizontal) * bubble_width + strlen(bottom_right) + 1);
strcpy(bubblelines[count+1], bottom_left);
for (int i = 0; i < bubble_width; i++)
strcat(bubblelines[count+1], horizontal);
strcat(bubblelines[count+1], bottom_right);
bubblelines[count + 2] = strdup("\\");
bubblelines[count + 3] = strdup(" \\");
freelines(lines);
return bubblelines;
}
static void print_help(void){
printf("A \"cowsay\" clone with Hatsune Miku ASCII art and speech bubbles.\n\n"
"Usage: mikusays [OPTIONS] [TEXT]\n\n"
"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");
}
static void list_all_art_styles(void){
printf("Available Miku art styles:\n");
printf("==========================\n");
printf("Total styles: %zu\n", migucount);
for(size_t i = 0; i < migucount; i++){
printf("\n--- Style %zu ---\n", i);
for(int j = 0; mikuart[i][j] != NULL; j++)
printf("%s\n", mikuart[i][j]);
}
printf("\nUse --style <number> to select a specific style, or omit for random selection.\n");
}
//display width in columns: counts utf 8 codepoints (skips continuation bytes)
//assumes every character is 1 column wide
//emojis and other bullshit won't work bu tthat's okay since who the fuck will try to put '🥺' into this
static int displaywidth(const char *s){
int w = 0;
for(; *s; s++)
if(((unsigned char)*s & 0xC0) != 0x80)
w++;
return w;
}
void draw_miku_says(char *text, int style){
char **speech_bubble_lines = getbubblelines(text);
if(speech_bubble_lines == NULL)
return;
char **miku_art = getmikuart(style);
int available_width = TERMINALWIDTH;
int available_height = TERMINALHEIGHT;
int max_content_width = 0;
for(int i = 0; speech_bubble_lines[i] != NULL; i++){
int width = displaywidth(speech_bubble_lines[i]);
if(width> max_content_width) max_content_width = width;
}
for(int i = 0; miku_art[i] != NULL; i++){
int width = displaywidth(miku_art[i]);
if(width > max_content_width) max_content_width = width;
}
int overall_left_padding;
if(available_width > max_content_width){
overall_left_padding = (available_width - max_content_width) / 2;
}else{
overall_left_padding = 0;
}
int bubble_width = displaywidth(speech_bubble_lines[0]);
const char *first_miku_line = miku_art[0] ? miku_art[0] : "";
int tail_pos = 0;
const char *ptr = first_miku_line;
while (*ptr == ' ') {
ptr += 1;
tail_pos++;
}
int bubble_center = bubble_width / 2;
int miku_tail_absolute_pos = overall_left_padding + tail_pos;
int bubble_left_padding;
if(miku_tail_absolute_pos >= bubble_center){
bubble_left_padding = miku_tail_absolute_pos - bubble_center;
}else{
bubble_left_padding = 0;
}
int bubble_line_count = 0;
for(int i = 0; speech_bubble_lines[i] != NULL; i++){
bubble_line_count++;
}
int art_count = 0;
while(miku_art[art_count] != NULL) art_count++;
char **all_content_lines = calloc(bubble_line_count + art_count + 1, sizeof(char *));
for(int i = 0; i < bubble_line_count; i++)
all_content_lines[i] = speech_bubble_lines[i];
for(int i = 0; i < art_count; i++)
all_content_lines[bubble_line_count + i] = miku_art[i];
int content_height = bubble_line_count + art_count;
int top_padding;
if(content_height < available_height){
top_padding = (available_height - content_height) / 2;
}else{
top_padding = 0;
}
for(int i = 0; i < top_padding; i++){
printf("\n");
}
for(int i = 0; all_content_lines[i] != NULL; i++){
int is_bubble_line = i < bubble_line_count;
int is_tail_line = is_bubble_line && (i == bubble_line_count - 2 || i == bubble_line_count - 1);
int padding;
if(is_tail_line)
padding = overall_left_padding + tail_pos;
else if(is_bubble_line)
padding = bubble_left_padding;
else
padding = overall_left_padding;
printf("%*s%s\n", padding, "", all_content_lines[i]);
}
for(int i = 0; i < top_padding; i++){
printf("\n");
}
freelines(speech_bubble_lines);
free(all_content_lines);
}
int main(int argc, char **argv){
Void(); //look at all.h
srand(time(NULL));
getterminalsize();
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);
if(!text) return 1;
*text = '\0';
for(int i = 1; i < argc; i++){
if(strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0){
print_help();
goto out;
} else if(strcmp(argv[i], "-V") == 0 || strcmp(argv[i], "--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++;
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;
goto out;
}
style = v < 0 ? -1 : v; //negative = random
} else {
strcat(text, argv[i]);
strcat(text, " ");
}
}
if(list){
list_all_art_styles();
} else if(text[0] == '\0'){
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
out:
free(text);
return ret;
}