commit 6ed6bfad7b89d4fa4e83dbf7cf9cf4565788b7f4
Author: Santtu Lakkala <inz@inz.fi>
Date: Wed, 15 Jul 2020 14:54:51 +0300
Initial import
Diffstat:
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;
+}