nyancat

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

nyancat.c (21629B)


      1 /*
      2  * Copyright (c) 2020 Santtu Lakkala
      3  *
      4  * Permission to use, copy, modify, and/or distribute this software for any
      5  * purpose with or without fee is hereby granted.
      6  *
      7  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
      8  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
      9  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     10  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     11  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     12  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
     13  * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     14  */
     15 
     16 #define _POSIX_C_SOURCE 199309L
     17 
     18 #include <stdarg.h>
     19 #include <stdlib.h>
     20 #include <stdio.h>
     21 #include <string.h>
     22 #include <stdbool.h>
     23 #include <stdint.h>
     24 
     25 #include <sys/types.h>
     26 #include <signal.h>
     27 #include <time.h>
     28 
     29 #include <math.h>
     30 
     31 #include <fcntl.h>
     32 #include <limits.h>
     33 #include <unistd.h>
     34 
     35 #include <termios.h>
     36 #include <sys/ioctl.h>
     37 
     38 #include <unicode/uchar.h>
     39 #include <unicode/utf8.h>
     40 
     41 #define PI 3.1415926535898
     42 #define N(a) ((sizeof(a) / sizeof(*(a))))
     43 #define MIN(a, b) ((a) < (b) ? (a) : (b))
     44 #define MAX(a, b) ((a) < (b) ? (b) : (a))
     45 #define CLAMP(a, b, c) MIN(MAX(a, b), c)
     46 #define GRADSTEPS 16
     47 
     48 struct lolcat {
     49 	int x;
     50 	int y;
     51 	int w;
     52 	int h;
     53 	int sx;
     54 	int sy;
     55 	bool transpose;
     56 	bool bash_escape;
     57 	uint32_t fg;
     58 	uint32_t bg;
     59 	bool bg_set;
     60 	bool bold;
     61 	bool reverse;
     62 	bool underline;
     63 	bool was_bold;
     64 	bool was_reverse;
     65 	bool was_underline;
     66 	bool was_bgset;
     67 	bool xterm_workaround;
     68 	enum { TRUECOLOR, INDEXED256, INDEXED16,
     69 		NYANANIMATE, GRADANIMATE, STOP } mode;
     70 	enum { NONE,
     71 		FCOLOR, FI256, FR, FG, FB,
     72 		BCOLOR, BI256, BR, BG, BB } cstate;
     73 
     74 	bool (*write)(const char *buffer, size_t buflen, void *data);
     75 	void *write_data;
     76 };
     77 
     78 int rainbow(float freq, int i)
     79 {
     80 	return ((int)(sin(freq * i + 0) * 127 + 128)) << 16 |
     81 		((int)(sin(freq * i + 2 * PI / 3) * 127 + 128)) << 8 |
     82 		((int)(sin(freq * i + 4 * PI / 3) * 127 + 128));
     83 }
     84 
     85 const uint32_t animation_colors[] = {
     86 	0xff0000,
     87 	0xffaf00,
     88 	0xffff00,
     89 	0x87ff00,
     90 	0x0087ff,
     91 	0x0000af
     92 };
     93 
     94 size_t alter_palette(char *buffer, int index, uint32_t color)
     95 {
     96 	return sprintf(buffer, "\x1b]4;%d;rgb:%02x/%02x/%02x\x1b\\",
     97 		       index,
     98 		       (unsigned)(color >> 16),
     99 		       (unsigned)((color >> 8) & 0xff),
    100 		       (unsigned)(color & 0xff));
    101 }
    102 
    103 bool animate_gradpalette(struct lolcat *lc, int offset)
    104 {
    105 	size_t i;
    106 
    107 	for (i = 0; i < GRADSTEPS; i++) {
    108 		char buffer[128];
    109 		if (!lc->write(buffer,
    110 			       alter_palette(buffer,
    111 					     16 + (i + offset) % GRADSTEPS,
    112 					     rainbow(PI * 2 / GRADSTEPS, i)),
    113 			       lc->write_data))
    114 			return false;
    115 	}
    116 
    117 	if (lc->xterm_workaround)
    118 		lc->write("\x1b[s\x1b[u", 6, lc->write_data);
    119 
    120 	return true;
    121 }
    122 
    123 bool animate_palette(struct lolcat *lc, bool up)
    124 {
    125 	size_t i;
    126 
    127 	for (i = 0; i < N(animation_colors); i++) {
    128 		char buffer[128];
    129 		if (!lc->write(buffer,
    130 			       alter_palette(buffer,
    131 					     16 +
    132 					     (3 * i + N(animation_colors) *
    133 					      3 - 1 - !!up) %
    134 					     (N(animation_colors) * 3),
    135 					     animation_colors[i]),
    136 			       lc->write_data) ||
    137 		    !lc->write(buffer,
    138 			       alter_palette(buffer,
    139 					     16 +
    140 					     (3 * i + 1 + !!up)
    141 					     % (N(animation_colors) * 3),
    142 					     animation_colors[i]),
    143 			       lc->write_data))
    144 			return false;
    145 	}
    146 
    147 	return true;
    148 }
    149 
    150 void prepare_palette(struct lolcat *lc)
    151 {
    152 	size_t i;
    153 
    154 	for (i = 0; i < N(animation_colors); i++) {
    155 		char buffer[128];
    156 		lc->write(buffer, alter_palette(buffer, 3 * i + 16,
    157 						animation_colors[i]),
    158 			  lc->write_data);
    159 	}
    160 
    161 	animate_palette(lc, false);
    162 }
    163 
    164 int nyan(int x, int y, bool transpose)
    165 {
    166 	if (transpose)
    167 		return sin(y * PI / 18) * 18 + x * 18;
    168 	return sin(x * PI / 18) * 18 + y * 18;
    169 }
    170 
    171 static size_t strnspn_printable(const char *str, size_t len)
    172 {
    173 	size_t rv = 0;
    174 	while (rv < len && (str[rv] & ~0x9f))
    175 		rv++;
    176 	return rv;
    177 }
    178 
    179 static size_t strnspn(const char *str, size_t len, const char *allowed)
    180 {
    181 	uint32_t allowed_mask[(1U << CHAR_BIT) / 32] = { 0 };
    182 	size_t i;
    183 	for (; *allowed; allowed++)
    184 		allowed_mask[*(unsigned char *)allowed / 32] |= 1 <<
    185 			(*(unsigned char *)allowed & 31);
    186 	for (i = 0; i < len; i++)
    187 		if (!(allowed_mask[((unsigned char *)str)[i] / 32] & 1 <<
    188 		      (((unsigned char *)str)[i] & 31)))
    189 			break;
    190 	return i;
    191 }
    192 
    193 static void strtok_foreach(const char *str, int len, char delim,
    194 			   int (*cb)(const char *tok, int toklen, void *data),
    195 			   void *data)
    196 {
    197 	const char *i = str;
    198 	const char *sep;
    199 
    200 	for (sep = memchr(str, delim, len);
    201 	     sep;
    202 	     i = sep + 1, sep = memchr(i, delim, len - (i - str)))
    203 		if (cb(i, sep - i, data))
    204 			return;
    205 	cb(i, len - (i - str), data);
    206 }
    207 
    208 struct strtok_int_data {
    209 	int (*cb)(int val, void *data);
    210 	void *data;
    211 };
    212 
    213 static int _strtok_fi_cb(const char *tok, int toklen, void *data)
    214 {
    215 	struct strtok_int_data *fidata = data;
    216 	int val = 0;
    217 
    218 	while (toklen-- && *tok >= '0' && *tok <= '9')
    219 		val = val * 10 + *tok++ - '0';
    220 	return fidata->cb(val, fidata->data);
    221 }
    222 
    223 static void strtok_foreach_int(const char *str, int len, char delim,
    224 			       int (*cb)(int val, void *data), void *data)
    225 {
    226 	struct strtok_int_data fidata;
    227 
    228 	fidata.cb = cb;
    229 	fidata.data = data;
    230 
    231 	strtok_foreach(str, len, delim, _strtok_fi_cb, &fidata);
    232 }
    233 
    234 struct lolcat *lolcat_init(bool (*write_cb)(const char *buffer, size_t buflen,
    235 					    void *data), void *cbdata)
    236 {
    237 	struct lolcat *rv = calloc(1, sizeof(struct lolcat));
    238 
    239 	rv->write = write_cb;
    240 	rv->write_data = cbdata;
    241 	rv->fg = 0xc0c0c0;
    242 	rv->bg = 0x000000;
    243 	rv->mode = TRUECOLOR;
    244 
    245 	return rv;
    246 }
    247 
    248 static char *strcpy_alter(char *to, const char *from, char f, char t)
    249 {
    250 	char *w = to;
    251 	for (; *from; from++) {
    252 		if (*from == f)
    253 			*w++ = t;
    254 		else
    255 			*w++ = *from;
    256 	}
    257 
    258 	return to;
    259 }
    260 
    261 static inline int _bold(int color)
    262 {
    263 	if (!color)
    264 		return 0x808080;
    265 	if (color == 0xc0c0c0)
    266 		return 0xffffff;
    267 	return (color << 1) - (color >> 7);
    268 }
    269 
    270 static uint32_t _color8(uint8_t color)
    271 {
    272 	if (color == 7)
    273 		return 0xc0c0c0;
    274 	return (color & 1) << 23 |
    275 		(color & 2) << 14 |
    276 		(color & 4) << 5;
    277 }
    278 
    279 static uint32_t _ccube(uint8_t idx)
    280 {
    281 	if (!idx)
    282 		return 0;
    283 	return 255 - 40 * (5 - idx);
    284 }
    285 
    286 static uint32_t _color256(uint8_t color)
    287 {
    288 	if (color < 8)
    289 		return _color8(color);
    290 	if (color < 16)
    291 		return _bold(_color8(color));
    292 	if (color < 232)
    293 		return _ccube((color - 16) / 36) << 16 |
    294 			_ccube((color - 16) / 6 % 6) << 8 |
    295 			_ccube((color - 16) % 6);
    296 	return 0x010101 * (0xee - 10 * (255 - color));
    297 }
    298 
    299 static int _lc_arg_m(int arg, void *data)
    300 {
    301 	struct lolcat *lc = data;
    302 
    303 	switch (lc->cstate) {
    304 	case FCOLOR:
    305 		if (arg == 2)
    306 			lc->cstate = FR;
    307 		else if (arg == 5)
    308 			lc->cstate = FI256;
    309 		else
    310 			return 1;
    311 		lc->fg = 0;
    312 		return 0;
    313 	case FI256:
    314 		lc->fg = _color256(arg);
    315 		lc->cstate = NONE;
    316 		return 0;
    317 	case FB:
    318 		lc->cstate = -1;
    319 		/* fall-through */
    320 	case FR:
    321 	case FG:
    322 		lc->fg = lc->fg << 8 | arg;
    323 		lc->cstate++;
    324 		return 0;
    325 
    326 	case BCOLOR:
    327 		if (arg == 2)
    328 			lc->cstate = BR;
    329 		else if (arg == 5)
    330 			lc->cstate = BI256;
    331 		else
    332 			return 1;
    333 		lc->bg = 0;
    334 		return 0;
    335 	case BI256:
    336 		lc->bg = _color256(arg);
    337 		lc->bg_set = true;
    338 		lc->cstate = NONE;
    339 		return 0;
    340 	case BB:
    341 		lc->cstate = -1;
    342 		lc->bg_set = true;
    343 		/* fall-through */
    344 	case BR:
    345 	case BG:
    346 		lc->bg = lc->bg << 8 | arg;
    347 		lc->cstate++;
    348 		return 0;
    349 	default:
    350 		break;
    351 	}
    352 
    353 	switch (arg) {
    354 	case 0:
    355 		lc->bold = false;
    356 		lc->reverse = false;
    357 		lc->fg = 0xc0c0c0;
    358 		lc->bg = 0x000000;
    359 		lc->bg_set = false;
    360 		break;
    361 	case 1:
    362 		lc->bold = true;
    363 		break;
    364 	case 4:
    365 		lc->underline = true;
    366 		break;
    367 	case 7:
    368 		lc->reverse = true;
    369 		break;
    370 	case 24:
    371 		lc->underline = false;
    372 		break;
    373 	case 30:
    374 	case 31:
    375 	case 32:
    376 	case 33:
    377 	case 34:
    378 	case 35:
    379 	case 36:
    380 		lc->fg = ((arg - 30) & 1) << 23 |
    381 			((arg - 30) & 2) << 14 |
    382 			((arg - 30) & 4) << 5;
    383 		break;
    384 	case 37:
    385 		lc->fg = 0xc0c0c0;
    386 		break;
    387 	case 38:
    388 		lc->cstate = FCOLOR;
    389 		break;
    390 	case 40:
    391 	case 41:
    392 	case 42:
    393 	case 43:
    394 	case 44:
    395 	case 45:
    396 	case 46:
    397 		lc->bg = ((arg - 40) & 1) << 23 |
    398 			((arg - 40) & 2) << 14 |
    399 			((arg - 40) & 4) << 5;
    400 		lc->bg_set = true;
    401 		break;
    402 	case 47:
    403 		lc->bg = 0xc0c0c0;
    404 		lc->bg_set = true;
    405 		break;
    406 	case 48:
    407 		lc->cstate = BCOLOR;
    408 		break;
    409 
    410 	default:
    411 		break;
    412 	}
    413 
    414 	return 0;
    415 }
    416 
    417 static int _add(int a, int b, double amount)
    418 {
    419 	int r = (a >> 16) + ((b >> 16) - 0xc0) * amount;
    420 	int g = ((a >> 8) & 0xff) + (((b >> 8) & 0xff) - 0xc0) * amount;
    421 	int bl = (a & 0xff) + ((b & 0xff) - 0xc0) * amount;
    422 	return CLAMP(r, 0, 255) << 16 |
    423 		CLAMP(g, 0, 255) << 8 |
    424 		CLAMP(bl, 0, 255);
    425 }
    426 
    427 static inline uint8_t absd(uint8_t a, uint8_t b)
    428 {
    429 	if (a > b)
    430 		return a - b;
    431 	return b - a;
    432 }
    433 
    434 static inline int _index16(uint32_t col, bool *bright)
    435 {
    436 	uint8_t r = col >> 16;
    437 	uint8_t g = (col >> 8) & 255;
    438 	uint8_t b = col & 255;
    439 
    440 	uint8_t ri = ((uint16_t)r + 64) * 2 / 256;
    441 	uint8_t gi = ((uint16_t)g + 64) * 2 / 256;
    442 	uint8_t bi = ((uint16_t)b + 64) * 2 / 256;
    443 
    444 	if (ri == 2 || gi == 2 || bi == 2) {
    445 		ri = r > 127U;
    446 		gi = g > 127U;
    447 		bi = b > 127U;
    448 		if (bright)
    449 			*bright = true;
    450 	} else {
    451 		if (bright)
    452 			*bright = false;
    453 	}
    454 
    455 	return ri | bi << 1 | gi << 2;
    456 }
    457 
    458 static inline int _index256(uint32_t col)
    459 {
    460 	uint8_t r = col >> 16;
    461 	uint8_t g = (col >> 8) & 255;
    462 	uint8_t b = col & 255;
    463 
    464 	uint8_t cr = r < 35 ? 0 : (r - 35) / 40;
    465 	uint8_t cb = b < 35 ? 0 : (b - 35) / 40;
    466 	uint8_t cg = g < 35 ? 0 : (g - 35) / 40;
    467 
    468 	uint8_t gs = (0xee - ((uint16_t)r + g + b) / 3) / 10;
    469 
    470 	if (gs > 23 ||
    471 	    absd(cr * 40 + !!cr * 55, r) +
    472 	    absd(cg * 40 + !!cr * 55, g) +
    473 	    absd(cb * 40 + !!cr * 55, b) <=
    474 	    absd(0xee - 10 * gs, r) +
    475 	    absd(0xee - 10 * gs, g) +
    476 	    absd(0xee - 10 * gs, b))
    477 		return cr * 36 + cg * 6 + cb + 16;
    478 	return 255 - gs;
    479 }
    480 
    481 static void _lc_colorize(struct lolcat *lc, const char *u8_char, size_t len)
    482 {
    483 	char buffer[128] = "\x1b[";
    484 	int col;
    485 	char *bw = buffer + 2;
    486 
    487 	if (len == 1 && u8_char[0] == ' ' &&
    488 	    !lc->reverse && !lc->was_reverse &&
    489 	    !lc->bg_set && !lc->was_bgset) {
    490 		lc->write(" ", 1, lc->write_data);
    491 		return;
    492 	}
    493 
    494 	if (lc->mode == NYANANIMATE) {
    495 		if (lc->transpose)
    496 			col = 16 + (((lc->x / 2 % N(animation_colors)) * 3) +
    497 				    (lc->x & 1) *
    498 				    (1 + (((lc->y + 25) / 30) & 1)));
    499 		else
    500 			col = 16 + (((lc->y / 2 % N(animation_colors)) * 3) +
    501 				    (lc->y & 1) *
    502 				    (1 + (((lc->x + 25) / 30) & 1)));
    503 	} else if (lc->mode == GRADANIMATE) {
    504 		col = 16 + (lc->x + lc->y) % GRADSTEPS;
    505 	} else {
    506 		if (lc->bold)
    507 			col = _bold(lc->fg);
    508 		else
    509 			col = lc->fg;
    510 		col = _add(rainbow(0.03,
    511 				   nyan(lc->x, lc->y, lc->transpose)),
    512 			   col, 0.5);
    513 
    514 		if (lc->mode == INDEXED16)
    515 			col = _index16(col, &lc->bold);
    516 		else if (lc->mode == INDEXED256)
    517 			col = _index256(col);
    518 	}
    519 
    520 	if ((lc->was_bold && !lc->bold) ||
    521 	    (lc->was_reverse && !lc->reverse) ||
    522 	    (lc->was_bgset && !lc->bg_set)) {
    523 		lc->was_underline = false;
    524 		lc->was_bold = false;
    525 		lc->was_reverse = false;
    526 		lc->was_bgset = false;
    527 		*bw++ = '0';
    528 		*bw++ = ';';
    529 	}
    530 	if (lc->was_underline != lc->underline) {
    531 		if (lc->was_underline)
    532 			*bw++ = '2';
    533 		*bw++ = '4';
    534 		*bw++ = ';';
    535 		lc->was_underline = lc->underline;
    536 	}
    537 	if (lc->bold && !lc->was_bold) {
    538 		*bw++ = '1';
    539 		*bw++ = ';';
    540 		lc->was_bold = true;
    541 	}
    542 	if (lc->reverse && !lc->was_reverse) {
    543 		*bw++ = '7';
    544 		*bw++ = ';';
    545 		lc->was_reverse = true;
    546 	}
    547 
    548 	if (lc->bg_set) {
    549 		if (lc->mode == INDEXED16)
    550 			bw += sprintf(bw, "4%d;",
    551 				      _index16(lc->bg, NULL));
    552 		else if (lc->mode == INDEXED256)
    553 			bw += sprintf(bw, "48;5;%d;",
    554 				      _index256(lc->bg));
    555 		else
    556 			bw += sprintf(bw, "48;2;%d;%d;%d;",
    557 				      lc->bg >> 16,
    558 				      (lc->bg >> 8) & 255,
    559 				      lc->bg & 255);
    560 		lc->was_bgset = true;
    561 	}
    562 
    563 	if (lc->mode == INDEXED16)
    564 		bw += sprintf(bw, "3%dm",
    565 			      col);
    566 	else if (lc->mode == INDEXED256 ||
    567 		 lc->mode == GRADANIMATE ||
    568 		 lc->mode == NYANANIMATE)
    569 		bw += sprintf(bw, "38;5;%dm",
    570 			      col);
    571 	else
    572 		bw += sprintf(bw, "38;2;%d;%d;%dm",
    573 			      col >> 16,
    574 			      (col >> 8) & 255,
    575 			      col & 255);
    576 
    577 	if (lc->bash_escape)
    578 		lc->write("\\[", 2, lc->write_data);
    579 	lc->write(buffer, bw - buffer, lc->write_data);
    580 	if (lc->bash_escape)
    581 		lc->write("\\]", 2, lc->write_data);
    582 	lc->write(u8_char, len, lc->write_data);
    583 }
    584 
    585 struct strnsplit_int_data {
    586 	va_list args;
    587 	int n;
    588 };
    589 
    590 static int _strnsplit_int_cb(int val, void *data)
    591 {
    592 	struct strnsplit_int_data *ssidata = data;
    593 	int *a = va_arg(ssidata->args, int *);
    594 	*a = val;
    595 	return !--ssidata->n;
    596 }
    597 
    598 static int strnsplit_int(const char *str, size_t len, char delim, int n, ...)
    599 {
    600 	struct strnsplit_int_data data;
    601 	va_start(data.args, n);
    602 	data.n = n;
    603 	strtok_foreach_int(str, len, delim, _strnsplit_int_cb, &data);
    604 	va_end(data.args);
    605 
    606 	return n - data.n;
    607 }
    608 
    609 ssize_t lc_process(struct lolcat *lc, const char *buffer, size_t len)
    610 {
    611 	size_t i = 0;
    612 	size_t ip = 0;
    613 	while (i < len) {
    614 		UChar32 c;
    615 		int eaw;
    616 
    617 		ip = i;
    618 		U8_NEXT(buffer, i, len, c);
    619 
    620 		if (c < 0)
    621 			return ip;
    622 
    623 		if (c == '\x1b') {
    624 			lc->cstate = NONE;
    625 			if (i >= len)
    626 				return ip;
    627 			if (buffer[i] == '[') {
    628 				size_t n_args;
    629 				char cmd;
    630 				if (i + 1 >= len)
    631 					return ip;
    632 				if (buffer[i + 1] == '?') {
    633 					n_args = strnspn(buffer + i + 2,
    634 							 len - i - 2,
    635 							 "0123456789;");
    636 					if (i + 2 + n_args >= len)
    637 						return ip;
    638 					lc->write(buffer + ip, n_args + 4,
    639 						  lc->write_data);
    640 					i += n_args + 3;
    641 					continue;
    642 				}
    643 				n_args = strnspn(buffer + i + 1, len - i - 1,
    644 						 "0123456789;");
    645 				if (i + 1 + n_args >= len)
    646 					return ip;
    647 				cmd = buffer[i + 1 + n_args];
    648 
    649 				if (!cmd)
    650 					return ip;
    651 
    652 				if (cmd == 'H') {
    653 					int x, y;
    654 					strnsplit_int(buffer + i + 1,
    655 						      len - i - 1, ';',
    656 						      2, &y, &x);
    657 					lc->x = x - 1;
    658 					lc->y = y - 1;
    659 				}
    660 				if (cmd >= 'A' && cmd <= 'D') {
    661 					int n = 1;
    662 					int dirs[][2] = {
    663 						{ 0, -1 },
    664 						{ 0, 1 },
    665 						{ 1, 0 },
    666 						{ -1, 0 }
    667 					};
    668 					strnsplit_int(buffer + i + 1,
    669 						      len - i - 1, ';', 1,
    670 						      &n);
    671 					lc->x += dirs[cmd - 'A'][0] * n;
    672 					lc->y += dirs[cmd - 'A'][1] * n;
    673 				}
    674 				if (cmd == 'G') {
    675 					int x;
    676 					strnsplit_int(buffer + i + 1,
    677 						      len - i - 1, ';', 1,
    678 						      &x);
    679 					lc->x = x - 1;
    680 				}
    681 				if (cmd == 'd') {
    682 					int y;
    683 					strnsplit_int(buffer + i + 1,
    684 						      len - i - 1, ';', 1,
    685 						      &y);
    686 					lc->y = y - 1;
    687 				}
    688 				if (cmd == 'J') {
    689 					if (atoi(buffer + i + 1) == 2) {
    690 						lc->x = 0;
    691 						lc->y = 0;
    692 					}
    693 				}
    694 				if (cmd == 'm') {
    695 					if (n_args == 0)
    696 						_lc_arg_m(0, lc);
    697 					else
    698 						strtok_foreach_int(
    699 							buffer + i + 1,
    700 							n_args, ';',
    701 							_lc_arg_m, lc);
    702 					i += n_args + 2;
    703 					continue;
    704 				}
    705 				if (cmd == 's') {
    706 					lc->sx = lc->x;
    707 					lc->sy = lc->y;
    708 				}
    709 				if (cmd == 'u') {
    710 					lc->x = lc->sx;
    711 					lc->y = lc->sy;
    712 				}
    713 				lc->write(buffer + ip, n_args + 3,
    714 					  lc->write_data);
    715 				i += n_args + 2;
    716 				continue;
    717 			}
    718 			if (buffer[i] == '(') {
    719 				size_t n_args;
    720 				n_args = strnspn(buffer + i + 1, len - i - 1,
    721 						 "0123456789;");
    722 				if (i + 1 + n_args >= len)
    723 					return ip;
    724 
    725 				lc->write(buffer + ip, n_args + 3,
    726 					  lc->write_data);
    727 				i += n_args + 2;
    728 				continue;
    729 			}
    730 			if (buffer[i] == ']') {
    731 				ssize_t dlen = strnspn_printable(
    732 					buffer + i + 1,
    733 					len - i - 1);
    734 
    735 				if (dlen + i + 1 == len)
    736 					return ip;
    737 				if (buffer[i + dlen + 1] == '\007' ||
    738 				    buffer[i + dlen + 1] == '\x9c') {
    739 					lc->write(buffer + ip, dlen + 3,
    740 						  lc->write_data);
    741 					i += dlen + 2;
    742 					continue;
    743 				}
    744 				if (buffer[i + dlen + 1] == '\x1b') {
    745 					if (dlen + i + 2 == len)
    746 						return ip;
    747 					if (buffer[i + dlen + 2] == '\\')
    748 						lc->write(buffer + ip,
    749 							  dlen + 4,
    750 							  lc->write_data);
    751 					i += dlen + 3;
    752 					continue;
    753 				}
    754 			}
    755 			if (buffer[i] == '>') {
    756 				lc->write(buffer + ip, 2, lc->write_data);
    757 				i += 1;
    758 				continue;
    759 			}
    760 		} else if (c == '\t') {
    761 			lc->x = (lc->x + 8) & ~(int)0x7;
    762 			lc->write("\t", 1, lc->write_data);
    763 			continue;
    764 		} else if (c == '\r') {
    765 			lc->x = 0;
    766 			lc->y++;
    767 			lc->write("\r", 1, lc->write_data);
    768 			continue;
    769 		} else if (c == '') {
    770 			if (lc->x)
    771 				lc->x--;
    772 			lc->write("", 1, lc->write_data);
    773 			continue;
    774 		} else if (c == '\n') {
    775 			if ((lc->was_bgset && !lc->bg) ||
    776 			    (lc->was_reverse && !lc->reverse)) {
    777 				const char buffer[] = "\x1b[0m";
    778 				lc->was_bgset = lc->was_reverse =
    779 					lc->was_bold = lc->was_underline =
    780 					false;
    781 				if (lc->bash_escape)
    782 					lc->write("\\[", 2, lc->write_data);
    783 				lc->write(buffer, sizeof(buffer),
    784 					  lc->write_data);
    785 				if (lc->bash_escape)
    786 					lc->write("\\[", 2, lc->write_data);
    787 			}
    788 			lc->x = 0;
    789 			lc->y++;
    790 			lc->write("\n", 1, lc->write_data);
    791 			continue;
    792 		}
    793 
    794 		eaw = u_getIntPropertyValue(c, UCHAR_EAST_ASIAN_WIDTH);
    795 
    796 		if (lc->w && lc->x + (eaw == U_EA_WIDE ||
    797 				      eaw == U_EA_FULLWIDTH) >= lc->w) {
    798 			lc->x = 0;
    799 			lc->y++;
    800 		}
    801 
    802 		_lc_colorize(lc, buffer + ip, i - ip);
    803 
    804 		if (eaw == U_EA_WIDE ||
    805 		    eaw == U_EA_FULLWIDTH)
    806 			lc->x++;
    807 		lc->x++;
    808 	}
    809 
    810 	return i;
    811 }
    812 
    813 bool _write(const char *data, size_t len, void *user_data)
    814 {
    815 	return fwrite(data, 1, len, user_data) == len;
    816 }
    817 
    818 void lolcat_set_size(struct lolcat *lc, int w, int h)
    819 {
    820 	lc->w = w;
    821 	lc->h = h;
    822 }
    823 
    824 static void usage(const char *binname)
    825 {
    826 	fprintf(stderr,
    827 		"Usage: %s [-t12vb] [file...]\n"
    828 		" -t    24-bit true color output (default)\n"
    829 		" -1    16 color output\n"
    830 		" -2    256 color output\n"
    831 		" -b    Escape color codes with \\[ \\] for bash prompt\n"
    832 		" -n    Animate the rainbowaves\n"
    833 		" -g    Animate rainbow gradient\n"
    834 		" -s    Stop animation\n"
    835 		" -v    Make rainbowaves vertical\n",
    836 		binname);
    837 	exit(0);
    838 }
    839 
    840 static bool hupped = false;
    841 static void sig_hup(int signum)
    842 {
    843 	(void)signum;
    844 	hupped = true;
    845 }
    846 
    847 int main(int argc, char **argv)
    848 {
    849 	struct lolcat *lc = lolcat_init(_write, stdout);
    850 	char buffer[2048];
    851 	size_t used = 0;
    852 	struct termios old_tio = { 0 }, new_tio;
    853 	int i = 1;
    854 
    855 	struct winsize ws = { 0 };
    856 
    857 	if (!ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) ||
    858 	    !ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws))
    859 		lolcat_set_size(lc, ws.ws_col, ws.ws_row);
    860 
    861 	while (i < argc) {
    862 		const char *opt;
    863 		if (argv[i][0] != '-' || argv[i][1] == '\0')
    864 			break;
    865 		if (argv[i][1] == '-' && argv[i][2] == '\0') {
    866 			i++;
    867 			break;
    868 		}
    869 		opt = &argv[i++][1];
    870 
    871 		while (*opt) {
    872 			switch (*opt++) {
    873 			case 't':
    874 				lc->mode = TRUECOLOR;
    875 				break;
    876 			case '1':
    877 				lc->mode = INDEXED16;
    878 				break;
    879 			case '2':
    880 				lc->mode = INDEXED256;
    881 				break;
    882 			case 'n':
    883 				lc->mode = NYANANIMATE;
    884 				break;
    885 			case 'g':
    886 				lc->mode = GRADANIMATE;
    887 				break;
    888 			case 's':
    889 				lc->mode = STOP;
    890 				break;
    891 			case 'v':
    892 				lc->transpose = true;
    893 				break;
    894 			case 'b':
    895 				lc->bash_escape = true;
    896 				break;
    897 			case 'h':
    898 			default:
    899 				usage(argv[0]);
    900 				break;
    901 			}
    902 		}
    903 	}
    904 
    905 
    906 	if ((lc->mode == GRADANIMATE || lc->mode == NYANANIMATE ||
    907 	     lc->mode == STOP) && isatty(STDERR_FILENO)) {
    908 		char buffer[8192];
    909 		const char *tty = ttyname(STDERR_FILENO);
    910 		strcpy_alter(buffer +
    911 			     sprintf(buffer, "/tmp/.nyancat-%u-", getuid()),
    912 			     tty, '/', '_');
    913 		FILE *pf = fopen(buffer, "r");
    914 		long unsigned pid = 0;
    915 		const char *term;
    916 
    917 		if ((term = getenv("TERM")))
    918 			lc->xterm_workaround = !strncmp(term, "xterm", 5);
    919 
    920 		if (pf) {
    921 			fscanf(pf, " %lu", &pid);
    922 			fclose(pf);
    923 		}
    924 
    925 		if (lc->mode == STOP) {
    926 			if (pid)
    927 				kill(pid, SIGHUP);
    928 			exit(0);
    929 		} else if (!pid || kill(pid, 0)) {
    930 			if ((pid = fork())) {
    931 				pf = fopen(buffer, "w");
    932 				if (pf) {
    933 					fprintf(pf, "%lu\n", pid);
    934 					fclose(pf);
    935 				}
    936 			} else {
    937 				size_t n = 0;
    938 				if (lc->mode == NYANANIMATE)
    939 					prepare_palette(lc);
    940 				else
    941 					animate_gradpalette(lc, 0);
    942 				fclose(stderr);
    943 				fclose(stdout);
    944 				fclose(stdin);
    945 
    946 				signal(SIGHUP, sig_hup);
    947 				signal(SIGINT, sig_hup);
    948 
    949 				while (!hupped) {
    950 					nanosleep(&(struct timespec){
    951 						.tv_sec = lc->mode ==
    952 						NYANANIMATE ? 1 : 0,
    953 						.tv_nsec = lc->mode ==
    954 						NYANANIMATE ? 0 : 100000000 },
    955 						NULL);
    956 					lc->write_data = fopen(tty, "w");
    957 					if (!lc->write_data)
    958 						break;
    959 					if (lc->mode == NYANANIMATE) {
    960 						if (!animate_palette(lc,
    961 								     n ^= 1))
    962 							break;
    963 					} else {
    964 						if (!animate_gradpalette(
    965 								lc,
    966 								n = (n + 1) %
    967 								GRADSTEPS))
    968 							break;
    969 					}
    970 					if (fflush(lc->write_data))
    971 						break;
    972 					fclose(lc->write_data);
    973 				}
    974 
    975 				close(STDOUT_FILENO);
    976 				unlink(buffer);
    977 				exit(0);
    978 			}
    979 		}
    980 	}
    981 
    982 	if (i >= argc) {
    983 		tcgetattr(STDIN_FILENO, &old_tio);
    984 
    985 		new_tio = old_tio;
    986 
    987 		new_tio.c_lflag &= (~ICANON & ~ECHO);
    988 		tcsetattr(STDIN_FILENO, TCSANOW, &new_tio);
    989 
    990 		while (TRUE) {
    991 			ssize_t bytes = read(STDIN_FILENO, buffer + used,
    992 					     sizeof(buffer) - used);
    993 			size_t processed;
    994 
    995 			if (bytes <= 0)
    996 				break;
    997 
    998 			used += bytes;
    999 			processed = lc_process(lc, buffer, used);
   1000 
   1001 			if (processed != used)
   1002 				memmove(buffer, buffer + processed,
   1003 					used - processed);
   1004 			used -= processed;
   1005 
   1006 			fflush(stdout);
   1007 		}
   1008 
   1009 		fflush(stdout);
   1010 		tcsetattr(STDIN_FILENO, TCSANOW, &old_tio);
   1011 	} else {
   1012 		for (; i < argc; i++) {
   1013 			int fd;
   1014 			if (!strcmp(argv[i], "-"))
   1015 				fd = STDIN_FILENO;
   1016 			else
   1017 				fd = open(argv[i], O_RDONLY);
   1018 			while (TRUE) {
   1019 				ssize_t bytes = read(fd, buffer + used,
   1020 						     sizeof(buffer) - used);
   1021 				size_t processed;
   1022 
   1023 				if (bytes <= 0)
   1024 					break;
   1025 
   1026 				used += bytes;
   1027 				processed = lc_process(lc, buffer, used);
   1028 
   1029 				if (processed != used)
   1030 					memmove(buffer, buffer + processed,
   1031 						used - processed);
   1032 				used -= processed;
   1033 
   1034 				fflush(stdout);
   1035 			}
   1036 			if (fd != STDIN_FILENO)
   1037 				close(fd);
   1038 		}
   1039 	}
   1040 
   1041 	if (lc->bash_escape)
   1042 		fprintf(stdout, "\\[\x1b[0m\\]");
   1043 	else
   1044 		fprintf(stdout, "\x1b[0m");
   1045 	fflush(stdout);
   1046 
   1047 
   1048 	return 0;
   1049 }