Something went wrong. Try again.
A fork of https://github.com/crosspoint-reader/crosspoint-reader
Something went wrong. Try again.
9.0 kB · 250 lines
C++
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251#include "FontCacheManager.h"
#include <FontDecompressor.h>#include <Logging.h>#include <SdCardFont.h>#include <TtfEpdFont.h>#include <Utf8.h>
#include <algorithm>#include <cstring>
namespace {
char* appendUtf8Codepoint(char* output, const uint32_t codepoint) { if (codepoint < 0x80) { *output++ = static_cast<char>(codepoint); } else if (codepoint < 0x800) { *output++ = static_cast<char>(0xC0 | (codepoint >> 6)); *output++ = static_cast<char>(0x80 | (codepoint & 0x3F)); } else if (codepoint < 0x10000) { *output++ = static_cast<char>(0xE0 | (codepoint >> 12)); *output++ = static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)); *output++ = static_cast<char>(0x80 | (codepoint & 0x3F)); } else { *output++ = static_cast<char>(0xF0 | (codepoint >> 18)); *output++ = static_cast<char>(0x80 | ((codepoint >> 12) & 0x3F)); *output++ = static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F)); *output++ = static_cast<char>(0x80 | (codepoint & 0x3F)); } return output;}
} // namespace
FontCacheManager::FontCacheManager(const std::map<int, EpdFontFamily>& fontMap, const std::map<int, SdCardFont*>& sdCardFonts, const std::map<int, TtfEpdFont*>& ttfFonts) : fontMap_(fontMap), sdCardFonts_(sdCardFonts), ttfFonts_(ttfFonts) {}
void FontCacheManager::setFontDecompressor(FontDecompressor* d) { fontDecompressor_ = d; }
void FontCacheManager::clearCache() { if (fontDecompressor_) fontDecompressor_->clearCache(); for (auto& [id, font] : sdCardFonts_) { font->clearCache(); }#if CROSSPOINT_VECTOR_FONTS for (auto& [id, font] : ttfFonts_) { if (font) font->clearCache(); }#endif}
void FontCacheManager::releaseSdFontCaches() { if (fontDecompressor_) fontDecompressor_->clearCache(); for (auto& [id, font] : sdCardFonts_) { font->releaseResidentCaches(); }#if CROSSPOINT_VECTOR_FONTS for (auto& [id, font] : ttfFonts_) { if (font) font->releaseResidentCaches(); }#endif}
void FontCacheManager::prewarmCache(int fontId, const char* utf8Text, uint8_t styleMask, bool accumulate) { // TTF (vector) font prewarm path. This is the single dispatch every draw path // funnels through (reader endScanAndPrewarm, the settings preview, UI text), // so building here covers them all. accumulate=false means "this is the whole // glyph set for this render" → replace; accumulate=true → add incrementally. // styleMask is ignored: a TTF face has no synthesized bold/italic here.#if CROSSPOINT_VECTOR_FONTS auto tit = ttfFonts_.find(fontId); if (tit != ttfFonts_.end() && tit->second) { if (accumulate) { tit->second->addCoverage(utf8Text); } else { tit->second->build(utf8Text); } return; }#endif
// SD card font prewarm path: prewarm all requested styles in one call auto it = sdCardFonts_.find(fontId); if (it != sdCardFonts_.end()) { int missed = it->second->prewarm(utf8Text, styleMask, /*metadataOnly=*/false, /*loadKernLig=*/true, accumulate); if (missed > 0) { LOG_DBG("FCM", "prewarmCache(SD): %d glyph(s) not found (styleMask=0x%02X)", missed, styleMask); } return; }
// Standard compressed font prewarm path: loop over all requested styles if (!fontDecompressor_ || fontMap_.count(fontId) == 0) return;
for (uint8_t i = 0; i < 4; i++) { if (!(styleMask & (1 << i))) continue; auto style = static_cast<EpdFontFamily::Style>(i); const EpdFontData* data = fontMap_.at(fontId).getData(style); if (!data || !data->groups) continue; int missed = fontDecompressor_->prewarmCache(data, utf8Text); if (missed > 0) { LOG_DBG("FCM", "prewarmCache: %d glyph(s) not cached for style %d", missed, i); } }}
void FontCacheManager::logStats(const char* label) { if (fontDecompressor_) fontDecompressor_->logStats(label); for (auto& [id, font] : sdCardFonts_) { font->logStats(label); }}
void FontCacheManager::resetStats() { if (fontDecompressor_) fontDecompressor_->resetStats(); for (auto& [id, font] : sdCardFonts_) { font->resetStats(); }}
bool FontCacheManager::isScanning() const { return scanMode_ == ScanMode::Scanning; }
uint8_t FontCacheManager::resolveScanStyle(int fontId, EpdFontFamily::Style style) const { const uint8_t baseStyle = static_cast<uint8_t>(style) & 0x03;
const auto sdFont = sdCardFonts_.find(fontId); if (sdFont != sdCardFonts_.end()) return sdFont->second->resolveStyle(baseStyle);
const auto font = fontMap_.find(fontId); if (font == fontMap_.end()) return baseStyle;
const EpdFontData* resolvedData = font->second.getData(static_cast<EpdFontFamily::Style>(baseStyle)); for (uint8_t candidate = 0; candidate < 4; candidate++) { if (font->second.getData(static_cast<EpdFontFamily::Style>(candidate)) == resolvedData) return candidate; } return baseStyle;}
void FontCacheManager::recordText(const char* text, int fontId, EpdFontFamily::Style style) { if (!text || *text == '\0') return;
uint8_t fontSlot = scanFontCount_; for (uint8_t i = 0; i < scanFontCount_; i++) { if (scanFontIds_[i] == fontId) { fontSlot = i; break; } } if (fontSlot == scanFontCount_) { if (scanFontCount_ >= MAX_SCAN_FONTS) return; scanFontIds_[scanFontCount_++] = fontId; }
const uint8_t resolvedStyle = resolveScanStyle(fontId, style); const uint8_t group = fontSlot * 4 + resolvedStyle; const unsigned char* cursor = reinterpret_cast<const unsigned char*>(text); while (*cursor) { const uint32_t codepoint = utf8NextCodepoint(&cursor); if (codepoint == 0) break;
const uint32_t packed = (static_cast<uint32_t>(fontSlot) << SCAN_FONT_SHIFT) | (static_cast<uint32_t>(resolvedStyle) << SCAN_STYLE_SHIFT) | codepoint; bool found = false; for (uint16_t i = 0; i < scanCodepointCount_; i++) { if (scanCodepoints_[i] == packed) { found = true; break; } } if (found) continue;
if (scanCodepointCount_ >= MAX_SCAN_CODEPOINTS) { if (!scanOverflowWarned_) { LOG_DBG("FCM", "Scan codepoint cap (%u) reached; excess glyphs will load on demand", static_cast<unsigned>(MAX_SCAN_CODEPOINTS)); scanOverflowWarned_ = true; } continue; }
scanCodepoints_[scanCodepointCount_++] = packed; scanGroupCounts_[group]++; }}
// --- PrewarmScope implementation ---
FontCacheManager::PrewarmScope::PrewarmScope(FontCacheManager& manager) : manager_(&manager) { manager_->scanMode_ = ScanMode::Scanning; manager_->clearCache(); manager_->resetStats(); manager_->scanCodepointCount_ = 0; manager_->scanFontCount_ = 0; manager_->scanOverflowWarned_ = false; memset(manager_->scanGroupCounts_, 0, sizeof(manager_->scanGroupCounts_));}
void FontCacheManager::PrewarmScope::endScanAndPrewarm() { manager_->scanMode_ = ScanMode::None; if (manager_->scanCodepointCount_ == 0) return;
std::sort(manager_->scanCodepoints_, manager_->scanCodepoints_ + manager_->scanCodepointCount_);
uint16_t groupStarts[SCAN_GROUP_COUNT] = {}; for (uint8_t group = 1; group < SCAN_GROUP_COUNT; group++) { groupStarts[group] = groupStarts[group - 1] + manager_->scanGroupCounts_[group - 1]; }
// Each packed entry provides four bytes, enough for one UTF-8 codepoint. // Encoding high groups first means a terminator can overwrite only a group // that has already been prewarmed; unread lower groups remain intact. for (int group = SCAN_GROUP_COUNT - 1; group >= 0; group--) { const uint16_t groupCount = manager_->scanGroupCounts_[group]; if (groupCount == 0) continue;
const uint16_t groupStart = groupStarts[group]; char* const utf8Text = reinterpret_cast<char*>(manager_->scanCodepoints_ + groupStart); char* output = utf8Text; for (uint16_t i = 0; i < groupCount; i++) { const uint32_t codepoint = manager_->scanCodepoints_[groupStart + i] & SCAN_CODEPOINT_MASK; output = appendUtf8Codepoint(output, codepoint); } *output = '\0';
const uint8_t fontSlot = static_cast<uint8_t>(group) / 4; const uint8_t style = static_cast<uint8_t>(group) & 0x03; // This group is the complete glyph set for one font/style in this render. manager_->prewarmCache(manager_->scanFontIds_[fontSlot], utf8Text, 1 << style, /*accumulate=*/false); }
manager_->scanCodepointCount_ = 0; manager_->scanFontCount_ = 0; memset(manager_->scanGroupCounts_, 0, sizeof(manager_->scanGroupCounts_));}
FontCacheManager::PrewarmScope::~PrewarmScope() { if (active_) { endScanAndPrewarm(); // no-op if already called manager_->clearCache(); }}
FontCacheManager::PrewarmScope::PrewarmScope(PrewarmScope&& other) noexcept : manager_(other.manager_), active_(other.active_) { other.active_ = false;}
FontCacheManager::PrewarmScope FontCacheManager::createPrewarmScope() { return PrewarmScope(*this); }