Fix unicode support for win9x

This commit is contained in:
Pat Hartl 2026-05-23 21:07:28 -05:00
parent 528356e9e4
commit dd4aa6b163

View file

@ -1,6 +1,15 @@
// gdi_font.cpp — TrueType font rendering via GDI, blitted into Allegro
// BITMAPs. This file includes Allegro AND Windows headers; winalleg.h
// resolves the BITMAP typedef conflict.
//
// Unicode strategy (MSLU / "unicows" pattern):
// All text input is UTF-8. We always convert to UTF-16 internally.
// At runtime we detect whether the OS supports W (wide) Win32 APIs:
// - NT-based (2000+): call W functions directly → full Unicode.
// - Win9x (95/98/Me): convert UTF-16 → current codepage via
// WideCharToMultiByte, then call A functions → best-effort rendering.
// This is the same approach Microsoft's unicows.dll takes, implemented
// inline for the small set of GDI functions we actually use.
#include <allegro.h>
#ifdef ALLEGRO_WINDOWS
@ -19,8 +28,18 @@ namespace
HFONT g_hfont = NULL;
HDC g_measure_dc = NULL; // off-screen DC kept for measuring only
int g_font_height = 0;
bool g_wide_ok = false; // true on NT-based systems (W APIs work)
// Convert a UTF-8 string to UTF-16 for wide Win32 APIs.
// Detect whether the OS supports W (wide/Unicode) Win32 APIs.
void detect_unicode_support()
{
OSVERSIONINFOA ovi;
ovi.dwOSVersionInfoSize = sizeof(ovi);
GetVersionExA(&ovi);
g_wide_ok = (ovi.dwPlatformId == VER_PLATFORM_WIN32_NT);
}
// Convert UTF-8 to UTF-16. Works on all Win32 platforms.
std::wstring utf8_to_wide(const char *s, int len = -1)
{
if (!s || (len == 0) || (len == -1 && !*s))
@ -30,27 +49,59 @@ namespace
return std::wstring();
std::wstring w(wlen, L'\0');
MultiByteToWideChar(CP_UTF8, 0, s, len, &w[0], wlen);
// If len was -1, MultiByteToWideChar includes the null terminator in wlen.
if (len == -1 && !w.empty() && w.back() == L'\0')
w.pop_back();
return w;
}
// Convert UTF-16 to the current ANSI codepage (for Win9x A-function fallback).
std::string wide_to_ansi(const std::wstring &w)
{
if (w.empty())
return std::string();
int alen = WideCharToMultiByte(CP_ACP, 0, w.c_str(), (int)w.size(),
NULL, 0, NULL, NULL);
if (alen <= 0)
return std::string();
std::string a(alen, '\0');
WideCharToMultiByte(CP_ACP, 0, w.c_str(), (int)w.size(),
&a[0], alen, NULL, NULL);
return a;
}
// Try to create the font with the given face name.
HFONT try_create(const char *face, int pt)
{
std::wstring wface = utf8_to_wide(face);
return CreateFontW(
-pt, // negative = point size (not cell height)
0, 0, 0,
FW_NORMAL, // weight
FALSE, FALSE, FALSE, // italic, underline, strikeout
DEFAULT_CHARSET,
OUT_TT_PRECIS, // prefer TrueType
CLIP_DEFAULT_PRECIS,
CLEARTYPE_QUALITY,
DEFAULT_PITCH | FF_SWISS,
wface.c_str());
// Win9x doesn't have ClearType.
DWORD quality = g_wide_ok ? CLEARTYPE_QUALITY : ANTIALIASED_QUALITY;
if (g_wide_ok)
{
std::wstring wface = utf8_to_wide(face);
return CreateFontW(
-pt, 0, 0, 0,
FW_NORMAL,
FALSE, FALSE, FALSE,
DEFAULT_CHARSET,
OUT_TT_PRECIS,
CLIP_DEFAULT_PRECIS,
quality,
DEFAULT_PITCH | FF_SWISS,
wface.c_str());
}
else
{
return CreateFontA(
-pt, 0, 0, 0,
FW_NORMAL,
FALSE, FALSE, FALSE,
DEFAULT_CHARSET,
OUT_TT_PRECIS,
CLIP_DEFAULT_PRECIS,
quality,
DEFAULT_PITCH | FF_SWISS,
face);
}
}
// Verify the returned font actually matches the requested face name
@ -59,17 +110,60 @@ namespace
{
HDC dc = GetDC(NULL);
HFONT old = (HFONT)SelectObject(dc, hf);
wchar_t actual[LF_FACESIZE] = {};
GetTextFaceW(dc, LF_FACESIZE, actual);
bool match = false;
if (g_wide_ok)
{
wchar_t actual[LF_FACESIZE] = {};
GetTextFaceW(dc, LF_FACESIZE, actual);
std::wstring wexpected = utf8_to_wide(expected);
match = (_wcsicmp(actual, wexpected.c_str()) == 0);
}
else
{
char actual[LF_FACESIZE] = {};
GetTextFaceA(dc, LF_FACESIZE, actual);
match = (_stricmp(actual, expected) == 0);
}
SelectObject(dc, old);
ReleaseDC(NULL, dc);
std::wstring wexpected = utf8_to_wide(expected);
return _wcsicmp(actual, wexpected.c_str()) == 0;
return match;
}
// Measure text width using the appropriate API.
void measure_text(HDC dc, const std::wstring &wtext, SIZE *sz)
{
if (g_wide_ok)
{
GetTextExtentPoint32W(dc, wtext.c_str(), (int)wtext.size(), sz);
}
else
{
std::string ansi = wide_to_ansi(wtext);
GetTextExtentPoint32A(dc, ansi.c_str(), (int)ansi.size(), sz);
}
}
// Draw text into a DC using the appropriate API.
void draw_to_dc(HDC dc, const std::wstring &wtext, RECT *rc, UINT fmt)
{
if (g_wide_ok)
{
DrawTextW(dc, wtext.c_str(), (int)wtext.size(), rc, fmt);
}
else
{
std::string ansi = wide_to_ansi(wtext);
DrawTextA(dc, ansi.c_str(), (int)ansi.size(), rc, fmt);
}
}
}
void gdi_font_init(int point_size)
{
detect_unicode_support();
// Try Inter first, then Segoe UI, then Arial.
static const char *faces[] = {"Inter", "Segoe UI", "Arial"};
for (int i = 0; i < 3; ++i)
@ -92,9 +186,18 @@ void gdi_font_init(int point_size)
g_measure_dc = CreateCompatibleDC(NULL);
SelectObject(g_measure_dc, g_hfont);
TEXTMETRICW tm;
GetTextMetricsW(g_measure_dc, &tm);
g_font_height = tm.tmHeight;
if (g_wide_ok)
{
TEXTMETRICW tm;
GetTextMetricsW(g_measure_dc, &tm);
g_font_height = tm.tmHeight;
}
else
{
TEXTMETRICA tm;
GetTextMetricsA(g_measure_dc, &tm);
g_font_height = tm.tmHeight;
}
}
void gdi_font_shutdown()
@ -122,7 +225,7 @@ int gdi_font_text_width(const char *text)
return 0;
std::wstring w = utf8_to_wide(text);
SIZE sz;
GetTextExtentPoint32W(g_measure_dc, w.c_str(), (int)w.size(), &sz);
measure_text(g_measure_dc, w, &sz);
return sz.cx;
}
@ -132,7 +235,7 @@ int gdi_font_text_width(const char *text)
// Strategy:
// 1. Create a small DIB section just big enough for the text.
// 2. Fill it with black (so GDI anti-aliasing blends against black).
// 3. Use DrawTextA to render white text onto the DIB.
// 3. Render white text onto the DIB.
// 4. Read pixels back — the white channel gives us coverage (alpha).
// 5. Blend each pixel into the Allegro BITMAP at the requested color.
// -----------------------------------------------------------------------
@ -143,10 +246,9 @@ static void render(BITMAP *dst, int x, int y, int allegro_color, const char *tex
return;
std::wstring wtext = utf8_to_wide(text);
int wlen = (int)wtext.size();
SIZE sz;
GetTextExtentPoint32W(g_measure_dc, wtext.c_str(), wlen, &sz);
measure_text(g_measure_dc, wtext, &sz);
int tw = sz.cx;
int th = sz.cy;
if (tw <= 0 || th <= 0)
@ -181,7 +283,7 @@ static void render(BITMAP *dst, int x, int y, int allegro_color, const char *tex
SetTextColor(dc, (COLORREF)0x00FFFFFF); // white
SetBkMode(dc, TRANSPARENT);
RECT rc = {0, 0, tw, th};
DrawTextW(dc, wtext.c_str(), wlen, &rc, DT_LEFT | DT_TOP | DT_NOPREFIX | DT_SINGLELINE);
draw_to_dc(dc, wtext, &rc, DT_LEFT | DT_TOP | DT_NOPREFIX | DT_SINGLELINE);
GdiFlush();