From 2fbc94e41c59efcf2981345ffd5edacd45ddc782 Mon Sep 17 00:00:00 2001 From: Nick Black Date: Wed, 1 Jan 2020 06:33:45 -0500 Subject: [PATCH] Higher planes obliterate bisected wide glyphs #158 (#243) * higher planes stomp wide glyphs * broken unit test * develop out widestomp PoC * fix notcurses_at_yx() * fix up dig_visible_cell() return value * refuse wide glyph on last column #242 * set adjacent cell wide when rendering #158 * xray: eliminate weird color flicker * witherworm: don't eat wide glyphs * unit test for boxed glyph * uniblock: no need to emit so many U+200Es * witherworm: remove wide glyph hack --- include/notcurses.h | 21 ++++++- src/demo/panelreel.c | 3 +- src/demo/unicodeblocks.c | 22 ++----- src/demo/witherworm.c | 17 ++--- src/demo/xray.c | 88 +++++++++++++------------- src/lib/internal.h | 18 +++++- src/lib/notcurses.c | 20 +++--- src/lib/render.c | 79 ++++++++++++++++++----- src/poc/widestomp.cpp | 75 ++++++++++++++++++++++ tests/ncplane.cpp | 61 +++++++++++++++++- tests/render.cpp | 133 +++++++++++++++++++++++++++++++++++++++ 11 files changed, 435 insertions(+), 102 deletions(-) create mode 100644 src/poc/widestomp.cpp create mode 100644 tests/render.cpp diff --git a/include/notcurses.h b/include/notcurses.h index 93441c0f4..77a57fa33 100644 --- a/include/notcurses.h +++ b/include/notcurses.h @@ -382,6 +382,11 @@ API void notcurses_stats(struct notcurses* nc, ncstats* stats); // Reset all cumulative stats (immediate ones, such as fbbytes, are not reset). API void notcurses_reset_stats(struct notcurses* nc, ncstats* stats); +// Retrieve the cell at the specified location on the specified plane, returning +// it in 'c'. This copy is safe to use until the ncplane is destroyed/erased. +// Returns the length of the EGC in bytes. +API char* notcurses_at_yx(struct notcurses* nc, int y, int x, cell* c); + // Resize the specified ncplane. The four parameters 'keepy', 'keepx', // 'keepleny', and 'keeplenx' define a subset of the ncplane to keep, // unchanged. This may be a section of size 0, though none of these four @@ -559,8 +564,8 @@ API int ncplane_putegc(struct ncplane* n, const char* gclust, uint32_t attr, // Call ncplane_putegc() after successfully moving to y, x. static inline int -ncplane_putegc_yx(struct ncplane* n, int y, int x, const char* gclust, uint32_t attr, - uint64_t channels, int* sbytes){ +ncplane_putegc_yx(struct ncplane* n, int y, int x, const char* gclust, + uint32_t attr, uint64_t channels, int* sbytes){ if(ncplane_cursor_move_yx(n, y, x)){ return -1; } @@ -1365,6 +1370,18 @@ cell_double_wide_p(const cell* c){ return (c->channels & CELL_WIDEASIAN_MASK); } +// Is this the right half of a wide character? +static inline bool +cell_wide_right_p(const cell* c){ + return cell_double_wide_p(c) && c->gcluster == 0; +} + +// Is this the left half of a wide character? +static inline bool +cell_wide_left_p(const cell* c){ + return cell_double_wide_p(c) && c->gcluster; +} + // Is the cell simple (a lone ASCII character, encoded as such)? static inline bool cell_simple_p(const cell* c){ diff --git a/src/demo/panelreel.c b/src/demo/panelreel.c index b0d56da96..83125d125 100644 --- a/src/demo/panelreel.c +++ b/src/demo/panelreel.c @@ -257,8 +257,7 @@ panelreel_demo_core(struct notcurses* nc, int efd, tabletctx** tctxs){ .circular = true, .min_supported_cols = 8, .min_supported_rows = 5, - .bordermask = NCBOXMASK_BOTTOM | NCBOXMASK_TOP | - NCBOXMASK_LEFT | NCBOXMASK_RIGHT, + .bordermask = 0, .borderchan = 0, .tabletchan = 0, .focusedchan = 0, diff --git a/src/demo/unicodeblocks.c b/src/demo/unicodeblocks.c index bb443e514..cacce1f30 100644 --- a/src/demo/unicodeblocks.c +++ b/src/demo/unicodeblocks.c @@ -28,9 +28,9 @@ draw_block(struct ncplane* nn, uint32_t blockstart){ cell_set_fg_rgb(&vl, 255, 255, 255); cell_set_bg_rgb(&hl, 0, 0, 0); cell_set_bg_rgb(&vl, 0, 0, 0); - if(ncplane_box_sized(nn, &ul, &ur, &ll, &lr, &hl, &vl, - BLOCKSIZE / CHUNKSIZE + 2, - (CHUNKSIZE * 2) + 2, 0)){ + int dimx, dimy; + ncplane_dim_yx(nn, &dimy, &dimx); + if(ncplane_box_sized(nn, &ul, &ur, &ll, &lr, &hl, &vl, dimy, dimx, 0)){ return -1; } cell_release(nn, &ul); cell_release(nn, &ur); cell_release(nn, &hl); @@ -59,25 +59,15 @@ draw_block(struct ncplane* nn, uint32_t blockstart){ } utf8arr[bwc] = '\0'; }else{ // don't dump non-printing codepoints - utf8arr[0] = ' '; - utf8arr[1] = 0xe2; - utf8arr[2] = 0x80; - utf8arr[3] = 0x8e; - utf8arr[4] = '\0'; - } - if(cell_load(nn, &c, utf8arr) < 0){ // FIXME check full len was eaten? - return -1;; + strcpy(utf8arr, " "); } cell_set_fg_rgb(&c, 0xad + z * 2, 0xd8, 0xe6 - z * 2); cell_set_bg_rgb(&c, 8 * chunk, 8 * chunk + z, 8 * chunk); - if(ncplane_putc(nn, &c) < 0){ + if(ncplane_putstr(nn, utf8arr) < 0){ return -1; } if(wcwidth(w[0]) < 2 || !iswprint(w[0])){ - if(cell_load(nn, &c, " ") < 0){ - return -1; - } - if(ncplane_putc(nn, &c) < 0){ + if(ncplane_putsimple(nn, ' ') < 0){ return -1; } } diff --git a/src/demo/witherworm.c b/src/demo/witherworm.c index bf0ace66c..77154401b 100644 --- a/src/demo/witherworm.c +++ b/src/demo/witherworm.c @@ -44,7 +44,7 @@ wall_p(const struct ncplane* n, const cell* c){ // the closer the coordinate is (lower distance), the more we lighten the cell static inline int lighten(struct ncplane* n, cell* c, int distance){ - if(c->gcluster == 0){ // don't blow away wide characters + if(cell_wide_right_p(c)){ // not really a character return 0; } unsigned r, g, b; @@ -63,16 +63,10 @@ lighten(struct ncplane* n, cell* c, int distance){ static void surrounding_cells(struct ncplane* n, cell* cells, int y, int x){ + ncplane_at_yx(n, y - 1, x - 1, &cells[0]); + ncplane_at_yx(n, y - 1, x, &cells[1]); + ncplane_at_yx(n, y - 1, x + 1, &cells[2]); // FIXME rewrite all these using ncplane_at_yx() - if(ncplane_cursor_move_yx(n, y - 1, x - 1) == 0){ - ncplane_at_cursor(n, &cells[0]); - } - if(ncplane_cursor_move_yx(n, y - 1, x) == 0){ - ncplane_at_cursor(n, &cells[1]); - } - if(ncplane_cursor_move_yx(n, y - 1, x + 1) == 0){ - ncplane_at_cursor(n, &cells[2]); - } if(ncplane_cursor_move_yx(n, y, x - 1) == 0){ ncplane_at_cursor(n, &cells[7]); } @@ -186,7 +180,6 @@ static int snakey_top(struct notcurses* nc, snake* s){ struct ncplane* n = notcurses_stdplane(nc); surrounding_cells(n, s->lightup, s->y, s->x); - ncplane_cursor_move_yx(n, s->y, s->x); if(lightup_surrounding_cells(n, s->lightup, s->y, s->x)){ return -1; } @@ -656,7 +649,7 @@ int witherworm_demo(struct notcurses* nc){ return -1; } if(i){ - uint64_t delay = demodelay.tv_sec * 1000000000 + demodelay.tv_nsec; + uint64_t delay = timespec_to_ns(&demodelay); delay /= screens; struct timespec tv; if(delay > GIG){ diff --git a/src/demo/xray.c b/src/demo/xray.c index 63722b1ee..2f4257099 100644 --- a/src/demo/xray.c +++ b/src/demo/xray.c @@ -1,16 +1,16 @@ #include "demo.h" static const char* leg[] = { -" 88 88 88 88 88 88 88 ", -" \"\" 88 88 88 88 \"\" \"\" ,d ", -" 88 88 88 88 88 ", -" ,adPPYYba, 8b,dPPYba, 88 ,adPPYba, 88 ,d8 88,dPPYba, 88 ,adPPYYba, ,adPPYba, 88 ,d8 88 ,adPPYba, 88 8b,dPPYba, MM88MMM ", -" \"\" `Y8 88P' `\"8a 88 a8\" \"\" 88 ,a8\" 88P' \"8a 88 \"\" `Y8 a8\" \"\" 88 ,a8\" 88 a8\" \"8a 88 88P' `\"8a 88 ", -" ,adPPPPP88 88 88 88 8b 8888[ 88 d8 88 ,adPPPPP88 8b 8888[ 88 8b d8 88 88 88 88 ", -" 88, ,88 88 88 88 \"8a, ,aa 88`\"Yba, 88b, ,a8\" 88 88, ,88 \"8a, ,aa 88`\"Yba, 88 \"8a, ,a8\" 88 88 88 88, ", -" `\"8bbdP\"Y8 88 88 88 `\"Ybbd8\"' 88 `Y8a 8Y\"Ybbd8\"' 88 `\"8bbdP\"Y8 `\"Ybbd8\"' 88 `Y8a 88 `\"YbbdP\"' 88 88 88 \"Y888 ", -" ,88 ", -" 888P ", +" 88 88 88 88 88 88 88 ", +" \"\" 88 88 88 88 \"\" \"\" ,d ", +" 88 88 88 88 88 ", +",adPPYYba, 8b,dPPYba, 88 ,adPPYba, 88 ,d8 88,dPPYba, 88 ,adPPYYba, ,adPPYba, 88 ,d8 88 ,adPPYba, 88 8b,dPPYba, MM88MMM ", +"\"\" `Y8 88P' `\"8a 88 a8\" \"\" 88 ,a8\" 88P' \"8a 88 \"\" `Y8 a8\" \"\" 88 ,a8\" 88 a8\" \"8a 88 88P' `\"8a 88 ", +",adPPPPP88 88 88 88 8b 8888[ 88 d8 88 ,adPPPPP88 8b 8888[ 88 8b d8 88 88 88 88 ", +"88, ,88 88 88 88 \"8a, ,aa 88`\"Yba, 88b, ,a8\" 88 88, ,88 \"8a, ,aa 88`\"Yba, 88 \"8a, ,a8\" 88 88 88 88, ", +"`\"8bbdP\"Y8 88 88 88 `\"Ybbd8\"' 88 `Y8a 8Y\"Ybbd8\"' 88 `\"8bbdP\"Y8 `\"Ybbd8\"' 88 `Y8a 88 `\"YbbdP\"' 88 88 88 \"Y888 ", +" ,88 ", +" 888P ", }; static int @@ -34,7 +34,6 @@ perframecb(struct notcurses* nc, struct ncvisual* ncv __attribute__ ((unused)), *(struct ncplane**)vnewplane = n; } ncplane_dim_yx(n, &dimy, &dimx); - y = 0; cell c = CELL_SIMPLE_INITIALIZER(' '); cell_set_fg_alpha(&c, CELL_ALPHA_TRANSPARENT); cell_set_bg_alpha(&c, CELL_ALPHA_TRANSPARENT); @@ -43,48 +42,49 @@ perframecb(struct notcurses* nc, struct ncvisual* ncv __attribute__ ((unused)), ncplane_set_bg_alpha(n, CELL_ALPHA_BLEND); // fg/bg rgbs are set within loop int x = dimx - frameno; - for(size_t l = 0 ; l < sizeof(leg) / sizeof(*leg) ; ++l, ++y){ - int r = startr; - int g = startg - (l * 0x8); - int b = startb; - ncplane_set_bg_rgb(n, l * 0x4, 0x20, l * 0x4); - int xoff = x; - while(xoff + (int)strlen(leg[l]) <= 0){ - xoff += strlen(leg[l]); - } - do{ - ncplane_set_fg_rgb(n, r, g, b); - int len = dimx - xoff; - if(xoff < 0){ - len = strlen(leg[l]) + xoff; - }else if(xoff == 0){ - int t = startr; - startr = startg; - startg = startb; - startb = t; + int r = startr; + int g = startg; + int b = startb; + const size_t llen = strlen(leg[0]); + do{ + if(x + (int)llen <= 0){ + x += llen; + }else{ + int len = dimx - x; + if(x < 0){ + len = llen + x; } - if(len > (int)strlen(leg[l])){ - len = strlen(leg[l]); + if(len > (int)llen){ + len = llen; } if(len > dimx){ len = dimx; } int stroff = 0; - if(xoff < 0){ - stroff = -xoff; - xoff = 0; + if(x < 0){ + stroff = -x; + x = 0; } - ncplane_printf_yx(n, y, xoff, "%*.*s", len, len, leg[l] + stroff); - xoff += len; - int t = r; - r = g; - g = b; - b = t; - }while(xoff < dimx); - } + for(size_t l = 0 ; l < sizeof(leg) / sizeof(*leg) ; ++l, ++y){ + if(ncplane_set_fg_rgb(n, r - 0xc * l, g - 0xc * l, b - 0xc * l)){ + return -1; + } + if(ncplane_set_bg_rgb(n, (l + 1) * 0x8, 0x20, (l + 1) * 0x8)){ + return -1; + } + if(ncplane_printf_yx(n, l, x, "%*.*s", len, len, leg[l] + stroff) != (int)len){ + return -1; + } + } + x += len; + } + int t = r; + r = g; + g = b; + b = t; + }while(x < dimx); ++frameno; demo_render(nc); - // FIXME we'll need some delay here return 0; } diff --git a/src/lib/internal.h b/src/lib/internal.h index 59e59add7..0d8047f99 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -213,19 +213,26 @@ fbcellidx(const ncplane* n, int row, int col){ // copy the UTF8-encoded EGC out of the cell, whether simple or complex. the // result is not tied to the ncplane, and persists across erases / destruction. static inline char* -cell_egc_copy(const ncplane* n, const cell* c){ +pool_egc_copy(const egcpool* e, const cell* c){ char* ret; if(cell_simple_p(c)){ - if( (ret = (char*)malloc(2)) ){ // cast required for c++ unit tests + if( (ret = (char*)malloc(2)) ){ ret[0] = c->gcluster; ret[1] = '\0'; } }else{ - ret = strdup(cell_extended_gcluster(n, c)); + ret = strdup(egcpool_extended_gcluster(e, c)); } return ret; } +// copy the UTF8-encoded EGC out of the cell, whether simple or complex. the +// result is not tied to the ncplane, and persists across erases / destruction. +static inline char* +cell_egc_copy(const ncplane* n, const cell* c){ + return pool_egc_copy(&n->pool, c); +} + // For our first attempt, O(1) uniform conversion from 8-bit r/g/b down to // ~2.4-bit 6x6x6 cube + greyscale (assumed on entry; I know no way to // even semi-portably recover the palette) proceeds via: map each 8-bit to @@ -280,6 +287,11 @@ extended_gcluster(const ncplane* n, const cell* c){ cell* ncplane_cell_ref_yx(ncplane* n, int y, int x); +static inline void +cell_set_wide(cell* c){ + c->channels |= CELL_WIDEASIAN_MASK; +} + #define NANOSECS_IN_SEC 1000000000 static inline uint64_t diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c index 69eda20bc..609ca7cdb 100644 --- a/src/lib/notcurses.c +++ b/src/lib/notcurses.c @@ -1025,11 +1025,6 @@ ncplane_cursor_stuck(const ncplane* n){ return (n->x == n->lenx && n->y == n->leny); } -static inline void -cell_set_wide(cell* c){ - c->channels |= CELL_WIDEASIAN_MASK; -} - static inline void cell_obliterate(ncplane* n, cell* c){ cell_release(n, c); @@ -1043,6 +1038,10 @@ int ncplane_putc(ncplane* n, const cell* c){ return -1; } bool wide = cell_double_wide_p(c); + if(wide && (n->x + 1 == n->lenx)){ + ncplane_unlock(n); + return -1; + } // A wide character obliterates anything to its immediate right (and marks // that cell as wide). Any character placed atop one half of a wide character // obliterates the other half. Note that a wide char can thus obliterate two @@ -1061,12 +1060,19 @@ int ncplane_putc(ncplane* n, const cell* c){ ncplane_unlock(n); return -1; } - int cols = 1 + wide; + int cols = 1; + if(wide){ + ++cols; + cell* rtarg = &n->fb[fbcellidx(n, n->y, n->x + 1)]; + cell_release(n, rtarg); + cell_init(rtarg); + cell_set_wide(rtarg); + } if(wide){ // must set our right wide, and check for further damage if(n->x < n->lenx - 1){ // check to our right cell* candidate = &n->fb[fbcellidx(n, n->y, n->x + 1)]; if(n->x < n->lenx - 2){ - if(cell_double_wide_p(candidate) && targ->gcluster){ // left half + if(cell_wide_left_p(candidate)){ cell_obliterate(n, &n->fb[fbcellidx(n, n->y, n->x + 2)]); } } diff --git a/src/lib/render.c b/src/lib/render.c index 101edabfa..7883f90e1 100644 --- a/src/lib/render.c +++ b/src/lib/render.c @@ -140,8 +140,18 @@ reshape_shadow_fb(notcurses* nc){ // whichever one occurs at the top with a non-transparent α (α < 2). To effect // tail recursion, though, we instead write first, and then recurse, blending // as we descend. α == 0 is opaque. α == 2 is fully transparent. +// +// It is useful to know how deep our glyph came from (the depth of the return +// value), so it will be recorded in 'previousz'. It is useful to know this +// value's relation to the previous cell, so the previous value is provided as +// input to 'previousz', and when we set 'previousz' in this function, we use +// the positive depth to indicate that the return value was above the previous +// plane, and a negative depth to indicate that the return value was equal to or +// below the previous plane. Relative depths are valid only within the context +// of a single render. 'previousz' must be non-negative on input. static inline ncplane* -dig_visible_cell(cell* c, int y, int x, ncplane* p){ +dig_visible_cell(cell* c, int y, int x, ncplane* p, int* previousz){ + int depth = 1; unsigned fgblends = 0; unsigned bgblends = 0; // once we decide on our glyph, it cannot be changed by anything below, so @@ -188,26 +198,40 @@ dig_visible_cell(cell* c, int y, int x, ncplane* p){ // if everything's locked in, we're done if((glyphplane && cell_fg_alpha(c) == CELL_ALPHA_OPAQUE && cell_bg_alpha(c) == CELL_ALPHA_OPAQUE)){ + if(depth < *previousz){ + *previousz = depth; + }else{ + *previousz = -depth; + } return glyphplane; } } } p = p->z; + ++depth; } // if we have a background set, but no glyph selected, load a space so that // the background will be printed if(c->gcluster == 0){ cell_load_simple(NULL, c, ' '); } + if(depth < *previousz){ + *previousz = depth; + }else{ + *previousz = -depth; + } return glyphplane; } static inline ncplane* -visible_cell(cell* c, int y, int x, ncplane* n){ +visible_cell(cell* c, int y, int x, ncplane* n, int* previousz){ cell_init(c); cell_set_fg_alpha(c, CELL_ALPHA_TRANSPARENT); cell_set_bg_alpha(c, CELL_ALPHA_TRANSPARENT); - return dig_visible_cell(c, y, x, n); + if(*previousz < 0){ + *previousz = -*previousz; + } + return dig_visible_cell(c, y, x, n, previousz); } // write the cell's UTF-8 grapheme cluster to the provided FILE*. returns the @@ -470,22 +494,48 @@ notcurses_render_internal(notcurses* nc){ // cursor movement with cup if we only elided one or two. set to INT_MAX // whenever we're on a new line. int needmove = INT_MAX; + // track the depth of our glyph, to see if we need need to stomp a wide + // glyph we're following. + int depth = 0; + // are we in the right half of a wide glyph? if so, we don't typically emit + // anything, *BUT* we must handle higher planes bisecting our wide glyph. + bool inright = false; for(x = 0 ; x < nc->stdscr->lenx ; ++x){ unsigned r, g, b, br, bg, bb; ncplane* p; cell c; // no need to initialize - p = visible_cell(&c, y, x, nc->top); + p = visible_cell(&c, y, x, nc->top, &depth); // don't try to print a wide character on the last column; it'll instead - // be printed on the next line. they probably shouldn't be admitted, but - // we can end up with one due to a resize. - // FIXME but...print what, exactly, instead? + // be printed on the next line. they aren't output, but we can end up + // with one due to a resize. FIXME but...print what, exactly, instead? if((x + 1 >= nc->stdscr->lenx && cell_double_wide_p(&c))){ continue; // needmove will be reset as we restart the line } +//fprintf(stderr, "%d %d depth: %d %d\n", y, x, depth, inright); + if(depth > 0){ // we are above the previous source plane + if(inright){ // wipe out the character to the left + cell* prev = &nc->lastframe[fbcellidx(nc->stdscr, y, x - 1)]; + pool_release(&nc->pool, prev); + cell_init(prev); + // FIXME technically we need rerun the visible cell search...? gross + cell_load_simple(NULL, prev, ' '); + // FIXME this space will be the wrong color, methinks? + term_emit("cup", tiparm(nc->cup, y, x - 1), out, false); + fputc(' ', out); + inright = false; +//fprintf(stderr, "WENT BACK NOW FOR %c\n", c.gcluster); + } + } // lastframe has already been sized to match the current size, so no need // to check whether we're within its bounds. just check the cell. if(nc->lastframe){ cell* oldcell = &nc->lastframe[fbcellidx(nc->stdscr, y, x)]; + if(inright){ + cell_set_wide(oldcell); + inright = false; + continue; + } + // check the damage map if(cellcmp_and_dupfar(&nc->pool, oldcell, p, &c) == 0){ // no need to emit a cell; what we rendered appears to already be // here. no updates are performed to elision state nor lastframe. @@ -498,7 +548,9 @@ notcurses_render_internal(notcurses* nc){ ++needmove; } ++nc->stats.cellelisions; - ++x; + inright = !inright; + }else{ + inright = false; } continue; } @@ -531,7 +583,6 @@ notcurses_render_internal(notcurses* nc){ // * we are a partial glyph, and the previous was default on both, or // * we are a no-foreground glyph, and the previous was default background, or // * we are a no-background glyph, and the previous was default foreground - bool noforeground = cell_noforeground_p(&c); bool nobackground = cell_nobackground_p(p, &c); if((!noforeground && cell_fg_default_p(&c)) || (!nobackground && cell_bg_default_p(&c))){ @@ -583,11 +634,9 @@ notcurses_render_internal(notcurses* nc){ ++nc->stats.bgelisions; } } -// fprintf(stderr, "[%02d/%02d] 0x%02x 0x%02x 0x%02x %p\n", y, x, r, g, b, p); +//fprintf(stderr, "[%02d/%02d] 0x%02x 0x%02x 0x%02x %p\n", y, x, r, g, b, p); term_putc(out, p, &c); - if(cell_double_wide_p(&c)){ - ++x; - } + inright = cell_double_wide_p(&c); } } ret |= fflush(out); @@ -627,7 +676,9 @@ char* notcurses_at_yx(notcurses* nc, int y, int x, cell* c){ if(x >= 0 || x < nc->lfdimx){ const cell* srccell = &nc->lastframe[y * nc->lfdimx + x]; memcpy(c, srccell, sizeof(*c)); // unsafe copy of gcluster - egc = cell_egc_copy(nc->stdscr, srccell); +//fprintf(stderr, "COPYING: %d from %p\n", c->gcluster, &nc->pool); + egc = pool_egc_copy(&nc->pool, srccell); + c->gcluster = 0; // otherwise cell_release() will blow up } } } diff --git a/src/poc/widestomp.cpp b/src/poc/widestomp.cpp new file mode 100644 index 000000000..b05a67e56 --- /dev/null +++ b/src/poc/widestomp.cpp @@ -0,0 +1,75 @@ +#include +#include +#include +#include + +constexpr auto DELAY = 1; + +// dump two wide glyphs, then create a new plane and drop it atop them + +int stomper(struct notcurses* nc, struct ncplane* nn){ + ncplane_move_yx(nn, 0, 1); + + notcurses_render(nc); + sleep(DELAY); + + // first wide glyph gone, second present + ncplane_move_yx(nn, 1, 0); + notcurses_render(nc); + sleep(DELAY); + + // second wide glyph gone, first present + ncplane_move_yx(nn, 2, 2); + notcurses_render(nc); + sleep(DELAY); + + ncplane_move_yx(nn, 4, 0); + notcurses_render(nc); + sleep(DELAY); + + ncplane_move_yx(nn, 5, 1); + notcurses_render(nc); + sleep(DELAY); + + ncplane_move_yx(nn, 6, 2); + notcurses_render(nc); + sleep(DELAY); + + return 0; +} + +int main(void){ + setlocale(LC_ALL, ""); + notcurses_options opts{}; + struct notcurses* nc = notcurses_init(&opts, stdout); + struct ncplane* n = notcurses_stdplane(nc); + + // first, a 2x1 with "AB" + struct ncplane* nn = ncplane_new(nc, 1, 2, 1, 16, nullptr); + ncplane_set_fg_rgb(nn, 0xc0, 0x80, 0xc0); + ncplane_set_bg_rgb(nn, 0x20, 0x00, 0x20); + ncplane_putstr(nn, "AB"); + + ncplane_set_fg_rgb(n, 0x80, 0xc0, 0x80); + ncplane_set_bg_rgb(n, 0x00, 0x40, 0x00); + ncplane_putstr(n, "\xe5\xbd\xa2\xe5\x85\xa8"); + ncplane_putstr_yx(n, 1, 0, "\xe5\xbd\xa2\xe5\x85\xa8"); + ncplane_putstr_yx(n, 2, 0, "\xe5\xbd\xa2\xe5\x85\xa8"); + ncplane_putstr_yx(n, 3, 0, "\xe5\xbd\xa2\xe5\x85\xa8"); + ncplane_putstr_yx(n, 4, 0, "abcdef"); + ncplane_putstr_yx(n, 5, 0, "abcdef"); + ncplane_putstr_yx(n, 6, 0, "abcdef"); + ncplane_putstr_yx(n, 7, 0, "abcdef"); + notcurses_render(nc); + sleep(1); + + stomper(nc, nn); + if(ncplane_putstr_yx(nn, 0, 0, "\xe5\xbd\xa1") <= 0){ + notcurses_stop(nc); + return EXIT_FAILURE; + } + stomper(nc, nn); + + notcurses_stop(nc); + return EXIT_SUCCESS; +} diff --git a/tests/ncplane.cpp b/tests/ncplane.cpp index 36df36094..e1c5c8b9a 100644 --- a/tests/ncplane.cpp +++ b/tests/ncplane.cpp @@ -1,4 +1,5 @@ #include +#include #include #include #include "internal.h" @@ -120,8 +121,8 @@ TEST_CASE("NCPlane") { CHECK(0 == notcurses_render(nc_)); } - // Verify we can emit a wide character, and it advances the cursor - SUBCASE("EmitWchar") { + // Verify we can emit a wchar_t, and it advances the cursor + SUBCASE("EmitWcharT") { const wchar_t* w = L"✔"; int sbytes = 0; CHECK(0 < ncplane_putwegc(n_, w, 0, 0, &sbytes)); @@ -132,6 +133,36 @@ TEST_CASE("NCPlane") { CHECK(0 == notcurses_render(nc_)); } + // Verify we can emit a wide character, and it advances the cursor + SUBCASE("EmitWideAsian") { + const char* w = "\u5168"; + int sbytes = 0; + CHECK(0 < ncplane_putegc(n_, w, 0, 0, &sbytes)); + int x, y; + ncplane_cursor_yx(n_, &y, &x); + CHECK(0 == y); + CHECK(2 == x); + CHECK(0 == notcurses_render(nc_)); + } + + // Verify a wide character is rejected on the last column + SUBCASE("EmitWideAsian") { + const char* w = "\u5168"; + int sbytes = 0; + int dimx; + ncplane_dim_yx(n_, NULL, &dimx); + CHECK(0 < ncplane_putegc_yx(n_, 0, dimx - 3, w, 0, 0, &sbytes)); + int x, y; + ncplane_cursor_yx(n_, &y, &x); + CHECK(0 == y); + CHECK(dimx - 1 == x); + // now it ought be rejected + CHECK(0 > ncplane_putegc(n_, w, 0, 0, &sbytes)); + CHECK(0 == y); + CHECK(dimx - 1 == x); + CHECK(0 == notcurses_render(nc_)); + } + // Verify we can emit a multibyte string, and it advances the cursor SUBCASE("EmitStr") { const char s[] = "Σιβυλλα τι θελεις; respondebat illa: αποθανειν θελω."; @@ -860,6 +891,32 @@ TEST_CASE("NCPlane") { CHECK(0 == notcurses_render(nc_)); } + SUBCASE("BoxedWideGlyph") { + struct ncplane* ncp = ncplane_new(nc_, 3, 4, 0, 0, nullptr); + REQUIRE(ncp); + int dimx, dimy; + ncplane_dim_yx(n_, &dimy, &dimx); + CHECK(0 == ncplane_rounded_box_sized(ncp, 0, 0, 3, 4, 0)); + CHECK(0 < ncplane_putegc_yx(ncp, 1, 1, "\xf0\x9f\xa6\x82", 0, 0, NULL)); + CHECK(0 == notcurses_render(nc_)); + cell c = CELL_TRIVIAL_INITIALIZER; + REQUIRE(0 < ncplane_at_yx(ncp, 1, 0, &c)); + CHECK(!strcmp(cell_extended_gcluster(ncp, &c), "│")); + cell_release(ncp, &c); + char* egc = notcurses_at_yx(nc_, 1, 0, &c); + REQUIRE(egc); + CHECK(!strcmp(egc, "│")); + free(egc); + REQUIRE(0 < ncplane_at_yx(ncp, 1, 3, &c)); + CHECK(!strcmp(cell_extended_gcluster(ncp, &c), "│")); + cell_release(ncp, &c); + egc = notcurses_at_yx(nc_, 1, 3, &c); + REQUIRE(egc); + CHECK(!strcmp(egc, "│")); + free(egc); + CHECK(0 == ncplane_destroy(ncp)); + } + CHECK(0 == notcurses_stop(nc_)); CHECK(0 == fclose(outfp_)); diff --git a/tests/render.cpp b/tests/render.cpp new file mode 100644 index 000000000..aa0de30ce --- /dev/null +++ b/tests/render.cpp @@ -0,0 +1,133 @@ +#include +#include +#include "internal.h" +#include "main.h" + +TEST_CASE("RenderTest") { + if(getenv("TERM") == nullptr){ + return; + } + notcurses_options nopts{}; + nopts.inhibit_alternate_screen = true; + nopts.suppress_banner = true; + FILE* outfp_ = fopen("/dev/tty", "wb"); + REQUIRE(outfp_); + struct notcurses* nc_ = notcurses_init(&nopts, outfp_); + REQUIRE(nc_); + struct ncplane* n_ = notcurses_stdplane(nc_); + REQUIRE(n_); + + // If an ncplane is moved atop the right half of a wide glyph, the entire + // glyph should be oblitrated. + SUBCASE("PlaneStompsWideGlyph"){ + cell c = CELL_TRIVIAL_INITIALIZER; + char* egc; + + // print two wide glyphs on the standard plane + int y, x; + ncplane_yx(n_, &y, &x); + CHECK(0 == y); + CHECK(0 == x); + CHECK(3 == ncplane_putstr(n_, "\xe5\x85\xa8")); + ncplane_cursor_yx(n_, &y, &x); + CHECK(0 == y); + CHECK(2 == x); + CHECK(3 == ncplane_putstr(n_, "\xe5\xbd\xa2")); + ncplane_cursor_yx(n_, &y, &x); + CHECK(0 == y); + CHECK(4 == x); + CHECK(!notcurses_render(nc_)); + + // should be wide char 1 + REQUIRE(3 == ncplane_at_yx(n_, 0, 0, &c)); + egc = cell_egc_copy(n_, &c); + REQUIRE(egc); + CHECK(!strcmp("\xe5\x85\xa8", egc)); + CHECK(cell_double_wide_p(&c)); + free(egc); + egc = notcurses_at_yx(nc_, 0, 0, &c); + REQUIRE(egc); + CHECK(!strcmp("\xe5\x85\xa8", egc)); + CHECK(cell_double_wide_p(&c)); + free(egc); + cell_init(&c); + // should be wide char 1 right side + REQUIRE(0 == ncplane_at_yx(n_, 0, 1, &c)); + egc = cell_egc_copy(n_, &c); + REQUIRE(egc); + CHECK(!strcmp("", egc)); + CHECK(cell_double_wide_p(&c)); + free(egc); + egc = notcurses_at_yx(nc_, 0, 1, &c); + REQUIRE(egc); + CHECK(!strcmp("", egc)); + CHECK(cell_double_wide_p(&c)); + free(egc); + cell_init(&c); + + // should be wide char 2 + REQUIRE(3 == ncplane_at_yx(n_, 0, 2, &c)); + egc = cell_egc_copy(n_, &c); + REQUIRE(egc); + CHECK(!strcmp("\xe5\xbd\xa2", egc)); + CHECK(cell_double_wide_p(&c)); + free(egc); + egc = notcurses_at_yx(nc_, 0, 2, &c); + REQUIRE(egc); +fprintf(stderr, "HAVE [%s] WANT \xe5\xbd\xa2\n", egc); + CHECK(!strcmp("\xe5\xbd\xa2", egc)); + CHECK(cell_double_wide_p(&c)); + free(egc); + cell_init(&c); + // should be wide char 2 right side + CHECK(0 == ncplane_at_yx(n_, 0, 3, &c)); + egc = cell_egc_copy(n_, &c); + REQUIRE(egc); + CHECK(!strcmp("", egc)); + CHECK(cell_double_wide_p(&c)); + free(egc); + egc = notcurses_at_yx(nc_, 0, 3, &c); + REQUIRE(egc); + CHECK(!strcmp("", egc)); + CHECK(cell_double_wide_p(&c)); + free(egc); + cell_init(&c); + + struct ncplane* n = ncplane_new(nc_, 1, 2, 0, 1, nullptr); + REQUIRE(n); + CHECK(0 < ncplane_putstr(n, "AB")); + CHECK(!notcurses_render(nc_)); + + // should be nothing, having been stomped + egc = notcurses_at_yx(nc_, 0, 0, &c); + REQUIRE(egc); + CHECK(!strcmp(" ", egc)); + free(egc); + cell_init(&c); + // should be character from higher plane + egc = notcurses_at_yx(nc_, 0, 1, &c); + REQUIRE(egc); + CHECK(!strcmp("A", egc)); + free(egc); + cell_init(&c); + + egc = notcurses_at_yx(nc_, 0, 2, &c); + REQUIRE(egc); + CHECK(!strcmp("B", egc)); + free(egc); + cell_init(&c); + + // should be nothing, having been stomped + egc = notcurses_at_yx(nc_, 0, 3, &c); + REQUIRE(egc); + CHECK(!strcmp(" ", egc)); + free(egc); + cell_init(&c); + + CHECK(!ncplane_destroy(n)); + } + + + CHECK(0 == notcurses_stop(nc_)); + CHECK(0 == fclose(outfp_)); +}