mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 03:50:05 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			584 lines
		
	
	
	
		
			17 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			584 lines
		
	
	
	
		
			17 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include <scwx/qt/manager/font_manager.hpp>
 | |
| #include <scwx/qt/manager/settings_manager.hpp>
 | |
| #include <scwx/qt/settings/text_settings.hpp>
 | |
| #include <scwx/util/environment.hpp>
 | |
| #include <scwx/util/logger.hpp>
 | |
| 
 | |
| #include <filesystem>
 | |
| #include <fstream>
 | |
| 
 | |
| #include <QFile>
 | |
| #include <QFileInfo>
 | |
| #include <QFontDatabase>
 | |
| #include <QGuiApplication>
 | |
| #include <QStandardPaths>
 | |
| #include <boost/container_hash/hash.hpp>
 | |
| #include <boost/unordered/unordered_flat_map.hpp>
 | |
| #include <boost/unordered/unordered_flat_set.hpp>
 | |
| #include <fmt/ranges.h>
 | |
| #include <fontconfig/fontconfig.h>
 | |
| 
 | |
| namespace scwx
 | |
| {
 | |
| namespace qt
 | |
| {
 | |
| 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"};
 | |
| static const std::string kFcOpenType_ {"CFF"};
 | |
| 
 | |
| struct FontRecord
 | |
| {
 | |
|    std::string family_ {};
 | |
|    std::string style_ {};
 | |
|    std::string filename_ {};
 | |
| };
 | |
| 
 | |
| typedef std::pair<FontRecord, units::font_size::pixels<int>> FontRecordPair;
 | |
| 
 | |
| template<class Key>
 | |
| struct FontRecordHash;
 | |
| 
 | |
| template<>
 | |
| struct FontRecordHash<FontRecordPair>
 | |
| {
 | |
|    size_t operator()(const FontRecordPair& x) const;
 | |
| };
 | |
| 
 | |
| class FontManager::Impl
 | |
| {
 | |
| public:
 | |
|    explicit Impl(FontManager* self) : self_ {self}
 | |
|    {
 | |
|       InitializeEnvironment();
 | |
|       InitializeFontCache();
 | |
|       InitializeFontconfig();
 | |
|       ConnectSignals();
 | |
|    }
 | |
|    ~Impl() { FinalizeFontconfig(); }
 | |
| 
 | |
|    void ConnectSignals();
 | |
|    void FinalizeFontconfig();
 | |
|    void InitializeEnvironment();
 | |
|    void InitializeFontCache();
 | |
|    void InitializeFontconfig();
 | |
|    void UpdateImGuiFont(types::FontCategory fontCategory);
 | |
|    void UpdateQFont(types::FontCategory fontCategory);
 | |
| 
 | |
|    const std::vector<char>& GetRawFontData(const std::string& filename);
 | |
| 
 | |
|    static bool       CheckFontFormat(const FcChar8* format);
 | |
|    static FontRecord MatchFontFile(const std::string&              family,
 | |
|                                    const std::vector<std::string>& styles);
 | |
| 
 | |
|    FontManager* self_;
 | |
| 
 | |
|    std::string fontCachePath_ {};
 | |
| 
 | |
|    std::shared_mutex imguiFontAtlasMutex_ {};
 | |
| 
 | |
|    std::uint64_t imguiFontsBuildCount_ {};
 | |
| 
 | |
|    boost::unordered_flat_map<FontRecordPair,
 | |
|                              std::shared_ptr<types::ImGuiFont>,
 | |
|                              FontRecordHash<FontRecordPair>>
 | |
|                      imguiFonts_ {};
 | |
|    std::shared_mutex imguiFontsMutex_ {};
 | |
| 
 | |
|    boost::unordered_flat_map<std::string, std::vector<char>> rawFontData_ {};
 | |
|    std::mutex rawFontDataMutex_ {};
 | |
| 
 | |
|    std::shared_ptr<types::ImGuiFont> defaultFont_ {};
 | |
|    boost::unordered_flat_map<types::FontCategory,
 | |
|                              std::shared_ptr<types::ImGuiFont>>
 | |
|       fontCategoryImguiFontMap_ {};
 | |
|    boost::unordered_flat_map<types::FontCategory, QFont>
 | |
|               fontCategoryQFontMap_ {};
 | |
|    std::mutex fontCategoryMutex_ {};
 | |
| 
 | |
|    boost::unordered_flat_set<types::FontCategory> dirtyFonts_ {};
 | |
|    std::mutex                                     dirtyFontsMutex_ {};
 | |
| 
 | |
|    boost::unordered_flat_map<types::Font, int> fontIds_ {};
 | |
| };
 | |
| 
 | |
| FontManager::FontManager() : p(std::make_unique<Impl>(this)) {}
 | |
| 
 | |
| FontManager::~FontManager() {};
 | |
| 
 | |
| void FontManager::Impl::ConnectSignals()
 | |
| {
 | |
|    auto& textSettings = settings::TextSettings::Instance();
 | |
| 
 | |
|    for (auto fontCategory : types::FontCategoryIterator())
 | |
|    {
 | |
|       textSettings.font_family(fontCategory)
 | |
|          .RegisterValueChangedCallback(
 | |
|             [this, fontCategory](const auto&)
 | |
|             {
 | |
|                std::unique_lock lock {dirtyFontsMutex_};
 | |
|                dirtyFonts_.insert(fontCategory);
 | |
|             });
 | |
|       textSettings.font_style(fontCategory)
 | |
|          .RegisterValueChangedCallback(
 | |
|             [this, fontCategory](const auto&)
 | |
|             {
 | |
|                std::unique_lock lock {dirtyFontsMutex_};
 | |
|                dirtyFonts_.insert(fontCategory);
 | |
|             });
 | |
|       textSettings.font_point_size(fontCategory)
 | |
|          .RegisterValueChangedCallback(
 | |
|             [this, fontCategory](const auto&)
 | |
|             {
 | |
|                std::unique_lock lock {dirtyFontsMutex_};
 | |
|                dirtyFonts_.insert(fontCategory);
 | |
|             });
 | |
|    }
 | |
| 
 | |
|    QObject::connect(
 | |
|       &SettingsManager::Instance(),
 | |
|       &SettingsManager::SettingsSaved,
 | |
|       self_,
 | |
|       [this]()
 | |
|       {
 | |
|          std::scoped_lock lock {dirtyFontsMutex_, fontCategoryMutex_};
 | |
| 
 | |
|          for (auto fontCategory : dirtyFonts_)
 | |
|          {
 | |
|             UpdateImGuiFont(fontCategory);
 | |
|             UpdateQFont(fontCategory);
 | |
|          }
 | |
| 
 | |
|          dirtyFonts_.clear();
 | |
|       });
 | |
| }
 | |
| 
 | |
| void FontManager::InitializeFonts()
 | |
| {
 | |
|    for (auto fontCategory : types::FontCategoryIterator())
 | |
|    {
 | |
|       p->UpdateImGuiFont(fontCategory);
 | |
|       p->UpdateQFont(fontCategory);
 | |
|    }
 | |
| }
 | |
| 
 | |
| void FontManager::Impl::UpdateImGuiFont(types::FontCategory fontCategory)
 | |
| {
 | |
|    auto& textSettings = settings::TextSettings::Instance();
 | |
| 
 | |
|    auto family = textSettings.font_family(fontCategory).GetValue();
 | |
|    auto styles = textSettings.font_style(fontCategory).GetValue();
 | |
|    units::font_size::points<double> size {
 | |
|       textSettings.font_point_size(fontCategory).GetValue()};
 | |
| 
 | |
|    fontCategoryImguiFontMap_.insert_or_assign(
 | |
|       fontCategory, self_->LoadImGuiFont(family, {styles}, size));
 | |
| }
 | |
| 
 | |
| void FontManager::Impl::UpdateQFont(types::FontCategory fontCategory)
 | |
| {
 | |
|    auto& textSettings = settings::TextSettings::Instance();
 | |
| 
 | |
|    auto family = textSettings.font_family(fontCategory).GetValue();
 | |
|    auto styles = textSettings.font_style(fontCategory).GetValue();
 | |
|    units::font_size::points<double> size {
 | |
|       textSettings.font_point_size(fontCategory).GetValue()};
 | |
| 
 | |
|    QFont font = QFontDatabase::font(QString::fromStdString(family),
 | |
|                                     QString::fromStdString(styles),
 | |
|                                     static_cast<int>(size.value()));
 | |
|    font.setPointSizeF(size.value());
 | |
| 
 | |
|    fontCategoryQFontMap_.insert_or_assign(fontCategory, font);
 | |
| }
 | |
| 
 | |
| std::shared_mutex& FontManager::imgui_font_atlas_mutex()
 | |
| {
 | |
|    return p->imguiFontAtlasMutex_;
 | |
| }
 | |
| 
 | |
| std::uint64_t FontManager::imgui_fonts_build_count() const
 | |
| {
 | |
|    return p->imguiFontsBuildCount_;
 | |
| }
 | |
| 
 | |
| int FontManager::GetFontId(types::Font font) const
 | |
| {
 | |
|    auto it = p->fontIds_.find(font);
 | |
|    if (it != p->fontIds_.cend())
 | |
|    {
 | |
|       return it->second;
 | |
|    }
 | |
|    return -1;
 | |
| }
 | |
| 
 | |
| std::shared_ptr<types::ImGuiFont>
 | |
| FontManager::GetImGuiFont(types::FontCategory fontCategory)
 | |
| {
 | |
|    std::unique_lock lock {p->fontCategoryMutex_};
 | |
| 
 | |
|    auto it = p->fontCategoryImguiFontMap_.find(fontCategory);
 | |
|    if (it != p->fontCategoryImguiFontMap_.cend())
 | |
|    {
 | |
|       return it->second;
 | |
|    }
 | |
| 
 | |
|    return p->defaultFont_;
 | |
| }
 | |
| 
 | |
| QFont FontManager::GetQFont(types::FontCategory fontCategory)
 | |
| {
 | |
|    std::unique_lock lock {p->fontCategoryMutex_};
 | |
| 
 | |
|    auto it = p->fontCategoryQFontMap_.find(fontCategory);
 | |
|    if (it != p->fontCategoryQFontMap_.cend())
 | |
|    {
 | |
|       return it->second;
 | |
|    }
 | |
| 
 | |
|    return QGuiApplication::font();
 | |
| }
 | |
| 
 | |
| std::shared_ptr<types::ImGuiFont>
 | |
| FontManager::LoadImGuiFont(const std::string&               family,
 | |
|                            const std::vector<std::string>&  styles,
 | |
|                            units::font_size::points<double> size,
 | |
|                            bool                             loadIfNotFound)
 | |
| {
 | |
|    const std::string styleString = fmt::format("{}", fmt::join(styles, " "));
 | |
|    const std::string fontString =
 | |
|       fmt::format("{}-{}:{}", family, size.value(), styleString);
 | |
| 
 | |
|    logger_->debug("LoadFontResource: {}", fontString);
 | |
| 
 | |
|    FontRecord fontRecord = Impl::MatchFontFile(family, styles);
 | |
| 
 | |
|    // Only allow whole pixels, and clamp to 6-72 pt
 | |
|    units::font_size::pixels<double> pixels {size};
 | |
|    units::font_size::pixels<int>    imFontSize {
 | |
|       std::clamp(static_cast<int>(pixels.value()), 8, 96)};
 | |
|    auto imguiFontKey = std::make_pair(fontRecord, imFontSize);
 | |
| 
 | |
|    // Search for a loaded ImGui font
 | |
|    {
 | |
|       std::shared_lock imguiFontLock {p->imguiFontsMutex_};
 | |
| 
 | |
|       // Search for the associated ImGui font
 | |
|       auto it = p->imguiFonts_.find(imguiFontKey);
 | |
|       if (it != p->imguiFonts_.end())
 | |
|       {
 | |
|          return it->second;
 | |
|       }
 | |
| 
 | |
|       // No ImGui font was found, we need to create one
 | |
|    }
 | |
| 
 | |
|    // No font was found, return an empty shared pointer if not loading
 | |
|    if (!loadIfNotFound)
 | |
|    {
 | |
|       return nullptr;
 | |
|    }
 | |
| 
 | |
|    // Get raw font data
 | |
|    const auto& rawFontData = p->GetRawFontData(fontRecord.filename_);
 | |
| 
 | |
|    // The font atlas mutex might already be locked within an ImGui render frame.
 | |
|    // Lock the font atlas mutex before the fonts mutex to prevent deadlock.
 | |
|    std::unique_lock imguiFontAtlasLock {p->imguiFontAtlasMutex_};
 | |
|    std::unique_lock imguiFontsLock {p->imguiFontsMutex_};
 | |
| 
 | |
|    // Search for the associated ImGui font again, to prevent loading the same
 | |
|    // font twice
 | |
|    auto it = p->imguiFonts_.find(imguiFontKey);
 | |
|    if (it != p->imguiFonts_.end())
 | |
|    {
 | |
|       return it->second;
 | |
|    }
 | |
| 
 | |
|    // Define a name for the ImGui font
 | |
|    std::string fontName;
 | |
|    try
 | |
|    {
 | |
|       fontName = fmt::format(
 | |
|          "{}:{}",
 | |
|          std::filesystem::path(fontRecord.filename_).filename().string(),
 | |
|          imFontSize.value());
 | |
|    }
 | |
|    catch (const std::exception& ex)
 | |
|    {
 | |
|       logger_->warn(ex.what());
 | |
|       fontName = fmt::format("{}:{}", fontRecord.filename_, imFontSize.value());
 | |
|    }
 | |
| 
 | |
|    // Create an ImGui font
 | |
|    std::shared_ptr<types::ImGuiFont> imguiFont =
 | |
|       std::make_shared<types::ImGuiFont>(fontName, rawFontData, imFontSize);
 | |
| 
 | |
|    // Store the ImGui font
 | |
|    p->imguiFonts_.insert_or_assign(imguiFontKey, imguiFont);
 | |
| 
 | |
|    // Increment ImGui font build count
 | |
|    ++p->imguiFontsBuildCount_;
 | |
| 
 | |
|    // Return the ImGui font
 | |
|    return imguiFont;
 | |
| }
 | |
| 
 | |
| const std::vector<char>&
 | |
| FontManager::Impl::GetRawFontData(const std::string& filename)
 | |
| {
 | |
|    std::unique_lock rawFontDataLock {rawFontDataMutex_};
 | |
| 
 | |
|    auto it = rawFontData_.find(filename);
 | |
|    if (it != rawFontData_.end())
 | |
|    {
 | |
|       // Raw font data has already been loaded
 | |
|       return it->second;
 | |
|    }
 | |
| 
 | |
|    // Raw font data needs to be loaded
 | |
|    std::basic_ifstream<char> ifs {filename, std::ios::binary};
 | |
|    ifs.seekg(0, std::ios_base::end);
 | |
|    std::size_t dataSize = ifs.tellg();
 | |
|    ifs.seekg(0, std::ios_base::beg);
 | |
| 
 | |
|    // Store the font data in a buffer
 | |
|    std::vector<char> buffer {};
 | |
|    buffer.reserve(dataSize);
 | |
|    std::copy(std::istreambuf_iterator<char>(ifs),
 | |
|              std::istreambuf_iterator<char>(),
 | |
|              std::back_inserter(buffer));
 | |
| 
 | |
|    // Place the buffer in the cache
 | |
|    auto result = rawFontData_.emplace(filename, std::move(buffer));
 | |
| 
 | |
|    // Return the cached buffer
 | |
|    return result.first->second;
 | |
| }
 | |
| 
 | |
| void FontManager::LoadApplicationFont(types::Font        font,
 | |
|                                       const std::string& filename)
 | |
| {
 | |
|    // If the font cache failed to create, don't attempt to cache any fonts
 | |
|    if (p->fontCachePath_.empty())
 | |
|    {
 | |
|       return;
 | |
|    }
 | |
| 
 | |
|    // 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 the Qt Font Database
 | |
|    int fontId =
 | |
|       QFontDatabase::addApplicationFont(QString::fromStdString(cacheFilename));
 | |
|    p->fontIds_.emplace(font, fontId);
 | |
| 
 | |
|    // 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::InitializeEnvironment()
 | |
| {
 | |
| #if defined(__linux__)
 | |
|    // Because of the way Fontconfig is built with Conan, FONTCONFIG_PATH must be
 | |
|    // defined on Linux to ensure fonts can be found
 | |
|    static const std::string kFontconfigPathKey {"FONTCONFIG_PATH"};
 | |
| 
 | |
|    std::string fontconfigPath = scwx::util::GetEnvironment(kFontconfigPathKey);
 | |
|    if (fontconfigPath.empty())
 | |
|    {
 | |
|       scwx::util::SetEnvironment(kFontconfigPathKey, "/etc/fonts");
 | |
|    }
 | |
| #endif
 | |
| }
 | |
| 
 | |
| 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();
 | |
| }
 | |
| 
 | |
| bool FontManager::Impl::CheckFontFormat(const FcChar8* format)
 | |
| {
 | |
|    const std::string stdFormat = reinterpret_cast<const char*>(format);
 | |
| 
 | |
|    return stdFormat == kFcTrueType_ || stdFormat == kFcOpenType_;
 | |
| }
 | |
| 
 | |
| 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()));
 | |
