diff --git a/CMakeLists.txt b/CMakeLists.txt index aa87e0f29..981f6e129 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -650,7 +650,7 @@ target_link_libraries(notcurses-tester ) add_test( NAME notcurses-tester - COMMAND notcurses-tester -p ${CMAKE_CURRENT_SOURCE_DIR}/data + COMMAND notcurses-tester -p ${CMAKE_CURRENT_SOURCE_DIR}/data --abort-after=1 ) set_tests_properties(notcurses-tester PROPERTIES RUN_SERIAL TRUE) install(TARGETS notcurses-tester DESTINATION bin) diff --git a/NEWS.md b/NEWS.md index 2fedadbb0..1fc5d0c57 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,6 +10,18 @@ rearrangements of Notcurses. only if its `ncinput` argument has no modifiers active. * Added `notcurses_cursor_yx()` to get the current location of the cursor. * Added `ncdirect_supported_styles()`. + * `ncplane_at_yx()` now properly integrates the plane's base cell when + appropriate, and thus represents the cell as it will be used during + rendering. This change cascades, affecting `ncplane_at_yx_cell()`, + `ncplane_contents()`, and `ncplane_as_rgba()`. + * `ncplane_at_yx()` now returns the EGC when called on any column of a + wide glyph. `ncplane_at_yx_cell()` continues to duplicate the exact + `nccell`, and can thus continue to be used to distinguish between primary + and secondary columns of a wide glyph. Likewise, `notcurses_at_yx()` + now returns the EGC when called on any column of a wide glyph. + * Sadly, `ncplane_contents()` no longer accepts a `const ncplane*`, since it + might write temporaries to the plane's EGCpool during operation. + * Added `ncdirect_styles()`, to retrieve the current styling. * 2.3.1 (2021-05-18) * Sprixels no longer interact with their associated plane's framebuffer. This diff --git a/USAGE.md b/USAGE.md index f9d1ba040..bb8f8ee12 100644 --- a/USAGE.md +++ b/USAGE.md @@ -1045,13 +1045,20 @@ char* ncplane_at_cursor(struct ncplane* n, uint16_t* styles, uint64_t* channels) int ncplane_at_cursor_cell(struct ncplane* n, nccell* c); // Retrieve the current contents of the specified cell. The EGC is returned, or -// NULL on error. This EGC must be free()d by the caller. The styles and -// channels are written to 'styles' and 'channels', respectively. +// NULL on error. This EGC must be free()d by the caller. The stylemask and +// channels are written to 'stylemask' and 'channels', respectively. The return +// represents how the cell will be used during rendering, and thus integrates +// any base cell where appropriate. If called upon the secondary columns of a +// wide glyph, the EGC will be returned (i.e. this function does not distinguish +// between the primary and secondary columns of a wide glyph). char* ncplane_at_yx(const struct ncplane* n, int y, int x, - uint16_t* styles, uint64_t* channels); + uint16_t* stylemask, uint64_t* channels); // Retrieve the current contents of the specified cell into 'c'. This cell is -// invalidated if the associated plane is destroyed. +// invalidated if the associated plane is destroyed. Returns the number of +// bytes in the EGC, or -1 on error. Unlike ncplane_at_yx(), when called upon +// the secondary columns of a wide glyph, the return can be distinguished from +// the primary column (nccell_wide_right_p(c) will return true). int ncplane_at_yx_cell(struct ncplane* n, int y, int x, nccell* c); // Create an RGBA flat array from the selected region of the ncplane 'nc'. diff --git a/doc/man/man3/notcurses_plane.3.md b/doc/man/man3/notcurses_plane.3.md index f2979a7a9..8b3ac747d 100644 --- a/doc/man/man3/notcurses_plane.3.md +++ b/doc/man/man3/notcurses_plane.3.md @@ -375,6 +375,16 @@ is resized, the plane is erased, or the plane is destroyed. The base cell of a sprixelated plane has no effect; if the sprixel is not even multiples of the cell geometry, the "excess plane" is ignored during rendering. +**ncplane_at_yx** and **ncplane_at_yx_cell** retrieve the contents of the plane +at the specified coordinate. The content is returned as it will be used during +rendering, and thus integrates any base cell as appropriate. If called on the +secondary columns of a wide glyph, **ncplane_at_yx** returns the EGC, and thus +cannot be used to distinguish between primary and secondary columns. +**ncplane_at_yx_cell**, however, preserves this information: retrieving a +secondary column of a wide glyph with **ncplane_at_yx_cell** will fill in +the **nccell** argument such that **nccell_extended_gcluster(3)** returns an +empty string, and **nccell_wide_right_p(3)** returns **true**. + # RETURN VALUES **ncplane_create** and **ncplane_dup** return a new **struct ncplane** on @@ -394,10 +404,11 @@ respectively, of the pile containing their argument. **notcurses_top** and **notcurses_bottom** do the same for the standard pile. **ncplane_at_yx** and **ncplane_at_cursor** return a heap-allocated copy of the -EGC at the relevant cell, or **NULL** if the cell is invalid. The caller should free -this result. **ncplane_at_yx_cell** and **ncplane_at_cursor_cell** instead load -these values into an **nccell**, which is invalidated if the associated plane is -destroyed. The caller should release this **nccell** with **nccell_release**. +EGC at the relevant cell, or **NULL** if the cell is invalid. The caller should +free this result. **ncplane_at_yx_cell** and **ncplane_at_cursor_cell** instead +load these values into an **nccell**, which is invalidated if the associated +plane is destroyed. The caller should release this **nccell** with +**nccell_release**. **ncplane_as_rgba** returns a heap-allocated array of **uint32_t** values, each representing a single RGBA pixel, or **NULL** on failure. diff --git a/doc/man/man3/notcurses_render.3.md b/doc/man/man3/notcurses_render.3.md index 3c33997ac..f02f228e5 100644 --- a/doc/man/man3/notcurses_render.3.md +++ b/doc/man/man3/notcurses_render.3.md @@ -93,8 +93,9 @@ If the algorithm concludes without an EGC, the cell is rendered with no glyph and a default background. If the algorithm concludes without a color locked in, the color as computed thus far is used. -**notcurses_at_yx** retrieves a call *as rendered*. The EGC in that cell is -copied and returned; it must be **free(3)**d by the caller. +**notcurses_at_yx** retrieves a cell *as rendered*. The EGC in that cell is +copied and returned; it must be **free(3)**d by the caller. If the cell is a +secondary column of a wide glyph, the glyph is still returned. # RETURN VALUES diff --git a/doc/release-checklist.md b/doc/release-checklist.md index b697a8603..1cc7a0a0f 100644 --- a/doc/release-checklist.md +++ b/doc/release-checklist.md @@ -47,7 +47,7 @@ ### Fedora -* In pagure/notcurses master: +* In pagure/notcurses rawhide: * Update notcurses.spec, bump version, add changelog entry * clear out any old ersatz detritus * spectool -g notcurses.spec @@ -56,8 +56,9 @@ * fedpkg commit * git push * fedpkg build - * fedpkg switch-branch f32 - * git merge master + * for each active branch: + * fedpkg switch-branch f32 + * git merge rawhide ### Arch diff --git a/include/notcurses/notcurses.h b/include/notcurses/notcurses.h index cc8114360..3672543dd 100644 --- a/include/notcurses/notcurses.h +++ b/include/notcurses/notcurses.h @@ -653,7 +653,7 @@ API int nccell_load(struct ncplane* n, nccell* c, const char* gcluster); // nccell_load(), plus blast the styling with 'attr' and 'channels'. static inline int nccell_prime(struct ncplane* n, nccell* c, const char* gcluster, - uint32_t stylemask, uint64_t channels){ + uint32_t stylemask, uint64_t channels){ c->stylemask = stylemask; c->channels = channels; int ret = nccell_load(n, c, gcluster); @@ -748,7 +748,7 @@ nccell_wide_left_p(const nccell* c){ // return a pointer to the NUL-terminated EGC referenced by 'c'. this pointer // can be invalidated by any further operation on the plane 'n', so...watch out! -API const char* nccell_extended_gcluster(const struct ncplane* n, const nccell* c); +API __attribute__ ((returns_nonnull)) const char* nccell_extended_gcluster(const struct ncplane* n, const nccell* c); // return the number of columns occupied by 'c'. returns -1 if passed a // sprixcell. see ncstrwidth() for an equivalent for multiple EGCs. @@ -1467,20 +1467,26 @@ API int ncplane_at_cursor_cell(struct ncplane* n, nccell* c); // Retrieve the current contents of the specified cell. The EGC is returned, or // NULL on error. This EGC must be free()d by the caller. The stylemask and -// channels are written to 'stylemask' and 'channels', respectively. +// channels are written to 'stylemask' and 'channels', respectively. The return +// represents how the cell will be used during rendering, and thus integrates +// any base cell where appropriate. If called upon the secondary columns of a +// wide glyph, the EGC will be returned (i.e. this function does not distinguish +// between the primary and secondary columns of a wide glyph). API char* ncplane_at_yx(const struct ncplane* n, int y, int x, uint16_t* stylemask, uint64_t* channels); // Retrieve the current contents of the specified cell into 'c'. This cell is // invalidated if the associated plane is destroyed. Returns the number of -// bytes in the EGC, or -1 on error. +// bytes in the EGC, or -1 on error. Unlike ncplane_at_yx(), when called upon +// the secondary columns of a wide glyph, the return can be distinguished from +// the primary column (nccell_wide_right_p(c) will return true). API int ncplane_at_yx_cell(struct ncplane* n, int y, int x, nccell* c); // Create a flat string from the EGCs of the selected region of the ncplane // 'n'. Start at the plane's 'begy'x'begx' coordinate (which must lie on the // plane), continuing for 'leny'x'lenx' cells. Either or both of 'leny' and // 'lenx' can be specified as -1 to go through the boundary of the plane. -API char* ncplane_contents(const struct ncplane* n, int begy, int begx, +API char* ncplane_contents(struct ncplane* n, int begy, int begx, int leny, int lenx); // Manipulate the opaque user pointer associated with this plane. diff --git a/rust/src/plane/methods.rs b/rust/src/plane/methods.rs index f035769e3..634ccdfc3 100644 --- a/rust/src/plane/methods.rs +++ b/rust/src/plane/methods.rs @@ -691,7 +691,7 @@ impl NcPlane { /// /// *C style function: [ncplane_contents()][crate::ncplane_contents].* pub fn contents( - &self, + &mut self, beg_y: NcDim, beg_x: NcDim, len_y: NcDim, diff --git a/src/demo/normal.c b/src/demo/normal.c index fe4caaaa5..af19bafda 100644 --- a/src/demo/normal.c +++ b/src/demo/normal.c @@ -62,9 +62,7 @@ rotate_visual(struct notcurses* nc, struct ncplane* n, int dy, int dx){ fromx = (dx - dy * 2) / 2; dx = dy * 2; } -//fprintf(stderr, "ASK %d/%d @ %d/%d: %p\n", dy, dx, fromy, fromx); - struct ncvisual* ncv = ncvisual_from_plane(n, NCBLIT_DEFAULT, fromy, fromx, dy, dx); -//fprintf(stderr, "%d/%d @ %d/%d: %p\n", dy, dx, fromy, fromx, ncv); + struct ncvisual* ncv = ncvisual_from_plane(n, NCBLIT_2x1, fromy, fromx, dy, dx); if(!ncv){ ncvisual_destroy(ncv); return -1; diff --git a/src/input/input.cpp b/src/input/input.cpp index 17b5f0c64..847ed2d35 100644 --- a/src/input/input.cpp +++ b/src/input/input.cpp @@ -206,7 +206,22 @@ void Ticker(ncpp::NotCurses* nc) { int input_demo(ncpp::NotCurses* nc) { constexpr auto PLOTHEIGHT = 6; auto n = nc->get_stdplane(&dimy, &dimx); - ncpp::Plane pplane{PLOTHEIGHT, dimx, dimy - PLOTHEIGHT, 0, nullptr}; + struct ncplane_options nopts = { + .y = dimy - PLOTHEIGHT, + .x = 0, + .rows = PLOTHEIGHT, + .cols = dimx, + .userptr = nullptr, + .name = "plot", + .resizecb = nullptr, // FIXME + .flags = 0, + .margin_b = 0, + .margin_r = 0, + }; + struct ncplane* pplane = ncplane_create(*n, &nopts); + if(pplane == nullptr){ + return EXIT_FAILURE; + } struct ncplot_options popts{}; // FIXME would be nice to switch over to exponential at some level popts.flags = NCPLOT_OPTION_LABELTICKSD | NCPLOT_OPTION_PRINTSAMPLE; @@ -222,11 +237,13 @@ int input_demo(ncpp::NotCurses* nc) { n->set_bg_rgb8(0xbb, 0x64, 0xbb); n->styles_on(CellStyle::Underline); if(n->putstr(0, NCAlign::Center, "mash keys, yo. give that mouse some waggle! ctrl+d exits.") <= 0){ + ncuplot_destroy(plot); return -1; } n->styles_set(CellStyle::None); n->set_bg_default(); if(!nc->render()){ + ncuplot_destroy(plot); return -1; } int y = 2; @@ -243,6 +260,7 @@ int input_demo(ncpp::NotCurses* nc) { if((r == 'D' || r == 'd') && ni.ctrl){ done = true; tid.join(); + ncuplot_destroy(plot); return 0; } if((r == 'L' || r == 'l') && ni.ctrl){ @@ -292,6 +310,7 @@ int input_demo(ncpp::NotCurses* nc) { } if(!nc->render()){ mtx.unlock(); + ncuplot_destroy(plot); throw std::runtime_error("error rendering"); } mtx.unlock(); @@ -309,6 +328,7 @@ int input_demo(ncpp::NotCurses* nc) { } done = true; tid.join(); + ncuplot_destroy(plot); return 0; } diff --git a/src/lib/blit.c b/src/lib/blit.c index 716662929..c19beb77b 100644 --- a/src/lib/blit.c +++ b/src/lib/blit.c @@ -850,24 +850,38 @@ braille_blit(ncplane* nc, int linesize, const void* data, // NCBLIT_DEFAULT is not included, as it has no defined properties. It ought // be replaced with some real blitter implementation by the calling widget. +// The order of contents is critical for 'egcs': ncplane_as_rgba() uses these +// arrays to map cells to source pixels. Map the upper-left logical bit to +// 1, and increase to the right, followed by down. The first egc ought thus +// always be space, to indicate an empty cell (all zeroes). static struct blitset notcurses_blitters[] = { - { .geom = NCBLIT_8x1, .width = 1, .height = 8, .egcs = L" ▁▂▃▄▅▆▇█", + { .geom = NCBLIT_8x1, .width = 1, .height = 8, + .egcs = NULL, .plotegcs = L" ▁▂▃▄▅▆▇█", .blit = tria_blit, .name = "eightstep", .fill = false, }, - { .geom = NCBLIT_1x1, .width = 1, .height = 1, .egcs = L" █", + { .geom = NCBLIT_1x1, .width = 1, .height = 1, + .egcs = L" █", .plotegcs = L" █", .blit = tria_blit_ascii,.name = "ascii", .fill = false, }, - { .geom = NCBLIT_2x1, .width = 1, .height = 2, .egcs = L" ▄█", + { .geom = NCBLIT_2x1, .width = 1, .height = 2, + .egcs = L" ▀▄█", .plotegcs = L" ▄█", .blit = tria_blit, .name = "half", .fill = false, }, - { .geom = NCBLIT_2x2, .width = 2, .height = 2, .egcs = L" ▗▐▖▄▟▌▙█", + { .geom = NCBLIT_2x2, .width = 2, .height = 2, + .egcs = L" ▘▝▀▖▌▞▛▗▚▐▜▄▙▟█", .plotegcs = L" ▗▐▖▄▟▌▙█", .blit = quadrant_blit, .name = "quad", .fill = false, }, - { .geom = NCBLIT_3x2, .width = 2, .height = 3, .egcs = L" 🬞🬦▐🬏🬭🬵🬷🬓🬱🬹🬻▌🬲🬺█", + { .geom = NCBLIT_3x2, .width = 2, .height = 3, + .egcs = L" 🬀🬁🬂🬃🬄🬅🬆🬇🬈🬊🬋🬌🬍🬎🬏🬐🬑🬒🬓▌🬔🬕🬖🬗🬘🬙🬚🬛🬜🬝🬞🬟🬠🬡🬢🬣🬤🬥🬦🬧🬨🬩🬪🬫🬬🬭🬮🬯🬰🬱🬲🬳🬴🬵🬶🬷🬸🬹🬺🬻█", + .plotegcs = L" 🬞🬦▐🬏🬭🬵🬷🬓🬱🬹🬻▌🬲🬺█", .blit = sextant_blit, .name = "sex", .fill = false, }, - { .geom = NCBLIT_4x1, .width = 1, .height = 4, .egcs = L" ▂▄▆█", + { .geom = NCBLIT_4x1, .width = 1, .height = 4, + .egcs = NULL, .plotegcs = L" ▂▄▆█", .blit = tria_blit, .name = "fourstep", .fill = false, }, - { .geom = NCBLIT_BRAILLE, .width = 2, .height = 4, .egcs = L"⠀⢀⢠⢰⢸⡀⣀⣠⣰⣸⡄⣄⣤⣴⣼⡆⣆⣦⣶⣾⡇⣇⣧⣷⣿", + { .geom = NCBLIT_BRAILLE, .width = 2, .height = 4, + .egcs = NULL, .plotegcs = L"⠀⢀⢠⢰⢸⡀⣀⣠⣰⣸⡄⣄⣤⣴⣼⡆⣆⣦⣶⣾⡇⣇⣧⣷⣿", // FIXME .blit = braille_blit, .name = "braille", .fill = true, }, - { .geom = NCBLIT_PIXEL, .width = 1, .height = 1, .egcs = L"", + { .geom = NCBLIT_PIXEL, .width = 1, .height = 1, + .egcs = L"", .plotegcs = NULL, .blit = sixel_blit, .name = "pixel", .fill = true, }, - { .geom = 0, .width = 0, .height = 0, .egcs = NULL, + { .geom = 0, .width = 0, .height = 0, + .egcs = NULL, .plotegcs = NULL, .blit = NULL, .name = NULL, .fill = false, }, }; @@ -924,7 +938,7 @@ const struct blitset* lookup_blitset(const tinfo* tcache, ncblitter_e setid, boo } } const struct blitset* bset = notcurses_blitters; - while(bset->egcs){ + while(bset->geom){ if(bset->geom == setid){ return bset; } diff --git a/src/lib/internal.h b/src/lib/internal.h index 4f9dd3b00..d83513c02 100644 --- a/src/lib/internal.h +++ b/src/lib/internal.h @@ -535,11 +535,14 @@ struct blitset { ncblitter_e geom; int width; // number of input pixels per output cell, width int height; // number of input pixels per output cell, height + // the EGCs which form the blitter. bits grow left to right, and then top to + // bottom. the first character is always a space, the last a full block. + const wchar_t* egcs; // the EGCs which form the various levels of a given plotset. if the geometry // is wide, things are arranged with the rightmost side increasing most // quickly, i.e. it can be indexed as height arrays of 1 + height glyphs. i.e. // the first five braille EGCs are all 0 on the left, [0..4] on the right. - const wchar_t* egcs; + const wchar_t* plotegcs; ncblitter blit; const char* name; bool fill; @@ -809,7 +812,7 @@ pool_extended_gcluster(const egcpool* pool, const nccell* c){ } static inline nccell* -ncplane_cell_ref_yx(ncplane* n, int y, int x){ +ncplane_cell_ref_yx(const ncplane* n, int y, int x){ return &n->fb[nfbcellidx(n, y, x)]; } @@ -1425,7 +1428,7 @@ pool_blit_direct(egcpool* pool, nccell* c, const char* gcluster, int bytes, int } // Do an RTL-check, reset the quadrant occupancy bits, and pass the cell down to -// pool_blit_direct(). +// pool_blit_direct(). Returns the number of bytes loaded. static inline int pool_load_direct(egcpool* pool, nccell* c, const char* gcluster, int bytes, int cols){ char* rtl = NULL; diff --git a/src/lib/notcurses.c b/src/lib/notcurses.c index 18ee68001..4fee44410 100644 --- a/src/lib/notcurses.c +++ b/src/lib/notcurses.c @@ -161,7 +161,27 @@ char* ncplane_at_cursor(ncplane* n, uint16_t* stylemask, uint64_t* channels){ char* ncplane_at_yx(const ncplane* n, int y, int x, uint16_t* stylemask, uint64_t* channels){ if(y < n->leny && x < n->lenx){ if(y >= 0 && x >= 0){ - return nccell_extract(n, &n->fb[nfbcellidx(n, y, x)], stylemask, channels); + const cell* yx = &n->fb[nfbcellidx(n, y, x)]; + // if we're the right side of a wide glyph, we return the main glyph + if(nccell_wide_right_p(yx)){ + return ncplane_at_yx(n, y, x - 1, stylemask, channels); + } + char* ret = nccell_extract(n, yx, stylemask, channels); + if(ret == NULL){ + return NULL; + } +//fprintf(stderr, "GOT [%s]\n", ret); + if(strcmp(ret, "") == 0){ + ret = nccell_strdup(n, &n->basecell); + if(ret == NULL){ + return NULL; + } + if(stylemask){ + *stylemask = n->basecell.stylemask; + } + } + // FIXME load basecell channels if appropriate + return ret; } } return NULL; @@ -176,6 +196,7 @@ int ncplane_at_yx_cell(ncplane* n, int y, int x, nccell* c){ if(y >= 0 && x >= 0){ nccell* targ = ncplane_cell_ref_yx(n, y, x); if(nccell_duplicate(n, c, targ) == 0){ + // FIXME take base cell into account where necessary! return strlen(nccell_extended_gcluster(n, targ)); } } @@ -2583,9 +2604,40 @@ int ncdirect_inputready_fd(ncdirect* n){ return n->tcache.input.ttyinfd; } -uint32_t* ncplane_as_rgba(const ncplane* nc, ncblitter_e blit, - int begy, int begx, int leny, int lenx, - int* pxdimy, int* pxdimx){ +// FIXME speed this up, PoC +// given an egc, get its index in the blitter's EGC set +static int +get_blitter_egc_idx(const struct blitset* bset, const char* egc){ + wchar_t wc; + mbstate_t mbs = {}; + size_t sret = mbrtowc(&wc, egc, strlen(egc), &mbs); + if(sret == (size_t)-1 || sret == (size_t)-2){ + return -1; + } + wchar_t* wptr = wcsrchr(bset->egcs, wc); + if(wptr == NULL){ +//fprintf(stderr, "FAILED TO FIND [%s] (%lc) in [%ls]\n", egc, wc, bset->egcs); + return -1; + } +//fprintf(stderr, "FOUND [%s] (%lc) in [%ls] (%zu)\n", egc, wc, bset->egcs, wptr - bset->egcs); + return wptr - bset->egcs; +} + +static bool +is_bg_p(int idx, int py, int px, int width){ + // bit increases to the right, and down + const int bpos = py * width + px; // bit corresponding to pixel, 0..|egcs|-1 + const unsigned mask = 1u << bpos; + if(idx & mask){ + return false; + } + return true; +} + +static inline uint32_t* +ncplane_as_rgba_internal(const ncplane* nc, ncblitter_e blit, + int begy, int begx, int leny, int lenx, + int* pxdimy, int* pxdimx){ const notcurses* ncur = ncplane_notcurses_const(nc); if(begy < 0 || begx < 0){ logerror(ncur, "Nil offset (%d,%d)\n", begy, begx); @@ -2611,23 +2663,31 @@ uint32_t* ncplane_as_rgba(const ncplane* nc, ncblitter_e blit, begx, lenx, nc->lenx, begy, leny, nc->leny); return NULL; } - if(blit > NCBLIT_2x1){ - logerror(ncur, "Blitter %d is not yet supported\n", blit); + if(blit == NCBLIT_PIXEL){ // FIXME extend this to support sprixels + logerror(ncur, "Pixel blitter %d not yet supported\n", blit); return NULL; } -//fprintf(stderr, "ALLOCATING %zu %d %d\n", 4u * lenx * leny * 2, leny, lenx); - // FIXME this all assumes NCBLIT_2x1, need blitter-specific scaling + if(blit == NCBLIT_DEFAULT){ + logerror(ncur, "Must specify exact blitter, not NCBLIT_DEFAULT\n"); + return NULL; + } + const struct blitset* bset = lookup_blitset(&ncur->tcache, blit, false); + if(bset == NULL){ + logerror(ncur, "Blitter %d invalid in current environment\n", blit); + return NULL; + } +//fprintf(stderr, "ALLOCATING %u %d %d %p\n", 4u * lenx * leny * 2, leny, lenx, bset); if(pxdimy){ - *pxdimy = leny * 2; + *pxdimy = leny * bset->height; } if(pxdimx){ - *pxdimx = lenx; + *pxdimx = lenx * bset->width; } - uint32_t* ret = malloc(sizeof(*ret) * lenx * leny * 2); + uint32_t* ret = malloc(sizeof(*ret) * lenx * bset->width * leny * bset->height); +//fprintf(stderr, "GEOM: %d/%d %d/%d ret: %p\n", bset->height, bset->width, *pxdimy, *pxdimx, ret); if(ret){ - for(int y = begy, targy = 0 ; y < begy + leny ; ++y, targy += 2){ - for(int x = begx, targx = 0 ; x < begx + lenx ; ++x, ++targx){ - // FIXME what if there's a wide glyph to the left of the selection? + for(int y = begy, targy = 0 ; y < begy + leny ; ++y, targy += bset->height){ + for(int x = begx, targx = 0 ; x < begx + lenx ; ++x, targx += bset->width){ uint16_t stylemask; uint64_t channels; char* c = ncplane_at_yx(nc, y, x, &stylemask, &channels); @@ -2635,31 +2695,43 @@ uint32_t* ncplane_as_rgba(const ncplane* nc, ncblitter_e blit, free(ret); return NULL; } - uint32_t* top = &ret[targy * lenx + targx]; - uint32_t* bot = &ret[(targy + 1) * lenx + targx]; - unsigned fr, fg, fb, br, bg, bb; - ncchannels_fg_rgb8(channels, &fr, &fb, &fg); - ncchannels_bg_rgb8(channels, &br, &bb, &bg); - // FIXME how do we deal with transparency? - uint32_t frgba = (fr) + (fg << 16u) + (fb << 8u) + 0xff000000; - uint32_t brgba = (br) + (bg << 16u) + (bb << 8u) + 0xff000000; - // FIXME need to be able to pick up quadrants! - if((strcmp(c, " ") == 0) || (strcmp(c, "") == 0)){ - *top = *bot = brgba; - }else if(strcmp(c, "▄") == 0){ - *top = frgba; - *bot = brgba; - }else if(strcmp(c, "▀") == 0){ - *top = brgba; - *bot = frgba; - }else if(strcmp(c, "█") == 0){ - *top = *bot = frgba; - }else{ - free(c); + int idx = get_blitter_egc_idx(bset, c); + if(idx < 0){ free(ret); -//fprintf(stderr, "bad rgba character: %s\n", c); + free(c); return NULL; } + unsigned fr, fg, fb, br, bg, bb, fa, ba; + ncchannels_fg_rgb8(channels, &fr, &fb, &fg); + fa = ncchannels_fg_alpha(channels); + ncchannels_bg_rgb8(channels, &br, &bb, &bg); + ba = ncchannels_bg_alpha(channels); + // handle each destination pixel from this cell + for(int py = 0 ; py < bset->height ; ++py){ + for(int px = 0 ; px < bset->width ; ++px){ + uint32_t* p = &ret[(targy + py) * (lenx * bset->width) + (targx + px)]; + bool background = is_bg_p(idx, py, px, bset->width); + if(background){ + if(ba){ + *p = 0; + }else{ + ncpixel_set_a(p, 0xff); + ncpixel_set_r(p, br); + ncpixel_set_g(p, bb); + ncpixel_set_b(p, bg); + } + }else{ + if(fa){ + *p = 0; + }else{ + ncpixel_set_a(p, 0xff); + ncpixel_set_r(p, fr); + ncpixel_set_g(p, fb); + ncpixel_set_b(p, fg); + } + } + } + } free(c); } } @@ -2667,8 +2739,21 @@ uint32_t* ncplane_as_rgba(const ncplane* nc, ncblitter_e blit, return ret; } +uint32_t* ncplane_as_rgba(const ncplane* nc, ncblitter_e blit, + int begy, int begx, int leny, int lenx, + int* pxdimy, int* pxdimx){ + int px, py; + if(!pxdimy){ + pxdimy = &py; + } + if(!pxdimx){ + pxdimx = &px; + } + return ncplane_as_rgba_internal(nc, blit, begy, begx, leny, lenx, pxdimy, pxdimx); +} + // return a heap-allocated copy of the contents -char* ncplane_contents(const ncplane* nc, int begy, int begx, int leny, int lenx){ +char* ncplane_contents(ncplane* nc, int begy, int begx, int leny, int lenx){ if(begy < 0 || begx < 0){ logerror(ncplane_notcurses_const(nc), "Beginning coordinates (%d/%d) below 0\n", begy, begx); return NULL; @@ -2698,18 +2783,18 @@ char* ncplane_contents(const ncplane* nc, int begy, int begx, int leny, int lenx if(ret){ for(int y = begy, targy = 0 ; y < begy + leny ; ++y, targy += 2){ for(int x = begx, targx = 0 ; x < begx + lenx ; ++x, ++targx){ - uint16_t stylemask; - uint64_t channels; - char* c = ncplane_at_yx(nc, y, x, &stylemask, &channels); - if(!c){ + nccell ncl = CELL_TRIVIAL_INITIALIZER; + // we need ncplane_at_yx_cell() here instead of ncplane_at_yx(), + // because we should only have one copy of each wide EGC. + int clen; + if((clen = ncplane_at_yx_cell(nc, y, x, &ncl)) < 0){ free(ret); return NULL; } - size_t clen = strlen(c); + const char* c = nccell_extended_gcluster(nc, &ncl); if(clen){ char* tmp = realloc(ret, retlen + clen); if(!tmp){ - free(c); free(ret); return NULL; } @@ -2717,7 +2802,6 @@ char* ncplane_contents(const ncplane* nc, int begy, int begx, int leny, int lenx memcpy(ret + retlen - 1, c, clen); retlen += clen; } - free(c); } } ret[retlen - 1] = '\0'; diff --git a/src/lib/plot.c b/src/lib/plot.c index b3a5e3489..7a4f9dfd5 100644 --- a/src/lib/plot.c +++ b/src/lib/plot.c @@ -124,7 +124,7 @@ int redraw_plot_##T(nc##X##plot* ncp){ \ direction, drawing egcs from the grid specification, aborting early if \ we can't draw anything in a given cell. */ \ T intervalbase = ncp->miny; \ - const wchar_t* egc = ncp->bset->egcs; \ + const wchar_t* egc = ncp->bset->plotegcs; \ bool done = !ncp->bset->fill; \ for(int y = 0 ; y < dimy ; ++y){ \ uint64_t channels = 0; \ @@ -204,6 +204,7 @@ int redraw_plot_##T(nc##X##plot* ncp){ \ if(ncp->printsample){ \ int lastslot = ncp->slotstart ? ncp->slotstart - 1 : ncp->slotcount - 1; \ ncplane_set_styles(ncp->ncp, ncp->legendstyle); \ + ncplane_set_channels(ncp->ncp, ncp->maxchannels); \ ncplane_printf_aligned(ncp->ncp, 0, NCALIGN_RIGHT, "%ju", (uintmax_t)ncp->slots[lastslot]); \ } \ ncplane_home(ncp->ncp); \ diff --git a/src/lib/render.c b/src/lib/render.c index 646f989db..00f6a9869 100644 --- a/src/lib/render.c +++ b/src/lib/render.c @@ -1428,6 +1428,9 @@ char* notcurses_at_yx(notcurses* nc, int yoff, int xoff, uint16_t* stylemask, ui if(yoff >= 0 && yoff < nc->lfdimy){ if(xoff >= 0 || xoff < nc->lfdimx){ const nccell* srccell = &nc->lastframe[yoff * nc->lfdimx + xoff]; + if(nccell_wide_right_p(srccell)){ + return notcurses_at_yx(nc, yoff, xoff - 1, stylemask, channels); + } if(stylemask){ *stylemask = srccell->stylemask; } diff --git a/src/lib/visual.c b/src/lib/visual.c index b8a4fed89..cedf856bd 100644 --- a/src/lib/visual.c +++ b/src/lib/visual.c @@ -916,9 +916,9 @@ ncplane* ncvisual_render(notcurses* nc, ncvisual* ncv, const struct ncvisual_opt ncvisual* ncvisual_from_plane(const ncplane* n, ncblitter_e blit, int begy, int begx, int leny, int lenx){ - uint32_t* rgba = ncplane_as_rgba(n, blit, begy, begx, leny, lenx, NULL, NULL); + int py, px; + uint32_t* rgba = ncplane_as_rgba(n, blit, begy, begx, leny, lenx, &py, &px); //fprintf(stderr, "snarg: %d/%d @ %d/%d (%p)\n", leny, lenx, begy, begx, rgba); -//fprintf(stderr, "RGBA %p\n", rgba); if(rgba == NULL){ return NULL; } @@ -930,7 +930,7 @@ ncvisual* ncvisual_from_plane(const ncplane* n, ncblitter_e blit, int begy, int if(leny == -1){ leny = (n->leny - begy); } - ncvisual* ncv = ncvisual_from_rgba(rgba, leny * 2, lenx * 4, lenx); + ncvisual* ncv = ncvisual_from_rgba(rgba, py, px * 4, px); free(rgba); //fprintf(stderr, "RETURNING %p\n", ncv); return ncv; diff --git a/src/poc/sgr-direct.c b/src/poc/sgr-direct.c index acc4b3f94..1b6722fdb 100644 --- a/src/poc/sgr-direct.c +++ b/src/poc/sgr-direct.c @@ -19,10 +19,8 @@ int main(void){ ncdirect_stop(nc); return EXIT_FAILURE; } - printf("%08x ", i); - }else{ - printf(" "); } + printf("%08x ", i); if(ncdirect_set_styles(nc, NCSTYLE_NONE)){ ncdirect_stop(nc); return EXIT_FAILURE; diff --git a/src/tests/Ncpp.cpp b/src/tests/Ncpp.cpp index 1c97a0750..89a1a4f79 100644 --- a/src/tests/Ncpp.cpp +++ b/src/tests/Ncpp.cpp @@ -89,11 +89,12 @@ TEST_CASE("Ncpp" NotCurses nc{ nopts }; { auto n = nc.get_stdplane(); + uint64_t chan = CHANNELS_RGB_INITIALIZER(0x22, 0xdd, 0x44, 0, 0, 0); + n->set_base(" ", 0, chan); REQUIRE(n); - // FIXME load something onto standard plane, load it into visual, erase - // plane, render visual, check for equivalence... + // FIXME load it into visual, erase plane, render visual, check for equivalence... { - Visual v = Visual(*n, NCBLIT_DEFAULT, 0, 0, -1, -1); + Visual v = Visual(*n, NCBLIT_1x1, 0, 0, -1, -1); } } CHECK(nc.stop()); diff --git a/src/tests/blit.cpp b/src/tests/blit.cpp index f601aaae1..e246cfba9 100644 --- a/src/tests/blit.cpp +++ b/src/tests/blit.cpp @@ -1,6 +1,6 @@ #include "main.h" -TEST_CASE("Blitting") { +TEST_CASE("Blit") { auto nc_ = testing_notcurses(); REQUIRE(nullptr != nc_); ncplane* ncp_ = notcurses_stdplane(nc_); @@ -160,5 +160,122 @@ TEST_CASE("Blitting") { } } + // put a visual through the ascii blitter, read it back, and check equality + SUBCASE("AsciiRoundtrip") { + const uint32_t data[2] = { + htole(0xffffffff), htole(0xff000000), + }; + auto ncv = ncvisual_from_rgba(data, 1, 8, 2); + REQUIRE(nullptr != ncv); + struct ncvisual_options vopts = { + .n = nullptr, + .scaling = NCSCALE_NONE, + .y = 0, .x = 0, + .begy = 0, .begx = 0, + .leny = 0, .lenx = 0, + .blitter = NCBLIT_1x1, + .flags = 0, + .transcolor = 0, + }; + auto p = ncvisual_render(nc_, ncv, &vopts); + REQUIRE(nullptr != p); + CHECK(1 == ncplane_dim_y(p)); + CHECK(2 == ncplane_dim_x(p)); + int pxdimy, pxdimx; + auto edata = ncplane_as_rgba(p, vopts.blitter, 0, 0, -1, -1, &pxdimy, &pxdimx); + REQUIRE(nullptr != edata); + CHECK(0 == memcmp(data, edata, sizeof(data))); + free(edata); + CHECK(0 == ncplane_destroy(p)); + ncvisual_destroy(ncv); + } + + // put a visual through the halfblock blitter, read it back, and check equality + SUBCASE("HalfblockRoundtrip") { + if(notcurses_canutf8(nc_)){ + // two rows of four pixels ought become 1 cell row of 4 cell columns + const uint32_t data[8] = { + htole(0xffffffff), htole(0xffcccccc), htole(0xffcc8844), htole(0xffdddddd), + htole(0xffcc8844), htole(0xff000000), htole(0xffffffff), htole(0xffdddddd), + }; + auto ncv = ncvisual_from_rgba(data, 2, 16, 4); + REQUIRE(nullptr != ncv); + struct ncvisual_options vopts = { + .n = nullptr, + .scaling = NCSCALE_NONE, + .y = 0, .x = 0, + .begy = 0, .begx = 0, + .leny = 0, .lenx = 0, + .blitter = NCBLIT_2x1, + .flags = 0, + .transcolor = 0, + }; + auto p = ncvisual_render(nc_, ncv, &vopts); + REQUIRE(nullptr != p); + CHECK(1 == ncplane_dim_y(p)); + CHECK(4 == ncplane_dim_x(p)); + int pxdimy, pxdimx; + auto edata = ncplane_as_rgba(p, vopts.blitter, 0, 0, -1, -1, &pxdimy, &pxdimx); + REQUIRE(nullptr != edata); + for(size_t i = 0 ; i < sizeof(data) / sizeof(*data) ; ++i){ + CHECK(edata[i] == data[i]); + } + free(edata); + CHECK(0 == ncplane_destroy(p)); + ncvisual_destroy(ncv); + } + } + + // put a visual through the quadblitter, read it back, and check equality + SUBCASE("QuadRoundtrip") { + if(notcurses_canquadrant(nc_)){ + // two rows of 32 pixels ought become 1 cell row of 16 cell columns + const uint32_t data[64] = { + htole(0xffffffff), htole(0xffffffff), htole(0xff000000), htole(0xffffffff), + htole(0xffffffff), htole(0xff000000), htole(0xffffffff), htole(0xffffffff), + htole(0xffffffff), htole(0xffffffff), htole(0xff000000), htole(0xffffffff), + htole(0xffffffff), htole(0xff000000), htole(0xffffffff), htole(0xffffffff), + htole(0xffffffff), htole(0xffffffff), htole(0xffffffff), htole(0xffffffff), + htole(0xff000000), htole(0xff000000), htole(0xff000000), htole(0xffffffff), + htole(0xffffffff), htole(0xff000000), htole(0xffffffff), htole(0xffffffff), + htole(0xffffffff), htole(0xffffffff), htole(0xffffffff), htole(0xffffffff), + + htole(0xffffffff), htole(0xffffffff), htole(0xffffffff), htole(0xffffffff), + htole(0xffffffff), htole(0xffffffff), htole(0xff000000), htole(0xffffffff), + htole(0xffffffff), htole(0xff000000), htole(0xff000000), htole(0xffffffff), + htole(0xffffffff), htole(0xff000000), htole(0xffffffff), htole(0xffffffff), + htole(0xffffffff), htole(0xffffffff), htole(0xff000000), htole(0xff000000), + htole(0xffffffff), htole(0xffffffff), htole(0xffffffff), htole(0xff000000), + htole(0xff000000), htole(0xffffffff), htole(0xffffffff), htole(0xffffffff), + htole(0xffffffff), htole(0xffffffff), htole(0xffffffff), htole(0xffffffff), + }; + auto ncv = ncvisual_from_rgba(data, 2, 128, 32); + REQUIRE(nullptr != ncv); + struct ncvisual_options vopts = { + .n = nullptr, + .scaling = NCSCALE_NONE, + .y = 0, .x = 0, + .begy = 0, .begx = 0, + .leny = 0, .lenx = 0, + .blitter = NCBLIT_2x2, + .flags = 0, + .transcolor = 0, + }; + auto p = ncvisual_render(nc_, ncv, &vopts); + REQUIRE(nullptr != p); + CHECK(1 == ncplane_dim_y(p)); + CHECK(16 == ncplane_dim_x(p)); + int pxdimy, pxdimx; + auto edata = ncplane_as_rgba(p, vopts.blitter, 0, 0, -1, -1, &pxdimy, &pxdimx); + REQUIRE(nullptr != edata); + for(size_t i = 0 ; i < sizeof(data) / sizeof(*data) ; ++i){ + CHECK(edata[i] == data[i]); + } + free(edata); + CHECK(0 == ncplane_destroy(p)); + ncvisual_destroy(ncv); + } + } + CHECK(!notcurses_stop(nc_)); } diff --git a/src/tests/layout.cpp b/src/tests/textlayout.cpp similarity index 99% rename from src/tests/layout.cpp rename to src/tests/textlayout.cpp index fe78d16fc..ff51552de 100644 --- a/src/tests/layout.cpp +++ b/src/tests/textlayout.cpp @@ -217,7 +217,7 @@ TEST_CASE("TextLayout") { CHECK(bytes == strlen(boundstr)); char* line = ncplane_contents(sp, 0, 0, -1, -1); REQUIRE(line); - CHECK(0 == strcmp(line, "a 血的神")); + CHECK(0 == strcmp(line, boundstr)); free(line); ncplane_destroy(sp); } diff --git a/tools/release.sh b/tools/release.sh index e9b67a089..1cd45ec44 100755 --- a/tools/release.sh +++ b/tools/release.sh @@ -12,6 +12,7 @@ OLDVERSION="$1" VERSION="$2" QUIP="$3" +# finalize the date on the most recent version, add any last-minute notes? vi NEWS.md git clean -f -d -x