-
Notifications
You must be signed in to change notification settings - Fork 8.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a simple tool to test rendering functionality (#15091)
This tool augments `vttest` by adding some things that are specific to us (like non-VT console attributes), and some things `vttest` is seemingly too old for (like emojis). I'm planning to add more "pages" of tests to the application in the future, whenever the need arises.
- Loading branch information
Showing
5 changed files
with
319 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
<PropertyGroup Label="Globals"> | ||
<VCProjectVersion>16.0</VCProjectVersion> | ||
<Keyword>Win32Proj</Keyword> | ||
<ProjectGuid>{37c995e0-2349-4154-8e77-4a52c0c7f46d}</ProjectGuid> | ||
<RootNamespace>RenderingTests</RootNamespace> | ||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion> | ||
</PropertyGroup> | ||
<Import Project="$(SolutionDir)\src\common.build.pre.props" /> | ||
<Import Project="$(SolutionDir)\src\common.nugetversions.props" /> | ||
<ItemDefinitionGroup> | ||
<ClCompile> | ||
<PrecompiledHeader>NotUsing</PrecompiledHeader> | ||
<PreprocessorDefinitions>_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions> | ||
</ClCompile> | ||
<Link> | ||
<SubSystem>Console</SubSystem> | ||
</Link> | ||
</ItemDefinitionGroup> | ||
<ItemGroup> | ||
<ClCompile Include="main.cpp" /> | ||
</ItemGroup> | ||
<Import Project="$(SolutionDir)\src\common.build.post.props" /> | ||
<Import Project="$(SolutionDir)\src\common.nugetversions.targets" /> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||
<PropertyGroup /> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,259 @@ | ||
#define NOMINMAX | ||
#define WIN32_LEAN_AND_MEAN | ||
#include <Windows.h> | ||
#include <conio.h> | ||
|
||
#include <array> | ||
#include <cassert> | ||
|
||
// Another variant of "defer" for C++. | ||
namespace | ||
{ | ||
namespace detail | ||
{ | ||
|
||
template<typename F> | ||
class scope_guard | ||
{ | ||
public: | ||
scope_guard(F f) noexcept : | ||
func(std::move(f)) | ||
{ | ||
} | ||
|
||
~scope_guard() | ||
{ | ||
func(); | ||
} | ||
|
||
scope_guard(const scope_guard&) = delete; | ||
scope_guard(scope_guard&& rhs) = delete; | ||
scope_guard& operator=(const scope_guard&) = delete; | ||
scope_guard& operator=(scope_guard&&) = delete; | ||
|
||
private: | ||
F func; | ||
}; | ||
|
||
enum class scope_guard_helper | ||
{ | ||
}; | ||
|
||
template<typename F> | ||
scope_guard<F> operator+(scope_guard_helper /*unused*/, F&& fn) | ||
{ | ||
return scope_guard<F>(std::forward<F>(fn)); | ||
} | ||
|
||
} // namespace detail | ||
|
||
// The extra indirection is necessary to prevent __LINE__ to be treated literally. | ||
#define _DEFER_CONCAT_IMPL(a, b) a##b | ||
#define _DEFER_CONCAT(a, b) _DEFER_CONCAT_IMPL(a, b) | ||
#define defer const auto _DEFER_CONCAT(_defer_, __LINE__) = ::detail::scope_guard_helper() + [&]() | ||
} | ||
|
||
static void printUTF16(const wchar_t* str) | ||
{ | ||
WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), str, static_cast<DWORD>(wcslen(str)), nullptr, nullptr); | ||
} | ||
|
||
// wprintf() in the uCRT prints every single wchar_t individually and thus breaks surrogate | ||
// pairs apart which Windows Terminal treats as invalid input and replaces it with U+FFFD. | ||
static void printfUTF16(_In_z_ _Printf_format_string_ wchar_t const* const format, ...) | ||
{ | ||
std::array<wchar_t, 128> buffer; | ||
|
||
va_list args; | ||
va_start(args, format); | ||
const auto length = _vsnwprintf_s(buffer.data(), buffer.size(), _TRUNCATE, format, args); | ||
va_end(args); | ||
|
||
assert(length >= 0); | ||
WriteConsoleW(GetStdHandle(STD_OUTPUT_HANDLE), buffer.data(), length, nullptr, nullptr); | ||
} | ||
|
||
static void wait() | ||
{ | ||
printUTF16(L"\x1B[9999;1HPress any key to continue..."); | ||
_getch(); | ||
} | ||
|
||
static void clear() | ||
{ | ||
printUTF16( | ||
L"\x1B[H" // move cursor to 0,0 | ||
L"\x1B[2J" // clear screen | ||
); | ||
} | ||
|
||
int main() | ||
{ | ||
const auto outputHandle = GetStdHandle(STD_OUTPUT_HANDLE); | ||
DWORD consoleMode = ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT; | ||
GetConsoleMode(outputHandle, &consoleMode); | ||
SetConsoleMode(outputHandle, consoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN); | ||
defer | ||
{ | ||
SetConsoleMode(outputHandle, consoleMode); | ||
}; | ||
|
||
printUTF16( | ||
L"\x1b[?1049h" // enable alternative screen buffer | ||
); | ||
defer | ||
{ | ||
printUTF16( | ||
L"\x1b[?1049l" // disable alternative screen buffer | ||
); | ||
}; | ||
|
||
{ | ||
struct ConsoleAttributeTest | ||
{ | ||
const wchar_t* text = nullptr; | ||
WORD attribute = 0; | ||
}; | ||
static constexpr ConsoleAttributeTest consoleAttributeTests[]{ | ||
{ L"Console attributes:", 0 }, | ||
#define MAKE_TEST_FOR_ATTRIBUTE(attr) { L## #attr, attr } | ||
MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_GRID_HORIZONTAL), | ||
MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_GRID_LVERTICAL), | ||
MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_GRID_RVERTICAL), | ||
MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_REVERSE_VIDEO), | ||
MAKE_TEST_FOR_ATTRIBUTE(COMMON_LVB_UNDERSCORE), | ||
#undef MAKE_TEST_FOR_ATTRIBUTE | ||
{ L"all gridlines", COMMON_LVB_GRID_HORIZONTAL | COMMON_LVB_GRID_LVERTICAL | COMMON_LVB_GRID_RVERTICAL | COMMON_LVB_UNDERSCORE }, | ||
{ L"all attributes", COMMON_LVB_GRID_HORIZONTAL | COMMON_LVB_GRID_LVERTICAL | COMMON_LVB_GRID_RVERTICAL | COMMON_LVB_REVERSE_VIDEO | COMMON_LVB_UNDERSCORE }, | ||
}; | ||
|
||
SHORT row = 2; | ||
for (const auto& t : consoleAttributeTests) | ||
{ | ||
const auto length = static_cast<DWORD>(wcslen(t.text)); | ||
printfUTF16(L"\x1B[%d;5H%s", row + 1, t.text); | ||
|
||
WORD attributes[32]; | ||
std::fill_n(&attributes[0], length, static_cast<WORD>(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | t.attribute)); | ||
|
||
DWORD numberOfAttrsWritten; | ||
WriteConsoleOutputAttribute(outputHandle, attributes, length, { 4, row }, &numberOfAttrsWritten); | ||
|
||
row += 2; | ||
} | ||
|
||
struct VTAttributeTest | ||
{ | ||
const wchar_t* text = nullptr; | ||
int sgr = 0; | ||
}; | ||
static constexpr VTAttributeTest vtAttributeTests[]{ | ||
{ L"ANSI escape SGR:", 0 }, | ||
{ L"italic", 3 }, | ||
{ L"underline", 4 }, | ||
{ L"reverse", 7 }, | ||
{ L"strikethrough", 9 }, | ||
{ L"double underline", 21 }, | ||
{ L"overlined", 53 }, | ||
}; | ||
|
||
row = 3; | ||
for (const auto& t : vtAttributeTests) | ||
{ | ||
printfUTF16(L"\x1B[%d;45H\x1b[%dm%s\x1b[m", row, t.sgr, t.text); | ||
row += 2; | ||
} | ||
|
||
printfUTF16(L"\x1B[%d;45H\x1b]8;;https://example.com\x1b\\hyperlink\x1b]8;;\x1b\\", row); | ||
|
||
wait(); | ||
clear(); | ||
} | ||
|
||
{ | ||
printUTF16( | ||
L"\x1B[3;5HDECDWL Double Width \U0001FAE0 A\u0353\u0353 B\u036F\u036F" | ||
L"\x1B[4;5H\x1b#6DECDWL Double Width \U0001FAE0 A\u0353\u0353 B\u036F\u036F" | ||
L"\x1B[8;5HDECDHL Double Height \U0001F642\U0001F6C1 A\u0353\u0353 B\u036F\u036F X\u0353\u0353 Y\u036F\u036F" | ||
L"\x1B[9;5H\x1b#3DECDHL Double Height Top \U0001F642 A\u0353\u0353 B\u036F\u036F" | ||
L"\x1B[10;5H\x1b#4DECDHL Double Height Bottom \U0001F6C1 X\u0353\u0353 Y\u036F\u036F"); | ||
|
||
wait(); | ||
clear(); | ||
} | ||
|
||
{ | ||
defer | ||
{ | ||
// Setting an empty DRCS gets us back to the regular font. | ||
printUTF16(L"\x1bP1;1;2{ @\x1b\\"); | ||
}; | ||
|
||
constexpr auto width = 14; | ||
const auto glyph = | ||
"W W " | ||
"W W " | ||
"W W W " | ||
"W W W " | ||
"W W W " | ||
"W W W TTTTTTT" | ||
" W W T " | ||
" T " | ||
" T " | ||
" T " | ||
" T " | ||
" T "; | ||
|
||
// Convert the above visual glyph to sixels | ||
wchar_t rows[2][width]; | ||
for (int r = 0; r < 2; ++r) | ||
{ | ||
const auto glyphData = &glyph[r * width * 6]; | ||
|
||
for (int x = 0; x < width; ++x) | ||
{ | ||
unsigned int accumulator = 0; | ||
for (int y = 5; y >= 0; --y) | ||
{ | ||
const auto isSet = glyphData[y * width + x] != ' '; | ||
accumulator <<= 1; | ||
accumulator |= static_cast<unsigned int>(isSet); | ||
} | ||
|
||
rows[r][x] = static_cast<wchar_t>(L'?' + accumulator); | ||
} | ||
} | ||
|
||
// DECDLD - Dynamically Redefinable Character Sets | ||
printfUTF16( | ||
// * Pfn | font number | 1 | | ||
// * Pcn | starting character | 3 | = ASCII 0x23 "#" | ||
// * Pe | erase control | 2 | erase all | ||
// Pcmw | character matrix width | %d | `width` pixels | ||
// Pw | font width | 0 | 80 columns | ||
// Pt | text or full cell | 0 | text | ||
// Pcmh | character matrix height | 0 | 12 pixels | ||
// Pcss | character set size | 0 | 94 | ||
// * Dscs | character set name | " @" | unregistered soft set | ||
L"\x1bP1;3;2;%d{ @%.15s/%.15s\x1b\\", | ||
width, | ||
rows[0], | ||
rows[1]); | ||
|
||
#define DRCS_SEQUENCE L"\x1b( @#\x1b(A" | ||
printUTF16( | ||
L"\x1B[3;5HDECDLD and DRCS test - it should show \"WT\" in a single cell" | ||
L"\x1B[5;5HRegular: " DRCS_SEQUENCE L"" | ||
L"\x1B[7;3H\x1b#6DECDWL: " DRCS_SEQUENCE L"" | ||
L"\x1B[9;3H\x1b#3DECDHL: " DRCS_SEQUENCE L"" | ||
L"\x1B[10;3H\x1b#4DECDHL: " DRCS_SEQUENCE L"" | ||
// We map soft fonts into the private use area starting at U+EF20. This test ensures | ||
// that we correctly map actual fallback glyphs mixed into the DRCS glyphs. | ||
L"\x1B[12;5HUnicode Fallback: \uE000\uE001" DRCS_SEQUENCE L"\uE003\uE004"); | ||
#undef DRCS_SEQUENCE | ||
|
||
wait(); | ||
} | ||
|
||
return 0; | ||
} |