Xteink-X4-crosspoint-reader/lib/EpdFont/SdFont.h
Eunchurn Park 68ce6db291
feat: Add custom font selection from SD card
Allow users to select custom fonts (.epdfont files) from the
/.crosspoint/fonts/ directory on the SD card for EPUB/TXT reading.

Features:
- New FontSelectionActivity for browsing and selecting fonts
- SdFont and SdFontFamily classes for loading fonts from SD card
- Dynamic font reloading without device reboot
- Reader cache invalidation when font changes
- Hash-based font ID generation for proper cache management

The custom fonts use the .epdfont binary format which supports:
- 2-bit antialiasing for smooth text rendering
- Efficient on-demand glyph loading with LRU cache
- Memory-optimized design for ESP32-C3 constraints
2026-01-18 18:46:23 +09:00

185 lines
5.1 KiB
C++

#pragma once
#include <SdFat.h>
#include <cstdint>
#include <list>
#include <string>
#include <unordered_map>
#include "EpdFontData.h"
#include "SdFontFormat.h"
/**
* LRU Cache for glyph bitmap data loaded from SD card.
* Automatically evicts least recently used entries when memory limit is reached.
*/
class GlyphBitmapCache {
public:
struct CacheEntry {
uint32_t codepoint;
uint8_t* bitmap;
uint32_t size;
};
private:
size_t maxCacheSize;
size_t currentSize;
std::list<CacheEntry> cacheList; // Most recent at front
std::unordered_map<uint32_t, std::list<CacheEntry>::iterator> cacheMap;
void evictOldest();
public:
explicit GlyphBitmapCache(size_t maxSize = 32768); // Default 32KB cache
~GlyphBitmapCache();
// Returns cached bitmap or nullptr if not cached
const uint8_t* get(uint32_t codepoint);
// Stores bitmap in cache, returns pointer to cached data
const uint8_t* put(uint32_t codepoint, const uint8_t* data, uint32_t size);
void clear();
size_t getUsedSize() const { return currentSize; }
size_t getMaxSize() const { return maxCacheSize; }
};
/**
* SD Card font data structure.
* Mimics EpdFontData interface but loads data on-demand from SD card.
*/
/**
* Simple fixed-size cache for glyph metadata (EpdGlyph) loaded on-demand.
* Uses a simple circular buffer to avoid STL container overhead on ESP32.
*/
class GlyphMetadataCache {
public:
static constexpr size_t MAX_ENTRIES = 128; // Balanced for Korean text while conserving memory
struct CacheEntry {
uint32_t codepoint;
EpdGlyph glyph;
bool valid;
};
private:
CacheEntry entries[MAX_ENTRIES];
size_t nextSlot;
public:
GlyphMetadataCache() : nextSlot(0) {
for (size_t i = 0; i < MAX_ENTRIES; i++) {
entries[i].valid = false;
}
}
const EpdGlyph* get(uint32_t codepoint);
const EpdGlyph* put(uint32_t codepoint, const EpdGlyph& glyph);
void clear();
};
class SdFontData {
private:
std::string filePath;
bool loaded;
// Font metadata (loaded once, kept in RAM)
EpdFontHeader header;
EpdFontInterval* intervals; // Dynamically allocated (~40KB for Korean)
// Note: glyphs are NOT preloaded - loaded on-demand to save memory
// Glyph metadata cache (per-font, small LRU cache)
mutable GlyphMetadataCache glyphCache;
// Bitmap cache (shared across all SdFontData instances)
static GlyphBitmapCache* sharedCache;
static int cacheRefCount;
// File handle for reading (opened on demand)
mutable FsFile fontFile;
// Binary search for glyph index
int findGlyphIndex(uint32_t codepoint) const;
// Load a single glyph from SD card by index
bool loadGlyphFromSD(int glyphIndex, EpdGlyph* outGlyph) const;
// Ensure font file is open (keeps handle open for performance)
bool ensureFileOpen() const;
public:
explicit SdFontData(const char* path);
~SdFontData();
// Disable copy to prevent resource issues
SdFontData(const SdFontData&) = delete;
SdFontData& operator=(const SdFontData&) = delete;
// Move constructor and assignment
SdFontData(SdFontData&& other) noexcept;
SdFontData& operator=(SdFontData&& other) noexcept;
// Load font header and metadata from SD card
bool load();
bool isLoaded() const { return loaded; }
// EpdFontData-compatible getters
uint8_t getAdvanceY() const { return header.advanceY; }
int8_t getAscender() const { return header.ascender; }
int8_t getDescender() const { return header.descender; }
bool is2Bit() const { return header.is2Bit != 0; }
uint32_t getIntervalCount() const { return header.intervalCount; }
uint32_t getGlyphCount() const { return header.glyphCount; }
// Get glyph by codepoint (loads bitmap on demand)
const EpdGlyph* getGlyph(uint32_t codepoint) const;
// Get bitmap for a glyph (loads from SD if not cached)
const uint8_t* getGlyphBitmap(uint32_t codepoint) const;
// Static cache management
static void setCacheSize(size_t maxBytes);
static void clearCache();
static size_t getCacheUsedSize();
};
/**
* SD Card font class - similar interface to EpdFont but loads from SD card.
*/
class SdFont {
private:
SdFontData* data;
bool ownsData;
public:
explicit SdFont(SdFontData* fontData, bool takeOwnership = false);
explicit SdFont(const char* filePath);
~SdFont();
// Disable copy
SdFont(const SdFont&) = delete;
SdFont& operator=(const SdFont&) = delete;
// Move semantics
SdFont(SdFont&& other) noexcept;
SdFont& operator=(SdFont&& other) noexcept;
bool load();
bool isLoaded() const { return data && data->isLoaded(); }
// EpdFont-compatible interface
void getTextDimensions(const char* string, int* w, int* h) const;
bool hasPrintableChars(const char* string) const;
const EpdGlyph* getGlyph(uint32_t cp) const;
const uint8_t* getGlyphBitmap(uint32_t cp) const;
// Metadata accessors
uint8_t getAdvanceY() const { return data ? data->getAdvanceY() : 0; }
int8_t getAscender() const { return data ? data->getAscender() : 0; }
int8_t getDescender() const { return data ? data->getDescender() : 0; }
bool is2Bit() const { return data ? data->is2Bit() : false; }
SdFontData* getData() const { return data; }
};