#include #include #include "main.h" #include "internal.h" TEST_CASE("Wide") { if(getenv("TERM") == nullptr){ return; } if(!enforce_utf8()){ 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_); // Verify we can emit a wide character, and it advances the cursor by 2 SUBCASE("EmitWideAsian") { const char* w = "\u5168"; int sbytes = 0; CHECK(0 < ncplane_putegc(n_, w, &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("RejectWideAsian") { const char* w = "\u5168"; int sbytes = 0; int dimx; ncplane_dim_yx(n_, nullptr, &dimx); CHECK(0 < ncplane_putegc_yx(n_, 0, dimx - 3, w, &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, &sbytes)); CHECK(0 == y); CHECK(dimx - 1 == x); CHECK(0 == notcurses_render(nc_)); } // half-width, double-width, huge-yet-narrow, all that crap SUBCASE("PlaneAtCursorInsane"){ const char EGC0[] = "\uffe0"; // fullwidth cent sign ¢ const char EGC1[] = "\u00c5"; // neutral A with ring above Å const char EGC2[] = "\u20a9"; // half-width won ₩ const char EGC3[] = "\u212b"; // ambiguous angstrom Å const char EGC4[] = "\ufdfd"; // neutral yet huge bismillah ﷽ std::array tcells; for(auto & tcell : tcells){ cell_init(&tcell); } REQUIRE(1 < cell_load(n_, &tcells[0], EGC0)); REQUIRE(1 < cell_load(n_, &tcells[1], EGC1)); REQUIRE(1 < cell_load(n_, &tcells[2], EGC2)); REQUIRE(1 < cell_load(n_, &tcells[3], EGC3)); REQUIRE(1 < cell_load(n_, &tcells[4], EGC4)); for(auto & tcell : tcells){ REQUIRE(0 < ncplane_putc(n_, &tcell)); } CHECK(0 == notcurses_render(nc_)); int x = 0; for(auto & tcell : tcells){ CHECK(0 == ncplane_cursor_move_yx(n_, 0, x)); cell testcell = CELL_TRIVIAL_INITIALIZER; REQUIRE(0 < ncplane_at_cursor_cell(n_, &testcell)); CHECK(!strcmp(cell_extended_gcluster(n_, &tcell), cell_extended_gcluster(n_, &testcell))); CHECK(0 == testcell.attrword); wchar_t w; REQUIRE(0 < mbtowc(&w, cell_extended_gcluster(n_, &tcell), MB_CUR_MAX)); if(wcwidth(w) == 2){ CHECK(CELL_WIDEASIAN_MASK == testcell.channels); ++x; }else{ CHECK(0 == testcell.channels); } ++x; } } // Placing a wide char to the immediate left of any other char ought obliterate // that cell. SUBCASE("WideCharAnnihilatesRight") { const char* w = "\xf0\x9f\x90\xb8"; // U+1F438 FROG FACE const char* wbashed = "\xf0\x9f\xa6\x82"; // U+1F982 SCORPION const char bashed = 'X'; int sbytes = 0; CHECK(0 < ncplane_putegc_yx(n_, 0, 1, wbashed, &sbytes)); CHECK(0 < ncplane_putsimple_yx(n_, 1, 1, bashed)); int x, y; ncplane_cursor_yx(n_, &y, &x); CHECK(1 == y); CHECK(2 == x); CHECK(0 < ncplane_putegc_yx(n_, 0, 0, w, &sbytes)); CHECK(0 < ncplane_putegc_yx(n_, 1, 0, w, &sbytes)); cell c = CELL_TRIVIAL_INITIALIZER; ncplane_at_yx_cell(n_, 0, 0, &c); const char* wres = extended_gcluster(n_, &c); CHECK(0 == strcmp(wres, "\xf0\x9f\x90\xb8")); // should be frog ncplane_at_yx_cell(n_, 0, 1, &c); CHECK(cell_double_wide_p(&c)); // should be wide ncplane_at_yx_cell(n_, 0, 2, &c); CHECK(0 == c.gcluster); // should be nothing ncplane_at_yx_cell(n_, 1, 0, &c); wres = extended_gcluster(n_, &c); CHECK(0 == strcmp(wres, "\xf0\x9f\x90\xb8")); // should be frog ncplane_at_yx_cell(n_, 1, 1, &c); CHECK(cell_double_wide_p(&c)); //should be wide ncplane_at_yx_cell(n_, 0, 2, &c); CHECK(0 == c.gcluster); CHECK(0 == notcurses_render(nc_)); // should be nothing } // Placing a wide char on the right half of a wide char ought obliterate the // original wide char. SUBCASE("WideCharAnnihilatesWideLeft") { const char* w = "\xf0\x9f\x90\x8d"; const char* wbashed = "\xf0\x9f\xa6\x82"; int sbytes = 0; CHECK(0 < ncplane_putegc_yx(n_, 0, 0, wbashed, &sbytes)); CHECK(0 < ncplane_putegc_yx(n_, 0, 1, w, &sbytes)); int x, y; ncplane_cursor_yx(n_, &y, &x); CHECK(0 == y); CHECK(3 == x); cell c = CELL_TRIVIAL_INITIALIZER; ncplane_at_yx_cell(n_, 0, 0, &c); CHECK(0 == c.gcluster); // should be nothing ncplane_at_yx_cell(n_, 0, 1, &c); const char* wres = extended_gcluster(n_, &c); CHECK(0 == strcmp(wres, "\xf0\x9f\x90\x8d")); // should be snake ncplane_at_yx_cell(n_, 0, 2, &c); CHECK(cell_double_wide_p(&c)); // should be wide CHECK(0 == notcurses_render(nc_)); } // Placing a normal char on either half of a wide char ought obliterate // the original wide char. SUBCASE("WideCharsAnnihilated") { const char cc = 'X'; const char* wbashedl = "\xf0\x9f\x90\x8d"; const char* wbashedr = "\xf0\x9f\xa6\x82"; int sbytes = 0; CHECK(0 < ncplane_putegc_yx(n_, 0, 0, wbashedl, &sbytes)); CHECK(0 < ncplane_putegc_yx(n_, 0, 2, wbashedr, &sbytes)); CHECK(1 == ncplane_putsimple_yx(n_, 0, 1, cc)); CHECK(1 == ncplane_putsimple_yx(n_, 0, 2, cc)); int x, y; ncplane_cursor_yx(n_, &y, &x); CHECK(0 == y); CHECK(3 == x); cell c = CELL_TRIVIAL_INITIALIZER; ncplane_at_yx_cell(n_, 0, 0, &c); CHECK(0 == c.gcluster); // should be nothing ncplane_at_yx_cell(n_, 0, 1, &c); CHECK(cc == c.gcluster); // should be 'X' ncplane_at_yx_cell(n_, 0, 2, &c); CHECK(cc == c.gcluster); // should be 'X" ncplane_at_yx_cell(n_, 0, 3, &c); CHECK(0 == c.gcluster); // should be nothing CHECK(0 == notcurses_render(nc_)); } // But placing something to the immediate right of any glyph, that is not a // problem. Ensure it is so. SUBCASE("AdjacentCharsSafe") { const char cc = 'X'; const char* wsafel = "\xf0\x9f\x90\x8d"; const char* wsafer = "\xf0\x9f\xa6\x82"; int sbytes = 0; CHECK(0 < ncplane_putegc_yx(n_, 0, 0, wsafel, &sbytes)); CHECK(0 < ncplane_putegc_yx(n_, 0, 3, wsafer, &sbytes)); CHECK(1 == ncplane_putsimple_yx(n_, 0, 2, cc)); int x, y; ncplane_cursor_yx(n_, &y, &x); CHECK(0 == y); CHECK(3 == x); cell c = CELL_TRIVIAL_INITIALIZER; ncplane_at_yx_cell(n_, 0, 0, &c); const char* wres = extended_gcluster(n_, &c); CHECK(0 == strcmp(wres, "\xf0\x9f\x90\x8d")); // should be snake ncplane_at_yx_cell(n_, 0, 1, &c); CHECK(cell_double_wide_p(&c)); // should be snake ncplane_at_yx_cell(n_, 0, 2, &c); CHECK(cc == c.gcluster); // should be 'X' ncplane_at_yx_cell(n_, 0, 3, &c); wres = extended_gcluster(n_, &c); CHECK(0 == strcmp(wres, "\xf0\x9f\xa6\x82")); // should be scorpion ncplane_at_yx_cell(n_, 0, 4, &c); CHECK(cell_double_wide_p(&c)); // should be scorpion 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(2 == ncplane_putegc_yx(ncp, 1, 1, "\xf0\x9f\xa6\x82", nullptr)); // scorpion CHECK(0 == notcurses_render(nc_)); cell c = CELL_TRIVIAL_INITIALIZER; REQUIRE(0 < ncplane_at_yx_cell(ncp, 1, 0, &c)); CHECK(!strcmp(cell_extended_gcluster(ncp, &c), "│")); cell_release(ncp, &c); char* egc = notcurses_at_yx(nc_, 1, 0, &c.attrword, &c.channels); REQUIRE(egc); CHECK(!strcmp(egc, "│")); free(egc); REQUIRE(0 < ncplane_at_yx_cell(ncp, 1, 3, &c)); CHECK(!strcmp(cell_extended_gcluster(ncp, &c), "│")); cell_release(ncp, &c); egc = notcurses_at_yx(nc_, 1, 3, &c.attrword, &c.channels); REQUIRE(egc); CHECK(!strcmp(egc, "│")); free(egc); CHECK(0 == ncplane_destroy(ncp)); } SUBCASE("RenderWides") { CHECK(0 <= ncplane_putstr(n_, "\xe5\xbd\xa2\xe5\x85\xa8")); cell c = CELL_TRIVIAL_INITIALIZER; ncplane_at_yx_cell(n_, 0, 0, &c); CHECK(cell_double_wide_p(&c)); ncplane_at_yx_cell(n_, 0, 1, &c); CHECK(cell_double_wide_p(&c)); ncplane_at_yx_cell(n_, 0, 2, &c); CHECK(cell_double_wide_p(&c)); ncplane_at_yx_cell(n_, 0, 3, &c); CHECK(cell_double_wide_p(&c)); ncplane_at_yx_cell(n_, 0, 4, &c); CHECK(!cell_double_wide_p(&c)); REQUIRE(0 == notcurses_render(nc_)); notcurses_at_yx(nc_, 0, 0, &c.attrword, &c.channels); CHECK(0 != (c.channels & 0x8000000080000000ull)); notcurses_at_yx(nc_, 0, 1, &c.attrword, &c.channels); CHECK(0 != (c.channels & 0x8000000080000000ull)); notcurses_at_yx(nc_, 0, 2, &c.attrword, &c.channels); CHECK(0 != (c.channels & 0x8000000080000000ull)); notcurses_at_yx(nc_, 0, 3, &c.attrword, &c.channels); CHECK(0 != (c.channels & 0x8000000080000000ull)); notcurses_at_yx(nc_, 0, 4, &c.attrword, &c.channels); CHECK(!(c.channels & 0x8000000080000000ull)); } // 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_cell(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.attrword, &c.channels); 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_cell(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.attrword, &c.channels); 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_cell(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.attrword, &c.channels); REQUIRE(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_cell(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.attrword, &c.channels); 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.attrword, &c.channels); REQUIRE(egc); CHECK(0 == strcmp(" ", egc)); free(egc); cell_init(&c); // should be character from higher plane egc = notcurses_at_yx(nc_, 0, 1, &c.attrword, &c.channels); REQUIRE(egc); CHECK(0 == strcmp("A", egc)); free(egc); cell_init(&c); egc = notcurses_at_yx(nc_, 0, 2, &c.attrword, &c.channels); REQUIRE(egc); CHECK(0 == strcmp("B", egc)); free(egc); cell_init(&c); // should be nothing, having been stomped egc = notcurses_at_yx(nc_, 0, 3, &c.attrword, &c.channels); REQUIRE(egc); CHECK(0 == strcmp(" ", egc)); free(egc); cell_init(&c); CHECK(0 == ncplane_destroy(n)); } // Render a translucent plane atop a wide glyph, and check the colors on both // cells. See https://github.com/dankamongmen/notcurses/issues/362. SUBCASE("OverWide") { struct ncplane* p = ncplane_new(nc_, 3, 4, 0, 0, nullptr); REQUIRE(nullptr != p); cell c = CELL_SIMPLE_INITIALIZER('X'); CHECK(0 == ncplane_perimeter(p, &c, &c, &c, &c, &c, &c, 0)); ncplane_set_bg_rgb(n_, 0x20, 0x20, 0x20); int sbytes; CHECK(2 == ncplane_putegc_yx(n_, 1, 1, "六", &sbytes)); uint64_t channels = 0; channels_set_bg_alpha(&channels, CELL_ALPHA_BLEND); channels_set_bg_rgb(&channels, 0x80, 0xf0, 0x10); CHECK(1 == ncplane_set_base(p, " ", 0, channels)); CHECK(0 == notcurses_render(nc_)); uint32_t attrword; uint64_t chanleft, chanright; char* egc = notcurses_at_yx(nc_, 1, 1, &attrword, &chanleft); REQUIRE(nullptr != egc); CHECK(0 == strcmp(" ", egc)); free(egc); egc = notcurses_at_yx(nc_, 1, 2, &attrword, &chanright); REQUIRE(nullptr != egc); CHECK(0 == strcmp(" ", egc)); free(egc); cell cl = CELL_TRIVIAL_INITIALIZER, cr = CELL_TRIVIAL_INITIALIZER; CHECK(3 == ncplane_at_yx_cell(n_, 1, 1, &cl)); REQUIRE(cell_extended_gcluster(n_, &cl)); CHECK(0 == strcmp("六", extended_gcluster(n_, &cl))); CHECK(0 == ncplane_at_yx_cell(n_, 1, 2, &cr)); REQUIRE(cell_simple_p(&cr)); CHECK(0 == cr.gcluster); cell_release(n_, &cl); cell_release(n_, &cr); CHECK(chanright == chanleft); ncplane_destroy(p); } // drag a plane of narrow chars across a plane of wide glyphs SUBCASE("NarrowPlaneAtopWide") { notcurses_cursor_disable(nc_); CHECK(0 == ncplane_set_fg_rgb(n_, 0xff, 0, 0xff)); // start the 1x4 top plane at 0, 4 struct ncplane* topp = ncplane_new(nc_, 1, 4, 0, 4, nullptr); REQUIRE(nullptr != topp); CHECK(0 == ncplane_set_bg_rgb(topp, 0, 0xff, 0)); CHECK(4 == ncplane_putstr(topp, "abcd")); CHECK(12 == ncplane_putstr(n_, "六六六六")); CHECK(0 == notcurses_render(nc_)); uint32_t attrword; uint64_t channels; char* egc; uint64_t widechans = ncplane_channels(n_) | CELL_WIDEASIAN_MASK; uint64_t narchans = ncplane_channels(topp); REQUIRE((egc = notcurses_at_yx(nc_, 0, 0, &attrword, &channels))); CHECK(0 == strcmp(egc, "六")); CHECK(widechans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 1, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 2, &attrword, &channels))); CHECK(0 == strcmp(egc, "六")); CHECK(widechans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 3, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 4, &attrword, &channels))); CHECK(0 == strcmp(egc, "a")); CHECK(narchans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 5, &attrword, &channels))); CHECK(0 == strcmp(egc, "b")); CHECK(narchans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 6, &attrword, &channels))); CHECK(0 == strcmp(egc, "c")); CHECK(narchans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 7, &attrword, &channels))); CHECK(0 == strcmp(egc, "d")); CHECK(narchans == channels); free(egc); CHECK(0 == ncplane_move_yx(topp, 0, 3)); CHECK(0 == notcurses_render(nc_)); REQUIRE((egc = notcurses_at_yx(nc_, 0, 0, &attrword, &channels))); CHECK(0 == strcmp(egc, "六")); CHECK(widechans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 1, &attrword, &channels))); CHECK(0 == strcmp(egc, " ")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 2, &attrword, &channels))); CHECK(0 == strcmp(egc, " ")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 3, &attrword, &channels))); CHECK(0 == strcmp(egc, "a")); CHECK(narchans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 4, &attrword, &channels))); CHECK(0 == strcmp(egc, "b")); CHECK(narchans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 5, &attrword, &channels))); CHECK(0 == strcmp(egc, "c")); CHECK(narchans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 6, &attrword, &channels))); CHECK(0 == strcmp(egc, "d")); CHECK(narchans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 7, &attrword, &channels))); CHECK(0 == strcmp(egc, " ")); free(egc); CHECK(0 == ncplane_move_yx(topp, 0, 2)); CHECK(0 == notcurses_render(nc_)); REQUIRE((egc = notcurses_at_yx(nc_, 0, 0, &attrword, &channels))); CHECK(0 == strcmp(egc, "六")); CHECK(widechans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 1, &attrword, &channels))); CHECK(0 == strcmp(egc, " ")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 2, &attrword, &channels))); CHECK(0 == strcmp(egc, "a")); CHECK(narchans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 3, &attrword, &channels))); CHECK(0 == strcmp(egc, "b")); CHECK(narchans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 4, &attrword, &channels))); CHECK(0 == strcmp(egc, "c")); CHECK(narchans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 5, &attrword, &channels))); CHECK(0 == strcmp(egc, "d")); CHECK(narchans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 6, &attrword, &channels))); CHECK(0 == strcmp(egc, "六")); CHECK(widechans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 7, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); CHECK(0 == ncplane_move_yx(topp, 0, 1)); CHECK(0 == notcurses_render(nc_)); REQUIRE((egc = notcurses_at_yx(nc_, 0, 0, &attrword, &channels))); CHECK(0 == strcmp(egc, " ")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 1, &attrword, &channels))); CHECK(0 == strcmp(egc, "a")); CHECK(narchans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 2, &attrword, &channels))); CHECK(0 == strcmp(egc, "b")); CHECK(narchans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 3, &attrword, &channels))); CHECK(0 == strcmp(egc, "c")); CHECK(narchans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 4, &attrword, &channels))); CHECK(0 == strcmp(egc, "d")); CHECK(narchans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 5, &attrword, &channels))); CHECK(0 == strcmp(egc, " ")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 6, &attrword, &channels))); CHECK(0 == strcmp(egc, "六")); CHECK(widechans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 7, &attrword, &channels))); CHECK(0 == strcmp(egc, " ")); free(egc); CHECK(0 == ncplane_move_yx(topp, 0, 0)); CHECK(0 == notcurses_render(nc_)); REQUIRE((egc = notcurses_at_yx(nc_, 0, 0, &attrword, &channels))); CHECK(0 == strcmp(egc, "a")); CHECK(narchans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 1, &attrword, &channels))); CHECK(0 == strcmp(egc, "b")); CHECK(narchans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 2, &attrword, &channels))); CHECK(0 == strcmp(egc, "c")); CHECK(narchans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 3, &attrword, &channels))); CHECK(0 == strcmp(egc, "d")); CHECK(narchans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 4, &attrword, &channels))); CHECK(0 == strcmp(egc, "六")); CHECK(widechans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 5, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 6, &attrword, &channels))); CHECK(0 == strcmp(egc, "六")); CHECK(widechans == channels); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 7, &attrword, &channels))); CHECK(0 == strcmp(egc, " ")); free(egc); ncplane_destroy(topp); } // drag a plane of wide glyphs across a plane of wide glyphs SUBCASE("WidePlaneAtopWide") { CHECK(0 == ncplane_set_fg_rgb(n_, 0xff, 0, 0xff)); // start the 1x4 top plane at 0, 4 struct ncplane* topp = ncplane_new(nc_, 1, 4, 0, 4, nullptr); REQUIRE(nullptr != topp); CHECK(0 == ncplane_set_bg_rgb(topp, 0, 0xff, 0)); CHECK(6 == ncplane_putstr(topp, "次次")); CHECK(12 == ncplane_putstr(n_, "六六六六")); CHECK(0 == notcurses_render(nc_)); uint32_t attrword; uint64_t channels; char* egc; REQUIRE((egc = notcurses_at_yx(nc_, 0, 0, &attrword, &channels))); CHECK(0 == strcmp(egc, "六")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 1, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 2, &attrword, &channels))); CHECK(0 == strcmp(egc, "六")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 3, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 4, &attrword, &channels))); CHECK(0 == strcmp(egc, "次")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 5, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 6, &attrword, &channels))); CHECK(0 == strcmp(egc, "次")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 7, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); CHECK(0 == ncplane_move_yx(topp, 0, 3)); CHECK(0 == notcurses_render(nc_)); REQUIRE((egc = notcurses_at_yx(nc_, 0, 0, &attrword, &channels))); CHECK(0 == strcmp(egc, "六")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 1, &attrword, &channels))); CHECK(0 == strcmp(egc, " ")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 2, &attrword, &channels))); CHECK(0 == strcmp(egc, " ")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 3, &attrword, &channels))); CHECK(0 == strcmp(egc, "次")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 4, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 5, &attrword, &channels))); CHECK(0 == strcmp(egc, "次")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 6, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 7, &attrword, &channels))); CHECK(0 == strcmp(egc, " ")); free(egc); CHECK(0 == ncplane_move_yx(topp, 0, 2)); CHECK(0 == notcurses_render(nc_)); REQUIRE((egc = notcurses_at_yx(nc_, 0, 0, &attrword, &channels))); CHECK(0 == strcmp(egc, "六")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 1, &attrword, &channels))); CHECK(0 == strcmp(egc, " ")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 2, &attrword, &channels))); CHECK(0 == strcmp(egc, "次")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 3, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 4, &attrword, &channels))); CHECK(0 == strcmp(egc, "次")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 5, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 6, &attrword, &channels))); CHECK(0 == strcmp(egc, "六")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 7, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); CHECK(0 == ncplane_move_yx(topp, 0, 1)); CHECK(0 == notcurses_render(nc_)); REQUIRE((egc = notcurses_at_yx(nc_, 0, 0, &attrword, &channels))); CHECK(0 == strcmp(egc, " ")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 1, &attrword, &channels))); CHECK(0 == strcmp(egc, "次")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 2, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 3, &attrword, &channels))); CHECK(0 == strcmp(egc, "次")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 4, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 5, &attrword, &channels))); CHECK(0 == strcmp(egc, " ")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 6, &attrword, &channels))); CHECK(0 == strcmp(egc, "六")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 7, &attrword, &channels))); CHECK(0 == strcmp(egc, " ")); free(egc); CHECK(0 == ncplane_move_yx(topp, 0, 0)); CHECK(0 == notcurses_render(nc_)); REQUIRE((egc = notcurses_at_yx(nc_, 0, 0, &attrword, &channels))); CHECK(0 == strcmp(egc, "次")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 1, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 2, &attrword, &channels))); CHECK(0 == strcmp(egc, "次")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 3, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 4, &attrword, &channels))); CHECK(0 == strcmp(egc, "六")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 5, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 6, &attrword, &channels))); CHECK(0 == strcmp(egc, "六")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 7, &attrword, &channels))); CHECK(0 == strcmp(egc, " ")); free(egc); ncplane_destroy(topp); } // drag a plane of wide glyphs across a plane of narrow glyphs SUBCASE("WidePlaneAtopNarrow") { CHECK(0 == ncplane_set_fg_rgb(n_, 0xff, 0, 0xff)); // start the 1x4 top plane at 0, 4 struct ncplane* topp = ncplane_new(nc_, 1, 4, 0, 4, nullptr); REQUIRE(nullptr != topp); CHECK(0 == ncplane_set_bg_rgb(topp, 0, 0xff, 0)); CHECK(6 == ncplane_putstr(topp, "次次")); CHECK(8 == ncplane_putstr(n_, "abcdefgh")); CHECK(0 == notcurses_render(nc_)); uint32_t attrword; uint64_t channels; char* egc; REQUIRE((egc = notcurses_at_yx(nc_, 0, 0, &attrword, &channels))); CHECK(0 == strcmp(egc, "a")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 1, &attrword, &channels))); CHECK(0 == strcmp(egc, "b")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 2, &attrword, &channels))); CHECK(0 == strcmp(egc, "c")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 3, &attrword, &channels))); CHECK(0 == strcmp(egc, "d")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 4, &attrword, &channels))); CHECK(0 == strcmp(egc, "次")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 5, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 6, &attrword, &channels))); CHECK(0 == strcmp(egc, "次")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 7, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); CHECK(0 == ncplane_move_yx(topp, 0, 3)); CHECK(0 == notcurses_render(nc_)); REQUIRE((egc = notcurses_at_yx(nc_, 0, 0, &attrword, &channels))); CHECK(0 == strcmp(egc, "a")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 1, &attrword, &channels))); CHECK(0 == strcmp(egc, "b")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 2, &attrword, &channels))); CHECK(0 == strcmp(egc, "c")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 3, &attrword, &channels))); CHECK(0 == strcmp(egc, "次")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 4, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 5, &attrword, &channels))); CHECK(0 == strcmp(egc, "次")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 6, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 7, &attrword, &channels))); CHECK(0 == strcmp(egc, "h")); free(egc); CHECK(0 == ncplane_move_yx(topp, 0, 2)); CHECK(0 == notcurses_render(nc_)); REQUIRE((egc = notcurses_at_yx(nc_, 0, 0, &attrword, &channels))); CHECK(0 == strcmp(egc, "a")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 1, &attrword, &channels))); CHECK(0 == strcmp(egc, "b")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 2, &attrword, &channels))); CHECK(0 == strcmp(egc, "次")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 3, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 4, &attrword, &channels))); CHECK(0 == strcmp(egc, "次")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 5, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 6, &attrword, &channels))); CHECK(0 == strcmp(egc, "g")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 7, &attrword, &channels))); CHECK(0 == strcmp(egc, "h")); free(egc); CHECK(0 == ncplane_move_yx(topp, 0, 1)); CHECK(0 == notcurses_render(nc_)); REQUIRE((egc = notcurses_at_yx(nc_, 0, 0, &attrword, &channels))); CHECK(0 == strcmp(egc, "a")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 1, &attrword, &channels))); CHECK(0 == strcmp(egc, "次")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 2, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 3, &attrword, &channels))); CHECK(0 == strcmp(egc, "次")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 4, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 5, &attrword, &channels))); CHECK(0 == strcmp(egc, "f")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 6, &attrword, &channels))); CHECK(0 == strcmp(egc, "g")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 7, &attrword, &channels))); CHECK(0 == strcmp(egc, "h")); free(egc); CHECK(0 == ncplane_move_yx(topp, 0, 0)); CHECK(0 == notcurses_render(nc_)); REQUIRE((egc = notcurses_at_yx(nc_, 0, 0, &attrword, &channels))); CHECK(0 == strcmp(egc, "次")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 1, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 2, &attrword, &channels))); CHECK(0 == strcmp(egc, "次")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 3, &attrword, &channels))); CHECK(0 == strcmp(egc, "")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 4, &attrword, &channels))); CHECK(0 == strcmp(egc, "e")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 5, &attrword, &channels))); CHECK(0 == strcmp(egc, "f")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 6, &attrword, &channels))); CHECK(0 == strcmp(egc, "g")); free(egc); REQUIRE((egc = notcurses_at_yx(nc_, 0, 7, &attrword, &channels))); CHECK(0 == strcmp(egc, "h")); free(egc); ncplane_destroy(topp); } CHECK(0 == notcurses_stop(nc_)); CHECK(0 == fclose(outfp_)); }