Backup: Stable state with EPUB reader fixes (Freeze, OOM, Speed, State, Tooling)
This commit is contained in:
323
lib/EpdFont/CustomEpdFont.cpp
Normal file
323
lib/EpdFont/CustomEpdFont.cpp
Normal 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;
|
||||
}
|
||||
50
lib/EpdFont/CustomEpdFont.h
Normal file
50
lib/EpdFont/CustomEpdFont.h
Normal 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;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
5
lib/EpdFont/EpdFontStyles.h
Normal file
5
lib/EpdFont/EpdFontStyles.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace EpdFontStyles {
|
||||
enum Style { REGULAR = 0, BOLD = 1, ITALIC = 2, BOLD_ITALIC = 3 };
|
||||
}
|
||||
@@ -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 C–F
|
||||
# (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("};")
|
||||
|
||||
Reference in New Issue
Block a user