X-Git-Url: https://git.josue.xyz/?p=VSoRC%2F.git;a=blobdiff_plain;f=node_modules%2Fnode-pty%2Fdeps%2Fwinpty%2Fsrc%2Fagent%2FConsoleFont.cc;fp=node_modules%2Fnode-pty%2Fdeps%2Fwinpty%2Fsrc%2Fagent%2FConsoleFont.cc;h=aa1f7876d3807ddb631b449419a7b8bc7b708399;hp=0000000000000000000000000000000000000000;hb=e79e4a5a87f3e84f7c1777f10a954453a69bf540;hpb=4339da12467b75fb8b6ca831f4bf0081c485ed2c diff --git a/node_modules/node-pty/deps/winpty/src/agent/ConsoleFont.cc b/node_modules/node-pty/deps/winpty/src/agent/ConsoleFont.cc new file mode 100644 index 0000000..aa1f787 --- /dev/null +++ b/node_modules/node-pty/deps/winpty/src/agent/ConsoleFont.cc @@ -0,0 +1,698 @@ +// Copyright (c) 2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#include "ConsoleFont.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "../shared/DebugClient.h" +#include "../shared/OsModule.h" +#include "../shared/StringUtil.h" +#include "../shared/WindowsVersion.h" +#include "../shared/WinptyAssert.h" +#include "../shared/winpty_snprintf.h" + +namespace { + +#define COUNT_OF(x) (sizeof(x) / sizeof((x)[0])) + +// See https://en.wikipedia.org/wiki/List_of_CJK_fonts +const wchar_t kLucidaConsole[] = L"Lucida Console"; +const wchar_t kMSGothic[] = { 0xff2d, 0xff33, 0x0020, 0x30b4, 0x30b7, 0x30c3, 0x30af, 0 }; // 932, Japanese +const wchar_t kNSimSun[] = { 0x65b0, 0x5b8b, 0x4f53, 0 }; // 936, Chinese Simplified +const wchar_t kGulimChe[] = { 0xad74, 0xb9bc, 0xccb4, 0 }; // 949, Korean +const wchar_t kMingLight[] = { 0x7d30, 0x660e, 0x9ad4, 0 }; // 950, Chinese Traditional + +struct FontSize { + short size; + int width; +}; + +struct Font { + const wchar_t *faceName; + unsigned int family; + short size; +}; + +// Ideographs in East Asian languages take two columns rather than one. +// In the console screen buffer, a "full-width" character will occupy two +// cells of the buffer, the first with attribute 0x100 and the second with +// attribute 0x200. +// +// Windows does not correctly identify code points as double-width in all +// configurations. It depends heavily on the code page, the font facename, +// and (somehow) even the font size. In the 437 code page (MS-DOS), for +// example, no codepoints are interpreted as double-width. When the console +// is in an East Asian code page (932, 936, 949, or 950), then sometimes +// selecting a "Western" facename like "Lucida Console" or "Consolas" doesn't +// register, or if the font *can* be chosen, then the console doesn't handle +// double-width correctly. I tested the double-width handling by writing +// several code points with WriteConsole and checking whether one or two cells +// were filled. +// +// In the Japanese code page (932), Microsoft's default font is MS Gothic. +// MS Gothic double-width handling seems to be broken with console versions +// prior to Windows 10 (including Windows 10's legacy mode), and it's +// especially broken in Windows 8 and 8.1. +// +// Test by running: misc/Utf16Echo A2 A3 2014 3044 30FC 4000 +// +// The first three codepoints are always rendered as half-width with the +// Windows Japanese fonts. (Of these, the first two must be half-width, +// but U+2014 could be either.) The last three are rendered as full-width, +// and they are East_Asian_Width=Wide. +// +// Windows 7 fails by modeling all codepoints as full-width with font +// sizes 22 and above. +// +// Windows 8 gets U+00A2, U+00A3, U+2014, U+30FC, and U+4000 wrong, but +// using a point size not listed in the console properties dialog +// (e.g. "9") is less wrong: +// +// | code point | +// font | 00A2 00A3 2014 3044 30FC 4000 | cell size +// ------------+---------------------------------+---------- +// 8 | F F F F H H | 4x8 +// 9 | F F F F F F | 5x9 +// 16 | F F F F H H | 8x16 +// raster 6x13 | H H H F F H(*) | 6x13 +// +// (*) The Raster Font renders U+4000 as a white box (i.e. an unsupported +// character). +// + +// See: +// - misc/Font-Report-June2016 directory for per-size details +// - misc/font-notes.txt +// - misc/Utf16Echo.cc, misc/FontSurvey.cc, misc/SetFont.cc, misc/GetFont.cc + +const FontSize kLucidaFontSizes[] = { + { 5, 3 }, + { 6, 4 }, + { 8, 5 }, + { 10, 6 }, + { 12, 7 }, + { 14, 8 }, + { 16, 10 }, + { 18, 11 }, + { 20, 12 }, + { 36, 22 }, + { 48, 29 }, + { 60, 36 }, + { 72, 43 }, +}; + +// Japanese. Used on Vista and Windows 7. +const FontSize k932GothicVista[] = { + { 6, 3 }, + { 8, 4 }, + { 10, 5 }, + { 12, 6 }, + { 13, 7 }, + { 15, 8 }, + { 17, 9 }, + { 19, 10 }, + { 21, 11 }, + // All larger fonts are more broken w.r.t. full-size East Asian characters. +}; + +// Japanese. Used on Windows 8, 8.1, and the legacy 10 console. +const FontSize k932GothicWin8[] = { + // All of these characters are broken w.r.t. full-size East Asian + // characters, but they're equally broken. + { 5, 3 }, + { 7, 4 }, + { 9, 5 }, + { 11, 6 }, + { 13, 7 }, + { 15, 8 }, + { 17, 9 }, + { 20, 10 }, + { 22, 11 }, + { 24, 12 }, + // include extra-large fonts for small terminals + { 36, 18 }, + { 48, 24 }, + { 60, 30 }, + { 72, 36 }, +}; + +// Japanese. Used on the new Windows 10 console. +const FontSize k932GothicWin10[] = { + { 6, 3 }, + { 8, 4 }, + { 10, 5 }, + { 12, 6 }, + { 14, 7 }, + { 16, 8 }, + { 18, 9 }, + { 20, 10 }, + { 22, 11 }, + { 24, 12 }, + // include extra-large fonts for small terminals + { 36, 18 }, + { 48, 24 }, + { 60, 30 }, + { 72, 36 }, +}; + +// Chinese Simplified. +const FontSize k936SimSun[] = { + { 6, 3 }, + { 8, 4 }, + { 10, 5 }, + { 12, 6 }, + { 14, 7 }, + { 16, 8 }, + { 18, 9 }, + { 20, 10 }, + { 22, 11 }, + { 24, 12 }, + // include extra-large fonts for small terminals + { 36, 18 }, + { 48, 24 }, + { 60, 30 }, + { 72, 36 }, +}; + +// Korean. +const FontSize k949GulimChe[] = { + { 6, 3 }, + { 8, 4 }, + { 10, 5 }, + { 12, 6 }, + { 14, 7 }, + { 16, 8 }, + { 18, 9 }, + { 20, 10 }, + { 22, 11 }, + { 24, 12 }, + // include extra-large fonts for small terminals + { 36, 18 }, + { 48, 24 }, + { 60, 30 }, + { 72, 36 }, +}; + +// Chinese Traditional. +const FontSize k950MingLight[] = { + { 6, 3 }, + { 8, 4 }, + { 10, 5 }, + { 12, 6 }, + { 14, 7 }, + { 16, 8 }, + { 18, 9 }, + { 20, 10 }, + { 22, 11 }, + { 24, 12 }, + // include extra-large fonts for small terminals + { 36, 18 }, + { 48, 24 }, + { 60, 30 }, + { 72, 36 }, +}; + +// Some of these types and functions are missing from the MinGW headers. +// Others are undocumented. + +struct AGENT_CONSOLE_FONT_INFO { + DWORD nFont; + COORD dwFontSize; +}; + +struct AGENT_CONSOLE_FONT_INFOEX { + ULONG cbSize; + DWORD nFont; + COORD dwFontSize; + UINT FontFamily; + UINT FontWeight; + WCHAR FaceName[LF_FACESIZE]; +}; + +// undocumented XP API +typedef BOOL WINAPI SetConsoleFont_t( + HANDLE hOutput, + DWORD dwFontIndex); + +// undocumented XP API +typedef DWORD WINAPI GetNumberOfConsoleFonts_t(); + +// XP and up +typedef BOOL WINAPI GetCurrentConsoleFont_t( + HANDLE hOutput, + BOOL bMaximumWindow, + AGENT_CONSOLE_FONT_INFO *lpConsoleCurrentFont); + +// XP and up +typedef COORD WINAPI GetConsoleFontSize_t( + HANDLE hConsoleOutput, + DWORD nFont); + +// Vista and up +typedef BOOL WINAPI GetCurrentConsoleFontEx_t( + HANDLE hConsoleOutput, + BOOL bMaximumWindow, + AGENT_CONSOLE_FONT_INFOEX *lpConsoleCurrentFontEx); + +// Vista and up +typedef BOOL WINAPI SetCurrentConsoleFontEx_t( + HANDLE hConsoleOutput, + BOOL bMaximumWindow, + AGENT_CONSOLE_FONT_INFOEX *lpConsoleCurrentFontEx); + +#define GET_MODULE_PROC(mod, funcName) \ + m_##funcName = reinterpret_cast((mod).proc(#funcName)); \ + +#define DEFINE_ACCESSOR(funcName) \ + funcName##_t &funcName() const { \ + ASSERT(valid()); \ + return *m_##funcName; \ + } + +class XPFontAPI { +public: + XPFontAPI() : m_kernel32(L"kernel32.dll") { + GET_MODULE_PROC(m_kernel32, GetCurrentConsoleFont); + GET_MODULE_PROC(m_kernel32, GetConsoleFontSize); + } + + bool valid() const { + return m_GetCurrentConsoleFont != NULL && + m_GetConsoleFontSize != NULL; + } + + DEFINE_ACCESSOR(GetCurrentConsoleFont) + DEFINE_ACCESSOR(GetConsoleFontSize) + +private: + OsModule m_kernel32; + GetCurrentConsoleFont_t *m_GetCurrentConsoleFont; + GetConsoleFontSize_t *m_GetConsoleFontSize; +}; + +class UndocumentedXPFontAPI : public XPFontAPI { +public: + UndocumentedXPFontAPI() : m_kernel32(L"kernel32.dll") { + GET_MODULE_PROC(m_kernel32, SetConsoleFont); + GET_MODULE_PROC(m_kernel32, GetNumberOfConsoleFonts); + } + + bool valid() const { + return this->XPFontAPI::valid() && + m_SetConsoleFont != NULL && + m_GetNumberOfConsoleFonts != NULL; + } + + DEFINE_ACCESSOR(SetConsoleFont) + DEFINE_ACCESSOR(GetNumberOfConsoleFonts) + +private: + OsModule m_kernel32; + SetConsoleFont_t *m_SetConsoleFont; + GetNumberOfConsoleFonts_t *m_GetNumberOfConsoleFonts; +}; + +class VistaFontAPI : public XPFontAPI { +public: + VistaFontAPI() : m_kernel32(L"kernel32.dll") { + GET_MODULE_PROC(m_kernel32, GetCurrentConsoleFontEx); + GET_MODULE_PROC(m_kernel32, SetCurrentConsoleFontEx); + } + + bool valid() const { + return this->XPFontAPI::valid() && + m_GetCurrentConsoleFontEx != NULL && + m_SetCurrentConsoleFontEx != NULL; + } + + DEFINE_ACCESSOR(GetCurrentConsoleFontEx) + DEFINE_ACCESSOR(SetCurrentConsoleFontEx) + +private: + OsModule m_kernel32; + GetCurrentConsoleFontEx_t *m_GetCurrentConsoleFontEx; + SetCurrentConsoleFontEx_t *m_SetCurrentConsoleFontEx; +}; + +static std::vector > readFontTable( + XPFontAPI &api, HANDLE conout, DWORD maxCount) { + std::vector > ret; + for (DWORD i = 0; i < maxCount; ++i) { + COORD size = api.GetConsoleFontSize()(conout, i); + if (size.X == 0 && size.Y == 0) { + break; + } + ret.push_back(std::make_pair(i, size)); + } + return ret; +} + +static void dumpFontTable(HANDLE conout, const char *prefix) { + const int kMaxCount = 1000; + if (!isTracingEnabled()) { + return; + } + XPFontAPI api; + if (!api.valid()) { + trace("dumpFontTable: cannot dump font table -- missing APIs"); + return; + } + std::vector > table = + readFontTable(api, conout, kMaxCount); + std::string line; + char tmp[128]; + size_t first = 0; + while (first < table.size()) { + size_t last = std::min(table.size() - 1, first + 10 - 1); + winpty_snprintf(tmp, "%sfonts %02u-%02u:", + prefix, static_cast(first), static_cast(last)); + line = tmp; + for (size_t i = first; i <= last; ++i) { + if (i % 10 == 5) { + line += " - "; + } + winpty_snprintf(tmp, " %2dx%-2d", + table[i].second.X, table[i].second.Y); + line += tmp; + } + trace("%s", line.c_str()); + first = last + 1; + } + if (table.size() == kMaxCount) { + trace("%sfonts: ... stopped reading at %d fonts ...", + prefix, kMaxCount); + } +} + +static std::string stringToCodePoints(const std::wstring &str) { + std::string ret = "("; + for (size_t i = 0; i < str.size(); ++i) { + char tmp[32]; + winpty_snprintf(tmp, "%X", str[i]); + if (ret.size() > 1) { + ret.push_back(' '); + } + ret += tmp; + } + ret.push_back(')'); + return ret; +} + +static void dumpFontInfoEx( + const AGENT_CONSOLE_FONT_INFOEX &infoex, + const char *prefix) { + if (!isTracingEnabled()) { + return; + } + std::wstring faceName(infoex.FaceName, + winpty_wcsnlen(infoex.FaceName, COUNT_OF(infoex.FaceName))); + trace("%snFont=%u dwFontSize=(%d,%d) " + "FontFamily=0x%x FontWeight=%u FaceName=%s %s", + prefix, + static_cast(infoex.nFont), + infoex.dwFontSize.X, infoex.dwFontSize.Y, + infoex.FontFamily, infoex.FontWeight, utf8FromWide(faceName).c_str(), + stringToCodePoints(faceName).c_str()); +} + +static void dumpVistaFont(VistaFontAPI &api, HANDLE conout, const char *prefix) { + if (!isTracingEnabled()) { + return; + } + AGENT_CONSOLE_FONT_INFOEX infoex = {0}; + infoex.cbSize = sizeof(infoex); + if (!api.GetCurrentConsoleFontEx()(conout, FALSE, &infoex)) { + trace("GetCurrentConsoleFontEx call failed"); + return; + } + dumpFontInfoEx(infoex, prefix); +} + +static void dumpXPFont(XPFontAPI &api, HANDLE conout, const char *prefix) { + if (!isTracingEnabled()) { + return; + } + AGENT_CONSOLE_FONT_INFO info = {0}; + if (!api.GetCurrentConsoleFont()(conout, FALSE, &info)) { + trace("GetCurrentConsoleFont call failed"); + return; + } + trace("%snFont=%u dwFontSize=(%d,%d)", + prefix, + static_cast(info.nFont), + info.dwFontSize.X, info.dwFontSize.Y); +} + +static bool setFontVista( + VistaFontAPI &api, + HANDLE conout, + const Font &font) { + AGENT_CONSOLE_FONT_INFOEX infoex = {}; + infoex.cbSize = sizeof(AGENT_CONSOLE_FONT_INFOEX); + infoex.dwFontSize.Y = font.size; + infoex.FontFamily = font.family; + infoex.FontWeight = 400; + winpty_wcsncpy_nul(infoex.FaceName, font.faceName); + dumpFontInfoEx(infoex, "setFontVista: setting font to: "); + if (!api.SetCurrentConsoleFontEx()(conout, FALSE, &infoex)) { + trace("setFontVista: SetCurrentConsoleFontEx call failed"); + return false; + } + memset(&infoex, 0, sizeof(infoex)); + infoex.cbSize = sizeof(infoex); + if (!api.GetCurrentConsoleFontEx()(conout, FALSE, &infoex)) { + trace("setFontVista: GetCurrentConsoleFontEx call failed"); + return false; + } + if (wcsncmp(infoex.FaceName, font.faceName, + COUNT_OF(infoex.FaceName)) != 0) { + trace("setFontVista: face name was not set"); + dumpFontInfoEx(infoex, "setFontVista: post-call font: "); + return false; + } + // We'd like to verify that the new font size is correct, but we can't + // predict what it will be, even though we just set it to `pxSize` through + // an apprently symmetric interface. For the Chinese and Korean fonts, the + // new `infoex.dwFontSize.Y` value can be slightly larger than the height + // we specified. + return true; +} + +static Font selectSmallFont(int codePage, int columns, bool isNewW10) { + // Iterate over a set of font sizes according to the code page, and select + // one. + + const wchar_t *faceName = nullptr; + unsigned int fontFamily = 0; + const FontSize *table = nullptr; + size_t tableSize = 0; + + switch (codePage) { + case 932: // Japanese + faceName = kMSGothic; + fontFamily = 0x36; + if (isNewW10) { + table = k932GothicWin10; + tableSize = COUNT_OF(k932GothicWin10); + } else if (isAtLeastWindows8()) { + table = k932GothicWin8; + tableSize = COUNT_OF(k932GothicWin8); + } else { + table = k932GothicVista; + tableSize = COUNT_OF(k932GothicVista); + } + break; + case 936: // Chinese Simplified + faceName = kNSimSun; + fontFamily = 0x36; + table = k936SimSun; + tableSize = COUNT_OF(k936SimSun); + break; + case 949: // Korean + faceName = kGulimChe; + fontFamily = 0x36; + table = k949GulimChe; + tableSize = COUNT_OF(k949GulimChe); + break; + case 950: // Chinese Traditional + faceName = kMingLight; + fontFamily = 0x36; + table = k950MingLight; + tableSize = COUNT_OF(k950MingLight); + break; + default: + faceName = kLucidaConsole; + fontFamily = 0x36; + table = kLucidaFontSizes; + tableSize = COUNT_OF(kLucidaFontSizes); + break; + } + + size_t bestIndex = static_cast(-1); + std::tuple bestScore = std::make_tuple(-1, -1); + + // We might want to pick the smallest possible font, because we don't know + // how large the monitor is (and the monitor size can change). We might + // want to pick a larger font to accommodate console programs that resize + // the console on their own, like DOS edit.com, which tends to resize the + // console to 80 columns. + + for (size_t i = 0; i < tableSize; ++i) { + const int width = table[i].width * columns; + + // In general, we'd like to pick a font size where cutting the number + // of columns in half doesn't immediately violate the minimum width + // constraint. (e.g. To run DOS edit.com, a user might resize their + // terminal to ~100 columns so it's big enough to show the 80 columns + // post-resize.) To achieve this, give priority to fonts that allow + // this halving. We don't want to encourage *very* large fonts, + // though, so disable the effect as the number of columns scales from + // 80 to 40. + const int halfColumns = std::min(columns, std::max(40, columns / 2)); + const int halfWidth = table[i].width * halfColumns; + + std::tuple thisScore = std::make_tuple(-1, -1); + if (width >= 160 && halfWidth >= 160) { + // Both sizes are good. Prefer the smaller fonts. + thisScore = std::make_tuple(2, -width); + } else if (width >= 160) { + // Prefer the smaller fonts. + thisScore = std::make_tuple(1, -width); + } else { + // Otherwise, prefer the largest font in our table. + thisScore = std::make_tuple(0, width); + } + if (thisScore > bestScore) { + bestIndex = i; + bestScore = thisScore; + } + } + + ASSERT(bestIndex != static_cast(-1)); + return Font { faceName, fontFamily, table[bestIndex].size }; +} + +static void setSmallFontVista(VistaFontAPI &api, HANDLE conout, + int columns, bool isNewW10) { + int codePage = GetConsoleOutputCP(); + const auto font = selectSmallFont(codePage, columns, isNewW10); + if (setFontVista(api, conout, font)) { + trace("setSmallFontVista: success"); + return; + } + if (codePage == 932 || codePage == 936 || + codePage == 949 || codePage == 950) { + trace("setSmallFontVista: falling back to default codepage font instead"); + const auto fontFB = selectSmallFont(0, columns, isNewW10); + if (setFontVista(api, conout, fontFB)) { + trace("setSmallFontVista: fallback was successful"); + return; + } + } + trace("setSmallFontVista: failure"); +} + +struct FontSizeComparator { + bool operator()(const std::pair &obj1, + const std::pair &obj2) const { + int score1 = obj1.second.X + obj1.second.Y; + int score2 = obj2.second.X + obj2.second.Y; + return score1 < score2; + } +}; + +static void setSmallFontXP(UndocumentedXPFontAPI &api, HANDLE conout) { + // Read the console font table and sort it from smallest to largest. + const DWORD fontCount = api.GetNumberOfConsoleFonts()(); + trace("setSmallFontXP: number of console fonts: %u", + static_cast(fontCount)); + std::vector > table = + readFontTable(api, conout, fontCount); + std::sort(table.begin(), table.end(), FontSizeComparator()); + for (size_t i = 0; i < table.size(); ++i) { + // Skip especially narrow fonts to permit narrower terminals. + if (table[i].second.X < 4) { + continue; + } + trace("setSmallFontXP: setting font to %u", + static_cast(table[i].first)); + if (!api.SetConsoleFont()(conout, table[i].first)) { + trace("setSmallFontXP: SetConsoleFont call failed"); + continue; + } + AGENT_CONSOLE_FONT_INFO info; + if (!api.GetCurrentConsoleFont()(conout, FALSE, &info)) { + trace("setSmallFontXP: GetCurrentConsoleFont call failed"); + return; + } + if (info.nFont != table[i].first) { + trace("setSmallFontXP: font was not set"); + dumpXPFont(api, conout, "setSmallFontXP: post-call font: "); + continue; + } + trace("setSmallFontXP: success"); + return; + } + trace("setSmallFontXP: failure"); +} + +} // anonymous namespace + +// A Windows console window can never be larger than the desktop window. To +// maximize the possible size of the console in rows*cols, try to configure +// the console with a small font. Unfortunately, we cannot make the font *too* +// small, because there is also a minimum window size in pixels. +void setSmallFont(HANDLE conout, int columns, bool isNewW10) { + trace("setSmallFont: attempting to set a small font for %d columns " + "(CP=%u OutputCP=%u)", + columns, + static_cast(GetConsoleCP()), + static_cast(GetConsoleOutputCP())); + VistaFontAPI vista; + if (vista.valid()) { + dumpVistaFont(vista, conout, "previous font: "); + dumpFontTable(conout, "previous font table: "); + setSmallFontVista(vista, conout, columns, isNewW10); + dumpVistaFont(vista, conout, "new font: "); + dumpFontTable(conout, "new font table: "); + return; + } + UndocumentedXPFontAPI xp; + if (xp.valid()) { + dumpXPFont(xp, conout, "previous font: "); + dumpFontTable(conout, "previous font table: "); + setSmallFontXP(xp, conout); + dumpXPFont(xp, conout, "new font: "); + dumpFontTable(conout, "new font table: "); + return; + } + trace("setSmallFont: neither Vista nor XP APIs detected -- giving up"); + dumpFontTable(conout, "font table: "); +}