From ce5ef584c984c07f2ceea9f9a1543c833b50e14c Mon Sep 17 00:00:00 2001 From: rtw1x1 Date: Tue, 3 Feb 2026 20:23:53 +0000 Subject: [PATCH] Implementation FreeType2-13.3 --- CMakeLists.txt | 1 + src/EterLib/CMakeLists.txt | 1 + src/EterLib/DibBar.cpp | 4 +- src/EterLib/DibBar.h | 2 +- src/EterLib/FontManager.cpp | 173 +++++++++++++ src/EterLib/FontManager.h | 39 +++ src/EterLib/GrpDIB.cpp | 89 +------ src/EterLib/GrpDIB.h | 18 +- src/EterLib/GrpFontTexture.cpp | 301 +++++++++++++--------- src/EterLib/GrpFontTexture.h | 21 +- src/EterLib/GrpText.cpp | 12 +- src/EterLib/GrpTextInstance.cpp | 17 +- src/EterLib/TextBar.cpp | 153 ++++++++--- src/EterLib/TextBar.h | 15 +- src/EterPythonLib/PythonGraphicModule.cpp | 4 +- src/UserInterface/UserInterface.cpp | 9 + vendor/CMakeLists.txt | 11 +- 17 files changed, 595 insertions(+), 275 deletions(-) create mode 100644 src/EterLib/FontManager.cpp create mode 100644 src/EterLib/FontManager.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 188e8d5..733fdf9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,6 +67,7 @@ add_compile_definitions("$<$:_DISTRIBUTE>") include_directories("src") include_directories("extern/include") include_directories("vendor/libsodium/src/libsodium/include") +include_directories("vendor/freetype-2.13.3/include") # Add subdirectories for libraries and executables add_subdirectory(vendor) diff --git a/src/EterLib/CMakeLists.txt b/src/EterLib/CMakeLists.txt index ab4b7d9..7754d8b 100644 --- a/src/EterLib/CMakeLists.txt +++ b/src/EterLib/CMakeLists.txt @@ -5,6 +5,7 @@ add_library(EterLib STATIC ${FILE_SOURCES}) target_link_libraries(EterLib sodium mio + freetype ) GroupSourcesByFolder(EterLib) diff --git a/src/EterLib/DibBar.cpp b/src/EterLib/DibBar.cpp index 19a9c89..2e5ce63 100644 --- a/src/EterLib/DibBar.cpp +++ b/src/EterLib/DibBar.cpp @@ -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"); return false; diff --git a/src/EterLib/DibBar.h b/src/EterLib/DibBar.h index 29f16b3..3a2f294 100644 --- a/src/EterLib/DibBar.h +++ b/src/EterLib/DibBar.h @@ -10,7 +10,7 @@ class CDibBar CDibBar(); virtual ~CDibBar(); - bool Create(HDC hdc, DWORD dwWidth, DWORD dwHeight); + bool Create(DWORD dwWidth, DWORD dwHeight); void Invalidate(); void SetClipRect(const RECT & c_rRect); void ClearBar(); diff --git a/src/EterLib/FontManager.cpp b/src/EterLib/FontManager.cpp new file mode 100644 index 0000000..dba64b3 --- /dev/null +++ b/src/EterLib/FontManager.cpp @@ -0,0 +1,173 @@ +#include "StdAfx.h" +#include "FontManager.h" + +#include +#include +#include + +#ifdef _WIN32 +#include +#include +#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; +} diff --git a/src/EterLib/FontManager.h b/src/EterLib/FontManager.h new file mode 100644 index 0000000..f4ee324 --- /dev/null +++ b/src/EterLib/FontManager.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include FT_FREETYPE_H + +#include +#include + +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 m_fontPathMap; + + // filePath -> FT_Face (cached, shared across sizes) + std::unordered_map m_faceCache; +}; diff --git a/src/EterLib/GrpDIB.cpp b/src/EterLib/GrpDIB.cpp index 61ecd09..ececd79 100644 --- a/src/EterLib/GrpDIB.cpp +++ b/src/EterLib/GrpDIB.cpp @@ -1,6 +1,5 @@ #include "StdAfx.h" #include "GrpDIB.h" -#include CGraphicDib::CGraphicDib() { @@ -14,98 +13,30 @@ CGraphicDib::~CGraphicDib() void CGraphicDib::Initialize() { - m_hDC=NULL; - m_hBmp=NULL; - m_pvBuf=NULL; - m_width=0; - m_height=0; + m_pvBuf = NULL; + m_width = 0; + m_height = 0; } void CGraphicDib::Destroy() -{ - if (m_hBmp) DeleteObject(m_hBmp); - if (m_hDC) DeleteDC(m_hDC); - - Initialize(); +{ + delete[] (DWORD*)m_pvBuf; + Initialize(); } -bool CGraphicDib::Create(HDC hDC, int width, int height) +bool CGraphicDib::Create(int width, int height) { 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)); + m_pvBuf = new DWORD[width * height]; + memset(m_pvBuf, 0, width * height * sizeof(DWORD)); 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() { return m_pvBuf; @@ -119,4 +50,4 @@ int CGraphicDib::GetWidth() int CGraphicDib::GetHeight() { return m_height; -} \ No newline at end of file +} diff --git a/src/EterLib/GrpDIB.h b/src/EterLib/GrpDIB.h index a0407ae..a8b21fe 100644 --- a/src/EterLib/GrpDIB.h +++ b/src/EterLib/GrpDIB.h @@ -1,33 +1,23 @@ #pragma once -class CGraphicDib +class CGraphicDib { public: CGraphicDib(); virtual ~CGraphicDib(); - void Destroy(); - bool Create(HDC hDC, 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); + void Destroy(); + bool Create(int width, int height); int GetWidth(); int GetHeight(); void* GetPointer(); - HDC GetDCHandle(); - protected: void Initialize(); - protected: - HDC m_hDC; - HBITMAP m_hBmp; - BITMAPINFO m_bmi; - + protected: int m_width; int m_height; diff --git a/src/EterLib/GrpFontTexture.cpp b/src/EterLib/GrpFontTexture.cpp index 3aa2cf4..cc0c428 100644 --- a/src/EterLib/GrpFontTexture.cpp +++ b/src/EterLib/GrpFontTexture.cpp @@ -1,10 +1,15 @@ #include "StdAfx.h" #include "GrpText.h" +#include "FontManager.h" #include "EterBase/Stl.h" #include "Util.h" #include +#include +#include FT_FREETYPE_H +#include FT_GLYPH_H + CGraphicFontTexture::CGraphicFontTexture() { Initialize(); @@ -18,43 +23,39 @@ CGraphicFontTexture::~CGraphicFontTexture() void CGraphicFontTexture::Initialize() { CGraphicTexture::Initialize(); - m_hFontOld = NULL; - m_hFont = NULL; + m_ftFace = nullptr; + m_pAtlasBuffer = nullptr; + m_atlasWidth = 0; + m_atlasHeight = 0; m_isDirty = 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 { - return m_fontMap.size() == 0; + return m_ftFace == nullptr; } void CGraphicFontTexture::Destroy() { - HDC hDC = m_dib.GetDCHandle(); - if (hDC) - SelectObject(hDC, m_hFontOld); - - m_dib.Destroy(); + delete[] m_pAtlasBuffer; + m_pAtlasBuffer = nullptr; m_lpd3dTexture = NULL; CGraphicTexture::Destroy(); stl_wipe(m_pFontTextureVector); m_charInfoMap.clear(); - if (m_fontMap.size()) - { - TFontMap::iterator i = m_fontMap.begin(); + // FT_Face is owned by CFontManager, do NOT free it here + m_ftFace = nullptr; - while(i != m_fontMap.end()) - { - DeleteObject((HGDIOBJ)i->second); - ++i; - } - - m_fontMap.clear(); - } - Initialize(); } @@ -71,7 +72,7 @@ bool CGraphicFontTexture::Create(const char* c_szFontName, int fontSize, bool bI { Destroy(); - // UTF-8 -> UTF-16 for font name + // UTF-8 -> UTF-16 for font name storage std::wstring wFontName = Utf8ToWide(c_szFontName ? c_szFontName : ""); 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_step = 0; + // Determine atlas dimensions DWORD width = 256, height = 256; if (GetMaxTextureWidth() > 512) width = 512; if (GetMaxTextureHeight() > 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; + } - 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); - SetTextColor(hDC, RGB(255, 255, 255)); - SetBkColor(hDC, 0); + // Apply italic via shear matrix if needed + if (bItalic) + { + 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()) return false; @@ -105,48 +141,11 @@ bool CGraphicFontTexture::Create(const char* c_szFontName, int fontSize, bool bI 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() { - 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; return false; @@ -158,33 +157,31 @@ bool CGraphicFontTexture::AppendTexture() bool CGraphicFontTexture::UpdateTexture() { - if(!m_isDirty) + if (!m_isDirty) return true; m_isDirty = false; - CGraphicImageTexture * pFontTexture = m_pFontTextureVector.back(); + CGraphicImageTexture* pFontTexture = m_pFontTextureVector.back(); if (!pFontTexture) return false; - WORD* pwDst; + DWORD* pdwDst; int pitch; - if (!pFontTexture->Lock(&pitch, (void**)&pwDst)) + if (!pFontTexture->Lock(&pitch, (void**)&pdwDst)) return false; - pitch /= 2; + pitch /= 4; // pitch in DWORDs (A8R8G8B8 = 4 bytes per pixel) - int width = m_dib.GetWidth(); - int height = m_dib.GetHeight(); + DWORD* pdwSrc = m_pAtlasBuffer; - DWORD * pdwSrc = (DWORD*)m_dib.GetPointer(); + for (int y = 0; y < m_atlasHeight; ++y, pdwDst += pitch, pdwSrc += m_atlasWidth) + { + memcpy(pdwDst, pdwSrc, m_atlasWidth * sizeof(DWORD)); + } - 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; } @@ -207,68 +204,122 @@ CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::GetCharacterInfo CGraphicFontTexture::TCharacterInfomation* CGraphicFontTexture::UpdateCharacterInfomation(TCharacterKey keyValue) { - HDC hDC = m_dib.GetDCHandle(); - 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)) + if (!m_ftFace) 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++; + // Re-apply face state (FT_Face is shared across instances via CFontManager) + int pixelSize = (m_fontSize < 0) ? -m_fontSize : m_fontSize; + if (pixelSize == 0) + pixelSize = 12; + FT_Set_Pixel_Sizes(m_ftFace, 0, pixelSize); - 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(); - int height = m_dib.GetHeight(); + if (keyValue == 0x08) + 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(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_step = 0; m_x = 0; - if (m_y + size.cy >= (height - 1)) + if (m_y + cellHeight >= (height - 1)) { if (!UpdateTexture()) - { return NULL; - } if (!AppendTexture()) return NULL; + // Reset atlas buffer for new texture + memset(m_pAtlasBuffer, 0, m_atlasWidth * m_atlasHeight * sizeof(DWORD)); 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= 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]; rNewCharInfo.index = static_cast(m_pFontTextureVector.size() - 1); - rNewCharInfo.width = size.cx; - rNewCharInfo.height = size.cy; + rNewCharInfo.width = (short)cellWidth; + rNewCharInfo.height = (short)cellHeight; 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; + rNewCharInfo.right = float(m_x + cellWidth) * rhwidth; + rNewCharInfo.bottom = float(m_y + cellHeight) * rhheight; + rNewCharInfo.advance = advance; - m_x += size.cx; + m_x += cellWidth; - if (m_step < size.cy) - m_step = size.cy; + if (m_step < cellHeight) + m_step = cellHeight; m_isDirty = true; diff --git a/src/EterLib/GrpFontTexture.h b/src/EterLib/GrpFontTexture.h index 743c756..1aaadd6 100644 --- a/src/EterLib/GrpFontTexture.h +++ b/src/EterLib/GrpFontTexture.h @@ -2,7 +2,9 @@ #include "GrpTexture.h" #include "GrpImageTexture.h" -#include "GrpDIB.h" + +#include +#include FT_FREETYPE_H #include #include @@ -51,25 +53,22 @@ class CGraphicFontTexture : public CGraphicTexture bool AppendTexture(); - HFONT GetFont(); - protected: typedef std::vector TGraphicImageTexturePointerVector; typedef std::map TCharacterInfomationMap; - typedef std::map TFontMap; protected: - CGraphicDib m_dib; + FT_Face m_ftFace; - HFONT m_hFontOld; - HFONT m_hFont; + // CPU-side atlas buffer (replaces CGraphicDib) + DWORD* m_pAtlasBuffer; + int m_atlasWidth; + int m_atlasHeight; TGraphicImageTexturePointerVector m_pFontTextureVector; TCharacterInfomationMap m_charInfoMap; - TFontMap m_fontMap; - int m_x; int m_y; int m_step; @@ -78,4 +77,8 @@ class CGraphicFontTexture : public CGraphicTexture TCHAR m_fontName[LF_FACESIZE]; LONG m_fontSize; bool m_bItalic; + + // FreeType metrics cached per-font + int m_ascender; + int m_lineHeight; }; diff --git a/src/EterLib/GrpText.cpp b/src/EterLib/GrpText.cpp index 6e4790e..b8ae228 100644 --- a/src/EterLib/GrpText.cpp +++ b/src/EterLib/GrpText.cpp @@ -45,7 +45,9 @@ bool CGraphicText::OnLoad(int /*iSize*/, const void* /*c_pvBuf*/) if (p) { - strncpy(strName, GetFileName(), MIN(31, p - GetFileName())); + int nameLen = MIN(31, (int)(p - GetFileName())); + strncpy(strName, GetFileName(), nameLen); + strName[nameLen] = '\0'; ++p; static char num[8]; @@ -71,8 +73,12 @@ bool CGraphicText::OnLoad(int /*iSize*/, const void* /*c_pvBuf*/) strName[0] = '\0'; } else - strncpy(strName, GetFileName(), MIN(31, p - GetFileName())); - + { + int nameLen = MIN(31, (int)(p - GetFileName())); + strncpy(strName, GetFileName(), nameLen); + strName[nameLen] = '\0'; + } + size = 12; } diff --git a/src/EterLib/GrpTextInstance.cpp b/src/EterLib/GrpTextInstance.cpp index b576b48..c9bf630 100644 --- a/src/EterLib/GrpTextInstance.cpp +++ b/src/EterLib/GrpTextInstance.cpp @@ -644,7 +644,8 @@ void CGraphicTextInstance::Render(RECT * pClipRect) akVertex[2].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; @@ -652,7 +653,8 @@ void CGraphicTextInstance::Render(RECT * pClipRect) akVertex[2].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[1].x=fFontSx-feather; @@ -665,7 +667,8 @@ void CGraphicTextInstance::Render(RECT * pClipRect) akVertex[2].y=fFontSy-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; @@ -673,7 +676,8 @@ void CGraphicTextInstance::Render(RECT * pClipRect) akVertex[2].y=fFontSy+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; } @@ -743,7 +747,8 @@ void CGraphicTextInstance::Render(RECT * pClipRect) 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; } @@ -827,7 +832,7 @@ void CGraphicTextInstance::Render(RECT * pClipRect) continue; 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) diff --git a/src/EterLib/TextBar.cpp b/src/EterLib/TextBar.cpp index 78d9c81..65e3482 100644 --- a/src/EterLib/TextBar.cpp +++ b/src/EterLib/TextBar.cpp @@ -1,37 +1,41 @@ #include "StdAfx.h" #include "TextBar.h" -#include "EterLib/Util.h" +#include "FontManager.h" +#include "Util.h" #include +#include +#include FT_BITMAP_H + void CTextBar::__SetFont(int fontSize, bool isBold) { - LOGFONTW logFont{}; + m_ftFace = CFontManager::Instance().GetFace("Tahoma"); + if (!m_ftFace) + return; - logFont.lfHeight = fontSize; - 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"); + __ApplyFaceState(); +} - m_hFont = CreateFontIndirect(&logFont); +void CTextBar::__ApplyFaceState() +{ + if (!m_ftFace) + return; - HDC hdc = m_dib.GetDCHandle(); - m_hOldFont = (HFONT)SelectObject(hdc, m_hFont); + int pixelSize = (m_fontSize < 0) ? -m_fontSize : m_fontSize; + 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) { - HDC hDC = m_dib.GetDCHandle(); - ::SetTextColor(hDC, RGB(r, g, b)); + m_textColor = ((DWORD)r) | ((DWORD)g << 8) | ((DWORD)b << 16); } 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; } - 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); - GetTextExtentPoint32W(hDC, wText.c_str(), static_cast(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) { - 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(); } void CTextBar::OnCreate() { - m_dib.SetBkMode(TRANSPARENT); - __SetFont(m_fontSize, m_isBold); } CTextBar::CTextBar(int fontSize, bool isBold) { - m_hOldFont = NULL; + m_ftFace = nullptr; + m_textColor = 0x00FFFFFF; // White (RGB) m_fontSize = fontSize; m_isBold = isBold; + m_ascender = 0; + m_lineHeight = 0; } CTextBar::~CTextBar() { - HDC hdc = m_dib.GetDCHandle(); - SelectObject(hdc, m_hOldFont); + // FT_Face is owned by CFontManager, do NOT free it here } diff --git a/src/EterLib/TextBar.h b/src/EterLib/TextBar.h index 6aec660..8b3c03b 100644 --- a/src/EterLib/TextBar.h +++ b/src/EterLib/TextBar.h @@ -2,25 +2,32 @@ #include "DibBar.h" +#include +#include FT_FREETYPE_H + class CTextBar : public CDibBar { public: CTextBar(int fontSize, bool isBold); virtual ~CTextBar(); - + void TextOut(int ix, int iy, const char * c_szText); void SetTextColor(int r, int g, int b); void GetTextExtent(const char * c_szText, SIZE* p_size); protected: void __SetFont(int fontSize, bool isBold); + void __ApplyFaceState(); void OnCreate(); protected: - HFONT m_hFont; - HFONT m_hOldFont; - + FT_Face m_ftFace; + DWORD m_textColor; + int m_fontSize; bool m_isBold; + + int m_ascender; + int m_lineHeight; }; diff --git a/src/EterPythonLib/PythonGraphicModule.cpp b/src/EterPythonLib/PythonGraphicModule.cpp index 63167df..2acf8b1 100644 --- a/src/EterPythonLib/PythonGraphicModule.cpp +++ b/src/EterPythonLib/PythonGraphicModule.cpp @@ -16,7 +16,7 @@ PyObject* grpCreateTextBar(PyObject* poSelf, PyObject* poArgs) return Py_BuildException(); CTextBar * pTextBar = new CTextBar(12, false); - if (!pTextBar->Create(NULL, iWidth, iHeight)) + if (!pTextBar->Create(iWidth, iHeight)) { delete pTextBar; return Py_BuildValue("K", NULL); @@ -39,7 +39,7 @@ PyObject* grpCreateBigTextBar(PyObject* poSelf, PyObject* poArgs) return Py_BuildException(); CTextBar * pTextBar = new CTextBar(iFontSize, true); - if (!pTextBar->Create(NULL, iWidth, iHeight)) + if (!pTextBar->Create(iWidth, iHeight)) { delete pTextBar; return Py_BuildValue("K", NULL); diff --git a/src/UserInterface/UserInterface.cpp b/src/UserInterface/UserInterface.cpp index 05ce7da..ca540ed 100644 --- a/src/UserInterface/UserInterface.cpp +++ b/src/UserInterface/UserInterface.cpp @@ -23,6 +23,7 @@ #include #include #include +#include "EterLib/FontManager.h" extern "C" { extern int _fltused; @@ -297,6 +298,12 @@ static bool Main(HINSTANCE hInstance, LPSTR lpCmdLine) return false; } + if (!CFontManager::Instance().Initialize()) + { + LogBox("FreeType initialization failed"); + return false; + } + static CLZO lzo; CPackManager packMgr; @@ -324,6 +331,8 @@ static bool Main(HINSTANCE hInstance, LPSTR lpCmdLine) app->Destroy(); delete app; + + CFontManager::Instance().Destroy(); return 0; } diff --git a/vendor/CMakeLists.txt b/vendor/CMakeLists.txt index dabee2f..4b6cee3 100644 --- a/vendor/CMakeLists.txt +++ b/vendor/CMakeLists.txt @@ -7,10 +7,19 @@ set(ZSTD_BUILD_SHARED OFF CACHE BOOL "BUILD SHARED LIBRARIES" FORCE) add_subdirectory(zstd-1.5.7/build/cmake zstd) 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) set_target_properties(lzo2 PROPERTIES FOLDER vendor) set_target_properties(sodium PROPERTIES FOLDER vendor) - + set_target_properties(freetype PROPERTIES FOLDER vendor) + ## zstd stuff set_target_properties(zstd PROPERTIES FOLDER vendor/zstd) set_target_properties(libzstd_static PROPERTIES FOLDER vendor/zstd)