Add FreeInkBook EPUB engine migration guide

Document migration plan from CrossPoint's lib/Epub to FreeInkBook, a memory-efficient clean-room EPUB engine. Covers advantages (O(paragraph+page) memory via SAX parsing, arena allocation, host-testable, full UAX#14 typography, runtime TTF/OTF support), 9-step migration strategy, ESP32-C3 constraints, and integration approach preserving existing UI layer while replacing parsing/layout stack.
This commit is contained in:
Justin Mitchell
2026-07-08 00:22:21 -04:00
parent 5776911af0
commit 0848649ce2
49 changed files with 1065 additions and 15633 deletions
+142
View File
@@ -0,0 +1,142 @@
#pragma once
// BookPaginator — one open book's FreeInkBook session: container, fonts,
// layout parameters, and the per-chapter page cache. This is the engine-side
// half of the reader; EpubReaderActivity keeps the UI half (input, menus,
// status bar, refresh cadence) and talks to the engine only through this.
//
// Memory model (ESP32-C3, no PSRAM — all buffers heap-allocated once in
// open() and freed in close()):
// book arena 64 KB ZIP catalog + metadata + spine + TOC (corpus
// high-water 5-40 KB; omnibus containers that exceed
// it fail with a clean OutOfMemory)
// index arena 16 KB current chapter's page index + anchor table
// page arena 16 KB decoded runs of the page being rendered
// build scratch (transient, ~120 KB) allocated only while a chapter
// (re)paginates — the layout engine's whole working set
// Steady-state page turns touch only the page arena (~2 KB used).
#include <FreeInkBook.h>
#include <cache/PageCache.h>
#include <css/Css.h>
#include <layout/ChapterLayout.h>
#include <render/TtfFont.h>
#include <text/Hyphenator.h>
#include <memory>
#include <string>
#include "CpFontAdapter.h"
#include "FreeInkBookStorage.h"
class GfxRenderer;
class BookPaginator {
public:
// Called periodically during a chapter (re)pagination so the UI can show
// an indexing popup / keep the watchdog fed.
struct BuildProgress {
void* ctx;
void (*fn)(void* ctx, uint32_t pagesDone);
}; // value-initialized ({nullptr, nullptr}) by the default argument
BookPaginator() = default;
~BookPaginator() { close(); }
BookPaginator(const BookPaginator&) = delete;
BookPaginator& operator=(const BookPaginator&) = delete;
// Opens the container and builds the font chain. `cacheDir` is the
// per-book directory (".crosspoint/epub_<hash>"). Plain-text files open
// as a one-chapter book with no container.
bool open(const std::string& path, const std::string& cacheDir, GfxRenderer& renderer);
void close();
bool isOpen() const { return open_; }
bool isTxt() const { return isTxt_; }
freeink::book::Book& book() { return book_; }
size_t spineCount() const { return isTxt_ ? 1 : book_.spineCount(); }
const char* language() const;
// Refreshes LayoutParams from SETTINGS and the given content box. Must be
// called before ensureChapter() and after any settings/orientation change;
// a changed generation makes the next ensureChapter() re-paginate.
void configureLayout(int16_t pageWidth, int16_t pageHeight, int16_t marginLeft, int16_t marginRight,
int16_t marginTop, int16_t marginBottom);
// Everything layout-relevant, hashed — the cache key.
uint32_t generation() const;
// Opens the chapter's page cache for the current generation, laying the
// chapter out first when missing or stale. Heavy only on that miss.
freeink::book::BookStatus ensureChapter(uint16_t spineIndex, const BuildProgress& progress = BuildProgress());
bool chapterReady() const { return curSpine_ != kNoSpine; }
uint16_t currentSpine() const { return curSpine_; }
uint32_t pageCount() const { return reader_.pageCount(); }
uint32_t totalChars() const { return reader_.totalChars(); }
uint32_t charStartOfPage(uint32_t pageIndex) const { return reader_.charStart(pageIndex); }
uint32_t pageForChar(uint32_t charOffset) const { return reader_.pageForChar(charOffset); }
// Resolve an id="" fragment in the CURRENT chapter to a char offset.
bool charForAnchor(const char* fragment, uint32_t* charOut) const;
// Decodes one page into the page arena; the returned Page's records stay
// valid until the next readPage() call.
freeink::book::BookStatus readPage(uint32_t pageIndex, freeink::book::Page* out);
// Spine index for a container href (link/TOC targets), -1 when absent.
int spineIndexForHref(const char* href) const;
// Render-path lockstep: the renderer font id for a run's sizePx, quantized
// exactly as CpFontAdapter quantized during layout.
int fontIdForRunSize(uint16_t sizePx) const;
// The ladder rung (px) a run size resolves to — for underline metrics etc.
uint16_t quantizeRunSize(uint16_t sizePx) const { return adapters_[0].quantize(sizePx); }
const freeink::book::LayoutParams& layoutParams() const { return params_; }
private:
static constexpr uint16_t kNoSpine = 0xFFFF;
static constexpr size_t kBookArenaSize = 48 * 1024;
static constexpr size_t kIndexArenaSize = 12 * 1024;
static constexpr size_t kPageArenaSize = 12 * 1024;
static constexpr size_t kSheetArenaSize = 12 * 1024;
static constexpr size_t kBuildScratchSize = 120 * 1024;
bool buildFontChain(GfxRenderer& renderer);
void loadHyphenator();
uint32_t fontFingerprint() const;
SdBookSource source_;
SdCacheStorage cache_;
freeink::book::Book book_;
freeink::book::LayoutParams params_;
freeink::book::PageCacheReader reader_;
freeink::book::FontChain chain_;
CpFontAdapter adapters_[4] = {
CpFontAdapter(EpdFontFamily::REGULAR),
CpFontAdapter(EpdFontFamily::BOLD),
CpFontAdapter(EpdFontFamily::ITALIC),
CpFontAdapter(EpdFontFamily::BOLD_ITALIC),
};
freeink::book::Hyphenator hyphenator_;
std::unique_ptr<uint8_t[]> hyphBlob_; // SD-loaded patterns (non-English books)
std::unique_ptr<uint8_t[]> bookBuf_;
std::unique_ptr<uint8_t[]> indexBuf_;
std::unique_ptr<uint8_t[]> pageBuf_;
std::unique_ptr<uint8_t[]> sheetBuf_;
freeink::book::Arena bookArena_;
freeink::book::Arena indexArena_;
freeink::book::Arena pageArena_;
freeink::book::Arena sheetArena_;
freeink::book::CssStylesheet sheet_{};
// PageCacheReader borrows this for readPage() — must outlive the reader.
char cacheName_[64] = "";
int ladderFontIds_[CpFontAdapter::kMaxLadder] = {};
uint16_t ladderSizes_[CpFontAdapter::kMaxLadder] = {};
uint8_t ladderCount_ = 0;
uint16_t curSpine_ = kNoSpine;
bool open_ = false;
bool isTxt_ = false;
};