Refactoring fontconfig from Resource Manager to Font Manager

This commit is contained in:
Dan Paulat 2023-09-24 01:11:04 -05:00
parent 190bd95781
commit c807188b2b
5 changed files with 206 additions and 183 deletions

View file

@ -1,6 +1,13 @@
#include <scwx/qt/manager/font_manager.hpp>
#include <scwx/util/logger.hpp>
#include <filesystem>
#include <QFile>
#include <QFileInfo>
#include <QStandardPaths>
#include <fontconfig/fontconfig.h>
namespace scwx
{
namespace qt
@ -11,33 +18,206 @@ namespace manager
static const std::string logPrefix_ = "scwx::qt::manager::font_manager";
static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
static const std::string kFcTrueType_ {"TrueType"};
struct FontRecord
{
std::string family_ {};
std::string style_ {};
std::string filename_ {};
};
class FontManager::Impl
{
public:
explicit Impl() {}
~Impl() {}
explicit Impl()
{
InitializeFontCache();
InitializeFontconfig();
}
~Impl() { FinalizeFontconfig(); }
void FinalizeFontconfig();
void InitializeFontCache();
void InitializeFontconfig();
static FontRecord MatchFontFile(const std::string& family,
const std::vector<std::string>& styles);
std::string fontCachePath_ {};
};
FontManager::FontManager() : p(std::make_unique<Impl>()) {}
FontManager::~FontManager() {};
std::shared_ptr<FontManager> FontManager::Instance()
std::shared_ptr<types::ImGuiFont>
FontManager::GetImGuiFont(const std::string& family,
const std::vector<std::string>& styles,
units::font_size::points<double> size)
{
static std::weak_ptr<FontManager> fontManagerReference_ {};
static std::mutex instanceMutex_ {};
const std::string styleString = fmt::format("{}", fmt::join(styles, " "));
const std::string fontString =
fmt::format("{}-{}:{}", family, size.value(), styleString);
std::unique_lock lock(instanceMutex_);
logger_->debug("LoadFontResource: {}", fontString);
std::shared_ptr<FontManager> fontManager = fontManagerReference_.lock();
FontRecord fontRecord = Impl::MatchFontFile(family, styles);
if (fontManager == nullptr)
// TODO:
Q_UNUSED(fontRecord);
return nullptr;
}
void FontManager::LoadApplicationFont(const std::string& filename)
{
// If the font cache failed to create, don't attempt to cache any fonts
if (p->fontCachePath_.empty())
{
fontManager = std::make_shared<FontManager>();
fontManagerReference_ = fontManager;
return;
}
return fontManager;
// Make a copy of the font in the cache (if it doesn't exist)
QFile fontFile(QString::fromStdString(filename));
QFileInfo fontFileInfo(fontFile);
QFile cacheFile(QString::fromStdString(p->fontCachePath_) +
fontFileInfo.fileName());
QFileInfo cacheFileInfo(cacheFile);
std::string cacheFilename = cacheFile.fileName().toStdString();
if (fontFile.exists())
{
// If the file has not been cached, or the font file size has changed
if (!cacheFile.exists() || fontFileInfo.size() != cacheFileInfo.size())
{
logger_->info("Caching font: {}", filename);
if (!fontFile.copy(cacheFile.fileName()))
{
logger_->error("Could not cache font: {}", filename);
return;
}
}
}
else
{
logger_->error("Font does not exist: {}", filename);
return;
}
// Load the file into fontconfig
FcBool result = FcConfigAppFontAddFile(
nullptr, reinterpret_cast<const FcChar8*>(cacheFilename.c_str()));
if (!result)
{
logger_->error("Could not load font into fontconfig database", filename);
}
}
void FontManager::Impl::InitializeFontCache()
{
std::string cachePath {
QStandardPaths::writableLocation(QStandardPaths::CacheLocation)
.toStdString() +
"/fonts"};
fontCachePath_ = cachePath + "/";
if (!std::filesystem::exists(cachePath))
{
std::error_code error;
if (!std::filesystem::create_directories(cachePath, error))
{
logger_->error("Unable to create font cache directory: \"{}\" ({})",
cachePath,
error.message());
fontCachePath_.clear();
}
}
}
void FontManager::Impl::InitializeFontconfig()
{
FcConfig* fcConfig = FcInitLoadConfigAndFonts();
FcConfigSetCurrent(fcConfig);
}
void FontManager::Impl::FinalizeFontconfig()
{
FcFini();
}
FontRecord
FontManager::Impl::MatchFontFile(const std::string& family,
const std::vector<std::string>& styles)
{
const std::string styleString = fmt::format("{}", fmt::join(styles, " "));
const std::string fontString = fmt::format("{}:{}", family, styleString);
// Build fontconfig pattern
FcPattern* pattern = FcPatternCreate();
FcPatternAddString(
pattern, FC_FAMILY, reinterpret_cast<const FcChar8*>(family.c_str()));
FcPatternAddString(pattern,
FC_FONTFORMAT,
reinterpret_cast<const FcChar8*>(kFcTrueType_.c_str()));
if (!styles.empty())
{
FcPatternAddString(pattern,
FC_STYLE,
reinterpret_cast<const FcChar8*>(styleString.c_str()));
}
// Perform font pattern match substitution
FcConfigSubstitute(nullptr, pattern, FcMatchPattern);
FcDefaultSubstitute(pattern);
// Find matching font
FcResult result;
FcPattern* match = FcFontMatch(nullptr, pattern, &result);
FontRecord record {};
if (match != nullptr)
{
FcChar8* fcFamily;
FcChar8* fcStyle;
FcChar8* fcFile;
// Match was found, get properties
if (FcPatternGetString(match, FC_FAMILY, 0, &fcFamily) == FcResultMatch &&
FcPatternGetString(match, FC_STYLE, 0, &fcStyle) == FcResultMatch &&
FcPatternGetString(match, FC_FILE, 0, &fcFile) == FcResultMatch)
{
record.family_ = reinterpret_cast<char*>(fcFamily);
record.style_ = reinterpret_cast<char*>(fcStyle);
record.filename_ = reinterpret_cast<char*>(fcFile);
logger_->debug("Found matching font: {}:{} ({})",
record.family_,
record.style_,
record.filename_);
}
}
if (record.filename_.empty())
{
logger_->warn("Could not find matching font: {}", fontString);
}
// Cleanup
FcPatternDestroy(match);
FcPatternDestroy(pattern);
return record;
}
FontManager& FontManager::Instance()
{
static FontManager instance_ {};
return instance_;
}
} // namespace manager

