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("};")