Backup: Stable state with EPUB reader fixes (Freeze, OOM, Speed, State, Tooling)

This commit is contained in:
Antigravity Agent
2026-01-19 18:44:45 -05:00
parent 21277e03eb
commit 1237f01ac2
34 changed files with 1600 additions and 192 deletions

View File

@@ -0,0 +1,323 @@
#include "CustomEpdFont.h"
#include <HardwareSerial.h>
#include <SDCardManager.h>
#include <algorithm>
CustomEpdFont::CustomEpdFont(const String& filePath, const EpdFontData* data, uint32_t offsetIntervals,
uint32_t offsetGlyphs, uint32_t offsetBitmaps)
: EpdFont(data),
filePath(filePath),
offsetIntervals(offsetIntervals),
offsetGlyphs(offsetGlyphs),
offsetBitmaps(offsetBitmaps) {
// Initialize bitmap cache
for (size_t i = 0; i < BITMAP_CACHE_CAPACITY; i++) {
bitmapCache[i].data = nullptr;
bitmapCache[i].size = 0;
bitmapCache[i].codePoint = 0;
bitmapCache[i].lastAccess = 0;
}
// Initialize glyph cache
for (size_t i = 0; i < GLYPH_CACHE_CAPACITY; i++) {
glyphCache[i].codePoint = 0xFFFFFFFF;
glyphCache[i].lastAccess = 0;
}
}
CustomEpdFont::~CustomEpdFont() {
clearCache();
if (fontFile.isOpen()) {
fontFile.close();
}
}
void CustomEpdFont::clearCache() const {
for (size_t i = 0; i < BITMAP_CACHE_CAPACITY; i++) {
if (bitmapCache[i].data) {
free(bitmapCache[i].data);
bitmapCache[i].data = nullptr;
}
bitmapCache[i].size = 0;
}
}
const EpdGlyph* CustomEpdFont::getGlyph(uint32_t cp, const EpdFontStyles::Style style) const {
// Serial.printf("CustomEpdFont::getGlyph cp=%u style=%d this=%p\n", cp, style, this);
// Check glyph cache first
for (size_t i = 0; i < GLYPH_CACHE_CAPACITY; i++) {
if (glyphCache[i].codePoint == cp) {
glyphCache[i].lastAccess = ++currentAccessCount;
// Serial.printf(" Cache hit: %p\n", &glyphCache[i].glyph);
return &glyphCache[i].glyph;
}
}
const EpdFontData* data = getData(style);
if (!data) {
Serial.println("CustomEpdFont::getGlyph: No data!");
return nullptr;
}
const EpdUnicodeInterval* intervals = data->intervals;
const int count = data->intervalCount;
uint32_t currentCp = cp;
bool triedFallback = false;
// Loop to allow for fallback attempts
while (true) {
// Check glyph cache first
for (size_t i = 0; i < GLYPH_CACHE_CAPACITY; i++) {
if (glyphCache[i].codePoint == currentCp) {
glyphCache[i].lastAccess = ++currentAccessCount;
// Serial.printf(" Cache hit: %p\n", &glyphCache[i].glyph);
return &glyphCache[i].glyph;
}
}
const EpdFontData* data = getData(style);
if (!data) {
Serial.println("CustomEpdFont::getGlyph: No data!");
return nullptr;
}
const EpdUnicodeInterval* intervals = data->intervals;
const int count = data->intervalCount;
int left = 0;
int right = count - 1;
bool foundInterval = false;
uint32_t glyphIndex = 0;
const EpdUnicodeInterval* foundIntervalPtr = nullptr;
while (left <= right) {
const int mid = left + (right - left) / 2;
const EpdUnicodeInterval* interval = &intervals[mid];
if (currentCp < interval->first) {
right = mid - 1;
} else if (currentCp > interval->last) {
left = mid + 1;
} else {
// Found interval. Calculate index.
glyphIndex = interval->offset + (currentCp - interval->first);
foundIntervalPtr = interval;
foundInterval = true;
break;
}
}
if (foundInterval) {
// Calculate total glyphs to ensure bounds safety
uint32_t totalGlyphCount = (offsetBitmaps - offsetGlyphs) / 13;
if (glyphIndex >= totalGlyphCount) {
Serial.printf("CustomEpdFont: Glyph index %u out of bounds (total %u)\n", glyphIndex, totalGlyphCount);
// If out of bounds, and we haven't tried fallback, try it.
if (!triedFallback) {
if (currentCp == 0x2018 || currentCp == 0x2019) {
currentCp = 0x0027;
triedFallback = true;
continue;
} else if (currentCp == 0x201C || currentCp == 0x201D) {
currentCp = 0x0022;
triedFallback = true;
continue;
}
}
return nullptr;
}
uint32_t glyphFileOffset = offsetGlyphs + (glyphIndex * 13);
if (!fontFile.isOpen()) {
if (!SdMan.openFileForRead("CustomFont", filePath.c_str(), fontFile)) {
Serial.printf("CustomEpdFont: Failed to open file %s\n", filePath.c_str());
return nullptr;
}
}
if (!fontFile.seekSet(glyphFileOffset)) {
Serial.printf("CustomEpdFont: Failed to seek to glyph offset %u\n", glyphFileOffset);
fontFile.close();
return nullptr;
}
uint8_t glyphBuf[13];
if (fontFile.read(glyphBuf, 13) != 13) {
Serial.println("CustomEpdFont: Read failed (glyph entry)");
fontFile.close();
return nullptr;
}
uint8_t w = glyphBuf[0];
uint8_t h = glyphBuf[1];
uint8_t adv = glyphBuf[2];
int8_t l = (int8_t)glyphBuf[3];
// glyphBuf[4] unused
int8_t t = (int8_t)glyphBuf[5];
// glyphBuf[6] unused
uint16_t dLen = glyphBuf[7] | (glyphBuf[8] << 8);
uint32_t dOffset = glyphBuf[9] | (glyphBuf[10] << 8) | (glyphBuf[11] << 16) | (glyphBuf[12] << 24);
/*
Serial.printf("[CEF] Parsed Glyph %u: Off=%u, Len=%u, W=%u, H=%u, L=%d, T=%d\n",
glyphIndex, dOffset, dLen, w, h, l, t);
*/
// Removed individual reads since we read all 13 bytes
// fontFile.close(); // Keep file open for performance
// Find slot in glyph cache (LRU)
int slotIndex = -1;
uint32_t minAccess = 0xFFFFFFFF;
for (size_t i = 0; i < GLYPH_CACHE_CAPACITY; i++) {
if (glyphCache[i].codePoint == 0xFFFFFFFF) {
slotIndex = i;
break;
}
if (glyphCache[i].lastAccess < minAccess) {
minAccess = glyphCache[i].lastAccess;
slotIndex = i;
}
}
// Populate cache
glyphCache[slotIndex].codePoint = currentCp;
glyphCache[slotIndex].lastAccess = ++currentAccessCount;
glyphCache[slotIndex].glyph.dataOffset = dOffset;
glyphCache[slotIndex].glyph.dataLength = dLen;
glyphCache[slotIndex].glyph.width = w;
glyphCache[slotIndex].glyph.height = h;
glyphCache[slotIndex].glyph.advanceX = adv;
glyphCache[slotIndex].glyph.left = l;
glyphCache[slotIndex].glyph.top = t;
// Serial.printf(" Loaded to cache[%d]: %p\n", slotIndex, &glyphCache[slotIndex].glyph);
return &glyphCache[slotIndex].glyph;
}
// Not found in intervals. Try fallback.
if (!triedFallback) {
if (currentCp == 0x2018 || currentCp == 0x2019) { // Left/Right single quote
currentCp = 0x0027; // ASCII apostrophe
triedFallback = true;
continue; // Retry with fallback CP
} else if (currentCp == 0x201C || currentCp == 0x201D) { // Left/Right double quote
currentCp = 0x0022; // ASCII double quote
triedFallback = true;
continue; // Retry with fallback CP
} else if (currentCp == 160) { // Non-breaking space
currentCp = 32; // Space
triedFallback = true;
continue;
}
}
return nullptr;
}
return nullptr;
}
const uint8_t* CustomEpdFont::loadGlyphBitmap(const EpdGlyph* glyph, uint8_t* buffer,
const EpdFontStyles::Style style) const {
if (!glyph) return nullptr;
// Serial.printf("CustomEpdFont::loadGlyphBitmap glyph=%p len=%u\n", glyph, glyph->dataLength);
if (glyph->dataLength == 0) {
return nullptr; // Empty glyph
}
if (glyph->dataLength > 32768) {
Serial.printf("CustomEpdFont: Glyph too large (%u)\n", glyph->dataLength);
return nullptr;
}
// Serial.printf("[CEF] loadGlyphBitmap: len=%u, off=%u\n", glyph->dataLength, glyph->dataOffset);
uint32_t offset = glyph->dataOffset;
// Check bitmap cache
for (size_t i = 0; i < BITMAP_CACHE_CAPACITY; i++) {
if (bitmapCache[i].data && bitmapCache[i].codePoint == offset) {
bitmapCache[i].lastAccess = ++currentAccessCount;
if (buffer) {
memcpy(buffer, bitmapCache[i].data, std::min((size_t)glyph->dataLength, (size_t)bitmapCache[i].size));
return buffer;
}
return bitmapCache[i].data;
}
}
// Cache miss - read from SD
if (!fontFile.isOpen()) {
if (!SdMan.openFileForRead("CustomFont", filePath.c_str(), fontFile)) {
Serial.printf("Failed to open font file: %s\n", filePath.c_str());
return nullptr;
}
}
if (!fontFile.seekSet(offsetBitmaps + offset)) {
Serial.printf("CustomEpdFont: Failed to seek to bitmap offset %u\n", offsetBitmaps + offset);
fontFile.close();
return nullptr;
}
// Allocate memory manually
uint8_t* newData = (uint8_t*)malloc(glyph->dataLength);
if (!newData) {
Serial.println("CustomEpdFont: MALLOC FAILED");
fontFile.close();
return nullptr;
}
size_t bytesRead = fontFile.read(newData, glyph->dataLength);
// fontFile.close(); // Keep file open
if (bytesRead != glyph->dataLength) {
Serial.printf("CustomEpdFont: Read mismatch. Expected %u, got %u\n", glyph->dataLength, bytesRead);
free(newData);
return nullptr;
}
// Find slot in bitmap cache (LRU)
int slotIndex = -1;
for (size_t i = 0; i < BITMAP_CACHE_CAPACITY; i++) {
if (bitmapCache[i].data == nullptr) {
slotIndex = i;
break;
}
}
if (slotIndex == -1) {
uint32_t minAccess = 0xFFFFFFFF;
for (size_t i = 0; i < BITMAP_CACHE_CAPACITY; i++) {
if (bitmapCache[i].lastAccess < minAccess) {
minAccess = bitmapCache[i].lastAccess;
slotIndex = i;
}
}
// Free evicted slot
if (bitmapCache[slotIndex].data) {
free(bitmapCache[slotIndex].data);
bitmapCache[slotIndex].data = nullptr;
}
}
// Store in cache
bitmapCache[slotIndex].codePoint = offset;
bitmapCache[slotIndex].lastAccess = ++currentAccessCount;
bitmapCache[slotIndex].data = newData;
bitmapCache[slotIndex].size = glyph->dataLength;
if (buffer) {
memcpy(buffer, newData, glyph->dataLength);
return buffer;
}
return newData;
}