View file

@ -19,7 +19,14 @@ public:
explicit FontManager();
~FontManager();
static std::shared_ptr<FontManager> Instance();
std::shared_ptr<types::ImGuiFont>
GetImGuiFont(const std::string& family,
const std::vector<std::string>& styles,
units::font_size::points<double> size);
void LoadApplicationFont(const std::string& filename);
static FontManager& Instance();
private:
class Impl;

View file

@ -1,4 +1,5 @@
#include <scwx/qt/manager/placefile_manager.hpp>
#include <scwx/qt/manager/font_manager.hpp>
#include <scwx/qt/manager/resource_manager.hpp>
#include <scwx/qt/main/application.hpp>
#include <scwx/qt/util/json.hpp>
@ -706,7 +707,7 @@ void PlacefileManager::Impl::LoadFontResources(
styles.push_back("italic");
}
ResourceManager::LoadFontResource(font.second->face_, styles, size);
FontManager::Instance().GetImGuiFont(font.second->face_, styles, size);
}
}

View file

@ -1,4 +1,5 @@
#include <scwx/qt/manager/resource_manager.hpp>
#include <scwx/qt/manager/font_manager.hpp>
#include <scwx/qt/config/county_database.hpp>
#include <scwx/qt/model/imgui_context_model.hpp>
#include <scwx/qt/util/font.hpp>
@ -6,14 +7,9 @@
#include <scwx/util/logger.hpp>
#include <execution>
#include <filesystem>
#include <mutex>
#include <QFile>
#include <QFileInfo>
#include <QFontDatabase>
#include <QStandardPaths>
#include <fontconfig/fontconfig.h>
#include <imgui.h>
namespace scwx
@ -28,42 +24,26 @@ namespace ResourceManager
static const std::string logPrefix_ = "scwx::qt::manager::resource_manager";
static const auto logger_ = scwx::util::Logger::Create(logPrefix_);
static void InitializeFonts();
static void InitializeFontCache();
static void LoadFcApplicationFont(const std::string& fontFilename);
static void LoadFonts();
static void LoadTextures();
static const std::string kFcTrueType_ {"TrueType"};
static const std::vector<std::pair<types::Font, std::string>> fontNames_ {
{types::Font::din1451alt, ":/res/fonts/din1451alt.ttf"},
{types::Font::din1451alt_g, ":/res/fonts/din1451alt_g.ttf"},
{types::Font::Inconsolata_Regular, ":/res/fonts/Inconsolata-Regular.ttf"}};
static std::string fontCachePath_ {};
static std::unordered_map<types::Font, int> fontIds_ {};
static std::unordered_map<types::Font, std::shared_ptr<util::Font>> fonts_ {};
static FcFontSet* fcFontSet_ {nullptr};
static FcObjectSet* fcObjectSet_ {nullptr};
void Initialize()
{
config::CountyDatabase::Initialize();
InitializeFonts();
LoadFonts();
LoadTextures();
}
void Shutdown()
{
// Finalize Fontconfig
FcFini();
}
void Shutdown() {}
int FontId(types::Font font)
{
@ -85,72 +65,6 @@ std::shared_ptr<util::Font> Font(types::Font font)
return nullptr;
}
void LoadFontResource(const std::string& family,
const std::vector<std::string>& styles,
units::font_size::points<double> size)
{
const std::string styleString = fmt::format("{}", fmt::join(styles, " "));
const std::string fontString =
fmt::format("{}-{}:{}", family, size.value(), styleString);
logger_->debug("LoadFontResource: {}", fontString);
// Build fontconfig pattern
FcPattern* pattern = FcPatternCreate();
FcPatternAddString(
pattern, FC_FAMILY, reinterpret_cast<const FcChar8*>(family.c_str()));
FcPatternAddDouble(pattern, FC_SIZE, size.value());
FcPatternAddString(pattern,
FC_FONTFORMAT,
reinterpret_cast<const FcChar8*>(kFcTrueType_.c_str()));
if (!styles.empty())
{
FcPatternAddString(pattern,
FC_STYLE,
reinterpret_cast<const FcChar8*>(styleString.c_str()));
}
// Perform font pattern match substitution
FcConfigSubstitute(nullptr, pattern, FcMatchPattern);
FcDefaultSubstitute(pattern);
// Find matching font
FcResult result;
FcPattern* match = FcFontMatch(nullptr, pattern, &result);
std::string fontFile {};
if (match != nullptr)
{
FcChar8* fcFamily;
FcChar8* fcStyle;
FcChar8* fcFile;
// Match was found, get properties
if (FcPatternGetString(match, FC_FAMILY, 0, &fcFamily) == FcResultMatch &&
FcPatternGetString(match, FC_STYLE, 0, &fcStyle) == FcResultMatch &&
FcPatternGetString(match, FC_FILE, 0, &fcFile) == FcResultMatch)
{
fontFile = reinterpret_cast<char*>(fcFile);
logger_->debug("Found matching font: {}:{} ({})",
reinterpret_cast<char*>(fcFamily),
reinterpret_cast<char*>(fcStyle),
fontFile);
}
}
if (fontFile.empty())
{
logger_->warn("Could not find matching font: {}", fontString);
}
// Cleanup
FcPatternDestroy(match);
FcPatternDestroy(pattern);
}
std::shared_ptr<boost::gil::rgba8_image_t>
LoadImageResource(const std::string& urlString)
{
@ -187,47 +101,17 @@ LoadImageResources(const std::vector<std::string>& urlStrings)
return images;
}
static void InitializeFonts()
{
// Create a directory to store local fonts
InitializeFontCache();
// Initialize Fontconfig
FcConfig* fcConfig = FcInitLoadConfigAndFonts();
FcConfigSetCurrent(fcConfig);
}
static void InitializeFontCache()
{
std::string cachePath {
QStandardPaths::writableLocation(QStandardPaths::CacheLocation)
.toStdString() +
"/fonts"};
fontCachePath_ = cachePath + "/";
if (!std::filesystem::exists(cachePath))
{
std::error_code error;
if (!std::filesystem::create_directories(cachePath, error))
{
logger_->error("Unable to create font cache directory: \"{}\" ({})",
cachePath,
error.message());
fontCachePath_.clear();
}
}
}
static void LoadFonts()
{
auto& fontManager = FontManager::Instance();
for (auto& fontName : fontNames_)
{
int fontId = QFontDatabase::addApplicationFont(
QString::fromStdString(fontName.second));
fontIds_.emplace(fontName.first, fontId);
LoadFcApplicationFont(fontName.second);
fontManager.LoadApplicationFont(fontName.second);
auto font = util::Font::Create(fontName.second);
fonts_.emplace(fontName.first, font);
@ -237,52 +121,6 @@ static void LoadFonts()
fontAtlas->AddFontDefault();
}
static void LoadFcApplicationFont(const std::string& fontFilename)
{
// If the font cache failed to create, don't attempt to cache any fonts
if (fontCachePath_.empty())
{
return;
}
// Make a copy of the font in the cache (if it doesn't exist)
QFile fontFile(QString::fromStdString(fontFilename));
QFileInfo fontFileInfo(fontFile);
QFile cacheFile(QString::fromStdString(fontCachePath_) +
fontFileInfo.fileName());
QFileInfo cacheFileInfo(cacheFile);
std::string cacheFilename = cacheFile.fileName().toStdString();
if (fontFile.exists())
{
// If the file has not been cached, or the font file size has changed
if (!cacheFile.exists() || fontFileInfo.size() != cacheFileInfo.size())
{
logger_->info("Caching font: {}", fontFilename);
if (!fontFile.copy(cacheFile.fileName()))
{
logger_->error("Could not cache font: {}", fontFilename);
return;
}
}
}
else
{
logger_->error("Font does not exist: {}", fontFilename);
return;
}
// Load the file into fontconfig (FcConfigAppFontAddFile)
FcBool result = FcConfigAppFontAddFile(
nullptr, reinterpret_cast<const FcChar8*>(cacheFilename.c_str()));
if (!result)
{
logger_->error("Could not load font into fontconfig database",
fontFilename);
}
}
static void LoadTextures()
{
util::TextureAtlas& textureAtlas = util::TextureAtlas::Instance();

View file

@ -22,9 +22,6 @@ void Shutdown();
int FontId(types::Font font);
std::shared_ptr<util::Font> Font(types::Font font);
void LoadFontResource(const std::string& family,
const std::vector<std::string>& styles,
units::font_size::points<double> size);
std::shared_ptr<boost::gil::rgba8_image_t>
LoadImageResource(const std::string& urlString);
std::vector<std::shared_ptr<boost::gil::rgba8_image_t>>