// usage: // #define VTERM_ROWS 50 // #define VTERM_COLS 200 // #include "ansi2html.h #pragma once #include #include #include #include typedef struct ansislop_t { int fg, bg; unsigned char bold; unsigned char italic; unsigned char underline; unsigned char dim; unsigned char blink; unsigned char strikethrough; // 2 wasted bytes 🥀🥀🥀🥀🥀 } ansislop_t; typedef struct { char utf8[5]; ansislop_t slop; } cell_t; typedef struct { cell_t cells[VTERM_ROWS][VTERM_COLS]; int row, col; ansislop_t slop; int last_row, last_col; } vterm_t; static void vterm_put(vterm_t* vt, const char* utf8, const int len) { while (vt->col < 0) vt->col += VTERM_COLS; while (vt->row < 0) vt->col += VTERM_ROWS; while (vt->row >= VTERM_ROWS) vt->row -= VTERM_ROWS; while (vt->col >= VTERM_COLS) vt->col -= VTERM_COLS; cell_t* c = &vt->cells[vt->row][vt->col]; if (vt->row > vt->last_row) vt->last_row = vt->row; if (vt->col > vt->last_col) vt->last_col = vt->col; memset(c->utf8, 0, 5); memcpy(c->utf8, utf8, len); c->slop = vt->slop; vt->col++; } static const char* ansi16[16] = { "#241f31", "#c01c28", "#2ec27e", "#f5c211", "#1e78e4", "#9841bb", "#0ab9dc", "#c0bfbc", "#5e5c64", "#ed333b", "#57e389", "#f8e45c", "#51a1ff", "#c061cb", "#4fd2fd", "#f6f5f4" }; static int color_css(char* buf, size_t size, int32_t color, int is_bg) { if (color == -1) { if (is_bg) strcpy(buf, ansi16[0]); else strcpy(buf, ansi16[15]); return 7; } if ((color & 0xff) < 16) { strcpy(buf, ansi16[color & 0xff]); return 7; } int r, g, b; if (color & 0x1000000) { r = (color >> 16) & 0xff; g = (color >> 8) & 0xff; b = color & 0xff; } else if ((color & 0xff) < 232) { int i = (color & 0xff) - 16; b = (i % 6) * 51; i /= 6; g = (i % 6) * 51; i /= 6; r = i * 51; } else { r = g = b = 8 + ((color & 0xff) - 232) * 10; } return snprintf(buf, size, "#%02x%02x%02x", r, g, b); } static char* vterm_render(const vterm_t* vt) { const size_t cap = 1024 + (size_t)(vt->last_row + 1) * 4 + (size_t)(vt->last_row + 1) * (vt->last_col + 1) * (sizeof("&") - 1); char* html = malloc(cap); size_t pos = 0; #define APPEND(...) do { \ int _n = snprintf(html + pos, cap - pos, __VA_ARGS__); \ if (_n > 0) pos += (size_t)_n; \ } while (0) APPEND( "" "" "\n" "" "" "fastfetch_server" "" "" "
"
    );

    for (int r = 0; r <= vt->last_row; r++) {
        ansislop_t slop;
        for (int c = 0; c <= vt->last_col; c++) {
            const cell_t* cell = &vt->cells[r][c];
            if (c == 0 || (memcmp(&cell->slop, &slop, sizeof(ansislop_t)) != 0)) {
                slop = cell->slop;
                if (c != 0) {
                    APPEND("");
                }
                struct {
                    char data[128];
                    size_t size;
                } style = {{0}, 0};

                char fg[16];
                color_css(fg, sizeof(fg), slop.fg, 0);
                style.size += snprintf(style.data + style.size, sizeof(style.data) - style.size, "color:%s;", fg);

                char bg[16];
                color_css(bg, sizeof(bg), slop.bg, 1);
                style.size += snprintf(style.data + style.size, sizeof(style.data) - style.size, "background:%s;", bg);

                struct {
                    char data[128];
                    size_t size;
                } cls = {{0}, 0};

                if (slop.bold) cls.size += snprintf(cls.data + cls.size, sizeof(cls.data) - cls.size, " b");
                if (slop.italic) cls.size += snprintf(cls.data + cls.size, sizeof(cls.data) - cls.size, " i");
                if (slop.underline) cls.size += snprintf(cls.data + cls.size, sizeof(cls.data) - cls.size, " u");
                if (slop.strikethrough) cls.size += snprintf(cls.data + cls.size, sizeof(cls.data) - cls.size, " s");
                if (slop.dim) cls.size += snprintf(cls.data + cls.size, sizeof(cls.data) - cls.size, " d");
                if (slop.blink) cls.size += snprintf(cls.data + cls.size, sizeof(cls.data) - cls.size, " k");

                const int has_style = style.data[0] != '\0';
                const int has_cls = cls.data[0] != '\0';

                APPEND("");
            }
            const char* ch = cell->utf8[0] ? cell->utf8 : " ";
            for (const char* q = ch; *q; q++) {
                if (*q == '<') APPEND("<");
                else if (*q == '>') APPEND(">");
                else if (*q == '&') APPEND("&");
                else { html[pos++] = *q; }
            }
        }
        APPEND("");
        APPEND("
"); } APPEND("
\n"); #undef APPEND return html; } static const char* vterm_csi(vterm_t* vt, const char* p) { struct { int data[32]; int size; } params = {{0}, 0}; while (*p < 0x40 || *p == ';') { if (*p == ';') { params.size++; if (params.size == 32) params.size = 31; } else if ('0' <= *p && *p <= '9') { params.data[params.size] = params.data[params.size] * 10 + (*p - '0'); } p++; } params.size++; // en.wikipedia.org/wiki/ANSI_escape_code <3 switch (*p++) { case 'A': vt->row -= params.data[0] > 0 ? params.data[0] : 1; break; case 'B': vt->row += params.data[0] > 0 ? params.data[0] : 1; break; case 'C': vt->col += params.data[0] > 0 ? params.data[0] : 1; break; case 'D': vt->col -= params.data[0] > 0 ? params.data[0] : 1; break; case 'E': vt->row += params.data[0] > 0 ? params.data[0] : 1; vt->col = 0; break; case 'F': vt->row -= params.data[0] > 0 ? params.data[0] : 1; vt->col = 0; break; case 'G': vt->col = (params.data[0] > 0 ? params.data[0] : 1) - 1; break; case 'H': case 'f': vt->row = (params.data[0] > 0 ? params.data[0] : 1) - 1; vt->col = (params.data[1] > 0 ? params.data[1] : 1) - 1; break; case 'J': case 'K': // no break; case 'm': for (int i = 0; i < params.size; i++) { const int p0 = params.data[i]; if (p0 == 0) { vt->slop.fg = vt->slop.bg = -1; vt->slop.bold = vt->slop.italic = vt->slop.underline = vt->slop.dim = vt->slop.blink = vt->slop.strikethrough = 0; } else if (p0 == 1) vt->slop.bold = 1; else if (p0 == 2) vt->slop.dim = 1; else if (p0 == 3) vt->slop.italic = 1; else if (p0 == 4) vt->slop.underline = 1; else if (p0 == 5 || p0 == 6) vt->slop.blink = 1; else if (p0 == 9) vt->slop.strikethrough = 1; else if (p0 == 22) vt->slop.bold = vt->slop.dim = 0; else if (p0 == 23) vt->slop.italic = 0; else if (p0 == 24) vt->slop.underline = 0; else if (p0 == 25) vt->slop.blink = 0; else if (p0 == 29) vt->slop.strikethrough = 0; else if (p0 >= 30 && p0 <= 37) vt->slop.fg = p0 - 30; else if (p0 == 38 && i + 2 < params.size && params.data[i + 1] == 5) { vt->slop.fg = params.data[i + 2]; i += 2; } else if (p0 == 38 && i + 4 < params.size && params.data[i + 1] == 2) { vt->slop.fg = 0x1000000 | (params.data[i + 2] << 16) | (params.data[i + 3] << 8) | params.data[i + 4]; i += 4; } else if (p0 == 39) vt->slop.fg = -1; else if (p0 >= 40 && p0 <= 47) vt->slop.bg = p0 - 40; else if (p0 == 48 && i + 2 < params.size && params.data[i + 1] == 5) { vt->slop.bg = params.data[i + 2]; i += 2; } else if (p0 == 48 && i + 4 < params.size && params.data[i + 1] == 2) { vt->slop.bg = 0x1000000 | (params.data[i + 2] << 16) | (params.data[i + 3] << 8) | params.data[i + 4]; i += 4; } else if (p0 == 49) vt->slop.bg = -1; else if (p0 >= 90 && p0 <= 97) vt->slop.fg = p0 - 90 + 8; else if (p0 >= 100 && p0 <= 107) vt->slop.bg = p0 - 100 + 8; } break; default: printf("unsupported escape: %c\n", *p); } return p; } static int utf8_len(const unsigned char c) { if (c < 0x80) return 1; if ((c & 0xe0) == 0xc0) return 2; if ((c & 0xf0) == 0xe0) return 3; if ((c & 0xf8) == 0xf0) return 4; return 1; } static char* ansi2html(const char* ansislop) { vterm_t* vt = malloc(sizeof(vterm_t)); memset(vt, 0, sizeof(vterm_t)); vt->slop.fg = vt->slop.bg = -1; for (int r = 0; r < VTERM_ROWS; r++) { for (int c = 0; c < VTERM_COLS; c++) { vt->cells[r][c].slop.fg = vt->cells[r][c].slop.bg = -1; vt->cells[r][c].utf8[0] = ' '; } } for (const char* p = ansislop; *p;) { if (*p == '\e') { p++; if (*p == '[') { p = vterm_csi(vt, p + 1); } else if (*p == '(') { p += 2; } else { while (0x40 > *p || *p > 0x7e) p++; } } else if (*p == '\r') { p++; vt->col = 0; } else if (*p == '\n') { p++; vt->col = 0; vt->row++; } else if (*p == '\t') { p++; vt->col = (vt->col + 8) & ~7; if (vt->col >= VTERM_COLS) { vt->col = 0; vt->row++; } } else if ((uint8_t)*p < 0x20) { p++; } else { const int l = utf8_len((unsigned char)*p); vterm_put(vt, p, l); p += l; } } char* html = vterm_render(vt); free(vt); return html; }