mirror of
https://github.com/dankamongmen/notcurses
synced 2025-03-09 17:19:03 -04:00
Wide EGC tweaks
Don't bias the nccell width by 1, or 0-length EGCs become 255 columns. We weren't actually using the width to drive much anything until now, so this wasn't a problem, but it is exposed as an error once we got rid of CELL_WIDE_ASIAN and start looping through the actual egc column width. Closes #1278, closes #1277
This commit is contained in:
parent
5636a146e4
commit
54b44513c4
@ -15,7 +15,6 @@ namespace ncpp
|
||||
class NCPP_API_EXPORT Cell : public Root
|
||||
{
|
||||
public:
|
||||
static constexpr uint64_t WideAsianMask = CELL_WIDEASIAN_MASK;
|
||||
static constexpr uint64_t FGDefaultMask = CELL_FGDEFAULT_MASK;
|
||||
static constexpr uint64_t FGRGBMask = CELL_FG_RGB_MASK;
|
||||
static constexpr uint64_t BGDefaultMask = CELL_BGDEFAULT_MASK;
|
||||
|
@ -109,10 +109,6 @@ API int notcurses_ucs32_to_utf8(const char32_t* ucs32, unsigned ucs32count,
|
||||
#define CELL_ALPHA_BLEND 0x10000000ull
|
||||
#define CELL_ALPHA_OPAQUE 0x00000000ull
|
||||
|
||||
// if this bit is set, the cell is part of a multicolumn glyph. whether a
|
||||
// cell is the left or right side of the glyph can be determined by checking
|
||||
// whether ->gcluster is zero.
|
||||
#define CELL_WIDEASIAN_MASK 0x8000000000000000ull
|
||||
#define CELL_NOBACKGROUND_MASK 0x0400000000000000ull
|
||||
// if this bit is set, we are *not* using the default background color
|
||||
#define CELL_BGDEFAULT_MASK 0x0000000040000000ull
|
||||
@ -584,13 +580,16 @@ typedef struct nccell {
|
||||
// It must not be allowed through the API, or havoc will result.
|
||||
uint32_t gcluster; // 4B → 4B little endian EGC
|
||||
uint8_t gcluster_backstop; // 1B → 5B (8 bits of zero)
|
||||
// we store the column width minus 1 in this field. this is necessary to
|
||||
// handle EGCs of more than 2 columns...for now. eventually, such an EGC will
|
||||
// set more than one subsequent cell to WIDE_RIGHT, and this won't be
|
||||
// necessary. it can then be used as a bytecount. see #1203. FIXME
|
||||
uint8_t width; // 1B → 6B (8 bits of EGC width, bias-1)
|
||||
// we store the column width in this field. for a multicolumn EGC of N
|
||||
// columns, there will be N nccells, and each has a width of N...for now.
|
||||
// eventually, such an EGC will set more than one subsequent cell to
|
||||
// WIDE_RIGHT, and this won't be necessary. it can then be used as a
|
||||
// bytecount. see #1203. FIXME iff width >= 2, the cell is part of a
|
||||
// multicolumn glyph. whether a cell is the left or right side of the glyph
|
||||
// can be determined by checking whether ->gcluster is zero.
|
||||
uint8_t width; // 1B → 6B (8 bits of EGC column width)
|
||||
uint16_t stylemask; // 2B → 8B (16 bits of NCSTYLE_* attributes)
|
||||
// (channels & 0x8000000000000000ull): part of a wide glyph
|
||||
// (channels & 0x8000000000000000ull): reserved, must be 0
|
||||
// (channels & 0x4000000000000000ull): foreground is *not* "default color"
|
||||
// (channels & 0x3000000000000000ull): foreground alpha (2 bits)
|
||||
// (channels & 0x0800000000000000ull): foreground uses palette index
|
||||
@ -614,11 +613,11 @@ typedef struct nccell {
|
||||
typedef nccell cell; // FIXME backwards-compat, remove in 3.0
|
||||
|
||||
#define CELL_TRIVIAL_INITIALIZER { .gcluster = 0, .gcluster_backstop = 0, .width = 0, .stylemask = 0, .channels = 0, }
|
||||
// do *not* load control characters, wide EGCs, nor invalid EGCs using these
|
||||
// macros! there is no way for us to protect against such misuse here. problems
|
||||
// *will* ensue. similarly, do not set channel flags other than colors/alpha.
|
||||
#define CELL_CHAR_INITIALIZER(c) { .gcluster = (htole(c)), .gcluster_backstop = 0, .width = 0, .stylemask = 0, .channels = 0, }
|
||||
#define CELL_INITIALIZER(c, s, chan) { .gcluster = (htole(c)), .gcluster_backstop = 0, .width = 0, .stylemask = (s), .channels = (chan), }
|
||||
// do *not* load invalid EGCs using these macros! there is no way for us to
|
||||
// protect against such misuse here. problems *will* ensue. similarly, do not
|
||||
// set channel flags other than colors/alpha.
|
||||
#define CELL_CHAR_INITIALIZER(c) { .gcluster = (htole(c)), .gcluster_backstop = 0, .width = (uint8_t)wcwidth(c), .stylemask = 0, .channels = 0, }
|
||||
#define CELL_INITIALIZER(c, s, chan) { .gcluster = (htole(c)), .gcluster_backstop = 0, .width = (uint8_t)wcwidth(c), .stylemask = (s), .channels = (chan), }
|
||||
|
||||
static inline void
|
||||
cell_init(nccell* c){
|
||||
@ -708,10 +707,10 @@ cell_set_bg_alpha(nccell* c, int alpha){
|
||||
return channels_set_bg_alpha(&c->channels, alpha);
|
||||
}
|
||||
|
||||
// Does the nccell contain an East Asian Wide codepoint?
|
||||
// Is the cell part of a multicolumn element?
|
||||
static inline bool
|
||||
cell_double_wide_p(const nccell* c){
|
||||
return (c->channels & CELL_WIDEASIAN_MASK);
|
||||
return (c->width >= 2);
|
||||
}
|
||||
|
||||
// Is this the right half of a wide character?
|
||||
@ -2422,16 +2421,20 @@ API int ncvisual_rotate(struct ncvisual* n, double rads);
|
||||
|
||||
// Resize the visual so that it is 'rows' X 'columns'. This is a lossy
|
||||
// transformation, unless the size is unchanged.
|
||||
API int ncvisual_resize(struct ncvisual* n, int rows, int cols);
|
||||
API int ncvisual_resize(struct ncvisual* n, int rows, int cols)
|
||||
__attribute__ ((nonnull (1)));
|
||||
|
||||
// Polyfill at the specified location within the ncvisual 'n', using 'rgba'.
|
||||
API int ncvisual_polyfill_yx(struct ncvisual* n, int y, int x, uint32_t rgba);
|
||||
API int ncvisual_polyfill_yx(struct ncvisual* n, int y, int x, uint32_t rgba)
|
||||
__attribute__ ((nonnull (1)));
|
||||
|
||||
// Get the specified pixel from the specified ncvisual.
|
||||
API int ncvisual_at_yx(const struct ncvisual* n, int y, int x, uint32_t* pixel);
|
||||
API int ncvisual_at_yx(const struct ncvisual* n, int y, int x, uint32_t* pixel)
|
||||
__attribute__ ((nonnull (1, 4)));
|
||||
|
||||
// Set the specified pixel in the specified ncvisual.
|
||||
API int ncvisual_set_yx(const struct ncvisual* n, int y, int x, uint32_t pixel);
|
||||
API int ncvisual_set_yx(const struct ncvisual* n, int y, int x, uint32_t pixel)
|
||||
__attribute__ ((nonnull (1)));
|
||||
|
||||
// Render the decoded frame to the specified ncplane (if one is not provided,
|
||||
// one will be created, having the exact size necessary to display the visual.
|
||||
@ -2629,7 +2632,8 @@ API struct ncreel* ncreel_create(struct ncplane* n, const ncreel_options* popts)
|
||||
__attribute__ ((nonnull (1)));
|
||||
|
||||
// Returns the ncplane on which this ncreel lives.
|
||||
API struct ncplane* ncreel_plane(struct ncreel* pr);
|
||||
API struct ncplane* ncreel_plane(struct ncreel* pr)
|
||||
__attribute__ ((nonnull (1)));
|
||||
|
||||
// Tablet draw callback, provided a tablet (from which the ncplane and userptr
|
||||
// may be extracted), and a bool indicating whether output ought be drawn from
|
||||
@ -2646,19 +2650,23 @@ typedef int (*tabletcb)(struct nctablet* t, bool drawfromtop);
|
||||
// it is not valid, or there is any other error, NULL will be returned.
|
||||
API struct nctablet* ncreel_add(struct ncreel* nr, struct nctablet* after,
|
||||
struct nctablet* before, tabletcb cb,
|
||||
void* opaque);
|
||||
void* opaque)
|
||||
__attribute__ ((nonnull (1)));
|
||||
|
||||
// Return the number of nctablets in the ncreel 'nr'.
|
||||
API int ncreel_tabletcount(const struct ncreel* nr);
|
||||
API int ncreel_tabletcount(const struct ncreel* nr)
|
||||
__attribute__ ((nonnull (1)));
|
||||
|
||||
// Delete the tablet specified by t from the ncreel 'nr'. Returns -1 if the
|
||||
// tablet cannot be found.
|
||||
API int ncreel_del(struct ncreel* nr, struct nctablet* t);
|
||||
API int ncreel_del(struct ncreel* nr, struct nctablet* t)
|
||||
__attribute__ ((nonnull (1)));
|
||||
|
||||
// Redraw the ncreel 'nr' in its entirety. The reel will be cleared, and
|
||||
// tablets will be lain out, using the focused tablet as a fulcrum. Tablet
|
||||
// drawing callbacks will be invoked for each visible tablet.
|
||||
API int ncreel_redraw(struct ncreel* nr);
|
||||
API int ncreel_redraw(struct ncreel* nr)
|
||||
__attribute__ ((nonnull (1)));
|
||||
|
||||
// Offer input 'ni' to the ncreel 'nr'. If it's relevant, this function returns
|
||||
// true, and the input ought not be processed further. If it's irrelevant to
|
||||
@ -2666,17 +2674,21 @@ API int ncreel_redraw(struct ncreel* nr);
|
||||
// * a mouse click on a tablet (focuses tablet)
|
||||
// * a mouse scrollwheel event (rolls reel)
|
||||
// * up, down, pgup, or pgdown (navigates among items)
|
||||
API bool ncreel_offer_input(struct ncreel* nr, const struct ncinput* ni);
|
||||
API bool ncreel_offer_input(struct ncreel* nr, const struct ncinput* ni)
|
||||
__attribute__ ((nonnull (1)));
|
||||
|
||||
// Return the focused tablet, if any tablets are present. This is not a copy;
|
||||
// be careful to use it only for the duration of a critical section.
|
||||
API struct nctablet* ncreel_focused(struct ncreel* nr);
|
||||
API struct nctablet* ncreel_focused(struct ncreel* nr)
|
||||
__attribute__ ((nonnull (1)));
|
||||
|
||||
// Change focus to the next tablet, if one exists
|
||||
API struct nctablet* ncreel_next(struct ncreel* nr);
|
||||
API struct nctablet* ncreel_next(struct ncreel* nr)
|
||||
__attribute__ ((nonnull (1)));
|
||||
|
||||
// Change focus to the previous tablet, if one exists
|
||||
API struct nctablet* ncreel_prev(struct ncreel* nr);
|
||||
API struct nctablet* ncreel_prev(struct ncreel* nr)
|
||||
__attribute__ ((nonnull (1)));
|
||||
|
||||
// Destroy an ncreel allocated with ncreel_create().
|
||||
API void ncreel_destroy(struct ncreel* nr);
|
||||
@ -2713,7 +2725,8 @@ API struct ncplane* nctablet_ncplane(struct nctablet* t)
|
||||
//
|
||||
// You are encouraged to consult notcurses_metric(3).
|
||||
API const char* ncmetric(uintmax_t val, uintmax_t decimal, char* buf,
|
||||
int omitdec, uintmax_t mult, int uprefix);
|
||||
int omitdec, uintmax_t mult, int uprefix)
|
||||
__attribute__ ((nonnull (3)));
|
||||
|
||||
// The number of columns is one fewer, as the STRLEN expressions must leave
|
||||
// an extra byte open in case 'µ' (U+00B5, 0xC2 0xB5) shows up. PREFIXCOLUMNS
|
||||
|
@ -606,11 +606,6 @@ ncplane_cell_ref_yx(ncplane* n, int y, int x){
|
||||
return &n->fb[nfbcellidx(n, y, x)];
|
||||
}
|
||||
|
||||
static inline void
|
||||
cell_set_wide(nccell* c){
|
||||
c->channels |= CELL_WIDEASIAN_MASK;
|
||||
}
|
||||
|
||||
#define NANOSECS_IN_SEC 1000000000
|
||||
|
||||
static inline uint64_t
|
||||
@ -998,16 +993,10 @@ pool_blit_direct(egcpool* pool, nccell* c, const char* gcluster, int bytes, int
|
||||
static inline int
|
||||
pool_load_direct(egcpool* pool, nccell* c, const char* gcluster, int bytes, int cols){
|
||||
char* rtl = NULL;
|
||||
c->width = cols - 1;
|
||||
if(cols < 2){
|
||||
c->channels &= ~CELL_WIDEASIAN_MASK;
|
||||
if(bytes == 3 && memcmp(gcluster, "\xe2\x96\x88", 4) == 0){
|
||||
c->channels |= CELL_NOBACKGROUND_MASK;
|
||||
}else{
|
||||
c->channels &= ~CELL_NOBACKGROUND_MASK;
|
||||
}
|
||||
c->width = cols;
|
||||
if(bytes == 3 && memcmp(gcluster, "\xe2\x96\x88", 4) == 0){
|
||||
c->channels |= CELL_NOBACKGROUND_MASK;
|
||||
}else{
|
||||
c->channels |= CELL_WIDEASIAN_MASK;
|
||||
c->channels &= ~CELL_NOBACKGROUND_MASK;
|
||||
}
|
||||
if(bytes >= 0){
|
||||
|
@ -1526,6 +1526,7 @@ ncplane_put(ncplane* n, int y, int x, const char* egc, int cols,
|
||||
cell_release(n, candidate);
|
||||
candidate->channels = targ->channels;
|
||||
candidate->stylemask = targ->stylemask;
|
||||
candidate->width = targ->width;
|
||||
++n->x;
|
||||
}
|
||||
return cols;
|
||||
|
@ -821,7 +821,7 @@ nctablet* ncreel_add(ncreel* nr, nctablet* after, nctablet *before,
|
||||
}
|
||||
|
||||
int ncreel_del(ncreel* nr, struct nctablet* t){
|
||||
if(nr == NULL || t == NULL){
|
||||
if(t == NULL){
|
||||
return -1;
|
||||
}
|
||||
t->prev->next = t->next;
|
||||
|
@ -284,12 +284,13 @@ paint(const ncplane* p, struct crender* rvec, int dstleny, int dstlenx,
|
||||
// are we on the last column of the real screen? if so, 0x20 us
|
||||
if(absx >= dstlenx - 1){
|
||||
targc->gcluster = htole(' ');
|
||||
targc->width = 1;
|
||||
// is the next cell occupied? if so, 0x20 us
|
||||
}else if(crender[1].c.gcluster){
|
||||
//fprintf(stderr, "NULLING out %d/%d (%d/%d) due to %u\n", y, x, absy, absx, crender[1].c.gcluster);
|
||||
targc->gcluster = htole(' ');
|
||||
targc->width = 1;
|
||||
}else{
|
||||
cell_set_wide(targc);
|
||||
targc->stylemask = vis->stylemask;
|
||||
targc->width = vis->width;
|
||||
}
|
||||
@ -299,9 +300,8 @@ paint(const ncplane* p, struct crender* rvec, int dstleny, int dstlenx,
|
||||
}
|
||||
crender->p = p;
|
||||
}else if(cell_wide_right_p(vis)){
|
||||
cell_set_wide(targc);
|
||||
crender->p = p;
|
||||
targc->width = vis->width;
|
||||
targc->width = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -403,21 +403,19 @@ postpaint_cell(nccell* lastframe, int dimx, struct crender* crender,
|
||||
if(cellcmp_and_dupfar(pool, prevcell, crender->p, targc) > 0){
|
||||
crender->damaged = true;
|
||||
assert(!cell_wide_right_p(targc));
|
||||
if(cell_wide_left_p(targc)){
|
||||
const int width = targc->width;
|
||||
for(int i = 1 ; i < width ; ++i){
|
||||
const ncplane* tmpp = crender->p;
|
||||
++crender;
|
||||
crender->p = tmpp;
|
||||
++*x;
|
||||
++prevcell;
|
||||
++targc;
|
||||
targc->gcluster = 0;
|
||||
targc->channels = crender[-1].c.channels;
|
||||
targc->stylemask = crender[-1].c.stylemask;
|
||||
if(cellcmp_and_dupfar(pool, prevcell, crender->p, targc) > 0){
|
||||
crender->damaged = true;
|
||||
}
|
||||
const int width = targc->width;
|
||||
for(int i = 1 ; i < width ; ++i){
|
||||
const ncplane* tmpp = crender->p;
|
||||
++crender;
|
||||
crender->p = tmpp;
|
||||
++*x;
|
||||
++prevcell;
|
||||
targc = &crender->c;
|
||||
targc->gcluster = 0;
|
||||
targc->channels = crender[-i].c.channels;
|
||||
targc->stylemask = crender[-i].c.stylemask;
|
||||
if(cellcmp_and_dupfar(pool, prevcell, crender->p, targc) > 0){
|
||||
crender->damaged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -970,12 +968,15 @@ notcurses_rasterize_inner(notcurses* nc, const ncpile* p, FILE* out){
|
||||
nc->rstate.bgdefelidable = false;
|
||||
nc->rstate.bgpalelidable = false;
|
||||
}
|
||||
//fprintf(stderr, "RAST %08x [%s] to %d/%d cols: %u %016lx\n", srccell->gcluster, pool_extended_gcluster(&nc->pool, srccell), y, x, srccell->width + 1, srccell->channels);
|
||||
//fprintf(stderr, "RAST %08x [%s] to %d/%d cols: %u %016lx\n", srccell->gcluster, pool_extended_gcluster(&nc->pool, srccell), y, x, srccell->width, srccell->channels);
|
||||
if(term_putc(out, &nc->pool, srccell)){
|
||||
return -1;
|
||||
}
|
||||
nc->rstate.x += srccell->width + 1;
|
||||
x += srccell->width;
|
||||
++nc->rstate.x;
|
||||
if(srccell->width >= 2){
|
||||
x += srccell->width - 1;
|
||||
nc->rstate.x += srccell->width - 1;
|
||||
}
|
||||
}
|
||||
//fprintf(stderr, "damageidx: %ld\n", damageidx);
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ TEST_CASE("Wide") {
|
||||
wchar_t w;
|
||||
CHECK(0 < mbtowc(&w, cell_extended_gcluster(n_, &tcell), MB_CUR_MAX));
|
||||
if(wcwidth(w) == 2){
|
||||
CHECK(1 == testcell.width);
|
||||
CHECK(2 == testcell.width);
|
||||
++x;
|
||||
}else{
|
||||
CHECK(0 == testcell.channels);
|
||||
@ -257,7 +257,7 @@ TEST_CASE("Wide") {
|
||||
}
|
||||
|
||||
SUBCASE("RenderWides") {
|
||||
CHECK(0 <= ncplane_putstr(n_, "\xe5\xbd\xa2\xe5\x85\xa8"));
|
||||
CHECK(0 <= ncplane_putstr(n_, "\u5f62\u5168"));
|
||||
nccell c = CELL_TRIVIAL_INITIALIZER;
|
||||
ncplane_at_yx_cell(n_, 0, 0, &c);
|
||||
CHECK(cell_double_wide_p(&c));
|
||||
@ -270,16 +270,19 @@ TEST_CASE("Wide") {
|
||||
ncplane_at_yx_cell(n_, 0, 4, &c);
|
||||
CHECK(!cell_double_wide_p(&c));
|
||||
CHECK(0 == notcurses_render(nc_));
|
||||
notcurses_at_yx(nc_, 0, 0, &c.stylemask, &c.channels);
|
||||
CHECK(0 != (c.channels & 0x8000000080000000ull));
|
||||
notcurses_at_yx(nc_, 0, 1, &c.stylemask, &c.channels);
|
||||
CHECK(0 != (c.channels & 0x8000000080000000ull));
|
||||
notcurses_at_yx(nc_, 0, 2, &c.stylemask, &c.channels);
|
||||
CHECK(0 != (c.channels & 0x8000000080000000ull));
|
||||
notcurses_at_yx(nc_, 0, 3, &c.stylemask, &c.channels);
|
||||
CHECK(0 != (c.channels & 0x8000000080000000ull));
|
||||
notcurses_at_yx(nc_, 0, 4, &c.stylemask, &c.channels);
|
||||
CHECK(!(c.channels & 0x8000000080000000ull));
|
||||
auto egc = notcurses_at_yx(nc_, 0, 0, &c.stylemask, &c.channels);
|
||||
REQUIRE(nullptr != egc);
|
||||
CHECK(0 == strcmp("\u5f62", egc));
|
||||
egc = notcurses_at_yx(nc_, 0, 1, &c.stylemask, &c.channels);
|
||||
REQUIRE(nullptr != egc);
|
||||
CHECK(0 == strcmp("", egc));
|
||||
egc = notcurses_at_yx(nc_, 0, 2, &c.stylemask, &c.channels);
|
||||
REQUIRE(nullptr != egc);
|
||||
CHECK(0 == strcmp("\u5168", egc));
|
||||
egc = notcurses_at_yx(nc_, 0, 3, &c.stylemask, &c.channels);
|
||||
REQUIRE(nullptr != egc);
|
||||
egc = notcurses_at_yx(nc_, 0, 4, &c.stylemask, &c.channels);
|
||||
REQUIRE(nullptr != egc);
|
||||
}
|
||||
|
||||
// If an ncplane is moved atop the right half of a wide glyph, the entire
|
||||
|
Loading…
x
Reference in New Issue
Block a user