3 Test program demonstrating a problem in Windows 15048's ReadConsoleOutput API.
7 cl /nologo /EHsc winbug-15048.cc shell32.lib
9 Example of regressed input:
14 > winbug-15048 -face-gothic 3044
18 1**34 (nb: U+3044 replaced with '**' to avoid MSVC encoding warning)
21 ReadConsoleOutputW (both rows, 3 cols)
22 row 0: U+0031(0007) U+3044(0107) U+3044(0207) U+0033(0007)
23 row 1: U+0035(0007) U+0036(0007) U+0037(0007) U+0038(0007)
25 ReadConsoleOutputW (both rows, 4 cols)
26 row 0: U+0031(0007) U+3044(0107) U+3044(0207) U+0033(0007) U+0034(0007)
27 row 1: U+0035(0007) U+0036(0007) U+0037(0007) U+0038(0007) U+0020(0007)
29 ReadConsoleOutputW (second row)
30 row 1: U+0035(0007) U+0036(0007) U+0037(0007) U+0038(0007) U+0020(0007)
34 Win10 15048 bad output:
39 ReadConsoleOutputW (both rows, 3 cols)
40 row 0: U+0031(0007) U+3044(0007) U+0033(0007) U+0035(0007)
41 row 1: U+0036(0007) U+0037(0007) U+0038(0007) U+0000(0000)
43 ReadConsoleOutputW (both rows, 4 cols)
44 row 0: U+0031(0007) U+3044(0007) U+0033(0007) U+0034(0007) U+0035(0007)
45 row 1: U+0036(0007) U+0037(0007) U+0038(0007) U+0020(0007) U+0000(0000)
47 ReadConsoleOutputW (second row)
48 row 1: U+0035(0007) U+0036(0007) U+0037(0007) U+0038(0007) U+0020(0007)
52 The U+3044 character (HIRAGANA LETTER I) occupies two columns, but it only
53 fills one record in the ReadConsoleOutput output buffer, which has the
54 effect of shifting the first cell of the second row into the last cell of
55 the first row. Ordinarily, the first and second cells would also have the
56 COMMON_LVB_LEADING_BYTE and COMMON_LVB_TRAILING_BYTE attributes set, which
57 allows winpty to detect the double-column character.
62 > winbug-15048 -face "Lucida Console" -h 4 221A
64 The same issue happens with U+221A (SQUARE ROOT), but only in certain
65 fonts. The console seems to think this character occupies two columns
66 if the font is sufficiently small. The Windows console properties dialog
67 doesn't allow fonts below 5 pt, but winpty tries to use 2pt and 4pt Lucida
68 Console to allow very large console windows.
73 > winbug-15048 -face "Lucida Console" -h 12 FF12
75 The console selection system thinks U+FF12 (FULLWIDTH DIGIT TWO) occupies
76 two columns, which happens to be correct, but it's displayed as a single
77 column unrecognized character. It otherwise behaves the same as the other
91 #define COUNT_OF(array) (sizeof(array) / sizeof((array)[0]))
93 // See https://en.wikipedia.org/wiki/List_of_CJK_fonts
94 const wchar_t kMSGothic[] = { 0xff2d, 0xff33, 0x0020, 0x30b4, 0x30b7, 0x30c3, 0x30af, 0 }; // Japanese
95 const wchar_t kNSimSun[] = { 0x65b0, 0x5b8b, 0x4f53, 0 }; // Simplified Chinese
96 const wchar_t kMingLight[] = { 0x7d30, 0x660e, 0x9ad4, 0 }; // Traditional Chinese
97 const wchar_t kGulimChe[] = { 0xad74, 0xb9bc, 0xccb4, 0 }; // Korean
99 static void set_font(const wchar_t *name, int size) {
100 const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE);
101 CONSOLE_FONT_INFOEX fontex {};
102 fontex.cbSize = sizeof(fontex);
103 fontex.dwFontSize.Y = size;
104 fontex.FontWeight = 400;
105 fontex.FontFamily = 0x36;
106 wcsncpy(fontex.FaceName, name, COUNT_OF(fontex.FaceName));
107 assert(SetCurrentConsoleFontEx(conout, FALSE, &fontex));
110 static void usage(const wchar_t *prog) {
111 printf("Usage: %ls [options]\n", prog);
112 printf(" -h HEIGHT\n");
113 printf(" -face FACENAME\n");
114 printf(" -face-{gothic|simsun|minglight|gulimche) [JP,CN-sim,CN-tra,KR]\n");
115 printf(" hhhh -- print U+hhhh\n");
119 static void dump_region(SMALL_RECT region, const char *name) {
120 const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE);
123 memset(buf, 0xcc, sizeof(buf));
125 const int w = region.Right - region.Left + 1;
126 const int h = region.Bottom - region.Top + 1;
128 assert(ReadConsoleOutputW(
129 conout, buf, { (short)w, (short)h }, { 0, 0 },
133 printf("ReadConsoleOutputW (%s)\n", name);
134 for (int y = 0; y < h; ++y) {
135 printf("row %d: ", region.Top + y);
136 for (int i = 0; i < region.Left * 13; ++i) {
139 for (int x = 0; x < w; ++x) {
140 const int i = y * w + x;
141 printf("U+%04x(%04x) ", buf[i].Char.UnicodeChar, buf[i].Attributes);
148 wchar_t *cmdline = GetCommandLineW();
150 wchar_t **argv = CommandLineToArgvW(cmdline, &argc);
151 const wchar_t *font_name = L"Lucida Console";
153 int test_ch = 0xff12; // U+FF12 FULLWIDTH DIGIT TWO
155 for (int i = 1; i < argc; ++i) {
156 const std::wstring arg = argv[i];
157 const std::wstring next = i + 1 < argc ? argv[i + 1] : L"";
158 if (arg == L"-face" && i + 1 < argc) {
159 font_name = argv[i + 1];
161 } else if (arg == L"-face-gothic") {
162 font_name = kMSGothic;
163 } else if (arg == L"-face-simsun") {
164 font_name = kNSimSun;
165 } else if (arg == L"-face-minglight") {
166 font_name = kMingLight;
167 } else if (arg == L"-face-gulimche") {
168 font_name = kGulimChe;
169 } else if (arg == L"-h" && i + 1 < argc) {
170 font_height = _wtoi(next.c_str());
172 } else if (arg.c_str()[0] != '-') {
173 test_ch = wcstol(arg.c_str(), NULL, 16);
175 printf("Unrecognized argument: %ls\n", arg.c_str());
180 const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE);
182 set_font(font_name, font_height);
186 wchar_t output[] = L"1234\n5678\n";
188 WriteConsoleW(conout, output, 10, &actual, nullptr);
190 dump_region({ 0, 0, 3, 1 }, "both rows, 3 cols");
191 dump_region({ 0, 0, 4, 1 }, "both rows, 4 cols");
192 dump_region({ 0, 1, 4, 1 }, "second row");
193 dump_region({ 0, 0, 4, 0 }, "first row");
194 dump_region({ 1, 0, 4, 0 }, "first row, skip 1");
195 dump_region({ 2, 0, 4, 0 }, "first row, skip 2");
196 dump_region({ 3, 0, 4, 0 }, "first row, skip 3");
198 set_font(font_name, 14);