diff --git a/docs/epub-toc-navigation.md b/docs/epub-toc-navigation.md index b84d3871..2a56a194 100644 --- a/docs/epub-toc-navigation.md +++ b/docs/epub-toc-navigation.md @@ -196,6 +196,22 @@ The section cache file (`section.bin`) gained a `pageBreakMap` block: `uint16_t `Section::getPrintedPageLabelForPage(devicePage)` returns the parenthesised label (e.g. `"(42)"`) for the rendered device page if one or more printed-page anchors land on it. When several labels co-occur on a single device page (short page contains both `page_7` and `page_8`), the result collapses to `"(7/8)"`. Returns `nullopt` when no printed-page anchor falls on this exact device page — in that case the status bar shows only the device counter. +### Sleep overlay lookup + +`Section::getPrintedPageLabelFromCache(sectionsDir, spineIndex, page)` is a standalone helper used by `SleepActivity` — it reads the printed-page label directly from `section.bin`'s page-break map without instantiating a `Section` or supplying render parameters. It walks the sections cache directory, finds any cache variant for `spineIndex` (all variants share the same printed-page anchors since those are content-derived), reads its `pageBreakMap` block, and returns the same `"(42)"` formatting as the in-memory query. The function is version-guarded against `SECTION_FILE_VERSION` so a cache from a different layout is skipped silently. Cost: one extra `section.bin` open per sleep entry, skipped when no cache exists. + +### "Jump to printed page" navigation + +The reader menu exposes a `STR_GO_TO_PRINTED_PAGE` entry, gated on the book having at least one integer-parseable label in `pagelist.bin` (roman-only or empty page lists hide the menu item). Selecting it opens `EpubReaderPrintedPageInputActivity`, a numeric input dialog: + +- Up/Down adjust the digit under the cursor by ±1 (with carry). PageBack/PageForward mirror Up/Down so the physical page-turn buttons still work. +- Left/Right move the cursor between digits. +- Confirm returns a `PrintedPageResult { std::string label }`; Back cancels. +- Pre-fills with the printed-page label currently shown on the status bar (stripped of parens), falling back to the lowest integer label in the book. +- Shows the valid integer range underneath ("Range: 1 - 305") and a step hint. + +On confirmation, `EpubReaderActivity` resolves the typed label by linear scan through the loaded `pagelist.bin` entries to recover `(href, anchor)`, calls `Epub::resolveHrefToSpineIndex` to get the target spine, and sets `navTarget = NavigationTarget::makeAnchor(anchor)` (or `makePage(0)` for top-of-file entries). The existing anchor-jump infrastructure in the renderer then handles the actual page-resolution and section load. Labels that exist in the dialog's integer range but skip in the book's `pagelist.bin` (e.g. publisher omitted page 17) are logged at DBG and the reader stays put — no navigation happens. + ## Performance characteristics - **Per page turn**: All in-memory. `getTocIndexForPage` (binary search on 1-3 entries), `getTocItem` for title (one file seek to BookMetadataCache -- noted as a future optimization opportunity). diff --git a/lib/Epub/Epub.cpp b/lib/Epub/Epub.cpp index 521d137a..b3de26bf 100644 --- a/lib/Epub/Epub.cpp +++ b/lib/Epub/Epub.cpp @@ -1301,6 +1301,30 @@ float Epub::calculateProgress(const int currentSpineIndex, const float currentSp return totalProgress / static_cast(bookSize); } +std::vector Epub::loadPrintedPageList() const { + std::vector entries; + const auto pageListPath = getCachePath() + "/pagelist.bin"; + if (!Storage.exists(pageListPath.c_str())) { + return entries; + } + FsFile f; + if (!Storage.openFileForRead("EBP", pageListPath, f)) { + return entries; + } + uint16_t count = 0; + serialization::readPod(f, count); + entries.reserve(count); + for (uint16_t i = 0; i < count; i++) { + PrintedPageEntry e; + serialization::readString(f, e.href); + serialization::readString(f, e.anchor); + serialization::readString(f, e.label); + entries.push_back(std::move(e)); + } + f.close(); + return entries; +} + int Epub::resolveHrefToSpineIndex(const std::string& href) const { if (!bookMetadataCache || !bookMetadataCache->isLoaded()) return -1; diff --git a/lib/Epub/Epub.h b/lib/Epub/Epub.h index f98ae7de..92b25b9f 100644 --- a/lib/Epub/Epub.h +++ b/lib/Epub/Epub.h @@ -89,4 +89,15 @@ class Epub { float calculateProgress(int currentSpineIndex, float currentSpineRead) const; CssParser* getCssParser() const { return cssParser.get(); } int resolveHrefToSpineIndex(const std::string& href) const; + + // Printed-page list (from NCX / EPUB 3 nav page-list / EPUB 2.01 page-map.xml). + // One entry per printed-page anchor: spine href + fragment id + visible label. + struct PrintedPageEntry { + std::string href; + std::string anchor; + std::string label; + }; + // Reads /pagelist.bin. Returns empty vector when the book has no printed-page data. + // Inexpensive — only invoked from menu paths, not page-turn hot paths. + std::vector loadPrintedPageList() const; }; diff --git a/lib/Epub/Epub/Section.cpp b/lib/Epub/Epub/Section.cpp index d05f92e9..a8f63478 100644 --- a/lib/Epub/Epub/Section.cpp +++ b/lib/Epub/Epub/Section.cpp @@ -966,6 +966,18 @@ std::optional Section::getPrintedPageLabelFromCache(const std::stri return std::string("(") + labelsOnPage.front() + "/" + labelsOnPage.back() + ")"; } +std::optional Section::getNearestPrintedPageLabelAtOrBefore(uint16_t page) const { + // pageBreakLabels is built in document order (i.e. ascending pageIndex), so the last + // entry whose page is <= `page` is the "you're currently reading at or after this + // printed page" hint. Returns the raw label (no parens, no slash-collapsing). + std::optional best; + for (const auto& [labelPage, label] : pageBreakLabels) { + if (labelPage > page) break; + best = label; + } + return best; +} + std::optional Section::getPrintedPageLabelForPage(uint16_t page) const { // Collect every printed-page label whose anchor lands on this exact rendered page. // Multiple labels can co-occur when a short device page contains more than one EPUB diff --git a/lib/Epub/Epub/Section.h b/lib/Epub/Epub/Section.h index 84630855..1c284e0b 100644 --- a/lib/Epub/Epub/Section.h +++ b/lib/Epub/Epub/Section.h @@ -94,6 +94,11 @@ class Section { // one or more EPUB pagebreak markers / NCX / page-map entries land on it. // Returns nullopt when no printed-page anchor falls on this exact page. std::optional getPrintedPageLabelForPage(uint16_t page) const; + // Like getPrintedPageLabelForPage but returns the most recent printed-page label at or + // before `page` (raw label, no parens). Useful for pre-filling jump-to-page dialogs when + // the current rendered page doesn't itself carry an anchor. Returns nullopt when no + // printed-page anchor exists on this or any earlier page in the section. + std::optional getNearestPrintedPageLabelAtOrBefore(uint16_t page) const; // Standalone lookup that doesn't require a loaded Section. Walks the book's sections cache // directory, finds any cache variant for `spineIndex`, reads its printed-page label map, diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index d5e99b9e..afb86d49 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -382,6 +382,10 @@ STR_HW_CONFIRM_LABEL: "Confirm (2nd button)" STR_HW_LEFT_LABEL: "Left (3rd button)" STR_HW_RIGHT_LABEL: "Right (4th button)" STR_GO_TO_PERCENT: "Go to %" +STR_GO_TO_PRINTED_PAGE: "Go to printed page" +STR_GO_TO_PRINTED_PAGE_RANGE: "Range: %u - %u" +STR_GO_TO_PRINTED_PAGE_HINT: "Up/Down: ±1 (x2: ±10) Left/Right: digit" +STR_GO_TO_PRINTED_PAGE_NOT_FOUND: "Page %s not found" STR_GO_HOME_BUTTON: "Go Home" STR_SYNC_PROGRESS: "Sync Progress" STR_PUSH_PROGRESS_FROM_THIS_DEVICE: "Push progress from this device"