Files
m2dev-client-src/src/EterLib/FontManager.cpp
2026-02-04 12:30:14 +00:00

196 lines
4.2 KiB
C++

#include "StdAfx.h"
#include "FontManager.h"
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_LCD_FILTER_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;
// Enable LCD subpixel filter to reduce color fringing
FT_Library_SetLcdFilter(m_ftLibrary, FT_LCD_FILTER_DEFAULT);
// 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["arial bold"] = "arialbd.ttf";
m_fontPathMap["tahoma"] = "tahoma.ttf";
m_fontPathMap["tahoma bold"] = "tahomabd.ttf";
m_fontPathMap["verdana"] = "verdana.ttf";
m_fontPathMap["verdana bold"] = "verdanab.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()
{
m_resolvedPathCache.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
static std::string s_fontsDir;
if (s_fontsDir.empty())
{
char winDir[MAX_PATH];
if (GetWindowsDirectoryA(winDir, MAX_PATH))
s_fontsDir = std::string(winDir) + "\\Fonts\\";
}
if (!s_fontsDir.empty())
{
std::string systemPath = s_fontsDir + 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 (!s_fontsDir.empty())
{
std::string systemPath = s_fontsDir + ttcName;
if (FileExists(systemPath))
return systemPath;
}
}
#endif
TraceError("CFontManager::ResolveFontPath - Could not find font: %s", faceName);
return "";
}
FT_Face CFontManager::CreateFace(const char* faceName)
{
if (!m_bInitialized)
{
if (!Initialize())
return nullptr;
}
if (!faceName || !faceName[0])
return nullptr;
std::string lowerName = ToLower(faceName);
// Check resolved path cache (avoids repeated disk stat calls)
std::string path;
auto cacheIt = m_resolvedPathCache.find(lowerName);
if (cacheIt != m_resolvedPathCache.end())
{
path = cacheIt->second;
}
else
{
path = ResolveFontPath(faceName);
if (path.empty())
return nullptr;
m_resolvedPathCache[lowerName] = path;
}
// Create a new FT_Face — caller owns it
FT_Face face = nullptr;
FT_Error err = FT_New_Face(m_ftLibrary, path.c_str(), 0, &face);
if (err != 0 || !face)
{
TraceError("CFontManager::CreateFace - FT_New_Face failed for '%s' (error %d)", path.c_str(), err);
return nullptr;
}
return face;
}