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