mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-06 15:47:39 +03:00
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
185 lines
5.1 KiB
C++
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; }
|
|
};
|