/ src / FontManager.cpp
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  }