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:
shenlei
2026-09-30 16:42:44 +09:00
co-authored by Claude Opus 5.5
parent 58a3d01027
commit 78d14776b8
2 changed files with 87 additions and 4 deletions
+57
View File
@@ -13,6 +13,8 @@
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cwchar>
#include <string>
#include <vector>
@@ -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<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)
{
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<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);
device->Release();
if (!g_failures)