|    FcPatternAddBool(pattern, FC_SYMBOL, FcFalse);
 | |
| 
 | |
|    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 {};
 | |
|    FcFontSet* matches = FcFontSort(nullptr, pattern, FcFalse, nullptr, &result);
 | |
|    FontRecord record {};
 | |
| 
 | |
|    if (matches != nullptr)
 | |
|    {
 | |
|       for (int i = 0; i < matches->nfont; i++)
 | |
|       {
 | |
|          FcPattern* match =
 | |
|             // Using C code requires pointer arithmetic
 | |
|             // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
 | |
|             FcFontRenderPrepare(nullptr, pattern, matches->fonts[i]);
 | |
|          if (match == nullptr)
 | |
|          {
 | |
|             continue;
 | |
|          }
 | |
|          FcChar8* fcFamily = nullptr;
 | |
|          FcChar8* fcStyle  = nullptr;
 | |
|          FcChar8* fcFile   = nullptr;
 | |
|          FcChar8* fcFormat = nullptr;
 | |
|          FcBool   fcSymbol = FcFalse;
 | |
| 
 | |
|          // 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 &&
 | |
|              FcPatternGetBool(match, FC_SYMBOL, 0, &fcSymbol) ==
 | |
|                 FcResultMatch &&
 | |
|              FcPatternGetString(match, FC_FONTFORMAT, 0, &fcFormat) ==
 | |
|                 FcResultMatch &&
 | |
|              fcSymbol == FcFalse /*Must check fcSymbol manually*/ &&
 | |
|              CheckFontFormat(fcFormat))
 | |
|          {
 | |
|             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_,
 | |
|                            fcSymbol);
 | |
|             FcPatternDestroy(match);
 | |
|             break;
 | |
|          }
 | |
