From 296e4385c4a250a36f91cfdbc45fceea425e43db Mon Sep 17 00:00:00 2001 From: w1naenator Date: Fri, 16 Jan 2026 16:25:49 +0200 Subject: [PATCH] Add system font path and override config support Introduce new config options for system font directory, fallback font name, and per-font overrides. Update config load/save logic to handle a [SystemFonts] TOML section, supporting both fallback and individual font overrides. Improve user instructions for custom font setup and clarify related code comments. These changes enhance flexibility and user experience for system font configuration. --- src/common/config.cpp | 110 +++++++++++++++++++++++++++++++++++++-- src/common/path_util.cpp | 15 +++--- src/common/path_util.h | 2 +- 3 files changed, 116 insertions(+), 11 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index eac463d0a..5159c9cf3 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -4,8 +4,9 @@ #include #include #include +#include +#include #include -#include // for wstring support #include #include "common/assert.h" @@ -144,6 +145,9 @@ static ConfigEntry isSideTrophy("right"); static ConfigEntry isConnectedToNetwork(false); static bool enableDiscordRPC = false; static std::filesystem::path sys_modules_path = {}; +static std::filesystem::path sys_font_path = {}; +static std::string sys_font_fallback_name = {}; +static std::unordered_map system_font_overrides; // Input static ConfigEntry cursorState(HideCursorState::Idle); @@ -234,6 +238,47 @@ void setSysModulesPath(const std::filesystem::path& path) { sys_modules_path = path; } +std::filesystem::path getSysFontPath() { + if (sys_font_path.empty()) { + return Common::FS::GetUserPath(Common::FS::PathType::FontDir); + } + return sys_font_path; +} + +void setSysFontPath(const std::filesystem::path& path) { + sys_font_path = path; +} + +std::optional getSystemFontOverride(std::string_view key) { + if (key.empty()) { + return std::nullopt; + } + auto it = system_font_overrides.find(std::string(key)); + if (it == system_font_overrides.end()) { + return std::nullopt; + } + return it->second; +} + +std::string getSystemFontFallbackName() { + return sys_font_fallback_name; +} + +void setSystemFontFallbackName(const std::string& name) { + sys_font_fallback_name = name; +} + +void setSystemFontOverride(std::string_view key, const std::filesystem::path& path) { + if (key.empty()) { + return; + } + system_font_overrides[std::string(key)] = path; +} + +void clearSystemFontOverrides() { + system_font_overrides.clear(); +} + int getVolumeSlider() { return volumeSlider.get(); } @@ -460,7 +505,7 @@ void setShowFpsCounter(bool enable, bool is_game_specific) { showFpsCounter.set(enable, is_game_specific); } -bool isLoggingEnabled() { +static bool isLoggingEnabled() { return logEnabled.get(); } @@ -862,6 +907,10 @@ void load(const std::filesystem::path& path, bool is_game_specific) { return; } + if (!is_game_specific) { + system_font_overrides.clear(); + } + if (data.contains("General")) { const toml::value& general = data.at("General"); @@ -885,6 +934,43 @@ void load(const std::filesystem::path& path, bool is_game_specific) { isConnectedToNetwork.setFromToml(general, "isConnectedToNetwork", is_game_specific); defaultControllerID.setFromToml(general, "defaultControllerID", is_game_specific); sys_modules_path = toml::find_fs_path_or(general, "sysModulesPath", sys_modules_path); + // Accept alias without trailing 's' + sys_modules_path = toml::find_fs_path_or(general, "sysModulePath", sys_modules_path); + // Prefer 'sysFontPath'; accept 'SysFontPath' for compatibility + sys_font_path = toml::find_fs_path_or(general, "sysFontPath", sys_font_path); + sys_font_path = toml::find_fs_path_or(general, "SysFontPath", sys_font_path); + } + + if (data.contains("SystemFonts")) { + const toml::value& fonts = data.at("SystemFonts"); + if (fonts.is_table()) { + // Read fallback (lowercase preferred), accept 'Fallback'/'FallbackFontName' for compat + if (fonts.contains("fallback")) { + const auto& v = fonts.at("fallback"); + if (v.is_string()) { + sys_font_fallback_name = toml::get(v); + } + } else if (fonts.contains("Fallback")) { + const auto& v = fonts.at("Fallback"); + if (v.is_string()) { + sys_font_fallback_name = toml::get(v); + } + } else if (fonts.contains("FallbackFontName")) { + const auto& v = fonts.at("FallbackFontName"); + if (v.is_string()) { + sys_font_fallback_name = toml::get(v); + } + } + for (const auto& [name, value] : fonts.as_table()) { + if (name == "fallback" || name == "Fallback" || name == "FallbackFontName") { + continue; + } + if (value.is_string()) { + system_font_overrides[name] = + std::filesystem::path(toml::get(value)); + } + } + } } if (data.contains("Input")) { @@ -1005,7 +1091,7 @@ void load(const std::filesystem::path& path, bool is_game_specific) { } } -void sortTomlSections(toml::ordered_value& data) { +static void sortTomlSections(toml::ordered_value& data) { toml::ordered_value ordered_data; std::vector section_order = {"General", "Input", "Audio", "GPU", "Vulkan", "Debug", "Keys", "GUI", "Settings"}; @@ -1158,6 +1244,22 @@ void save(const std::filesystem::path& path, bool is_game_specific) { // Non game-specific entries data["General"]["enableDiscordRPC"] = enableDiscordRPC; data["General"]["sysModulesPath"] = string{fmt::UTF(sys_modules_path.u8string()).data}; + // Save using 'sysFontPath' to match style + data["General"]["sysFontPath"] = string{fmt::UTF(sys_font_path.u8string()).data}; + { + toml::table fonts_table; + if (!sys_font_fallback_name.empty()) { + fonts_table["fallback"] = sys_font_fallback_name; + } + for (const auto& [name, path_override] : system_font_overrides) { + fonts_table[name] = string{fmt::UTF(path_override.u8string()).data}; + } + if (!fonts_table.empty()) { + data["SystemFonts"] = fonts_table; + } else if (data.is_table()) { + data.as_table().erase("SystemFonts"); + } + } data["GUI"]["installDirs"] = install_dirs; data["GUI"]["installDirsEnabled"] = install_dirs_enabled; data["GUI"]["saveDataPath"] = string{fmt::UTF(save_data_path.u8string()).data}; @@ -1302,7 +1404,7 @@ hotkey_quit = lctrl, lshift, end )"; } -constexpr std::string_view GetDefaultInputConfig() { +static constexpr std::string_view GetDefaultInputConfig() { return R"(#Feeling lost? Check out the Help section! # Keyboard bindings diff --git a/src/common/path_util.cpp b/src/common/path_util.cpp index 7137a2205..0415c1a26 100644 --- a/src/common/path_util.cpp +++ b/src/common/path_util.cpp @@ -129,7 +129,6 @@ static auto UserPaths = [] { create_path(PathType::CustomConfigs, user_dir / CUSTOM_CONFIGS); create_path(PathType::CacheDir, user_dir / CACHE_DIR); create_path(PathType::FontDir, user_dir / SYSFONTS_DIR); - // subdirectory for fonts std::filesystem::create_directory(user_dir / SYSFONTS_DIR / "font"); std::filesystem::create_directory(user_dir / SYSFONTS_DIR / "font2"); @@ -148,11 +147,15 @@ static auto UserPaths = [] { notice_file.close(); } - std::ofstream font_instructions(user_dir / SYSFONTS_DIR / "Instructions.txt"); - if (font_instructions.is_open()) { - font_instructions << "Place /preinst/common/font contents into font folder\n" - "Place /system/common/font2 contents into font2 folder\n"; - font_instructions.close(); + const auto instructions_path = user_dir / SYSFONTS_DIR / "Instructions.txt"; + std::error_code ec; + if (!std::filesystem::exists(instructions_path, ec)) { + std::ofstream font_instructions(instructions_path); + if (font_instructions.is_open()) { + font_instructions << "Place system font files (.otf/.ttf) into the 'font' and 'font2' " + "folders.\n"; + font_instructions.close(); + } } return paths; diff --git a/src/common/path_util.h b/src/common/path_util.h index 00cf870e7..cf4611903 100644 --- a/src/common/path_util.h +++ b/src/common/path_util.h @@ -25,7 +25,7 @@ enum class PathType { CustomTrophy, // Where custom files for trophies are stored. CustomConfigs, // Where custom files for different games are stored. CacheDir, // Where pipeline and shader cache is stored. - FontDir // Where font files are stored. + FontDir, // Where system font files are stored. }; constexpr auto PORTABLE_DIR = "user";