feat: slim footnotes support#1031
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds EPUB footnote support: new FootnoteEntry type, parser capture of internal links as footnotes, page persistence and serialization for footnotes, href→spine resolver, reader save/restore and navigation for footnotes, a footnotes UI activity and menu entry, and related i18n strings. Changes
Sequence DiagramsequenceDiagram
participant Parser as HTML Parser
participant Page as Page Storage
participant EpubCore as Epub Core
participant Reader as Reader Activity
participant FootnotesUI as Footnotes Activity
Note over Parser,Page: parse content and capture footnotes
Parser->>Parser: detect internal <a>, collect text & href -> FootnoteEntry
Parser->>Parser: enqueue pendingFootnotes (word index)
Parser->>Page: transfer pendingFootnotes to Page during layout
Note over Reader,FootnotesUI: user inspects footnotes
Reader->>FootnotesUI: open list (pass page footnotes)
FootnotesUI->>FootnotesUI: render & select footnote
FootnotesUI->>Reader: onSelectFootnote(href)
Reader->>Reader: push saved position (optional)
Reader->>EpubCore: resolveHrefToSpineIndex(href)
EpubCore-->>Reader: spine index or -1
Reader->>Reader: navigateToHref -> load target spine/page
Reader->>Page: load page & render
Note over Reader: return from footnote
Reader->>Reader: BACK (if footnoteDepth>0) -> restoreSavedPosition()
Reader->>Page: load previous page & render
Estimated code review effort🎯 4 (Complex) | ⏱️ ~65 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
This PR is #988 copy. |
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
lib/Epub/Epub.cpplib/Epub/Epub.hlib/Epub/Epub/FootnoteEntry.hlib/Epub/Epub/Page.cpplib/Epub/Epub/Page.hlib/Epub/Epub/Section.cpplib/Epub/Epub/blocks/TextBlock.hlib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpplib/Epub/Epub/parsers/ChapterHtmlSlimParser.hlib/I18n/I18nKeys.hlib/I18n/translations/english.yamlsrc/activities/reader/EpubReaderActivity.cppsrc/activities/reader/EpubReaderActivity.hsrc/activities/reader/EpubReaderFootnotesActivity.cppsrc/activities/reader/EpubReaderFootnotesActivity.hsrc/activities/reader/EpubReaderMenuActivity.h
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 988
File: lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp:649-661
Timestamp: 2026-02-19T12:17:00.962Z
Learning: In ChapterHtmlSlimParser.cpp, when computing footnote word indices in endElement() for footnote links, the wordIndex must be cumulative across the 750-word mid-paragraph flush boundary. The correct calculation is: `wordIndex = wordsExtractedInBlock + currentTextBlock->size()`, not just `currentTextBlock->size()`. This ensures footnotes attach to the page containing their actual anchor word, even after layoutAndExtractLines(false) has extracted and removed earlier words from the block.
📚 Learning: 2026-02-19T12:17:00.962Z
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 988
File: lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp:649-661
Timestamp: 2026-02-19T12:17:00.962Z
Learning: In ChapterHtmlSlimParser.cpp, when computing footnote word indices in endElement() for footnote links, the wordIndex must be cumulative across the 750-word mid-paragraph flush boundary. The correct calculation is: `wordIndex = wordsExtractedInBlock + currentTextBlock->size()`, not just `currentTextBlock->size()`. This ensures footnotes attach to the page containing their actual anchor word, even after layoutAndExtractLines(false) has extracted and removed earlier words from the block.
Applied to files:
lib/Epub/Epub/blocks/TextBlock.hlib/Epub/Epub/FootnoteEntry.hlib/Epub/Epub.hsrc/activities/reader/EpubReaderActivity.cpplib/Epub/Epub/Page.cpplib/Epub/Epub/parsers/ChapterHtmlSlimParser.hlib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpplib/Epub/Epub/Page.hsrc/activities/reader/EpubReaderActivity.hlib/Epub/Epub/Section.cpplib/Epub/Epub.cpp
📚 Learning: 2026-02-16T20:43:19.339Z
Learnt from: Levrk
Repo: crosspoint-reader/crosspoint-reader PR: 909
File: src/activities/reader/EpubReaderActivity.cpp:461-461
Timestamp: 2026-02-16T20:43:19.339Z
Learning: In src/activities/reader/EpubReaderActivity.cpp, when using ConfirmationActivity with enterNewActivity(), it's not necessary to call exitActivity() beforehand. The ConfirmationActivity operates as a modal dialog and cleanup is handled via the pendingSubactivityExit flag in the callback lambda. This differs from other activities like EpubReaderChapterSelectionActivity or KOReaderSyncActivity which do require exitActivity() before enterNewActivity().
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cppsrc/activities/reader/EpubReaderFootnotesActivity.hsrc/activities/reader/EpubReaderMenuActivity.hsrc/activities/reader/EpubReaderActivity.cppsrc/activities/reader/EpubReaderActivity.h
📚 Learning: 2026-02-18T15:43:07.515Z
Learnt from: GavinHigham
Repo: crosspoint-reader/crosspoint-reader PR: 966
File: src/activities/ActivityWithSubactivity.h:20-21
Timestamp: 2026-02-18T15:43:07.515Z
Learning: In the crosspoint-reader activity architecture, when using ActivityWithSubactivity, the currentActivity global variable remains at the top-level activity (e.g., ReaderActivity) even when nested subactivities are active. The supportsOrientation() delegation chain starts from the top-level activity and stops when it reaches a subactivity that overrides the method (e.g., EpubReaderActivity returns true). Deeper nested subactivities (like EpubReaderChapterSelectionActivity or EpubReaderPercentSelectionActivity) do not need to override supportsOrientation() because they never become the currentActivity and the delegation chain doesn't reach them.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cppsrc/activities/reader/EpubReaderFootnotesActivity.hsrc/activities/reader/EpubReaderMenuActivity.hsrc/activities/reader/EpubReaderActivity.cppsrc/activities/reader/EpubReaderActivity.h
📚 Learning: 2026-02-19T17:46:26.871Z
Learnt from: Tritlo
Repo: crosspoint-reader/crosspoint-reader PR: 1003
File: src/activities/reader/EpubReaderActivity.cpp:657-674
Timestamp: 2026-02-19T17:46:26.871Z
Learning: In src/activities/reader/EpubReaderActivity.cpp's renderContents() method, when uncached images exist, Phase 1 intentionally calls displayBuffer(forceFullRefresh) to perform a HALF_REFRESH (if needed), while Phase 2 intentionally calls renderer.displayBuffer() directly without forceFullRefresh. This is by design: Phase 1's refresh clears the screen properly to prevent ghosting, so Phase 2 can use a faster refresh mode for better performance. The grayscale anti-aliasing is handled separately after renderContents() via displayGrayBuffer().
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cppsrc/activities/reader/EpubReaderMenuActivity.h
📚 Learning: 2026-02-19T17:46:26.871Z
Learnt from: Tritlo
Repo: crosspoint-reader/crosspoint-reader PR: 1003
File: src/activities/reader/EpubReaderActivity.cpp:657-674
Timestamp: 2026-02-19T17:46:26.871Z
Learning: In EpubReaderActivity.cpp, renderContents() behavior is intentional: if uncached images exist, Phase 1 should call displayBuffer(forceFullRefresh) to perform a HALF_REFRESH and clear ghosting; Phase 2 should call renderer.displayBuffer() without forceFullRefresh for a faster refresh. Grayscale anti-aliasing is applied separately after renderContents() via displayGrayBuffer(). This guidance applies specifically to this file and situation.
Applied to files:
src/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-02-12T06:57:35.955Z
Learnt from: CaptainFrito
Repo: crosspoint-reader/crosspoint-reader PR: 725
File: src/activities/network/CalibreConnectActivity.cpp:218-264
Timestamp: 2026-02-12T06:57:35.955Z
Learning: In src/activities/network/CalibreConnectActivity.cpp, button hints (« Exit) are intentionally omitted from the SERVER_STARTING and ERROR states—only the SERVER_RUNNING state displays navigation hints. This is a deliberate design decision by the author.
Applied to files:
src/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-02-16T22:25:35.674Z
Learnt from: whyte-j
Repo: crosspoint-reader/crosspoint-reader PR: 733
File: lib/I18n/I18nKeys.h:271-272
Timestamp: 2026-02-16T22:25:35.674Z
Learning: In the crosspoint-reader i18n system (lib/I18n/), missing translation keys in non-English YAML files are automatically filled with English strings at build time by the gen_i18n.py script. All generated string arrays (STRINGS_EN, STRINGS_ES, etc.) have identical lengths, so runtime array indexing is safe without per-string fallback logic. The system prints "INFO: '{key}' missing in {lang_code}, using English fallback" during code generation when this occurs.
Applied to files:
lib/I18n/translations/english.yaml
🧬 Code graph analysis (6)
src/activities/reader/EpubReaderFootnotesActivity.cpp (1)
src/activities/reader/EpubReaderActivity.cpp (4)
loop(121-273)loop(121-121)render(510-645)render(510-510)
src/activities/reader/EpubReaderFootnotesActivity.h (1)
src/activities/reader/EpubReaderFootnotesActivity.cpp (8)
onEnter(12-16)onEnter(12-12)onExit(18-18)onExit(18-18)loop(20-51)loop(20-20)render(53-95)render(53-53)
lib/Epub/Epub.h (1)
lib/Epub/Epub.cpp (2)
resolveHrefToSpineIndex(850-875)resolveHrefToSpineIndex(850-850)
src/activities/reader/EpubReaderActivity.cpp (3)
src/main.cpp (4)
exitActivity(136-142)exitActivity(136-136)enterNewActivity(144-147)enterNewActivity(144-144)src/activities/ActivityWithSubactivity.cpp (6)
exitActivity(20-27)exitActivity(20-20)enterNewActivity(29-34)enterNewActivity(29-29)requestUpdate(42-47)requestUpdate(42-42)src/activities/Activity.cpp (2)
requestUpdate(42-48)requestUpdate(42-42)
lib/Epub/Epub/Page.cpp (2)
lib/Serialization/Serialization.h (1)
writePod(8-10)src/CrossPointSettings.cpp (2)
file(94-100)file(94-94)
src/activities/reader/EpubReaderActivity.h (1)
src/activities/reader/EpubReaderActivity.cpp (4)
navigateToHref(816-853)navigateToHref(816-816)restoreSavedPosition(855-868)restoreSavedPosition(855-855)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: cppcheck
- GitHub Check: build
🔇 Additional comments (21)
lib/I18n/I18nKeys.h (1)
354-356: LGTM — new StrId entries look consistent.lib/I18n/translations/english.yaml (1)
320-322: LGTM — English strings added for new keys.lib/Epub/Epub/blocks/TextBlock.h (1)
31-31: LGTM — accessor is simple and const-correct.lib/Epub/Epub/Section.cpp (1)
12-12: LGTM — cache version bump is appropriate.lib/Epub/Epub.h (1)
75-75: LGTM — public API addition is clear.lib/Epub/Epub/Page.h (1)
59-69: LGTM — footnote storage and cap look fine.lib/Epub/Epub/FootnoteEntry.h (1)
1-12: LGTM — simple, clear POD struct.lib/Epub/Epub.cpp (1)
850-874: Remove concern about anchor-only hrefs; anchor-only references are already handled by the caller beforeresolveHrefToSpineIndexis invoked.The
navigateToHreffunction (EpubReaderActivity.cpp line 828) checks for anchor-only hrefs (hrefStr[0] == '#') and handles them by using the current spine index directly, so they never reachresolveHrefToSpineIndex. The remaining concern about filename-only fallback causing mis-resolution with duplicate basenames is a minor edge case—the code attempts an exact match first, and duplicate filenames across directories are uncommon in well-formed EPUBs.src/activities/reader/EpubReaderActivity.h (1)
3-36: Footnote state scaffolding is clean and self-contained.Also applies to: 48-50
src/activities/reader/EpubReaderMenuActivity.h (1)
15-15: FOOTNOTES menu action wiring looks consistent.Also applies to: 42-46
lib/Epub/Epub/Page.cpp (1)
70-81: Footnote (de)serialization handles bounds and errors cleanly.Also applies to: 107-124
src/activities/reader/EpubReaderFootnotesActivity.h (1)
1-35: Footnotes activity interface is concise and clear.src/activities/reader/EpubReaderActivity.cpp (5)
13-13: Include for footnotes support is fine.
190-195: Back now restores saved footnote position—nice UX touch.
386-402: Footnotes menu action wires in the new activity cleanly.
636-638: Collecting page footnotes for the UI is straightforward.
855-868: Saved-position restore logic is clear and self-contained.src/activities/reader/EpubReaderFootnotesActivity.cpp (1)
1-95: Footnotes list navigation and rendering look solid.lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h (1)
8-10: Footnote tracking state additions are clear.Also applies to: 68-75
lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp (2)
52-68: Footnote link detection and label normalization are well-contained.Also applies to: 325-366, 516-528, 649-662
138-139: Cumulative word tracking and footnote-to-page assignment look consistent.Also applies to: 802-812, 851-859
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/activities/reader/EpubReaderActivity.cpp`:
- Around line 816-853: The navigateToHref function can decrement footnoteDepth
on failure even when nothing was pushed; fix by tracking whether a push actually
occurred (e.g., introduce a local bool pushed = false) and only decrement
footnoteDepth on the error path if pushed is true; ensure the push logic around
savedPositions, section, and footnoteDepth < MAX_FOOTNOTE_DEPTH sets pushed =
true when it stores the current position so later rollback only happens if a
push was performed.
|
@Uri-Tauber I noticed that scrolling in the footnotes list doesn't overwrap (going up from the first item doesn't go to the last, etc), do you think we should fix this? Otherwise looks good |
Sure. It's a small fix. |
|
Small UX improvement: I "stole" the idea from #857, where the dictionary lookup option is hidden when no dictionary files are found on the SD card. |
|
@Uri-Tauber new conflicts 😄 |
6fc3fb0 to
9652d03
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/activities/reader/EpubReaderActivity.cpp`:
- Around line 646-649: currentPageFootnotes can persist from a previous page
when render() fails or returns early, causing the Footnotes menu to be
incorrect; update the render() function (and any explicit early-return branches)
to reset/clear currentPageFootnotes (e.g., set to empty container/null) at the
start of render() and before every early return so footnotes reflect the
currently displayed screen rather than stale data.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
lib/Epub/Epub.cpplib/Epub/Epub/Page.hlib/Epub/Epub/Section.cpplib/Epub/Epub/blocks/TextBlock.hlib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpplib/Epub/Epub/parsers/ChapterHtmlSlimParser.hlib/I18n/I18nKeys.hlib/I18n/translations/english.yamlsrc/activities/reader/EpubReaderActivity.cppsrc/activities/reader/EpubReaderActivity.hsrc/activities/reader/EpubReaderMenuActivity.cppsrc/activities/reader/EpubReaderMenuActivity.h
🚧 Files skipped from review as they are similar to previous changes (6)
- src/activities/reader/EpubReaderActivity.h
- src/activities/reader/EpubReaderMenuActivity.cpp
- lib/I18n/translations/english.yaml
- lib/Epub/Epub/Section.cpp
- lib/Epub/Epub/blocks/TextBlock.h
- lib/Epub/Epub/Page.h
📜 Review details
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 1031
File: src/activities/reader/EpubReaderActivity.cpp:816-853
Timestamp: 2026-02-21T16:47:45.578Z
Learning: In src/activities/reader/EpubReaderActivity.cpp, the navigateToHref() function uses a fixed-size mini-stack (MAX_FOOTNOTE_DEPTH = 3) for saved positions rather than a dynamic stack. This is intentional to minimize RAM usage on ESP32-C3, which has limited memory. The design accepts that failed navigation at max depth may decrement footnoteDepth even when no push occurred, as a tradeoff for memory efficiency. Users exit by pressing Back three times maximum.
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 988
File: lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp:649-661
Timestamp: 2026-02-19T12:17:05.421Z
Learning: In ChapterHtmlSlimParser.cpp, when computing footnote word indices in endElement() for footnote links, the wordIndex must be cumulative across the 750-word mid-paragraph flush boundary. The correct calculation is: `wordIndex = wordsExtractedInBlock + currentTextBlock->size()`, not just `currentTextBlock->size()`. This ensures footnotes attach to the page containing their actual anchor word, even after layoutAndExtractLines(false) has extracted and removed earlier words from the block.
📚 Learning: 2026-02-19T12:17:05.421Z
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 988
File: lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp:649-661
Timestamp: 2026-02-19T12:17:05.421Z
Learning: In ChapterHtmlSlimParser.cpp, when computing footnote word indices in endElement() for footnote links, the wordIndex must be cumulative across the 750-word mid-paragraph flush boundary. The correct calculation is: `wordIndex = wordsExtractedInBlock + currentTextBlock->size()`, not just `currentTextBlock->size()`. This ensures footnotes attach to the page containing their actual anchor word, even after layoutAndExtractLines(false) has extracted and removed earlier words from the block.
Applied to files:
lib/Epub/Epub/parsers/ChapterHtmlSlimParser.hlib/Epub/Epub.cpplib/Epub/Epub/parsers/ChapterHtmlSlimParser.cppsrc/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-02-21T16:47:45.578Z
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 1031
File: src/activities/reader/EpubReaderActivity.cpp:816-853
Timestamp: 2026-02-21T16:47:45.578Z
Learning: In src/activities/reader/EpubReaderActivity.cpp, the navigateToHref() function uses a fixed-size mini-stack (MAX_FOOTNOTE_DEPTH = 3) for saved positions rather than a dynamic stack. This is intentional to minimize RAM usage on ESP32-C3, which has limited memory. The design accepts that failed navigation at max depth may decrement footnoteDepth even when no push occurred, as a tradeoff for memory efficiency. Users exit by pressing Back three times maximum.
Applied to files:
lib/Epub/Epub.cppsrc/activities/reader/EpubReaderMenuActivity.hlib/Epub/Epub/parsers/ChapterHtmlSlimParser.cppsrc/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-02-22T19:13:14.049Z
Learnt from: ngxson
Repo: crosspoint-reader/crosspoint-reader PR: 1016
File: src/activities/reader/EpubReaderActivity.cpp:138-146
Timestamp: 2026-02-22T19:13:14.049Z
Learning: In src/activities/reader/EpubReaderActivity.cpp, the EpubReaderMenuActivity result callback intentionally applies orientation changes (via applyOrientation) before checking result.isCancelled. This differs from other callbacks in the file because orientation is treated as an immediate setting that should persist even when the user cancels the menu, rather than a deferred action requiring confirmation.
Applied to files:
src/activities/reader/EpubReaderMenuActivity.hsrc/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-02-16T20:43:19.339Z
Learnt from: Levrk
Repo: crosspoint-reader/crosspoint-reader PR: 909
File: src/activities/reader/EpubReaderActivity.cpp:461-461
Timestamp: 2026-02-16T20:43:19.339Z
Learning: In src/activities/reader/EpubReaderActivity.cpp, when using ConfirmationActivity with enterNewActivity(), it's not necessary to call exitActivity() beforehand. The ConfirmationActivity operates as a modal dialog and cleanup is handled via the pendingSubactivityExit flag in the callback lambda. This differs from other activities like EpubReaderChapterSelectionActivity or KOReaderSyncActivity which do require exitActivity() before enterNewActivity().
Applied to files:
src/activities/reader/EpubReaderMenuActivity.hsrc/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-02-18T15:43:12.258Z
Learnt from: GavinHigham
Repo: crosspoint-reader/crosspoint-reader PR: 966
File: src/activities/ActivityWithSubactivity.h:20-21
Timestamp: 2026-02-18T15:43:12.258Z
Learning: In the crosspoint-reader activity architecture, when using ActivityWithSubactivity, the currentActivity global variable remains at the top-level activity (e.g., ReaderActivity) even when nested subactivities are active. The supportsOrientation() delegation chain starts from the top-level activity and stops when it reaches a subactivity that overrides the method (e.g., EpubReaderActivity returns true). Deeper nested subactivities (like EpubReaderChapterSelectionActivity or EpubReaderPercentSelectionActivity) do not need to override supportsOrientation() because they never become the currentActivity and the delegation chain doesn't reach them.
Applied to files:
src/activities/reader/EpubReaderMenuActivity.hsrc/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-02-19T17:46:26.871Z
Learnt from: Tritlo
Repo: crosspoint-reader/crosspoint-reader PR: 1003
File: src/activities/reader/EpubReaderActivity.cpp:657-674
Timestamp: 2026-02-19T17:46:26.871Z
Learning: In EpubReaderActivity.cpp, renderContents() behavior is intentional: if uncached images exist, Phase 1 should call displayBuffer(forceFullRefresh) to perform a HALF_REFRESH and clear ghosting; Phase 2 should call renderer.displayBuffer() without forceFullRefresh for a faster refresh. Grayscale anti-aliasing is applied separately after renderContents() via displayGrayBuffer(). This guidance applies specifically to this file and situation.
Applied to files:
src/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-02-23T06:18:08.408Z
Learnt from: raygan
Repo: crosspoint-reader/crosspoint-reader PR: 1116
File: src/activities/settings/WallabagSettingsActivity.cpp:57-75
Timestamp: 2026-02-23T06:18:08.408Z
Learning: In settings activities (e.g., KOReaderSettingsActivity, WallabagSettingsActivity), the established pattern for launching KeyboardEntryActivity is to call exitActivity() on the parent settings activity before enterNewActivity(new KeyboardEntryActivity(...)). The lambda callbacks then call exitActivity() to exit the KeyboardEntryActivity and return to the parent. This differs from ConfirmationActivity which does not require exitActivity() before enterNewActivity().
Applied to files:
src/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-02-12T06:57:35.955Z
Learnt from: CaptainFrito
Repo: crosspoint-reader/crosspoint-reader PR: 725
File: src/activities/network/CalibreConnectActivity.cpp:218-264
Timestamp: 2026-02-12T06:57:35.955Z
Learning: In src/activities/network/CalibreConnectActivity.cpp, button hints (« Exit) are intentionally omitted from the SERVER_STARTING and ERROR states—only the SERVER_RUNNING state displays navigation hints. This is a deliberate design decision by the author.
Applied to files:
src/activities/reader/EpubReaderActivity.cpp
🧬 Code graph analysis (2)
src/activities/reader/EpubReaderMenuActivity.h (1)
src/activities/reader/EpubReaderMenuActivity.cpp (3)
EpubReaderMenuActivity(10-23)buildMenuItems(25-38)buildMenuItems(25-25)
src/activities/reader/EpubReaderActivity.cpp (3)
src/main.cpp (4)
exitActivity(136-142)exitActivity(136-136)enterNewActivity(144-147)enterNewActivity(144-144)src/activities/ActivityWithSubactivity.cpp (6)
exitActivity(20-27)exitActivity(20-20)enterNewActivity(29-34)enterNewActivity(29-29)requestUpdate(42-47)requestUpdate(42-42)src/activities/Activity.cpp (2)
requestUpdate(42-48)requestUpdate(42-42)
🔇 Additional comments (19)
lib/I18n/I18nKeys.h (1)
360-363: Footnotes string IDs wired in cleanly.
Placement before the sentinel is correct and keeps enum order stable.lib/Epub/Epub.cpp (1)
862-887: No issues found. The code correctly handles anchor-only hrefs in thenavigateToHref()caller (EpubReaderActivity.cpp:865–869) by detectinghrefStr[0] == '#'and routing to the current spine index, preventing anchor-only references from ever reachingresolveHrefToSpineIndex(). Cross-file hrefs are properly resolved by the function, and-1failures are logged and handled.src/activities/reader/EpubReaderMenuActivity.h (2)
15-30: Good: FOOTNOTES action and hasFootnotes flag wired into the menu API.
43-47: Nice refactor to centralize menu item construction.src/activities/reader/EpubReaderActivity.cpp (6)
13-13: Include for footnotes activity looks correct.
179-183: Menu now correctly reflects whether the current page has footnotes.
192-197: Back behavior cleanly restores the saved position for footnote navigation.
388-404: Footnotes menu action wiring is clear and consistent with the subactivity flow.
850-887: Navigation helper is clean and keeps the reader state consistent.
889-902: Saved-position restore is straightforward and well scoped.lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h (2)
8-10: Includes are aligned with new footnote state.Explicitly pulling in
<vector>andFootnoteEntrymakes the dependency clear.
71-78: Footnote tracking state looks well-scoped.Fixed-size buffers and the pending list align with the low-RAM design goals.
lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp (7)
52-68: Helper utilities are clean and focused.Centralizing attribute lookup and internal-link checks simplifies the parsing flow.
141-143: Good reset of cumulative word tracking.Reinitializing
wordsExtractedInBlockper text block keeps indices consistent.
452-494: Internal-link handling is solid.Buffer flush + underline styling + href capture provides clear link behavior without extra CSS work.
648-659: Footnote label normalization is correct.Stripping whitespace/brackets yields stable display labels like “1”.
822-835: Cumulative word index logic is correct.Using
wordsExtractedInBlock + currentTextBlock->size()preserves accurate placement after mid-paragraph flushes.
Based on learnings, “when computing footnote word indices in endElement() for footnote links, the wordIndex must be cumulative across the 750-word mid-paragraph flush boundary. The correct calculation is:wordIndex = wordsExtractedInBlock + currentTextBlock->size()”.
990-1000: Pending-footnote transfer is well-timed.Advancing
wordsExtractedInBlockbefore assignment ensures anchors land on the correct page.
1039-1047: Fallback footnote transfer is a good safety net.This prevents edge-case drops at the end of a block.
|
I resolved the latest merge conflicts with I flashed the updated firmware to the device and ran validation tests to ensure everything was functioning correctly. During testing, I pressed several buttons simultaneously and encountered a crash. I attempted to reproduce the issue several times but was unable to do so. I don’t believe the crash is related to the changes introduced in this PR, as it had not occurred previously with this branch. It may be related to other recent changes merged into I will continue investigating. In the meantime, here is the serial log captured at the time of the crash: |
|
The best is that you can decode the stack trace using https://esphome.github.io/esp-stacktrace-decoder/ Note that the stack trace is only valid with a given |
|
Here's a patch that adds a test epub document: And the test epub (zipped the already zipped .epub, because GitHub doesn't accept .epub files): |
52decc7 to
de656ed
Compare
| int footnoteLinkDepth = -1; | ||
| char currentFootnoteLinkText[24] = {}; | ||
| int currentFootnoteLinkTextLen = 0; | ||
| char currentFootnoteLinkHref[64] = {}; |
There was a problem hiding this comment.
Note that these should better be abstracted to a StackBuffer<char, length>, see CssParser.cpp. But I'll do that in another PR
| // Extract filename (remove #anchor) | ||
| std::string target = href; | ||
| size_t hashPos = target.find('#'); | ||
| if (hashPos != std::string::npos) target = target.substr(0, hashPos); |
There was a problem hiding this comment.
nits, but you can use string_view to avoid create copy of the substring
There was a problem hiding this comment.
Agreed, but I have another change I'll propose soon to use string_view where possible. I'm working on some decent benchmarking measurements first, to better communicate the improvements.
| private: | ||
| const std::vector<FootnoteEntry>& footnotes; | ||
| const std::function<void()> onGoBack; | ||
| const std::function<void(const char*)> onSelectFootnote; |
There was a problem hiding this comment.
since the argument will be copied into std::string anyway, it's better to simply pass std::string here
also, note that after #1016, data passing between 2 activities will be a copy to avoid any memory safety issues
**What is the goal of this PR?** Implement support for footnotes in epub files. It is based on crosspoint-reader#553, but simplified — removed the parts which complicated the code and burden the CPU/RAM. This version supports basic footnotes and lets the user jump from location to location inside the epub. **What changes are included?** - `FootnoteEntry` struct — A small POD struct (number[24], href[64]) shared between parser, page storage, and UI. - Parser: `<a href>` detection (`ChapterHtmlSlimParser`) — During a single parsing pass, internal epub links are detected and collected as footnotes. The link text is underlined to hint navigability. Bracket/whitespace normalization is applied to the display label (e.g. [1] → 1). - Footnote-to-page assignment (`ChapterHtmlSlimParser`, `Page`) — Footnotes are attached to the exact page where their anchor word appears, tracked via a cumulative word counter during layout, surviving paragraph splits and the 750-word mid-paragraph safety flush. - Page serialization (`Page`, `Section`) — Footnotes are serialized/deserialized per page (max 16 per page). Section cache version bumped to 14 to force a clean rebuild. - Href → spine resolution (`Epub`) — `resolveHrefToSpineIndex()` maps an href (e.g. `chapter2.xhtml#note1`) to its spine index by filename matching. - Footnotes menu + activity (`EpubReaderMenuActivity`, `EpubReaderFootnotesActivity`) — A new "Footnotes" entry in the reader menu lists all footnote links found on the current page. The user scrolls and selects to navigate. - Navigate & restore (`EpubReaderActivity`) — `navigateToHref()` saves the current spine index and page number, then jumps to the target. The Back button restores the saved position when the user is done reading the footnote. **Additional Context** **What was removed vs crosspoint-reader#553:** virtual spine items (`addVirtualSpineItem`, `isVirtualSpineItem`), two-pass parsing, `<aside>` content extraction to temp HTML files, `<p class="note">` paragraph note extraction, `replaceHtmlEntities` (master already has `lookupHtmlEntity`), `footnotePages` / `buildFilteredChapterList`, `noterefCallback` / `Noteref` struct, and the stack size increase from 8 KB to 24 KB (not needed without two-pass parsing and virtual file I/O on the render task). **Performance:** Single-pass parsing. No new heap allocations in the hot path — footnote text is collected into fixed stack buffers (char[24], char[64]). Active runtime memory is ~2.8 KB worst-case (one page × 16 footnotes × 88 bytes, mirrored in `currentPageFootnotes`). Flash usage is unchanged at 97.4%; RAM stays at 31%. **Known limitations:** When clicking a footnote, it jumps to the start of the HTML file instead of the specific anchor. This could be problematic for books that don't have separate files for each footnote. (no element-id-to-page mapping yet - will be another PR soon). --- Did you use AI tools to help write this code? _**< PARTIALLY>**_ Claude Opus 4.6 was used to do most of the migration, I checked manually its work, and fixed some stuff, but I haven't review all the changes yet, so feedback is welcomed. --------- Co-authored-by: Arthur Tazhitdinov <[email protected]>
**What is the goal of this PR?** Implement support for footnotes in epub files. It is based on crosspoint-reader#553, but simplified — removed the parts which complicated the code and burden the CPU/RAM. This version supports basic footnotes and lets the user jump from location to location inside the epub. **What changes are included?** - `FootnoteEntry` struct — A small POD struct (number[24], href[64]) shared between parser, page storage, and UI. - Parser: `<a href>` detection (`ChapterHtmlSlimParser`) — During a single parsing pass, internal epub links are detected and collected as footnotes. The link text is underlined to hint navigability. Bracket/whitespace normalization is applied to the display label (e.g. [1] → 1). - Footnote-to-page assignment (`ChapterHtmlSlimParser`, `Page`) — Footnotes are attached to the exact page where their anchor word appears, tracked via a cumulative word counter during layout, surviving paragraph splits and the 750-word mid-paragraph safety flush. - Page serialization (`Page`, `Section`) — Footnotes are serialized/deserialized per page (max 16 per page). Section cache version bumped to 14 to force a clean rebuild. - Href → spine resolution (`Epub`) — `resolveHrefToSpineIndex()` maps an href (e.g. `chapter2.xhtml#note1`) to its spine index by filename matching. - Footnotes menu + activity (`EpubReaderMenuActivity`, `EpubReaderFootnotesActivity`) — A new "Footnotes" entry in the reader menu lists all footnote links found on the current page. The user scrolls and selects to navigate. - Navigate & restore (`EpubReaderActivity`) — `navigateToHref()` saves the current spine index and page number, then jumps to the target. The Back button restores the saved position when the user is done reading the footnote. **Additional Context** **What was removed vs crosspoint-reader#553:** virtual spine items (`addVirtualSpineItem`, `isVirtualSpineItem`), two-pass parsing, `<aside>` content extraction to temp HTML files, `<p class="note">` paragraph note extraction, `replaceHtmlEntities` (master already has `lookupHtmlEntity`), `footnotePages` / `buildFilteredChapterList`, `noterefCallback` / `Noteref` struct, and the stack size increase from 8 KB to 24 KB (not needed without two-pass parsing and virtual file I/O on the render task). **Performance:** Single-pass parsing. No new heap allocations in the hot path — footnote text is collected into fixed stack buffers (char[24], char[64]). Active runtime memory is ~2.8 KB worst-case (one page × 16 footnotes × 88 bytes, mirrored in `currentPageFootnotes`). Flash usage is unchanged at 97.4%; RAM stays at 31%. **Known limitations:** When clicking a footnote, it jumps to the start of the HTML file instead of the specific anchor. This could be problematic for books that don't have separate files for each footnote. (no element-id-to-page mapping yet - will be another PR soon). --- Did you use AI tools to help write this code? _**< PARTIALLY>**_ Claude Opus 4.6 was used to do most of the migration, I checked manually its work, and fixed some stuff, but I haven't review all the changes yet, so feedback is welcomed. --------- Co-authored-by: Arthur Tazhitdinov <[email protected]>
**What is the goal of this PR?** Implement support for footnotes in epub files. It is based on crosspoint-reader#553, but simplified — removed the parts which complicated the code and burden the CPU/RAM. This version supports basic footnotes and lets the user jump from location to location inside the epub. **What changes are included?** - `FootnoteEntry` struct — A small POD struct (number[24], href[64]) shared between parser, page storage, and UI. - Parser: `<a href>` detection (`ChapterHtmlSlimParser`) — During a single parsing pass, internal epub links are detected and collected as footnotes. The link text is underlined to hint navigability. Bracket/whitespace normalization is applied to the display label (e.g. [1] → 1). - Footnote-to-page assignment (`ChapterHtmlSlimParser`, `Page`) — Footnotes are attached to the exact page where their anchor word appears, tracked via a cumulative word counter during layout, surviving paragraph splits and the 750-word mid-paragraph safety flush. - Page serialization (`Page`, `Section`) — Footnotes are serialized/deserialized per page (max 16 per page). Section cache version bumped to 14 to force a clean rebuild. - Href → spine resolution (`Epub`) — `resolveHrefToSpineIndex()` maps an href (e.g. `chapter2.xhtml#note1`) to its spine index by filename matching. - Footnotes menu + activity (`EpubReaderMenuActivity`, `EpubReaderFootnotesActivity`) — A new "Footnotes" entry in the reader menu lists all footnote links found on the current page. The user scrolls and selects to navigate. - Navigate & restore (`EpubReaderActivity`) — `navigateToHref()` saves the current spine index and page number, then jumps to the target. The Back button restores the saved position when the user is done reading the footnote. **Additional Context** **What was removed vs crosspoint-reader#553:** virtual spine items (`addVirtualSpineItem`, `isVirtualSpineItem`), two-pass parsing, `<aside>` content extraction to temp HTML files, `<p class="note">` paragraph note extraction, `replaceHtmlEntities` (master already has `lookupHtmlEntity`), `footnotePages` / `buildFilteredChapterList`, `noterefCallback` / `Noteref` struct, and the stack size increase from 8 KB to 24 KB (not needed without two-pass parsing and virtual file I/O on the render task). **Performance:** Single-pass parsing. No new heap allocations in the hot path — footnote text is collected into fixed stack buffers (char[24], char[64]). Active runtime memory is ~2.8 KB worst-case (one page × 16 footnotes × 88 bytes, mirrored in `currentPageFootnotes`). Flash usage is unchanged at 97.4%; RAM stays at 31%. **Known limitations:** When clicking a footnote, it jumps to the start of the HTML file instead of the specific anchor. This could be problematic for books that don't have separate files for each footnote. (no element-id-to-page mapping yet - will be another PR soon). --- Did you use AI tools to help write this code? _**< PARTIALLY>**_ Claude Opus 4.6 was used to do most of the migration, I checked manually its work, and fixed some stuff, but I haven't review all the changes yet, so feedback is welcomed. --------- Co-authored-by: Arthur Tazhitdinov <[email protected]>
…er#1169,crosspoint-reader#1031 feat: Auto Page Turn for Epub Reader (crosspoint-reader#1219) fix: clarity issue with ambiguous string SET (crosspoint-reader#1169) feat: slim footnotes support (crosspoint-reader#1031)
## Summary * **What is the goal of this PR?** Add missing strings and tweaks for polish language * **What changes are included?** ## Additional Context * Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks, specific areas to focus on). --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**< YES | PARTIALLY | NO >**_
…t-reader#1169,crosspoint-reader#1031 +tweaks (crosspoint-reader#1227) ## Summary * **What is the goal of this PR?** Add missing strings and tweaks for polish language * **What changes are included?** ## Additional Context * Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks, specific areas to focus on). --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**< YES | PARTIALLY | NO >**_
Summary
What is the goal of this PR? Implement support for footnotes in epub files.
It is based on #553, but simplified — removed the parts which complicated the code and burden the CPU/RAM. This version supports basic footnotes and lets the user jump from location to location inside the epub.
What changes are included?
-
FootnoteEntrystruct — A small POD struct (number[24], href[64]) shared between parser, page storage, and UI.- Parser:
<a href>detection (ChapterHtmlSlimParser) — During a single parsing pass, internal epub links are detected and collected as footnotes. The link text is underlined to hint navigability. Bracket/whitespace normalization is applied to the display label (e.g. [1] → 1).- Footnote-to-page assignment (
ChapterHtmlSlimParser,Page) — Footnotes are attached to the exact page where their anchor word appears, tracked via a cumulative word counter during layout, surviving paragraph splits and the 750-word mid-paragraph safety flush.- Page serialization (
Page,Section) — Footnotes are serialized/deserialized per page (max 16 per page). Section cache version bumped to 14 to force a clean rebuild.- Href → spine resolution (
Epub) —resolveHrefToSpineIndex()maps an href (e.g.chapter2.xhtml#note1) to its spine index by filename matching.- Footnotes menu + activity (
EpubReaderMenuActivity,EpubReaderFootnotesActivity) — A new "Footnotes" entry in the reader menu lists all footnote links found on the current page. The user scrolls and selects to navigate.- Navigate & restore (
EpubReaderActivity) —navigateToHref()saves the current spine index and page number, then jumps to the target. The Back button restores the saved position when the user is done reading the footnote.Additional Context
What was removed vs #553: virtual spine items (
addVirtualSpineItem,isVirtualSpineItem), two-pass parsing,<aside>content extraction to temp HTML files,<p class="note">paragraph note extraction,replaceHtmlEntities(master already haslookupHtmlEntity),footnotePages/buildFilteredChapterList,noterefCallback/Noterefstruct, and the stack size increase from 8 KB to 24 KB (not needed without two-pass parsing and virtual file I/O on the render task).Performance: Single-pass parsing. No new heap allocations in the hot path — footnote text is collected into fixed stack buffers (char[24], char[64]). Active runtime memory is ~2.8 KB worst-case (one page × 16 footnotes × 88 bytes, mirrored in
currentPageFootnotes). Flash usage is unchanged at 97.4%; RAM stays at 31%.Known limitations: When clicking a footnote, it jumps to the start of the HTML file instead of the specific anchor. This could be problematic for books that don't have separate files for each footnote. (no element-id-to-page mapping yet - will be another PR soon).
AI Usage
Did you use AI tools to help write this code? < PARTIALLY>
Claude Opus 4.6 was used to do most of the migration, I checked manually its work, and fixed some stuff, but I haven't review all the changes yet, so feedback is welcomed.