Files
fastfetch_server/ansi2html.h
2026-05-27 15:27:37 +03:00

342 lines
11 KiB
C

// usage:
// #define VTERM_ROWS 50
// #define VTERM_COLS 200
// #include "ansi2html.h
#pragma once
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
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 =
+ (size_t)(vt->last_row + 1) * 4
+ (size_t)(vt->last_row + 1) * (vt->last_col + 1) *
(sizeof("<span class=\" b i u s d k\" style=\"color:#xxxxxx;background:#xxxxxx;\">&amp;</span>") - 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(
"<!doctype html>"
"<html lang=\"en\">"
"<head>\n"
"<meta charset=\"UTF-8\">"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
"<title>fastfetch_server</title>"
"<style>"
"* { box-sizing: border-box; margin: 0; padding: 0; }"
"html, body {"
"background: #000000;"
"font-family: 'Adwaita Mono', monospace;"
"font-size: 24px;"
//"display: flex; justify-content: center;"
"}"
".b { font-weight: bold; }"
".i { font-style: italic; }"
".u { text-decoration: underline; }"
".s { text-decoration: line-through; }"
".d { opacity: 0.5; }"
".k { animation: blink 1s step-start infinite; }"
"@keyframes blink { 50%% { opacity: 0; } }"
"</style>"
"</head>"
"<body><pre>"
);
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("</span>");
}
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("<span");
if (has_cls) APPEND(" class=\"%s\"", cls.data + 1);
if (has_style) APPEND(" style=\"%s\"", style.data);
APPEND(">");
}
const char* ch = cell->utf8[0] ? cell->utf8 : " ";
for (const char* q = ch; *q; q++) {
if (*q == '<') APPEND("&lt;");
else if (*q == '>') APPEND("&gt;");
else if (*q == '&') APPEND("&amp;");
else { html[pos++] = *q; }
}
}
APPEND("</span>");
APPEND("<br>");
}
APPEND("</pre></body></html>\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 = {0};
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;
}
}
return vterm_render(&vt);
}