/* Copyright (C) 2019 June McEnroe * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #define MIN(a, b) ((a) < (b) ? (a) : (b)) typedef unsigned uint; enum { NUL, SOH, STX, ETX, EOT, ENQ, ACK, BEL, BS, HT, NL, VT, NP, CR, SO, SI, DLE, DC1, DC2, DC3, DC4, NAK, SYN, ETB, CAN, EM, SUB, ESC, FS, GS, RS, US, DEL = 0x7F, }; struct Style { bool bold, italic, underline, reverse; uint bg, fg; }; struct Cell { struct Style style; wchar_t ch; }; static struct Style def = { .fg = 7 }; static uint rows = 24, cols = 80; static uint y, x; static bool insert; static struct Style style; static struct Cell *cells; static struct Cell *cell(uint y, uint x) { return &cells[y * cols + x]; } static void clear(struct Cell *a, struct Cell *b) { for (; a <= b; ++a) { a->style = style; a->ch = ' '; } } static void move(struct Cell *dst, struct Cell *src, uint len) { memmove(dst, src, sizeof(*dst) * len); } static char updateNUL(wchar_t ch) { switch (ch) { case BS: if (x) x--; return NUL; case NL: y = MIN(y + 1, rows - 1); return NUL; case CR: x = 0; return NUL; case ESC: return ESC; } if (ch < ' ') { warnx("unhandled \\x%02X", ch); return NUL; } int width = wcwidth(ch); if (width < 0) { warnx("unhandled \\u%X", ch); return NUL; } if (x + width > cols) { warnx("cannot fit '%lc'", ch); return NUL; } if (insert) { move(cell(y, x + width), cell(y, x), cols - x - width); } cell(y, x)->style = style; cell(y, x)->ch = ch; for (int i = 1; i < width; ++i) { cell(y, x + i)->style = style; cell(y, x + i)->ch = '\0'; } x = MIN(x + width, cols - 1); return NUL; } enum { CSI = '[', CUU = 'A', CUD, CUF, CUB, CNL, CPL, CHA, CUP, ED = 'J', EL, DCH = 'P', VPA = 'd', SM = 'h', RM = 'l', SGR, }; static char updateESC(wchar_t ch) { static bool discard; if (discard) { discard = false; return NUL; } switch (ch) { case '(': discard = true; return ESC; case '=': return NUL; case CSI: return CSI; default: warnx("unhandled ESC %lc", ch); return NUL; } } static char updateCSI(wchar_t ch) { static bool dec; if (ch == '?') { dec = true; return CSI; } static uint n, p, ps[8]; if (ch == ';') { n++; p++; ps[p %= 8] = 0; return CSI; } if (ch >= '0' && ch <= '9') { ps[p] *= 10; ps[p] += ch - '0'; if (!n) n++; return CSI; } switch (ch) { break; case CUU: y -= MIN((n ? ps[0] : 1), y); break; case CUD: y = MIN(y + (n ? ps[0] : 1), rows - 1); break; case CUF: x = MIN(x + (n ? ps[0] : 1), cols - 1); break; case CUB: x -= MIN((n ? ps[0] : 1), x); break; case CNL: y = MIN(y + (n ? ps[0] : 1), rows - 1); x = 0; break; case CPL: y -= MIN((n ? ps[0] : 1), y); x = 0; break; case CHA: x = MIN((n ? ps[0] - 1 : 0), cols - 1); break; case VPA: y = MIN((n ? ps[0] - 1 : 0), rows - 1); break; case CUP: { y = MIN((n > 0 ? ps[0] - 1 : 0), rows - 1); x = MIN((n > 1 ? ps[1] - 1 : 0), cols - 1); } break; case ED: { struct Cell *a = cell(0, 0); struct Cell *b = cell(rows - 1, cols - 1); if (ps[0] == 0) a = cell(y, x); if (ps[0] == 1) b = cell(y, x); clear(a, b); } break; case EL: { struct Cell *a = cell(y, 0); struct Cell *b = cell(y, cols - 1); if (ps[0] == 0) a = cell(y, x); if (ps[0] == 1) b = cell(y, x); clear(a, b); } break; case DCH: { uint i = MIN((n ? ps[0] : 1), cols - x); move(cell(y, x), cell(y, x + i), cols - x - i); clear(cell(y, cols - i), cell(y, cols - 1)); } break; case SM: { if (dec) break; switch (ps[0]) { break; case 4: insert = true; break; default: warnx("unhandled SM %u", ps[0]); } } break; case RM: { if (dec) break; switch (ps[0]) { break; case 4: insert = false; break; default: warnx("unhandled RM %u", ps[0]); } } break; case SGR: { if (ps[0] == 38 && ps[1] == 5) { style.fg = ps[2]; break; } if (ps[0] == 48 && ps[1] == 5) { style.bg = ps[2]; break; } for (uint i = 0; i < p + 1; ++i) { switch (ps[i]) { break; case 0: style = def; break; case 1: style.bold = true; break; case 3: style.italic = true; break; case 4: style.underline = true; break; case 7: style.reverse = true; break; case 21: style.bold = false; break; case 22: style.bold = false; break; case 23: style.italic = false; break; case 24: style.underline = false; break; case 27: style.reverse = false; break; case 39: style.fg = def.fg; break; case 49: style.bg = def.bg; break; default: { if (ps[i] >= 30 && ps[i] <= 37) { style.fg = ps[i] - 30; } else if (ps[i] >= 40 && ps[i] <= 47) { style.bg = ps[i] - 40; } else if (ps[i] >= 90 && ps[i] <= 97) { style.fg = 7 + ps[i] - 90; } else if (ps[i] >= 100 && ps[i] <= 107) { style.bg = 7 + ps[i] - 100; } else { warnx("unhandled SGR %u", ps[i]); } } } } } break; default: warnx("unhandled CSI %lc", ch); } dec = false; ps[n = p = 0] = 0; return NUL; } static void update(wchar_t ch) { static char seq; switch (seq) { break; case NUL: seq = updateNUL(ch); break; case ESC: seq = updateESC(ch); break; case CSI: seq = updateCSI(ch); } } static void html(const struct Style *prev, const struct Cell *cell) { if (!prev || memcmp(&cell->style, prev, sizeof(cell->style))) { if (prev) printf(""); uint bg = (cell->style.reverse ? cell->style.fg : cell->style.bg); uint fg = (cell->style.reverse ? cell->style.bg : cell->style.fg); printf( "", cell->style.bold ? "font-weight:bold;" : "", cell->style.italic ? "font-style:italic;" : "", cell->style.underline ? "text-decoration:underline;" : "", bg, fg ); } switch (cell->ch) { break; case '&': printf("&"); break; case '<': printf("<"); break; case '>': printf(">"); break; default: printf("%lc", cell->ch); } } int main(int argc, char *argv[]) { setlocale(LC_CTYPE, ""); bool bright = false; bool size = false; FILE *file = stdin; int opt; while (0 < (opt = getopt(argc, argv, "Bb:f:h:sw:"))) { switch (opt) { break; case 'B': bright = true; break; case 'b': def.bg = strtoul(optarg, NULL, 0); break; case 'f': def.fg = strtoul(optarg, NULL, 0); break; case 'h': rows = strtoul(optarg, NULL, 0); break; case 's': size = true; break; case 'w': cols = strtoul(optarg, NULL, 0); break; default: return EX_USAGE; } } if (optind < argc) { file = fopen(argv[optind], "r"); if (!file) err(EX_NOINPUT, "%s", argv[optind]); } if (size) { struct winsize window; int error = ioctl(STDERR_FILENO, TIOCGWINSZ, &window); if (error) err(EX_IOERR, "ioctl"); rows = window.ws_row; cols = window.ws_col; } cells = calloc(rows * cols, sizeof(*cells)); if (!cells) err(EX_OSERR, "calloc"); style = def; clear(cell(0, 0), cell(rows - 1, cols - 1)); wint_t ch; while (WEOF != (ch = getwc(file))) { update(ch); } if (ferror(file)) err(EX_IOERR, "getwc"); if (bright) { for (uint i = 0; i < rows * cols; ++i) { if (!cells[i].style.bold || cells[i].style.fg > 7) continue; cells[i].style.bold = false; cells[i].style.fg += 8; } } printf( "
",
		cols, def.bg, def.fg
	);
	for (uint y = 0; y < rows; ++y) {
		for (uint x = 0; x < cols; ++x) {
			if (!cell(y, x)->ch) continue;
			html(x ? &cell(y, x - 1)->style : NULL, cell(y, x));
		}
		printf("\n");
	}
	printf("
\n"); }