diff --git a/src/platform/Win32Gdi.cpp b/src/platform/Win32Gdi.cpp index 21189d7d..e451f46a 100644 --- a/src/platform/Win32Gdi.cpp +++ b/src/platform/Win32Gdi.cpp @@ -62,6 +62,7 @@ struct HFONT__ : GdiObject int descent = 0; bool italic = false; bool antialiased = false; + BYTE charset = ANSI_CHARSET; }; struct HDC__ @@ -293,6 +294,7 @@ HFONT CreateFontIndirect(const LOGFONT* lplf) for (const char* name : kLinked) if (FT_Face linked = open_named(name); linked && linked != face) font->fallbacks.push_back(linked); font->italic = lplf->lfItalic != 0; + font->charset = lplf->lfCharSet; font->antialiased = lplf->lfQuality == ANTIALIASED_QUALITY; const TT_OS2* os2 = static_cast(FT_Get_Sfnt_Table(face, FT_SFNT_OS2)); @@ -447,24 +449,48 @@ BOOL TextOutW(HDC hdc, int x, int y, LPCWSTR lpString, int c) return TRUE; } +// The ANSI GDI calls decode the bytes with the code page of the selected font's charset (CTextBar selects +// GetCharsetFromCodePage of the locale, e.g. GB2312_CHARSET for a 936 locale); ANSI/DEFAULT fall back to CP_ACP. +static UINT dc_code_page(HDC hdc) +{ + switch (dc_font(hdc)->charset) { + case SHIFTJIS_CHARSET: return 932; + case HANGUL_CHARSET: return 949; + case GB2312_CHARSET: return 936; + case CHINESEBIG5_CHARSET: return 950; + case GREEK_CHARSET: return 1253; + case TURKISH_CHARSET: return 1254; + case VIETNAMESE_CHARSET: return 1258; + case HEBREW_CHARSET: return 1255; + case ARABIC_CHARSET: return 1256; + case BALTIC_CHARSET: return 1257; + case RUSSIAN_CHARSET: return 1251; + case THAI_CHARSET: return 874; + case EASTEUROPE_CHARSET: return 1250; + default: return CP_ACP; + } +} + BOOL TextOutA(HDC hdc, int x, int y, LPCSTR lpString, int c) { - const int n = MultiByteToWideChar(CP_ACP, 0, lpString, c, nullptr, 0); + const UINT cp = dc_code_page(hdc); + const int n = MultiByteToWideChar(cp, 0, lpString, c, nullptr, 0); if (n <= 0) return c == 0; std::vector wide(static_cast(n)); - MultiByteToWideChar(CP_ACP, 0, lpString, c, wide.data(), n); + MultiByteToWideChar(cp, 0, lpString, c, wide.data(), n); return TextOutW(hdc, x, y, wide.data(), n); } BOOL GetTextExtentPoint32A(HDC hdc, LPCSTR lpString, int c, LPSIZE psizl) { - const int n = MultiByteToWideChar(CP_ACP, 0, lpString, c, nullptr, 0); + const UINT cp = dc_code_page(hdc); + const int n = MultiByteToWideChar(cp, 0, lpString, c, nullptr, 0); if (n <= 0) { if (c != 0 || !psizl) return FALSE; return GetTextExtentPoint32W(hdc, nullptr, 0, psizl); } std::vector wide(static_cast(n)); - MultiByteToWideChar(CP_ACP, 0, lpString, c, wide.data(), n); + MultiByteToWideChar(cp, 0, lpString, c, wide.data(), n); return GetTextExtentPoint32W(hdc, wide.data(), n, psizl); } diff --git a/tests/port/port_text_bar_test.cpp b/tests/port/port_text_bar_test.cpp index 4ebe742e..ef5fce97 100644 --- a/tests/port/port_text_bar_test.cpp +++ b/tests/port/port_text_bar_test.cpp @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include @@ -60,6 +62,39 @@ static int opaque_pixels(const std::string& name, std::uint32_t* sample) return count; } +// Renders text into a 64 x 20 DIB with a 12 px font of the given charset (A or W call); returns the bits. +static std::vector gdi_text(BYTE charset, const char* ansi, const wchar_t* wide) +{ + BITMAPINFO info = {}; + info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + info.bmiHeader.biWidth = 64; + info.bmiHeader.biHeight = -20; + info.bmiHeader.biPlanes = 1; + info.bmiHeader.biBitCount = 32; + void* bits = NULL; + HDC dc = CreateCompatibleDC(NULL); + HBITMAP bmp = CreateDIBSection(dc, &info, DIB_RGB_COLORS, &bits, NULL, 0); + SelectObject(dc, bmp); + LOGFONT lf = {}; + lf.lfHeight = 12; + lf.lfCharSet = charset; + lf.lfQuality = ANTIALIASED_QUALITY; + std::strcpy(lf.lfFaceName, "Arial"); + HFONT font = CreateFontIndirect(&lf); + SelectObject(dc, font); + SetBkMode(dc, TRANSPARENT); + SetTextColor(dc, RGB(255, 255, 255)); + if (ansi) + TextOutA(dc, 0, 0, ansi, int(std::strlen(ansi))); + else + TextOutW(dc, 0, 0, wide, int(std::wcslen(wide))); + std::vector out(static_cast(bits), static_cast(bits) + 64 * 20); + DeleteObject(font); + DeleteDC(dc); + DeleteObject(bmp); + return out; +} + static bool near(float a, float b) { return std::fabs(a - b) < 1e-3f; @@ -162,6 +197,28 @@ int main() CHECK(images().size() == 1); // 64 x 64 fits in one block (count 0, rest 64) } + { + // GDI's ANSI calls decode with the selected font's charset (CTextBar: GetCharsetFromCodePage of the + // locale), so a 936 server notice in GBK draws as the CJK text, not as its bytes read as CP 1252. + const char gbk[] = "\xB9\xAB\xB8\xE6"; // "公告" + const std::vector wide = gdi_text(GB2312_CHARSET, NULL, L"\u516C\u544A"); + int lit = 0; + for (std::uint32_t px : wide) + lit += px != 0; + CHECK(lit > 20); + CHECK(gdi_text(GB2312_CHARSET, gbk, NULL) == wide); + CHECK(gdi_text(ANSI_CHARSET, gbk, NULL) != wide); + SIZE size = {}; + SetDefaultCodePage(936); + CTextBar bar(12, false); + CHECK(bar.Create(NULL, 64, 20)); + bar.GetTextExtent(gbk, &size); + SIZE one = {}; + bar.GetTextExtent("\xB9\xAB", &one); + CHECK(one.cx > 0 && size.cx == 2 * one.cx); // two full-width glyphs, not four Latin-1 ones + SetDefaultCodePage(1252); + } + DeviceAccess::Set(NULL); device->Release(); if (!g_failures)