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
@@ -10,7 +10,7 @@ if(NOT TARGET mtpython)
|
||||
list(FILTER MT_PLATFORM_SOURCES EXCLUDE REGEX "/ScriptLib/")
|
||||
endif()
|
||||
add_library(port_platform STATIC ${MT_PLATFORM_SOURCES})
|
||||
target_link_libraries(port_platform PUBLIC port_logic)
|
||||
target_link_libraries(port_platform PUBLIC port_logic PRIVATE mt3p::freetype)
|
||||
foreach(src IN LISTS MT_PLATFORM_SOURCES)
|
||||
mt_port_apply_defines(${src})
|
||||
endforeach()
|
||||
|
||||
@@ -1,71 +1,115 @@
|
||||
// Platform skeleton for EterLib/GrpDIB.h (40250 EterLib/GrpDIB.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
// 40250 EterLib/GrpDIB.cpp, verbatim. The GDI calls are the FreeType-backed platform/Win32Gdi.cpp.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/GrpDIB.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
|
||||
CGraphicDib::CGraphicDib()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
Initialize();
|
||||
}
|
||||
|
||||
CGraphicDib::~CGraphicDib()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
Destroy();
|
||||
}
|
||||
|
||||
auto CGraphicDib::Destroy() -> void
|
||||
void CGraphicDib::Initialize()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
m_hDC=NULL;
|
||||
m_hBmp=NULL;
|
||||
m_pvBuf=NULL;
|
||||
m_width=0;
|
||||
m_height=0;
|
||||
}
|
||||
|
||||
auto CGraphicDib::Create(HDC, int, int) -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
void CGraphicDib::Destroy()
|
||||
{
|
||||
if (m_hBmp) DeleteObject(m_hBmp);
|
||||
if (m_hDC) DeleteDC(m_hDC);
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
auto CGraphicDib::SetBkMode(int) -> void
|
||||
bool CGraphicDib::Create(HDC hDC, int width, int height)
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
Destroy();
|
||||
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
|
||||
ZeroMemory(&m_bmi.bmiHeader, sizeof(BITMAPINFOHEADER));
|
||||
m_bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
m_bmi.bmiHeader.biWidth = m_width;
|
||||
m_bmi.bmiHeader.biHeight = -m_height;
|
||||
m_bmi.bmiHeader.biPlanes = 1;
|
||||
m_bmi.bmiHeader.biBitCount = 32;
|
||||
m_bmi.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
m_hDC=CreateCompatibleDC(hDC);
|
||||
if (!m_hDC)
|
||||
{
|
||||
assert(!"CGraphicDib::Create CreateCompatibleDC Error");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_hBmp=CreateDIBSection(m_hDC, &m_bmi, DIB_RGB_COLORS, &m_pvBuf, NULL, 0);
|
||||
if (!m_hBmp)
|
||||
{
|
||||
assert(!"CGraphicDib::Create CreateDIBSection Error");
|
||||
return false;
|
||||
}
|
||||
|
||||
SelectObject(m_hDC, m_hBmp);
|
||||
|
||||
::SetTextColor(m_hDC, RGB(255, 255, 255));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto CGraphicDib::TextOut(int, int, const char *) -> void
|
||||
HDC CGraphicDib::GetDCHandle()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return m_hDC;
|
||||
}
|
||||
|
||||
auto CGraphicDib::Put(HDC, int, int) -> void
|
||||
void CGraphicDib::SetBkMode(int iBkMode)
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
::SetBkMode(m_hDC, iBkMode);
|
||||
}
|
||||
|
||||
auto CGraphicDib::GetWidth() -> int
|
||||
void CGraphicDib::TextOut(int ix, int iy, const char * c_szText)
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<int>();
|
||||
::SetBkColor(m_hDC, 0);
|
||||
::TextOut(m_hDC, ix, iy, c_szText, strlen(c_szText));
|
||||
}
|
||||
|
||||
auto CGraphicDib::GetHeight() -> int
|
||||
void CGraphicDib::Put(HDC hDC, int x, int y)
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<int>();
|
||||
SetDIBitsToDevice(
|
||||
hDC,
|
||||
x,
|
||||
y,
|
||||
m_width,
|
||||
m_height,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
m_height,
|
||||
m_pvBuf,
|
||||
&m_bmi,
|
||||
DIB_RGB_COLORS
|
||||
);
|
||||
}
|
||||
|
||||
auto CGraphicDib::GetPointer() -> void *
|
||||
void* CGraphicDib::GetPointer()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<void *>();
|
||||
return m_pvBuf;
|
||||
}
|
||||
|
||||
auto CGraphicDib::GetDCHandle() -> HDC
|
||||
int CGraphicDib::GetWidth()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<HDC>();
|
||||
return m_width;
|
||||
}
|
||||
|
||||
auto CGraphicDib::Initialize() -> void
|
||||
int CGraphicDib::GetHeight()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return m_height;
|
||||
}
|
||||
|
||||
@@ -146,3 +146,17 @@ auto CGraphicDevice::CreateDoublePNTStreamVertexShader() -> DWORD
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<DWORD>();
|
||||
}
|
||||
|
||||
// 40250 GrpDevice.cpp:298-306; s_MaxTextureWidth/Height come from D3DCAPS8 in __CheckD3DCaps.
|
||||
// PORT: 4096 is the texture size every Godot renderer on the target GPUs supports.
|
||||
static DWORD s_MaxTextureWidth = 4096, s_MaxTextureHeight = 4096;
|
||||
|
||||
DWORD GetMaxTextureWidth()
|
||||
{
|
||||
return s_MaxTextureWidth;
|
||||
}
|
||||
|
||||
DWORD GetMaxTextureHeight()
|
||||
{
|
||||
return s_MaxTextureHeight;
|
||||
}
|
||||
|
||||
@@ -1,90 +1,316 @@
|
||||
// Platform skeleton for EterLib/GrpFontTexture.h (40250 EterLib/GrpFontTexture.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
// 40250 EterLib/GrpFontTexture.cpp, verbatim over the GDI emulation in platform/Win32Gdi.cpp. The
|
||||
// A4R4G4B4 page textures are CGraphicImageTexture memory textures the Godot canvas samples.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/GrpFontTexture.h"
|
||||
#include "EterLib/GrpText.h"
|
||||
#include "EterBase/Stl.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
#include "EterLib/Util.h"
|
||||
|
||||
CGraphicFontTexture::CGraphicFontTexture()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
Initialize();
|
||||
}
|
||||
|
||||
CGraphicFontTexture::~CGraphicFontTexture()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
Destroy();
|
||||
}
|
||||
|
||||
auto CGraphicFontTexture::Destroy() -> void
|
||||
void CGraphicFontTexture::Initialize()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
CGraphicTexture::Initialize();
|
||||
m_hFontOld = NULL;
|
||||
m_hFont = NULL;
|
||||
m_isDirty = false;
|
||||
m_bItalic = false;
|
||||
}
|
||||
|
||||
auto CGraphicFontTexture::Create(const char *, int, bool) -> bool
|
||||
bool CGraphicFontTexture::IsEmpty() const
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
return m_fontMap.size() == 0;
|
||||
}
|
||||
|
||||
auto CGraphicFontTexture::CreateDeviceObjects() -> bool
|
||||
void CGraphicFontTexture::Destroy()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
HDC hDC = m_dib.GetDCHandle();
|
||||
if (hDC)
|
||||
SelectObject(hDC, m_hFontOld);
|
||||
|
||||
m_dib.Destroy();
|
||||
|
||||
m_lpd3dTexture = NULL;
|
||||
CGraphicTexture::Destroy();
|
||||
stl_wipe(m_pFontTextureVector);
|
||||
m_charInfoMap.clear();
|
||||
|
||||
if (m_fontMap.size())
|
||||
{
|
||||
TFontMap::iterator i = m_fontMap.begin();
|
||||
|
||||
while(i != m_fontMap.end())
|
||||
{
|
||||
DeleteObject((HGDIOBJ)i->second);
|
||||
++i;
|
||||
}
|
||||
|
||||
m_fontMap.clear();
|
||||
}
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
auto CGraphicFontTexture::DestroyDeviceObjects() -> void
|
||||
bool CGraphicFontTexture::CreateDeviceObjects()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto CGraphicFontTexture::CheckTextureIndex(DWORD) -> bool
|
||||
void CGraphicFontTexture::DestroyDeviceObjects()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
}
|
||||
|
||||
auto CGraphicFontTexture::SelectTexture(DWORD) -> void
|
||||
bool CGraphicFontTexture::Create(const char* c_szFontName, int fontSize, bool bItalic)
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
Destroy();
|
||||
|
||||
strncpy(m_fontName, c_szFontName, sizeof(m_fontName)-1);
|
||||
m_fontSize = fontSize;
|
||||
m_bItalic = bItalic;
|
||||
|
||||
m_x = 0;
|
||||
m_y = 0;
|
||||
m_step = 0;
|
||||
|
||||
DWORD width = 256,height = 256;
|
||||
if (GetMaxTextureWidth() > 512)
|
||||
width = 512;
|
||||
if (GetMaxTextureHeight() > 512)
|
||||
height = 512;
|
||||
|
||||
if (!m_dib.Create(ms_hDC, width, height))
|
||||
return false;
|
||||
|
||||
HDC hDC = m_dib.GetDCHandle();
|
||||
|
||||
m_hFont = GetFont(GetDefaultCodePage());
|
||||
|
||||
m_hFontOld=(HFONT)SelectObject(hDC, m_hFont);
|
||||
SetTextColor(hDC, RGB(255, 255, 255));
|
||||
SetBkColor(hDC, 0);
|
||||
|
||||
if (!AppendTexture())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto CGraphicFontTexture::UpdateTexture() -> bool
|
||||
|
||||
|
||||
HFONT CGraphicFontTexture::GetFont(WORD codePage)
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
HFONT hFont = NULL;
|
||||
TFontMap::iterator i = m_fontMap.find(codePage);
|
||||
|
||||
if(i != m_fontMap.end())
|
||||
{
|
||||
hFont = i->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOGFONT logFont;
|
||||
|
||||
memset(&logFont, 0, sizeof(LOGFONT));
|
||||
|
||||
logFont.lfHeight = m_fontSize;
|
||||
logFont.lfEscapement = 0;
|
||||
logFont.lfOrientation = 0;
|
||||
logFont.lfWeight = FW_NORMAL;
|
||||
logFont.lfItalic = (BYTE) m_bItalic;
|
||||
logFont.lfUnderline = FALSE;
|
||||
logFont.lfStrikeOut = FALSE;
|
||||
logFont.lfCharSet = GetCharsetFromCodePage(codePage);
|
||||
logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
|
||||
logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
|
||||
logFont.lfQuality = ANTIALIASED_QUALITY;
|
||||
logFont.lfPitchAndFamily = DEFAULT_PITCH;
|
||||
//Tracenf("font: %s", GetFontFaceFromCodePage(codePage));
|
||||
strcpy(logFont.lfFaceName, m_fontName); //GetFontFaceFromCodePage(codePage));
|
||||
//strcpy(logFont.lfFaceName, GetFontFaceFromCodePage(codePage));
|
||||
|
||||
hFont = CreateFontIndirect(&logFont);
|
||||
|
||||
m_fontMap.insert(TFontMap::value_type(codePage, hFont));
|
||||
}
|
||||
|
||||
return hFont;
|
||||
}
|
||||
|
||||
auto CGraphicFontTexture::GetCharacterInfomation(WORD, wchar_t) -> TCharacterInfomation *
|
||||
bool CGraphicFontTexture::AppendTexture()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<TCharacterInfomation *>();
|
||||
CGraphicImageTexture * pNewTexture = new CGraphicImageTexture;
|
||||
|
||||
if (!pNewTexture->Create(m_dib.GetWidth(), m_dib.GetHeight(), D3DFMT_A4R4G4B4))
|
||||
{
|
||||
delete pNewTexture;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_pFontTextureVector.push_back(pNewTexture);
|
||||
return true;
|
||||
}
|
||||
|
||||
auto CGraphicFontTexture::UpdateCharacterInfomation(TCharacterKey) -> TCharacterInfomation *
|
||||
bool CGraphicFontTexture::UpdateTexture()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<TCharacterInfomation *>();
|
||||
if(!m_isDirty)
|
||||
return true;
|
||||
|
||||
m_isDirty = false;
|
||||
|
||||
CGraphicImageTexture * pFontTexture = m_pFontTextureVector.back();
|
||||
|
||||
if (!pFontTexture)
|
||||
return false;
|
||||
|
||||
WORD* pwDst;
|
||||
int pitch;
|
||||
|
||||
if (!pFontTexture->Lock(&pitch, (void**)&pwDst))
|
||||
return false;
|
||||
|
||||
pitch /= 2;
|
||||
|
||||
int width = m_dib.GetWidth();
|
||||
int height = m_dib.GetHeight();
|
||||
|
||||
DWORD * pdwSrc = (DWORD*)m_dib.GetPointer();
|
||||
|
||||
for (int y = 0; y < height; ++y, pwDst += pitch, pdwSrc += width)
|
||||
for (int x = 0; x < width; ++x)
|
||||
pwDst[x]=pdwSrc[x];
|
||||
|
||||
pFontTexture->Unlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto CGraphicFontTexture::IsEmpty() const -> bool
|
||||
CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::GetCharacterInfomation(WORD codePage, wchar_t keyValue)
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
TCharacterKey code(codePage, keyValue);
|
||||
|
||||
TCharacterInfomationMap::iterator f = m_charInfoMap.find(code);
|
||||
|
||||
if (m_charInfoMap.end() == f)
|
||||
{
|
||||
return UpdateCharacterInfomation(code);
|
||||
}
|
||||
else
|
||||
{
|
||||
return &f->second;
|
||||
}
|
||||
}
|
||||
|
||||
auto CGraphicFontTexture::Initialize() -> void
|
||||
CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::UpdateCharacterInfomation(TCharacterKey code)
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
HDC hDC = m_dib.GetDCHandle();
|
||||
SelectObject(hDC, GetFont(code.first));
|
||||
|
||||
wchar_t keyValue = code.second;
|
||||
|
||||
if (keyValue == 0x08)
|
||||
keyValue = L' '; // 탭은 공백으로 바꾼다 (아랍 출력시 탭 사용: NAME:\tTEXT -> TEXT\t:NAME 로 전환됨 )
|
||||
|
||||
ABCFLOAT stABC;
|
||||
SIZE size;
|
||||
|
||||
if (!GetTextExtentPoint32W(hDC, &keyValue, 1, &size) || !GetCharABCWidthsFloatW(hDC, keyValue, keyValue, &stABC))
|
||||
return NULL;
|
||||
|
||||
size.cx = stABC.abcfB;
|
||||
if( stABC.abcfA > 0.0f )
|
||||
size.cx += ceilf(stABC.abcfA);
|
||||
if( stABC.abcfC > 0.0f )
|
||||
size.cx += ceilf(stABC.abcfC);
|
||||
size.cx++;
|
||||
|
||||
LONG lAdvance = ceilf( stABC.abcfA + stABC.abcfB + stABC.abcfC );
|
||||
|
||||
int width = m_dib.GetWidth();
|
||||
int height = m_dib.GetHeight();
|
||||
|
||||
if (m_x + size.cx >= (width - 1))
|
||||
{
|
||||
m_y += (m_step + 1);
|
||||
m_step = 0;
|
||||
m_x = 0;
|
||||
|
||||
if (m_y + size.cy >= (height - 1))
|
||||
{
|
||||
if (!UpdateTexture())
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!AppendTexture())
|
||||
return NULL;
|
||||
|
||||
m_y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
TextOutW(hDC, m_x, m_y, &keyValue, 1);
|
||||
|
||||
int nChrX;
|
||||
int nChrY;
|
||||
int nChrWidth = size.cx;
|
||||
int nChrHeight = size.cy;
|
||||
int nDIBWidth = m_dib.GetWidth();
|
||||
|
||||
|
||||
DWORD*pdwDIBData=(DWORD*)m_dib.GetPointer();
|
||||
DWORD*pdwDIBBase=pdwDIBData+nDIBWidth*m_y+m_x;
|
||||
DWORD*pdwDIBRow;
|
||||
|
||||
pdwDIBRow=pdwDIBBase;
|
||||
for (nChrY=0; nChrY<nChrHeight; ++nChrY, pdwDIBRow+=nDIBWidth)
|
||||
{
|
||||
for (nChrX=0; nChrX<nChrWidth; ++nChrX)
|
||||
{
|
||||
pdwDIBRow[nChrX]=(pdwDIBRow[nChrX]&0xff) ? 0xffff : 0;
|
||||
}
|
||||
}
|
||||
|
||||
float rhwidth = 1.0f / float(width);
|
||||
float rhheight = 1.0f / float(height);
|
||||
|
||||
TCharacterInfomation& rNewCharInfo = m_charInfoMap[code];
|
||||
|
||||
rNewCharInfo.index = static_cast<short>(m_pFontTextureVector.size() - 1);
|
||||
rNewCharInfo.width = size.cx;
|
||||
rNewCharInfo.height = size.cy;
|
||||
rNewCharInfo.left = float(m_x) * rhwidth;
|
||||
rNewCharInfo.top = float(m_y) * rhheight;
|
||||
rNewCharInfo.right = float(m_x+size.cx) * rhwidth;
|
||||
rNewCharInfo.bottom = float(m_y+size.cy) * rhheight;
|
||||
rNewCharInfo.advance = (float) lAdvance;
|
||||
|
||||
m_x += size.cx;
|
||||
|
||||
if (m_step < size.cy)
|
||||
m_step = size.cy;
|
||||
|
||||
m_isDirty = true;
|
||||
|
||||
return &rNewCharInfo;
|
||||
}
|
||||
|
||||
auto CGraphicFontTexture::AppendTexture() -> bool
|
||||
bool CGraphicFontTexture::CheckTextureIndex(DWORD dwTexture)
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
if (dwTexture >= m_pFontTextureVector.size())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto CGraphicFontTexture::GetFont(WORD) -> HFONT
|
||||
void CGraphicFontTexture::SelectTexture(DWORD dwTexture)
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<HFONT>();
|
||||
assert(CheckTextureIndex(dwTexture));
|
||||
m_lpd3dTexture = m_pFontTextureVector[dwTexture]->GetD3DTexture();
|
||||
}
|
||||
|
||||
@@ -4,7 +4,11 @@
|
||||
#include "EterLib/GrpImageTexture.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
#include "UIRenderCommands.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
|
||||
namespace {
|
||||
unsigned little16(const unsigned char* data) { return data[0] | (unsigned(data[1]) << 8); }
|
||||
@@ -36,33 +40,150 @@ bool jpeg_size(const unsigned char* data, unsigned size, unsigned* width, unsign
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// PORT: the IDirect3DTexture8 behind a CGraphicImageTexture::Create texture. Lock hands out the
|
||||
// pixels in the texture's own format (40250 writes A4R4G4B4 words into the font pages); Unlock
|
||||
// converts them to ARGB32 for the canvas and bumps the revision in the texture's name.
|
||||
struct MemoryTexture : IDirect3DTexture8
|
||||
{
|
||||
unsigned id = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
D3DFORMAT format = D3DFMT_UNKNOWN;
|
||||
std::vector<unsigned char> native;
|
||||
std::vector<std::uint32_t> argb;
|
||||
std::uint32_t revision = 0;
|
||||
};
|
||||
|
||||
std::mutex g_memory_mutex;
|
||||
std::map<unsigned, MemoryTexture*> g_memory;
|
||||
unsigned g_memory_next_id = 1;
|
||||
|
||||
int bytes_per_pixel(D3DFORMAT format)
|
||||
{
|
||||
switch (format) {
|
||||
case D3DFMT_A4R4G4B4: case D3DFMT_R5G6B5: case D3DFMT_A1R5G5B5: case D3DFMT_X1R5G5B5: case D3DFMT_X4R4G4B4:
|
||||
return 2;
|
||||
default:
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
std::uint32_t expand(unsigned value, int bits)
|
||||
{
|
||||
return bits == 4 ? value * 0x11 : bits == 5 ? (value << 3) | (value >> 2) : bits == 6 ? (value << 2) | (value >> 4) : value;
|
||||
}
|
||||
|
||||
std::uint32_t native_to_argb(D3DFORMAT format, const unsigned char* p)
|
||||
{
|
||||
const unsigned w = p[0] | (unsigned(p[1]) << 8);
|
||||
switch (format) {
|
||||
case D3DFMT_A4R4G4B4:
|
||||
return (expand(w >> 12, 4) << 24) | (expand((w >> 8) & 15, 4) << 16) | (expand((w >> 4) & 15, 4) << 8) | expand(w & 15, 4);
|
||||
case D3DFMT_X4R4G4B4:
|
||||
return 0xFF000000u | (expand((w >> 8) & 15, 4) << 16) | (expand((w >> 4) & 15, 4) << 8) | expand(w & 15, 4);
|
||||
case D3DFMT_R5G6B5:
|
||||
return 0xFF000000u | (expand(w >> 11, 5) << 16) | (expand((w >> 5) & 63, 6) << 8) | expand(w & 31, 5);
|
||||
case D3DFMT_A1R5G5B5: case D3DFMT_X1R5G5B5:
|
||||
return ((format == D3DFMT_X1R5G5B5 || (w & 0x8000)) ? 0xFF000000u : 0) | (expand((w >> 10) & 31, 5) << 16) |
|
||||
(expand((w >> 5) & 31, 5) << 8) | expand(w & 31, 5);
|
||||
case D3DFMT_X8R8G8B8:
|
||||
return 0xFF000000u | (p[2] << 16) | (p[1] << 8) | p[0];
|
||||
default:
|
||||
return (std::uint32_t(p[3]) << 24) | (p[2] << 16) | (p[1] << 8) | p[0];
|
||||
}
|
||||
}
|
||||
|
||||
MemoryTexture* live_memory_texture(const IDirect3DTexture8* texture)
|
||||
{
|
||||
for (const auto& entry : g_memory)
|
||||
if (entry.second == texture) return entry.second;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void UIRenderReleaseMemoryTexture(IDirect3DTexture8* texture)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_memory_mutex);
|
||||
if (MemoryTexture* memory = live_memory_texture(texture)) {
|
||||
g_memory.erase(memory->id);
|
||||
delete memory;
|
||||
}
|
||||
}
|
||||
|
||||
bool UIRenderMemoryTexture(const std::string& name, UIMemoryTexture* out)
|
||||
{
|
||||
if (name.compare(0, 4, "mem:") != 0 || !out) return false;
|
||||
const unsigned id = unsigned(std::strtoul(name.c_str() + 4, nullptr, 10));
|
||||
std::lock_guard<std::mutex> lock(g_memory_mutex);
|
||||
auto it = g_memory.find(id);
|
||||
if (it == g_memory.end()) return false;
|
||||
out->width = it->second->width;
|
||||
out->height = it->second->height;
|
||||
out->revision = it->second->revision;
|
||||
out->argb = it->second->argb;
|
||||
return true;
|
||||
}
|
||||
|
||||
CGraphicImageTexture::CGraphicImageTexture()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
Initialize();
|
||||
}
|
||||
|
||||
CGraphicImageTexture::~CGraphicImageTexture()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
Destroy();
|
||||
}
|
||||
|
||||
auto CGraphicImageTexture::Destroy() -> void
|
||||
{
|
||||
CGraphicTexture::Destroy();
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
auto CGraphicImageTexture::Create(UINT, UINT, D3DFORMAT, DWORD) -> bool
|
||||
auto CGraphicImageTexture::Create(UINT width, UINT height, D3DFORMAT d3dFmt, DWORD dwFilter) -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
Destroy();
|
||||
|
||||
m_width = width;
|
||||
m_height = height;
|
||||
m_d3dFmt = d3dFmt;
|
||||
m_dwFilter = dwFilter;
|
||||
|
||||
return CreateDeviceObjects();
|
||||
}
|
||||
|
||||
auto CGraphicImageTexture::CreateDeviceObjects() -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
if (m_stFileName.empty())
|
||||
{
|
||||
// PORT: ms_lpd3dDevice->CreateTexture(m_width, m_height, 1, 0, m_d3dFmt, D3DPOOL_MANAGED) becomes a
|
||||
// memory texture the canvas fetches by name.
|
||||
if (m_width <= 0 || m_height <= 0)
|
||||
return false;
|
||||
auto* memory = new MemoryTexture();
|
||||
memory->width = m_width;
|
||||
memory->height = m_height;
|
||||
memory->format = m_d3dFmt;
|
||||
memory->native.assign(size_t(m_width) * size_t(m_height) * size_t(bytes_per_pixel(m_d3dFmt)), 0);
|
||||
memory->argb.assign(size_t(m_width) * size_t(m_height), 0);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_memory_mutex);
|
||||
memory->id = g_memory_next_id++;
|
||||
g_memory[memory->id] = memory;
|
||||
}
|
||||
m_lpd3dTexture = memory;
|
||||
}
|
||||
else
|
||||
{
|
||||
// PORT: file textures are decoded by the canvas from the pack path; only their size is read here
|
||||
// (CreateFromMemoryFile), so there is no device object to recreate.
|
||||
return !m_bEmpty;
|
||||
}
|
||||
|
||||
m_bEmpty = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
auto CGraphicImageTexture::CreateFromTexturePointer(const CGraphicTexture *source) -> void
|
||||
@@ -117,9 +238,16 @@ auto CGraphicImageTexture::SetFileName(const char *name) -> void
|
||||
}
|
||||
|
||||
// PORT: where 40250 binds pTexture->GetD3DTexture(), the canvas gets the pack path of the file the
|
||||
// texture was decoded from (m_stFileName is protected, hence the derived accessor).
|
||||
// texture was decoded from (m_stFileName is protected, hence the derived accessor), or the name of the
|
||||
// memory texture bound to it (a CGraphicFontTexture after SelectTexture).
|
||||
std::string UIRenderTextureName(const CGraphicTexture* texture)
|
||||
{
|
||||
if (!texture) return {};
|
||||
if (texture->GetD3DTexture()) {
|
||||
std::lock_guard<std::mutex> lock(g_memory_mutex);
|
||||
if (const MemoryTexture* memory = live_memory_texture(texture->GetD3DTexture()))
|
||||
return "mem:" + std::to_string(memory->id) + "@" + std::to_string(memory->revision);
|
||||
}
|
||||
struct Access : CGraphicImageTexture {
|
||||
static const std::string& name(const CGraphicImageTexture* t) { return t->*(&Access::m_stFileName); }
|
||||
};
|
||||
@@ -127,18 +255,38 @@ std::string UIRenderTextureName(const CGraphicTexture* texture)
|
||||
return image ? Access::name(image) : std::string();
|
||||
}
|
||||
|
||||
auto CGraphicImageTexture::Lock(int *, void **, int) -> bool
|
||||
// PORT: LockRect/UnlockRect on the memory texture (one level; the lock is on the whole surface).
|
||||
auto CGraphicImageTexture::Lock(int* pRetPitch, void** ppRetPixels, int) -> bool
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
std::lock_guard<std::mutex> lock(g_memory_mutex);
|
||||
MemoryTexture* memory = live_memory_texture(m_lpd3dTexture);
|
||||
if (!memory)
|
||||
return false;
|
||||
|
||||
*pRetPitch = memory->width * bytes_per_pixel(memory->format);
|
||||
*ppRetPixels = memory->native.data();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto CGraphicImageTexture::Unlock(int) -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
std::lock_guard<std::mutex> lock(g_memory_mutex);
|
||||
MemoryTexture* memory = live_memory_texture(m_lpd3dTexture);
|
||||
assert(memory != NULL);
|
||||
if (!memory)
|
||||
return;
|
||||
const int bpp = bytes_per_pixel(memory->format);
|
||||
for (size_t i = 0; i < memory->argb.size(); ++i)
|
||||
memory->argb[i] = native_to_argb(memory->format, memory->native.data() + i * size_t(bpp));
|
||||
++memory->revision;
|
||||
}
|
||||
|
||||
auto CGraphicImageTexture::Initialize() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
CGraphicTexture::Initialize();
|
||||
|
||||
m_stFileName = "";
|
||||
|
||||
m_d3dFmt=D3DFMT_UNKNOWN;
|
||||
m_dwFilter=0;
|
||||
}
|
||||
|
||||
@@ -1,62 +1,102 @@
|
||||
// Platform skeleton for EterLib/GrpText.h (40250 EterLib/GrpText.cpp), generated by platform_stub.py.
|
||||
// Every MT_PLATFORM_STUB() body is unimplemented: replace it with the platform implementation.
|
||||
// 40250 EterLib/GrpText.cpp, verbatim.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterBase/Utils.h"
|
||||
#include "EterLib/GrpText.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
|
||||
auto CGraphicText::Type() -> TType
|
||||
CGraphicText::CGraphicText(const char* c_szFileName) : CResource(c_szFileName)
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<TType>();
|
||||
}
|
||||
|
||||
CGraphicText::CGraphicText(const char * c_szFileName) : CResource(c_szFileName) // initializers as in 40250
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
CGraphicText::~CGraphicText()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CGraphicText::CreateDeviceObjects() -> bool
|
||||
bool CGraphicText::CreateDeviceObjects()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
return m_fontTexture.CreateDeviceObjects();
|
||||
}
|
||||
|
||||
auto CGraphicText::DestroyDeviceObjects() -> void
|
||||
void CGraphicText::DestroyDeviceObjects()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
m_fontTexture.DestroyDeviceObjects();
|
||||
}
|
||||
|
||||
auto CGraphicText::GetFontTexturePointer() -> CGraphicFontTexture *
|
||||
CGraphicFontTexture* CGraphicText::GetFontTexturePointer()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<CGraphicFontTexture *>();
|
||||
return &m_fontTexture;
|
||||
}
|
||||
|
||||
auto CGraphicText::OnLoad(int, const void *) -> bool
|
||||
CGraphicText::TType CGraphicText::Type()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
static TType s_type = StringToType("CGraphicText");
|
||||
return s_type;
|
||||
}
|
||||
|
||||
auto CGraphicText::OnClear() -> void
|
||||
bool CGraphicText::OnLoad(int /*iSize*/, const void* /*c_pvBuf*/)
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
static char strName[32];
|
||||
int size;
|
||||
bool bItalic = false;
|
||||
|
||||
// format
|
||||
// 굴림.fnt "굴림" 폰트 기본 사이즈 12 로 로딩
|
||||
// 굴림:18.fnt "굴림" 폰트 사이즈 18 로 로딩
|
||||
// 굴림:14i.fnt "굴림" 폰트 사이즈 14 & 이탤릭으로 로딩
|
||||
const char * p = strrchr(GetFileName(), ':');
|
||||
|
||||
if (p)
|
||||
{
|
||||
strncpy(strName, GetFileName(), MIN(31, p - GetFileName()));
|
||||
++p;
|
||||
|
||||
static char num[8];
|
||||
|
||||
int i = 0;
|
||||
while (*p && isdigit(*p))
|
||||
{
|
||||
num[i++] = *(p++);
|
||||
}
|
||||
|
||||
num[i] = '\0';
|
||||
if(*p == 'i')
|
||||
bItalic = true;
|
||||
size = atoi(num);
|
||||
}
|
||||
else
|
||||
{
|
||||
p = strrchr(GetFileName(), '.');
|
||||
|
||||
if (!p)
|
||||
{
|
||||
assert(!"CGraphicText::OnLoadFromFile there is no extension (ie: .fnt)");
|
||||
strName[0] = '\0';
|
||||
}
|
||||
else
|
||||
strncpy(strName, GetFileName(), MIN(31, p - GetFileName()));
|
||||
|
||||
size = 12;
|
||||
}
|
||||
|
||||
if (!m_fontTexture.Create(strName, size, bItalic))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
auto CGraphicText::OnIsEmpty() const -> bool
|
||||
void CGraphicText::OnClear()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
m_fontTexture.Destroy();
|
||||
}
|
||||
|
||||
auto CGraphicText::OnIsType(TType) -> bool
|
||||
bool CGraphicText::OnIsEmpty() const
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<bool>();
|
||||
return m_fontTexture.IsEmpty();
|
||||
}
|
||||
|
||||
bool CGraphicText::OnIsType(TType type)
|
||||
{
|
||||
if (CGraphicText::Type() == type)
|
||||
return true;
|
||||
|
||||
return CResource::OnIsType(type);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@
|
||||
#include "EterLib/GrpTexture.h"
|
||||
|
||||
#include "../PlatformStub.h"
|
||||
#include "UIRenderCommands.h"
|
||||
|
||||
auto CGraphicTexture::IsEmpty() const -> bool
|
||||
{
|
||||
@@ -27,34 +28,37 @@ auto CGraphicTexture::SetTextureStage(int) const -> void
|
||||
|
||||
auto CGraphicTexture::GetD3DTexture() const -> LPDIRECT3DTEXTURE8
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
return mt_platform_stub_return<LPDIRECT3DTEXTURE8>();
|
||||
return m_lpd3dTexture;
|
||||
}
|
||||
|
||||
// PORT: safe_release(m_lpd3dTexture); the only textures this platform creates are memory textures.
|
||||
auto CGraphicTexture::DestroyDeviceObjects() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
if (m_lpd3dTexture)
|
||||
UIRenderReleaseMemoryTexture(m_lpd3dTexture);
|
||||
m_lpd3dTexture = NULL;
|
||||
}
|
||||
|
||||
CGraphicTexture::CGraphicTexture()
|
||||
{
|
||||
m_bEmpty = true;
|
||||
m_width = m_height = 0;
|
||||
m_lpd3dTexture = nullptr;
|
||||
Initialize();
|
||||
}
|
||||
|
||||
CGraphicTexture::~CGraphicTexture()
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
}
|
||||
|
||||
auto CGraphicTexture::Destroy() -> void
|
||||
{
|
||||
m_bEmpty = true;
|
||||
m_width = m_height = 0;
|
||||
DestroyDeviceObjects();
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
auto CGraphicTexture::Initialize() -> void
|
||||
{
|
||||
MT_PLATFORM_STUB();
|
||||
m_lpd3dTexture = NULL;
|
||||
m_width = 0;
|
||||
m_height = 0;
|
||||
m_bEmpty = true;
|
||||
}
|
||||
|
||||
@@ -30,8 +30,22 @@ std::uint32_t UIRenderColor(const Color& color) {
|
||||
}
|
||||
|
||||
class CGraphicTexture;
|
||||
// A file-backed texture is named by its pack path; a memory texture (CGraphicImageTexture::Create,
|
||||
// filled through Lock/Unlock: the CGraphicFontTexture glyph pages) by "mem:<id>@<revision>", the
|
||||
// revision counting Unlocks so the canvas knows when to fetch the pixels again.
|
||||
std::string UIRenderTextureName(const CGraphicTexture* texture);
|
||||
|
||||
// The pixels of memory texture "mem:<id>" (the part of the name before '@'), as 0xAARRGGBB.
|
||||
struct UIMemoryTexture {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
std::uint32_t revision = 0;
|
||||
std::vector<std::uint32_t> argb;
|
||||
};
|
||||
bool UIRenderMemoryTexture(const std::string& name, UIMemoryTexture* out);
|
||||
struct IDirect3DTexture8;
|
||||
void UIRenderReleaseMemoryTexture(IDirect3DTexture8* texture);
|
||||
|
||||
// A textured quad from 40250's TPDTVertex[4] (TL, TR, BL, BR) positions and texture coordinates.
|
||||
// PORT: D3D8 puts pixel centres on integers, which is why 40250 subtracts 0.5 from every vertex; the
|
||||
// Godot canvas puts them on .5, so the quad is shifted back by +0.5 here.
|
||||
|
||||
@@ -1,18 +1,109 @@
|
||||
// 40250 EterLib/Util.cpp 的“默认代码页”那一小块。
|
||||
// 40250 EterLib/Util.cpp:140-297, the default code page and font face.
|
||||
//
|
||||
// Util.cpp 本身是逻辑单元(LoadTextData / base64 / TokenTo*),但代码页这几个函数和它们旁边的
|
||||
// GetFontFaceFromCodePage 是连在一起的,后者靠 GDI 的 EnumFontFamiliesEx 枚举字体,win32 shim 里没有,
|
||||
// 真正的实现要等 2V0-f 的文字渲染。所以整份 Util.cpp 的移植留到那时,这里只放现在真正被调用到的
|
||||
// GetDefaultCodePage:EterPythonLib/PythonWindow.cpp 的 CTextLine::OnChangePosition 用它判断 CP_ARABIC。
|
||||
//
|
||||
// 默认值和 40250 一致:gs_codePage = 0,也就是“还没有 SetDefaultCodePage”。40250 在
|
||||
// PythonApplication 里根据 locale 调 SetDefaultCodePage(),那条路径同样等 2V0-d。
|
||||
// The rest of Util.cpp (LoadTextData / base64 / TokenTo*) is logic and not here. The 9x face table has
|
||||
// CP949/Shift-JIS/GBK/Big5 literals, so this block is copied by hand instead of port_copy.
|
||||
#include "EterLib/StdAfx.h"
|
||||
#include "EterLib/Util.h"
|
||||
#include "EterLocale/CodePageId.h"
|
||||
|
||||
static DWORD gs_codePage = 0;
|
||||
static std::string gs_fontFace="";
|
||||
static DWORD gs_codePage=0;
|
||||
|
||||
int GetCharsetFromCodePage(WORD codePage)
|
||||
{
|
||||
switch( codePage )
|
||||
{
|
||||
case CP_932:
|
||||
return SHIFTJIS_CHARSET;
|
||||
case CP_949:
|
||||
return HANGUL_CHARSET;
|
||||
case CP_936:
|
||||
return GB2312_CHARSET;
|
||||
case CP_950:
|
||||
return CHINESEBIG5_CHARSET;
|
||||
case CP_1253:
|
||||
return GREEK_CHARSET;
|
||||
case CP_1254:
|
||||
return TURKISH_CHARSET;
|
||||
case CP_1255:
|
||||
return HEBREW_CHARSET;
|
||||
case CP_1256:
|
||||
return ARABIC_CHARSET;
|
||||
case CP_1257:
|
||||
return BALTIC_CHARSET;
|
||||
case CP_1258:
|
||||
return VIETNAMESE_CHARSET;
|
||||
case CP_874:
|
||||
return THAI_CHARSET;
|
||||
case CP_1250:
|
||||
return EASTEUROPE_CHARSET;
|
||||
case CP_1251:
|
||||
return RUSSIAN_CHARSET;
|
||||
default:
|
||||
return DEFAULT_CHARSET;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char* GetFontFaceFromCodePageNT(WORD codePage)
|
||||
{
|
||||
switch( codePage )
|
||||
{
|
||||
case CP_932:
|
||||
return "MS PGothic";
|
||||
case CP_949:
|
||||
return "GulimChe";
|
||||
case CP_936:
|
||||
return "SimSun";
|
||||
case CP_950:
|
||||
return "MingLiU";
|
||||
case CP_874:
|
||||
return "Tahoma";
|
||||
case CP_1252:
|
||||
return "Arial";
|
||||
case CP_1256:
|
||||
return "Tahoma";
|
||||
case CP_1258:
|
||||
return "Tahoma";
|
||||
case CP_65001:
|
||||
return "Arial";
|
||||
default:
|
||||
return "Arial";
|
||||
}
|
||||
}
|
||||
|
||||
DWORD GetDefaultCodePage()
|
||||
{
|
||||
return gs_codePage;
|
||||
}
|
||||
|
||||
const char * GetDefaultFontFace()
|
||||
{
|
||||
return gs_fontFace.c_str();
|
||||
}
|
||||
|
||||
// PORT: 40250 asks EnumFontFamiliesEx for the 9x (native-script) face name, then the NT one. The native
|
||||
// names never match a font file here; CreateFontIndirect (platform/Win32Gdi.cpp) substitutes a missing
|
||||
// face, so the NT name is the answer.
|
||||
const char* GetFontFaceFromCodePage(WORD codePage)
|
||||
{
|
||||
return GetFontFaceFromCodePageNT(codePage);
|
||||
}
|
||||
|
||||
void SetDefaultFontFace(const char* fontFace)
|
||||
{
|
||||
gs_fontFace=fontFace;
|
||||
}
|
||||
|
||||
bool SetDefaultCodePage(DWORD codePage)
|
||||
{
|
||||
gs_codePage=codePage;
|
||||
|
||||
std::string fontFace=GetFontFaceFromCodePage(codePage);
|
||||
if (fontFace.empty())
|
||||
return false;
|
||||
|
||||
SetDefaultFontFace(fontFace.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "UserInterface/StdAfx.h" // initpack and the rest of the module initializer list
|
||||
#include "EterPythonLib/StdAfx.h"
|
||||
#include "EterBase/Timer.h"
|
||||
#include "EterLib/Util.h"
|
||||
#include "PythonBoot.h"
|
||||
#include "PythonHost.h"
|
||||
#include "../EterLib/UIRenderCommands.h"
|
||||
@@ -176,6 +177,10 @@ bool Start(const char* stdlib_path, std::string* error)
|
||||
if (g_launcher)
|
||||
return true;
|
||||
|
||||
// 40250 Main (UserInterface.cpp:410): if (LocaleService_LoadGlobal(hInstance)) SetDefaultCodePage(...).
|
||||
// PORT: the platform LocaleService is already the supplied locale.cfg (en, code page 1252).
|
||||
SetDefaultCodePage(LocaleService_GetCodePage());
|
||||
|
||||
const std::string stdlib = (stdlib_path && *stdlib_path) ? stdlib_path : PythonHost::DefaultStdLibPath();
|
||||
if (stdlib.empty())
|
||||
return fail(error, "no Python standard library path (Metin2Python.stdlib_path / $MT_PYTHON_STDLIB)");
|
||||
|
||||
@@ -2,16 +2,20 @@
|
||||
#include "ScriptLib/Resource.h"
|
||||
#include "EterLib/GrpImage.h"
|
||||
#include "EterLib/GrpSubImage.h"
|
||||
#include "EterLib/GrpText.h"
|
||||
|
||||
namespace {
|
||||
CResource* NewUIImage(const char* name) { return new CGraphicImage(name); }
|
||||
CResource* NewUISubImage(const char* name) { return new CGraphicSubImage(name); }
|
||||
// 40250 UserInterface/PythonApplication.cpp NewText: a "<face>:<size>[i].fnt" name is a CGraphicText.
|
||||
CResource* NewText(const char* name) { return new CGraphicText(name); }
|
||||
}
|
||||
|
||||
CPythonResource::CPythonResource() {
|
||||
for (const char* ext : {"tga", "dds", "png", "jpg", "bmp"})
|
||||
m_resManager.RegisterResourceNewFunctionPointer(ext, NewUIImage);
|
||||
m_resManager.RegisterResourceNewFunctionPointer("sub", NewUISubImage);
|
||||
m_resManager.RegisterResourceNewFunctionPointer("fnt", NewText);
|
||||
}
|
||||
CPythonResource::~CPythonResource() { m_resManager.DestroyDeletingList(); }
|
||||
void CPythonResource::Destroy() { m_resManager.DestroyDeletingList(); m_resManager.Destroy(); }
|
||||
|
||||
@@ -155,7 +155,6 @@ int g_iAccumulationTime = 0;
|
||||
|
||||
bool LoadLocaleData(const char*) { MT_PLATFORM_STUB(); return false; }
|
||||
void SetGuildSymbolPath(const char*) { MT_PLATFORM_STUB(); }
|
||||
void DefaultFont_SetName(const char*) { MT_PLATFORM_STUB(); }
|
||||
void TextTail_SetLivingTime(long) { MT_PLATFORM_STUB(); }
|
||||
namespace {
|
||||
// The supplied 40250 Client/locale.cfg selects "en" (code page 1252).
|
||||
@@ -173,3 +172,72 @@ void LocaleService_ForceSetLocale(const char* name, const char* path) {
|
||||
locale_name = name ? name : "";
|
||||
locale_path = path ? path : "";
|
||||
}
|
||||
|
||||
// 40250 UserInterface/GameType.cpp:9-73 (DEFAULT_FONT), verbatim.
|
||||
// PORT: the default names are CP949 literals in 40250; ui.py sets the locale font (app.SetDefaultFontName)
|
||||
// before anything asks for it.
|
||||
static std::string gs_strDefaultFontName = "굴림체:12.fnt";
|
||||
static std::string gs_strDefaultItalicFontName = "굴림체:12i.fnt";
|
||||
static CResource* gs_pkDefaultFont = NULL;
|
||||
static CResource* gs_pkDefaultItalicFont = NULL;
|
||||
|
||||
static bool gs_isReloadDefaultFont = false;
|
||||
|
||||
void DefaultFont_Startup()
|
||||
{
|
||||
gs_pkDefaultFont = NULL;
|
||||
}
|
||||
|
||||
void DefaultFont_Cleanup()
|
||||
{
|
||||
if (gs_pkDefaultFont)
|
||||
gs_pkDefaultFont->Release();
|
||||
}
|
||||
|
||||
void DefaultFont_SetName(const char * c_szFontName)
|
||||
{
|
||||
gs_strDefaultFontName = c_szFontName;
|
||||
gs_strDefaultFontName += ".fnt";
|
||||
|
||||
gs_strDefaultItalicFontName = c_szFontName;
|
||||
if(strchr(c_szFontName, ':'))
|
||||
gs_strDefaultItalicFontName += "i";
|
||||
gs_strDefaultItalicFontName += ".fnt";
|
||||
|
||||
gs_isReloadDefaultFont = true;
|
||||
}
|
||||
|
||||
bool ReloadDefaultFonts()
|
||||
{
|
||||
CResourceManager& rkResMgr = CResourceManager::Instance();
|
||||
|
||||
gs_isReloadDefaultFont = false;
|
||||
|
||||
CResource* pkNewFont = rkResMgr.GetResourcePointer(gs_strDefaultFontName.c_str());
|
||||
pkNewFont->AddReference();
|
||||
if (gs_pkDefaultFont)
|
||||
gs_pkDefaultFont->Release();
|
||||
gs_pkDefaultFont = pkNewFont;
|
||||
|
||||
CResource* pkNewItalicFont = rkResMgr.GetResourcePointer(gs_strDefaultItalicFontName.c_str());
|
||||
pkNewItalicFont->AddReference();
|
||||
if (gs_pkDefaultItalicFont)
|
||||
gs_pkDefaultItalicFont->Release();
|
||||
gs_pkDefaultItalicFont = pkNewItalicFont;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CResource* DefaultFont_GetResource()
|
||||
{
|
||||
if (!gs_pkDefaultFont || gs_isReloadDefaultFont)
|
||||
ReloadDefaultFonts();
|
||||
return gs_pkDefaultFont;
|
||||
}
|
||||
|
||||
CResource* DefaultItalicFont_GetResource()
|
||||
{
|
||||
if (!gs_pkDefaultItalicFont || gs_isReloadDefaultFont)
|
||||
ReloadDefaultFonts();
|
||||
return gs_pkDefaultItalicFont;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,457 @@
|
||||
// The <wingdi.h> subset the 40250 font path calls (common/shim/win32/wingdi.h), over FreeType.
|
||||
// CGraphicDib / CGraphicFontTexture run unchanged on top of it: a memory DC owns a 32-bit DIB,
|
||||
// CreateFontIndirect maps the LOGFONT onto a TrueType face the way GDI sizes it, and TextOutW
|
||||
// rasterises into the DIB with the DC's text/background colours.
|
||||
//
|
||||
// GDI sizing that the glyph atlas depends on:
|
||||
// - lfHeight > 0 is the cell height (ascent + descent), lfHeight < 0 the em height; the em is
|
||||
// lfHeight * unitsPerEm / (usWinAscent + usWinDescent), rounded to whole pixels.
|
||||
// - tmAscent / tmDescent are usWinAscent / usWinDescent scaled to that em and rounded;
|
||||
// GetTextExtentPoint32W reports tmHeight = tmAscent + tmDescent as the height.
|
||||
// - ANTIALIASED_QUALITY follows the face's gasp table: grayscale only in the ppem ranges it
|
||||
// flags GASP_DO_GRAY, otherwise bilevel (Tahoma at 10/12 ppem is bilevel).
|
||||
// - The glyph origin is (x + A, y + tmAscent - top); advance = A + B + C.
|
||||
// Missing glyphs fall back to the first installed face that has them, like GDI font linking.
|
||||
#include "EterLib/StdAfx.h"
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
#include FT_TRUETYPE_TABLES_H
|
||||
#include FT_GASP_H
|
||||
#include FT_SYNTHESIS_H
|
||||
#include FT_DRIVER_H
|
||||
#include FT_MODULE_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <dirent.h>
|
||||
|
||||
namespace {
|
||||
|
||||
enum class GdiKind { Bitmap, Font };
|
||||
|
||||
struct GdiObject
|
||||
{
|
||||
GdiKind kind;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
struct HBITMAP__ : GdiObject
|
||||
{
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
bool top_down = true;
|
||||
std::vector<uint32_t> bits;
|
||||
};
|
||||
|
||||
struct HFONT__ : GdiObject
|
||||
{
|
||||
FT_Face face = nullptr;
|
||||
std::vector<FT_Face> fallbacks;
|
||||
int ppem = 0;
|
||||
int ascent = 0;
|
||||
int descent = 0;
|
||||
bool italic = false;
|
||||
bool antialiased = false;
|
||||
};
|
||||
|
||||
struct HDC__
|
||||
{
|
||||
HBITMAP bitmap = nullptr;
|
||||
HFONT font = nullptr;
|
||||
COLORREF text = 0;
|
||||
COLORREF bk = 0x00FFFFFF;
|
||||
int bk_mode = OPAQUE;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
std::mutex g_mutex;
|
||||
FT_Library g_library = nullptr;
|
||||
std::unordered_map<std::string, FT_Face> g_faces; // loaded face per file path
|
||||
|
||||
FT_Library library()
|
||||
{
|
||||
if (!g_library) {
|
||||
FT_Init_FreeType(&g_library);
|
||||
// GDI runs the full TrueType bytecode, which is FreeType's v35 interpreter; v40 drops the
|
||||
// horizontal hints and gives different advances at small sizes.
|
||||
FT_UInt version = TT_INTERPRETER_VERSION_35;
|
||||
FT_Property_Set(g_library, "truetype", "interpreter-version", &version);
|
||||
}
|
||||
return g_library;
|
||||
}
|
||||
|
||||
std::string lower(std::string s)
|
||||
{
|
||||
for (char& c : s) c = char(std::tolower(static_cast<unsigned char>(c)));
|
||||
return s;
|
||||
}
|
||||
|
||||
const std::vector<std::string>& font_dirs()
|
||||
{
|
||||
static const std::vector<std::string> dirs = {
|
||||
#if defined(__ANDROID__)
|
||||
"/system/fonts",
|
||||
#else
|
||||
"/System/Library/Fonts/Supplemental",
|
||||
"/System/Library/Fonts",
|
||||
"/Library/Fonts",
|
||||
#endif
|
||||
};
|
||||
return dirs;
|
||||
}
|
||||
|
||||
// Face name -> font file: the file stem matched case-insensitively ("Tahoma" -> Tahoma.ttf).
|
||||
std::string find_font_file(const std::string& face)
|
||||
{
|
||||
const std::string want = lower(face);
|
||||
for (const std::string& dir : font_dirs()) {
|
||||
DIR* d = opendir(dir.c_str());
|
||||
if (!d) continue;
|
||||
std::string hit;
|
||||
while (dirent* e = readdir(d)) {
|
||||
std::string name = e->d_name;
|
||||
const size_t dot = name.rfind('.');
|
||||
if (dot == std::string::npos) continue;
|
||||
const std::string ext = lower(name.substr(dot + 1));
|
||||
if (ext != "ttf" && ext != "ttc" && ext != "otf") continue;
|
||||
if (lower(name.substr(0, dot)) == want) { hit = dir + "/" + name; break; }
|
||||
}
|
||||
closedir(d);
|
||||
if (!hit.empty()) return hit;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
FT_Face open_face(const std::string& path)
|
||||
{
|
||||
auto it = g_faces.find(path);
|
||||
if (it != g_faces.end()) return it->second;
|
||||
FT_Face face = nullptr;
|
||||
if (FT_New_Face(library(), path.c_str(), 0, &face) != 0) face = nullptr;
|
||||
if (face) FT_Select_Charmap(face, FT_ENCODING_UNICODE);
|
||||
g_faces[path] = face;
|
||||
return face;
|
||||
}
|
||||
|
||||
FT_Face open_named(const char* name)
|
||||
{
|
||||
const std::string path = find_font_file(name);
|
||||
return path.empty() ? nullptr : open_face(path);
|
||||
}
|
||||
|
||||
// PORT: the faces GDI would substitute when the requested one is not installed (the 40250 locale
|
||||
// asks for Tahoma / Arial on code page 1252), then the font-linking faces for CJK/Hangul glyphs.
|
||||
const char* const kSubstitutes[] = {"Arial", "Helvetica", "Roboto-Regular", "DroidSans", "NotoSans-Regular"};
|
||||
const char* const kLinked[] = {"Arial Unicode", "AppleSDGothicNeo", "NotoSansCJK-Regular", "NotoSansCJKkr-Regular",
|
||||
"DroidSansFallback"};
|
||||
|
||||
bool gray_at(FT_Face face, int ppem)
|
||||
{
|
||||
const FT_Int flags = FT_Get_Gasp(face, FT_UInt(ppem));
|
||||
if (flags == FT_GASP_NO_TABLE) return true;
|
||||
return (flags & FT_GASP_DO_GRAY) != 0;
|
||||
}
|
||||
|
||||
void set_size(FT_Face face, int ppem)
|
||||
{
|
||||
FT_Set_Pixel_Sizes(face, 0, FT_UInt(ppem));
|
||||
}
|
||||
|
||||
struct Glyph
|
||||
{
|
||||
int left = 0; // A
|
||||
int top = 0; // rows above the baseline
|
||||
int advance = 0; // A + B + C
|
||||
int width = 0; // B
|
||||
int rows = 0;
|
||||
int pitch = 0;
|
||||
bool mono = true;
|
||||
std::vector<unsigned char> bitmap;
|
||||
};
|
||||
|
||||
bool load_glyph(HFONT font, wchar_t ch, bool render, Glyph* out)
|
||||
{
|
||||
FT_Face face = font->face;
|
||||
FT_UInt index = FT_Get_Char_Index(face, FT_ULong(ch));
|
||||
if (index == 0) {
|
||||
for (FT_Face fallback : font->fallbacks) {
|
||||
FT_UInt i = FT_Get_Char_Index(fallback, FT_ULong(ch));
|
||||
if (i) { face = fallback; index = i; break; }
|
||||
}
|
||||
}
|
||||
set_size(face, font->ppem);
|
||||
const bool gray = font->antialiased && gray_at(face, font->ppem);
|
||||
const FT_Int32 target = gray ? FT_LOAD_TARGET_NORMAL : FT_LOAD_TARGET_MONO;
|
||||
if (FT_Load_Glyph(face, index, FT_LOAD_DEFAULT | target) != 0) return false;
|
||||
FT_GlyphSlot slot = face->glyph;
|
||||
if (font->italic) FT_GlyphSlot_Oblique(slot);
|
||||
out->advance = int((slot->advance.x + 32) >> 6);
|
||||
if (slot->format == FT_GLYPH_FORMAT_OUTLINE) {
|
||||
if (FT_Render_Glyph(slot, gray ? FT_RENDER_MODE_NORMAL : FT_RENDER_MODE_MONO) != 0) return false;
|
||||
}
|
||||
const FT_Bitmap& bm = slot->bitmap;
|
||||
out->left = slot->bitmap_left;
|
||||
out->top = slot->bitmap_top;
|
||||
out->width = int(bm.width);
|
||||
out->rows = int(bm.rows);
|
||||
out->mono = bm.pixel_mode == FT_PIXEL_MODE_MONO;
|
||||
if (render && bm.buffer && bm.rows) {
|
||||
out->pitch = std::abs(bm.pitch);
|
||||
out->bitmap.assign(bm.buffer, bm.buffer + size_t(out->pitch) * bm.rows);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
HFONT default_font()
|
||||
{
|
||||
static HFONT font = [] {
|
||||
LOGFONT lf{};
|
||||
lf.lfHeight = 12;
|
||||
std::strcpy(lf.lfFaceName, "Arial");
|
||||
return CreateFontIndirect(&lf);
|
||||
}();
|
||||
return font;
|
||||
}
|
||||
|
||||
HFONT dc_font(HDC hdc)
|
||||
{
|
||||
return hdc->font ? hdc->font : default_font();
|
||||
}
|
||||
|
||||
// COLORREF is 0x00BBGGRR; a 32-bit DIB pixel is 0x00RRGGBB.
|
||||
uint32_t dib_pixel(COLORREF c)
|
||||
{
|
||||
return ((c & 0xFF) << 16) | (c & 0xFF00) | ((c >> 16) & 0xFF);
|
||||
}
|
||||
|
||||
uint32_t* dib_row(HBITMAP bmp, int y)
|
||||
{
|
||||
const int row = bmp->top_down ? y : bmp->height - 1 - y;
|
||||
return bmp->bits.data() + size_t(row) * size_t(bmp->width);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
HDC CreateCompatibleDC(HDC)
|
||||
{
|
||||
return new HDC__();
|
||||
}
|
||||
|
||||
BOOL DeleteDC(HDC hdc)
|
||||
{
|
||||
delete hdc;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
HBITMAP CreateDIBSection(HDC, const BITMAPINFO* pbmi, UINT, void** ppvBits, HANDLE, DWORD)
|
||||
{
|
||||
if (!pbmi || pbmi->bmiHeader.biBitCount != 32 || pbmi->bmiHeader.biCompression != BI_RGB) return nullptr;
|
||||
const LONG w = pbmi->bmiHeader.biWidth;
|
||||
const LONG h = pbmi->bmiHeader.biHeight;
|
||||
if (w <= 0 || h == 0) return nullptr;
|
||||
auto* bmp = new HBITMAP__();
|
||||
bmp->kind = GdiKind::Bitmap;
|
||||
bmp->width = int(w);
|
||||
bmp->height = int(h < 0 ? -h : h);
|
||||
bmp->top_down = h < 0;
|
||||
bmp->bits.assign(size_t(bmp->width) * size_t(bmp->height), 0);
|
||||
if (ppvBits) *ppvBits = bmp->bits.data();
|
||||
return bmp;
|
||||
}
|
||||
|
||||
// The only caller is CGraphicDib::Put onto a window DC, which this platform does not have.
|
||||
int SetDIBitsToDevice(HDC, int, int, DWORD, DWORD h, int, int, UINT, UINT, const void*, const BITMAPINFO*, UINT)
|
||||
{
|
||||
return int(h);
|
||||
}
|
||||
|
||||
HFONT CreateFontIndirect(const LOGFONT* lplf)
|
||||
{
|
||||
if (!lplf) return nullptr;
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
FT_Face face = open_named(lplf->lfFaceName);
|
||||
for (const char* name : kSubstitutes) {
|
||||
if (face) break;
|
||||
face = open_named(name);
|
||||
}
|
||||
if (!face) return nullptr;
|
||||
|
||||
auto* font = new HFONT__();
|
||||
font->kind = GdiKind::Font;
|
||||
font->face = face;
|
||||
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->antialiased = lplf->lfQuality == ANTIALIASED_QUALITY;
|
||||
|
||||
const TT_OS2* os2 = static_cast<const TT_OS2*>(FT_Get_Sfnt_Table(face, FT_SFNT_OS2));
|
||||
const double upem = face->units_per_EM ? face->units_per_EM : 2048;
|
||||
const double win_ascent = os2 && os2->usWinAscent ? os2->usWinAscent : face->ascender;
|
||||
const double win_descent = os2 && os2->usWinAscent ? os2->usWinDescent : -face->descender;
|
||||
LONG height = lplf->lfHeight ? lplf->lfHeight : 12;
|
||||
int ppem = height > 0 ? int(std::lround(height * upem / (win_ascent + win_descent))) : int(-height);
|
||||
font->ppem = std::max(ppem, 1);
|
||||
font->ascent = int(std::lround(win_ascent * font->ppem / upem));
|
||||
font->descent = int(std::lround(win_descent * font->ppem / upem));
|
||||
return font;
|
||||
}
|
||||
|
||||
BOOL DeleteObject(HGDIOBJ ho)
|
||||
{
|
||||
if (!ho) return FALSE;
|
||||
auto* object = static_cast<GdiObject*>(ho);
|
||||
if (object->kind == GdiKind::Bitmap) delete static_cast<HBITMAP__*>(object);
|
||||
else delete static_cast<HFONT__*>(object); // faces stay cached for the process
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
HGDIOBJ SelectObject(HDC hdc, HGDIOBJ h)
|
||||
{
|
||||
if (!hdc || !h) return nullptr;
|
||||
auto* object = static_cast<GdiObject*>(h);
|
||||
if (object->kind == GdiKind::Bitmap) {
|
||||
HGDIOBJ old = hdc->bitmap;
|
||||
hdc->bitmap = static_cast<HBITMAP__*>(object);
|
||||
return old;
|
||||
}
|
||||
HGDIOBJ old = hdc->font;
|
||||
hdc->font = static_cast<HFONT__*>(object);
|
||||
return old;
|
||||
}
|
||||
|
||||
COLORREF SetTextColor(HDC hdc, COLORREF color)
|
||||
{
|
||||
const COLORREF old = hdc->text;
|
||||
hdc->text = color;
|
||||
return old;
|
||||
}
|
||||
|
||||
COLORREF SetBkColor(HDC hdc, COLORREF color)
|
||||
{
|
||||
const COLORREF old = hdc->bk;
|
||||
hdc->bk = color;
|
||||
return old;
|
||||
}
|
||||
|
||||
int SetBkMode(HDC hdc, int mode)
|
||||
{
|
||||
const int old = hdc->bk_mode;
|
||||
hdc->bk_mode = mode;
|
||||
return old;
|
||||
}
|
||||
|
||||
BOOL GetTextExtentPoint32W(HDC hdc, LPCWSTR lpString, int c, LPSIZE psizl)
|
||||
{
|
||||
if (!hdc || !psizl) return FALSE;
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
HFONT font = dc_font(hdc);
|
||||
if (!font) return FALSE;
|
||||
LONG cx = 0;
|
||||
for (int i = 0; i < c; ++i) {
|
||||
Glyph glyph;
|
||||
if (load_glyph(font, lpString[i], false, &glyph)) cx += glyph.advance;
|
||||
}
|
||||
psizl->cx = cx;
|
||||
psizl->cy = font->ascent + font->descent;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL GetCharABCWidthsFloatW(HDC hdc, UINT iFirst, UINT iLast, LPABCFLOAT lpABC)
|
||||
{
|
||||
if (!hdc || !lpABC || iLast < iFirst) return FALSE;
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
HFONT font = dc_font(hdc);
|
||||
if (!font) return FALSE;
|
||||
for (UINT ch = iFirst; ch <= iLast; ++ch) {
|
||||
Glyph glyph;
|
||||
if (!load_glyph(font, wchar_t(ch), false, &glyph)) return FALSE;
|
||||
ABCFLOAT& abc = lpABC[ch - iFirst];
|
||||
if (glyph.width == 0) {
|
||||
// A blank glyph (space) is all advance, like GDI reports it.
|
||||
abc.abcfA = 0;
|
||||
abc.abcfB = float(glyph.advance);
|
||||
abc.abcfC = 0;
|
||||
} else {
|
||||
abc.abcfA = float(glyph.left);
|
||||
abc.abcfB = float(glyph.width);
|
||||
abc.abcfC = float(glyph.advance - glyph.left - glyph.width);
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL TextOutW(HDC hdc, int x, int y, LPCWSTR lpString, int c)
|
||||
{
|
||||
if (!hdc || !hdc->bitmap) return FALSE;
|
||||
std::lock_guard<std::mutex> lock(g_mutex);
|
||||
HFONT font = dc_font(hdc);
|
||||
if (!font) return FALSE;
|
||||
HBITMAP bmp = hdc->bitmap;
|
||||
const int height = font->ascent + font->descent;
|
||||
|
||||
std::vector<Glyph> glyphs(size_t(std::max(c, 0)));
|
||||
int extent = 0;
|
||||
for (int i = 0; i < c; ++i) {
|
||||
load_glyph(font, lpString[i], true, &glyphs[size_t(i)]);
|
||||
extent += glyphs[size_t(i)].advance;
|
||||
}
|
||||
|
||||
if (hdc->bk_mode == OPAQUE) {
|
||||
const uint32_t bk = dib_pixel(hdc->bk);
|
||||
for (int py = std::max(y, 0); py < std::min(y + height, bmp->height); ++py) {
|
||||
uint32_t* row = dib_row(bmp, py);
|
||||
for (int px = std::max(x, 0); px < std::min(x + extent, bmp->width); ++px) row[px] = bk;
|
||||
}
|
||||
}
|
||||
|
||||
const uint32_t fg = dib_pixel(hdc->text);
|
||||
const uint32_t bg = dib_pixel(hdc->bk);
|
||||
int pen = x;
|
||||
for (const Glyph& glyph : glyphs) {
|
||||
const int ox = pen + glyph.left;
|
||||
const int oy = y + font->ascent - glyph.top;
|
||||
for (int gy = 0; gy < glyph.rows && !glyph.bitmap.empty(); ++gy) {
|
||||
const int py = oy + gy;
|
||||
if (py < 0 || py >= bmp->height) continue;
|
||||
uint32_t* row = dib_row(bmp, py);
|
||||
const unsigned char* src = glyph.bitmap.data() + size_t(gy) * size_t(glyph.pitch);
|
||||
for (int gx = 0; gx < glyph.width; ++gx) {
|
||||
const int px = ox + gx;
|
||||
if (px < 0 || px >= bmp->width) continue;
|
||||
unsigned coverage = glyph.mono ? ((src[gx >> 3] >> (7 - (gx & 7))) & 1) * 255 : src[gx];
|
||||
if (!coverage) continue;
|
||||
if (coverage == 255) { row[px] = fg; continue; }
|
||||
// Grayscale GDI text blends the text colour over the background colour.
|
||||
const uint32_t under = hdc->bk_mode == OPAQUE ? bg : row[px];
|
||||
uint32_t out = 0;
|
||||
for (int shift = 0; shift < 24; shift += 8) {
|
||||
const unsigned a = (fg >> shift) & 0xFF, b = (under >> shift) & 0xFF;
|
||||
out |= ((a * coverage + b * (255 - coverage) + 127) / 255) << shift;
|
||||
}
|
||||
row[px] = out;
|
||||
}
|
||||
}
|
||||
pen += glyph.advance;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL TextOutA(HDC hdc, int x, int y, LPCSTR lpString, int c)
|
||||
{
|
||||
const int n = MultiByteToWideChar(CP_ACP, 0, lpString, c, nullptr, 0);
|
||||
if (n <= 0) return c == 0;
|
||||
std::vector<WCHAR> wide(static_cast<size_t>(n));
|
||||
MultiByteToWideChar(CP_ACP, 0, lpString, c, wide.data(), n);
|
||||
return TextOutW(hdc, x, y, wide.data(), n);
|
||||
}
|
||||
Reference in New Issue
Block a user