yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
font_loader.cc
Go to the documentation of this file.
2
3#include <cstring>
4#include <filesystem>
5#include <fstream>
6#include <string>
7#include <vector>
8
9#include "absl/status/status.h"
10#include "absl/strings/str_cat.h"
11#include "absl/strings/str_format.h"
12#include "app/gui/core/icons.h"
13#include "imgui/imgui.h"
14#include "util/file_util.h"
15#include "util/macro.h"
16#include "util/platform_paths.h"
17
18namespace yaze {
19
20static const char* KARLA_REGULAR = "Karla-Regular.ttf";
21static const char* ROBOTO_MEDIUM = "Roboto-Medium.ttf";
22static const char* COUSINE_REGULAR = "Cousine-Regular.ttf";
23static const char* DROID_SANS = "DroidSans.ttf";
24static const char* NOTO_SANS_JP = "NotoSansJP.ttf";
25static const char* IBM_PLEX_JP = "IBMPlexSansJP-Bold.ttf";
26
27static const float FONT_SIZE_DEFAULT = 16.0F;
28static const float FONT_SIZE_DROID_SANS = 18.0F;
29static const float ICON_FONT_SIZE = 18.0F;
30
31namespace {
32
33std::string ResolveRepoFontPath(const std::string& font_path) {
34 // Prefer the robust asset search (exe-dir, ~/.yaze, /usr/share, repo-relative)
35 // so installed binaries and .desktop/double-click launches (CWD=$HOME or /)
36 // still find bundled fonts at startup.
37 auto found = util::PlatformPaths::FindAsset("font/" + font_path);
38 if (found.ok()) {
39 return found->string();
40 }
41 // Fallback: CWD-relative probes for running directly from the repo/build dir.
42 const std::vector<std::filesystem::path> candidates = {
43 std::filesystem::path("assets/font") / font_path,
44 std::filesystem::path("../assets/font") / font_path,
45 std::filesystem::path("../../assets/font") / font_path,
46 std::filesystem::path("../../../assets/font") / font_path,
47 };
48 for (const auto& candidate : candidates) {
49 if (std::filesystem::exists(candidate)) {
50 return candidate.string();
51 }
52 }
53 return (std::filesystem::path("assets/font") / font_path).string();
54}
55
56std::string SetFontPath(const std::string& font_path) {
57#ifdef __APPLE__
58#if TARGET_OS_IOS == 1
59 const std::string bundle_root = util::GetBundleResourcePath();
60 std::string bundle_path =
61 absl::StrCat(bundle_root, "assets/font/", font_path);
62 if (std::filesystem::exists(bundle_path)) {
63 return bundle_path;
64 }
65 bundle_path = absl::StrCat(bundle_root, font_path);
66 if (std::filesystem::exists(bundle_path)) {
67 return bundle_path;
68 }
69 return ResolveRepoFontPath(font_path);
70#else
71 std::string bundle_path = absl::StrCat(util::GetBundleResourcePath(),
72 "Contents/Resources/font/", font_path);
73 if (std::filesystem::exists(bundle_path)) {
74 return bundle_path;
75 }
76 return ResolveRepoFontPath(font_path);
77#endif
78#else
79 return ResolveRepoFontPath(font_path);
80#endif
81}
82
83absl::Status LoadFont(const FontConfig& font_config) {
84 ImGuiIO& imgui_io = ImGui::GetIO();
85 std::string actual_font_path = SetFontPath(font_config.font_path);
86 // Check if the file exists with std library first, since ImGui IO will assert
87 // if the file does not exist
88 if (!std::filesystem::exists(actual_font_path)) {
89 return absl::InternalError(
90 absl::StrFormat("Font file %s does not exist", actual_font_path));
91 }
92
93 if (!imgui_io.Fonts->AddFontFromFileTTF(actual_font_path.data(),
94 font_config.font_size)) {
95 return absl::InternalError(
96 absl::StrFormat("Failed to load font from %s", actual_font_path));
97 }
98 return absl::OkStatus();
99}
100
101absl::Status AddIconFont(const FontConfig& /*config*/) {
102 static const ImWchar icons_ranges[] = {ICON_MIN_MD, 0xf900, 0};
103 ImFontConfig icons_config{};
104 icons_config.MergeMode = true;
105 icons_config.GlyphOffset.y = 5.0F;
106 icons_config.GlyphMinAdvanceX = 13.0F;
107 icons_config.PixelSnapH = true;
108 std::string icon_font_path = SetFontPath(FONT_ICON_FILE_NAME_MD);
109 ImGuiIO& imgui_io = ImGui::GetIO();
110 if (!imgui_io.Fonts->AddFontFromFileTTF(icon_font_path.c_str(),
111 ICON_FONT_SIZE, &icons_config,
112 icons_ranges)) {
113 return absl::InternalError("Failed to add icon fonts");
114 }
115 return absl::OkStatus();
116}
117
118// NotoSansJP.ttf is ~9.5 MB and is merged into every base font; loading it
119// from file per merge kept one copy per font. All merges share this buffer.
120// ImGui 1.92 reads font source data for the life of the atlas, so the buffer
121// is never freed. An empty buffer means the file was missing or unreadable.
122std::vector<char>* JapaneseFontData() {
123 static std::vector<char>* data = [] {
124 auto* buffer = new std::vector<char>();
125 std::ifstream file(SetFontPath(NOTO_SANS_JP),
126 std::ios::binary | std::ios::ate);
127 if (!file) {
128 return buffer;
129 }
130 const std::streamsize size = file.tellg();
131 if (size <= 0) {
132 return buffer;
133 }
134 buffer->resize(static_cast<size_t>(size));
135 file.seekg(0);
136 if (!file.read(buffer->data(), size)) {
137 buffer->clear();
138 }
139 return buffer;
140 }();
141 return data;
142}
143
144absl::Status AddJapaneseFont(const FontConfig& /*config*/) {
145 std::vector<char>* font_data = JapaneseFontData();
146 if (font_data->empty()) {
147 return absl::InternalError("Failed to add Japanese fonts");
148 }
149 ImFontConfig japanese_font_config{};
150 japanese_font_config.FontDataOwnedByAtlas = false;
151 japanese_font_config.MergeMode = true;
152 japanese_font_config.GlyphOffset.y = 5.0F;
153 japanese_font_config.GlyphMinAdvanceX = 13.0F;
154 japanese_font_config.PixelSnapH = true;
155 std::strncpy(japanese_font_config.Name, NOTO_SANS_JP,
156 sizeof(japanese_font_config.Name) - 1);
157 ImGuiIO& imgui_io = ImGui::GetIO();
158 if (!imgui_io.Fonts->AddFontFromMemoryTTF(
159 font_data->data(), static_cast<int>(font_data->size()),
160 ICON_FONT_SIZE, &japanese_font_config,
161 imgui_io.Fonts->GetGlyphRangesJapanese())) {
162 return absl::InternalError("Failed to add Japanese fonts");
163 }
164 return absl::OkStatus();
165}
166
167} // namespace
168
169absl::Status LoadPackageFonts() {
170 if (font_registry.fonts.empty()) {
171 // Initialize the font names and sizes with proper ImFontConfig
172 // initialization
174 FontConfig{KARLA_REGULAR, FONT_SIZE_DEFAULT, {}, {}},
175 FontConfig{ROBOTO_MEDIUM, FONT_SIZE_DEFAULT, {}, {}},
176 FontConfig{COUSINE_REGULAR, FONT_SIZE_DEFAULT, {}, {}},
177 FontConfig{IBM_PLEX_JP, FONT_SIZE_DEFAULT, {}, {}},
178 FontConfig{DROID_SANS, FONT_SIZE_DROID_SANS, {}, {}},
179 };
180 }
181
182 // Load fonts with associated icon and Japanese merges
183 for (const auto& font_config : font_registry.fonts) {
184 RETURN_IF_ERROR(LoadFont(font_config));
185 RETURN_IF_ERROR(AddIconFont(font_config));
186 RETURN_IF_ERROR(AddJapaneseFont(font_config));
187 }
188 return absl::OkStatus();
189}
190
191void SetActiveFontIndex(int index) {
192 ImGuiIO& io = ImGui::GetIO();
193 if (io.Fonts == nullptr || io.Fonts->Fonts.Size == 0) {
194 return;
195 }
196 if (index < 0 || index >= io.Fonts->Fonts.Size) {
197 index = 0;
198 }
199 io.FontDefault = io.Fonts->Fonts[index];
200}
201
202absl::Status ReloadPackageFont(const FontConfig& config) {
203 ImGuiIO& imgui_io = ImGui::GetIO();
204 std::string actual_font_path = SetFontPath(config.font_path);
205 if (!imgui_io.Fonts->AddFontFromFileTTF(actual_font_path.data(),
206 config.font_size)) {
207 return absl::InternalError(
208 absl::StrFormat("Failed to load font from %s", actual_font_path));
209 }
210 RETURN_IF_ERROR(AddIconFont(config));
211 RETURN_IF_ERROR(AddJapaneseFont(config));
212 return absl::OkStatus();
213}
214
215absl::Status LoadFontFromMemory(const std::string& name,
216 const std::string& data, float size_pixels) {
217 ImGuiIO& imgui_io = ImGui::GetIO();
218
219 // ImGui takes ownership of the data and will free it
220 void* font_data = ImGui::MemAlloc(data.size());
221 if (!font_data) {
222 return absl::InternalError("Failed to allocate memory for font data");
223 }
224 std::memcpy(font_data, data.data(), data.size());
225
226 ImFontConfig config;
227 std::strncpy(config.Name, name.c_str(), sizeof(config.Name) - 1);
228 config.Name[sizeof(config.Name) - 1] = 0;
229
230 if (!imgui_io.Fonts->AddFontFromMemoryTTF(
231 font_data, static_cast<int>(data.size()), size_pixels, &config)) {
232 ImGui::MemFree(font_data);
233 return absl::InternalError("Failed to load font from memory");
234 }
235
236 // We also need to add icons and Japanese characters to this new font
237 // Note: This is a simplified version of AddIconFont/AddJapaneseFont that
238 // works with the current font being added (since we can't easily merge into
239 // a font that's just been added without rebuilding atlas immediately)
240 // For now, we'll just load the base font. Merging requires more complex logic.
241
242 // Important: We must rebuild the font atlas!
243 // This is usually done by the backend, but since we changed fonts at runtime...
244 // Ideally, this should be done before NewFrame().
245 // If called during a frame, changes won't appear until texture is rebuilt.
246
247 return absl::OkStatus();
248}
249
250#ifdef __linux__
251void LoadSystemFonts() {
252 // Load Linux System Fonts into ImGui
253 // System font loading is now handled by NFD (Native File Dialog)
254 // This function is kept for compatibility but does nothing
255}
256#endif
257
258} // namespace yaze
static absl::StatusOr< std::filesystem::path > FindAsset(const std::string &relative_path)
Find an asset file in multiple standard locations.
#define ICON_MIN_MD
Definition icons.h:10
#define FONT_ICON_FILE_NAME_MD
Definition icons.h:8
absl::Status AddJapaneseFont(const FontConfig &)
std::string ResolveRepoFontPath(const std::string &font_path)
std::string SetFontPath(const std::string &font_path)
absl::Status LoadFont(const FontConfig &font_config)
absl::Status AddIconFont(const FontConfig &)
std::string GetBundleResourcePath()
GetBundleResourcePath returns the path to the bundle resource directory. Specific to MacOS.
absl::Status ReloadPackageFont(const FontConfig &config)
absl::Status LoadPackageFonts()
void LoadSystemFonts()
absl::Status LoadFontFromMemory(const std::string &name, const std::string &data, float size_pixels)
void SetActiveFontIndex(int index)
FontState font_registry
Definition font_loader.h:23
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
const char * font_path
Definition font_loader.h:13
std::vector< FontConfig > fonts
Definition font_loader.h:20