2V0-g: port the .fnt font pipeline over a FreeType GDI emulation
CGraphicText, CGraphicFontTexture and CGraphicDib are the 40250 bodies verbatim; CGraphicTextInstance is verbatim except that its D3D draws become UI render commands (one image quad per glyph, Bar for cursor/underline). - platform/Win32Gdi.cpp: the GDI subset those files call, over vendored FreeType 2.13.3, following GDI's LOGFONT cell height, win metrics and gasp rules; system font lookup with substitute and CJK fallback faces - Win32Crt: MultiByteToWideChar / WideCharToMultiByte (UTF-8 and 1252 by hand, other code pages through iconv) - CGraphicImageTexture memory textures: glyph pages reach Godot as "mem:<id>@<revision>"; Metin2PythonHost.memory_texture + Canvas cache - Util.cpp code page / font face table, SetDefaultCodePage at boot, DefaultFont_* block, fnt resource type, GetMaxTextureWidth/Height - TextTag, StringCodec, Arabic, Vietnamese copied from 40250 - tests: port.text (Tahoma 12 metrics, alignment, CP949), port.app_loop and python_host_test.gd now check glyph quads instead of text commands Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
141206cb69
commit
afe63bfa61
@@ -11,6 +11,10 @@
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#if __has_include(<iconv.h>)
|
||||
#include <iconv.h>
|
||||
#define MT_WIN32CRT_HAS_ICONV 1
|
||||
#endif
|
||||
#include <glob.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
@@ -165,4 +169,198 @@ DWORD GetTickCount()
|
||||
return timeGetTime();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Windows-1252 0x80..0x9F; the other bytes map to the same code point (0 marks the undefined ones).
|
||||
const char32_t kCp1252High[32] = {
|
||||
0x20AC, 0, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0, 0x017D, 0,
|
||||
0, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0, 0x017E, 0x0178,
|
||||
};
|
||||
|
||||
bool is_cp1252(UINT cp) { return cp == CP_ACP || cp == 1252; }
|
||||
|
||||
bool decode_utf8(const unsigned char* p, size_t n, std::u32string& out)
|
||||
{
|
||||
bool ok = true;
|
||||
for (size_t i = 0; i < n;) {
|
||||
const unsigned char c = p[i];
|
||||
size_t len = c < 0x80 ? 1 : c >= 0xC2 && c <= 0xDF ? 2 : c >= 0xE0 && c <= 0xEF ? 3 : c >= 0xF0 && c <= 0xF4 ? 4 : 0;
|
||||
char32_t cp = len == 1 ? c : len == 2 ? (c & 0x1F) : len == 3 ? (c & 0x0F) : (c & 0x07);
|
||||
if (len == 0 || i + len > n) { out.push_back(0xFFFD); ok = false; ++i; continue; }
|
||||
bool valid = true;
|
||||
for (size_t k = 1; k < len; ++k) {
|
||||
if ((p[i + k] & 0xC0) != 0x80) { valid = false; break; }
|
||||
cp = (cp << 6) | (p[i + k] & 0x3F);
|
||||
}
|
||||
if (!valid || (len == 3 && cp < 0x800) || (len == 4 && (cp < 0x10000 || cp > 0x10FFFF)) || (cp >= 0xD800 && cp <= 0xDFFF)) {
|
||||
out.push_back(0xFFFD); ok = false; ++i; continue;
|
||||
}
|
||||
out.push_back(cp);
|
||||
i += len;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
void encode_utf8(char32_t cp, std::string& out)
|
||||
{
|
||||
if (cp < 0x80) out.push_back(char(cp));
|
||||
else if (cp < 0x800) { out.push_back(char(0xC0 | (cp >> 6))); out.push_back(char(0x80 | (cp & 0x3F))); }
|
||||
else if (cp < 0x10000) { out.push_back(char(0xE0 | (cp >> 12))); out.push_back(char(0x80 | ((cp >> 6) & 0x3F))); out.push_back(char(0x80 | (cp & 0x3F))); }
|
||||
else { out.push_back(char(0xF0 | (cp >> 18))); out.push_back(char(0x80 | ((cp >> 12) & 0x3F))); out.push_back(char(0x80 | ((cp >> 6) & 0x3F))); out.push_back(char(0x80 | (cp & 0x3F))); }
|
||||
}
|
||||
|
||||
#if defined(MT_WIN32CRT_HAS_ICONV)
|
||||
std::string iconv_name(UINT cp)
|
||||
{
|
||||
switch (cp) {
|
||||
case 949: return "CP949";
|
||||
case 936: return "GBK";
|
||||
case 950: return "BIG5";
|
||||
case 932: return "SHIFT_JIS";
|
||||
case 874: return "CP874";
|
||||
default: return "CP" + std::to_string(cp);
|
||||
}
|
||||
}
|
||||
|
||||
// One character at a time, so an undecodable byte becomes one default char like Windows does.
|
||||
bool iconv_decode(UINT cp, const unsigned char* p, size_t n, std::u32string& out)
|
||||
{
|
||||
iconv_t cd = iconv_open("UTF-32LE", iconv_name(cp).c_str());
|
||||
if (cd == reinterpret_cast<iconv_t>(-1)) return false;
|
||||
for (size_t i = 0; i < n;) {
|
||||
unsigned char wide[16];
|
||||
bool done = false;
|
||||
for (size_t len = 1; len <= 4 && i + len <= n && !done; ++len) {
|
||||
iconv(cd, nullptr, nullptr, nullptr, nullptr);
|
||||
char* in = const_cast<char*>(reinterpret_cast<const char*>(p + i));
|
||||
size_t in_left = len;
|
||||
char* o = reinterpret_cast<char*>(wide);
|
||||
size_t o_left = sizeof(wide);
|
||||
if (iconv(cd, &in, &in_left, &o, &o_left) != size_t(-1) && in_left == 0 && o_left < sizeof(wide)) {
|
||||
for (size_t k = 0; k + 4 <= sizeof(wide) - o_left; k += 4)
|
||||
out.push_back(char32_t(wide[k] | (wide[k + 1] << 8) | (wide[k + 2] << 16) | (unsigned(wide[k + 3]) << 24)));
|
||||
i += len;
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
if (!done) { out.push_back(0xFFFD); ++i; }
|
||||
}
|
||||
iconv_close(cd);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool iconv_encode(UINT cp, const std::u32string& in, std::string& out, bool* used_default)
|
||||
{
|
||||
iconv_t cd = iconv_open(iconv_name(cp).c_str(), "UTF-32LE");
|
||||
if (cd == reinterpret_cast<iconv_t>(-1)) return false;
|
||||
for (char32_t c : in) {
|
||||
unsigned char src[4] = {static_cast<unsigned char>(c), static_cast<unsigned char>(c >> 8), static_cast<unsigned char>(c >> 16), static_cast<unsigned char>(c >> 24)};
|
||||
char buf[16];
|
||||
char* ip = reinterpret_cast<char*>(src);
|
||||
size_t il = 4;
|
||||
char* op = buf;
|
||||
size_t ol = sizeof(buf);
|
||||
iconv(cd, nullptr, nullptr, nullptr, nullptr);
|
||||
if (iconv(cd, &ip, &il, &op, &ol) != size_t(-1) && il == 0) {
|
||||
out.append(buf, sizeof(buf) - ol);
|
||||
} else {
|
||||
out.push_back('?');
|
||||
*used_default = true;
|
||||
}
|
||||
}
|
||||
iconv_close(cd);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool decode(UINT cp, const unsigned char* p, size_t n, std::u32string& out)
|
||||
{
|
||||
if (cp == CP_UTF8) return decode_utf8(p, n, out);
|
||||
if (is_cp1252(cp)) {
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
const unsigned char c = p[i];
|
||||
out.push_back(c >= 0x80 && c < 0xA0 ? (kCp1252High[c - 0x80] ? kCp1252High[c - 0x80] : char32_t(c)) : char32_t(c));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#if defined(MT_WIN32CRT_HAS_ICONV)
|
||||
if (iconv_decode(cp, p, n, out)) return true;
|
||||
#endif
|
||||
// No converter for this code page: ASCII passes, the rest becomes U+FFFD.
|
||||
for (size_t i = 0; i < n; ++i) out.push_back(p[i] < 0x80 ? char32_t(p[i]) : char32_t(0xFFFD));
|
||||
return true;
|
||||
}
|
||||
|
||||
void encode(UINT cp, const std::u32string& in, std::string& out, bool* used_default)
|
||||
{
|
||||
if (cp == CP_UTF8) {
|
||||
for (char32_t c : in) encode_utf8(c, out);
|
||||
return;
|
||||
}
|
||||
if (is_cp1252(cp)) {
|
||||
for (char32_t c : in) {
|
||||
if (c < 0x80 || (c >= 0xA0 && c < 0x100)) { out.push_back(char(c)); continue; }
|
||||
int hit = -1;
|
||||
for (int k = 0; k < 32; ++k)
|
||||
if (kCp1252High[k] == c) { hit = k; break; }
|
||||
if (hit >= 0) out.push_back(char(0x80 + hit));
|
||||
else { out.push_back('?'); *used_default = true; }
|
||||
}
|
||||
return;
|
||||
}
|
||||
#if defined(MT_WIN32CRT_HAS_ICONV)
|
||||
if (iconv_encode(cp, in, out, used_default)) return;
|
||||
#endif
|
||||
for (char32_t c : in) {
|
||||
if (c < 0x80) out.push_back(char(c));
|
||||
else { out.push_back('?'); *used_default = true; }
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int MultiByteToWideChar(UINT CodePage, DWORD dwFlags, LPCSTR lpMultiByteStr, int cbMultiByte,
|
||||
LPWSTR lpWideCharStr, int cchWideChar)
|
||||
{
|
||||
if (!lpMultiByteStr || cbMultiByte == 0 || cchWideChar < 0) return 0;
|
||||
const size_t n = cbMultiByte < 0 ? std::strlen(lpMultiByteStr) + 1 : size_t(cbMultiByte);
|
||||
std::u32string wide;
|
||||
const bool ok = decode(CodePage, reinterpret_cast<const unsigned char*>(lpMultiByteStr), n, wide);
|
||||
if (!ok && (dwFlags & MB_ERR_INVALID_CHARS)) return 0;
|
||||
if (cchWideChar == 0) return int(wide.size());
|
||||
if (wide.size() > size_t(cchWideChar)) return 0;
|
||||
for (size_t i = 0; i < wide.size(); ++i) lpWideCharStr[i] = WCHAR(wide[i]);
|
||||
return int(wide.size());
|
||||
}
|
||||
|
||||
int WideCharToMultiByte(UINT CodePage, DWORD, LPCWSTR lpWideCharStr, int cchWideChar,
|
||||
LPSTR lpMultiByteStr, int cbMultiByte, LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar)
|
||||
{
|
||||
if (!lpWideCharStr || cchWideChar == 0 || cbMultiByte < 0) return 0;
|
||||
size_t n = 0;
|
||||
if (cchWideChar < 0) { while (lpWideCharStr[n]) ++n; ++n; }
|
||||
else n = size_t(cchWideChar);
|
||||
std::u32string wide(n, 0);
|
||||
for (size_t i = 0; i < n; ++i) wide[i] = char32_t(lpWideCharStr[i]);
|
||||
std::string bytes;
|
||||
bool used_default = false;
|
||||
encode(CodePage, wide, bytes, &used_default);
|
||||
if (used_default && lpDefaultChar && CodePage != CP_UTF8) {
|
||||
// '?' was the placeholder; the caller's default char replaces it only where a fallback happened.
|
||||
std::string redo;
|
||||
for (char32_t c : wide) {
|
||||
std::string one;
|
||||
bool miss = false;
|
||||
encode(CodePage, std::u32string(1, c), one, &miss);
|
||||
redo += miss ? std::string(1, *lpDefaultChar) : one;
|
||||
}
|
||||
bytes.swap(redo);
|
||||
}
|
||||
if (lpUsedDefaultChar) *lpUsedDefaultChar = used_default ? TRUE : FALSE;
|
||||
if (cbMultiByte == 0) return int(bytes.size());
|
||||
if (bytes.size() > size_t(cbMultiByte)) return 0;
|
||||
std::memcpy(lpMultiByteStr, bytes.data(), bytes.size());
|
||||
return int(bytes.size());
|
||||
}
|
||||
|
||||
#endif // !_WIN32
|
||||
|
||||
@@ -83,4 +83,16 @@ void DeleteCriticalSection(LPCRITICAL_SECTION cs);
|
||||
void EnterCriticalSection(LPCRITICAL_SECTION cs);
|
||||
void LeaveCriticalSection(LPCRITICAL_SECTION cs);
|
||||
|
||||
// stringapiset.h. WCHAR is 32-bit here, so wide strings hold UTF-32 code points. CP_UTF8 and
|
||||
// CP_ACP/1252 are converted directly; other code pages go through iconv where the platform has it.
|
||||
#define CP_ACP 0
|
||||
#define CP_UTF8 65001
|
||||
#define MB_PRECOMPOSED 0x00000001
|
||||
#define MB_ERR_INVALID_CHARS 0x00000008
|
||||
#define WC_NO_BEST_FIT_CHARS 0x00000400
|
||||
int MultiByteToWideChar(UINT CodePage, DWORD dwFlags, LPCSTR lpMultiByteStr, int cbMultiByte,
|
||||
LPWSTR lpWideCharStr, int cchWideChar);
|
||||
int WideCharToMultiByte(UINT CodePage, DWORD dwFlags, LPCWSTR lpWideCharStr, int cchWideChar,
|
||||
LPSTR lpMultiByteStr, int cbMultiByte, LPCSTR lpDefaultChar, LPBOOL lpUsedDefaultChar);
|
||||
|
||||
#endif // !_WIN32
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
#pragma once
|
||||
// Shim for <windows.h> on non-Windows targets: the Win32 scalar types and CRT spellings
|
||||
// 40250 sources use (common/Win32Types.h, common/Win32Crt.h), plus the headers the real one pulls
|
||||
// in that 40250 relies on transitively (winnt.h brings <string.h> for memcpy/memset).
|
||||
// in that 40250 relies on transitively (winnt.h brings <string.h> for memcpy/memset, and <wchar.h>
|
||||
// for wcslen).
|
||||
#include <string.h>
|
||||
#include <wchar.h>
|
||||
#include "../../Win32Types.h"
|
||||
#include "../../Win32Crt.h"
|
||||
#include "wingdi.h"
|
||||
|
||||
// winuser.h / wingdi.h constants named in 40250 declarations (default arguments).
|
||||
#define WS_OVERLAPPED 0x00000000L
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
// Shim for the <wingdi.h> subset the 40250 font path uses (EterLib/GrpDIB, GrpFontTexture, Util).
|
||||
// The calls are implemented by the platform layer over FreeType (platform/Win32Gdi.cpp): one DIB
|
||||
// per memory DC, glyphs rendered the way GDI lays out a TrueType face for a LOGFONT cell height.
|
||||
#include "../../Win32Types.h"
|
||||
|
||||
typedef DWORD COLORREF;
|
||||
typedef void* HGDIOBJ;
|
||||
#define RGB(r, g, b) ((COLORREF)(((BYTE)(r) | ((WORD)((BYTE)(g)) << 8)) | (((DWORD)(BYTE)(b)) << 16)))
|
||||
|
||||
typedef struct _ABCFLOAT
|
||||
{
|
||||
FLOAT abcfA;
|
||||
FLOAT abcfB;
|
||||
FLOAT abcfC;
|
||||
} ABCFLOAT, *LPABCFLOAT;
|
||||
|
||||
#define TRANSPARENT 1
|
||||
#define OPAQUE 2
|
||||
|
||||
#define FW_NORMAL 400
|
||||
#define FW_BOLD 700
|
||||
#define OUT_DEFAULT_PRECIS 0
|
||||
#define CLIP_DEFAULT_PRECIS 0
|
||||
#define DEFAULT_QUALITY 0
|
||||
#define NONANTIALIASED_QUALITY 3
|
||||
#define ANTIALIASED_QUALITY 4
|
||||
#define DEFAULT_PITCH 0
|
||||
|
||||
#define ANSI_CHARSET 0
|
||||
#define DEFAULT_CHARSET 1
|
||||
#define SHIFTJIS_CHARSET 128
|
||||
#define HANGUL_CHARSET 129
|
||||
#define GB2312_CHARSET 134
|
||||
#define CHINESEBIG5_CHARSET 136
|
||||
#define GREEK_CHARSET 161
|
||||
#define TURKISH_CHARSET 162
|
||||
#define VIETNAMESE_CHARSET 163
|
||||
#define HEBREW_CHARSET 177
|
||||
#define ARABIC_CHARSET 178
|
||||
#define BALTIC_CHARSET 186
|
||||
#define RUSSIAN_CHARSET 204
|
||||
#define THAI_CHARSET 222
|
||||
#define EASTEUROPE_CHARSET 238
|
||||
|
||||
#define BI_RGB 0L
|
||||
#define DIB_RGB_COLORS 0
|
||||
|
||||
HDC CreateCompatibleDC(HDC hdc);
|
||||
BOOL DeleteDC(HDC hdc);
|
||||
HBITMAP CreateDIBSection(HDC hdc, const BITMAPINFO* pbmi, UINT usage, void** ppvBits, HANDLE hSection, DWORD offset);
|
||||
int SetDIBitsToDevice(HDC hdc, int xDest, int yDest, DWORD w, DWORD h, int xSrc, int ySrc, UINT StartScan,
|
||||
UINT cLines, const void* lpvBits, const BITMAPINFO* lpbmi, UINT ColorUse);
|
||||
HFONT CreateFontIndirect(const LOGFONT* lplf);
|
||||
BOOL DeleteObject(HGDIOBJ ho);
|
||||
HGDIOBJ SelectObject(HDC hdc, HGDIOBJ h);
|
||||
COLORREF SetTextColor(HDC hdc, COLORREF color);
|
||||
COLORREF SetBkColor(HDC hdc, COLORREF color);
|
||||
int SetBkMode(HDC hdc, int mode);
|
||||
BOOL GetTextExtentPoint32W(HDC hdc, LPCWSTR lpString, int c, LPSIZE psizl);
|
||||
BOOL GetCharABCWidthsFloatW(HDC hdc, UINT iFirst, UINT iLast, LPABCFLOAT lpABC);
|
||||
BOOL TextOutW(HDC hdc, int x, int y, LPCWSTR lpString, int c);
|
||||
BOOL TextOutA(HDC hdc, int x, int y, LPCSTR lpString, int c);
|
||||
#define TextOut TextOutA
|
||||
Reference in New Issue
Block a user