mirror of
https://github.com/pelgraine/Meck.git
synced 2026-07-03 08:11:20 +02:00
117 lines
5.2 KiB
Markdown
117 lines
5.2 KiB
Markdown
# Text & EPUB Reader Integration for Meck Firmware
|
||
|
||
## Overview
|
||
|
||
This adds a text reader accessible via the **R** key from the home screen.
|
||
|
||
**Features:**
|
||
- Browse `.txt` and `.epub` files from `/books/` folder on SD card
|
||
- Automatic EPUB-to-text conversion on first open (cached for instant re-opens)
|
||
- Word-wrapped text rendering using tiny font (maximum text density)
|
||
- Page navigation with W/S/A/D keys
|
||
- Automatic reading position resume (persisted to SD card)
|
||
- Index files cached to SD for instant re-opens
|
||
- Bookmark indicator (`*`) on files with saved positions
|
||
|
||
**Key Mapping:**
|
||
| Context | Key | Action |
|
||
|---------|-----|--------|
|
||
| Home screen | E | Open text reader |
|
||
| File list | W/S | Navigate up/down |
|
||
| File list | Enter | Open selected file |
|
||
| File list | Q | Back to home screen |
|
||
| Reading | W/A | Previous page |
|
||
| Reading | S/D/Space/Enter | Next page |
|
||
| Reading | Q | Close book → file list |
|
||
| Reading | C | Enter compose mode |
|
||
|
||
---
|
||
|
||
## SD Card Setup
|
||
|
||
Place `.txt` or `.epub` files in a `/books/` folder on the SD card root. The reader will:
|
||
- Auto-create `/books/` if it doesn't exist
|
||
- Auto-create `/.indexes/` for page index cache files
|
||
- Auto-create `/books/.epub_cache/` for converted EPUB text
|
||
- Skip macOS hidden files (`._*`, `.DS_Store`)
|
||
- Support up to 50 files
|
||
|
||
**Index format** is compatible with the standalone reader (version 4), so if you've used the standalone reader previously, bookmarks and indexes will carry over.
|
||
|
||
---
|
||
|
||
## EPUB Support
|
||
|
||
### How It Works
|
||
|
||
EPUB files are transparently converted to plain text on first open. The conversion pipeline is:
|
||
|
||
1. **File list** — `scanFiles()` picks up both `.txt` and `.epub` files from `/books/`
|
||
2. **First open** — `openBook()` detects the `.epub` extension and triggers conversion:
|
||
- Shows a "Converting EPUB..." splash screen
|
||
- Extracts the ZIP structure using ESP32-S3's built-in ROM `tinfl` decompressor (no external library needed)
|
||
- Parses `META-INF/container.xml` → finds the OPF file
|
||
- Parses the OPF manifest and spine to get chapters in reading order
|
||
- Extracts each XHTML chapter, strips tags, decodes HTML entities
|
||
- Writes concatenated plain text to `/books/.epub_cache/<filename>.txt`
|
||
3. **Subsequent opens** — the cached `.txt` is found immediately and opened like any regular text file
|
||
|
||
### Cache Structure
|
||
|
||
```
|
||
/books/
|
||
MyBook.epub ← original EPUB (untouched)
|
||
SomeStory.txt ← regular text file
|
||
.epub_cache/
|
||
MyBook.txt ← auto-generated from MyBook.epub
|
||
/.indexes/
|
||
MyBook.txt.idx ← page index for the converted text
|
||
```
|
||
|
||
- The original `.epub` file is never modified
|
||
- Deleting a cached `.txt` from `.epub_cache/` forces re-conversion on next open
|
||
- Index files (`.idx`) work identically for both regular and EPUB-derived text files
|
||
- Boot scan picks up previously cached EPUB text files so they appear in the file list even before the EPUB is re-opened
|
||
|
||
### EPUB Processing Details
|
||
|
||
The conversion is handled by three components:
|
||
|
||
| Component | Role |
|
||
|-----------|------|
|
||
| `EpubZipReader.h` | ZIP central directory parsing + `tinfl` decompression (supports Store and Deflate) |
|
||
| `EpubProcessor.h` | EPUB structure parsing (container.xml → OPF → spine) and XHTML tag stripping |
|
||
| `TextReaderScreen.h` | Integration: detects `.epub`, triggers conversion, redirects to cached `.txt` |
|
||
|
||
**XHTML stripping handles:**
|
||
- Tag removal with block-element newlines (`<p>`, `<br>`, `<div>`, `<h1>`–`<h6>`, `<li>`, etc.)
|
||
- `<head>`, `<style>`, `<script>` content skipped entirely
|
||
- HTML entity decoding: named (`&`, `—`, `“`, etc.) and numeric (`—`, `—`)
|
||
- Smart quote / em-dash / ellipsis → ASCII equivalents (e-ink font is ASCII-only)
|
||
- Whitespace collapsing and cleanup
|
||
|
||
**Limits:**
|
||
- Max 200 chapters in spine (`EPUB_MAX_CHAPTERS`)
|
||
- Max 256 manifest items (`EPUB_MAX_MANIFEST`)
|
||
- Manifest and chapter data are heap-allocated in PSRAM where available
|
||
- Typical conversion time: 2–10 seconds depending on book size
|
||
|
||
### Troubleshooting
|
||
|
||
| Symptom | Likely Cause |
|
||
|---------|-------------|
|
||
| "Convert failed!" splash | EPUB may be DRM-protected, corrupted, or use an unusual structure |
|
||
| EPUB appears in list but opens as blank | Check serial output for `EpubProc:` messages; chapter count may be 0 |
|
||
| Stale content after replacing an EPUB | Delete the matching `.txt` from `/books/.epub_cache/` to force re-conversion |
|
||
|
||
---
|
||
|
||
## Architecture Notes
|
||
|
||
- The reader renders through the standard `UIScreen::render()` framework, so no special bypass is needed in the main loop (unlike compose mode)
|
||
- SD card uses the same HSPI bus as e-ink display and LoRa radio — CS pin management handles contention
|
||
- Page content is pre-read from SD into a memory buffer during `handleInput()`, then rendered from buffer during `render()` — this avoids SPI bus conflicts during display refresh
|
||
- Layout metrics (chars per line, lines per page) are calculated dynamically from the display driver's font metrics on first entry
|
||
- EPUB conversion runs synchronously in `openBook()` — the e-ink splash screen keeps the user informed while the ESP32 processes the archive
|
||
- ZIP extraction uses the ESP32-S3's hardware-optimised ROM `tinfl` inflate, avoiding external compression library dependencies and the linker conflicts they cause
|