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.
This commit is contained in:
w1naenator 2026-01-16 16:25:49 +02:00
parent a30014fbfc
commit 296e4385c4
3 changed files with 116 additions and 11 deletions

View File

@ -4,8 +4,9 @@
#include <fstream>
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
#include <fmt/core.h>
#include <fmt/xchar.h> // for wstring support
#include <toml.hpp>
#include "common/assert.h"
@ -144,6 +145,9 @@ static ConfigEntry<string> isSideTrophy("right");
static ConfigEntry<bool> 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<std::string, std::filesystem::path> system_font_overrides;
// Input
static ConfigEntry<int> 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<std::filesystem::path> 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<std::string>(v);
}
} else if (fonts.contains("Fallback")) {
const auto& v = fonts.at("Fallback");
if (v.is_string()) {
sys_font_fallback_name = toml::get<std::string>(v);
}
} else if (fonts.contains("FallbackFontName")) {
const auto& v = fonts.at("FallbackFontName");
if (v.is_string()) {
sys_font_fallback_name = toml::get<std::string>(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<std::string>(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<string> 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

View File

@ -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;

View File

@ -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";