fix(platform): decode GDI ANSI text with the font charset's code page
TextOutA / GetTextExtentPoint32A in the Win32 GDI shim always decoded with CP_ACP (1252), so the server notices drawn by uiTip.TipBoard / BigBoard (CTextBar, a GB2312_CHARSET font in the 936 locale) showed GBK bytes as Latin-1 mojibake. Windows GDI decodes ANSI calls with the code page of the selected font's charset; the shim now keeps lfCharSet on the font and does the same. port.text_bar checks TextOutA of GBK bytes against TextOutW of the Unicode text, and the extent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
58a3d01027
commit
78d14776b8
@@ -62,6 +62,7 @@ struct HFONT__ : GdiObject
|
|||||||
int descent = 0;
|
int descent = 0;
|
||||||
bool italic = false;
|
bool italic = false;
|
||||||
bool antialiased = false;
|
bool antialiased = false;
|
||||||
|
BYTE charset = ANSI_CHARSET;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct HDC__
|
struct HDC__
|
||||||
@@ -293,6 +294,7 @@ HFONT CreateFontIndirect(const LOGFONT* lplf)
|
|||||||
for (const char* name : kLinked)
|
for (const char* name : kLinked)
|
||||||
if (FT_Face linked = open_named(name); linked && linked != face) font->fallbacks.push_back(linked);
|
if (FT_Face linked = open_named(name); linked && linked != face) font->fallbacks.push_back(linked);
|
||||||
font->italic = lplf->lfItalic != 0;
|
font->italic = lplf->lfItalic != 0;
|
||||||
|
font->charset = lplf->lfCharSet;
|
||||||
font->antialiased = lplf->lfQuality == ANTIALIASED_QUALITY;
|
font->antialiased = lplf->lfQuality == ANTIALIASED_QUALITY;
|
||||||
|
|
||||||
const TT_OS2* os2 = static_cast<const TT_OS2*>(FT_Get_Sfnt_Table(face, FT_SFNT_OS2));
|
const TT_OS2* os2 = static_cast<const TT_OS2*>(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;
|
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)
|
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;
|
if (n <= 0) return c == 0;
|
||||||
std::vector<WCHAR> wide(static_cast<size_t>(n));
|
std::vector<WCHAR> wide(static_cast<size_t>(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);
|
return TextOutW(hdc, x, y, wide.data(), n);
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOL GetTextExtentPoint32A(HDC hdc, LPCSTR lpString, int c, LPSIZE psizl)
|
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 (n <= 0)
|
||||||
{
|
{
|
||||||
if (c != 0 || !psizl) return FALSE;
|
if (c != 0 || !psizl) return FALSE;
|
||||||
return GetTextExtentPoint32W(hdc, nullptr, 0, psizl);
|
return GetTextExtentPoint32W(hdc, nullptr, 0, psizl);
|
||||||
}
|
}
|
||||||
std::vector<WCHAR> wide(static_cast<size_t>(n));
|
std::vector<WCHAR> wide(static_cast<size_t>(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);
|
return GetTextExtentPoint32W(hdc, wide.data(), n, psizl);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <cwchar>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -60,6 +62,39 @@ static int opaque_pixels(const std::string& name, std::uint32_t* sample)
|
|||||||
return count;
|
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<std::uint32_t> 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<std::uint32_t> out(static_cast<std::uint32_t*>(bits), static_cast<std::uint32_t*>(bits) + 64 * 20);
|
||||||
|
DeleteObject(font);
|
||||||
|
DeleteDC(dc);
|
||||||
|
DeleteObject(bmp);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
static bool near(float a, float b)
|
static bool near(float a, float b)
|
||||||
{
|
{
|
||||||
return std::fabs(a - b) < 1e-3f;
|
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)
|
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<std::uint32_t> 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);
|
DeviceAccess::Set(NULL);
|
||||||
device->Release();
|
device->Release();
|
||||||
if (!g_failures)
|
if (!g_failures)
|
||||||
|
|||||||
Reference in New Issue
Block a user