Merge branch 'master' of https://github.com/jpirnay/crosspoint-reader into mybuild
This commit is contained in:
@@ -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); }
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user