A fork of https://github.com/crosspoint-reader/crosspoint-reader
1#include "FontCacheManager.h"
2
3#include <FontDecompressor.h>
4#include <Logging.h>
5
6#include <cstring>
7
8FontCacheManager::FontCacheManager(const std::map<int, EpdFontFamily>& fontMap) : fontMap_(fontMap) {}
9
10void FontCacheManager::setFontDecompressor(FontDecompressor* d) { fontDecompressor_ = d; }
11
12void FontCacheManager::clearCache() {
13 if (fontDecompressor_) fontDecompressor_->clearCache();
14}
15
16void FontCacheManager::prewarmCache(int fontId, const char* utf8Text, uint8_t styleMask) {
17 if (!fontDecompressor_ || fontMap_.count(fontId) == 0) return;
18
19 for (uint8_t i = 0; i < 4; i++) {
20 if (!(styleMask & (1 << i))) continue;
21 auto style = static_cast<EpdFontFamily::Style>(i);
22 const EpdFontData* data = fontMap_.at(fontId).getData(style);
23 if (!data || !data->groups) continue;
24 int missed = fontDecompressor_->prewarmCache(data, utf8Text);
25 if (missed > 0) {
26 LOG_DBG("FCM", "prewarmCache: %d glyph(s) not cached for style %d", missed, i);
27 }
28 }
29}
30
31void FontCacheManager::logStats(const char* label) {
32 if (fontDecompressor_) fontDecompressor_->logStats(label);
33}
34
35void FontCacheManager::resetStats() {
36 if (fontDecompressor_) fontDecompressor_->resetStats();
37}
38
39bool FontCacheManager::isScanning() const { return scanMode_ == ScanMode::Scanning; }
40
41void FontCacheManager::recordText(const char* text, int fontId, EpdFontFamily::Style style) {
42 scanText_ += text;
43 if (scanFontId_ < 0) scanFontId_ = fontId;
44 const uint8_t baseStyle = static_cast<uint8_t>(style) & 0x03;
45 const unsigned char* p = reinterpret_cast<const unsigned char*>(text);
46 uint32_t cpCount = 0;
47 while (*p) {
48 if ((*p & 0xC0) != 0x80) cpCount++;
49 p++;
50 }
51 scanStyleCounts_[baseStyle] += cpCount;
52}
53
54// --- PrewarmScope implementation ---
55
56FontCacheManager::PrewarmScope::PrewarmScope(FontCacheManager& manager) : manager_(&manager) {
57 manager_->scanMode_ = ScanMode::Scanning;
58 manager_->clearCache();
59 manager_->resetStats();
60 manager_->scanText_.clear();
61 manager_->scanText_.reserve(2048); // Pre-allocate to avoid heap fragmentation from repeated concat
62 memset(manager_->scanStyleCounts_, 0, sizeof(manager_->scanStyleCounts_));
63 manager_->scanFontId_ = -1;
64}
65
66void FontCacheManager::PrewarmScope::endScanAndPrewarm() {
67 manager_->scanMode_ = ScanMode::None;
68 if (manager_->scanText_.empty()) return;
69
70 // Build style bitmask from all styles that appeared during the scan
71 uint8_t styleMask = 0;
72 for (uint8_t i = 0; i < 4; i++) {
73 if (manager_->scanStyleCounts_[i] > 0) styleMask |= (1 << i);
74 }
75 if (styleMask == 0) styleMask = 1; // default to regular
76
77 manager_->prewarmCache(manager_->scanFontId_, manager_->scanText_.c_str(), styleMask);
78
79 // Free scan string memory
80 manager_->scanText_.clear();
81 manager_->scanText_.shrink_to_fit();
82}
83
84FontCacheManager::PrewarmScope::~PrewarmScope() {
85 if (active_) {
86 endScanAndPrewarm(); // no-op if already called (scanText_ is empty)
87 manager_->clearCache();
88 }
89}
90
91FontCacheManager::PrewarmScope::PrewarmScope(PrewarmScope&& other) noexcept
92 : manager_(other.manager_), active_(other.active_) {
93 other.active_ = false;
94}
95
96FontCacheManager::PrewarmScope FontCacheManager::createPrewarmScope() { return PrewarmScope(*this); }