nyancat

Unnamed repository; edit this file 'description' to name the repository.
git clone https://git.inz.fi/nyancat
Log | Files | Refs | README

commit 6ed6bfad7b89d4fa4e83dbf7cf9cf4565788b7f4
Author: Santtu Lakkala <inz@inz.fi>
Date:   Wed, 15 Jul 2020 14:54:51 +0300

Initial import

Diffstat:
A.gitignore | 3+++
AMakefile | 6++++++
AREADME.md | 1+
Anyancat.c | 565+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 575 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,3 @@ +*~ +*.o +nyancat diff --git a/Makefile b/Makefile @@ -0,0 +1,6 @@ +CFLAGS := -W -Wall -std=c99 $(shell pkg-config icu-uc --cflags) +LIBS := $(shell pkg-config icu-uc --libs) -lm +nyancat: nyancat.o + $(CC) -o $@ $^ $(LIBS) +%.o: %.c + $(CC) $(CFLAGS) -c $< -o $@ diff --git a/README.md b/README.md @@ -0,0 +1 @@ +# nyancat diff --git a/nyancat.c b/nyancat.c @@ -0,0 +1,565 @@ +/* + * Copyright (c) 2020 Santtu Lakkala + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include <math.h> + +#include <fcntl.h> +#include <limits.h> +#include <unistd.h> + +#include <termios.h> +#include <sys/ioctl.h> + +#include <unicode/uchar.h> +#include <unicode/utf8.h> + +#define PI 3.1415926535898 + +struct lolcat { + int x; + int y; + int w; + int h; + int sx; + int sy; + int fg; + int bg; + char bold; + char reverse; + char underline; + char was_bold; + char was_reverse; + char was_underline; + + void (*write)(const char *buffer, size_t buflen, void *data); + void *write_data; +}; + +int nyan(int x, int y) +{ + return sin(x * PI / 18) * 18 + y * 18; +} + +int rainbow(float freq, int i) +{ + return ((int)(sin(freq * i + 0) * 127 + 128)) << 16 | + ((int)(sin(freq * i + 2 * PI / 3) * 127 + 128)) << 8 | + ((int)(sin(freq * i + 4 * PI / 3) * 127 + 128)); +} + +static size_t strnspn_printable(const char *str, size_t len) +{ + size_t rv = 0; + while (rv < len && (str[rv] & ~0x9f)) + rv++; + return rv; +} + +static size_t strnspn(const char *str, size_t len, const char *allowed) +{ + uint32_t allowed_mask[(1U << CHAR_BIT) / 32] = { 0 }; + size_t i; + for (; *allowed; allowed++) + allowed_mask[*(unsigned char *)allowed / 32] |= 1 << + (*(unsigned char *)allowed & 31); + for (i = 0; i < len; i++) + if (!(allowed_mask[((unsigned char *)str)[i] / 32] & 1 << + (((unsigned char *)str)[i] & 31))) + break; + return i; +} + +static void strtok_foreach(const char *str, int len, char delim, + int (*cb)(const char *tok, int toklen, void *data), + void *data) +{ + const char *i = str; + const char *sep; + + for (sep = memchr(str, delim, len); + sep; + i = sep + 1, sep = memchr(i, delim, len - (i - str))) + if (cb(i, sep - i, data)) + return; + cb(i, len - (i - str), data); +} + +struct strtok_int_data { + int (*cb)(int val, void *data); + void *data; +}; + +static int _strtok_fi_cb(const char *tok, int toklen, void *data) +{ + struct strtok_int_data *fidata = data; + int val = 0; + + while (toklen-- && *tok >= '0' && *tok <= '9') + val = val * 10 + *tok++ - '0'; + return fidata->cb(val, fidata->data); +} + +static void strtok_foreach_int(const char *str, int len, char delim, + int (*cb)(int val, void *data), void *data) +{ + struct strtok_int_data fidata; + + fidata.cb = cb; + fidata.data = data; + + strtok_foreach(str, len, delim, _strtok_fi_cb, &fidata); +} + +struct lolcat *lolcat_init(void (*write_cb)(const char *buffer, size_t buflen, + void *data), void *cbdata) +{ + struct lolcat *rv = calloc(1, sizeof(struct lolcat)); + + rv->write = write_cb; + rv->write_data = cbdata; + rv->fg = 0xc0c0c0; + rv->bg = 0x000000; + + return rv; +} + +static int _lc_arg_m(int arg, void *data) +{ + struct lolcat *lc = data; + + if (arg == 38 || arg == 48) + return 1; + + switch (arg) { + case 0: + lc->bold = 0; + lc->reverse = 0; + lc->fg = 0xc0c0c0; + lc->bg = 0x000000; + break; + case 1: + lc->bold = 1; + break; + case 4: + lc->underline = 1; + break; + case 7: + lc->reverse = 1; + break; + case 24: + lc->underline = 0; + break; + case 30: + case 31: + case 32: + case 33: + case 34: + case 35: + case 36: + lc->fg = ((arg - 30) & 1) << 23 | + ((arg - 30) & 2) << 14 | + ((arg - 30) & 4) << 5; + break; + case 37: + lc->fg = 0xc0c0c0; + break; + case 40: + case 41: + case 42: + case 43: + case 44: + case 45: + case 46: + lc->fg = ((arg - 40) & 1) << 23 | + ((arg - 40) & 2) << 14 | + ((arg - 40) & 4) << 5; + break; + + default: + break; + } + + return 0; +} + +static inline int _bold(int color) +{ + if (!color) + return 0x808080; + if (color == 0xc0c0c0) + return 0xffffff; + return (color << 1) - (color >> 7); +} + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) < (b) ? (b) : (a)) +#define CLAMP(a, b, c) MIN(MAX(a, b), c) +static int _add(int a, int b, double amount) +{ + int r = (a >> 16) + ((b >> 16) - 0xc0) * amount; + int g = ((a >> 8) & 0xff) + (((b >> 8) & 0xff) - 0xc0) * amount; + int bl = (a & 0xff) + ((b & 0xff) - 0xc0) * amount; + return CLAMP(r, 0, 255) << 16 | + CLAMP(g, 0, 255) << 8 | + CLAMP(bl, 0, 255); +} + +static void _lc_colorize(struct lolcat *lc, const char *u8_char, size_t len) +{ + char buffer[128] = "\x1b["; + int col; + char *bw = buffer + 2; + + if (len == 1 && u8_char[0] == ' ' && + !lc->reverse && !lc->was_reverse) { + lc->write(" ", 1, lc->write_data); + return; + } + + if ((lc->was_bold && !lc->bold) || + (lc->was_reverse && !lc->reverse)) { + lc->was_underline = 0; + lc->was_bold = 0; + lc->was_reverse = 0; + *bw++ = '0'; + *bw++ = ';'; + } + if (lc->was_underline != lc->underline) { + if (lc->was_underline) + *bw++ = '2'; + *bw++ = '4'; + *bw++ = ';'; + lc->was_underline = lc->underline; + } + if (lc->bold && !lc->was_bold) { + *bw++ = '1'; + *bw++ = ';'; + lc->was_bold = 1; + } + if (lc->reverse && !lc->was_reverse) { + *bw++ = '7'; + *bw++ = ';'; + lc->was_reverse = 1; + } + + if (lc->bold) + col = _bold(lc->fg); + else + col = lc->fg; + col = _add(rainbow(0.03, nyan(lc->x, lc->y)), col, 0.5); + + bw += sprintf(bw, "38;2;%d;%d;%dm%.*s", + col >> 16, + (col >> 8) & 255, + col & 255, (int)len, u8_char); + + lc->write(buffer, bw - buffer, lc->write_data); +} + +struct strnsplit_int_data { + va_list args; + int n; +}; + +static int _strnsplit_int_cb(int val, void *data) +{ + struct strnsplit_int_data *ssidata = data; + int *a = va_arg(ssidata->args, int *); + *a = val; + return !--ssidata->n; +} + +static int strnsplit_int(const char *str, size_t len, char delim, int n, ...) +{ + struct strnsplit_int_data data; + va_start(data.args, n); + data.n = n; + strtok_foreach_int(str, len, delim, _strnsplit_int_cb, &data); + va_end(data.args); + + return n - data.n; +} + +ssize_t lc_process(struct lolcat *lc, const char *buffer, int32_t len) +{ + int32_t i = 0; + int32_t ip = 0; + while (i < len) { + UChar32 c; + int eaw; + + ip = i; + U8_NEXT(buffer, i, len, c); + + if (c < 0) + return ip; + + if (c == '\x1b') { + if (!buffer[i]) + return ip; + if (buffer[i] == '[') { + size_t n_args; + char cmd; + if (buffer[i + 1] == '?') { + n_args = strnspn(buffer + i + 2, + len - i - 2, + "0123456789;"); + if (!buffer[i + 2 + n_args]) + return ip; + lc->write(buffer + ip, n_args + 4, + lc->write_data); + i += n_args + 3; + continue; + } + n_args = strnspn(buffer + i + 1, len - i - 1, + "0123456789;"); + cmd = buffer[i + 1 + n_args]; + + if (!cmd) + return ip; + + if (cmd == 'H') { + int x, y; + strnsplit_int(buffer + i + 1, + len - i - 1, ';', + 2, &y, &x); + lc->x = x - 1; + lc->y = y - 1; + } + if (cmd >= 'A' && cmd <= 'D') { + int n = 1; + int dirs[][2] = { + { 0, -1 }, + { 0, 1 }, + { 1, 0 }, + { -1, 0 } + }; + strnsplit_int(buffer + i + 1, + len - i - 1, ';', 1, + &n); + lc->x += dirs[cmd - 'A'][0] * n; + lc->y += dirs[cmd - 'A'][1] * n; + } + if (cmd == 'G') { + int x; + strnsplit_int(buffer + i + 1, + len - i - 1, ';', 1, + &x); + lc->x = x; + } + if (cmd == 'd') { + int y; + strnsplit_int(buffer + i + 1, + len - i - 1, ';', 1, + &y); + lc->y = y; + } + if (cmd == 'J') { + if (atoi(buffer + i + 1) == 2) { + lc->x = 0; + lc->y = 0; + } + } + if (cmd == 'm') { + if (n_args == 0) + _lc_arg_m(0, lc); + else + strtok_foreach_int( + buffer + i + 1, + n_args, ';', + _lc_arg_m, lc); + i += n_args + 2; + continue; + } + if (cmd == 's') { + lc->sx = lc->x; + lc->sy = lc->y; + } + if (cmd == 'u') { + lc->x = lc->sx; + lc->y = lc->sy; + } + lc->write(buffer + ip, n_args + 3, + lc->write_data); + i += n_args + 2; + continue; + } + if (buffer[i] == '(') { + size_t n_args; + char cmd; + n_args = strnspn(buffer + i + 1, len - i - 1, + "0123456789;"); + cmd = buffer[i + 1 + n_args]; + + if (!cmd) + return ip; + + lc->write(buffer + ip, n_args + 3, + lc->write_data); + i += n_args + 2; + continue; + } + if (buffer[i] == ']') { + ssize_t dlen = strnspn_printable( + buffer + i + 1, + len - i - 1); + + if (dlen + i + 1 == len) + return ip; + if (buffer[i + dlen + 1] == '\007' || + buffer[i + dlen + 1] == '\x9c') { + lc->write(buffer + ip, dlen + 3, + lc->write_data); + i += dlen + 2; + continue; + } + } + if (buffer[i] == '>') { + lc->write(buffer + ip, 2, lc->write_data); + i += 1; + continue; + } + } else if (c == '\t') { + lc->x = (lc->x + 8) & ~(int)0x7; + lc->write("\t", 1, lc->write_data); + continue; + } else if (c == '\r') { + lc->x = 0; + lc->y++; + lc->write("\r", 1, lc->write_data); + continue; + } else if (c == '') { + if (lc->x) + lc->x--; + lc->write("", 1, lc->write_data); + continue; + } else if (c == '\n') { + lc->x = 0; + lc->y++; + lc->write("\n", 1, lc->write_data); + continue; + } + + eaw = u_getIntPropertyValue(c, UCHAR_EAST_ASIAN_WIDTH); + + if (lc->w && lc->x + (eaw == U_EA_WIDE || + eaw == U_EA_FULLWIDTH) >= lc->w) { + lc->x = 0; + lc->y++; + } + + _lc_colorize(lc, buffer + ip, i - ip); + + if (eaw == U_EA_WIDE || + eaw == U_EA_FULLWIDTH) + lc->x++; + lc->x++; + } + + return i; +} + +void _write(const char *data, size_t len, void *user_data) +{ + fwrite(data, 1, len, user_data); +} + +void lolcat_set_size(struct lolcat *lc, int w, int h) +{ + lc->w = w; + lc->h = h; +} + +int main(int argc, char **argv) +{ + struct lolcat *lc = lolcat_init(_write, stdout); + char buffer[2048]; + size_t used = 0; + struct termios old_tio = { 0 }, new_tio; + int i; + + struct winsize ws = { 0 }; + + if (!ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) || + !ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws)) + lolcat_set_size(lc, ws.ws_col, ws.ws_row); + + if (argc < 2) { + tcgetattr(STDIN_FILENO, &old_tio); + + new_tio = old_tio; + + new_tio.c_lflag &= (~ICANON & ~ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &new_tio); + + while (TRUE) { + ssize_t bytes = read(STDIN_FILENO, buffer + used, + sizeof(buffer) - used); + size_t processed; + + if (bytes <= 0) + break; + + used += bytes; + processed = lc_process(lc, buffer, used); + + if (processed != used) + memmove(buffer, buffer + processed, + used - processed); + used -= processed; + + fflush(stdout); + } + + fflush(stdout); + tcsetattr(STDIN_FILENO, TCSANOW, &old_tio); + } else { + for (i = 1; i < argc; i++) { + int fd; + if (!strcmp(argv[i], "-")) + fd = STDIN_FILENO; + else + fd = open(argv[i], O_RDONLY); + while (TRUE) { + ssize_t bytes = read(fd, buffer + used, + sizeof(buffer) - used); + size_t processed; + + if (bytes <= 0) + break; + + used += bytes; + processed = lc_process(lc, buffer, used); + + if (processed != used) + memmove(buffer, buffer + processed, + used - processed); + used -= processed; + + fflush(stdout); + } + if (fd != STDIN_FILENO) + close(fd); + } + } + + fprintf(stdout, "\x1b[0m"); + + return 0; +}