FontManager.cpp
1 #include "FontManager.h" 2 3 #include <EpdFontLoader.h> 4 #include <SDCardManager.h> 5 #include <StreamingEpdFont.h> 6 #include <esp_heap_caps.h> 7 8 #include <cstring> 9 10 #include "config.h" 11 12 FontManager& FontManager::instance() { 13 static FontManager instance; 14 return instance; 15 } 16 17 FontManager::FontManager() = default; 18 19 FontManager::~FontManager() { 20 unloadAllFonts(); 21 unloadExternalFont(); 22 } 23 24 void FontManager::init(GfxRenderer& r) { renderer = &r; } 25 26 bool FontManager::loadFontFamily(const char* familyName, int fontId) { 27 if (!renderer || !familyName || !*familyName) { 28 return false; 29 } 30 31 // Build base path 32 char basePath[64]; 33 snprintf(basePath, sizeof(basePath), "%s/%s", CONFIG_FONTS_DIR, familyName); 34 35 // Check if directory exists 36 if (!SdMan.exists(basePath)) { 37 return false; 38 } 39 40 struct StyleInfo { 41 const char* filename; 42 EpdFontFamily::Style style; 43 }; 44 const StyleInfo styles[] = {{"regular.epdfont", EpdFontFamily::REGULAR}, 45 {"bold.epdfont", EpdFontFamily::BOLD}, 46 {"italic.epdfont", EpdFontFamily::ITALIC}}; 47 48 LoadedFamily family; 49 family.fontId = fontId; 50 EpdFont* fontPtrs[4] = {nullptr, nullptr, nullptr, nullptr}; 51 52 for (const auto& s : styles) { 53 char fontPath[80]; 54 snprintf(fontPath, sizeof(fontPath), "%s/%s", basePath, s.filename); 55 56 if (!SdMan.exists(fontPath)) { 57 if (s.style == EpdFontFamily::REGULAR) { 58 return false; // Regular is required 59 } 60 continue; 61 } 62 63 LoadedFont loaded = _useStreamingFonts ? loadStreamingFont(fontPath) : loadSingleFont(fontPath); 64 65 if (!loaded.font && !loaded.streamingFont) { 66 if (s.style == EpdFontFamily::REGULAR) { 67 return false; 68 } 69 continue; 70 } 71 72 // Create EpdFont wrapper if streaming 73 if (loaded.streamingFont) { 74 loaded.font = new EpdFont(loaded.streamingFont->getData()); 75 renderer->setStreamingFont(fontId, s.style, loaded.streamingFont); 76 } 77 78 fontPtrs[s.style] = loaded.font; 79 family.fonts.push_back(loaded); 80 } 81 82 // Bold-italic (4th param) uses bold font; EpdFontFamily falls back to regular if nullptr 83 EpdFontFamily fontFamily(fontPtrs[0], fontPtrs[1], fontPtrs[2], fontPtrs[1]); 84 renderer->insertFont(fontId, fontFamily); 85 86 // Store for cleanup 87 loadedFamilies[fontId] = std::move(family); 88 return true; 89 } 90 91 FontManager::LoadedFont FontManager::loadSingleFont(const char* path) { 92 LoadedFont result = {}; 93 94 if (!SdMan.exists(path)) { 95 return result; 96 } 97 98 EpdFontLoader::LoadResult loaded = EpdFontLoader::loadFromFile(path); 99 if (!loaded.success) { 100 return result; 101 } 102 103 result.data = loaded.fontData; 104 result.bitmap = loaded.bitmap; 105 result.glyphs = loaded.glyphs; 106 result.intervals = loaded.intervals; 107 result.font = new EpdFont(result.data); 108 109 // Store sizes for memory profiling 110 result.bitmapSize = loaded.bitmapSize; 111 result.glyphsSize = loaded.glyphsSize; 112 result.intervalsSize = loaded.intervalsSize; 113 114 return result; 115 } 116 117 FontManager::LoadedFont FontManager::loadStreamingFont(const char* path) { 118 LoadedFont result = {}; 119 120 if (!SdMan.exists(path)) { 121 return result; 122 } 123 124 StreamingEpdFont* streamingFont = new (std::nothrow) StreamingEpdFont(); 125 if (!streamingFont) { 126 return result; 127 } 128 129 if (!streamingFont->load(path)) { 130 delete streamingFont; 131 return result; 132 } 133 134 result.streamingFont = streamingFont; 135 // glyphsSize and intervalsSize are tracked inside StreamingEpdFont 136 // totalSize() will use streamingFont->getMemoryUsage() 137 138 return result; 139 } 140 141 void FontManager::freeFont(LoadedFont& font) { 142 // Free streaming font if present 143 if (font.streamingFont) { 144 delete font.streamingFont; 145 font.streamingFont = nullptr; 146 } 147 148 // Free full-load font resources if present 149 delete font.font; 150 delete font.data; 151 delete[] font.bitmap; 152 delete[] font.glyphs; 153 delete[] font.intervals; 154 155 font = {}; 156 } 157 158 void FontManager::unloadFontFamily(int fontId) { 159 auto it = loadedFamilies.find(fontId); 160 if (it != loadedFamilies.end()) { 161 if (renderer) { 162 renderer->removeFont(fontId); 163 } 164 for (auto& f : it->second.fonts) { 165 freeFont(f); 166 } 167 loadedFamilies.erase(it); 168 } 169 } 170 171 void FontManager::unloadAllFonts() { 172 for (auto& pair : loadedFamilies) { 173 if (renderer) { 174 renderer->removeFont(pair.second.fontId); 175 } 176 for (auto& f : pair.second.fonts) { 177 freeFont(f); 178 } 179 } 180 loadedFamilies.clear(); 181 } 182 183 std::vector<std::string> FontManager::listAvailableFonts() { 184 std::vector<std::string> fonts; 185 186 FsFile dir = SdMan.open(CONFIG_FONTS_DIR); 187 if (!dir || !dir.isDirectory()) { 188 return fonts; 189 } 190 191 FsFile entry; 192 while (entry.openNext(&dir, O_RDONLY)) { 193 if (entry.isDirectory()) { 194 char name[64]; 195 entry.getName(name, sizeof(name)); 196 // Skip hidden directories 197 if (name[0] != '.') { 198 // Check if it has at least regular.epdfont 199 char regularPath[80]; 200 snprintf(regularPath, sizeof(regularPath), "%s/%s/regular.epdfont", CONFIG_FONTS_DIR, name); 201 if (SdMan.exists(regularPath)) { 202 fonts.push_back(name); 203 } 204 } 205 } 206 entry.close(); 207 } 208 dir.close(); 209 210 return fonts; 211 } 212 213 bool FontManager::fontFamilyExists(const char* familyName) { 214 if (!familyName || !*familyName) return false; 215 216 char path[80]; 217 snprintf(path, sizeof(path), "%s/%s/regular.epdfont", CONFIG_FONTS_DIR, familyName); 218 return SdMan.exists(path); 219 } 220 221 int FontManager::getFontId(const char* familyName, int builtinFontId) { 222 if (!familyName || !*familyName) { 223 return builtinFontId; 224 } 225 226 int targetId = generateFontId(familyName); 227 if (loadedFamilies.find(targetId) != loadedFamilies.end()) { 228 return targetId; 229 } 230 231 // Load from SD card 232 if (loadFontFamily(familyName, targetId)) { 233 return targetId; 234 } 235 236 return builtinFontId; 237 } 238 239 int FontManager::generateFontId(const char* familyName) { 240 // Simple hash for consistent font IDs 241 uint32_t hash = 5381; 242 while (*familyName) { 243 hash = ((hash << 5) + hash) + static_cast<uint8_t>(*familyName); 244 familyName++; 245 } 246 return static_cast<int>(hash); 247 } 248 249 bool FontManager::isBinFont(const char* familyName) { 250 if (!familyName) return false; 251 size_t len = strlen(familyName); 252 return len > 4 && strcmp(familyName + len - 4, ".bin") == 0; 253 } 254 255 int FontManager::getReaderFontId(const char* familyName, int builtinFontId) { 256 if (!familyName || !*familyName) { 257 // Using built-in font - unload any custom reader font and external font 258 if (_activeReaderFontId != 0 && _activeReaderFontId != builtinFontId) { 259 unloadFontFamily(_activeReaderFontId); 260 _activeReaderFontId = 0; 261 } 262 unloadExternalFont(); 263 return builtinFontId; 264 } 265 266 // Handle .bin fonts as external fonts (CJK fallback) 267 if (isBinFont(familyName)) { 268 // Unload any previous custom .epdfont reader font 269 if (_activeReaderFontId != 0 && _activeReaderFontId != builtinFontId) { 270 unloadFontFamily(_activeReaderFontId); 271 _activeReaderFontId = 0; 272 } 273 274 // Load as external font - provides fallback for CJK characters 275 loadExternalFont(familyName); 276 // Return builtin font ID - ASCII uses built-in, CJK falls back to external 277 return builtinFontId; 278 } 279 280 int targetId = generateFontId(familyName); 281 282 // If switching to a different custom font, unload previous 283 if (_activeReaderFontId != 0 && _activeReaderFontId != targetId) { 284 unloadFontFamily(_activeReaderFontId); 285 } 286 287 // Load new font if needed 288 if (loadedFamilies.find(targetId) == loadedFamilies.end()) { 289 if (!loadFontFamily(familyName, targetId)) { 290 _activeReaderFontId = 0; 291 return builtinFontId; 292 } 293 } 294 295 _activeReaderFontId = targetId; 296 return targetId; 297 } 298 299 bool FontManager::loadExternalFont(const char* filename) { 300 if (!renderer || !filename || !*filename) { 301 return false; 302 } 303 304 char path[80]; 305 snprintf(path, sizeof(path), "%s/%s", CONFIG_FONTS_DIR, filename); 306 307 // Allocate if needed 308 if (!_externalFont) { 309 _externalFont = new ExternalFont(); 310 } 311 312 if (!_externalFont->load(path)) { 313 delete _externalFont; 314 _externalFont = nullptr; 315 return false; 316 } 317 318 renderer->setExternalFont(_externalFont); 319 return true; 320 } 321 322 void FontManager::unloadExternalFont() { 323 if (_externalFont) { 324 if (renderer) { 325 renderer->setExternalFont(nullptr); 326 } 327 delete _externalFont; 328 _externalFont = nullptr; 329 } 330 } 331 332 void FontManager::logFontInfo() const { 333 // No-op: debug logging removed 334 } 335 336 void FontManager::logMemoryStatus(const char*) const { 337 // No-op: debug logging removed 338 } 339 340 void FontManager::unloadReaderFonts() { 341 // Unload any custom .epdfont reader font 342 if (_activeReaderFontId != 0) { 343 unloadFontFamily(_activeReaderFontId); 344 _activeReaderFontId = 0; 345 } 346 347 // Unload external CJK font 348 unloadExternalFont(); 349 } 350 351 size_t FontManager::getCustomFontMemoryUsage() const { 352 size_t total = 0; 353 for (const auto& pair : loadedFamilies) { 354 for (const auto& font : pair.second.fonts) { 355 total += font.totalSize(); 356 } 357 } 358 return total; 359 } 360 361 size_t FontManager::getExternalFontMemoryUsage() const { 362 if (_externalFont && _externalFont->isLoaded()) { 363 return ExternalFont::getCacheMemorySize(); 364 } 365 return 0; 366 } 367 368 size_t FontManager::getTotalFontMemoryUsage() const { 369 return getCustomFontMemoryUsage() + getExternalFontMemoryUsage(); 370 } 371 372 void FontManager::logMemoryReport() const { 373 // No-op: debug logging removed 374 }