417 lines
11 KiB
C
417 lines
11 KiB
C
|
|
#include "main.h"
|
|
#include "color.h"
|
|
#include "font.h"
|
|
#include "gif.h"
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <wchar.h>
|
|
#include <locale.h>
|
|
#include <sys/timeb.h>
|
|
#include <unistd.h>
|
|
|
|
/* CAUTION: REQUIRES INPUT TO BE \0-TERMINATED
|
|
* ...also, it does a hardcodes setlocale of LC_CTYPE to en_US.utf8 for... reasons. */
|
|
framebuffer_t *framebuffer_render_text(char *s, glyph_t **glyph_table, unsigned int glyph_table_size){
|
|
unsigned int len = strlen(s);
|
|
|
|
color_t *gbuf = NULL;
|
|
unsigned int gbufwidth = 0;
|
|
unsigned int gbufheight = 0;
|
|
char *p = s;
|
|
|
|
/* Calculate screen width of string prior to allocating memory for the frame buffer */
|
|
wchar_t c;
|
|
mbstate_t ps = {0};
|
|
memset(&ps, 0, sizeof(mbstate_t));
|
|
if(!setlocale(LC_CTYPE, "en_US.utf8")){
|
|
fprintf(stderr, "Cannot set locale\n");
|
|
goto error;
|
|
}
|
|
for(;;){
|
|
while(*p == '\033'){
|
|
p++;
|
|
/* Jump over escape sequences */
|
|
for(;;p++){
|
|
if(!(*p == ';' || *p == '[' || ('0' <= *p && *p <= '9'))){
|
|
p++;
|
|
break;
|
|
}
|
|
}
|
|
memset(&ps, 0, sizeof(mbstate_t));
|
|
}
|
|
|
|
size_t inc = mbrtowc(&c, p, MB_CUR_MAX, &ps); /* MB_CUR_MAX is safe since p is \0-terminated */
|
|
if(inc == -1 || inc == -2){
|
|
fprintf(stderr, "Error rendering string: No valid UTF-8 input.\n");
|
|
goto error;
|
|
}
|
|
if(inc == 0) /* Reached end of string */
|
|
break;
|
|
p += inc;
|
|
|
|
if(c > glyph_table_size){
|
|
fprintf(stderr, "Error rendering string: Codepoint 0x%lx out of valid range (0-%d).\n", (long int)c, glyph_table_size);
|
|
goto error;
|
|
}
|
|
|
|
glyph_t *g = glyph_table[c];
|
|
if(!g){
|
|
fprintf(stderr, "Error rendering string: Codepoint 0x%lx not in font.\n", (long int)c);
|
|
goto error;
|
|
}
|
|
|
|
if(g->height > gbufheight)
|
|
gbufheight = g->height;
|
|
|
|
gbufwidth += g->width;
|
|
}
|
|
/* For easier rendering on the terminal, round up to multiples of two */
|
|
gbufheight += gbufheight&1;
|
|
|
|
size_t gbufsize = gbufwidth*gbufheight;
|
|
gbuf = calloc(gbufsize, sizeof(color_t));
|
|
if(!gbuf){
|
|
fprintf(stderr, "Cannot malloc() %lu bytes.\n", gbufsize*sizeof(color_t));
|
|
goto error;
|
|
}
|
|
memset(gbuf, 0, gbufsize*sizeof(color_t));
|
|
|
|
unsigned int x = 0;
|
|
p = s;
|
|
memset(&ps, 0, sizeof(mbstate_t));
|
|
struct {
|
|
color_t fg;
|
|
color_t bg;
|
|
unsigned int blink:4;
|
|
unsigned int bold:1; /* TODO */
|
|
unsigned int underline:1;
|
|
unsigned int strikethrough:1;
|
|
unsigned int fraktur:1; /* TODO See: Flat10 Fraktur font */
|
|
unsigned int invert:1;
|
|
} style = {
|
|
colortable[DEFAULT_FG_COLOR], colortable[DEFAULT_BG_COLOR], 0, 0, 0, 0, 0, 0
|
|
};
|
|
/* Render glyphs (now with escape sequence rendering!) */
|
|
for(;;){
|
|
/* NOTE: This nested escape sequence parsing does not contain any unicode-awareness whatsoever */
|
|
if(*p == '\033'){ /* Escape sequence YAY */
|
|
char *sequence_start = ++p;
|
|
if(*p == '['){ /* This was a CSI! */
|
|
/* Disassemble the list of numbers, only accept SGR sequences (those ending with 'm') */
|
|
long elems[MAX_CSI_ELEMENTS];
|
|
int nelems;
|
|
for(nelems = 0; nelems<MAX_CSI_ELEMENTS; nelems++){
|
|
p++;
|
|
char *endptr;
|
|
elems[nelems] = strtol(p, &endptr, 10);
|
|
if(p == endptr){
|
|
fprintf(stderr, "Invalid escape sequence: \"\\e%s\"\n", sequence_start);
|
|
goto error;
|
|
}
|
|
p = endptr;
|
|
if(*endptr == 'm')
|
|
break;
|
|
if(*endptr != ';'){
|
|
fprintf(stderr, "Invalid escape sequence: \"\\e%s\"\n", sequence_start);
|
|
goto error;
|
|
}
|
|
}
|
|
p++; /* gobble up trailing 'm' of "\033[23;42m" */
|
|
nelems++;
|
|
/* By now we know it's a SGR since we error'ed out on anything else */
|
|
if(nelems < 1){
|
|
fprintf(stderr, "Unsupported escape sequence: \"\\e%s\"\n", sequence_start);
|
|
goto error;
|
|
}
|
|
/* Parse the sequence numbers */
|
|
for(int i=0; i<nelems; i++){
|
|
switch(elems[i]){
|
|
case 0: /* reset style */
|
|
style.fg = colortable[DEFAULT_FG_COLOR];
|
|
style.bg = colortable[DEFAULT_BG_COLOR];
|
|
style.bold = 0;
|
|
style.underline = 0;
|
|
style.blink = 0;
|
|
style.strikethrough = 0;
|
|
style.fraktur = 0;
|
|
style.invert = 0;
|
|
break;
|
|
case 1: /* bold */
|
|
style.bold = 1;
|
|
break;
|
|
case 4: /* underline */
|
|
style.underline = 1;
|
|
break;
|
|
case 5: /* slow blink */
|
|
style.blink = 1;
|
|
break;
|
|
case 6: /* rapid blink */
|
|
style.blink = 8;
|
|
break;
|
|
case 7: /* color invert on */
|
|
style.invert = 1;
|
|
break;
|
|
case 9: /* strike-through */
|
|
style.strikethrough = 1;
|
|
break;
|
|
case 20:/* Fraktur */
|
|
style.fraktur = 1;
|
|
break;
|
|
case 22:/* Bold off */
|
|
style.bold = 0;
|
|
break;
|
|
case 24:/* Underline off */
|
|
style.underline = 0;
|
|
break;
|
|
case 25:/* Blink off */
|
|
style.blink = 0;
|
|
break;
|
|
case 27:/* color invert off */
|
|
style.invert = 0;
|
|
break;
|
|
case 29:/* strike-through off */
|
|
style.strikethrough = 0;
|
|
break;
|
|
case 30: /* Set foreground color, "dim" colors */
|
|
case 31:
|
|
case 32:
|
|
case 33:
|
|
case 34:
|
|
case 35:
|
|
case 36:
|
|
case 37:
|
|
style.fg = colortable[elems[i]-30];
|
|
break;
|
|
case 38: /* Set xterm-256 foreground color */
|
|
i++;
|
|
if(nelems-i < 2 || elems[i] != 5){
|
|
fprintf(stderr, "Invalid ANSI escape code: \"\\e%s\"\n", sequence_start);
|
|
goto error;
|
|
}
|
|
style.fg = colortable[elems[++i]];
|
|
break;
|
|
case 39: /* Reset foreground color to default */
|
|
style.bg = colortable[DEFAULT_FG_COLOR];
|
|
break;
|
|
case 40: /* Set background color, "dim" colors */
|
|
case 41:
|
|
case 42:
|
|
case 43:
|
|
case 44:
|
|
case 45:
|
|
case 46:
|
|
case 47:
|
|
style.bg = colortable[elems[i]-40];
|
|
break;
|
|
case 48: /* Set xterm-256 background color */
|
|
i++;
|
|
if(nelems-i < 2 || elems[i] != 5){
|
|
fprintf(stderr, "Invalid ANSI escape code: \"\\e%s\"\n", sequence_start);
|
|
goto error;
|
|
}
|
|
style.bg = colortable[elems[++i]];
|
|
break;
|
|
case 49: /* Reset background color to default */
|
|
style.bg = colortable[DEFAULT_BG_COLOR];
|
|
break;
|
|
case 90: /* Set foreground color, "bright" colors */
|
|
case 91:
|
|
case 92:
|
|
case 93:
|
|
case 94:
|
|
case 95:
|
|
case 96:
|
|
case 97:
|
|
style.fg = colortable[elems[i]-90+8];
|
|
break;
|
|
case 100: /* Set background color, "bright" colors */
|
|
case 101:
|
|
case 102:
|
|
case 103:
|
|
case 104:
|
|
case 105:
|
|
case 106:
|
|
case 107:
|
|
style.bg = colortable[elems[i]-100+8];
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Unsupported escape sequence: \"\\e%s\"\n", sequence_start);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
}else{
|
|
fprintf(stderr, "Unsupported escape sequence: \"\\e%s\"\n", sequence_start);
|
|
goto error;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
size_t inc = mbrtowc(&c, p, (s+len+1)-p, NULL);
|
|
/* If p contained */
|
|
if(inc == 0) /* Reached end of string */
|
|
break;
|
|
p += inc;
|
|
|
|
/* Render glyph into frame buffer */
|
|
struct timeb time = {0};
|
|
ftime(&time);
|
|
unsigned long int t = time.time*1000 + time.millitm;
|
|
int blink = style.blink && (t % (1000/style.blink) < (333/style.blink));
|
|
int inv = !(style.invert ^ blink);
|
|
color_t fg = inv ? style.fg : style.bg;
|
|
color_t bg = inv ? style.bg : style.fg;
|
|
|
|
glyph_t *g = glyph_table[c];
|
|
render_glyph(g, gbuf, gbufwidth, x, 0, fg, bg);
|
|
if(style.strikethrough || style.underline){
|
|
int sty = gbufheight/2;
|
|
/* g->y usually is a negative index of the glyph's baseline measured from the glyph's bottom */
|
|
int uly = gbufheight + g->y;
|
|
for(int i=0; i<g->width; i++){
|
|
if(style.strikethrough)
|
|
gbuf[sty*gbufwidth + x + i] = fg;
|
|
if(style.underline)
|
|
gbuf[uly*gbufwidth + x + i] = fg;
|
|
}
|
|
}
|
|
x += g->width;
|
|
}
|
|
framebuffer_t *fb = malloc(sizeof(framebuffer_t));
|
|
if(!fb){
|
|
fprintf(stderr, "Cannot malloc() %lu bytes.\n", sizeof(framebuffer_t));
|
|
goto error;
|
|
}
|
|
fb->w = gbufwidth;
|
|
fb->h = gbufheight;
|
|
fb->data = gbuf;
|
|
return fb;
|
|
error:
|
|
free(gbuf);
|
|
return 0;
|
|
}
|
|
|
|
void console_render_buffer(framebuffer_t *fb){
|
|
/* Render framebuffer to terminal, two pixels per character using Unicode box drawing stuff */
|
|
color_t lastfg = {0, 0, 0}, lastbg = {0, 0, 0};
|
|
printf("\e[38;5;0;48;5;0m");
|
|
color_t *data = fb->data;
|
|
size_t w = fb->w;
|
|
size_t h = fb->h;
|
|
for(size_t y=0; y < h; y+=2){
|
|
for(size_t x=0; x < w; x++){
|
|
/* Da magicks: ▀█▄ */
|
|
color_t ct = data[y*w + x]; /* Top pixel */
|
|
color_t cb = data[(y+1)*w + x]; /* Bottom pixel */
|
|
/* The following, rather convoluted logic tries to "save" escape sequences when rendering. */
|
|
if(!memcmp(&ct, &lastfg, sizeof(color_t))){
|
|
if(!memcmp(&cb, &lastbg, sizeof(color_t))){
|
|
printf("▀");
|
|
}else if(!memcmp(&cb, &lastfg, sizeof(color_t))){
|
|
printf("█");
|
|
}else{
|
|
printf("\033[48;5;%dm▀", xterm_color_index(cb));
|
|
lastbg = cb;
|
|
}
|
|
}else if(!memcmp(&ct, &lastbg, sizeof(color_t))){
|
|
if(!memcmp(&cb, &lastfg, sizeof(color_t))){
|
|
printf("▄");
|
|
}else if(!memcmp(&cb, &lastbg, sizeof(color_t))){
|
|
printf(" ");
|
|
}else{
|
|
printf("\033[38;5;%dm▄", xterm_color_index(cb));
|
|
lastfg = cb;
|
|
}
|
|
}else{ /* No matches for the upper pixel */
|
|
if(!memcmp(&cb, &lastfg, sizeof(color_t))){
|
|
printf("\033[48;5;%dm▄", xterm_color_index(ct));
|
|
lastbg = ct;
|
|
}else if(!memcmp(&cb, &lastbg, sizeof(color_t))){
|
|
printf("\033[38;5;%dm▀", xterm_color_index(ct));
|
|
lastfg = ct;
|
|
}else{
|
|
printf("\033[38;5;%d;48;5;%dm▀", xterm_color_index(ct), xterm_color_index(cb));
|
|
lastfg = ct;
|
|
lastbg = cb;
|
|
}
|
|
}
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv){
|
|
if(argc != 2){
|
|
fprintf(stderr, "No or too much input text given\n");
|
|
return 1;
|
|
}
|
|
|
|
FILE *fontfile = fopen("unifont.bdf", "r");
|
|
if(!fontfile){
|
|
fprintf(stderr, "Error opening font file: %s\n", strerror(errno));
|
|
return 1;
|
|
}
|
|
glyph_t* glyph_table[BLP_SIZE];
|
|
if(read_bdf(fontfile, glyph_table, BLP_SIZE)){
|
|
fprintf(stderr, "Error reading font file.\n");
|
|
return 1;
|
|
}
|
|
fclose(fontfile);
|
|
|
|
int delay = 20;
|
|
framebuffer_t *fb;
|
|
gifAnimationState_t *gifstate = NULL;
|
|
char *p = strstr(argv[1], ".gif");
|
|
if(p && p[4] == '\0'){ /* Argument ends with ".gif", try to read it as a gif file */
|
|
FILE *f = fopen(argv[1], "r");
|
|
if(!f){
|
|
fprintf(stderr, "Error opening gif file from argument (\"%s\"): %s\n", argv[1], strerror(errno));
|
|
return 1;
|
|
}
|
|
uint8_t *buf = NULL;
|
|
size_t size = 0;
|
|
size_t read = 0;
|
|
const size_t READ_INC = 1024;
|
|
do{
|
|
size_t newsize = size+READ_INC;
|
|
buf = realloc(buf, newsize);
|
|
if(!buf){
|
|
fprintf(stderr, "Error opening gif file from argument (\"%s\"): Cannot allocate %lu bytes.\n", argv[1], newsize);
|
|
return 1;
|
|
}
|
|
read += fread(buf+size, 1, newsize, f);
|
|
size = newsize;
|
|
}while(read == size);
|
|
fb = framebuffer_render_gif(buf, read, &gifstate, &delay);
|
|
free(buf);
|
|
}else{
|
|
fb = framebuffer_render_text(argv[1], glyph_table, BLP_SIZE);
|
|
}
|
|
|
|
for(;;){ /* Never gonna give you up, never gonna let you down! */
|
|
printf("\033[2J");
|
|
|
|
if(!fb){
|
|
fprintf(stderr, "Error rendering text.\n");
|
|
return 1;
|
|
}
|
|
|
|
console_render_buffer(fb);
|
|
printf("\n");
|
|
usleep(delay*1000);
|
|
|
|
if(gifstate){
|
|
fb = framebuffer_render_gif(NULL, 0, &gifstate, &delay);
|
|
if(delay == -1)
|
|
delay = 20;
|
|
}else{
|
|
fb = framebuffer_render_text(argv[1], glyph_table, BLP_SIZE);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|