This commit is contained in:
jpirnay
2026-03-13 21:24:04 +01:00
83 changed files with 104973 additions and 120228 deletions
+96
View File
@@ -0,0 +1,96 @@
#include "FontCacheManager.h"
#include <FontDecompressor.h>
#include <Logging.h>
#include <cstring>
FontCacheManager::FontCacheManager(const std::map<int, EpdFontFamily>& fontMap) : fontMap_(fontMap) {}
void FontCacheManager::setFontDecompressor(FontDecompressor* d) { fontDecompressor_ = d; }
void FontCacheManager::clearCache() {
if (fontDecompressor_) fontDecompressor_->clearCache();
}
void FontCacheManager::prewarmCache(int fontId, const char* utf8Text, uint8_t styleMask) {
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);
}
void FontCacheManager::resetStats() {
if (fontDecompressor_) fontDecompressor_->resetStats();
}
bool FontCacheManager::isScanning() const { return scanMode_ == ScanMode::Scanning; }
void FontCacheManager::recordText(const char* text, int fontId, EpdFontFamily::Style style) {
scanText_ += text;
if (scanFontId_ < 0) scanFontId_ = fontId;
const uint8_t baseStyle = static_cast<uint8_t>(style) & 0x03;
const unsigned char* p = reinterpret_cast<const unsigned char*>(text);
uint32_t cpCount = 0;
while (*p) {
if ((*p & 0xC0) != 0x80) cpCount++;
p++;
}
scanStyleCounts_[baseStyle] += cpCount;
}
// --- PrewarmScope implementation ---
FontCacheManager::PrewarmScope::PrewarmScope(FontCacheManager& manager) : manager_(&manager) {
manager_->scanMode_ = ScanMode::Scanning;
manager_->clearCache();
manager_->resetStats();
manager_->scanText_.clear();
manager_->scanText_.reserve(2048); // Pre-allocate to avoid heap fragmentation from repeated concat
memset(manager_->scanStyleCounts_, 0, sizeof(manager_->scanStyleCounts_));
manager_->scanFontId_ = -1;
}
void FontCacheManager::PrewarmScope::endScanAndPrewarm() {
manager_->scanMode_ = ScanMode::None;
if (manager_->scanText_.empty()) return;
// Build style bitmask from all styles that appeared during the scan
uint8_t styleMask = 0;
for (uint8_t i = 0; i < 4; i++) {
if (manager_->scanStyleCounts_[i] > 0) styleMask |= (1 << i);
}
if (styleMask == 0) styleMask = 1; // default to regular
manager_->prewarmCache(manager_->scanFontId_, manager_->scanText_.c_str(), styleMask);
// Free scan string memory
manager_->scanText_.clear();
manager_->scanText_.shrink_to_fit();
}
FontCacheManager::PrewarmScope::~PrewarmScope() {
if (active_) {
endScanAndPrewarm(); // no-op if already called (scanText_ is empty)
manager_->clearCache();
}
}
FontCacheManager::PrewarmScope::PrewarmScope(PrewarmScope&& other) noexcept
: manager_(other.manager_), active_(other.active_) {
other.active_ = false;
}
FontCacheManager::PrewarmScope FontCacheManager::createPrewarmScope() { return PrewarmScope(*this); }
+55
View File
@@ -0,0 +1,55 @@
#pragma once
#include <EpdFontFamily.h>
#include <cstdint>
#include <map>
#include <string>
class FontDecompressor;
class FontCacheManager {
public:
explicit FontCacheManager(const std::map<int, EpdFontFamily>& fontMap);
void setFontDecompressor(FontDecompressor* d);
void clearCache();
void prewarmCache(int fontId, const char* utf8Text, uint8_t styleMask = 0x0F);
void logStats(const char* label = "render");
void resetStats();
// Scan-mode API: called by GfxRenderer::drawText() during scan pass
bool isScanning() const;
void recordText(const char* text, int fontId, EpdFontFamily::Style style);
// The FontDecompressor pointer, needed by GfxRenderer::getGlyphBitmap()
FontDecompressor* getDecompressor() const { return fontDecompressor_; }
// RAII scope for two-pass prewarm pattern
class PrewarmScope {
public:
explicit PrewarmScope(FontCacheManager& manager);
~PrewarmScope();
void endScanAndPrewarm();
PrewarmScope(PrewarmScope&& other) noexcept;
PrewarmScope& operator=(PrewarmScope&&) = delete;
PrewarmScope(const PrewarmScope&) = delete;
PrewarmScope& operator=(const PrewarmScope&) = delete;
private:
FontCacheManager* manager_;
bool active_ = true;
};
PrewarmScope createPrewarmScope();
private:
const std::map<int, EpdFontFamily>& fontMap_;
FontDecompressor* fontDecompressor_ = nullptr;
enum class ScanMode : uint8_t { None, Scanning };
ScanMode scanMode_ = ScanMode::None;
std::string scanText_;
uint32_t scanStyleCounts_[4] = {};
int scanFontId_ = -1;
};
+25 -8
View File
@@ -1,18 +1,23 @@
#include "GfxRenderer.h"
#include <FontDecompressor.h>
#include <Logging.h>
#include <Utf8.h>
#include <cstring>
#include "FontCacheManager.h"
const uint8_t* GfxRenderer::getGlyphBitmap(const EpdFontData* fontData, const EpdGlyph* glyph) const {
if (fontData->groups != nullptr) {
if (!fontDecompressor) {
auto* fd = fontCacheManager_ ? fontCacheManager_->getDecompressor() : nullptr;
if (!fd) {
LOG_ERR("GFX", "Compressed font but no FontDecompressor set");
return nullptr;
}
uint16_t glyphIndex = static_cast<uint16_t>(glyph - fontData->glyph);
return fontDecompressor->getBitmap(fontData, glyph, glyphIndex);
uint32_t glyphIndex = static_cast<uint32_t>(glyph - fontData->glyph);
// For page-buffer hits the pointer is stable for the page lifetime.
// For hot-group hits it is valid only until the next getBitmap() call — callers
// must consume it (draw the glyph) before requesting another bitmap.
return fd->getBitmap(fontData, glyph, glyphIndex);
}
return &fontData->bitmap[glyph->dataOffset];
}
@@ -749,6 +754,11 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y, const cha
return;
}
if (fontCacheManager_ && fontCacheManager_->isScanning()) {
fontCacheManager_->recordText(text, fontId, style);
return;
}
const auto fontIt = fontMap.find(fontId);
if (fontIt == fontMap.end()) {
LOG_ERR("GFX", "Font %d not found", fontId);
@@ -884,6 +894,7 @@ void GfxRenderer::drawText2BitLegacy(const int fontId, const int x, const int y,
#endif // ENABLE_RENDERCHAR_BENCHMARK
void GfxRenderer::drawLine(int x1, int y1, int x2, int y2, const bool state) const {
if (fontCacheManager_ && fontCacheManager_->isScanning()) return;
if (x1 == x2) {
if (y2 < y1) {
std::swap(y1, y2);
@@ -1355,6 +1366,7 @@ void GfxRenderer::drawIcon(const uint8_t bitmap[], const int x, const int y, con
void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, const int maxWidth, const int maxHeight,
const float cropX, const float cropY) const {
if (fontCacheManager_ && fontCacheManager_->isScanning()) return;
// For 1-bit bitmaps, use optimized 1-bit rendering path (no crop support for 1-bit)
if (bitmap.is1Bit() && cropX == 0.0f && cropY == 0.0f) {
drawBitmap1Bit(bitmap, x, y, maxWidth, maxHeight);
@@ -1729,13 +1741,18 @@ int GfxRenderer::getSpaceWidth(const int fontId, const EpdFontFamily::Style styl
return spaceGlyph ? fp4::toPixel(spaceGlyph->advanceX) : 0; // snap 12.4 fixed-point to nearest pixel
}
int GfxRenderer::getSpaceKernAdjust(const int fontId, const uint32_t leftCp, const uint32_t rightCp,
const EpdFontFamily::Style style) const {
int GfxRenderer::getSpaceAdvance(const int fontId, const uint32_t leftCp, const uint32_t rightCp,
const EpdFontFamily::Style style) const {
const auto fontIt = fontMap.find(fontId);
if (fontIt == fontMap.end()) return 0;
const auto& font = fontIt->second;
const int kernFP = font.getKerning(leftCp, ' ', style) + font.getKerning(' ', rightCp, style); // 4.4 fixed-point
return fp4::toPixel(kernFP); // snap 4.4 fixed-point to nearest pixel
const EpdGlyph* spaceGlyph = font.getGlyph(' ', style);
const int32_t spaceAdvanceFP = spaceGlyph ? static_cast<int32_t>(spaceGlyph->advanceX) : 0;
// Combine space advance + flanking kern into one fixed-point sum before snapping.
// Snapping the combined value avoids the +/-1 px error from snapping each component separately.
const int32_t kernFP = static_cast<int32_t>(font.getKerning(leftCp, ' ', style)) +
static_cast<int32_t>(font.getKerning(' ', rightCp, style));
return fp4::toPixel(spaceAdvanceFP + kernFP);
}
int GfxRenderer::getKerning(const int fontId, const uint32_t leftCp, const uint32_t rightCp,
+18 -9
View File
@@ -1,9 +1,11 @@
#pragma once
#include <EpdFontFamily.h>
#include <FontDecompressor.h>
#include <HalDisplay.h>
class FontCacheManager;
#include <cstring>
#include <map>
#include <string>
#include <vector>
@@ -39,7 +41,14 @@ class GfxRenderer {
uint8_t* frameBuffer = nullptr;
uint8_t* bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr};
std::map<int, EpdFontFamily> fontMap;
FontDecompressor* fontDecompressor = nullptr;
// Mutable because drawText() is const but needs to delegate scan-mode
// recording to the (non-const) FontCacheManager. Same pragmatic compromise
// as before, concentrated in a single pointer instead of four fields.
mutable FontCacheManager* fontCacheManager_ = nullptr;
void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, int* y, bool pixelState,
EpdFontFamily::Style style) const;
void freeBwBufferChunks();
template <Color color>
void drawPixelDither(int x, int y) const;
@@ -67,10 +76,9 @@ class GfxRenderer {
// Setup
void begin(); // must be called right after display.begin()
void insertFont(int fontId, EpdFontFamily font);
void setFontDecompressor(FontDecompressor* d) { fontDecompressor = d; }
void clearFontCache() {
if (fontDecompressor) fontDecompressor->clearCache();
}
void setFontCacheManager(FontCacheManager* m) { fontCacheManager_ = m; }
FontCacheManager* getFontCacheManager() const { return fontCacheManager_; }
const std::map<int, EpdFontFamily>& getFontMap() const { return fontMap; }
// Orientation control (affects logical width/height and coordinate transforms)
void setOrientation(const Orientation o) { orientation = o; }
@@ -118,9 +126,10 @@ class GfxRenderer {
void drawText(int fontId, int x, int y, const char* text, bool black = true,
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
int getSpaceWidth(int fontId, EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
/// Returns the kerning adjustment for a space between two codepoints:
/// kern(leftCp, ' ') + kern(' ', rightCp). Returns 0 if kerning is unavailable.
int getSpaceKernAdjust(int fontId, uint32_t leftCp, uint32_t rightCp, EpdFontFamily::Style style) const;
/// Returns the total inter-word advance: fp4::toPixel(spaceAdvance + kern(leftCp,' ') + kern(' ',rightCp)).
/// Using a single snap avoids the +/-1 px rounding error that arises when space advance and kern are
/// snapped separately and then added as integers.
int getSpaceAdvance(int fontId, uint32_t leftCp, uint32_t rightCp, EpdFontFamily::Style style) const;
/// Returns the kerning adjustment between two adjacent codepoints.
int getKerning(int fontId, uint32_t leftCp, uint32_t rightCp, EpdFontFamily::Style style) const;
int getTextAdvanceX(int fontId, const char* text, EpdFontFamily::Style style) const;