From 9f31f80c80ff43390c5a3281679aeea9e441426d Mon Sep 17 00:00:00 2001 From: Dave Allie Date: Tue, 30 Dec 2025 17:52:42 +1000 Subject: [PATCH] Show previous title for unnamed spines (#158) ## Summary * Show previous title for unnamed spines * The spec is a little unclear, but there are plenty of cases where chapters are split up in parts and should show the previous chapter's title * List TOC items instead of spine items in chapter select * Bump `BOOK_CACHE_VERSION` to `2` to force regeneration of spine item's TOC indexes --- lib/Epub/Epub/BookMetadataCache.cpp | 10 ++++-- .../EpubReaderChapterSelectionActivity.cpp | 34 +++++++++++-------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/lib/Epub/Epub/BookMetadataCache.cpp b/lib/Epub/Epub/BookMetadataCache.cpp index 6272414..6cc7ea7 100644 --- a/lib/Epub/Epub/BookMetadataCache.cpp +++ b/lib/Epub/Epub/BookMetadataCache.cpp @@ -9,7 +9,7 @@ #include "FsHelpers.h" namespace { -constexpr uint8_t BOOK_CACHE_VERSION = 1; +constexpr uint8_t BOOK_CACHE_VERSION = 2; constexpr char bookBinFile[] = "/book.bin"; constexpr char tmpSpineBinFile[] = "/spine.bin.tmp"; constexpr char tmpTocBinFile[] = "/toc.bin.tmp"; @@ -143,6 +143,7 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta } uint32_t cumSize = 0; spineFile.seek(0); + int lastSpineTocIndex = -1; for (int i = 0; i < spineCount; i++) { auto spineEntry = readSpineEntry(spineFile); @@ -158,9 +159,12 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta // Not a huge deal if we don't fine a TOC entry for the spine entry, this is expected behaviour for EPUBs // Logging here is for debugging if (spineEntry.tocIndex == -1) { - Serial.printf("[%lu] [BMC] Warning: Could not find TOC entry for spine item %d: %s\n", millis(), i, - spineEntry.href.c_str()); + Serial.printf( + "[%lu] [BMC] Warning: Could not find TOC entry for spine item %d: %s, using title from last section\n", + millis(), i, spineEntry.href.c_str()); + spineEntry.tocIndex = lastSpineTocIndex; } + lastSpineTocIndex = spineEntry.tocIndex; // Calculate size for cumulative size size_t itemSize = 0; diff --git a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp index 8245ed2..5f0cab2 100644 --- a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp +++ b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp @@ -39,7 +39,10 @@ void EpubReaderChapterSelectionActivity::onEnter() { } renderingMutex = xSemaphoreCreateMutex(); - selectorIndex = currentSpineIndex; + selectorIndex = epub->getTocIndexForSpineIndex(currentSpineIndex); + if (selectorIndex == -1) { + selectorIndex = 0; + } // Trigger first update updateRequired = true; @@ -74,22 +77,27 @@ void EpubReaderChapterSelectionActivity::loop() { const int pageItems = getPageItems(); if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) { - onSelectSpineIndex(selectorIndex); + const auto newSpineIndex = epub->getSpineIndexForTocIndex(selectorIndex); + if (newSpineIndex == -1) { + onGoBack(); + } else { + onSelectSpineIndex(newSpineIndex); + } } else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { onGoBack(); } else if (prevReleased) { if (skipPage) { selectorIndex = - ((selectorIndex / pageItems - 1) * pageItems + epub->getSpineItemsCount()) % epub->getSpineItemsCount(); + ((selectorIndex / pageItems - 1) * pageItems + epub->getTocItemsCount()) % epub->getTocItemsCount(); } else { - selectorIndex = (selectorIndex + epub->getSpineItemsCount() - 1) % epub->getSpineItemsCount(); + selectorIndex = (selectorIndex + epub->getTocItemsCount() - 1) % epub->getTocItemsCount(); } updateRequired = true; } else if (nextReleased) { if (skipPage) { - selectorIndex = ((selectorIndex / pageItems + 1) * pageItems) % epub->getSpineItemsCount(); + selectorIndex = ((selectorIndex / pageItems + 1) * pageItems) % epub->getTocItemsCount(); } else { - selectorIndex = (selectorIndex + 1) % epub->getSpineItemsCount(); + selectorIndex = (selectorIndex + 1) % epub->getTocItemsCount(); } updateRequired = true; } @@ -116,15 +124,11 @@ void EpubReaderChapterSelectionActivity::renderScreen() { const auto pageStartIndex = selectorIndex / pageItems * pageItems; renderer.fillRect(0, 60 + (selectorIndex % pageItems) * 30 - 2, pageWidth - 1, 30); - for (int i = pageStartIndex; i < epub->getSpineItemsCount() && i < pageStartIndex + pageItems; i++) { - const int tocIndex = epub->getTocIndexForSpineIndex(i); - if (tocIndex == -1) { - renderer.drawText(UI_FONT_ID, 20, 60 + (i % pageItems) * 30, "Unnamed", i != selectorIndex); - } else { - auto item = epub->getTocItem(tocIndex); - renderer.drawText(UI_FONT_ID, 20 + (item.level - 1) * 15, 60 + (i % pageItems) * 30, item.title.c_str(), - i != selectorIndex); - } + for (int tocIndex = pageStartIndex; tocIndex < epub->getTocItemsCount() && tocIndex < pageStartIndex + pageItems; + tocIndex++) { + auto item = epub->getTocItem(tocIndex); + renderer.drawText(UI_FONT_ID, 20 + (item.level - 1) * 15, 60 + (tocIndex % pageItems) * 30, item.title.c_str(), + tocIndex != selectorIndex); } renderer.displayBuffer();