|
#include <windows.h> |
|
|
|
#include <assert.h> |
|
#include <vector> |
|
|
|
#include "TestUtil.cc" |
|
|
|
#define COUNT_OF(x) (sizeof(x) / sizeof((x)[0])) |
|
|
|
|
|
CHAR_INFO ci(wchar_t ch, WORD attributes) { |
|
CHAR_INFO ret; |
|
ret.Char.UnicodeChar = ch; |
|
ret.Attributes = attributes; |
|
return ret; |
|
} |
|
|
|
CHAR_INFO ci(wchar_t ch) { |
|
return ci(ch, 7); |
|
} |
|
|
|
CHAR_INFO ci() { |
|
return ci(L' '); |
|
} |
|
|
|
bool operator==(SMALL_RECT x, SMALL_RECT y) { |
|
return !memcmp(&x, &y, sizeof(x)); |
|
} |
|
|
|
SMALL_RECT sr(COORD pt, COORD size) { |
|
return { |
|
pt.X, pt.Y, |
|
static_cast<SHORT>(pt.X + size.X - 1), |
|
static_cast<SHORT>(pt.Y + size.Y - 1) |
|
}; |
|
} |
|
|
|
static void set( |
|
const COORD pt, |
|
const COORD size, |
|
const std::vector<CHAR_INFO> &data) { |
|
assert(data.size() == size.X * size.Y); |
|
SMALL_RECT writeRegion = sr(pt, size); |
|
BOOL ret = WriteConsoleOutputW( |
|
GetStdHandle(STD_OUTPUT_HANDLE), |
|
data.data(), size, {0, 0}, &writeRegion); |
|
assert(ret && writeRegion == sr(pt, size)); |
|
} |
|
|
|
static void set( |
|
const COORD pt, |
|
const std::vector<CHAR_INFO> &data) { |
|
set(pt, {static_cast<SHORT>(data.size()), 1}, data); |
|
} |
|
|
|
static void writeAttrsAt( |
|
const COORD pt, |
|
const std::vector<WORD> &data) { |
|
DWORD actual = 0; |
|
BOOL ret = WriteConsoleOutputAttribute( |
|
GetStdHandle(STD_OUTPUT_HANDLE), |
|
data.data(), data.size(), pt, &actual); |
|
assert(ret && actual == data.size()); |
|
} |
|
|
|
static void writeCharsAt( |
|
const COORD pt, |
|
const std::vector<wchar_t> &data) { |
|
DWORD actual = 0; |
|
BOOL ret = WriteConsoleOutputCharacterW( |
|
GetStdHandle(STD_OUTPUT_HANDLE), |
|
data.data(), data.size(), pt, &actual); |
|
assert(ret && actual == data.size()); |
|
} |
|
|
|
static void writeChars( |
|
const std::vector<wchar_t> &data) { |
|
DWORD actual = 0; |
|
BOOL ret = WriteConsoleW( |
|
GetStdHandle(STD_OUTPUT_HANDLE), |
|
data.data(), data.size(), &actual, NULL); |
|
assert(ret && actual == data.size()); |
|
} |
|
|
|
std::vector<CHAR_INFO> get( |
|
const COORD pt, |
|
const COORD size) { |
|
std::vector<CHAR_INFO> data(size.X * size.Y); |
|
SMALL_RECT readRegion = sr(pt, size); |
|
BOOL ret = ReadConsoleOutputW( |
|
GetStdHandle(STD_OUTPUT_HANDLE), |
|
data.data(), size, {0, 0}, &readRegion); |
|
assert(ret && readRegion == sr(pt, size)); |
|
return data; |
|
} |
|
|
|
std::vector<wchar_t> readCharsAt( |
|
const COORD pt, |
|
int size) { |
|
std::vector<wchar_t> data(size); |
|
DWORD actual = 0; |
|
BOOL ret = ReadConsoleOutputCharacterW( |
|
GetStdHandle(STD_OUTPUT_HANDLE), |
|
data.data(), data.size(), pt, &actual); |
|
assert(ret); |
|
data.resize(actual); |
|
return data; |
|
} |
|
|
|
static void dump(const COORD pt, const COORD size) { |
|
for (CHAR_INFO ci : get(pt, size)) { |
|
printf("%04X %04X\n", ci.Char.UnicodeChar, ci.Attributes); |
|
} |
|
} |
|
|
|
static void dumpCharsAt(const COORD pt, int size) { |
|
for (wchar_t ch : readCharsAt(pt, size)) { |
|
printf("%04X\n", ch); |
|
} |
|
} |
|
|
|
static COORD getCursorPos() { |
|
CONSOLE_SCREEN_BUFFER_INFO info = { sizeof(info) }; |
|
assert(GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info)); |
|
return info.dwCursorPosition; |
|
} |
|
|
|
static void test1() { |
|
|
|
|
|
|
|
printf("test1 - overlap full-width char with full-width char\n"); |
|
writeCharsAt({1,0}, {0x4000, 0x4000}); |
|
dump({0,0}, {6,1}); |
|
printf("\n"); |
|
writeCharsAt({2,0}, {0x4001}); |
|
dump({0,0}, {6,1}); |
|
printf("\n"); |
|
} |
|
|
|
static void test2() { |
|
|
|
|
|
printf("test2 - overlap full-width char with full-width char (lowlevel)\n"); |
|
writeCharsAt({1,0}, {0x4000, 0x4000}); |
|
dump({0,0}, {6,1}); |
|
printf("\n"); |
|
set({2,0}, {ci(0x4001,0x107), ci(0x4001,0x207)}); |
|
dump({0,0}, {6,1}); |
|
printf("\n"); |
|
} |
|
|
|
static void test3() { |
|
|
|
|
|
printf("test3 - explicitly violate LEADING/TRAILING using lowlevel API\n"); |
|
set({1,0}, { |
|
ci(0x4000, 0x207), |
|
ci(0x4001, 0x107), |
|
ci(0x3044, 7), |
|
ci(L'X', 0x107), |
|
ci(L'X', 0x207), |
|
}); |
|
dump({0,0}, {7,1}); |
|
} |
|
|
|
static void test4() { |
|
|
|
|
|
printf("test4 - use lowlevel to assign two colors to one full-width char\n"); |
|
set({0,0}, { |
|
ci(0x4000, 0x142), |
|
ci(0x4000, 0x224), |
|
}); |
|
dump({0,0}, {2,1}); |
|
} |
|
|
|
static void test5() { |
|
|
|
|
|
printf("test5 - WriteConsoleOutputAttribute cannot affect LEADING/TRAILING\n"); |
|
|
|
|
|
writeCharsAt({0,0}, {0x4000}); |
|
dump({0,0}, {2,1}); |
|
writeAttrsAt({0,0}, {0x42, 0x24}); |
|
printf("\n"); |
|
dump({0,0}, {2,1}); |
|
|
|
|
|
writeCharsAt({0,1}, {'A', ' '}); |
|
writeAttrsAt({0,1}, {0x107, 0x207}); |
|
printf("\n"); |
|
dump({0,1}, {2,1}); |
|
} |
|
|
|
static void test6() { |
|
|
|
|
|
|
|
printf("test6 - cursor can be either left or right cell of full-width char\n"); |
|
|
|
writeCharsAt({2,1}, {0x4000}); |
|
|
|
setCursorPos(2, 1); |
|
auto pos1 = getCursorPos(); |
|
Sleep(1000); |
|
|
|
setCursorPos(3, 1); |
|
auto pos2 = getCursorPos(); |
|
Sleep(1000); |
|
|
|
setCursorPos(0, 15); |
|
printf("%d,%d\n", pos1.X, pos1.Y); |
|
printf("%d,%d\n", pos2.X, pos2.Y); |
|
} |
|
|
|
static void runTest(void (&test)()) { |
|
system("cls"); |
|
setCursorPos(0, 14); |
|
test(); |
|
system("pause"); |
|
} |
|
|
|
int main(int argc, char *argv[]) { |
|
if (argc == 1) { |
|
startChildProcess(L"CHILD"); |
|
return 0; |
|
} |
|
|
|
setWindowPos(0, 0, 1, 1); |
|
setBufferSize(80, 40); |
|
setWindowPos(0, 0, 80, 40); |
|
|
|
auto cp = GetConsoleOutputCP(); |
|
assert(cp == 932 || cp == 936 || cp == 949 || cp == 950); |
|
|
|
runTest(test1); |
|
runTest(test2); |
|
runTest(test3); |
|
runTest(test4); |
|
runTest(test5); |
|
runTest(test6); |
|
|
|
return 0; |
|
} |
|
|