From a6f43bc59fc1292060081d5a4837f5679e1b186f Mon Sep 17 00:00:00 2001 From: Vito Caputo Date: Sat, 16 Nov 2019 19:37:22 -0800 Subject: libs/txt: add minimal ascii text renderer This is as basic as it gets, the only fanciness is it recognizes newlines and supports horizontal and vertical justification. As this is intended to be run from potentially threaded fragmenter renderers, it receives a fragment and *frame* coordinates for the text to be rendered. If the text doesn't land in the given fragment, nothing gets drawn. Currently this is not optimized at all. There's a stubbed out rect overlap test function which could be used to avoid entering the text rendering loop for fragments with zero overlap, that's an obvious low-hanging fruit optimization. After that, skipping characters that don't overlap would be another obvious thing. As-is the text render loop is always entered and the bounds-checked put pixel helper is used. So every fragment will incur the cost of rendering the full string, even when it's not visible. For the rtv captions this isn't a particularly huge deal, but stuff to improve upon in the future. --- src/Makefile.am | 2 +- src/libs/Makefile.am | 2 +- src/libs/txt/Makefile.am | 3 + src/libs/txt/txt.c | 200 +++++++++++++++++++++++++++++++++++++++++++++++ src/libs/txt/txt.h | 33 ++++++++ 5 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 src/libs/txt/Makefile.am create mode 100644 src/libs/txt/txt.c create mode 100644 src/libs/txt/txt.h (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index d0651fa..86ce127 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,4 +4,4 @@ rototiller_SOURCES = fb.c fb.h fps.c fps.h rototiller.c rototiller.h sdl_fb.c se if ENABLE_DRM rototiller_SOURCES += drm_fb.c endif -rototiller_LDADD = modules/flui2d/libflui2d.a modules/julia/libjulia.a modules/pixbounce/libpixbounce.a modules/plasma/libplasma.a modules/ray/libray.a modules/roto/libroto.a modules/rtv/librtv.a modules/snow/libsnow.a modules/sparkler/libsparkler.a modules/stars/libstars.a modules/submit/libsubmit.a libs/grid/libgrid.a libs/ray/libray.a libs/ascii/libascii.a -lm +rototiller_LDADD = modules/flui2d/libflui2d.a modules/julia/libjulia.a modules/pixbounce/libpixbounce.a modules/plasma/libplasma.a modules/ray/libray.a modules/roto/libroto.a modules/rtv/librtv.a modules/snow/libsnow.a modules/sparkler/libsparkler.a modules/stars/libstars.a modules/submit/libsubmit.a libs/grid/libgrid.a libs/ray/libray.a libs/txt/libtxt.a libs/ascii/libascii.a -lm diff --git a/src/libs/Makefile.am b/src/libs/Makefile.am index 48fb649..e0895ce 100644 --- a/src/libs/Makefile.am +++ b/src/libs/Makefile.am @@ -1 +1 @@ -SUBDIRS = ascii grid ray +SUBDIRS = ascii grid ray txt diff --git a/src/libs/txt/Makefile.am b/src/libs/txt/Makefile.am new file mode 100644 index 0000000..88aa79b --- /dev/null +++ b/src/libs/txt/Makefile.am @@ -0,0 +1,3 @@ +noinst_LIBRARIES = libtxt.a +libtxt_a_SOURCES = txt.c txt.h +libtxt_a_CPPFLAGS = -I@top_srcdir@/src -I@top_srcdir@/src/libs diff --git a/src/libs/txt/txt.c b/src/libs/txt/txt.c new file mode 100644 index 0000000..e42d8c2 --- /dev/null +++ b/src/libs/txt/txt.c @@ -0,0 +1,200 @@ +#include +#include +#include + +#include "fb.h" + +#include "ascii/ascii.h" +#include "txt.h" + + +struct txt_t { + int len, width, height; + char str[]; +}; + + +/* compute the rectangle dimensions of the string in rendered pixels */ +static void measure_str(const char *str, int *res_width, int *res_height) +{ + int rows = 1, cols = 0, col = 0; + char c; + + assert(str); + assert(res_width); + assert(res_height); + + while (c = *str) { + switch (c) { + case ' '...'~': + col++; + break; + + case '\n': + if (col > cols) + cols = col; + col = 0; + rows++; + break; + + default: + break; + } + str++; + } + + *res_height = 1 + rows * (ASCII_HEIGHT + 1); + *res_width = 1 + cols * (ASCII_WIDTH + 1); +} + + +txt_t * txt_new(const char *str) +{ + txt_t *txt; + int len; + + assert(str); + + len = strlen(str); + + txt = calloc(1, sizeof(txt_t) + len + 1); + if (!txt) + return NULL; + + txt->len = len; + memcpy(txt->str, str, len); + + measure_str(txt->str, &txt->width, &txt->height); + + return txt; +} + + +txt_t * txt_newf(const char *fmt, ...) +{ + char buf[1024] = {}; /* XXX: it's not expected there will be long strings */ + va_list ap; + + assert(fmt); + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf) - 1, fmt, ap); + va_end(ap); + + return txt_new(buf); +} + + +txt_t * txt_free(txt_t *txt) +{ + free(txt); + + return NULL; +} + + +/* Adjusts x and y according to alignment, width, and height. Returning the adjusted x and y + * in res_x, res_y. + * + * res_x,res_y will be the coordinate of the upper left corner of the rect + * described by width,height when aligned relative to x,y according to the + * specified alignment. + * + * e.g. if an alignment of TXT_HALIGN_LEFT,TXT_VALIGN_TOP is supplied, x,y is returned verbatim + * in res_x,res_y. + * an alignment of TXT_HALIGN_CENTER,TXT_VALIGN_CENTER returns x-width/2 and y-width/2. + */ +static void justify(txt_align_t alignment, int x, int y, unsigned width, unsigned height, int *res_x, int *res_y) +{ + assert(res_x); + assert(res_y); + + switch (alignment.horiz) { + case TXT_HALIGN_CENTER: + x -= width >> 1; + break; + + case TXT_HALIGN_LEFT: + break; + + case TXT_HALIGN_RIGHT: + x -= width; + break; + + default: + assert(0); + } + + switch (alignment.vert) { + case TXT_VALIGN_CENTER: + y -= height >> 1; + break; + + case TXT_VALIGN_TOP: + break; + + case TXT_VALIGN_BOTTOM: + y -= height; + break; + + default: + assert(0); + } + + *res_x = x; + *res_y = y; +} + + +static int overlaps(int x1, int y1, unsigned w1, unsigned h1, int x2, int y2, unsigned w2, unsigned h2) +{ + /* TODO */ + return 1; +} + + +static inline void draw_char(fb_fragment_t *fragment, uint32_t color, int x, int y, char c) +{ + /* TODO: this could be optimized to skip characters with no overlap */ + for (int i = 0; i < ASCII_HEIGHT; i++) { + for (int j = 0; j < ASCII_WIDTH; j++) { + if (ascii_chars[c][i * ASCII_WIDTH + j]) + fb_fragment_put_pixel_checked(fragment, x + j, y + i, color); + } + } +} + + +void txt_render_fragment(txt_t *txt, fb_fragment_t *fragment, uint32_t color, int x, int y, txt_align_t alignment) +{ + int jx, jy, col, row; + char c, *str; + + assert(txt); + assert(fragment); + + justify(alignment, x, y, txt->width, txt->height, &jx, &jy); + + if (!overlaps(jx, jy, txt->width, txt->height, + fragment->x, fragment->y, + fragment->width, fragment->height)) + return; + + + for (col = 0, row = 0, str = txt->str; *str; str++) { + switch (*str) { + case ' '...'~': + draw_char(fragment, color, jx + 1 + col * (ASCII_WIDTH + 1), jy + 1 + row * (ASCII_HEIGHT + 1), *str); + col++; + break; + + case '\n': + col = 0; + row++; + break; + + default: + break; + } + } +} diff --git a/src/libs/txt/txt.h b/src/libs/txt/txt.h new file mode 100644 index 0000000..8c68e30 --- /dev/null +++ b/src/libs/txt/txt.h @@ -0,0 +1,33 @@ +#ifndef _TXT_H +#define _TXT_H + +#include + +typedef struct fb_fragment_t fb_fragment_t; +typedef struct txt_t txt_t; + +typedef enum txt_halign_t { + TXT_HALIGN_CENTER, + TXT_HALIGN_LEFT, + TXT_HALIGN_RIGHT, + TXT_HALIGN_CNT, +} txt_halign_t; + +typedef enum txt_valign_t { + TXT_VALIGN_CENTER, + TXT_VALIGN_TOP, + TXT_VALIGN_BOTTOM, + TXT_VALIGN_CNT, +} txt_valign_t; + +typedef struct txt_align_t { + txt_halign_t horiz; + txt_valign_t vert; +} txt_align_t; + +txt_t * txt_new(const char *str); +txt_t * txt_newf(const char *fmt, ...); +txt_t * txt_free(txt_t *txt); +void txt_render_fragment(txt_t *txt, fb_fragment_t *fragment, uint32_t color, int x, int y, txt_align_t alignment); + +#endif -- cgit v1.2.3