diff options
Diffstat (limited to 'src/main.c')
| -rw-r--r-- | src/main.c | 357 |
1 files changed, 357 insertions, 0 deletions
diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..adbd1d3 --- /dev/null +++ b/src/main.c @@ -0,0 +1,357 @@ +#include "../defs/all.h" +#include "../defs/mikuart.h" +#include <stdlib.h> +#include <string.h> + +#define VERSION "0.1.0" +//proud.release.patch + +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; +} |
