Implementation FreeType2-13.3

This commit is contained in:
rtw1x1
2026-02-03 20:23:53 +00:00
parent 00cd91524e
commit ce5ef584c9
17 changed files with 595 additions and 275 deletions

View File

@@ -67,6 +67,7 @@ add_compile_definitions("$<$<CONFIG:Release>:_DISTRIBUTE>")
include_directories("src") include_directories("src")
include_directories("extern/include") include_directories("extern/include")
include_directories("vendor/libsodium/src/libsodium/include") include_directories("vendor/libsodium/src/libsodium/include")
include_directories("vendor/freetype-2.13.3/include")
# Add subdirectories for libraries and executables # Add subdirectories for libraries and executables
add_subdirectory(vendor) add_subdirectory(vendor)

View File

@@ -5,6 +5,7 @@ add_library(EterLib STATIC ${FILE_SOURCES})
target_link_libraries(EterLib target_link_libraries(EterLib
sodium sodium
mio mio
freetype
) )
GroupSourcesByFolder(EterLib) GroupSourcesByFolder(EterLib)

View File

@@ -138,9 +138,9 @@ void CDibBar::__BuildTextureBlockList(DWORD dwWidth, DWORD dwHeight, DWORD dwMax
} }
} }
bool CDibBar::Create(HDC hdc, DWORD dwWidth, DWORD dwHeight) bool CDibBar::Create(DWORD dwWidth, DWORD dwHeight)
{ {
if (!m_dib.Create(hdc, dwWidth, dwHeight)) if (!m_dib.Create(dwWidth, dwHeight))
{ {
Tracef(" Failed to create CDibBar\n"); Tracef(" Failed to create CDibBar\n");
return false; return false;

View File

@@ -10,7 +10,7 @@ class CDibBar
CDibBar(); CDibBar();
virtual ~CDibBar(); virtual ~CDibBar();
bool Create(HDC hdc, DWORD dwWidth, DWORD dwHeight); bool Create(DWORD dwWidth, DWORD dwHeight);
void Invalidate(); void Invalidate();
void SetClipRect(const RECT & c_rRect); void SetClipRect(const RECT & c_rRect);
void ClearBar(); void ClearBar();

173
src/EterLib/FontManager.cpp Normal file
View File

@@ -0,0 +1,173 @@
#include "StdAfx.h"
#include "FontManager.h"
#include <algorithm>
#include <cctype>
#include <sys/stat.h>
#ifdef _WIN32
#include <windows.h>
#include <shlobj.h>
#endif
static std::string ToLower(const std::string& s)
{
std::string result = s;
std::transform(result.begin(), result.end(), result.begin(),
[](unsigned char c) { return std::tolower(c); });
return result;
}
static bool FileExists(const std::string& path)
{
struct stat st;
return stat(path.c_str(), &st) == 0;
}
CFontManager::CFontManager()
: m_ftLibrary(nullptr)
, m_bInitialized(false)
{
}
CFontManager::~CFontManager()
{
Destroy();
}
CFontManager& CFontManager::Instance()
{
static CFontManager instance;
return instance;
}
bool CFontManager::Initialize()
{
if (m_bInitialized)
return true;
if (FT_Init_FreeType(&m_ftLibrary) != 0)
{
TraceError("CFontManager::Initialize - FT_Init_FreeType failed");
return false;
}
m_bInitialized = true;
// Register default font name -> file mappings
// Korean system fonts
m_fontPathMap["gulim"] = "gulim.ttc";
m_fontPathMap["\xea\xb5\xb4\xeb\xa6\xbc"] = "gulim.ttc"; // 굴림 (UTF-8)
m_fontPathMap["\xea\xb5\xb4\xeb\xa6\xbc\xec\xb2\xb4"] = "gulimche.ttc"; // 굴림체
// Common Latin fonts
m_fontPathMap["arial"] = "arial.ttf";
m_fontPathMap["tahoma"] = "tahoma.ttf";
m_fontPathMap["verdana"] = "verdana.ttf";
m_fontPathMap["times new roman"] = "times.ttf";
m_fontPathMap["courier new"] = "cour.ttf";
m_fontPathMap["segoe ui"] = "segoeui.ttf";
return true;
}
void CFontManager::Destroy()
{
for (auto& pair : m_faceCache)
{
if (pair.second)
FT_Done_Face(pair.second);
}
m_faceCache.clear();
m_fontPathMap.clear();
if (m_ftLibrary)
{
FT_Done_FreeType(m_ftLibrary);
m_ftLibrary = nullptr;
}
m_bInitialized = false;
}
std::string CFontManager::ResolveFontPath(const char* faceName)
{
if (!faceName || !faceName[0])
return "";
std::string lowerName = ToLower(faceName);
// 1. Check explicit mappings
auto it = m_fontPathMap.find(lowerName);
std::string fileName;
if (it != m_fontPathMap.end())
fileName = it->second;
else
fileName = lowerName + ".ttf";
// 2. Check local fonts/ directory first
std::string localPath = "fonts/" + fileName;
if (FileExists(localPath))
return localPath;
// 3. Fall back to C:\Windows\Fonts
#ifdef _WIN32
char winDir[MAX_PATH];
if (GetWindowsDirectoryA(winDir, MAX_PATH))
{
std::string systemPath = std::string(winDir) + "\\Fonts\\" + fileName;
if (FileExists(systemPath))
return systemPath;
}
// 4. Try .ttc variant if .ttf wasn't found
if (fileName.size() > 4 && fileName.substr(fileName.size() - 4) == ".ttf")
{
std::string ttcName = fileName.substr(0, fileName.size() - 4) + ".ttc";
localPath = "fonts/" + ttcName;
if (FileExists(localPath))
return localPath;
if (GetWindowsDirectoryA(winDir, MAX_PATH))
{
std::string systemPath = std::string(winDir) + "\\Fonts\\" + ttcName;
if (FileExists(systemPath))
return systemPath;
}
}
#endif
TraceError("CFontManager::ResolveFontPath - Could not find font: %s", faceName);
return "";
}
FT_Face CFontManager::GetFace(const char* faceName)
{
if (!m_bInitialized)
{
if (!Initialize())
return nullptr;
}
std::string path = ResolveFontPath(faceName);
if (path.empty())
return nullptr;
// Check cache
auto it = m_faceCache.find(path);
if (it != m_faceCache.end())
return it->second;
// Load new face
FT_Face face = nullptr;
FT_Error err = FT_New_Face(m_ftLibrary, path.c_str(), 0, &face);
if (err != 0 || !face)
{
TraceError("CFontManager::GetFace - FT_New_Face failed for '%s' (error %d)", path.c_str(), err);
return nullptr;
}
m_faceCache[path] = face;
return face;
}

39
src/EterLib/FontManager.h Normal file
View File

@@ -0,0 +1,39 @@
#pragma once
#include <ft2build.h>
#include FT_FREETYPE_H
#include <string>
#include <unordered_map>
class CFontManager
{
public:
static CFontManager& Instance();
bool Initialize();
void Destroy();
// Get an FT_Face for a given face name. The face is owned by CFontManager.
// Callers must NOT call FT_Done_Face on it.
FT_Face GetFace(const char* faceName);
FT_Library GetLibrary() const { return m_ftLibrary; }
private:
CFontManager();
~CFontManager();
CFontManager(const CFontManager&) = delete;
CFontManager& operator=(const CFontManager&) = delete;
std::string ResolveFontPath(const char* faceName);
FT_Library m_ftLibrary;
bool m_bInitialized;
// faceName (lowercase) -> file path
std::unordered_map<std::string, std::string> m_fontPathMap;
// filePath -> FT_Face (cached, shared across sizes)
std::unordered_map<std::string, FT_Face> m_faceCache;
};

View File

@@ -1,6 +1,5 @@
#include "StdAfx.h" #include "StdAfx.h"
#include "GrpDIB.h" #include "GrpDIB.h"
#include <utf8.h>
CGraphicDib::CGraphicDib() CGraphicDib::CGraphicDib()
{ {
@@ -14,8 +13,6 @@ CGraphicDib::~CGraphicDib()
void CGraphicDib::Initialize() void CGraphicDib::Initialize()
{ {
m_hDC=NULL;
m_hBmp=NULL;
m_pvBuf = NULL; m_pvBuf = NULL;
m_width = 0; m_width = 0;
m_height = 0; m_height = 0;
@@ -23,89 +20,23 @@ void CGraphicDib::Initialize()
void CGraphicDib::Destroy() void CGraphicDib::Destroy()
{ {
if (m_hBmp) DeleteObject(m_hBmp); delete[] (DWORD*)m_pvBuf;
if (m_hDC) DeleteDC(m_hDC);
Initialize(); Initialize();
} }
bool CGraphicDib::Create(HDC hDC, int width, int height) bool CGraphicDib::Create(int width, int height)
{ {
Destroy(); Destroy();
m_width = width; m_width = width;
m_height = height; m_height = height;
ZeroMemory(&m_bmi.bmiHeader, sizeof(BITMAPINFOHEADER)); m_pvBuf = new DWORD[width * height];
m_bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); memset(m_pvBuf, 0, width * height * sizeof(DWORD));
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; return true;
} }
HDC CGraphicDib::GetDCHandle()
{
return m_hDC;
}
void CGraphicDib::SetBkMode(int iBkMode)
{
::SetBkMode(m_hDC, iBkMode);
}
void CGraphicDib::TextOut(int ix, int iy, const char* c_szText)
{
::SetBkColor(m_hDC, 0);
if (!c_szText || !*c_szText)
return;
std::wstring wText = Utf8ToWide(c_szText);
if (!wText.empty())
::TextOutW(m_hDC, ix, iy, wText.c_str(), (int)wText.length());
}
void CGraphicDib::Put(HDC hDC, int x, int y)
{
SetDIBitsToDevice(
hDC,
x,
y,
m_width,
m_height,
0,
0,
0,
m_height,
m_pvBuf,
&m_bmi,
DIB_RGB_COLORS
);
}
void* CGraphicDib::GetPointer() void* CGraphicDib::GetPointer()
{ {
return m_pvBuf; return m_pvBuf;

View File

@@ -7,27 +7,17 @@ class CGraphicDib
virtual ~CGraphicDib(); virtual ~CGraphicDib();
void Destroy(); void Destroy();
bool Create(HDC hDC, int width, int height); bool Create(int width, int height);
void SetBkMode(int iBkMode);
void TextOut(int ix, int iy, const char * c_szText);
void Put(HDC hDC, int x, int y);
int GetWidth(); int GetWidth();
int GetHeight(); int GetHeight();
void* GetPointer(); void* GetPointer();
HDC GetDCHandle();
protected: protected:
void Initialize(); void Initialize();
protected: protected:
HDC m_hDC;
HBITMAP m_hBmp;
BITMAPINFO m_bmi;
int m_width; int m_width;
int m_height; int m_height;

View File

@@ -1,10 +1,15 @@
#include "StdAfx.h" #include "StdAfx.h"
#include "GrpText.h" #include "GrpText.h"
#include "FontManager.h"
#include "EterBase/Stl.h" #include "EterBase/Stl.h"
#include "Util.h" #include "Util.h"
#include <utf8.h> #include <utf8.h>
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
CGraphicFontTexture::CGraphicFontTexture() CGraphicFontTexture::CGraphicFontTexture()
{ {
Initialize(); Initialize();
@@ -18,42 +23,38 @@ CGraphicFontTexture::~CGraphicFontTexture()
void CGraphicFontTexture::Initialize() void CGraphicFontTexture::Initialize()
{ {
CGraphicTexture::Initialize(); CGraphicTexture::Initialize();
m_hFontOld = NULL; m_ftFace = nullptr;
m_hFont = NULL; m_pAtlasBuffer = nullptr;
m_atlasWidth = 0;
m_atlasHeight = 0;
m_isDirty = false; m_isDirty = false;
m_bItalic = false; m_bItalic = false;
m_ascender = 0;
m_lineHeight = 0;
m_x = 0;
m_y = 0;
m_step = 0;
m_fontSize = 0;
memset(m_fontName, 0, sizeof(m_fontName));
} }
bool CGraphicFontTexture::IsEmpty() const bool CGraphicFontTexture::IsEmpty() const
{ {
return m_fontMap.size() == 0; return m_ftFace == nullptr;
} }
void CGraphicFontTexture::Destroy() void CGraphicFontTexture::Destroy()
{ {
HDC hDC = m_dib.GetDCHandle(); delete[] m_pAtlasBuffer;
if (hDC) m_pAtlasBuffer = nullptr;
SelectObject(hDC, m_hFontOld);
m_dib.Destroy();
m_lpd3dTexture = NULL; m_lpd3dTexture = NULL;
CGraphicTexture::Destroy(); CGraphicTexture::Destroy();
stl_wipe(m_pFontTextureVector); stl_wipe(m_pFontTextureVector);
m_charInfoMap.clear(); m_charInfoMap.clear();
if (m_fontMap.size()) // FT_Face is owned by CFontManager, do NOT free it here
{ m_ftFace = nullptr;
TFontMap::iterator i = m_fontMap.begin();
while(i != m_fontMap.end())
{
DeleteObject((HGDIOBJ)i->second);
++i;
}
m_fontMap.clear();
}
Initialize(); Initialize();
} }
@@ -71,7 +72,7 @@ bool CGraphicFontTexture::Create(const char* c_szFontName, int fontSize, bool bI
{ {
Destroy(); Destroy();
// UTF-8 -> UTF-16 for font name // UTF-8 -> UTF-16 for font name storage
std::wstring wFontName = Utf8ToWide(c_szFontName ? c_szFontName : ""); std::wstring wFontName = Utf8ToWide(c_szFontName ? c_szFontName : "");
wcsncpy_s(m_fontName, wFontName.c_str(), _TRUNCATE); wcsncpy_s(m_fontName, wFontName.c_str(), _TRUNCATE);
@@ -82,22 +83,57 @@ bool CGraphicFontTexture::Create(const char* c_szFontName, int fontSize, bool bI
m_y = 0; m_y = 0;
m_step = 0; m_step = 0;
// Determine atlas dimensions
DWORD width = 256, height = 256; DWORD width = 256, height = 256;
if (GetMaxTextureWidth() > 512) if (GetMaxTextureWidth() > 512)
width = 512; width = 512;
if (GetMaxTextureHeight() > 512) if (GetMaxTextureHeight() > 512)
height = 512; height = 512;
if (!m_dib.Create(ms_hDC, width, height)) m_atlasWidth = width;
m_atlasHeight = height;
// Allocate CPU-side atlas buffer
m_pAtlasBuffer = new DWORD[width * height];
memset(m_pAtlasBuffer, 0, width * height * sizeof(DWORD));
// Get FT_Face from FontManager
m_ftFace = CFontManager::Instance().GetFace(c_szFontName);
if (!m_ftFace)
{
TraceError("CGraphicFontTexture::Create - Failed to get face for '%s'", c_szFontName ? c_szFontName : "(null)");
return false; return false;
}
HDC hDC = m_dib.GetDCHandle(); Tracef(" FontTexture: loaded '%s' size=%d family='%s' style='%s'\n",
c_szFontName ? c_szFontName : "(null)", fontSize,
m_ftFace->family_name ? m_ftFace->family_name : "?",
m_ftFace->style_name ? m_ftFace->style_name : "?");
m_hFont = GetFont(); // Set pixel size
int pixelSize = (fontSize < 0) ? -fontSize : fontSize;
if (pixelSize == 0)
pixelSize = 12;
FT_Set_Pixel_Sizes(m_ftFace, 0, pixelSize);
m_hFontOld = (HFONT)SelectObject(hDC, m_hFont); // Apply italic via shear matrix if needed
SetTextColor(hDC, RGB(255, 255, 255)); if (bItalic)
SetBkColor(hDC, 0); {
FT_Matrix matrix;
matrix.xx = 0x10000L;
matrix.xy = 0x5800L; // ~0.34 shear for synthetic italic
matrix.yx = 0;
matrix.yy = 0x10000L;
FT_Set_Transform(m_ftFace, &matrix, NULL);
}
else
{
FT_Set_Transform(m_ftFace, NULL, NULL);
}
// Cache font metrics
m_ascender = (int)(m_ftFace->size->metrics.ascender >> 6);
m_lineHeight = (int)(m_ftFace->size->metrics.height >> 6);
if (!AppendTexture()) if (!AppendTexture())
return false; return false;
@@ -105,48 +141,11 @@ bool CGraphicFontTexture::Create(const char* c_szFontName, int fontSize, bool bI
return true; return true;
} }
HFONT CGraphicFontTexture::GetFont()
{
HFONT hFont = nullptr;
// For Unicode, codePage should NOT affect font selection anymore
static const WORD kUnicodeFontKey = 0;
TFontMap::iterator it = m_fontMap.find(kUnicodeFontKey);
if (it != m_fontMap.end())
return it->second;
LOGFONTW 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 = DEFAULT_CHARSET;
logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
logFont.lfQuality = ANTIALIASED_QUALITY;
logFont.lfPitchAndFamily = DEFAULT_PITCH;
// Copy Unicode font face name safely
wcsncpy_s(logFont.lfFaceName, m_fontName, _TRUNCATE);
hFont = CreateFontIndirectW(&logFont);
if (hFont)
m_fontMap.insert(TFontMap::value_type(kUnicodeFontKey, hFont));
return hFont;
}
bool CGraphicFontTexture::AppendTexture() bool CGraphicFontTexture::AppendTexture()
{ {
CGraphicImageTexture* pNewTexture = new CGraphicImageTexture; CGraphicImageTexture* pNewTexture = new CGraphicImageTexture;
if (!pNewTexture->Create(m_dib.GetWidth(), m_dib.GetHeight(), D3DFMT_A4R4G4B4)) if (!pNewTexture->Create(m_atlasWidth, m_atlasHeight, D3DFMT_A8R8G8B8))
{ {
delete pNewTexture; delete pNewTexture;
return false; return false;
@@ -168,22 +167,20 @@ bool CGraphicFontTexture::UpdateTexture()
if (!pFontTexture) if (!pFontTexture)
return false; return false;
WORD* pwDst; DWORD* pdwDst;
int pitch; int pitch;
if (!pFontTexture->Lock(&pitch, (void**)&pwDst)) if (!pFontTexture->Lock(&pitch, (void**)&pdwDst))
return false; return false;
pitch /= 2; pitch /= 4; // pitch in DWORDs (A8R8G8B8 = 4 bytes per pixel)
int width = m_dib.GetWidth(); DWORD* pdwSrc = m_pAtlasBuffer;
int height = m_dib.GetHeight();
DWORD * pdwSrc = (DWORD*)m_dib.GetPointer(); for (int y = 0; y < m_atlasHeight; ++y, pdwDst += pitch, pdwSrc += m_atlasWidth)
{
for (int y = 0; y < height; ++y, pwDst += pitch, pdwSrc += width) memcpy(pdwDst, pdwSrc, m_atlasWidth * sizeof(DWORD));
for (int x = 0; x < width; ++x) }
pwDst[x]=pdwSrc[x];
pFontTexture->Unlock(); pFontTexture->Unlock();
return true; return true;
@@ -207,68 +204,122 @@ CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::GetCharacterInfo
CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::UpdateCharacterInfomation(TCharacterKey keyValue) CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::UpdateCharacterInfomation(TCharacterKey keyValue)
{ {
HDC hDC = m_dib.GetDCHandle(); if (!m_ftFace)
SelectObject(hDC, GetFont());
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; return NULL;
size.cx = stABC.abcfB; // Re-apply face state (FT_Face is shared across instances via CFontManager)
if( stABC.abcfA > 0.0f ) int pixelSize = (m_fontSize < 0) ? -m_fontSize : m_fontSize;
size.cx += ceilf(stABC.abcfA); if (pixelSize == 0)
if( stABC.abcfC > 0.0f ) pixelSize = 12;
size.cx += ceilf(stABC.abcfC); FT_Set_Pixel_Sizes(m_ftFace, 0, pixelSize);
size.cx++;
LONG lAdvance = ceilf( stABC.abcfA + stABC.abcfB + stABC.abcfC ); if (m_bItalic)
{
FT_Matrix matrix;
matrix.xx = 0x10000L;
matrix.xy = 0x5800L;
matrix.yx = 0;
matrix.yy = 0x10000L;
FT_Set_Transform(m_ftFace, &matrix, NULL);
}
else
{
FT_Set_Transform(m_ftFace, NULL, NULL);
}
int width = m_dib.GetWidth(); if (keyValue == 0x08)
int height = m_dib.GetHeight(); keyValue = L' ';
if (m_x + size.cx >= (width - 1)) // Load and render the glyph
FT_UInt glyphIndex = FT_Get_Char_Index(m_ftFace, keyValue);
if (glyphIndex == 0 && keyValue != L' ')
{
// Try space as fallback for unknown characters
glyphIndex = FT_Get_Char_Index(m_ftFace, L' ');
if (glyphIndex == 0)
return NULL;
}
if (FT_Load_Glyph(m_ftFace, glyphIndex, FT_LOAD_RENDER | FT_LOAD_TARGET_NORMAL) != 0)
return NULL;
FT_GlyphSlot slot = m_ftFace->glyph;
FT_Bitmap& bitmap = slot->bitmap;
int glyphBitmapWidth = bitmap.width;
int glyphBitmapHeight = bitmap.rows;
int bearingY = slot->bitmap_top;
float advance = (float)(slot->advance.x >> 6);
// Normalize glyph placement to common baseline
// yOffset = distance from atlas row top to where the glyph bitmap starts
int yOffset = m_ascender - bearingY;
if (yOffset < 0)
yOffset = 0;
// The effective cell height is the full line height
int cellHeight = m_lineHeight;
int cellWidth = glyphBitmapWidth;
// For spacing characters (space, etc.)
if (glyphBitmapWidth == 0 || glyphBitmapHeight == 0)
{
TCharacterInfomation& rNewCharInfo = m_charInfoMap[keyValue];
rNewCharInfo.index = static_cast<short>(m_pFontTextureVector.size() - 1);
rNewCharInfo.width = 0;
rNewCharInfo.height = (short)cellHeight;
rNewCharInfo.left = 0;
rNewCharInfo.top = 0;
rNewCharInfo.right = 0;
rNewCharInfo.bottom = 0;
rNewCharInfo.advance = advance;
return &rNewCharInfo;
}
// Make sure cell fits the glyph including offset
int requiredHeight = yOffset + glyphBitmapHeight;
if (requiredHeight > cellHeight)
cellHeight = requiredHeight;
int width = m_atlasWidth;
int height = m_atlasHeight;
// Atlas packing (row-based)
if (m_x + cellWidth >= (width - 1))
{ {
m_y += (m_step + 1); m_y += (m_step + 1);
m_step = 0; m_step = 0;
m_x = 0; m_x = 0;
if (m_y + size.cy >= (height - 1)) if (m_y + cellHeight >= (height - 1))
{ {
if (!UpdateTexture()) if (!UpdateTexture())
{
return NULL; return NULL;
}
if (!AppendTexture()) if (!AppendTexture())
return NULL; return NULL;
// Reset atlas buffer for new texture
memset(m_pAtlasBuffer, 0, m_atlasWidth * m_atlasHeight * sizeof(DWORD));
m_y = 0; m_y = 0;
} }
} }
TextOutW(hDC, m_x, m_y, &keyValue, 1); // Copy FreeType bitmap into atlas buffer at baseline-normalized position
for (int row = 0; row < glyphBitmapHeight; ++row)
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) int atlasY = m_y + yOffset + row;
if (atlasY < 0 || atlasY >= height)
continue;
unsigned char* srcRow = bitmap.buffer + row * bitmap.pitch;
DWORD* dstRow = m_pAtlasBuffer + atlasY * m_atlasWidth + m_x;
for (int col = 0; col < glyphBitmapWidth; ++col)
{ {
pdwDIBRow[nChrX]=(pdwDIBRow[nChrX]&0xff) ? 0xffff : 0; unsigned char alpha = srcRow[col];
if (alpha)
dstRow[col] = ((DWORD)alpha << 24) | 0x00FFFFFF; // White + alpha
} }
} }
@@ -278,18 +329,18 @@ CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::UpdateCharacterI
TCharacterInfomation& rNewCharInfo = m_charInfoMap[keyValue]; TCharacterInfomation& rNewCharInfo = m_charInfoMap[keyValue];
rNewCharInfo.index = static_cast<short>(m_pFontTextureVector.size() - 1); rNewCharInfo.index = static_cast<short>(m_pFontTextureVector.size() - 1);
rNewCharInfo.width = size.cx; rNewCharInfo.width = (short)cellWidth;
rNewCharInfo.height = size.cy; rNewCharInfo.height = (short)cellHeight;
rNewCharInfo.left = float(m_x) * rhwidth; rNewCharInfo.left = float(m_x) * rhwidth;
rNewCharInfo.top = float(m_y) * rhheight; rNewCharInfo.top = float(m_y) * rhheight;
rNewCharInfo.right = float(m_x+size.cx) * rhwidth; rNewCharInfo.right = float(m_x + cellWidth) * rhwidth;
rNewCharInfo.bottom = float(m_y+size.cy) * rhheight; rNewCharInfo.bottom = float(m_y + cellHeight) * rhheight;
rNewCharInfo.advance = (float) lAdvance; rNewCharInfo.advance = advance;
m_x += size.cx; m_x += cellWidth;
if (m_step < size.cy) if (m_step < cellHeight)
m_step = size.cy; m_step = cellHeight;
m_isDirty = true; m_isDirty = true;

View File

@@ -2,7 +2,9 @@
#include "GrpTexture.h" #include "GrpTexture.h"
#include "GrpImageTexture.h" #include "GrpImageTexture.h"
#include "GrpDIB.h"
#include <ft2build.h>
#include FT_FREETYPE_H
#include <vector> #include <vector>
#include <map> #include <map>
@@ -51,25 +53,22 @@ class CGraphicFontTexture : public CGraphicTexture
bool AppendTexture(); bool AppendTexture();
HFONT GetFont();
protected: protected:
typedef std::vector<CGraphicImageTexture*> TGraphicImageTexturePointerVector; typedef std::vector<CGraphicImageTexture*> TGraphicImageTexturePointerVector;
typedef std::map<TCharacterKey, TCharacterInfomation> TCharacterInfomationMap; typedef std::map<TCharacterKey, TCharacterInfomation> TCharacterInfomationMap;
typedef std::map<WORD, HFONT> TFontMap;
protected: protected:
CGraphicDib m_dib; FT_Face m_ftFace;
HFONT m_hFontOld; // CPU-side atlas buffer (replaces CGraphicDib)
HFONT m_hFont; DWORD* m_pAtlasBuffer;
int m_atlasWidth;
int m_atlasHeight;
TGraphicImageTexturePointerVector m_pFontTextureVector; TGraphicImageTexturePointerVector m_pFontTextureVector;
TCharacterInfomationMap m_charInfoMap; TCharacterInfomationMap m_charInfoMap;
TFontMap m_fontMap;
int m_x; int m_x;
int m_y; int m_y;
int m_step; int m_step;
@@ -78,4 +77,8 @@ class CGraphicFontTexture : public CGraphicTexture
TCHAR m_fontName[LF_FACESIZE]; TCHAR m_fontName[LF_FACESIZE];
LONG m_fontSize; LONG m_fontSize;
bool m_bItalic; bool m_bItalic;
// FreeType metrics cached per-font
int m_ascender;
int m_lineHeight;
}; };

View File

@@ -45,7 +45,9 @@ bool CGraphicText::OnLoad(int /*iSize*/, const void* /*c_pvBuf*/)
if (p) if (p)
{ {
strncpy(strName, GetFileName(), MIN(31, p - GetFileName())); int nameLen = MIN(31, (int)(p - GetFileName()));
strncpy(strName, GetFileName(), nameLen);
strName[nameLen] = '\0';
++p; ++p;
static char num[8]; static char num[8];
@@ -71,7 +73,11 @@ bool CGraphicText::OnLoad(int /*iSize*/, const void* /*c_pvBuf*/)
strName[0] = '\0'; strName[0] = '\0';
} }
else else
strncpy(strName, GetFileName(), MIN(31, p - GetFileName())); {
int nameLen = MIN(31, (int)(p - GetFileName()));
strncpy(strName, GetFileName(), nameLen);
strName[nameLen] = '\0';
}
size = 12; size = 12;
} }

View File

@@ -644,7 +644,8 @@ void CGraphicTextInstance::Render(RECT * pClipRect)
akVertex[2].x=fFontEx-fFontHalfWeight+feather; akVertex[2].x=fFontEx-fFontHalfWeight+feather;
akVertex[3].x=fFontEx-fFontHalfWeight+feather; akVertex[3].x=fFontEx-fFontHalfWeight+feather;
vtxBatch.insert(vtxBatch.end(), std::begin(akVertex), std::end(akVertex)); vtxBatch.push_back(akVertex[0]); vtxBatch.push_back(akVertex[1]); vtxBatch.push_back(akVertex[2]);
vtxBatch.push_back(akVertex[2]); vtxBatch.push_back(akVertex[1]); vtxBatch.push_back(akVertex[3]);
// 오른 // 오른
akVertex[0].x=fFontSx+fFontHalfWeight-feather; akVertex[0].x=fFontSx+fFontHalfWeight-feather;
@@ -652,7 +653,8 @@ void CGraphicTextInstance::Render(RECT * pClipRect)
akVertex[2].x=fFontEx+fFontHalfWeight+feather; akVertex[2].x=fFontEx+fFontHalfWeight+feather;
akVertex[3].x=fFontEx+fFontHalfWeight+feather; akVertex[3].x=fFontEx+fFontHalfWeight+feather;
vtxBatch.insert(vtxBatch.end(), std::begin(akVertex), std::end(akVertex)); vtxBatch.push_back(akVertex[0]); vtxBatch.push_back(akVertex[1]); vtxBatch.push_back(akVertex[2]);
vtxBatch.push_back(akVertex[2]); vtxBatch.push_back(akVertex[1]); vtxBatch.push_back(akVertex[3]);
akVertex[0].x=fFontSx-feather; akVertex[0].x=fFontSx-feather;
akVertex[1].x=fFontSx-feather; akVertex[1].x=fFontSx-feather;
@@ -665,7 +667,8 @@ void CGraphicTextInstance::Render(RECT * pClipRect)
akVertex[2].y=fFontSy-fFontHalfWeight-feather; akVertex[2].y=fFontSy-fFontHalfWeight-feather;
akVertex[3].y=fFontEy-fFontHalfWeight+feather; akVertex[3].y=fFontEy-fFontHalfWeight+feather;
vtxBatch.insert(vtxBatch.end(), std::begin(akVertex), std::end(akVertex)); vtxBatch.push_back(akVertex[0]); vtxBatch.push_back(akVertex[1]); vtxBatch.push_back(akVertex[2]);
vtxBatch.push_back(akVertex[2]); vtxBatch.push_back(akVertex[1]); vtxBatch.push_back(akVertex[3]);
// 아래 // 아래
akVertex[0].y=fFontSy+fFontHalfWeight-feather; akVertex[0].y=fFontSy+fFontHalfWeight-feather;
@@ -673,7 +676,8 @@ void CGraphicTextInstance::Render(RECT * pClipRect)
akVertex[2].y=fFontSy+fFontHalfWeight-feather; akVertex[2].y=fFontSy+fFontHalfWeight-feather;
akVertex[3].y=fFontEy+fFontHalfWeight+feather; akVertex[3].y=fFontEy+fFontHalfWeight+feather;
vtxBatch.insert(vtxBatch.end(), std::begin(akVertex), std::end(akVertex)); vtxBatch.push_back(akVertex[0]); vtxBatch.push_back(akVertex[1]); vtxBatch.push_back(akVertex[2]);
vtxBatch.push_back(akVertex[2]); vtxBatch.push_back(akVertex[1]); vtxBatch.push_back(akVertex[3]);
fCurX += fFontAdvance; fCurX += fFontAdvance;
} }
@@ -743,7 +747,8 @@ void CGraphicTextInstance::Render(RECT * pClipRect)
akVertex[0].color = akVertex[1].color = akVertex[2].color = akVertex[3].color = m_dwColorInfoVector[i]; akVertex[0].color = akVertex[1].color = akVertex[2].color = akVertex[3].color = m_dwColorInfoVector[i];
vtxBatch.insert(vtxBatch.end(), std::begin(akVertex), std::end(akVertex)); vtxBatch.push_back(akVertex[0]); vtxBatch.push_back(akVertex[1]); vtxBatch.push_back(akVertex[2]);
vtxBatch.push_back(akVertex[2]); vtxBatch.push_back(akVertex[1]); vtxBatch.push_back(akVertex[3]);
fCurX += fFontAdvance; fCurX += fFontAdvance;
} }
@@ -827,7 +832,7 @@ void CGraphicTextInstance::Render(RECT * pClipRect)
continue; continue;
STATEMANAGER.SetTexture(0, pTexture); STATEMANAGER.SetTexture(0, pTexture);
STATEMANAGER.DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, vtxBatch.size() - 2, vtxBatch.data(), sizeof(SVertex)); STATEMANAGER.DrawPrimitiveUP(D3DPT_TRIANGLELIST, vtxBatch.size() / 3, vtxBatch.data(), sizeof(SVertex));
} }
if (m_isCursor) if (m_isCursor)

View File

@@ -1,37 +1,41 @@
#include "StdAfx.h" #include "StdAfx.h"
#include "TextBar.h" #include "TextBar.h"
#include "EterLib/Util.h" #include "FontManager.h"
#include "Util.h"
#include <utf8.h> #include <utf8.h>
#include <ft2build.h>
#include FT_BITMAP_H
void CTextBar::__SetFont(int fontSize, bool isBold) void CTextBar::__SetFont(int fontSize, bool isBold)
{ {
LOGFONTW logFont{}; m_ftFace = CFontManager::Instance().GetFace("Tahoma");
if (!m_ftFace)
return;
logFont.lfHeight = fontSize; __ApplyFaceState();
logFont.lfEscapement = 0; }
logFont.lfOrientation = 0;
logFont.lfWeight = isBold ? FW_BOLD : FW_NORMAL;
logFont.lfItalic = FALSE;
logFont.lfUnderline = FALSE;
logFont.lfStrikeOut = FALSE;
logFont.lfCharSet = DEFAULT_CHARSET;
logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
logFont.lfQuality = ANTIALIASED_QUALITY;
logFont.lfPitchAndFamily = DEFAULT_PITCH;
wcscpy_s(logFont.lfFaceName, LF_FACESIZE, L"Tahoma");
m_hFont = CreateFontIndirect(&logFont); void CTextBar::__ApplyFaceState()
{
if (!m_ftFace)
return;
HDC hdc = m_dib.GetDCHandle(); int pixelSize = (m_fontSize < 0) ? -m_fontSize : m_fontSize;
m_hOldFont = (HFONT)SelectObject(hdc, m_hFont); if (pixelSize == 0)
pixelSize = 12;
FT_Set_Pixel_Sizes(m_ftFace, 0, pixelSize);
FT_Set_Transform(m_ftFace, NULL, NULL); // TextBar never uses italic
m_ascender = (int)(m_ftFace->size->metrics.ascender >> 6);
m_lineHeight = (int)(m_ftFace->size->metrics.height >> 6);
} }
void CTextBar::SetTextColor(int r, int g, int b) void CTextBar::SetTextColor(int r, int g, int b)
{ {
HDC hDC = m_dib.GetDCHandle(); m_textColor = ((DWORD)r) | ((DWORD)g << 8) | ((DWORD)b << 16);
::SetTextColor(hDC, RGB(r, g, b));
} }
void CTextBar::GetTextExtent(const char* c_szText, SIZE* p_size) void CTextBar::GetTextExtent(const char* c_szText, SIZE* p_size)
@@ -46,35 +50,126 @@ void CTextBar::GetTextExtent(const char* c_szText, SIZE* p_size)
return; return;
} }
HDC hDC = m_dib.GetDCHandle(); if (!m_ftFace)
{
p_size->cx = 0;
p_size->cy = 0;
return;
}
// Re-apply face state (shared FT_Face may have been changed by another user)
__ApplyFaceState();
// UTF-8 → UTF-16
std::wstring wText = Utf8ToWide(c_szText); std::wstring wText = Utf8ToWide(c_szText);
GetTextExtentPoint32W(hDC, wText.c_str(), static_cast<int>(wText.length()), p_size);
int totalAdvance = 0;
for (size_t i = 0; i < wText.size(); ++i)
{
FT_UInt glyphIndex = FT_Get_Char_Index(m_ftFace, wText[i]);
if (glyphIndex == 0)
glyphIndex = FT_Get_Char_Index(m_ftFace, L' ');
if (FT_Load_Glyph(m_ftFace, glyphIndex, FT_LOAD_DEFAULT) == 0)
totalAdvance += (int)(m_ftFace->glyph->advance.x >> 6);
}
p_size->cx = totalAdvance;
p_size->cy = m_lineHeight;
} }
void CTextBar::TextOut(int ix, int iy, const char * c_szText) void CTextBar::TextOut(int ix, int iy, const char * c_szText)
{ {
m_dib.TextOut(ix, iy, c_szText); if (!c_szText || !*c_szText || !m_ftFace)
return;
// Re-apply face state (shared FT_Face may have been changed by another user)
__ApplyFaceState();
DWORD* pdwBuf = (DWORD*)m_dib.GetPointer();
if (!pdwBuf)
return;
int bufWidth = m_dib.GetWidth();
int bufHeight = m_dib.GetHeight();
std::wstring wText = Utf8ToWide(c_szText);
int penX = ix;
int penY = iy;
DWORD colorRGB = m_textColor; // 0x00BBGGRR in memory
for (size_t i = 0; i < wText.size(); ++i)
{
FT_UInt glyphIndex = FT_Get_Char_Index(m_ftFace, wText[i]);
if (glyphIndex == 0)
glyphIndex = FT_Get_Char_Index(m_ftFace, L' ');
FT_Int32 loadFlags = FT_LOAD_RENDER | FT_LOAD_TARGET_NORMAL;
if (FT_Load_Glyph(m_ftFace, glyphIndex, loadFlags) != 0)
continue;
FT_GlyphSlot slot = m_ftFace->glyph;
// Apply synthetic bold by embolden
if (m_isBold && slot->bitmap.buffer)
FT_Bitmap_Embolden(CFontManager::Instance().GetLibrary(), &slot->bitmap, 64, 0);
FT_Bitmap& bitmap = slot->bitmap;
int bmpX = penX + slot->bitmap_left;
int bmpY = penY + m_ascender - slot->bitmap_top;
for (int row = 0; row < (int)bitmap.rows; ++row)
{
int destY = bmpY + row;
if (destY < 0 || destY >= bufHeight)
continue;
unsigned char* srcRow = bitmap.buffer + row * bitmap.pitch;
DWORD* dstRow = pdwBuf + destY * bufWidth;
for (int col = 0; col < (int)bitmap.width; ++col)
{
int destX = bmpX + col;
if (destX < 0 || destX >= bufWidth)
continue;
unsigned char alpha = srcRow[col];
if (alpha)
{
// D3DFMT_A8R8G8B8 = ARGB in DWORD
// colorRGB is stored as 0x00BBGGRR, convert to ARGB
DWORD r = (colorRGB >> 0) & 0xFF;
DWORD g = (colorRGB >> 8) & 0xFF;
DWORD b = (colorRGB >> 16) & 0xFF;
dstRow[destX] = ((DWORD)alpha << 24) | (r << 16) | (g << 8) | b;
}
}
}
penX += (int)(slot->advance.x >> 6);
}
Invalidate(); Invalidate();
} }
void CTextBar::OnCreate() void CTextBar::OnCreate()
{ {
m_dib.SetBkMode(TRANSPARENT);
__SetFont(m_fontSize, m_isBold); __SetFont(m_fontSize, m_isBold);
} }
CTextBar::CTextBar(int fontSize, bool isBold) CTextBar::CTextBar(int fontSize, bool isBold)
{ {
m_hOldFont = NULL; m_ftFace = nullptr;
m_textColor = 0x00FFFFFF; // White (RGB)
m_fontSize = fontSize; m_fontSize = fontSize;
m_isBold = isBold; m_isBold = isBold;
m_ascender = 0;
m_lineHeight = 0;
} }
CTextBar::~CTextBar() CTextBar::~CTextBar()
{ {
HDC hdc = m_dib.GetDCHandle(); // FT_Face is owned by CFontManager, do NOT free it here
SelectObject(hdc, m_hOldFont);
} }

View File

@@ -2,6 +2,9 @@
#include "DibBar.h" #include "DibBar.h"
#include <ft2build.h>
#include FT_FREETYPE_H
class CTextBar : public CDibBar class CTextBar : public CDibBar
{ {
public: public:
@@ -14,13 +17,17 @@ class CTextBar : public CDibBar
protected: protected:
void __SetFont(int fontSize, bool isBold); void __SetFont(int fontSize, bool isBold);
void __ApplyFaceState();
void OnCreate(); void OnCreate();
protected: protected:
HFONT m_hFont; FT_Face m_ftFace;
HFONT m_hOldFont; DWORD m_textColor;
int m_fontSize; int m_fontSize;
bool m_isBold; bool m_isBold;
int m_ascender;
int m_lineHeight;
}; };

View File

@@ -16,7 +16,7 @@ PyObject* grpCreateTextBar(PyObject* poSelf, PyObject* poArgs)
return Py_BuildException(); return Py_BuildException();
CTextBar * pTextBar = new CTextBar(12, false); CTextBar * pTextBar = new CTextBar(12, false);
if (!pTextBar->Create(NULL, iWidth, iHeight)) if (!pTextBar->Create(iWidth, iHeight))
{ {
delete pTextBar; delete pTextBar;
return Py_BuildValue("K", NULL); return Py_BuildValue("K", NULL);
@@ -39,7 +39,7 @@ PyObject* grpCreateBigTextBar(PyObject* poSelf, PyObject* poArgs)
return Py_BuildException(); return Py_BuildException();
CTextBar * pTextBar = new CTextBar(iFontSize, true); CTextBar * pTextBar = new CTextBar(iFontSize, true);
if (!pTextBar->Create(NULL, iWidth, iHeight)) if (!pTextBar->Create(iWidth, iHeight))
{ {
delete pTextBar; delete pTextBar;
return Py_BuildValue("K", NULL); return Py_BuildValue("K", NULL);

View File

@@ -23,6 +23,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <utf8.h> #include <utf8.h>
#include <sodium.h> #include <sodium.h>
#include "EterLib/FontManager.h"
extern "C" { extern "C" {
extern int _fltused; extern int _fltused;
@@ -297,6 +298,12 @@ static bool Main(HINSTANCE hInstance, LPSTR lpCmdLine)
return false; return false;
} }
if (!CFontManager::Instance().Initialize())
{
LogBox("FreeType initialization failed");
return false;
}
static CLZO lzo; static CLZO lzo;
CPackManager packMgr; CPackManager packMgr;
@@ -324,6 +331,8 @@ static bool Main(HINSTANCE hInstance, LPSTR lpCmdLine)
app->Destroy(); app->Destroy();
delete app; delete app;
CFontManager::Instance().Destroy();
return 0; return 0;
} }

View File

@@ -7,9 +7,18 @@ set(ZSTD_BUILD_SHARED OFF CACHE BOOL "BUILD SHARED LIBRARIES" FORCE)
add_subdirectory(zstd-1.5.7/build/cmake zstd) add_subdirectory(zstd-1.5.7/build/cmake zstd)
include_directories("zstd/lib") include_directories("zstd/lib")
## FreeType - disable optional dependencies we don't need
set(FT_DISABLE_HARFBUZZ ON CACHE BOOL "" FORCE)
set(FT_DISABLE_BROTLI ON CACHE BOOL "" FORCE)
set(FT_DISABLE_PNG ON CACHE BOOL "" FORCE)
set(FT_DISABLE_BZIP2 ON CACHE BOOL "" FORCE)
set(FT_DISABLE_ZLIB ON CACHE BOOL "" FORCE)
add_subdirectory(freetype-2.13.3)
if (WIN32) if (WIN32)
set_target_properties(lzo2 PROPERTIES FOLDER vendor) set_target_properties(lzo2 PROPERTIES FOLDER vendor)
set_target_properties(sodium PROPERTIES FOLDER vendor) set_target_properties(sodium PROPERTIES FOLDER vendor)
set_target_properties(freetype PROPERTIES FOLDER vendor)
## zstd stuff ## zstd stuff
set_target_properties(zstd PROPERTIES FOLDER vendor/zstd) set_target_properties(zstd PROPERTIES FOLDER vendor/zstd)