View File

@@ -0,0 +1,50 @@
#pragma once
#include <SDCardManager.h>
#include <vector>
#include "EpdFont.h"
struct BitmapCacheEntry {
uint32_t codePoint = 0;
uint32_t lastAccess = 0;
uint8_t* data = nullptr;
uint16_t size = 0;
};
struct GlyphStructCacheEntry {
uint32_t codePoint = 0xFFFFFFFF; // Invalid initial value
uint32_t lastAccess = 0;
EpdGlyph glyph;
};
class CustomEpdFont : public EpdFont {
public:
CustomEpdFont(const String& filePath, const EpdFontData* data, uint32_t offsetIntervals, uint32_t offsetGlyphs,
uint32_t offsetBitmaps);
~CustomEpdFont() override;
const EpdGlyph* getGlyph(uint32_t cp, const EpdFontStyles::Style style = EpdFontStyles::REGULAR) const override;
const uint8_t* loadGlyphBitmap(const EpdGlyph* glyph, uint8_t* buffer,
const EpdFontStyles::Style style = EpdFontStyles::REGULAR) const override;
private:
String filePath;
mutable FsFile fontFile;
uint32_t offsetIntervals;
uint32_t offsetGlyphs;
uint32_t offsetBitmaps;
// Bitmap Cache (Pixel data)
static constexpr size_t BITMAP_CACHE_CAPACITY = 10;
mutable BitmapCacheEntry bitmapCache[BITMAP_CACHE_CAPACITY];
// Glyph Struct Cache (Metadata)
static constexpr size_t GLYPH_CACHE_CAPACITY = 200;
mutable GlyphStructCacheEntry glyphCache[GLYPH_CACHE_CAPACITY];
mutable uint32_t currentAccessCount = 0;
void clearCache() const;
};

View File

