feat: add multi-spine chapter caching for seamless cross-spine navigation

When loading a section, proactively indexes all spine items belonging
to the same TOC chapter so page-turning across spine boundaries within
a chapter is instant. Uses Section::readCachedPageCount() to skip
already-cached sections and shows an "Indexing (x/y)" progress popup.

Ported from upstream PR #1172, adapted to mod architecture.

Made-with: Cursor
This commit is contained in:
cottongin
2026-03-08 04:59:26 -04:00
parent 867faad916
commit 7fe093b57a
2 changed files with 60 additions and 0 deletions

View File

@@ -892,6 +892,58 @@ bool EpubReaderActivity::silentIndexNextChapterIfNeeded() {
return true;
}
void EpubReaderActivity::cacheMultiSpineChapter(const uint16_t vpWidth, const uint16_t vpHeight) {
if (!epub || !section) return;
const int tocCount = epub->getTocItemsCount();
if (tocCount == 0) return;
const int currentTocIdx = section->getTocIndexForPage(section->currentPage);
if (currentTocIdx < 0) return;
const int startSpine = epub->getSpineIndexForTocIndex(currentTocIdx);
if (startSpine < 0) return;
// Chapter ends where the next TOC entry begins on a different spine
int endSpine = epub->getSpineItemsCount();
for (int t = currentTocIdx + 1; t < tocCount; t++) {
const int tSpine = epub->getSpineIndexForTocIndex(t);
if (tSpine > startSpine) {
endSpine = tSpine;
break;
}
}
// Collect uncached spines in this chapter
std::vector<int> uncached;
uncached.reserve(endSpine - startSpine);
for (int i = startSpine; i < endSpine; i++) {
if (i == currentSpineIndex) continue;
auto cached = Section::readCachedPageCount(epub->getCachePath(), i, SETTINGS.getReaderFontId(),
SETTINGS.getReaderLineCompression(), SETTINGS.extraParagraphSpacing,
SETTINGS.paragraphAlignment, vpWidth, vpHeight,
SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle,
SETTINGS.imageRendering);
if (!cached) uncached.push_back(i);
}
if (uncached.empty()) return;
const int total = static_cast<int>(uncached.size());
char buf[64];
for (int idx = 0; idx < total; idx++) {
snprintf(buf, sizeof(buf), "%s (%d/%d)", tr(STR_INDEXING), idx + 1, total);
GUI.drawPopup(renderer, buf);
Section s(epub, uncached[idx], renderer);
if (!s.createSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(),
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, vpWidth, vpHeight,
SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle, SETTINGS.imageRendering)) {
LOG_ERR("ERS", "Multi-spine cache failed for spine %d", uncached[idx]);
}
}
}
// TODO: Failure handling
void EpubReaderActivity::render(RenderLock&& lock) {
if (!epub) {
@@ -1011,6 +1063,10 @@ void EpubReaderActivity::render(RenderLock&& lock) {
section->currentPage = newPage;
pendingPercentJump = false;
}
const uint16_t vpW = renderer.getScreenWidth() - orientedMarginLeft - orientedMarginRight;
const uint16_t vpH = renderer.getScreenHeight() - orientedMarginTop - orientedMarginBottom;
cacheMultiSpineChapter(vpW, vpH);
}
renderer.clearScreen();

View File

@@ -40,6 +40,10 @@ class EpubReaderActivity final : public Activity {
bool silentIndexingActive = false;
bool silentIndexNextChapterIfNeeded();
// Multi-spine chapter caching: proactively indexes all spine items that belong
// to the same TOC chapter so cross-spine page turns are instant.
void cacheMultiSpineChapter(uint16_t vpWidth, uint16_t vpHeight);
// Footnote support
std::vector<FootnoteEntry> currentPageFootnotes;
struct SavedPosition {