summaryrefslogtreecommitdiff
path: root/src/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.c')
-rw-r--r--src/main.c357
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;
+}