1 #include "main.h" 2 3 // These tests address cases where box characters on two overlapping planes 4 // interact in non-trivial ways. A simple example is a U2580 UPPER HALF BLOCK 5 // (▀) with a white foreground and transparent background, above a U2584 LOWER 6 // HALF BLOCK (▄) with a white foreground and transparent background. One might 7 // expect the result to be an entirely white cell, but by typical Notcurses 8 // rendering rules, we would instead get a white upper half and transparent 9 // lower half: 10 // 11 // - after first cell, glyph is locked U2584, fg is locked white, bg transparent 12 // - second cell can't override glyph nor fg, and background remains transparent 13 // 14 // we will instead special-case block-drawing characters. 15 // see https://github.com/dankamongmen/notcurses/issues/1068 16 TEST_CASE("Stacking") { 17 auto nc_ = testing_notcurses(); 18 if(!nc_){ 19 return; 20 } 21 if(!notcurses_canutf8(nc_)){ 22 CHECK(0 == notcurses_stop(nc_)); 23 return; 24 } 25 unsigned dimy, dimx; 26 struct ncplane* n_ = notcurses_stddim_yx(nc_, &dimy, &dimx); 27 REQUIRE(nullptr != n_); 28 29 // whenever the foreground matches the background (using RGB color, *not* 30 // default colors not palette-indexed color), we ought emit a space with the 31 // specified background, or a full block with the specified foreground (only 32 // if UTF8 is available). default colors must not be merged (palette-indexed 33 // could be, but it's pointless). the transformation must only take place at 34 // raster time--the original output must be recoverable from the plane. 35 SUBCASE("FgMatchesBgRGB") { 36 // first we write an a with the desired background, but a distinct 37 // foreground. then we write an a with the two matching (via RGB). 38 // this ought generate a space with the desired background on the 39 // second cell. 40 ncplane_set_fg_default(n_); 41 CHECK(0 == ncplane_set_bg_rgb(n_, 0x808080)); 42 CHECK(1 == ncplane_putchar(n_, 'a')); 43 CHECK(0 == ncplane_set_fg_rgb(n_, 0x808080)); 44 CHECK(1 == ncplane_putchar(n_, 'a')); // ought become a space 45 // now we write an x with the desired foreground, but a distinct 46 // background. then we write an x with the two matching. this ought 47 // generate a full block with the desired foreground if UTF8 is 48 // available, and a space with the desired background otherwise. 49 ncplane_set_bg_default(n_); 50 CHECK(1 == ncplane_putchar(n_, 'x')); 51 CHECK(0 == ncplane_set_bg_rgb(n_, 0x808080)); 52 CHECK(1 == ncplane_putchar(n_, 'x')); // ought become a space/block 53 CHECK(0 == notcurses_render(nc_)); 54 // now we check the output. the plane ought have the characters as written, 55 // but we ought have rasterized the optimal forms. 56 uint64_t channels; 57 auto pblit = ncplane_at_yx(n_, 0, 1, nullptr, &channels); 58 CHECK(0 == strcmp("a", pblit)); 59 CHECK(0x808080 == ncchannels_bg_rgb(channels)); 60 CHECK(0x808080 == ncchannels_fg_rgb(channels)); 61 free(pblit); 62 pblit = ncplane_at_yx(n_, 0, 3, nullptr, &channels); 63 CHECK(0 == strcmp("x", pblit)); 64 CHECK(0x808080 == ncchannels_bg_rgb(channels)); 65 CHECK(0x808080 == ncchannels_fg_rgb(channels)); 66 free(pblit); 67 auto rblit = notcurses_at_yx(nc_, 0, 1, nullptr, &channels); 68 CHECK(0 == strcmp(" ", rblit)); 69 CHECK(0x808080 == ncchannels_bg_rgb(channels)); 70 free(rblit); 71 rblit = notcurses_at_yx(nc_, 0, 3, nullptr, &channels); 72 if(notcurses_canutf8(nc_)){ 73 CHECK(0x808080 == ncchannels_fg_rgb(channels)); 74 // FIXME we're not yet this advanced, and use space instead 75 // CHECK(0 == strcmp(u8"\u2588", rblit)); 76 CHECK(0 == strcmp(u8" ", rblit)); 77 }else{ 78 CHECK(0 == strcmp(" ", rblit)); 79 CHECK(0x808080 == ncchannels_bg_rgb(channels)); 80 } 81 free(rblit); 82 } 83 84 SUBCASE("LowerAtopUpperWhite") { 85 struct ncplane_options opts = { 86 .y = 0, .x = 0, .rows = 1, .cols = 1, 87 .userptr = nullptr, .name = "top", 88 .resizecb = nullptr, 89 .flags = 0, 90 .margin_b = 0, .margin_r = 0, 91 }; 92 auto top = ncplane_create(n_, &opts); 93 REQUIRE(nullptr != top); 94 // create an ncvisual of 2 rows, 1 column, with the bottom 0xffffff 95 const uint32_t topv[] = {htole(0), htole(0xffffffff)}; 96 auto ncv = ncvisual_from_rgba(topv, 2, 4, 1); 97 REQUIRE(nullptr != ncv); 98 struct ncvisual_options vopts = { 99 .n = top, .scaling = NCSCALE_NONE, .y = 0, .x = 0, .begy = 0, .begx = 0, 100 .leny = 2, .lenx = 1, .blitter = NCBLIT_2x1, .flags = 0, 101 .transcolor = 0, .pxoffy = 0, .pxoffx = 0, 102 }; 103 CHECK(top == ncvisual_blit(nc_, ncv, &vopts)); 104 ncvisual_destroy(ncv); 105 106 // create an ncvisual of 2 rows, 1 column, with the top 0xffffff 107 const uint32_t botv[] = {htole(0xffffffff), htole(0)}; 108 ncv = ncvisual_from_rgba(botv, 2, 4, 1); 109 REQUIRE(nullptr != ncv); 110 vopts.n = n_; 111 vopts.flags |= NCVISUAL_OPTION_CHILDPLANE; 112 auto newn = ncvisual_blit(nc_, ncv, &vopts); 113 REQUIRE(nullptr != newn); 114 ncvisual_destroy(ncv); 115 ncplane_move_below(newn, top); 116 117 CHECK(0 == notcurses_render(nc_)); 118 uint64_t channels; 119 auto egc = notcurses_at_yx(nc_, 0, 0, nullptr, &channels); 120 REQUIRE(nullptr != egc); 121 CHECK(0 == strcmp(u8" ", egc)); 122 free(egc); 123 CHECK(0xffffff == ncchannels_fg_rgb(channels)); 124 CHECK(0xffffff == ncchannels_bg_rgb(channels)); 125 CHECK(0 == ncplane_destroy(top)); 126 CHECK(0 == ncplane_destroy(newn)); 127 } 128 129 SUBCASE("UpperAtopLowerWhite") { 130 struct ncplane_options opts = { 131 0, 0, 1, 1, nullptr, "top", nullptr, 0, 0, 0, 132 }; 133 auto top = ncplane_create(n_, &opts); 134 REQUIRE(nullptr != top); 135 // create an ncvisual of 2 rows, 1 column, with the top 0xffffff 136 const uint32_t topv[] = {htole(0xffffffff), htole(0)}; 137 auto ncv = ncvisual_from_rgba(topv, 2, 4, 1); 138 REQUIRE(nullptr != ncv); 139 struct ncvisual_options vopts = { 140 .n = top, .scaling = NCSCALE_NONE, .y = 0, .x = 0, .begy = 0, .begx = 0, 141 .leny = 2, .lenx = 1, .blitter = NCBLIT_2x1, .flags = 0, 142 .transcolor = 0, .pxoffy = 0, .pxoffx = 0, 143 }; 144 CHECK(top == ncvisual_blit(nc_, ncv, &vopts)); 145 ncvisual_destroy(ncv); 146 147 // create an ncvisual of 2 rows, 1 column, with the bottom 0xffffff 148 const uint32_t botv[] = {htole(0), htole(0xffffffff)}; 149 ncv = ncvisual_from_rgba(botv, 2, 4, 1); 150 REQUIRE(nullptr != ncv); 151 vopts.n = n_; 152 vopts.flags |= NCVISUAL_OPTION_CHILDPLANE; 153 auto newn = ncvisual_blit(nc_, ncv, &vopts); 154 REQUIRE(nullptr != newn); 155 ncvisual_destroy(ncv); 156 ncplane_move_below(newn, top); 157 158 CHECK(0 == notcurses_render(nc_)); 159 uint64_t channels; 160 auto egc = notcurses_at_yx(nc_, 0, 0, nullptr, &channels); 161 REQUIRE(nullptr != egc); 162 CHECK(0 == strcmp(u8" ", egc)); 163 free(egc); 164 CHECK(0xffffff == ncchannels_fg_rgb(channels)); 165 CHECK(0xffffff == ncchannels_bg_rgb(channels)); 166 CHECK(0 == ncplane_destroy(top)); 167 CHECK(0 == ncplane_destroy(newn)); 168 } 169 170 SUBCASE("StackedQuadHalves") { 171 if(notcurses_canquadrant(nc_)){ 172 struct ncplane_options opts = { 173 0, 0, 1, 1, nullptr, "top", nullptr, 0, 0, 0, 174 }; 175 auto top = ncplane_create(n_, &opts); 176 REQUIRE(nullptr != top); 177 // create an ncvisual of 2 rows, 2 columns, with the top 0xffffff 178 const uint32_t topv[] = {htole(0xff00ff00), htole(0xff00ff00), htole(0), htole(0)}; 179 auto ncv = ncvisual_from_rgba(topv, 2, 8, 2); 180 REQUIRE(nullptr != ncv); 181 struct ncvisual_options vopts = { 182 .n = top, .scaling = NCSCALE_NONE, .y = 0, .x = 0, .begy = 0, .begx = 0, 183 .leny = 2, .lenx = 2, .blitter = NCBLIT_2x2, .flags = 0, 184 .transcolor = 0, .pxoffy = 0, .pxoffx = 0, 185 }; 186 CHECK(top == ncvisual_blit(nc_, ncv, &vopts)); 187 ncvisual_destroy(ncv); 188 189 // create an ncvisual of 2 rows, 2 columns, with the bottom 0xffffff 190 const uint32_t botv[] = {htole(0), htole(0), htole(0xff00ff00), htole(0xff00ff00)}; 191 ncv = ncvisual_from_rgba(botv, 2, 8, 2); 192 REQUIRE(nullptr != ncv); 193 vopts.n = n_; 194 vopts.flags = NCVISUAL_OPTION_CHILDPLANE; 195 auto newn = ncvisual_blit(nc_, ncv, &vopts); 196 REQUIRE(nullptr != newn); 197 ncvisual_destroy(ncv); 198 ncplane_move_below(newn, top); 199 200 CHECK(0 == notcurses_render(nc_)); 201 uint64_t channels; 202 auto egc = notcurses_at_yx(nc_, 0, 0, nullptr, &channels); 203 REQUIRE(nullptr != egc); 204 CHECK(0 == strcmp(u8" ", egc)); 205 free(egc); 206 CHECK(0x00ff00 == ncchannels_fg_rgb(channels)); 207 CHECK(0x00ff00 == ncchannels_bg_rgb(channels)); 208 CHECK(0 == ncplane_destroy(top)); 209 CHECK(0 == ncplane_destroy(newn)); 210 } 211 } 212 213 SUBCASE("StackedQuadCrossed") { 214 if(notcurses_canquadrant(nc_)){ 215 ncplane_erase(n_); 216 notcurses_refresh(nc_, nullptr, nullptr); 217 struct ncplane_options opts = { 218 0, 0, 1, 1, nullptr, "top", nullptr, 0, 0, 0, 219 }; 220 auto top = ncplane_create(n_, &opts); 221 REQUIRE(nullptr != top); 222 // create an ncvisual of 2 rows, 2 columns, with the tl, br 0xffffff 223 const uint32_t topv[] = {htole(0xffffffff), htole(0), htole(0), htole(0xffffffff)}; 224 auto ncv = ncvisual_from_rgba(topv, 2, 8, 2); 225 REQUIRE(nullptr != ncv); 226 struct ncvisual_options vopts = { 227 .n = top, .scaling = NCSCALE_NONE, .y = 0, .x = 0, .begy = 0, .begx = 0, 228 .leny = 2, .lenx = 2, .blitter = NCBLIT_2x2, .flags = 0, 229 .transcolor = 0, .pxoffy = 0, .pxoffx = 0, 230 }; 231 CHECK(top == ncvisual_blit(nc_, ncv, &vopts)); 232 ncvisual_destroy(ncv); 233 234 // create an ncvisual of 2 rows, 2 columns, with the tr, bl 0xffffff 235 const uint32_t botv[] = {htole(0), htole(0xffffffff), htole(0xffffffff), htole(0)}; 236 ncv = ncvisual_from_rgba(botv, 2, 8, 2); 237 REQUIRE(nullptr != ncv); 238 vopts.n = n_; 239 vopts.flags = NCVISUAL_OPTION_CHILDPLANE; 240 auto newn = ncvisual_blit(nc_, ncv, &vopts); 241 REQUIRE(nullptr != newn); 242 ncvisual_destroy(ncv); 243 ncplane_move_below(newn, top); 244 245 CHECK(0 == notcurses_render(nc_)); 246 uint64_t channels; 247 auto egc = notcurses_at_yx(nc_, 0, 0, nullptr, &channels); 248 REQUIRE(nullptr != egc); 249 CHECK(0 == strcmp(u8" ", egc)); // quadrant upper left and lower right 250 free(egc); 251 CHECK(0xffffff == ncchannels_fg_rgb(channels)); 252 CHECK(0xffffff == ncchannels_bg_rgb(channels)); 253 CHECK(0 == ncplane_destroy(top)); 254 CHECK(0 == ncplane_destroy(newn)); 255 } 256 } 257 258 // common teardown 259 CHECK(0 == notcurses_stop(nc_)); 260 } 261