2025-12-03 22:00:29 +11:00
|
|
|
// From
|
|
|
|
|
// https://github.com/vroland/epdiy/blob/c61e9e923ce2418150d54f88cea5d196cdc40c54/src/epd_internals.h
|
|
|
|
|
|
|
|
|
|
#pragma once
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
|
|
|
|
|
/// Font data stored PER GLYPH
|
|
|
|
|
typedef struct {
|
2025-12-07 01:26:49 +11:00
|
|
|
uint8_t width; ///< Bitmap dimensions in pixels
|
|
|
|
|
uint8_t height; ///< Bitmap dimensions in pixels
|
|
|
|
|
uint8_t advanceX; ///< Distance to advance cursor (x axis)
|
|
|
|
|
int16_t left; ///< X dist from cursor pos to UL corner
|
|
|
|
|
int16_t top; ///< Y dist from cursor pos to UL corner
|
|
|
|
|
uint16_t dataLength; ///< Size of the font data.
|
perf: Reduce overall flash usage by 30.7% by compressing built-in fonts (#831)
## Summary
**What is the goal of this PR?**
Compress reader font bitmaps to reduce flash usage by 30.7%.
**What changes are included?**
- New `EpdFontGroup` struct and extended `EpdFontData` with
`groups`/`groupCount` fields
- `--compress` flag in `fontconvert.py`: groups glyphs (ASCII base group
+ groups of 8) and compresses each with raw DEFLATE
- `FontDecompressor` class with 4-slot LRU cache for on-demand
decompression during rendering
- `GfxRenderer` transparently routes bitmap access through
`getGlyphBitmap()` (compressed or direct flash)
- Uses `uzlib` for decompression with minimal heap overhead.
- 48 reader fonts (Bookerly, NotoSans 12-18pt, OpenDyslexic) regenerated
with compression; 5 UI fonts unchanged
- Round-trip verification script (`verify_compression.py`) runs as part
of font generation
## Additional Context
## Flash & RAM
| | baseline | font-compression | Difference |
|--|--------|-----------------|------------|
| Flash (ELF) | 6,302,476 B (96.2%) | 4,365,022 B (66.6%) | -1,937,454 B
(-30.7%) |
| firmware.bin | 6,468,192 B | 4,531,008 B | -1,937,184 B (-29.9%) |
| RAM | 101,700 B (31.0%) | 103,076 B (31.5%) | +1,376 B (+0.5%) |
## Script-Based Grouping (Cold Cache)
Comparison of uncompressed baseline vs script-based group compression
(4-slot LRU cache, cleared each page). Glyphs are grouped by Unicode
block (ASCII, Latin-1, Latin Extended-A, Combining Marks, Cyrillic,
General Punctuation, etc.) instead of sequential groups of 8.
### Render Time
| | Baseline | Compressed (cold cache) | Difference |
|---|---|---|---|
| **Median** | 414.9 ms | 431.6 ms | +16.7 ms (+4.0%) |
| **Pages** | 37 | 37 | |
### Memory Usage
| | Baseline | Compressed (cold cache) | Difference |
|---|---|---|---|
| **Heap free (median)** | 187.0 KB | 176.3 KB | -10.7 KB |
| **Heap free (min)** | 186.0 KB | 166.5 KB | -19.5 KB |
| **Largest block (median)** | 148.0 KB | 128.0 KB | -20.0 KB |
| **Largest block (min)** | 148.0 KB | 120.0 KB | -28.0 KB |
### Cache Effectiveness
| | Misses/page | Hit rate |
|---|---|---|
| **Compressed (cold cache)** | 2.1 | 99.85% |
------
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**YES**_
Implementation was done by Claude Code (Opus 4.6) based on a plan
developed collaboratively. All generated font headers were verified with
an automated round-trip decompression test. The firmware was compiled
successfully but has not yet been tested on-device.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 20:30:15 +11:00
|
|
|
uint32_t dataOffset; ///< Pointer into EpdFont->bitmap (or within-group offset for compressed fonts)
|
2025-12-03 22:00:29 +11:00
|
|
|
} EpdGlyph;
|
|
|
|
|
|
perf: Reduce overall flash usage by 30.7% by compressing built-in fonts (#831)
## Summary
**What is the goal of this PR?**
Compress reader font bitmaps to reduce flash usage by 30.7%.
**What changes are included?**
- New `EpdFontGroup` struct and extended `EpdFontData` with
`groups`/`groupCount` fields
- `--compress` flag in `fontconvert.py`: groups glyphs (ASCII base group
+ groups of 8) and compresses each with raw DEFLATE
- `FontDecompressor` class with 4-slot LRU cache for on-demand
decompression during rendering
- `GfxRenderer` transparently routes bitmap access through
`getGlyphBitmap()` (compressed or direct flash)
- Uses `uzlib` for decompression with minimal heap overhead.
- 48 reader fonts (Bookerly, NotoSans 12-18pt, OpenDyslexic) regenerated
with compression; 5 UI fonts unchanged
- Round-trip verification script (`verify_compression.py`) runs as part
of font generation
## Additional Context
## Flash & RAM
| | baseline | font-compression | Difference |
|--|--------|-----------------|------------|
| Flash (ELF) | 6,302,476 B (96.2%) | 4,365,022 B (66.6%) | -1,937,454 B
(-30.7%) |
| firmware.bin | 6,468,192 B | 4,531,008 B | -1,937,184 B (-29.9%) |
| RAM | 101,700 B (31.0%) | 103,076 B (31.5%) | +1,376 B (+0.5%) |
## Script-Based Grouping (Cold Cache)
Comparison of uncompressed baseline vs script-based group compression
(4-slot LRU cache, cleared each page). Glyphs are grouped by Unicode
block (ASCII, Latin-1, Latin Extended-A, Combining Marks, Cyrillic,
General Punctuation, etc.) instead of sequential groups of 8.
### Render Time
| | Baseline | Compressed (cold cache) | Difference |
|---|---|---|---|
| **Median** | 414.9 ms | 431.6 ms | +16.7 ms (+4.0%) |
| **Pages** | 37 | 37 | |
### Memory Usage
| | Baseline | Compressed (cold cache) | Difference |
|---|---|---|---|
| **Heap free (median)** | 187.0 KB | 176.3 KB | -10.7 KB |
| **Heap free (min)** | 186.0 KB | 166.5 KB | -19.5 KB |
| **Largest block (median)** | 148.0 KB | 128.0 KB | -20.0 KB |
| **Largest block (min)** | 148.0 KB | 120.0 KB | -28.0 KB |
### Cache Effectiveness
| | Misses/page | Hit rate |
|---|---|---|
| **Compressed (cold cache)** | 2.1 | 99.85% |
------
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**YES**_
Implementation was done by Claude Code (Opus 4.6) based on a plan
developed collaboratively. All generated font headers were verified with
an automated round-trip decompression test. The firmware was compiled
successfully but has not yet been tested on-device.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 20:30:15 +11:00
|
|
|
/// Compressed font group: a DEFLATE-compressed block of glyph bitmaps
|
|
|
|
|
typedef struct {
|
|
|
|
|
uint32_t compressedOffset; ///< Byte offset into compressed data array
|
|
|
|
|
uint32_t compressedSize; ///< Compressed DEFLATE stream size
|
|
|
|
|
uint32_t uncompressedSize; ///< Decompressed size
|
|
|
|
|
uint16_t glyphCount; ///< Number of glyphs in this group
|
|
|
|
|
uint16_t firstGlyphIndex; ///< First glyph index in the global glyph array
|
|
|
|
|
} EpdFontGroup;
|
|
|
|
|
|
2025-12-03 22:00:29 +11:00
|
|
|
/// Glyph interval structure
|
|
|
|
|
typedef struct {
|
|
|
|
|
uint32_t first; ///< The first unicode code point of the interval
|
|
|
|
|
uint32_t last; ///< The last unicode code point of the interval
|
|
|
|
|
uint32_t offset; ///< Index of the first code point into the glyph array
|
|
|
|
|
} EpdUnicodeInterval;
|
|
|
|
|
|
feat: Support for kerning and ligatures (#873)
## Summary
**What is the goal of this PR?**
Improved typesetting, including
[kerning](https://en.wikipedia.org/wiki/Kerning) and
[ligatures](https://en.wikipedia.org/wiki/Ligature_(writing)#Latin_alphabet).
**What changes are included?**
- The script to convert built-in fonts now adds kerning and ligature
information to the generated font headers.
- Epub page layout calculates proper kerning spaces and makes ligature
substitutions according to the selected font.



## Additional Context
- I am not a typography expert.
- The implementation has been reworked from the earlier version, so it
is no longer necessary to omit Open Dyslexic, and kerning data now
covers all fonts, styles, and codepoints for which we include bitmap
data.
- Claude Opus 4.6 helped with a lot of this.
- There's an included test epub document with lots of kerning and
ligature examples, shown in the photos.
**_After some time to mature, I think this change is in decent shape to
merge and get people testing._**
After opening this PR I came across #660, which overlaps in adding
ligature support.
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**YES, Claude Opus 4.6**_
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 02:31:43 -06:00
|
|
|
/// Maps a codepoint to a kerning class ID, sorted by codepoint for binary search.
|
|
|
|
|
/// Class IDs are 1-based; codepoints not in the table have implicit class 0 (no kerning).
|
|
|
|
|
typedef struct {
|
|
|
|
|
uint16_t codepoint; ///< Unicode codepoint
|
|
|
|
|
uint8_t classId; ///< 1-based kerning class ID
|
|
|
|
|
} __attribute__((packed)) EpdKernClassEntry;
|
|
|
|
|
|
|
|
|
|
/// Ligature substitution for a specific glyph pair, sorted by `pair` for binary search.
|
|
|
|
|
/// `pair` encodes (leftCodepoint << 16 | rightCodepoint) for single-key lookup.
|
|
|
|
|
typedef struct {
|
|
|
|
|
uint32_t pair; ///< Packed codepoint pair (left << 16 | right)
|
|
|
|
|
uint32_t ligatureCp; ///< Codepoint of the replacement ligature glyph
|
|
|
|
|
} __attribute__((packed)) EpdLigaturePair;
|
|
|
|
|
|
2025-12-03 22:00:29 +11:00
|
|
|
/// Data stored for FONT AS A WHOLE
|
|
|
|
|
typedef struct {
|
|
|
|
|
const uint8_t* bitmap; ///< Glyph bitmaps, concatenated
|
|
|
|
|
const EpdGlyph* glyph; ///< Glyph array
|
|
|
|
|
const EpdUnicodeInterval* intervals; ///< Valid unicode intervals for this font
|
|
|
|
|
uint32_t intervalCount; ///< Number of unicode intervals.
|
|
|
|
|
uint8_t advanceY; ///< Newline distance (y axis)
|
|
|
|
|
int ascender; ///< Maximal height of a glyph above the base line
|
|
|
|
|
int descender; ///< Maximal height of a glyph below the base line
|
2025-12-08 19:48:49 +11:00
|
|
|
bool is2Bit;
|
feat: Support for kerning and ligatures (#873)
## Summary
**What is the goal of this PR?**
Improved typesetting, including
[kerning](https://en.wikipedia.org/wiki/Kerning) and
[ligatures](https://en.wikipedia.org/wiki/Ligature_(writing)#Latin_alphabet).
**What changes are included?**
- The script to convert built-in fonts now adds kerning and ligature
information to the generated font headers.
- Epub page layout calculates proper kerning spaces and makes ligature
substitutions according to the selected font.



## Additional Context
- I am not a typography expert.
- The implementation has been reworked from the earlier version, so it
is no longer necessary to omit Open Dyslexic, and kerning data now
covers all fonts, styles, and codepoints for which we include bitmap
data.
- Claude Opus 4.6 helped with a lot of this.
- There's an included test epub document with lots of kerning and
ligature examples, shown in the photos.
**_After some time to mature, I think this change is in decent shape to
merge and get people testing._**
After opening this PR I came across #660, which overlaps in adding
ligature support.
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**YES, Claude Opus 4.6**_
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 02:31:43 -06:00
|
|
|
const EpdFontGroup* groups; ///< NULL for uncompressed fonts
|
|
|
|
|
uint16_t groupCount; ///< 0 for uncompressed fonts
|
|
|
|
|
const EpdKernClassEntry* kernLeftClasses; ///< Sorted left-side class map (nullptr if none)
|
|
|
|
|
const EpdKernClassEntry* kernRightClasses; ///< Sorted right-side class map (nullptr if none)
|
|
|
|
|
const int8_t* kernMatrix; ///< Flat leftClassCount x rightClassCount matrix
|
|
|
|
|
uint16_t kernLeftEntryCount; ///< Entries in kernLeftClasses
|
|
|
|
|
uint16_t kernRightEntryCount; ///< Entries in kernRightClasses
|
|
|
|
|
uint8_t kernLeftClassCount; ///< Number of distinct left classes (matrix rows)
|
|
|
|
|
uint8_t kernRightClassCount; ///< Number of distinct right classes (matrix cols)
|
|
|
|
|
const EpdLigaturePair* ligaturePairs; ///< Sorted ligature pair table (nullptr if none)
|
|
|
|
|
uint32_t ligaturePairCount; ///< Number of entries in ligaturePairs
|
2025-12-03 22:00:29 +11:00
|
|
|
} EpdFontData;
|