From d16223fd257ccc1d7060e372eb3d950cf68f8d37 Mon Sep 17 00:00:00 2001 From: jaseg Date: Fri, 7 Mar 2014 00:49:16 +0100 Subject: Modified text rendering to something frame based --- host/Makefile | 4 +- host/bdf.c | 356 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ host/bdf.h | 11 ++ host/font.c | 14 ++- host/font.h | 2 +- host/main.c | 356 --------------------------------------------------------- host/main.h | 10 -- host/server.py | 51 ++++----- 8 files changed, 404 insertions(+), 400 deletions(-) create mode 100644 host/bdf.c create mode 100644 host/bdf.h delete mode 100644 host/main.c delete mode 100644 host/main.h diff --git a/host/Makefile b/host/Makefile index 8abc88a..85536f5 100644 --- a/host/Makefile +++ b/host/Makefile @@ -5,8 +5,8 @@ all: libml libbdf libml: usb.c color.c color.h gcc -shared -fPIC -std=gnu11 -Wall -lm -lusb-1.0 -o libml.so -g usb.c color.c -libbdf: main.c font.c font.h color.c color.h - gcc -shared -fPIC -std=gnu11 -Wall -lm -o libbdf.so -g main.c font.c color.c +libbdf: bdf.c font.c font.h color.c color.h + gcc -shared -fPIC -std=gnu11 -Wall -lm -o libbdf.so -g bdf.c font.c color.c clean: rm libbdf.so libml.so diff --git a/host/bdf.c b/host/bdf.c new file mode 100644 index 0000000..e6ab827 --- /dev/null +++ b/host/bdf.c @@ -0,0 +1,356 @@ + +#include "config.h" +#include "bdf.h" +#include "color.h" +#include "font.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* CAUTION: REQUIRES INPUT TO BE \0-TERMINATED + * ...also, it does a hardcoded setlocale of LC_CTYPE to en_US.utf8 for... reasons. */ +int framebuffer_get_text_bounds(char *s, glyphtable_t *glyph_table, size_t *outw, size_t *outh){ + size_t gbufwidth = 0; + size_t 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-%zd).\n", (long int)c, glyph_table->size); + goto error; + } + + glyph_t *g = glyph_table->data[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; + } + *outw = gbufwidth; + *outh = gbufheight; + return 0; +error: + return 1; +} + +/* CAUTION: REQUIRES INPUT TO BE \0-TERMINATED + * ...also, it does a hardcoded setlocale of LC_CTYPE to en_US.utf8 for... reasons. */ +/* Render the string beginning from the specified x offset (in pixels) */ +int framebuffer_render_text(char *s, glyphtable_t *glyph_table, color_t *gbuf, size_t gbufwidth, size_t gbufheight, size_t offx){ + unsigned int len = strlen(s); + char *p = s; + + if(!setlocale(LC_CTYPE, "en_US.utf8")){ + fprintf(stderr, "Cannot set locale\n"); + goto error; + } + + memset(gbuf, 0, gbufwidth*gbufheight*sizeof(color_t)); + + unsigned int x = 0; + wchar_t c; + p = s; + mbstate_t ps = {0}; + 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; nelemsdata[c]; + /* Is the glyph within the buffer's bounds? */ + if(x+g->width > offx && x < offx+gbufwidth){ + /* x-offx might be negative down to -g->width+1, but that's ok */ + render_glyph(g, gbuf, gbufwidth, x-offx, 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; iwidth; i++){ + if(x+i >= offx){ /* Stay within the frame buffer's bounds */ + if(style.strikethrough) + gbuf[sty*gbufwidth + x + i - offx] = fg; + if(style.underline) + gbuf[uly*gbufwidth + x + i - offx] = fg; + } + } + } + } + x += g->width; + } + return 0; +error: + return 1; +} + +void console_render_buffer(color_t *data, size_t w, size_t h){ + /* 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\e[K"); + 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\e[K"); + } + printf("\033[0m"); +} + diff --git a/host/bdf.h b/host/bdf.h new file mode 100644 index 0000000..c81277e --- /dev/null +++ b/host/bdf.h @@ -0,0 +1,11 @@ +#ifndef __MAIN_H__ +#define __MAIN_H__ + +#include "color.h" +#include "font.h" + +int framebuffer_get_text_bounds(char *s, glyphtable_t *glyph_table, size_t *outw, size_t *outh); +int framebuffer_render_text(char *s, glyphtable_t *glyph_table, color_t *gbuf, size_t gbufwidth, size_t gbufheight, size_t offx); +void console_render_buffer(color_t *data, size_t w, size_t h); + +#endif//__MAIN_H__ diff --git a/host/font.c b/host/font.c index ee9adb3..c1f1d22 100644 --- a/host/font.c +++ b/host/font.c @@ -6,7 +6,8 @@ #include #include -void render_glyph(glyph_t *g, color_t *buf, unsigned int bufwidth, unsigned int offx, unsigned int offy, color_t fg, color_t bg){ +/* Clips at buffer bounds */ +void render_glyph(glyph_t *g, color_t *buf, unsigned int bufwidth, int offx, unsigned int offy, color_t fg, color_t bg){ unsigned int bitmap_row_width = g->width/8; uint8_t *bitmap = ((uint8_t *)g) + sizeof(glyph_t); for(unsigned int y=0; y < g->height; y++){ @@ -16,10 +17,13 @@ void render_glyph(glyph_t *g, color_t *buf, unsigned int bufwidth, unsigned int data |= bitmap[y*bitmap_row_width+i]; } color_t *p = buf + (offy+y)*bufwidth + offx; - for(unsigned int x=0; x < g->width; x++){ - color_t c = (data&(1<<(g->width-1))) ? fg : bg; - *p++ = c; - data <<= 1; + /* Take care to only render what's within the framebuffer's bounds */ + for(unsigned int x=0; (x < g->width) && (offx+x < bufwidth); x++){ + if(offx + x >= 0){ + color_t c = (data&(1<<(g->width-1))) ? fg : bg; + *p++ = c; + data <<= 1; + } } } } diff --git a/host/font.h b/host/font.h index e9fbb35..ad7f851 100644 --- a/host/font.h +++ b/host/font.h @@ -30,6 +30,6 @@ glyphtable_t *read_bdf(FILE *f); void free_glyphtable(glyphtable_t *glyph_table); // Requires buf to point to a buffer at least of size glyph->width*glyph->height. -void render_glyph(glyph_t *glyph, color_t *buf, unsigned int bufwidth, unsigned int offx, unsigned int offy, color_t fg, color_t bg); +void render_glyph(glyph_t *g, color_t *buf, unsigned int bufwidth, int offx, unsigned int offy, color_t fg, color_t bg); #endif//__FONT_H__ diff --git a/host/main.c b/host/main.c deleted file mode 100644 index d8cb371..0000000 --- a/host/main.c +++ /dev/null @@ -1,356 +0,0 @@ - -#include "config.h" -#include "main.h" -#include "color.h" -#include "font.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -void free_framebuffer(framebuffer_t *fb){ - free(fb->data); - free(fb); -} - -/* CAUTION: REQUIRES INPUT TO BE \0-TERMINATED - * ...also, it does a hardcoded setlocale of LC_CTYPE to en_US.utf8 for... reasons. */ -framebuffer_t *framebuffer_render_text(char *s, glyphtable_t *glyph_table){ - 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-%zd).\n", (long int)c, glyph_table->size); - goto error; - } - - glyph_t *g = glyph_table->data[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() %zu 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; nelemsdata[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; iwidth; 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() %zu 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(color_t *data, size_t w, size_t h){ - /* 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\e[K"); - 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\e[K"); - } - printf("\033[0m"); -} - diff --git a/host/main.h b/host/main.h deleted file mode 100644 index 5ace45e..0000000 --- a/host/main.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef __MAIN_H__ -#define __MAIN_H__ - -#include "color.h" -#include "font.h" - -framebuffer_t *framebuffer_render_text(char *s, glyphtable_t *glyph_table); -void console_render_buffer(color_t *data, size_t w, size_t h); - -#endif//__MAIN_H__ diff --git a/host/server.py b/host/server.py index 56002fa..679b13b 100755 --- a/host/server.py +++ b/host/server.py @@ -7,7 +7,7 @@ from itertools import product, cycle import threading import random -from ctypes import CDLL, POINTER, c_void_p, Structure, c_uint8, c_size_t, cast, addressof +from ctypes import * import numpy as np @@ -26,20 +26,26 @@ class FRAMEBUFFER(Structure): bdf = CDLL('./libbdf.so') bdf.read_bdf_file.restype = c_void_p bdf.framebuffer_render_text.restype = POINTER(FRAMEBUFFER) +bdf.framebuffer_render_text.argtypes= [c_char_p, c_void_p, c_void_p, c_size_t, c_size_t, c_size_t] unifont = bdf.read_bdf_file('unifont.bdf') -def render_text(text): +def compute_text_bounds(text): assert unifont - fb = bdf.framebuffer_render_text(bytes(str(text), 'UTF-8'), unifont) - fbd = fb.contents - buf = np.ctypeslib.as_array(cast(fbd.data, POINTER(c_uint8)), shape=(fbd.h, fbd.w, 4)) - # Set data pointer to NULL before freeing framebuffer struct to prevent free_framebuffer from also freeing the data - # buffer that is now used by numpy - #bdf.console_render_buffer(fb) - fbd.data = cast(c_void_p(), POINTER(COLOR)) - bdf.free_framebuffer(fb) - return buf + textbytes = bytes(str(text), 'UTF-8') + textw, texth = c_size_t(0), c_size_t(0) + res = bdf.framebuffer_get_text_bounds(textbytes, unifont, byref(textw), byref(texth)) + if res: + raise ValueError('Invalid text') + return textw.value, texth.value + +def render_text(text, offset): + cbuf = create_string_buffer(FRAME_SIZE*sizeof(COLOR)) + textbytes = bytes(str(text), 'UTF-8') + res = bdf.framebuffer_render_text(textbytes, unifont, cbuf, DISPLAY_WIDTH, DISPLAY_HEIGHT, offset) + if res: + raise ValueError('Invalid text') + return np.ctypeslib.as_array(cast(cbuf, POINTER(c_uint8)), shape=(DISPLAY_WIDTH, DISPLAY_HEIGHT, 4)) printlock = threading.Lock() @@ -55,19 +61,11 @@ def printframe(fb): def scroll(text): """ Returns whether it could scroll all the text uninterrupted """ log('Scrolling', text) - fb = render_text(text); - h,w,_ = fb.shape + w,h = compute_text_bounds(text) for i in range(-DISPLAY_WIDTH,w+1): -# if current_entry.entrytype == 'udp' or (textqueue and current_entry in defaulttexts): -# log('Aborting rendering due to new input') -# return False - leftpad = np.zeros((DISPLAY_HEIGHT, max(-i, 0), 4), dtype=np.uint8) - framedata = fb[:, max(0, i):min(i+DISPLAY_WIDTH, w)] - rightpad = np.zeros((DISPLAY_HEIGHT, min(DISPLAY_WIDTH, max(0, i+DISPLAY_WIDTH-w)), 4), dtype=np.uint8) - dice = np.concatenate((leftpad, framedata, rightpad), 1) - sendframe(dice) - printframe(dice) - fb = render_text(text); + fb = render_text(text, i); + sendframe(fb) + printframe(fb) return True QueueEntry = namedtuple('QueueEntry', ['entrytype', 'remote', 'timestamp', 'text']) @@ -80,7 +78,7 @@ textqueue = [] def log(*args): printlock.acquire() - print(strftime('[%m-%d %H:%M:%S]'), ' '.join(str(arg) for arg in args), '\x1B[0m') + print(strftime('\x1B[93m[%m-%d %H:%M:%S]\x1B[0m'), ' '.join(str(arg) for arg in args), '\x1B[0m') printlock.release() class MateLightUDPHandler(BaseRequestHandler): @@ -107,7 +105,7 @@ class MateLightUDPHandler(BaseRequestHandler): addr = self.client_address[0] conn = QueueEntry('udp', addr, timestamp, '') if addr not in conns: - log('New UDP connection from', addr) + log('\x1B[91mNew UDP connection from\x1B[0m', addr) current_entry = conn conns[addr] = current_entry if current_entry.entrytype == 'udp' and current_entry.remote == addr: @@ -127,7 +125,7 @@ class MateLightTCPTextHandler(BaseRequestHandler): if len(data) > 140: self.request.sendall('TOO MUCH INFORMATION!\n') return - log('Text from {}: {}\x1B[0m'.format(addr, data)) + log('\x1B[95mText from\x1B[0m {}: {}\x1B[0m'.format(addr, data)) textqueue.append(QueueEntry('text', addr, timestamp, data)) self.request.sendall(b'KTHXBYE!\n') @@ -159,6 +157,7 @@ if __name__ == '__main__': current_entry = next(defaulttexts) if current_entry.entrytype != 'udp' and textqueue: current_entry = textqueue[0] + log('\x1B[92mScrolling\x1B[0m', current_entry.text) if current_entry.entrytype == 'udp': if time() - current_entry.timestamp > UDP_TIMEOUT: current_entry = next(defaulttexts) -- cgit