@@ -1,11 +1,12 @@
#include "EpdFont.h"
#include <Arduino.h>
#include <Utf8.h>
#include <algorithm>
void EpdFont::getTextBounds(const char* string, const int startX, const int startY, int* minX, int* minY, int* maxX,
int* maxY) const {
int* maxY, const EpdFontStyles::Style style) const {
*minX = startX;
*minY = startY;
*maxX = startX;
@@ -19,15 +20,13 @@ void EpdFont::getTextBounds(const char* string, const int startX, const int star
const int cursorY = startY;
uint32_t cp;
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&string)))) {
const EpdGlyph* glyph = getGlyph(cp);
const EpdGlyph* glyph = getGlyph(cp, style);
if (!glyph) {
// TODO: Replace with fallback glyph property?
glyph = getGlyph('?');
glyph = getGlyph('?', style);
}
if (!glyph) {
// TODO: Better handle this?
continue;
}
@@ -39,31 +38,32 @@ void EpdFont::getTextBounds(const char* string, const int startX, const int star
}
}
void EpdFont::getTextDimensions(const char* string, int* w, int* h) const {
void EpdFont::getTextDimensions(const char* string, int* w, int* h, const EpdFontStyles::Style style) const {
int minX = 0, minY = 0, maxX = 0, maxY = 0;
getTextBounds(string, 0, 0, &minX, &minY, &maxX, &maxY);
getTextBounds(string, 0, 0, &minX, &minY, &maxX, &maxY, style);
*w = maxX - minX;
*h = maxY - minY;
}
bool EpdFont::hasPrintableChars(const char* string) const {
bool EpdFont::hasPrintableChars(const char* string, const EpdFontStyles::Style style) const {
int w = 0, h = 0;
getTextDimensions(string, &w, &h);
getTextDimensions(string, &w, &h, style);
return w > 0 || h > 0;
}
const EpdGlyph* EpdFont::getGlyph(const uint32_t cp) const {
const EpdGlyph* EpdFont::getGlyph(const uint32_t cp, const EpdFontStyles::Style style) const {
const EpdFontData* data = getData(style);
if (!data) return nullptr;
const EpdUnicodeInterval* intervals = data->intervals;
const int count = data->intervalCount;
if (count == 0) return nullptr;
// Binary search for O(log n) lookup instead of O(n)
// Critical for Korean fonts with many unicode intervals
int left = 0;
int right = count - 1;
@@ -76,10 +76,19 @@ const EpdGlyph* EpdFont::getGlyph(const uint32_t cp) const {
} else if (cp > interval->last) {
left = mid + 1;
} else {
// Found: cp >= interval->first && cp <= interval->last
return &data->glyph[interval->offset + (cp - interval->first)];
if (data->glyph) {
return &data->glyph[interval->offset + (cp - interval->first)];
}
return nullptr;
}
}
return nullptr;
}
const uint8_t* EpdFont::loadGlyphBitmap(const EpdGlyph* glyph, uint8_t* buffer,
const EpdFontStyles::Style style) const {
const EpdFontData* data = getData(style);
if (!data || !data->bitmap) return nullptr;
return data->bitmap + glyph->dataOffset;
}

View File

@@ -1,15 +1,24 @@
#pragma once
#include "EpdFontData.h"
#include "EpdFontStyles.h"
class EpdFont {
void getTextBounds(const char* string, int startX, int startY, int* minX, int* minY, int* maxX, int* maxY) const;
protected:
void getTextBounds(const char* string, int startX, int startY, int* minX, int* minY, int* maxX, int* maxY,
const EpdFontStyles::Style style = EpdFontStyles::REGULAR) const;
public:
const EpdFontData* data;
explicit EpdFont(const EpdFontData* data) : data(data) {}
~EpdFont() = default;
void getTextDimensions(const char* string, int* w, int* h) const;
bool hasPrintableChars(const char* string) const;
virtual ~EpdFont() = default;
const EpdGlyph* getGlyph(uint32_t cp) const;
void getTextDimensions(const char* string, int* w, int* h,
const EpdFontStyles::Style style = EpdFontStyles::REGULAR) const;
bool hasPrintableChars(const char* string, const EpdFontStyles::Style style = EpdFontStyles::REGULAR) const;
virtual const EpdGlyph* getGlyph(uint32_t cp, const EpdFontStyles::Style style = EpdFontStyles::REGULAR) const;
virtual const uint8_t* loadGlyphBitmap(const EpdGlyph* glyph, uint8_t* buffer,
const EpdFontStyles::Style style = EpdFontStyles::REGULAR) const;
virtual const EpdFontData* getData(const EpdFontStyles::Style style = EpdFontStyles::REGULAR) const { return data; }
};

View File

@@ -1,13 +1,13 @@
#include "EpdFontFamily.h"
const EpdFont* EpdFontFamily::getFont(const Style style) const {
if (style == BOLD && bold) {
if (style == EpdFontStyles::BOLD && bold) {
return bold;
}
if (style == ITALIC && italic) {
if (style == EpdFontStyles::ITALIC && italic) {
return italic;
}
if (style == BOLD_ITALIC) {
if (style == EpdFontStyles::BOLD_ITALIC) {
if (boldItalic) {
return boldItalic;
}
@@ -23,15 +23,24 @@ const EpdFont* EpdFontFamily::getFont(const Style style) const {
}
void EpdFontFamily::getTextDimensions(const char* string, int* w, int* h, const Style style) const {
getFont(style)->getTextDimensions(string, w, h);
getFont(style)->getTextDimensions(string, w, h, style);
}
bool EpdFontFamily::hasPrintableChars(const char* string, const Style style) const {
return getFont(style)->hasPrintableChars(string);
return getFont(style)->hasPrintableChars(string, style);
}
const EpdFontData* EpdFontFamily::getData(const Style style) const { return getFont(style)->data; }
const EpdFontData* EpdFontFamily::getData(const Style style) const {
const EpdFont* font = getFont(style);
return font ? font->getData(style) : nullptr;
}
const EpdGlyph* EpdFontFamily::getGlyph(const uint32_t cp, const Style style) const {
return getFont(style)->getGlyph(cp);
};
const EpdFont* font = getFont(style);
return font ? font->getGlyph(cp, style) : nullptr;
}
const uint8_t* EpdFontFamily::loadGlyphBitmap(const EpdGlyph* glyph, uint8_t* buffer, const Style style) const {
const EpdFont* font = getFont(style);
return font ? font->loadGlyphBitmap(glyph, buffer, style) : nullptr;
}

View File

@@ -1,24 +1,33 @@
#pragma once
#include "EpdFont.h"
#include "EpdFontStyles.h"
class EpdFontFamily {
public:
enum Style : uint8_t { REGULAR = 0, BOLD = 1, ITALIC = 2, BOLD_ITALIC = 3 };
typedef EpdFontStyles::Style Style;
static constexpr Style REGULAR = EpdFontStyles::REGULAR;
static constexpr Style BOLD = EpdFontStyles::BOLD;
static constexpr Style ITALIC = EpdFontStyles::ITALIC;
static constexpr Style BOLD_ITALIC = EpdFontStyles::BOLD_ITALIC;
EpdFontFamily() : regular(nullptr), bold(nullptr), italic(nullptr), boldItalic(nullptr) {}
explicit EpdFontFamily(const EpdFont* regular, const EpdFont* bold = nullptr, const EpdFont* italic = nullptr,
const EpdFont* boldItalic = nullptr)
: regular(regular), bold(bold), italic(italic), boldItalic(boldItalic) {}
~EpdFontFamily() = default;
void getTextDimensions(const char* string, int* w, int* h, Style style = REGULAR) const;
bool hasPrintableChars(const char* string, Style style = REGULAR) const;
const EpdFontData* getData(Style style = REGULAR) const;
const EpdGlyph* getGlyph(uint32_t cp, Style style = REGULAR) const;
void getTextDimensions(const char* string, int* w, int* h, Style style = EpdFontStyles::REGULAR) const;
bool hasPrintableChars(const char* string, Style style = EpdFontStyles::REGULAR) const;
const EpdFontData* getData(Style style = EpdFontStyles::REGULAR) const;
const EpdGlyph* getGlyph(uint32_t cp, Style style = EpdFontStyles::REGULAR) const;
const EpdFont* getFont(Style style = EpdFontStyles::REGULAR) const;
// Helper to load glyph bitmap seamlessly from either static or custom (SD-based) fonts
const uint8_t* loadGlyphBitmap(const EpdGlyph* glyph, uint8_t* buffer, Style style = EpdFontStyles::REGULAR) const;
private:
const EpdFont* regular;
const EpdFont* bold;
const EpdFont* italic;
const EpdFont* boldItalic;
const EpdFont* getFont(Style style) const;
};

View File

@@ -0,0 +1,5 @@
#pragma once
namespace EpdFontStyles {
enum Style { REGULAR = 0, BOLD = 1, ITALIC = 2, BOLD_ITALIC = 3 };
}

View File

@@ -15,12 +15,14 @@ parser.add_argument("size", type=int, help="font size to use.")
parser.add_argument("fontstack", action="store", nargs='+', help="list of font files, ordered by descending priority.")
parser.add_argument("--2bit", dest="is2Bit", action="store_true", help="generate 2-bit greyscale bitmap instead of 1-bit black and white.")
parser.add_argument("--additional-intervals", dest="additional_intervals", action="append", help="Additional code point intervals to export as min,max. This argument can be repeated.")
parser.add_argument("--binary", dest="isBinary", action="store_true", help="output a binary .epdfont file instead of a C header.")
args = parser.parse_args()
GlyphProps = namedtuple("GlyphProps", ["width", "height", "advance_x", "left", "top", "data_length", "data_offset", "code_point"])
font_stack = [freetype.Face(f) for f in args.fontstack]
is2Bit = args.is2Bit
isBinary = args.isBinary
size = args.size
font_name = args.name
@@ -60,45 +62,6 @@ intervals = [
(0x2200, 0x22FF),
# Arrows
(0x2190, 0x21FF),
### CJK ###
# Core Unified Ideographs
# (0x4E00, 0x9FFF),
# # Extension A
# (0x3400, 0x4DBF),
# # Extension B
# (0x20000, 0x2A6DF),
# # Extension CF
# (0x2A700, 0x2EBEF),
# # Extension G
# (0x30000, 0x3134F),
# # Hiragana
# (0x3040, 0x309F),
# # Katakana
# (0x30A0, 0x30FF),
# # Katakana Phonetic Extensions
# (0x31F0, 0x31FF),
# # Halfwidth Katakana
# (0xFF60, 0xFF9F),
# # Hangul Syllables
# (0xAC00, 0xD7AF),
# # Hangul Jamo
# (0x1100, 0x11FF),
# # Hangul Compatibility Jamo
# (0x3130, 0x318F),
# # Hangul Jamo Extended-A
# (0xA960, 0xA97F),
# # Hangul Jamo Extended-B
# (0xD7B0, 0xD7FF),
# # CJK Radicals Supplement
# (0x2E80, 0x2EFF),
# # Kangxi Radicals
# (0x2F00, 0x2FDF),
# # CJK Symbols and Punctuation
# (0x3000, 0x303F),
# # CJK Compatibility Forms
# (0xFE30, 0xFE4F),
# # CJK Compatibility Ideographs
# (0xF900, 0xFAFF),
]
add_ints = []
@@ -200,16 +163,6 @@ for i_start, i_end in intervals:
if (bitmap.width * bitmap.rows) % 4 != 0:
px = px << (4 - (bitmap.width * bitmap.rows) % 4) * 2
pixels2b.append(px)
# for y in range(bitmap.rows):
# line = ''
# for x in range(bitmap.width):
# pixelPosition = y * bitmap.width + x
# byte = pixels2b[pixelPosition // 4]
# bit_index = (3 - (pixelPosition % 4)) * 2
# line += '#' if ((byte >> bit_index) & 3) > 0 else '.'
# print(line)
# print('')
else:
# Downsample to 1-bit bitmap - treat any 2+ as black
pixelsbw = []
@@ -228,16 +181,6 @@ for i_start, i_end in intervals:
px = px << (8 - (bitmap.width * bitmap.rows) % 8)
pixelsbw.append(px)
# for y in range(bitmap.rows):
# line = ''
# for x in range(bitmap.width):
# pixelPosition = y * bitmap.width + x
# byte = pixelsbw[pixelPosition // 8]
# bit_index = 7 - (pixelPosition % 8)
# line += '#' if (byte >> bit_index) & 1 else '.'
# print(line)
# print('')
pixels = pixels2b if is2Bit else pixelsbw
# Build output data
@@ -265,33 +208,79 @@ for index, glyph in enumerate(all_glyphs):
glyph_data.extend([b for b in packed])
glyph_props.append(props)
print(f"/**\n * generated by fontconvert.py\n * name: {font_name}\n * size: {size}\n * mode: {'2-bit' if is2Bit else '1-bit'}\n */")
print("#pragma once")
print("#include \"EpdFontData.h\"\n")
print(f"static const uint8_t {font_name}Bitmaps[{len(glyph_data)}] = {{")
for c in chunks(glyph_data, 16):
print (" " + " ".join(f"0x{b:02X}," for b in c))
print ("};\n");
if isBinary:
import struct
with open(f"{font_name}.epdfont", "wb") as f:
# Magic
f.write(b"EPDF")
# Metrics (22 bytes)
# intervalCount (uint32_t), advanceY (uint8_t), ascender (int32_t), descender (int32_t), is2Bit (uint8_t), totalGlyphCount (uint32_t)
f.write(struct.pack("<IBiiBI", len(intervals), norm_ceil(face.size.height), norm_ceil(face.size.ascender), norm_floor(face.size.descender), 1 if is2Bit else 0, len(glyph_props)))
# Intervals
offset = 0
for i_start, i_end in intervals:
f.write(struct.pack("<III", i_start, i_end, offset))
offset += i_end - i_start + 1
# Glyphs
for g in glyph_props:
# dataOffset (uint32_t), dataLength (uint16_t), width (uint8_t), height (uint8_t), advanceX (uint8_t), left (int8_t), top (int8_t)
# wait, GlyphProps has width, height, advance_x, left, top, data_length, data_offset, code_point
# We need: dataOffset (4), dataLength (2), width (1), height (1), advanceX (1), left (1), top (1) = 11 bytes?
# Let's use 13 bytes as planned to be safer or just 11.
# Original EpdGlyph:
# uint32_t dataOffset;
# uint16_t dataLength;
# uint8_t width;
# uint8_t height;
# uint8_t advanceX;
# int8_t left;
# int8_t top;
# Total: 4+2+1+1+1+1+1 = 11 bytes.
# I will use 13 bytes to align better if needed, but 11 is fine.
# Let's use 13 bytes as per plan: 4+2+1+1+1+1+1 + 2 padding.
# CustomEpdFont.cpp expects:
# glyphBuf[0] = w
# glyphBuf[1] = h
# glyphBuf[2] = adv
# glyphBuf[3] = l (signed)
# glyphBuf[4] = unused
# glyphBuf[5] = t (signed)
# glyphBuf[6] = unused
# glyphBuf[7-8] = dLen
# glyphBuf[9-12] = dOffset
f.write(struct.pack("<BBB b B b B H I", g.width, g.height, g.advance_x, g.left, 0, g.top, 0, g.data_length, g.data_offset))
# Bitmaps
f.write(bytes(glyph_data))
print(f"Generated {font_name}.epdfont")
else:
print(f"/**\n * generated by fontconvert.py\n * name: {font_name}\n * size: {size}\n * mode: {'2-bit' if is2Bit else '1-bit'}\n */")
print("#pragma once")
print("#include \"EpdFontData.h\"\n")
print(f"static const uint8_t {font_name}Bitmaps[{len(glyph_data)}] = {{")
for c in chunks(glyph_data, 16):
print (" " + " ".join(f"0x{b:02X}," for b in c))
print ("};\n");
print(f"static const EpdGlyph {font_name}Glyphs[] = {{")
for i, g in enumerate(glyph_props):
print (" { " + ", ".join([f"{a}" for a in list(g[:-1])]),"},", f"// {chr(g.code_point) if g.code_point != 92 else '<backslash>'}")
print ("};\n");
print(f"static const EpdGlyph {font_name}Glyphs[] = {{")
for i, g in enumerate(glyph_props):
print (" { " + ", ".join([f"{a}" for a in list(g[:-1])]),"},", f"// {chr(g.code_point) if g.code_point != 92 else '<backslash>'}")
print ("};\n");
print(f"static const EpdUnicodeInterval {font_name}Intervals[] = {{")
offset = 0
for i_start, i_end in intervals:
print (f" {{ 0x{i_start:X}, 0x{i_end:X}, 0x{offset:X} }},")
offset += i_end - i_start + 1
print ("};\n");
print(f"static const EpdUnicodeInterval {font_name}Intervals[] = {{")
offset = 0
for i_start, i_end in intervals:
print (f" {{ 0x{i_start:X}, 0x{i_end:X}, 0x{offset:X} }},")
offset += i_end - i_start + 1
print ("};\n");
print(f"static const EpdFontData {font_name} = {{")
print(f" {font_name}Bitmaps,")
print(f" {font_name}Glyphs,")
print(f" {font_name}Intervals,")
print(f" {len(intervals)},")
print(f" {norm_ceil(face.size.height)},")
print(f" {norm_ceil(face.size.ascender)},")
print(f" {norm_floor(face.size.descender)},")
print(f" {'true' if is2Bit else 'false'},")
print("};")
print(f"static const EpdFontData {font_name} = {{")
print(f" {font_name}Bitmaps,")
print(f" {font_name}Glyphs,")
print(f" {font_name}Intervals,")
print(f" {len(intervals)},")
print(f" {norm_ceil(face.size.height)},")
print(f" {norm_ceil(face.size.ascender)},")
print(f" {norm_floor(face.size.descender)},")
print(f" {'true' if is2Bit else 'false'},")
print("};")

View File

@@ -0,0 +1,74 @@
#include "EpdFontLoader.h"
#include <HardwareSerial.h>
#include <cstring>
#include <string>
#include "../../src/CrossPointSettings.h"
#include "../../src/managers/FontManager.h"
void EpdFontLoader::loadFontsFromSd(GfxRenderer& renderer) {
// Check settings for custom font
if (SETTINGS.fontFamily == CrossPointSettings::FONT_CUSTOM) {
if (strlen(SETTINGS.customFontFamily) > 0) {
Serial.printf("Loading custom font: %s size %d\n", SETTINGS.customFontFamily, SETTINGS.fontSize);
Serial.flush();
// Map enum size to point size roughly (or use customFontSize if non-zero)
int size = 12; // default
// Map generic sizes (Small, Medium, Large, XL) to likely point sizes if not specified
// Assume standard sizes: 12, 14, 16, 18
switch (SETTINGS.fontSize) {
case CrossPointSettings::SMALL:
size = 12;
break;
case CrossPointSettings::MEDIUM:
size = 14;
break;
case CrossPointSettings::LARGE:
size = 16;
break;
case CrossPointSettings::EXTRA_LARGE:
size = 18;
break;
}
EpdFontFamily* family = FontManager::getInstance().getCustomFontFamily(SETTINGS.customFontFamily, size);
if (family) {
// IDs are usually static consts. For custom font, we need a dynamic ID or reserved ID.
// In main.cpp or somewhere, a range might be reserved or we replace an existing one?
// The stash code in main.cpp step 120 showed:
// "Calculate hash ID manually... int id = (int)hash;"
// "renderer.insertFont(id, *msgFont);"
std::string key = std::string(SETTINGS.customFontFamily) + "-" + std::to_string(size);
uint32_t hash = 5381;
for (char c : key) hash = ((hash << 5) + hash) + c;
int id = (int)hash;
Serial.printf("[FontLoader] Inserting custom font '%s' with ID %d (key: %s)\n", SETTINGS.customFontFamily, id,
key.c_str());
renderer.insertFont(id, *family);
} else {
Serial.println("Failed to load custom font family");
}
}
}
}
int EpdFontLoader::getBestFontId(const char* familyName, int size) {
if (!familyName || strlen(familyName) == 0) return -1;
// We assume the font is loaded if we are asking for its ID,
// or at least that the ID generation is deterministic.
// The renderer uses the ID to look up the font.
// If we return an ID that isn't inserted, renderer might crash or show nothing.
// So we should ideally check if it's available.
// For now, just return the deterministic hash.
std::string key = std::string(familyName) + "-" + std::to_string(size);
uint32_t hash = 5381;
for (char c : key) hash = ((hash << 5) + hash) + c;
return (int)hash;
}

View File

@@ -0,0 +1,9 @@
#pragma once
#include <GfxRenderer.h>
class EpdFontLoader {
public:
static void loadFontsFromSd(GfxRenderer& renderer);
static int getBestFontId(const char* familyName, int size);
};

View File

@@ -319,11 +319,16 @@ bool Epub::clearCache() const {
}
void Epub::setupCacheDir() const {
if (SdMan.exists(cachePath.c_str())) {
return;
// Always try to create, just in case.
if (!SdMan.mkdir(cachePath.c_str())) {
// If mkdir failed, it might already exist. Check if it's a directory.
// SdMan doesn't allow checking type easily without opening.
// But let's log the detailed failure state.
bool exists = SdMan.exists(cachePath.c_str());
Serial.printf("[%lu] [EBP] mkdir failed for %s. Exists? %s\n", millis(), cachePath.c_str(), exists ? "YES" : "NO");
} else {
// Serial.printf("[%lu] [EBP] Created cache directory: %s\n", millis(), cachePath.c_str());
}
SdMan.mkdir(cachePath.c_str());
}
const std::string& Epub::getCachePath() const { return cachePath; }

View File

@@ -52,6 +52,11 @@ std::unique_ptr<Page> Page::deserialize(FsFile& file) {
uint16_t count;
serialization::readPod(file, count);
if (count > 1000) {
Serial.printf("[%lu] [PGE] WARNING: Suspicious element count %d\n", millis(), count);
return nullptr;
}
for (uint16_t i = 0; i < count; i++) {
uint8_t tag;
serialization::readPod(file, tag);
@@ -60,7 +65,7 @@ std::unique_ptr<Page> Page::deserialize(FsFile& file) {
auto pl = PageLine::deserialize(file);
page->elements.push_back(std::move(pl));
} else {
Serial.printf("[%lu] [PGE] Deserialization failed: Unknown tag %u\n", millis(), tag);
Serial.printf("[%lu] [PGE] Deserialization failed: Unknown tag %u at index %d\n", millis(), tag, i);
return nullptr;
}
}

View File

@@ -11,6 +11,7 @@
constexpr int MAX_COST = std::numeric_limits<int>::max();
void ParsedText::addWord(std::string word, const EpdFontFamily::Style fontStyle) {
// Serial.printf("addWord: %s\n", word.c_str());
if (word.empty()) return;
words.push_back(std::move(word));

View File

@@ -7,12 +7,13 @@
#include "parsers/ChapterHtmlSlimParser.h"
namespace {
constexpr uint8_t SECTION_FILE_VERSION = 9;
constexpr uint8_t SECTION_FILE_VERSION = 10;
constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + sizeof(int) + sizeof(float) + sizeof(bool) + sizeof(uint8_t) +
sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t);
} // namespace
uint32_t Section::onPageComplete(std::unique_ptr<Page> page) {
SDLock lock;
if (!file) {
Serial.printf("[%lu] [SCT] File not open for writing page %d\n", millis(), pageCount);
return 0;
@@ -23,7 +24,6 @@ uint32_t Section::onPageComplete(std::unique_ptr<Page> page) {
Serial.printf("[%lu] [SCT] Failed to serialize page %d\n", millis(), pageCount);
return 0;
}
Serial.printf("[%lu] [SCT] Page %d processed\n", millis(), pageCount);
pageCount++;
return position;
@@ -54,6 +54,7 @@ void Section::writeSectionFileHeader(const int fontId, const float lineCompressi
bool Section::loadSectionFile(const int fontId, const float lineCompression, const bool extraParagraphSpacing,
const uint8_t paragraphAlignment, const uint16_t viewportWidth,
const uint16_t viewportHeight) {
SDLock lock;
if (!SdMan.openFileForRead("SCT", filePath, file)) {
return false;
}
@@ -93,14 +94,14 @@ bool Section::loadSectionFile(const int fontId, const float lineCompression, con
serialization::readPod(file, pageCount);
file.close();
Serial.printf("[%lu] [SCT] Deserialization succeeded: %d pages\n", millis(), pageCount);
return true;
}
// Your updated class method (assuming you are using the 'SD' object, which is a wrapper for a specific filesystem)
bool Section::clearCache() const {
SDLock lock;
if (!SdMan.exists(filePath.c_str())) {
Serial.printf("[%lu] [SCT] Cache does not exist, no action needed\n", millis());
return true;
}
@@ -109,7 +110,6 @@ bool Section::clearCache() const {
return false;
}
Serial.printf("[%lu] [SCT] Cache cleared successfully\n", millis());
return true;
}
@@ -117,6 +117,7 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
const uint8_t paragraphAlignment, const uint16_t viewportWidth,
const uint16_t viewportHeight, const std::function<void()>& progressSetupFn,
const std::function<void(int)>& progressFn) {
SDLock lock;
constexpr uint32_t MIN_SIZE_FOR_PROGRESS = 50 * 1024; // 50KB
const auto localPath = epub->getSpineItem(spineIndex).href;
const auto tmpHtmlPath = epub->getCachePath() + "/.tmp_" + std::to_string(spineIndex) + ".html";
@@ -161,8 +162,6 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
return false;
}
Serial.printf("[%lu] [SCT] Streamed temp HTML to %s (%d bytes)\n", millis(), tmpHtmlPath.c_str(), fileSize);
// Only show progress bar for larger chapters where rendering overhead is worth it
if (progressSetupFn && fileSize >= MIN_SIZE_FOR_PROGRESS) {
progressSetupFn();
@@ -217,6 +216,7 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
}
std::unique_ptr<Page> Section::loadPageFromSectionFile() {
SDLock lock;
if (!SdMan.openFileForRead("SCT", filePath, file)) {
return nullptr;
}
@@ -224,9 +224,23 @@ std::unique_ptr<Page> Section::loadPageFromSectionFile() {
file.seek(HEADER_SIZE - sizeof(uint32_t));
uint32_t lutOffset;
serialization::readPod(file, lutOffset);
if (lutOffset > file.size() || lutOffset < HEADER_SIZE) {
Serial.printf("[%lu] [SCT] Invalid LUT offset %u (file size %u)\n", millis(), lutOffset, file.size());
file.close();
return nullptr;
}
file.seek(lutOffset + sizeof(uint32_t) * currentPage);
uint32_t pagePos;
serialization::readPod(file, pagePos);
if (pagePos > file.size()) {
Serial.printf("[%lu] [SCT] Invalid page pos %u for page %d\n", millis(), pagePos, currentPage);
file.close();
return nullptr;
}
file.seek(pagePos);
auto page = Page::deserialize(file);

View File

@@ -16,6 +16,7 @@ void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int
auto wordXposIt = wordXpos.begin();
for (size_t i = 0; i < words.size(); i++) {
Serial.printf("[%lu] [TXB] Rendering word: %s\n", millis(), wordIt->c_str());
renderer.drawText(fontId, *wordXposIt + x, y, wordIt->c_str(), true, *wordStylesIt);
std::advance(wordIt, 1);
@@ -52,6 +53,7 @@ std::unique_ptr<TextBlock> TextBlock::deserialize(FsFile& file) {
// Word count
serialization::readPod(file, wc);
Serial.printf("[%lu] [TXB] Deserializing TextBlock: %u words\n", millis(), wc);
// Sanity check: prevent allocation of unreasonably large lists (max 10000 words per block)
if (wc > 10000) {
@@ -63,7 +65,13 @@ std::unique_ptr<TextBlock> TextBlock::deserialize(FsFile& file) {
words.resize(wc);
wordXpos.resize(wc);
wordStyles.resize(wc);
for (auto& w : words) serialization::readString(file, w);
wordStyles.resize(wc);
int i = 0;
for (auto& w : words) {
if (i % 100 == 0 && i > 0) Serial.printf("[%lu] [TXB] Reading word %d/%d\n", millis(), i, wc);
serialization::readString(file, w);
i++;
}
for (auto& x : wordXpos) serialization::readPod(file, x);
for (auto& s : wordStyles) serialization::readPod(file, s);

View File

@@ -55,6 +55,7 @@ void ChapterHtmlSlimParser::startNewTextBlock(const TextBlock::Style style) {
}
void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* name, const XML_Char** atts) {
// Serial.printf("startElement: %s\n", name);
auto* self = static_cast<ChapterHtmlSlimParser*>(userData);
// Middle of skip
@@ -268,9 +269,17 @@ void XMLCALL ChapterHtmlSlimParser::endElement(void* userData, const XML_Char* n
}
bool ChapterHtmlSlimParser::parseAndBuildPages() {
startNewTextBlock((TextBlock::Style)this->paragraphAlignment);
SDLock lock;
Serial.printf("[%lu] [EHP] parseAndBuildPages start. Heap: %u\n", millis(), ESP.getFreeHeap());
Serial.printf("[%lu] [EHP] Calling startNewTextBlock\n", millis());
startNewTextBlock((TextBlock::Style)this->paragraphAlignment);
Serial.printf("[%lu] [EHP] startNewTextBlock returned\n", millis());
Serial.printf("[%lu] [EHP] Creating XML parser\n", millis());
const XML_Parser parser = XML_ParserCreate(nullptr);
if (parser) Serial.printf("[%lu] [EHP] Parser created\n", millis());
int done;
if (!parser) {
@@ -306,6 +315,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
}
const size_t len = file.read(buf, 1024);
// Serial.printf("[%lu] [EHP] Read %d bytes\n", millis(), len);
if (len == 0 && file.available() > 0) {
Serial.printf("[%lu] [EHP] File read error\n", millis());
@@ -331,6 +341,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
done = file.available() == 0;
if (XML_ParseBuffer(parser, static_cast<int>(len), done) == XML_STATUS_ERROR) {
Serial.printf("[%lu] [EHP] XML_ParseBuffer returned error\n", millis());
Serial.printf("[%lu] [EHP] Parse error at line %lu:\n%s\n", millis(), XML_GetCurrentLineNumber(parser),
XML_ErrorString(XML_GetErrorCode(parser)));
XML_StopParser(parser, XML_FALSE); // Stop any pending processing
@@ -340,6 +351,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
file.close();
return false;
}
vTaskDelay(1);
} while (!done);
XML_StopParser(parser, XML_FALSE); // Stop any pending processing

View File

@@ -2,7 +2,13 @@
#include <Utf8.h>
void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { fontMap.insert({fontId, font}); }
void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { fontMap[fontId] = font; }
void GfxRenderer::clearCustomFonts(const int startId) {
for (auto it = fontMap.lower_bound(startId); it != fontMap.end();) {
it = fontMap.erase(it);
}
}
void GfxRenderer::rotateCoordinates(const int x, const int y, int* rotatedX, int* rotatedY) const {
switch (orientation) {
@@ -101,10 +107,12 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y, const cha
// no printable characters
if (!font.hasPrintableChars(text, style)) {
Serial.printf("[%lu] [GFX] text '%s' has no printable chars\n", millis(), text);
return;
}
uint32_t cp;
const char* p = text;
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) {
renderChar(font, cp, &xpos, &yPos, black, style);
}
@@ -164,8 +172,6 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con
bool isScaled = false;
int cropPixX = std::floor(bitmap.getWidth() * cropX / 2.0f);
int cropPixY = std::floor(bitmap.getHeight() * cropY / 2.0f);
Serial.printf("[%lu] [GFX] Cropping %dx%d by %dx%d pix, is %s\n", millis(), bitmap.getWidth(), bitmap.getHeight(),
cropPixX, cropPixY, bitmap.isTopDown() ? "top-down" : "bottom-up");
if (maxWidth > 0 && (1.0f - cropX) * bitmap.getWidth() > maxWidth) {
scale = static_cast<float>(maxWidth) / static_cast<float>((1.0f - cropX) * bitmap.getWidth());
@@ -175,7 +181,6 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con
scale = std::min(scale, static_cast<float>(maxHeight) / static_cast<float>((1.0f - cropY) * bitmap.getHeight()));
isScaled = true;
}
Serial.printf("[%lu] [GFX] Scaling by %f - %s\n", millis(), scale, isScaled ? "scaled" : "not scaled");
// Calculate output row size (2 bits per pixel, packed into bytes)
// IMPORTANT: Use int, not uint8_t, to avoid overflow for images > 1020 pixels wide
@@ -446,7 +451,16 @@ int GfxRenderer::getSpaceWidth(const int fontId) const {
return 0;
}
return fontMap.at(fontId).getGlyph(' ', EpdFontFamily::REGULAR)->advanceX;
const EpdGlyph* glyph = fontMap.at(fontId).getGlyph(' ', EpdFontFamily::REGULAR);
if (!glyph) {
// Serial.printf("[%lu] [GFX] Font %d (Regular) has no space glyph! Using fallback.\n", millis(), fontId);
const EpdFontData* data = fontMap.at(fontId).getData(EpdFontFamily::REGULAR);
if (data) {
return data->ascender / 3;
}
return 0;
}
return glyph->advanceX;
}
int GfxRenderer::getFontAscenderSize(const int fontId) const {
@@ -455,7 +469,13 @@ int GfxRenderer::getFontAscenderSize(const int fontId) const {
return 0;
}
return fontMap.at(fontId).getData(EpdFontFamily::REGULAR)->ascender;
const EpdFontData* data = fontMap.at(fontId).getData(EpdFontFamily::REGULAR);
if (!data) {
Serial.printf("[%lu] [GFX] Font %d (Regular) has no data!\n", millis(), fontId);
return 0;
}
return data->ascender;
}
int GfxRenderer::getLineHeight(const int fontId) const {
@@ -464,7 +484,13 @@ int GfxRenderer::getLineHeight(const int fontId) const {
return 0;
}
return fontMap.at(fontId).getData(EpdFontFamily::REGULAR)->advanceY;
const EpdFontData* data = fontMap.at(fontId).getData(EpdFontFamily::REGULAR);
if (!data) {
Serial.printf("[%lu] [GFX] Font %d (Regular) has no data!\n", millis(), fontId);
return 0;
}
return data->advanceY;
}
void GfxRenderer::drawButtonHints(const int fontId, const char* btn1, const char* btn2, const char* btn3,
@@ -595,7 +621,9 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y
const int left = glyph->left;
const int top = glyph->top;
const uint8_t* bitmap = &font.getData(style)->bitmap[offset];
// Use loadGlyphBitmap to support both static and custom (SD-based) fonts
uint8_t* buffer = nullptr; // Not used for now, as we expect a pointer or cache
const uint8_t* bitmap = font.loadGlyphBitmap(glyph, buffer, style);
if (bitmap != nullptr) {
for (int glyphY = 0; glyphY < height; glyphY++) {
@@ -695,8 +723,6 @@ bool GfxRenderer::storeBwBuffer() {
memcpy(bwBufferChunks[i], frameBuffer + offset, BW_BUFFER_CHUNK_SIZE);
}
Serial.printf("[%lu] [GFX] Stored BW buffer in %zu chunks (%zu bytes each)\n", millis(), BW_BUFFER_NUM_CHUNKS,
BW_BUFFER_CHUNK_SIZE);
return true;
}
@@ -742,7 +768,6 @@ void GfxRenderer::restoreBwBuffer() {
einkDisplay.cleanupGrayscaleBuffers(frameBuffer);
freeBwBufferChunks();
Serial.printf("[%lu] [GFX] Restored and freed BW buffer chunks\n", millis());
}
/**
@@ -760,24 +785,25 @@ void GfxRenderer::renderChar(const EpdFontFamily& fontFamily, const uint32_t cp,
const bool pixelState, const EpdFontFamily::Style style) const {
const EpdGlyph* glyph = fontFamily.getGlyph(cp, style);
if (!glyph) {
// TODO: Replace with fallback glyph property?
glyph = fontFamily.getGlyph('?', style);
}
// no glyph?
if (!glyph) {
Serial.printf("[%lu] [GFX] No glyph for codepoint %d\n", millis(), cp);
return;
}
const int is2Bit = fontFamily.getData(style)->is2Bit;
const uint32_t offset = glyph->dataOffset;
const EpdFont* font = fontFamily.getFont(style);
if (!font) return;
const EpdFontData* data = font->getData(style);
if (!data) return;
const int is2Bit = data->is2Bit;
const uint8_t width = glyph->width;
const uint8_t height = glyph->height;
const int left = glyph->left;
const uint8_t* bitmap = nullptr;
bitmap = &fontFamily.getData(style)->bitmap[offset];
const uint8_t* bitmap = font->loadGlyphBitmap(glyph, nullptr, style);
if (bitmap != nullptr) {
for (int glyphY = 0; glyphY < height; glyphY++) {

View File

@@ -2,8 +2,10 @@
#include <EInkDisplay.h>
#include <EpdFontFamily.h>
#include <EpdFontStyles.h>
#include <map>
#include <string>
#include "Bitmap.h"
@@ -46,6 +48,7 @@ class GfxRenderer {
// Setup
void insertFont(int fontId, EpdFontFamily font);
void clearCustomFonts(int startId = 1000);
// Orientation control (affects logical width/height and coordinate transforms)
void setOrientation(const Orientation o) { orientation = o; }
@@ -72,16 +75,16 @@ class GfxRenderer {
void fillPolygon(const int* xPoints, const int* yPoints, int numPoints, bool state = true) const;
// Text
int getTextWidth(int fontId, const char* text, EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
int getTextWidth(int fontId, const char* text, EpdFontFamily::Style style = EpdFontStyles::REGULAR) const;
void drawCenteredText(int fontId, int y, const char* text, bool black = true,
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
EpdFontFamily::Style style = EpdFontStyles::REGULAR) const;
void drawText(int fontId, int x, int y, const char* text, bool black = true,
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
EpdFontFamily::Style style = EpdFontStyles::REGULAR) const;
int getSpaceWidth(int fontId) const;
int getFontAscenderSize(int fontId) const;
int getLineHeight(int fontId) const;
std::string truncatedText(int fontId, const char* text, int maxWidth,
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
EpdFontFamily::Style style = EpdFontStyles::REGULAR) const;
// UI Components
void drawButtonHints(int fontId, const char* btn1, const char* btn2, const char* btn3, const char* btn4);
@@ -90,7 +93,7 @@ class GfxRenderer {
private:
// Helper for drawing rotated text (90 degrees clockwise, for side buttons)
void drawTextRotated90CW(int fontId, int x, int y, const char* text, bool black = true,
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
EpdFontFamily::Style style = EpdFontStyles::REGULAR) const;
int getTextHeight(int fontId) const;
public:

View File

@@ -39,14 +39,26 @@ static void writeString(FsFile& file, const std::string& s) {
static void readString(std::istream& is, std::string& s) {
uint32_t len;
readPod(is, len);
if (len > 4096) {
s = "";
return;
}
s.resize(len);
is.read(&s[0], len);
if (len > 0) {
is.read(&s[0], len);
}
}
static void readString(FsFile& file, std::string& s) {
uint32_t len;
readPod(file, len);
if (len > 4096) {
s = "";
return;
}
s.resize(len);
file.read(&s[0], len);
if (len > 0) {
file.read(&s[0], len);
}
}
} // namespace serialization