| 
 | |
|          FcPatternDestroy(match);
 | |
|       }
 | |
|    }
 | |
| 
 | |
|    if (record.filename_.empty())
 | |
|    {
 | |
|       logger_->warn("Could not find matching font: {}", fontString);
 | |
|    }
 | |
| 
 | |
|    // Cleanup
 | |
|    FcFontSetDestroy(matches);
 | |
|    FcPatternDestroy(pattern);
 | |
| 
 | |
|    return record;
 | |
| }
 | |
| 
 | |
| FontManager& FontManager::Instance()
 | |
| {
 | |
|    static FontManager instance_ {};
 | |
|    return instance_;
 | |
| }
 | |
| 
 | |
| size_t FontRecordHash<FontRecordPair>::operator()(const FontRecordPair& x) const
 | |
| {
 | |
|    size_t seed = 0;
 | |
|    boost::hash_combine(seed, x.first.family_);
 | |
|    boost::hash_combine(seed, x.first.style_);
 | |
|    boost::hash_combine(seed, x.first.filename_);
 | |
|    boost::hash_combine(seed, x.second.value());
 | |
|    return seed;
 | |
| }
 | |
| 
 | |
| bool operator==(const FontRecord& lhs, const FontRecord& rhs)
 | |
| {
 | |
|    return lhs.family_ == rhs.family_ && //
 | |
|           lhs.style_ == rhs.style_ &&   //
 | |
|           lhs.filename_ == rhs.filename_;
 | |
| }
 | |
| 
 | |
| } // namespace manager
 | |
| } // namespace qt
 | |
| } // namespace scwx
 | 
