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>
This commit is contained in:
Adrian Wilkins-Caruana
2026-02-19 20:30:15 +11:00
committed by GitHub
co-authored by Claude Opus 4.6
parent f16c0e52fd
commit 47aa0dda76
73 changed files with 126417 additions and 246010 deletions
+1
View File
@@ -26,4 +26,5 @@ git ls-files --exclude-standard ${GIT_LS_FILES_FLAGS} \
| grep -E '\.(c|cpp|h|hpp)$' \
| grep -v -E '^lib/EpdFont/builtinFonts/' \
| grep -v -E '^lib/Epub/Epub/hyphenation/generated/' \
| grep -v -E '^lib/uzlib/' \
| xargs -r clang-format -style=file -i
+12 -1
View File
@@ -12,9 +12,18 @@ typedef struct {
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.
uint32_t dataOffset; ///< Pointer into EpdFont->bitmap
uint32_t dataOffset; ///< Pointer into EpdFont->bitmap (or within-group offset for compressed fonts)
} EpdGlyph;
/// 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;
/// Glyph interval structure
typedef struct {
uint32_t first; ///< The first unicode code point of the interval
@@ -32,4 +41,6 @@ typedef struct {
int ascender; ///< Maximal height of a glyph above the base line
int descender; ///< Maximal height of a glyph below the base line
bool is2Bit;
const EpdFontGroup* groups; ///< NULL for uncompressed fonts
uint16_t groupCount; ///< 0 for uncompressed fonts
} EpdFontData;
+147
View File
@@ -0,0 +1,147 @@
#include "FontDecompressor.h"
#include <Logging.h>
#include <uzlib.h>
#include <cstdlib>
#include <cstring>
bool FontDecompressor::init() {
clearCache();
memset(&decomp, 0, sizeof(decomp));
return true;
}
void FontDecompressor::freeAllEntries() {
for (auto& entry : cache) {
if (entry.data) {
free(entry.data);
entry.data = nullptr;
}
entry.valid = false;
}
}
void FontDecompressor::deinit() { freeAllEntries(); }
void FontDecompressor::clearCache() {
freeAllEntries();
accessCounter = 0;
}
uint16_t FontDecompressor::getGroupIndex(const EpdFontData* fontData, uint16_t glyphIndex) {
for (uint16_t i = 0; i < fontData->groupCount; i++) {
uint16_t first = fontData->groups[i].firstGlyphIndex;
if (glyphIndex >= first && glyphIndex < first + fontData->groups[i].glyphCount) {
return i;
}
}
return fontData->groupCount; // sentinel = not found
}
FontDecompressor::CacheEntry* FontDecompressor::findInCache(const EpdFontData* fontData, uint16_t groupIndex) {
for (auto& entry : cache) {
if (entry.valid && entry.font == fontData && entry.groupIndex == groupIndex) {
return &entry;
}
}
return nullptr;
}
FontDecompressor::CacheEntry* FontDecompressor::findEvictionCandidate() {
// Find an invalid slot first
for (auto& entry : cache) {
if (!entry.valid) {
return &entry;
}
}
// Otherwise evict LRU
CacheEntry* lru = &cache[0];
for (auto& entry : cache) {
if (entry.lastUsed < lru->lastUsed) {
lru = &entry;
}
}
return lru;
}
bool FontDecompressor::decompressGroup(const EpdFontData* fontData, uint16_t groupIndex, CacheEntry* entry) {
const EpdFontGroup& group = fontData->groups[groupIndex];
// Free old buffer if reusing a slot
if (entry->data) {
free(entry->data);
entry->data = nullptr;
}
entry->valid = false;
// Allocate output buffer
auto* outBuf = static_cast<uint8_t*>(malloc(group.uncompressedSize));
if (!outBuf) {
LOG_ERR("FDC", "Failed to allocate %u bytes for group %u", group.uncompressedSize, groupIndex);
return false;
}
// Decompress using uzlib
const uint8_t* inputBuf = &fontData->bitmap[group.compressedOffset];
uzlib_uncompress_init(&decomp, NULL, 0);
decomp.source = inputBuf;
decomp.source_limit = inputBuf + group.compressedSize;
decomp.dest_start = outBuf;
decomp.dest = outBuf;
decomp.dest_limit = outBuf + group.uncompressedSize;
int res = uzlib_uncompress(&decomp);
if (res < 0 || decomp.dest != decomp.dest_limit) {
LOG_ERR("FDC", "Decompression failed for group %u (status %d)", groupIndex, res);
free(outBuf);
return false;
}
entry->font = fontData;
entry->groupIndex = groupIndex;
entry->data = outBuf;
entry->dataSize = group.uncompressedSize;
entry->valid = true;
return true;
}
const uint8_t* FontDecompressor::getBitmap(const EpdFontData* fontData, const EpdGlyph* glyph, uint16_t glyphIndex) {
if (!fontData->groups || fontData->groupCount == 0) {
return &fontData->bitmap[glyph->dataOffset];
}
uint16_t groupIndex = getGroupIndex(fontData, glyphIndex);
if (groupIndex >= fontData->groupCount) {
LOG_ERR("FDC", "Glyph %u not found in any group", glyphIndex);
return nullptr;
}
// Check cache
CacheEntry* entry = findInCache(fontData, groupIndex);
if (entry) {
entry->lastUsed = ++accessCounter;
if (glyph->dataOffset + glyph->dataLength > entry->dataSize) {
LOG_ERR("FDC", "dataOffset %u + dataLength %u out of bounds for group %u (size %u)", glyph->dataOffset,
glyph->dataLength, groupIndex, entry->dataSize);
return nullptr;
}
return &entry->data[glyph->dataOffset];
}
// Cache miss - decompress
entry = findEvictionCandidate();
if (!decompressGroup(fontData, groupIndex, entry)) {
return nullptr;
}
entry->lastUsed = ++accessCounter;
if (glyph->dataOffset + glyph->dataLength > entry->dataSize) {
LOG_ERR("FDC", "dataOffset %u + dataLength %u out of bounds for group %u (size %u)", glyph->dataOffset,
glyph->dataLength, groupIndex, entry->dataSize);
return nullptr;
}
return &entry->data[glyph->dataOffset];
}
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include <uzlib.h>
#include <cstdint>
#include "EpdFontData.h"
class FontDecompressor {
public:
bool init();
void deinit();
// Returns pointer to decompressed bitmap data for the given glyph.
// Valid until LRU eviction (safe for the duration of one glyph render).
const uint8_t* getBitmap(const EpdFontData* fontData, const EpdGlyph* glyph, uint16_t glyphIndex);
// Evict all cached decompressed groups (call between pages for within-page-only caching).
void clearCache();
private:
static constexpr uint8_t CACHE_SLOTS = 4;
struct CacheEntry {
const EpdFontData* font = nullptr;
uint16_t groupIndex = 0;
uint8_t* data = nullptr;
uint32_t dataSize = 0;
uint32_t lastUsed = 0;
bool valid = false;
};
struct uzlib_uncomp decomp = {};
CacheEntry cache[CACHE_SLOTS] = {};
uint32_t accessCounter = 0;
void freeAllEntries();
uint16_t getGroupIndex(const EpdFontData* fontData, uint16_t glyphIndex);
CacheEntry* findInCache(const EpdFontData* fontData, uint16_t groupIndex);
CacheEntry* findEvictionCandidate();
bool decompressGroup(const EpdFontData* fontData, uint16_t groupIndex, CacheEntry* entry);
};
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1619,4 +1619,6 @@ static const EpdFontData notosans_8_regular = {
18,
-5,
false,
nullptr,
0,
};
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1522,4 +1522,6 @@ static const EpdFontData ubuntu_10_bold = {
20,
-4,
false,
nullptr,
0,
};
@@ -1436,4 +1436,6 @@ static const EpdFontData ubuntu_10_regular = {
20,
-4,
false,
nullptr,
0,
};
@@ -1832,4 +1832,6 @@ static const EpdFontData ubuntu_12_bold = {
24,
-5,
false,
nullptr,
0,
};
@@ -1724,4 +1724,6 @@ static const EpdFontData ubuntu_12_regular = {
24,
-5,
false,
nullptr,
0,
};
+7 -3
View File
@@ -14,7 +14,7 @@ for size in ${BOOKERLY_FONT_SIZES[@]}; do
font_name="bookerly_${size}_$(echo $style | tr '[:upper:]' '[:lower:]')"
font_path="../builtinFonts/source/Bookerly/Bookerly-${style}.ttf"
output_path="../builtinFonts/${font_name}.h"
python fontconvert.py $font_name $size $font_path --2bit > $output_path
python fontconvert.py $font_name $size $font_path --2bit --compress > $output_path
echo "Generated $output_path"
done
done
@@ -24,7 +24,7 @@ for size in ${NOTOSANS_FONT_SIZES[@]}; do
font_name="notosans_${size}_$(echo $style | tr '[:upper:]' '[:lower:]')"
font_path="../builtinFonts/source/NotoSans/NotoSans-${style}.ttf"
output_path="../builtinFonts/${font_name}.h"
python fontconvert.py $font_name $size $font_path --2bit > $output_path
python fontconvert.py $font_name $size $font_path --2bit --compress > $output_path
echo "Generated $output_path"
done
done
@@ -34,7 +34,7 @@ for size in ${OPENDYSLEXIC_FONT_SIZES[@]}; do
font_name="opendyslexic_${size}_$(echo $style | tr '[:upper:]' '[:lower:]')"
font_path="../builtinFonts/source/OpenDyslexic/OpenDyslexic-${style}.otf"
output_path="../builtinFonts/${font_name}.h"
python fontconvert.py $font_name $size $font_path --2bit > $output_path
python fontconvert.py $font_name $size $font_path --2bit --compress > $output_path
echo "Generated $output_path"
done
done
@@ -53,3 +53,7 @@ for size in ${UI_FONT_SIZES[@]}; do
done
python fontconvert.py notosans_8_regular 8 ../builtinFonts/source/NotoSans/NotoSans-Regular.ttf > ../builtinFonts/notosans_8_regular.h
echo ""
echo "Running compression verification..."
python verify_compression.py ../builtinFonts/
+114 -5
View File
@@ -15,6 +15,7 @@ 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("--compress", dest="compress", action="store_true", help="Compress glyph bitmaps using DEFLATE with group-based compression.")
args = parser.parse_args()
GlyphProps = namedtuple("GlyphProps", ["width", "height", "advance_x", "left", "top", "data_length", "data_offset", "code_point"])
@@ -270,21 +271,115 @@ for index, glyph in enumerate(all_glyphs):
glyph_data.extend([b for b in packed])
glyph_props.append(props)
compress = args.compress
# Build groups for compression
if compress:
# Script-based grouping: glyphs that co-occur in typical text rendering
# are grouped together for efficient LRU caching on the embedded target.
# Since glyphs are in codepoint order, glyphs in the same Unicode block
# are contiguous in the array and form natural groups.
SCRIPT_GROUP_RANGES = [
(0x0000, 0x007F), # ASCII
(0x0080, 0x00FF), # Latin-1 Supplement
(0x0100, 0x017F), # Latin Extended-A
(0x0300, 0x036F), # Combining Diacritical Marks
(0x0400, 0x04FF), # Cyrillic
(0x2000, 0x206F), # General Punctuation
(0x2070, 0x209F), # Superscripts & Subscripts
(0x20A0, 0x20CF), # Currency Symbols
(0x2190, 0x21FF), # Arrows
(0x2200, 0x22FF), # Math Operators
(0xFFFD, 0xFFFD), # Replacement Character
]
def get_script_group(code_point):
for i, (start, end) in enumerate(SCRIPT_GROUP_RANGES):
if start <= code_point <= end:
return i
return -1
groups = [] # list of (first_glyph_index, glyph_count)
current_group_id = None
group_start = 0
group_count = 0
for i, (props, packed) in enumerate(all_glyphs):
sg = get_script_group(props.code_point)
if sg != current_group_id:
if group_count > 0:
groups.append((group_start, group_count))
current_group_id = sg
group_start = i
group_count = 1
else:
group_count += 1
if group_count > 0:
groups.append((group_start, group_count))
# Compress each group
compressed_groups = [] # list of (compressed_bytes, uncompressed_size, glyph_count, first_glyph_index)
compressed_bitmap_data = []
compressed_offset = 0
# Also build modified glyph props with within-group offsets
modified_glyph_props = list(glyph_props)
for first_idx, count in groups:
# Concatenate bitmap data for this group
group_data = b''
for gi in range(first_idx, first_idx + count):
props, packed = all_glyphs[gi]
# Update glyph's dataOffset to be within-group offset
within_group_offset = len(group_data)
old_props = modified_glyph_props[gi]
modified_glyph_props[gi] = GlyphProps(
width=old_props.width,
height=old_props.height,
advance_x=old_props.advance_x,
left=old_props.left,
top=old_props.top,
data_length=old_props.data_length,
data_offset=within_group_offset,
code_point=old_props.code_point,
)
group_data += packed
# Compress with raw DEFLATE (no zlib/gzip header)
compressor = zlib.compressobj(level=9, wbits=-15)
compressed = compressor.compress(group_data) + compressor.flush()
compressed_groups.append((compressed, len(group_data), count, first_idx))
compressed_bitmap_data.extend(compressed)
compressed_offset += len(compressed)
glyph_props = modified_glyph_props
total_compressed = len(compressed_bitmap_data)
total_uncompressed = len(glyph_data)
print(f"// Compression: {total_uncompressed} -> {total_compressed} bytes ({100*total_compressed/total_uncompressed:.1f}%), {len(groups)} groups", file=sys.stderr)
print(f"""/**
* generated by fontconvert.py
* name: {font_name}
* size: {size}
* mode: {'2-bit' if is2Bit else '1-bit'}
* mode: {'2-bit' if is2Bit else '1-bit'}{' compressed: true' if compress else ''}
* Command used: {' '.join(sys.argv)}
*/
#pragma once
#include "EpdFontData.h"
""")
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 compress:
print(f"static const uint8_t {font_name}Bitmaps[{len(compressed_bitmap_data)}] = {{")
for c in chunks(compressed_bitmap_data, 16):
print (" " + " ".join(f"0x{b:02X}," for b in c))
print ("};\n");
else:
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):
@@ -298,6 +393,14 @@ for i_start, i_end in intervals:
offset += i_end - i_start + 1
print ("};\n");
if compress:
print(f"static const EpdFontGroup {font_name}Groups[] = {{")
compressed_offset = 0
for compressed, uncompressed_size, count, first_idx in compressed_groups:
print(f" {{ {compressed_offset}, {len(compressed)}, {uncompressed_size}, {count}, {first_idx} }},")
compressed_offset += len(compressed)
print("};\n")
print(f"static const EpdFontData {font_name} = {{")
print(f" {font_name}Bitmaps,")
print(f" {font_name}Glyphs,")
@@ -307,4 +410,10 @@ 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'},")
if compress:
print(f" {font_name}Groups,")
print(f" {len(compressed_groups)},")
else:
print(f" nullptr,")
print(f" 0,")
print("};")
+164
View File
@@ -0,0 +1,164 @@
#!/usr/bin/env python3
"""
Round-trip verification for compressed font headers.
Parses each generated .h file in the given directory, identifies compressed fonts
(those with a Groups array), decompresses each group, and verifies that
decompression succeeds and all glyph offsets/lengths fall within bounds.
"""
import os
import re
import sys
import zlib
def parse_hex_array(text):
"""Extract bytes from a C hex array string like '{ 0xAB, 0xCD, ... }'"""
hex_vals = re.findall(r'0x([0-9A-Fa-f]{2})', text)
return bytes(int(h, 16) for h in hex_vals)
def parse_groups(text):
"""Parse EpdFontGroup array entries: { compressedOffset, compressedSize, uncompressedSize, glyphCount, firstGlyphIndex }"""
groups = []
for match in re.finditer(r'\{\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\}', text):
groups.append({
'compressedOffset': int(match.group(1)),
'compressedSize': int(match.group(2)),
'uncompressedSize': int(match.group(3)),
'glyphCount': int(match.group(4)),
'firstGlyphIndex': int(match.group(5)),
})
return groups
def parse_glyphs(text):
"""Parse EpdGlyph array entries: { width, height, advanceX, left, top, dataLength, dataOffset }"""
glyphs = []
for match in re.finditer(r'\{\s*(-?\d+)\s*,\s*(-?\d+)\s*,\s*(-?\d+)\s*,\s*(-?\d+)\s*,\s*(-?\d+)\s*,\s*(-?\d+)\s*,\s*(-?\d+)\s*\}', text):
glyphs.append({
'width': int(match.group(1)),
'height': int(match.group(2)),
'advanceX': int(match.group(3)),
'left': int(match.group(4)),
'top': int(match.group(5)),
'dataLength': int(match.group(6)),
'dataOffset': int(match.group(7)),
})
return glyphs
def verify_font_file(filepath):
"""Verify a single font header file. Returns (font_name, success, message)."""
with open(filepath, 'r') as f:
content = f.read()
# Check if this is a compressed font (has Groups array)
groups_match = re.search(r'static const EpdFontGroup (\w+)Groups\[\]', content)
if not groups_match:
return (os.path.basename(filepath), None, "uncompressed, skipping")
font_name = groups_match.group(1)
# Extract bitmap data
bitmap_match = re.search(
r'static const uint8_t ' + re.escape(font_name) + r'Bitmaps\[\d+\]\s*=\s*\{([^}]+)\}',
content, re.DOTALL
)
if not bitmap_match:
return (font_name, False, "could not find Bitmaps array")
compressed_data = parse_hex_array(bitmap_match.group(1))
# Extract groups
groups_array_match = re.search(
r'static const EpdFontGroup ' + re.escape(font_name) + r'Groups\[\]\s*=\s*\{(.+?)\};',
content, re.DOTALL
)
if not groups_array_match:
return (font_name, False, "could not find Groups array")
groups = parse_groups(groups_array_match.group(1))
if not groups:
return (font_name, False, "Groups array parsed to 0 entries; check format")
# Extract glyphs
glyphs_match = re.search(
r'static const EpdGlyph ' + re.escape(font_name) + r'Glyphs\[\]\s*=\s*\{(.+?)\};',
content, re.DOTALL
)
if not glyphs_match:
return (font_name, False, "could not find Glyphs array")
glyphs = parse_glyphs(glyphs_match.group(1))
# Verify each group
for gi, group in enumerate(groups):
# Extract compressed chunk
chunk = compressed_data[group['compressedOffset']:group['compressedOffset'] + group['compressedSize']]
if len(chunk) != group['compressedSize']:
return (font_name, False, f"group {gi}: compressed data truncated (expected {group['compressedSize']}, got {len(chunk)})")
# Decompress with raw DEFLATE
try:
decompressed = zlib.decompress(chunk, -15)
except zlib.error as e:
return (font_name, False, f"group {gi}: decompression failed: {e}")
if len(decompressed) != group['uncompressedSize']:
return (font_name, False, f"group {gi}: size mismatch (expected {group['uncompressedSize']}, got {len(decompressed)})")
# Verify each glyph's data within the group
first = group['firstGlyphIndex']
for j in range(group['glyphCount']):
glyph_idx = first + j
if glyph_idx >= len(glyphs):
return (font_name, False, f"group {gi}: glyph index {glyph_idx} out of range")
glyph = glyphs[glyph_idx]
offset = glyph['dataOffset']
length = glyph['dataLength']
if offset + length > len(decompressed):
return (font_name, False, f"group {gi}, glyph {glyph_idx}: data extends beyond decompressed buffer "
f"(offset={offset}, length={length}, decompressed_size={len(decompressed)})")
return (font_name, True, f"{len(groups)} groups, {len(glyphs)} glyphs OK")
def main():
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <font_headers_directory>", file=sys.stderr)
sys.exit(1)
font_dir = sys.argv[1]
if not os.path.isdir(font_dir):
print(f"Error: {font_dir} is not a directory", file=sys.stderr)
sys.exit(1)
files = sorted(f for f in os.listdir(font_dir) if f.endswith('.h') and f != 'all.h')
passed = 0
failed = 0
skipped = 0
for filename in files:
filepath = os.path.join(font_dir, filename)
_font_name, success, message = verify_font_file(filepath)
if success is None:
skipped += 1
elif success:
passed += 1
print(f" PASS: {filename} ({message})")
else:
failed += 1
print(f" FAIL: {filename} - {message}")
print(f"\nResults: {passed} passed, {failed} failed, {skipped} skipped (uncompressed)")
if failed > 0:
sys.exit(1)
if __name__ == '__main__':
main()
+19 -8
View File
@@ -3,6 +3,18 @@
#include <Logging.h>
#include <Utf8.h>
const uint8_t* GfxRenderer::getGlyphBitmap(const EpdFontData* fontData, const EpdGlyph* glyph) const {
if (fontData->groups != nullptr) {
if (!fontDecompressor) {
LOG_ERR("GFX", "Compressed font but no FontDecompressor set");
return nullptr;
}
uint16_t glyphIndex = static_cast<uint16_t>(glyph - fontData->glyph);
return fontDecompressor->getBitmap(fontData, glyph, glyphIndex);
}
return &fontData->bitmap[glyph->dataOffset];
}
void GfxRenderer::begin() {
frameBuffer = display.getFrameBuffer();
if (!frameBuffer) {
@@ -801,14 +813,14 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y
continue;
}
const int is2Bit = font.getData(style)->is2Bit;
const uint32_t offset = glyph->dataOffset;
const EpdFontData* fontData = font.getData(style);
const int is2Bit = fontData->is2Bit;
const uint8_t width = glyph->width;
const uint8_t height = glyph->height;
const int left = glyph->left;
const int top = glyph->top;
const uint8_t* bitmap = &font.getData(style)->bitmap[offset];
const uint8_t* bitmap = getGlyphBitmap(fontData, glyph);
if (bitmap != nullptr) {
for (int glyphY = 0; glyphY < height; glyphY++) {
@@ -818,7 +830,7 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y
// 90° clockwise rotation transformation:
// screenX = x + (ascender - top + glyphY)
// screenY = yPos - (left + glyphX)
const int screenX = x + (font.getData(style)->ascender - top + glyphY);
const int screenX = x + (fontData->ascender - top + glyphY);
const int screenY = yPos - left - glyphX;
if (is2Bit) {
@@ -966,14 +978,13 @@ void GfxRenderer::renderChar(const EpdFontFamily& fontFamily, const uint32_t cp,
return;
}
const int is2Bit = fontFamily.getData(style)->is2Bit;
const uint32_t offset = glyph->dataOffset;
const EpdFontData* fontData = fontFamily.getData(style);
const int is2Bit = fontData->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 = getGlyphBitmap(fontData, glyph);
if (bitmap != nullptr) {
for (int glyphY = 0; glyphY < height; glyphY++) {
+7
View File
@@ -1,6 +1,7 @@
#pragma once
#include <EpdFontFamily.h>
#include <FontDecompressor.h>
#include <HalDisplay.h>
#include <map>
@@ -36,9 +37,11 @@ class GfxRenderer {
uint8_t* frameBuffer = nullptr;
uint8_t* bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr};
std::map<int, EpdFontFamily> fontMap;
FontDecompressor* fontDecompressor = nullptr;
void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, const int* y, bool pixelState,
EpdFontFamily::Style style) const;
void freeBwBufferChunks();
const uint8_t* getGlyphBitmap(const EpdFontData* fontData, const EpdGlyph* glyph) const;
template <Color color>
void drawPixelDither(int x, int y) const;
template <Color color>
@@ -57,6 +60,10 @@ class GfxRenderer {
// Setup
void begin(); // must be called right after display.begin()
void insertFont(int fontId, EpdFontFamily font);
void setFontDecompressor(FontDecompressor* d) { fontDecompressor = d; }
void clearFontCache() {
if (fontDecompressor) fontDecompressor->clearCache();
}
// Orientation control (affects logical width/height and coordinate transforms)
void setOrientation(const Orientation o) { orientation = o; }
+5 -5
View File
@@ -6,8 +6,8 @@
#include <algorithm>
bool inflateOneShot(const uint8_t* inputBuf, const size_t deflatedSize, uint8_t* outputBuf, const size_t inflatedSize) {
// Setup inflator
static bool inflateOneShot(const uint8_t* inputBuf, const size_t deflatedSize, uint8_t* outputBuf,
const size_t inflatedSize) {
const auto inflator = static_cast<tinfl_decompressor*>(malloc(sizeof(tinfl_decompressor)));
if (!inflator) {
LOG_ERR("ZIP", "Failed to allocate memory for inflator");
@@ -20,6 +20,7 @@ bool inflateOneShot(const uint8_t* inputBuf, const size_t deflatedSize, uint8_t*
size_t outBytes = inflatedSize;
const tinfl_status status = tinfl_decompress(inflator, inputBuf, &inBytes, nullptr, outputBuf, &outBytes,
TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF);
free(inflator);
if (status != TINFL_STATUS_DONE) {
@@ -451,7 +452,7 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo
return nullptr;
}
const bool success = inflateOneShot(deflatedData, deflatedDataSize, data, inflatedDataSize);
bool success = inflateOneShot(deflatedData, deflatedDataSize, data, inflatedDataSize);
free(deflatedData);
if (!success) {
@@ -529,8 +530,7 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
}
if (fileStat.method == MZ_DEFLATED) {
// Setup inflator
const auto inflator = static_cast<tinfl_decompressor*>(malloc(sizeof(tinfl_decompressor)));
auto* inflator = static_cast<tinfl_decompressor*>(malloc(sizeof(tinfl_decompressor)));
if (!inflator) {
LOG_ERR("ZIP", "Failed to allocate memory for inflator");
if (!wasOpen) {
+15
View File
@@ -0,0 +1,15 @@
{
"name": "uzlib",
"version": "2.9.8",
"description": "Micro deflate/inflate library (uzlib)",
"repository": {
"type": "git",
"url": "https://github.com/pfalcon/uzlib.git"
},
"frameworks": "*",
"platforms": "*",
"build": {
"srcDir": "src",
"includeDir": "src"
}
}
+37
View File
@@ -0,0 +1,37 @@
/*
* Copyright (c) uzlib authors
*
* This software is provided 'as-is', without any express
* or implied warranty. In no event will the authors be
* held liable for any damages arising from the use of
* this software.
*
* Permission is granted to anyone to use this software
* for any purpose, including commercial applications,
* and to alter it and redistribute it freely, subject to
* the following restrictions:
*
* 1. The origin of this software must not be
* misrepresented; you must not claim that you
* wrote the original software. If you use this
* software in a product, an acknowledgment in
* the product documentation would be appreciated
* but is not required.
*
* 2. Altered source versions must be plainly marked
* as such, and must not be misrepresented as
* being the original software.
*
* 3. This notice may not be removed or altered from
* any source distribution.
*/
/* This files contains type declaration and prototypes for defl_static.c.
They may be altered/distinct from the originals used in PuTTY source
code. */
void outbits(struct uzlib_comp *ctx, unsigned long bits, int nbits);
void zlib_start_block(struct uzlib_comp *ctx);
void zlib_finish_block(struct uzlib_comp *ctx);
void zlib_literal(struct uzlib_comp *ctx, unsigned char c);
void zlib_match(struct uzlib_comp *ctx, int distance, int len);
+3
View File
@@ -0,0 +1,3 @@
/* Compatibility header for the original tinf lib/older versions of uzlib.
Note: may be removed in the future, please migrate to uzlib.h. */
#include "uzlib.h"
+9
View File
@@ -0,0 +1,9 @@
/* This header contains compatibility defines for the original tinf API
and uzlib 2.x and below API. These defines are deprecated and going
to be removed in the future, so applications should migrate to new
uzlib API. */
#define TINF_DATA struct uzlib_uncomp
#define destSize dest_size
#define destStart dest_start
#define readSource source_read_cb
+674
View File
@@ -0,0 +1,674 @@
/*
* uzlib - tiny deflate/inflate library (deflate, gzip, zlib)
*
* Copyright (c) 2003 by Joergen Ibsen / Jibz
* All Rights Reserved
* http://www.ibsensoftware.com/
*
* Copyright (c) 2014-2018 by Paul Sokolovsky
*
* This software is provided 'as-is', without any express
* or implied warranty. In no event will the authors be
* held liable for any damages arising from the use of
* this software.
*
* Permission is granted to anyone to use this software
* for any purpose, including commercial applications,
* and to alter it and redistribute it freely, subject to
* the following restrictions:
*
* 1. The origin of this software must not be
* misrepresented; you must not claim that you
* wrote the original software. If you use this
* software in a product, an acknowledgment in
* the product documentation would be appreciated
* but is not required.
*
* 2. Altered source versions must be plainly marked
* as such, and must not be misrepresented as
* being the original software.
*
* 3. This notice may not be removed or altered from
* any source distribution.
*/
#include <assert.h>
#include <string.h>
#include "tinf.h"
#define UZLIB_DUMP_ARRAY(heading, arr, size) \
{ \
printf("%s", heading); \
for (int i = 0; i < size; ++i) { \
printf(" %d", (arr)[i]); \
} \
printf("\n"); \
}
uint32_t tinf_get_le_uint32(TINF_DATA *d);
uint32_t tinf_get_be_uint32(TINF_DATA *d);
/* --------------------------------------------------- *
* -- uninitialized global data (static structures) -- *
* --------------------------------------------------- */
#ifdef RUNTIME_BITS_TABLES
/* extra bits and base tables for length codes */
unsigned char length_bits[30];
unsigned short length_base[30];
/* extra bits and base tables for distance codes */
unsigned char dist_bits[30];
unsigned short dist_base[30];
#else
const unsigned char length_bits[30] = {
0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 2, 2, 2, 2,
3, 3, 3, 3, 4, 4, 4, 4,
5, 5, 5, 5
};
const unsigned short length_base[30] = {
3, 4, 5, 6, 7, 8, 9, 10,
11, 13, 15, 17, 19, 23, 27, 31,
35, 43, 51, 59, 67, 83, 99, 115,
131, 163, 195, 227, 258
};
const unsigned char dist_bits[30] = {
0, 0, 0, 0, 1, 1, 2, 2,
3, 3, 4, 4, 5, 5, 6, 6,
7, 7, 8, 8, 9, 9, 10, 10,
11, 11, 12, 12, 13, 13
};
const unsigned short dist_base[30] = {
1, 2, 3, 4, 5, 7, 9, 13,
17, 25, 33, 49, 65, 97, 129, 193,
257, 385, 513, 769, 1025, 1537, 2049, 3073,
4097, 6145, 8193, 12289, 16385, 24577
};
#endif
/* special ordering of code length codes */
const unsigned char clcidx[] = {
16, 17, 18, 0, 8, 7, 9, 6,
10, 5, 11, 4, 12, 3, 13, 2,
14, 1, 15
};
/* ----------------------- *
* -- utility functions -- *
* ----------------------- */
#ifdef RUNTIME_BITS_TABLES
/* build extra bits and base tables */
static void tinf_build_bits_base(unsigned char *bits, unsigned short *base, int delta, int first)
{
int i, sum;
/* build bits table */
for (i = 0; i < delta; ++i) bits[i] = 0;
for (i = 0; i < 30 - delta; ++i) bits[i + delta] = i / delta;
/* build base table */
for (sum = first, i = 0; i < 30; ++i)
{
base[i] = sum;
sum += 1 << bits[i];
}
}
#endif
/* build the fixed huffman trees */
static void tinf_build_fixed_trees(TINF_TREE *lt, TINF_TREE *dt)
{
int i;
/* build fixed length tree */
for (i = 0; i < 7; ++i) lt->table[i] = 0;
lt->table[7] = 24;
lt->table[8] = 152;
lt->table[9] = 112;
for (i = 0; i < 24; ++i) lt->trans[i] = 256 + i;
for (i = 0; i < 144; ++i) lt->trans[24 + i] = i;
for (i = 0; i < 8; ++i) lt->trans[24 + 144 + i] = 280 + i;
for (i = 0; i < 112; ++i) lt->trans[24 + 144 + 8 + i] = 144 + i;
/* build fixed distance tree */
for (i = 0; i < 5; ++i) dt->table[i] = 0;
dt->table[5] = 32;
for (i = 0; i < 32; ++i) dt->trans[i] = i;
}
/* given an array of code lengths, build a tree */
static void tinf_build_tree(TINF_TREE *t, const unsigned char *lengths, unsigned int num)
{
unsigned short offs[16];
unsigned int i, sum;
/* clear code length count table */
for (i = 0; i < 16; ++i) t->table[i] = 0;
/* scan symbol lengths, and sum code length counts */
for (i = 0; i < num; ++i) t->table[lengths[i]]++;
#if UZLIB_CONF_DEBUG_LOG >= 2
UZLIB_DUMP_ARRAY("codelen counts:", t->table, TINF_ARRAY_SIZE(t->table));
#endif
/* In the lengths array, 0 means unused code. So, t->table[0] now contains
number of unused codes. But table's purpose is to contain # of codes of
particular length, and there're 0 codes of length 0. */
t->table[0] = 0;
/* compute offset table for distribution sort */
for (sum = 0, i = 0; i < 16; ++i)
{
offs[i] = sum;
sum += t->table[i];
}
#if UZLIB_CONF_DEBUG_LOG >= 2
UZLIB_DUMP_ARRAY("codelen offsets:", offs, TINF_ARRAY_SIZE(offs));
#endif
/* create code->symbol translation table (symbols sorted by code) */
for (i = 0; i < num; ++i)
{
if (lengths[i]) t->trans[offs[lengths[i]]++] = i;
}
}
/* ---------------------- *
* -- decode functions -- *
* ---------------------- */
unsigned char uzlib_get_byte(TINF_DATA *d)
{
/* If end of source buffer is not reached, return next byte from source
buffer. */
if (d->source < d->source_limit) {
return *d->source++;
}
/* Otherwise if there's callback and we haven't seen EOF yet, try to
read next byte using it. (Note: the callback can also update ->source
and ->source_limit). */
if (d->readSource && !d->eof) {
int val = d->readSource(d);
if (val >= 0) {
return (unsigned char)val;
}
}
/* Otherwise, we hit EOF (either from ->readSource() or from exhaustion
of the buffer), and it will be "sticky", i.e. further calls to this
function will end up here too. */
d->eof = true;
return 0;
}
uint32_t tinf_get_le_uint32(TINF_DATA *d)
{
uint32_t val = 0;
int i;
for (i = 4; i--;) {
val = val >> 8 | ((uint32_t)uzlib_get_byte(d)) << 24;
}
return val;
}
uint32_t tinf_get_be_uint32(TINF_DATA *d)
{
uint32_t val = 0;
int i;
for (i = 4; i--;) {
val = val << 8 | uzlib_get_byte(d);
}
return val;
}
/* get one bit from source stream */
static int tinf_getbit(TINF_DATA *d)
{
unsigned int bit;
/* check if tag is empty */
if (!d->bitcount--)
{
/* load next tag */
d->tag = uzlib_get_byte(d);
d->bitcount = 7;
}
/* shift bit out of tag */
bit = d->tag & 0x01;
d->tag >>= 1;
return bit;
}
/* read a num bit value from a stream and add base */
static unsigned int tinf_read_bits(TINF_DATA *d, int num, int base)
{
unsigned int val = 0;
/* read num bits */
if (num)
{
unsigned int limit = 1 << (num);
unsigned int mask;
for (mask = 1; mask < limit; mask *= 2)
if (tinf_getbit(d)) val += mask;
}
return val + base;
}
/* given a data stream and a tree, decode a symbol */
static int tinf_decode_symbol(TINF_DATA *d, TINF_TREE *t)
{
int sum = 0, cur = 0, len = 0;
/* get more bits while code value is above sum */
do {
cur = 2*cur + tinf_getbit(d);
if (++len == TINF_ARRAY_SIZE(t->table)) {
return TINF_DATA_ERROR;
}
sum += t->table[len];
cur -= t->table[len];
} while (cur >= 0);
sum += cur;
#if UZLIB_CONF_PARANOID_CHECKS
if (sum < 0 || sum >= TINF_ARRAY_SIZE(t->trans)) {
return TINF_DATA_ERROR;
}
#endif
return t->trans[sum];
}
/* given a data stream, decode dynamic trees from it */
static int tinf_decode_trees(TINF_DATA *d, TINF_TREE *lt, TINF_TREE *dt)
{
/* code lengths for 288 literal/len symbols and 32 dist symbols */
unsigned char lengths[288+32];
unsigned int hlit, hdist, hclen, hlimit;
unsigned int i, num, length;
/* get 5 bits HLIT (257-286) */
hlit = tinf_read_bits(d, 5, 257);
/* get 5 bits HDIST (1-32) */
hdist = tinf_read_bits(d, 5, 1);
/* get 4 bits HCLEN (4-19) */
hclen = tinf_read_bits(d, 4, 4);
for (i = 0; i < 19; ++i) lengths[i] = 0;
/* read code lengths for code length alphabet */
for (i = 0; i < hclen; ++i)
{
/* get 3 bits code length (0-7) */
unsigned int clen = tinf_read_bits(d, 3, 0);
lengths[clcidx[i]] = clen;
}
/* build code length tree, temporarily use length tree */
tinf_build_tree(lt, lengths, 19);
/* decode code lengths for the dynamic trees */
hlimit = hlit + hdist;
for (num = 0; num < hlimit; )
{
int sym = tinf_decode_symbol(d, lt);
unsigned char fill_value = 0;
int lbits, lbase = 3;
/* error decoding */
if (sym < 0) return sym;
switch (sym)
{
case 16:
/* copy previous code length 3-6 times (read 2 bits) */
if (num == 0) return TINF_DATA_ERROR;
fill_value = lengths[num - 1];
lbits = 2;
break;
case 17:
/* repeat code length 0 for 3-10 times (read 3 bits) */
lbits = 3;
break;
case 18:
/* repeat code length 0 for 11-138 times (read 7 bits) */
lbits = 7;
lbase = 11;
break;
default:
/* values 0-15 represent the actual code lengths */
lengths[num++] = sym;
/* continue the for loop */
continue;
}
/* special code length 16-18 are handled here */
length = tinf_read_bits(d, lbits, lbase);
if (num + length > hlimit) return TINF_DATA_ERROR;
for (; length; --length)
{
lengths[num++] = fill_value;
}
}
#if UZLIB_CONF_DEBUG_LOG >= 2
printf("lit code lengths (%d):", hlit);
UZLIB_DUMP_ARRAY("", lengths, hlit);
printf("dist code lengths (%d):", hdist);
UZLIB_DUMP_ARRAY("", lengths + hlit, hdist);
#endif
#if UZLIB_CONF_PARANOID_CHECKS
/* Check that there's "end of block" symbol */
if (lengths[256] == 0) {
return TINF_DATA_ERROR;
}
#endif
/* build dynamic trees */
tinf_build_tree(lt, lengths, hlit);
tinf_build_tree(dt, lengths + hlit, hdist);
return TINF_OK;
}
/* ----------------------------- *
* -- block inflate functions -- *
* ----------------------------- */
/* given a stream and two trees, inflate next chunk of output (a byte or more) */
static int tinf_inflate_block_data(TINF_DATA *d, TINF_TREE *lt, TINF_TREE *dt)
{
if (d->curlen == 0) {
unsigned int offs;
int dist;
int sym = tinf_decode_symbol(d, lt);
//printf("huff sym: %02x\n", sym);
if (d->eof) {
return TINF_DATA_ERROR;
}
/* literal byte */
if (sym < 256) {
TINF_PUT(d, sym);
return TINF_OK;
}
/* end of block */
if (sym == 256) {
return TINF_DONE;
}
/* substring from sliding dictionary */
sym -= 257;
if (sym >= 29) {
return TINF_DATA_ERROR;
}
/* possibly get more bits from length code */
d->curlen = tinf_read_bits(d, length_bits[sym], length_base[sym]);
dist = tinf_decode_symbol(d, dt);
if (dist >= 30) {
return TINF_DATA_ERROR;
}
/* possibly get more bits from distance code */
offs = tinf_read_bits(d, dist_bits[dist], dist_base[dist]);
/* calculate and validate actual LZ offset to use */
if (d->dict_ring) {
if (offs > d->dict_size) {
return TINF_DICT_ERROR;
}
/* Note: unlike full-dest-in-memory case below, we don't
try to catch offset which points to not yet filled
part of the dictionary here. Doing so would require
keeping another variable to track "filled in" size
of the dictionary. Appearance of such an offset cannot
lead to accessing memory outside of the dictionary
buffer, and clients which don't want to leak unrelated
information, should explicitly initialize dictionary
buffer passed to uzlib. */
d->lzOff = d->dict_idx - offs;
if (d->lzOff < 0) {
d->lzOff += d->dict_size;
}
} else {
/* catch trying to point before the start of dest buffer */
if (offs > (unsigned)(d->dest - d->destStart)) {
return TINF_DATA_ERROR;
}
d->lzOff = -offs;
}
}
/* copy next byte from dict substring */
if (d->dict_ring) {
TINF_PUT(d, d->dict_ring[d->lzOff]);
if ((unsigned)++d->lzOff == d->dict_size) {
d->lzOff = 0;
}
} else {
#if UZLIB_CONF_USE_MEMCPY
/* copy as much as possible, in one memcpy() call */
unsigned int to_copy = d->curlen, dest_len = d->dest_limit - d->dest;
if (to_copy > dest_len) {
to_copy = dest_len;
}
memcpy(d->dest, d->dest + d->lzOff, to_copy);
d->dest += to_copy;
d->curlen -= to_copy;
return TINF_OK;
#else
d->dest[0] = d->dest[d->lzOff];
d->dest++;
#endif
}
d->curlen--;
return TINF_OK;
}
/* inflate next byte from uncompressed block of data */
static int tinf_inflate_uncompressed_block(TINF_DATA *d)
{
if (d->curlen == 0) {
unsigned int length, invlength;
/* get length */
length = uzlib_get_byte(d);
length += 256 * uzlib_get_byte(d);
/* get one's complement of length */
invlength = uzlib_get_byte(d);
invlength += 256 * uzlib_get_byte(d);
/* check length */
if (length != (~invlength & 0x0000ffff)) return TINF_DATA_ERROR;
/* increment length to properly return TINF_DONE below, without
producing data at the same time */
d->curlen = length + 1;
/* make sure we start next block on a byte boundary */
d->bitcount = 0;
}
if (--d->curlen == 0) {
return TINF_DONE;
}
unsigned char c = uzlib_get_byte(d);
TINF_PUT(d, c);
return TINF_OK;
}
/* ---------------------- *
* -- public functions -- *
* ---------------------- */
/* initialize global (static) data */
void uzlib_init(void)
{
#ifdef RUNTIME_BITS_TABLES
/* build extra bits and base tables */
tinf_build_bits_base(length_bits, length_base, 4, 3);
tinf_build_bits_base(dist_bits, dist_base, 2, 1);
/* fix a special case */
length_bits[28] = 0;
length_base[28] = 258;
#endif
}
/* initialize decompression structure */
void uzlib_uncompress_init(TINF_DATA *d, void *dict, unsigned int dictLen)
{
d->eof = 0;
d->bitcount = 0;
d->bfinal = 0;
d->btype = -1;
d->dict_size = dictLen;
d->dict_ring = dict;
d->dict_idx = 0;
d->curlen = 0;
}
/* inflate next output bytes from compressed stream */
int uzlib_uncompress(TINF_DATA *d)
{
do {
int res;
/* start a new block */
if (d->btype == -1) {
int old_btype;
next_blk:
old_btype = d->btype;
/* read final block flag */
d->bfinal = tinf_getbit(d);
/* read block type (2 bits) */
d->btype = tinf_read_bits(d, 2, 0);
#if UZLIB_CONF_DEBUG_LOG >= 1
printf("Started new block: type=%d final=%d\n", d->btype, d->bfinal);
#endif
if (d->btype == 1 && old_btype != 1) {
/* build fixed huffman trees */
tinf_build_fixed_trees(&d->ltree, &d->dtree);
} else if (d->btype == 2) {
/* decode trees from stream */
res = tinf_decode_trees(d, &d->ltree, &d->dtree);
if (res != TINF_OK) {
return res;
}
}
}
/* process current block */
switch (d->btype)
{
case 0:
/* decompress uncompressed block */
res = tinf_inflate_uncompressed_block(d);
break;
case 1:
case 2:
/* decompress block with fixed/dynamic huffman trees */
/* trees were decoded previously, so it's the same routine for both */
res = tinf_inflate_block_data(d, &d->ltree, &d->dtree);
break;
default:
return TINF_DATA_ERROR;
}
if (res == TINF_DONE && !d->bfinal) {
/* the block has ended (without producing more data), but we
can't return without data, so start procesing next block */
goto next_blk;
}
if (res != TINF_OK) {
return res;
}
} while (d->dest < d->dest_limit);
return TINF_OK;
}
/* inflate next output bytes from compressed stream, updating
checksum, and at the end of stream, verify it */
int uzlib_uncompress_chksum(TINF_DATA *d)
{
int res;
unsigned char *data = d->dest;
res = uzlib_uncompress(d);
if (res < 0) return res;
switch (d->checksum_type) {
case TINF_CHKSUM_ADLER:
d->checksum = uzlib_adler32(data, d->dest - data, d->checksum);
break;
case TINF_CHKSUM_CRC:
d->checksum = uzlib_crc32(data, d->dest - data, d->checksum);
break;
}
if (res == TINF_DONE) {
unsigned int val;
switch (d->checksum_type) {
case TINF_CHKSUM_ADLER:
val = tinf_get_be_uint32(d);
if (d->checksum != val) {
return TINF_CHKSUM_ERROR;
}
break;
case TINF_CHKSUM_CRC:
val = tinf_get_le_uint32(d);
if (~d->checksum != val) {
return TINF_CHKSUM_ERROR;
}
// Uncompressed size. TODO: Check
val = tinf_get_le_uint32(d);
break;
}
}
return res;
}
+173
View File
@@ -0,0 +1,173 @@
/*
* uzlib - tiny deflate/inflate library (deflate, gzip, zlib)
*
* Copyright (c) 2003 by Joergen Ibsen / Jibz
* All Rights Reserved
* http://www.ibsensoftware.com/
*
* Copyright (c) 2014-2018 by Paul Sokolovsky
*
* This software is provided 'as-is', without any express
* or implied warranty. In no event will the authors be
* held liable for any damages arising from the use of
* this software.
*
* Permission is granted to anyone to use this software
* for any purpose, including commercial applications,
* and to alter it and redistribute it freely, subject to
* the following restrictions:
*
* 1. The origin of this software must not be
* misrepresented; you must not claim that you
* wrote the original software. If you use this
* software in a product, an acknowledgment in
* the product documentation would be appreciated
* but is not required.
*
* 2. Altered source versions must be plainly marked
* as such, and must not be misrepresented as
* being the original software.
*
* 3. This notice may not be removed or altered from
* any source distribution.
*/
#ifndef UZLIB_H_INCLUDED
#define UZLIB_H_INCLUDED
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
#include "uzlib_conf.h"
#if UZLIB_CONF_DEBUG_LOG
#include <stdio.h>
#endif
/* calling convention */
#ifndef TINFCC
#ifdef __WATCOMC__
#define TINFCC __cdecl
#else
#define TINFCC
#endif
#endif
/* ok status, more data produced */
#define TINF_OK 0
/* end of compressed stream reached */
#define TINF_DONE 1
#define TINF_DATA_ERROR (-3)
#define TINF_CHKSUM_ERROR (-4)
#define TINF_DICT_ERROR (-5)
/* checksum types */
#define TINF_CHKSUM_NONE 0
#define TINF_CHKSUM_ADLER 1
#define TINF_CHKSUM_CRC 2
/* helper macros */
#define TINF_ARRAY_SIZE(arr) (sizeof(arr) / sizeof(*(arr)))
/* data structures */
typedef struct {
unsigned short table[16]; /* table of code length counts */
unsigned short trans[288]; /* code -> symbol translation table */
} TINF_TREE;
struct uzlib_uncomp {
/* Pointer to the next byte in the input buffer */
const unsigned char *source;
/* Pointer to the next byte past the input buffer (source_limit = source + len) */
const unsigned char *source_limit;
/* If source_limit == NULL, or source >= source_limit, this function
will be used to read next byte from source stream. The function may
also return -1 in case of EOF (or irrecoverable error). Note that
besides returning the next byte, it may also update source and
source_limit fields, thus allowing for buffered operation. */
int (*source_read_cb)(struct uzlib_uncomp *uncomp);
unsigned int tag;
unsigned int bitcount;
/* Destination (output) buffer start */
unsigned char *dest_start;
/* Current pointer in dest buffer */
unsigned char *dest;
/* Pointer past the end of the dest buffer, similar to source_limit */
unsigned char *dest_limit;
/* Accumulating checksum */
unsigned int checksum;
char checksum_type;
bool eof;
int btype;
int bfinal;
unsigned int curlen;
int lzOff;
unsigned char *dict_ring;
unsigned int dict_size;
unsigned int dict_idx;
TINF_TREE ltree; /* dynamic length/symbol tree */
TINF_TREE dtree; /* dynamic distance tree */
};
#include "tinf_compat.h"
#define TINF_PUT(d, c) \
{ \
*d->dest++ = c; \
if (d->dict_ring) { d->dict_ring[d->dict_idx++] = c; if (d->dict_idx == d->dict_size) d->dict_idx = 0; } \
}
unsigned char TINFCC uzlib_get_byte(TINF_DATA *d);
/* Decompression API */
void TINFCC uzlib_init(void);
void TINFCC uzlib_uncompress_init(TINF_DATA *d, void *dict, unsigned int dictLen);
int TINFCC uzlib_uncompress(TINF_DATA *d);
int TINFCC uzlib_uncompress_chksum(TINF_DATA *d);
int TINFCC uzlib_zlib_parse_header(TINF_DATA *d);
int TINFCC uzlib_gzip_parse_header(TINF_DATA *d);
/* Compression API */
typedef const uint8_t *uzlib_hash_entry_t;
struct uzlib_comp {
unsigned char *outbuf;
int outlen, outsize;
unsigned long outbits;
int noutbits;
int comp_disabled;
uzlib_hash_entry_t *hash_table;
unsigned int hash_bits;
unsigned int dict_size;
};
void TINFCC uzlib_compress(struct uzlib_comp *c, const uint8_t *src, unsigned slen);
#include "defl_static.h"
/* Checksum API */
/* prev_sum is previous value for incremental computation, 1 initially */
uint32_t TINFCC uzlib_adler32(const void *data, unsigned int length, uint32_t prev_sum);
/* crc is previous value for incremental computation, 0xffffffff initially */
uint32_t TINFCC uzlib_crc32(const void *data, unsigned int length, uint32_t crc);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* UZLIB_H_INCLUDED */
+32
View File
@@ -0,0 +1,32 @@
/*
* uzlib - tiny deflate/inflate library (deflate, gzip, zlib)
*
* Copyright (c) 2014-2018 by Paul Sokolovsky
*/
#ifndef UZLIB_CONF_H_INCLUDED
#define UZLIB_CONF_H_INCLUDED
#ifndef UZLIB_CONF_DEBUG_LOG
/* Debug logging level 0, 1, 2, etc. */
#define UZLIB_CONF_DEBUG_LOG 0
#endif
#ifndef UZLIB_CONF_PARANOID_CHECKS
/* Perform extra checks on the input stream, even if they aren't proven
to be strictly required (== lack of them wasn't proven to lead to
crashes). */
#define UZLIB_CONF_PARANOID_CHECKS 0
#endif
#ifndef UZLIB_CONF_USE_MEMCPY
/* Use memcpy() for copying data out of LZ window or uncompressed blocks,
instead of doing this byte by byte. For well-compressed data, this
may noticeably increase decompression speed. But for less compressed,
it can actually deteriorate it (due to the fact that many memcpy()
implementations are optimized for large blocks of data, and have
too much overhead for short strings of just a few bytes). */
#define UZLIB_CONF_USE_MEMCPY 0
#endif
#endif /* UZLIB_CONF_H_INCLUDED */
@@ -613,6 +613,7 @@ void EpubReaderActivity::render(Activity::RenderLock&& lock) {
const auto start = millis();
renderContents(std::move(p), orientedMarginTop, orientedMarginRight, orientedMarginBottom, orientedMarginLeft);
LOG_DBG("ERS", "Rendered page in %dms", millis() - start);
renderer.clearFontCache();
}
saveProgress(currentSpineIndex, section->currentPage, section->pageCount);
}
@@ -368,6 +368,7 @@ void TxtReaderActivity::render(Activity::RenderLock&&) {
renderer.clearScreen();
renderPage();
renderer.clearFontCache();
// Save progress
saveProgress();
+8
View File
@@ -1,5 +1,6 @@
#include <Arduino.h>
#include <Epub.h>
#include <FontDecompressor.h>
#include <GfxRenderer.h>
#include <HalDisplay.h>
#include <HalGPIO.h>
@@ -36,6 +37,7 @@ HalDisplay display;
HalGPIO gpio;
MappedInputManager mappedInputManager(gpio);
GfxRenderer renderer(display);
FontDecompressor fontDecompressor;
Activity* currentActivity;
// Fonts
@@ -259,6 +261,12 @@ void setupDisplayAndFonts() {
display.begin();
renderer.begin();
LOG_DBG("MAIN", "Display initialized");
// Initialize font decompressor for compressed reader fonts
if (!fontDecompressor.init()) {
LOG_ERR("MAIN", "Font decompressor init failed");
}
renderer.setFontDecompressor(&fontDecompressor);
renderer.insertFont(BOOKERLY_14_FONT_ID, bookerly14FontFamily);
#ifndef OMIT_FONTS
renderer.insertFont(BOOKERLY_12_FONT_ID, bookerly12FontFamily);