fix: make footnotes consider orientation for gutters#1665
Conversation
📝 WalkthroughWalkthroughThe EpubReaderFootnotesActivity render routine was changed to compute layout offsets from renderer orientation, reserving a 30px horizontal gutter in landscape and a 50px vertical gutter in inverted portrait. Title, "no footnotes" message, and list item positions now use manual x/y offsets derived from those gutters. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 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 |
|
@coderabbitai review |
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/EpubReaderFootnotesActivity.cpp`:
- Line 75: The empty-state string STR_NO_FOOTNOTES is being drawn using the
titleX coordinate which miscenters it when its width differs; compute the pixel
width of tr(STR_NO_FOOTNOTES) with the same font (UI_10_FONT_ID) and set its x
to (centerX - footnoteTextWidth/2) (or reuse the same centering expression used
for the title, but using the computed width) before calling renderer.drawText;
update the call to renderer.drawText(UI_10_FONT_ID, computedX, 90 + contentY,
tr(STR_NO_FOOTNOTES)) so the message is centered correctly instead of reusing
titleX.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b4f71fdf-15fd-4636-8728-72e16d14518d
📒 Files selected for processing (1)
src/activities/reader/EpubReaderFootnotesActivity.cpp
📜 Review details
🧰 Additional context used
🧠 Learnings (17)
📓 Common learnings
Learnt from: aarons
Repo: crosspoint-reader/crosspoint-reader PR: 1659
File: src/activities/reader/ReaderUtils.h:48-51
Timestamp: 2026-04-13T22:45:24.939Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/reader/ReaderUtils.h, detectRotation), the rotation direction logic `isBottom ? (orientation - 1 + COUNT) % COUNT : (orientation + 1) % COUNT` is intentionally correct as written. Despite the enum ordering PORTRAIT→LANDSCAPE_CW→INVERTED→LANDSCAPE_CCW suggesting "+1 = clockwise", on the actual device hardware the bottom button (isBottom=true) stepping backward through the enum (-1) produces a clockwise rotation and the top button (+1) produces counter-clockwise. Do not flag this as reversed in future reviews.
Learnt from: aarons
Repo: crosspoint-reader/crosspoint-reader PR: 1659
File: src/activities/reader/ReaderUtils.h:48-51
Timestamp: 2026-04-13T23:10:44.397Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/reader/ReaderUtils.h, detectRotation), the rotation logic uses "content rotation" semantics, not "device rotation" semantics. The bottom button (isBottom=true) rotates the on-screen content clockwise, which physically corresponds to rotating the device counter-clockwise. The enum step `(orientation - 1 + COUNT) % COUNT` achieves CW content rotation; `(orientation + 1) % COUNT` achieves CCW content rotation. This is intentional. Do not flag as reversed. Named helpers `rotateContentCW` / `rotateContentCCW` were suggested by the author as a potential future clarity improvement.
Learnt from: znelson
Repo: crosspoint-reader/crosspoint-reader PR: 1447
File: src/activities/reader/EpubReaderActivity.cpp:642-660
Timestamp: 2026-03-20T19:51:12.696Z
Learning: In crosspoint-reader/crosspoint-reader (EpubReaderActivity.cpp, prepareSection), when `epub->getTocIndexForSpineIndex(currentSpineIndex)` returns a negative value (pre-TOC spine with no TOC entry), `prepareSection` skips the entire chapter walk and leaves `chapterPageInfo` unset. The status bar correctly falls back to `section->pageCount` in this scenario. This means the `pageTocIndex == -1` path inside the `render()` per-page TOC update block is handled correctly by simply leaving `chapterPageInfo` unchanged — no explicit clear is needed.
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 0
File: :0-0
Timestamp: 2026-03-28T20:57:09.493Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/home/HomeActivity.cpp, HomeActivity::onEnter), `coverRendered = false` and `coverBufferStored = false` ARE correctly reset in `onEnter()`. This was fixed in PR `#1488`. Do not flag their absence as a bug in future reviews.
Learnt from: ngxson
Repo: crosspoint-reader/crosspoint-reader PR: 1016
File: src/activities/reader/EpubReaderActivity.cpp:138-146
Timestamp: 2026-02-22T19:13:22.166Z
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.
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.
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 1629
File: src/activities/home/HomeActivity.cpp:119-120
Timestamp: 2026-04-12T12:28:36.371Z
Learning: In crosspoint-reader/crosspoint-reader, review comment severity should follow this policy:
- "Major" (🟠): reserve exclusively for issues with a realistic risk of crash, OOM, invalid pointer dereference, data corruption, or other defects that are unlikely to surface in casual device testing.
- "Minor" or informational: use for UX gaps, logic edge-cases, style issues, or missing feature completeness that the author can verify (or has verified) through normal device use.
- Assume the author has tested the feature on their device; do not escalate behavioral/UX observations to Major.
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: whyte-j
Repo: crosspoint-reader/crosspoint-reader PR: 733
File: src/components/StatusBar.cpp:63-78
Timestamp: 2026-02-16T22:59:07.687Z
Learning: In the CrossPoint Reader codebase, screen margin values are preset in settings with constrained options, so calculations involving margins (like `rendererableScreenWidth` or `availableTitleSpace` in StatusBar rendering) are unlikely to produce negative values in practice given these bounds.
Learnt from: Tritlo
Repo: crosspoint-reader/crosspoint-reader PR: 1003
File: src/activities/reader/EpubReaderActivity.cpp:657-674
Timestamp: 2026-02-19T17:46:36.345Z
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().
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.
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 1245
File: lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp:137-150
Timestamp: 2026-03-02T21:18:25.387Z
Learning: In ChapterHtmlSlimParser.cpp, pendingAnchorId is a single string (not a vector) because EPUB footnote targets are virtually always block-level elements (<aside>, <p>, <div>) that trigger startNewTextBlock() and flush the anchor before overwrite can occur. Multiple consecutive inline IDs would resolve to the same completedPageCount anyway, and vector allocation overhead is unnecessary for ESP32-C3. Only inline IDs in practice are footnote back-references (<a id="ref1">), which are never navigation targets.
📚 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:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-03-20T19:51:12.696Z
Learnt from: znelson
Repo: crosspoint-reader/crosspoint-reader PR: 1447
File: src/activities/reader/EpubReaderActivity.cpp:642-660
Timestamp: 2026-03-20T19:51:12.696Z
Learning: In crosspoint-reader/crosspoint-reader (EpubReaderActivity.cpp, prepareSection), when `epub->getTocIndexForSpineIndex(currentSpineIndex)` returns a negative value (pre-TOC spine with no TOC entry), `prepareSection` skips the entire chapter walk and leaves `chapterPageInfo` unset. The status bar correctly falls back to `section->pageCount` in this scenario. This means the `pageTocIndex == -1` path inside the `render()` per-page TOC update block is handled correctly by simply leaving `chapterPageInfo` unchanged — no explicit clear is needed.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-02-22T19:13:22.166Z
Learnt from: ngxson
Repo: crosspoint-reader/crosspoint-reader PR: 1016
File: src/activities/reader/EpubReaderActivity.cpp:138-146
Timestamp: 2026-02-22T19:13:22.166Z
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/EpubReaderFootnotesActivity.cpp
📚 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:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-02-19T17:46:36.345Z
Learnt from: Tritlo
Repo: crosspoint-reader/crosspoint-reader PR: 1003
File: src/activities/reader/EpubReaderActivity.cpp:657-674
Timestamp: 2026-02-19T17:46:36.345Z
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.cpp
📚 Learning: 2026-03-02T21:18:25.387Z
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 1245
File: lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp:137-150
Timestamp: 2026-03-02T21:18:25.387Z
Learning: In ChapterHtmlSlimParser.cpp, pendingAnchorId is a single string (not a vector) because EPUB footnote targets are virtually always block-level elements (<aside>, <p>, <div>) that trigger startNewTextBlock() and flush the anchor before overwrite can occur. Multiple consecutive inline IDs would resolve to the same completedPageCount anyway, and vector allocation overhead is unnecessary for ESP32-C3. Only inline IDs in practice are footnote back-references (<a id="ref1">), which are never navigation targets.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-04-13T23:10:44.397Z
Learnt from: aarons
Repo: crosspoint-reader/crosspoint-reader PR: 1659
File: src/activities/reader/ReaderUtils.h:48-51
Timestamp: 2026-04-13T23:10:44.397Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/reader/ReaderUtils.h, detectRotation), the rotation logic uses "content rotation" semantics, not "device rotation" semantics. The bottom button (isBottom=true) rotates the on-screen content clockwise, which physically corresponds to rotating the device counter-clockwise. The enum step `(orientation - 1 + COUNT) % COUNT` achieves CW content rotation; `(orientation + 1) % COUNT` achieves CCW content rotation. This is intentional. Do not flag as reversed. Named helpers `rotateContentCW` / `rotateContentCCW` were suggested by the author as a potential future clarity improvement.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-02-16T22:59:07.687Z
Learnt from: whyte-j
Repo: crosspoint-reader/crosspoint-reader PR: 733
File: src/components/StatusBar.cpp:63-78
Timestamp: 2026-02-16T22:59:07.687Z
Learning: In the CrossPoint Reader codebase, screen margin values are preset in settings with constrained options, so calculations involving margins (like `rendererableScreenWidth` or `availableTitleSpace` in StatusBar rendering) are unlikely to produce negative values in practice given these bounds.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-03-29T19:50:44.877Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 1534
File: src/activities/boot_sleep/SleepActivity.cpp:177-183
Timestamp: 2026-03-29T19:50:44.877Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/boot_sleep/SleepActivity.cpp, SleepActivity::getBookOverlayInfo), when reading totalPages from the TXT index cache (index.bin), the code must validate the cache header: check the magic number (0x54585449 / "TXTI") and version fields before trusting totalPages. This mirrors TxtReaderActivity::loadPageIndexCache(). If validation fails, totalPages stays 0 and the overlay falls back to "Page X" (no total) via STR_OVERLAY_READING_PROGRESS_NO_TOTAL. Do not flag this validation as unnecessary in future reviews.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-04-13T22:45:24.939Z
Learnt from: aarons
Repo: crosspoint-reader/crosspoint-reader PR: 1659
File: src/activities/reader/ReaderUtils.h:48-51
Timestamp: 2026-04-13T22:45:24.939Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/reader/ReaderUtils.h, detectRotation), the rotation direction logic `isBottom ? (orientation - 1 + COUNT) % COUNT : (orientation + 1) % COUNT` is intentionally correct as written. Despite the enum ordering PORTRAIT→LANDSCAPE_CW→INVERTED→LANDSCAPE_CCW suggesting "+1 = clockwise", on the actual device hardware the bottom button (isBottom=true) stepping backward through the enum (-1) produces a clockwise rotation and the top button (+1) produces counter-clockwise. Do not flag this as reversed in future reviews.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-04-08T15:24:06.672Z
Learnt from: zgredex
Repo: crosspoint-reader/crosspoint-reader PR: 0
File: :0-0
Timestamp: 2026-04-08T15:24:06.672Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/reader/XtcReaderActivity.cpp, src/activities/util/BmpViewerActivity.cpp, src/activities/reader/EpubReaderActivity.cpp, and other callsites), renderGrayscale's callback type is `void (*renderFn)(const GfxRenderer&, const void*)`. Lambdas passed as this callback correctly use `const GfxRenderer& r` as the first parameter. Where the renderer must be mutated inside the callback (e.g., drawBitmap, drawButtonHints), `const_cast<GfxRenderer&>(r)` is used. No `cppcheck-suppress constParameterReference` is needed. Do not flag `const GfxRenderer& r` in these lambdas or suggest removing the `const_cast` in future reviews — this is the established pattern.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-04-09T19:53:18.381Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 1488
File: src/activities/home/HomeActivity.cpp:73-77
Timestamp: 2026-04-09T19:53:18.381Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/home/HomeActivity.cpp, HomeActivity::loadRecentCovers and HomeActivity::render), `coverDisabled` must ONLY be cleared (set to false) at two explicit points: (1) after a successful `generateThumbBmp()` call in `loadRecentCovers`, and (2) after an explicit user "Enable cover" / "Generate cover" COVER_ACTION in EpubReaderActivity. Do NOT clear `coverDisabled` based on the mere existence of a cached cover BMP (neither in `loadRecentCovers` nor in `render`). A stale BMP on disk is not proof of user re-enable intent and must not override the per-book `coverDisabled` flag.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-02-27T22:49:59.600Z
Learnt from: ngxson
Repo: crosspoint-reader/crosspoint-reader PR: 1218
File: src/activities/ActivityManager.cpp:254-265
Timestamp: 2026-02-27T22:49:59.600Z
Learning: In this codebase, assertions are always enabled (no NDEBUG). Use assert() to crash on programmer errors and surface logic bugs during development and in production builds. Do not rely on asserts for runtime error handling; they should enforce invariants that must always hold. Keep asserts side-effect free and inexpensive, and avoid relying on them for user-visible failures. Include <cassert> where appropriate and document the invariant being tested.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-03-02T10:14:16.036Z
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 1245
File: lib/Epub/Epub/Section.cpp:277-308
Timestamp: 2026-03-02T10:14:16.036Z
Learning: Guideline: Strengthen serialization::readString to defend against unbounded growth when reading from disk data. Implement and enforce a maximum allowed length (e.g., a configured or reasonable constant) and validate the incoming length before resizing or allocating. Audit all call sites (e.g., BookMetadataCache, TextBlock, KOReaderCredentialStore, Section cache readers) to ensure they do not rely on unbounded len-based resizing. If the readString API must remain, add internal safeguards (bounds checks, length validation, and error handling) so per-call-site validations are not required. Ensure Section cache files remain versioned (SECTION_FILE_VERSION) and parameter mismatches invalidate caches, but do not rely on unsafe allocations; prefer safe, bounded reads with explicit errors on invalid data.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-03-28T11:06:29.611Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 1488
File: src/activities/home/HomeActivity.cpp:92-95
Timestamp: 2026-03-28T11:06:29.611Z
Learning: When reviewing crosspoint-reader code, avoid flagging a missing `renderer.displayBuffer()` call immediately after `GUI.drawPopup()` / `BaseTheme::drawPopup()`: `BaseTheme::drawPopup()` already calls `renderer.displayBuffer()` before returning, so the popup is guaranteed to be flushed to the e-ink panel before subsequent blocking work begins. Conversely, do not require a `renderer.displayBuffer()` call after `fillPopupProgress()`; it intentionally does not flush, so intermediate progress-bar updates may not appear unless the update granularity warrants an explicit flush elsewhere.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-04-12T12:28:33.205Z
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 1629
File: src/activities/home/HomeActivity.cpp:119-120
Timestamp: 2026-04-12T12:28:33.205Z
Learning: When reviewing code in this repository (C/C++ sources), set review comment severity according to this policy:
- Use **Major** (🟠) only for defects with realistic risk of crash, out-of-memory (OOM), invalid pointer dereference, data corruption, or other severe issues that are unlikely to be caught in casual/manual device testing.
- Use **Minor** or informational for UX gaps, logic edge-cases, style issues, or missing feature completeness that the author can verify (or has verified) through normal device use.
- Do **not** escalate severity to Major based on behavioral/UX observations alone; assume the author has already tested the feature on their own device and only treat issues as Major if they match the high-risk defect categories above.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
🔇 Additional comments (1)
src/activities/reader/EpubReaderFootnotesActivity.cpp (1)
55-72: Orientation-aware gutter handling looks solid.The
contentX/contentWidth/contentYflow is clean and correctly applied to title and list layout, so content now respects hint gutters across orientations.Also applies to: 84-86, 91-91
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/activities/reader/EpubReaderFootnotesActivity.cpp (1)
84-95:⚠️ Potential issue | 🟡 MinorList viewport/highlight math should use the same content bounds.
visibleCountis computed from full remaining screen height, but rows start at60 + contentY, which overestimates visible rows. Also, selection highlight still paints full width (including gutters).Suggested fix
- const int screenWidth = renderer.getScreenWidth(); const int marginLeft = contentX + 20; - - const int visibleCount = std::max(1, (renderer.getScreenHeight() - contentY) / lineHeight); + const int listStartY = 60 + contentY; + const int listHeight = renderer.getScreenHeight() - listStartY; + const int visibleCount = std::max(1, listHeight / lineHeight); @@ - const int y = 60 + contentY + (i - scrollOffset) * lineHeight; + const int y = listStartY + (i - scrollOffset) * lineHeight; @@ - renderer.fillRect(0, y, screenWidth, lineHeight, true); + renderer.fillRect(contentX, y, contentWidth - 1, lineHeight, true);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/activities/reader/EpubReaderFootnotesActivity.cpp` around lines 84 - 95, visibleCount is calculated from (renderer.getScreenHeight() - contentY) but rows and highlights are drawn starting at y = 60 + contentY and using marginLeft for content; update the math so both viewport and highlight use the same content bounds: compute visibleCount = std::max(1, (renderer.getScreenHeight() - (60 + contentY)) / lineHeight), keep the y = 60 + contentY + (...) logic, and change the selection paint call (renderer.fillRect) to use marginLeft as x and a width based on the content area (e.g. screenWidth - marginLeft) so the highlight aligns with the content gutter and visible rows. Ensure scrollOffset/selectedIndex logic remains unchanged.
♻️ Duplicate comments (1)
src/activities/reader/EpubReaderFootnotesActivity.cpp (1)
75-75:⚠️ Potential issue | 🟡 MinorEmpty-state text should be centered inside the gutter-safe content region.
At Line 75,
drawCenteredText(...)centers on full screen width, so LandscapeClockwise can drift into the reserved hint gutter.Suggested fix
if (footnotes.empty()) { - renderer.drawCenteredText(UI_10_FONT_ID, 90 + contentY, tr(STR_NO_FOOTNOTES)); + const int emptyX = + contentX + (contentWidth - renderer.getTextWidth(UI_10_FONT_ID, tr(STR_NO_FOOTNOTES))) / 2; + renderer.drawText(UI_10_FONT_ID, emptyX, 90 + contentY, tr(STR_NO_FOOTNOTES)); const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", ""); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); renderer.displayBuffer();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/activities/reader/EpubReaderFootnotesActivity.cpp` at line 75, The empty-state text is currently centered across the full screen by calling renderer.drawCenteredText(UI_10_FONT_ID, 90 + contentY, tr(STR_NO_FOOTNOTES)), which allows LandscapeClockwise to overlap the reserved hint gutter; instead center the text inside the gutter-safe content region (not full screen). Locate renderer.drawCenteredText usage and change it to center relative to the content region (e.g., use the content area width/rect or a renderer method that accepts a bounding rect) so the text X origin is computed from the content region center rather than the full-screen center while keeping the same font (UI_10_FONT_ID) and string (STR_NO_FOOTNOTES).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@src/activities/reader/EpubReaderFootnotesActivity.cpp`:
- Around line 84-95: visibleCount is calculated from (renderer.getScreenHeight()
- contentY) but rows and highlights are drawn starting at y = 60 + contentY and
using marginLeft for content; update the math so both viewport and highlight use
the same content bounds: compute visibleCount = std::max(1,
(renderer.getScreenHeight() - (60 + contentY)) / lineHeight), keep the y = 60 +
contentY + (...) logic, and change the selection paint call (renderer.fillRect)
to use marginLeft as x and a width based on the content area (e.g. screenWidth -
marginLeft) so the highlight aligns with the content gutter and visible rows.
Ensure scrollOffset/selectedIndex logic remains unchanged.
---
Duplicate comments:
In `@src/activities/reader/EpubReaderFootnotesActivity.cpp`:
- Line 75: The empty-state text is currently centered across the full screen by
calling renderer.drawCenteredText(UI_10_FONT_ID, 90 + contentY,
tr(STR_NO_FOOTNOTES)), which allows LandscapeClockwise to overlap the reserved
hint gutter; instead center the text inside the gutter-safe content region (not
full screen). Locate renderer.drawCenteredText usage and change it to center
relative to the content region (e.g., use the content area width/rect or a
renderer method that accepts a bounding rect) so the text X origin is computed
from the content region center rather than the full-screen center while keeping
the same font (UI_10_FONT_ID) and string (STR_NO_FOOTNOTES).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 07c05c98-fcc7-4360-b2a9-f212a6207b04
📒 Files selected for processing (1)
src/activities/reader/EpubReaderFootnotesActivity.cpp
📜 Review details
⏰ 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 context used
🧠 Learnings (21)
📓 Common learnings
Learnt from: aarons
Repo: crosspoint-reader/crosspoint-reader PR: 1659
File: src/activities/reader/ReaderUtils.h:48-51
Timestamp: 2026-04-13T22:45:24.939Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/reader/ReaderUtils.h, detectRotation), the rotation direction logic `isBottom ? (orientation - 1 + COUNT) % COUNT : (orientation + 1) % COUNT` is intentionally correct as written. Despite the enum ordering PORTRAIT→LANDSCAPE_CW→INVERTED→LANDSCAPE_CCW suggesting "+1 = clockwise", on the actual device hardware the bottom button (isBottom=true) stepping backward through the enum (-1) produces a clockwise rotation and the top button (+1) produces counter-clockwise. Do not flag this as reversed in future reviews.
Learnt from: aarons
Repo: crosspoint-reader/crosspoint-reader PR: 1659
File: src/activities/reader/ReaderUtils.h:48-51
Timestamp: 2026-04-13T23:10:44.397Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/reader/ReaderUtils.h, detectRotation), the rotation logic uses "content rotation" semantics, not "device rotation" semantics. The bottom button (isBottom=true) rotates the on-screen content clockwise, which physically corresponds to rotating the device counter-clockwise. The enum step `(orientation - 1 + COUNT) % COUNT` achieves CW content rotation; `(orientation + 1) % COUNT` achieves CCW content rotation. This is intentional. Do not flag as reversed. Named helpers `rotateContentCW` / `rotateContentCCW` were suggested by the author as a potential future clarity improvement.
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: ngxson
Repo: crosspoint-reader/crosspoint-reader PR: 1016
File: src/activities/reader/EpubReaderActivity.cpp:138-146
Timestamp: 2026-02-22T19:13:22.166Z
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.
Learnt from: znelson
Repo: crosspoint-reader/crosspoint-reader PR: 1447
File: src/activities/reader/EpubReaderActivity.cpp:642-660
Timestamp: 2026-03-20T19:51:12.696Z
Learning: In crosspoint-reader/crosspoint-reader (EpubReaderActivity.cpp, prepareSection), when `epub->getTocIndexForSpineIndex(currentSpineIndex)` returns a negative value (pre-TOC spine with no TOC entry), `prepareSection` skips the entire chapter walk and leaves `chapterPageInfo` unset. The status bar correctly falls back to `section->pageCount` in this scenario. This means the `pageTocIndex == -1` path inside the `render()` per-page TOC update block is handled correctly by simply leaving `chapterPageInfo` unchanged — no explicit clear is needed.
Learnt from: Tritlo
Repo: crosspoint-reader/crosspoint-reader PR: 1003
File: src/activities/reader/EpubReaderActivity.cpp:657-674
Timestamp: 2026-02-19T17:46:36.345Z
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().
Learnt from: whyte-j
Repo: crosspoint-reader/crosspoint-reader PR: 733
File: src/components/StatusBar.cpp:63-78
Timestamp: 2026-02-16T22:59:07.687Z
Learning: In the CrossPoint Reader codebase, screen margin values are preset in settings with constrained options, so calculations involving margins (like `rendererableScreenWidth` or `availableTitleSpace` in StatusBar rendering) are unlikely to produce negative values in practice given these bounds.
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.
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.
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 1245
File: lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp:137-150
Timestamp: 2026-03-02T21:18:25.387Z
Learning: In ChapterHtmlSlimParser.cpp, pendingAnchorId is a single string (not a vector) because EPUB footnote targets are virtually always block-level elements (<aside>, <p>, <div>) that trigger startNewTextBlock() and flush the anchor before overwrite can occur. Multiple consecutive inline IDs would resolve to the same completedPageCount anyway, and vector allocation overhead is unnecessary for ESP32-C3. Only inline IDs in practice are footnote back-references (<a id="ref1">), which are never navigation targets.
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 1245
File: lib/Epub/Epub/Section.cpp:238-246
Timestamp: 2026-03-02T09:58:47.539Z
Learning: In EPUB section files, typical sections contain approximately 20 footnote/note anchors plus a few sub-chapter anchors (max ~30-50 total) even in large chapters. While the code tracks all HTML id attributes, realistic anchor counts per section remain well under 255, making uint8_t sufficient for 99% of EPUBs, though uint16_t provides extra safety margin for edge cases.
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 0
File: :0-0
Timestamp: 2026-04-09T18:06:14.309Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/home/HomeActivity.cpp, HomeActivity::loadRecentCovers), the XTC generateThumbBmp() failure path uses master-style handling: it clears coverBmpPath by calling RECENT_BOOKS.updateBook(book.path, book.title, book.author, "") rather than setting coverDisabled=true. Only the EPUB path uses setCoverDisabled(true) on generateThumbBmp failure. Do not flag XTC failure→clear-bmpPath vs EPUB failure→coverDisabled as an inconsistency; they are intentionally different by design.
📚 Learning: 2026-03-20T19:51:12.696Z
Learnt from: znelson
Repo: crosspoint-reader/crosspoint-reader PR: 1447
File: src/activities/reader/EpubReaderActivity.cpp:642-660
Timestamp: 2026-03-20T19:51:12.696Z
Learning: In crosspoint-reader/crosspoint-reader (EpubReaderActivity.cpp, prepareSection), when `epub->getTocIndexForSpineIndex(currentSpineIndex)` returns a negative value (pre-TOC spine with no TOC entry), `prepareSection` skips the entire chapter walk and leaves `chapterPageInfo` unset. The status bar correctly falls back to `section->pageCount` in this scenario. This means the `pageTocIndex == -1` path inside the `render()` per-page TOC update block is handled correctly by simply leaving `chapterPageInfo` unchanged — no explicit clear is needed.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.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:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-02-22T19:13:22.166Z
Learnt from: ngxson
Repo: crosspoint-reader/crosspoint-reader PR: 1016
File: src/activities/reader/EpubReaderActivity.cpp:138-146
Timestamp: 2026-02-22T19:13:22.166Z
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/EpubReaderFootnotesActivity.cpp
📚 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:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-02-19T17:46:36.345Z
Learnt from: Tritlo
Repo: crosspoint-reader/crosspoint-reader PR: 1003
File: src/activities/reader/EpubReaderActivity.cpp:657-674
Timestamp: 2026-02-19T17:46:36.345Z
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.cpp
📚 Learning: 2026-04-13T23:10:44.397Z
Learnt from: aarons
Repo: crosspoint-reader/crosspoint-reader PR: 1659
File: src/activities/reader/ReaderUtils.h:48-51
Timestamp: 2026-04-13T23:10:44.397Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/reader/ReaderUtils.h, detectRotation), the rotation logic uses "content rotation" semantics, not "device rotation" semantics. The bottom button (isBottom=true) rotates the on-screen content clockwise, which physically corresponds to rotating the device counter-clockwise. The enum step `(orientation - 1 + COUNT) % COUNT` achieves CW content rotation; `(orientation + 1) % COUNT` achieves CCW content rotation. This is intentional. Do not flag as reversed. Named helpers `rotateContentCW` / `rotateContentCCW` were suggested by the author as a potential future clarity improvement.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-04-09T18:06:14.309Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 0
File: :0-0
Timestamp: 2026-04-09T18:06:14.309Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/home/HomeActivity.cpp, HomeActivity::loadRecentCovers), the XTC generateThumbBmp() failure path uses master-style handling: it clears coverBmpPath by calling RECENT_BOOKS.updateBook(book.path, book.title, book.author, "") rather than setting coverDisabled=true. Only the EPUB path uses setCoverDisabled(true) on generateThumbBmp failure. Do not flag XTC failure→clear-bmpPath vs EPUB failure→coverDisabled as an inconsistency; they are intentionally different by design.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-03-02T21:18:25.387Z
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 1245
File: lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp:137-150
Timestamp: 2026-03-02T21:18:25.387Z
Learning: In ChapterHtmlSlimParser.cpp, pendingAnchorId is a single string (not a vector) because EPUB footnote targets are virtually always block-level elements (<aside>, <p>, <div>) that trigger startNewTextBlock() and flush the anchor before overwrite can occur. Multiple consecutive inline IDs would resolve to the same completedPageCount anyway, and vector allocation overhead is unnecessary for ESP32-C3. Only inline IDs in practice are footnote back-references (<a id="ref1">), which are never navigation targets.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-04-09T18:26:10.532Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 0
File: :0-0
Timestamp: 2026-04-09T18:26:10.532Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/home/HomeActivity.cpp, HomeActivity::loadRecentCovers), the XTC generateThumbBmp() failure path uses master-style handling: it clears coverBmpPath by calling RECENT_BOOKS.updateBook(book.path, book.title, book.author, "") rather than setting coverDisabled=true. Only the EPUB path uses setCoverDisabled(true) on generateThumbBmp failure. The reason is intentional by design: XTC reader has no reader menu COVER_ACTION (no "Enable/Generate cover" option), so setting coverDisabled=true on XTC failure would permanently block regeneration with no user-accessible recovery path. Clearing the BMP path instead allows HOME to fall back to the classical placeholder, and a future successful generateThumbBmp() call will repopulate it naturally. Do not flag XTC failure→clear-bmpPath vs EPUB failure→coverDisabled as an inconsistency in future reviews.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-04-08T15:24:06.672Z
Learnt from: zgredex
Repo: crosspoint-reader/crosspoint-reader PR: 0
File: :0-0
Timestamp: 2026-04-08T15:24:06.672Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/reader/XtcReaderActivity.cpp, src/activities/util/BmpViewerActivity.cpp, src/activities/reader/EpubReaderActivity.cpp, and other callsites), renderGrayscale's callback type is `void (*renderFn)(const GfxRenderer&, const void*)`. Lambdas passed as this callback correctly use `const GfxRenderer& r` as the first parameter. Where the renderer must be mutated inside the callback (e.g., drawBitmap, drawButtonHints), `const_cast<GfxRenderer&>(r)` is used. No `cppcheck-suppress constParameterReference` is needed. Do not flag `const GfxRenderer& r` in these lambdas or suggest removing the `const_cast` in future reviews — this is the established pattern.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-04-12T02:49:05.293Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 1644
File: src/components/themes/BaseTheme.cpp:835-835
Timestamp: 2026-04-12T02:49:05.293Z
Learning: In crosspoint-reader/crosspoint-reader (src/components/themes/BaseTheme.cpp), the secondary label in `BaseTheme::drawKeyboardKey` is intentionally drawn at `rect.y - 3` (3px above the key rectangle's top edge). This uses part of BaseTheme's `keySpacing = 10` inter-row gap to visually separate the secondary symbol hint from the primary character. In contrast, `LyraTheme::drawKeyboardKey` uses `rect.y` (no offset) because LyraTheme has `keySpacing = 0`. Both placements are correct and deliberate for their respective themes. Do not flag the `-3` offset as a bug or suggest aligning it to `rect.y`.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-03-17T15:27:17.468Z
Learnt from: znelson
Repo: crosspoint-reader/crosspoint-reader PR: 1413
File: lib/EpdFont/EpdFont.cpp:34-36
Timestamp: 2026-03-17T15:27:17.468Z
Learning: In crosspoint-reader/crosspoint-reader, `EpdFont::getGlyph()` falls back to `getGlyph(REPLACEMENT_GLYPH)` (U+FFFD) before returning `nullptr`. All fonts in this project are guaranteed to include a U+FFFD replacement glyph, so the `!glyph` null branch in `EpdFont::getTextBounds()` (and similar rendering paths) is unreachable in practice. Do not flag stale-state issues in that branch (e.g., leftover `lastBaseAdvanceFP`/`lastBaseTop` after a null glyph) as bugs in future reviews.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-04-08T13:05:57.197Z
Learnt from: zgredex
Repo: crosspoint-reader/crosspoint-reader PR: 1614
File: src/activities/util/PxcViewerActivity.cpp:23-29
Timestamp: 2026-04-08T13:05:57.197Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/util/BmpViewerActivity.cpp and src/activities/util/PxcViewerActivity.cpp), error-screen messages such as "Could not open file" and "Invalid BMP File" / "Invalid PXC file" are intentionally hardcoded English strings (not routed through tr()). Only button labels like STR_BACK use tr(). Do not flag these hardcoded viewer error strings as missing localization in future reviews — this is the established pattern for BmpViewerActivity and PxcViewerActivity.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-03-29T19:50:44.877Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 1534
File: src/activities/boot_sleep/SleepActivity.cpp:177-183
Timestamp: 2026-03-29T19:50:44.877Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/boot_sleep/SleepActivity.cpp, SleepActivity::getBookOverlayInfo), when reading totalPages from the TXT index cache (index.bin), the code must validate the cache header: check the magic number (0x54585449 / "TXTI") and version fields before trusting totalPages. This mirrors TxtReaderActivity::loadPageIndexCache(). If validation fails, totalPages stays 0 and the overlay falls back to "Page X" (no total) via STR_OVERLAY_READING_PROGRESS_NO_TOTAL. Do not flag this validation as unnecessary in future reviews.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-04-09T20:05:11.820Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 1488
File: src/activities/reader/EpubReaderMenuActivity.cpp:39-45
Timestamp: 2026-04-09T20:05:11.820Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/reader/EpubReaderMenuActivity.cpp, EpubReaderMenuActivity::buildMenuItems), the `hasCoverForTheme` boolean (derived from `Storage.exists(coverPath)`) is intentionally used — not `!coverDisabled` — to choose between STR_HOME_COVER_ACTION_DISABLE and STR_HOME_COVER_ACTION_ENABLE in non-TIMEOUT global modes. The label reflects the action that will be performed: BMP exists → "Disable" (delete it); no BMP → "Enable" (generate it). Using `coverDisabled` would create confusing edge cases (e.g., coverDisabled=false with no BMP on disk would show "Disable" when there is nothing to disable). Do not flag this as a bug in future reviews.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-04-09T19:53:18.381Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 1488
File: src/activities/home/HomeActivity.cpp:73-77
Timestamp: 2026-04-09T19:53:18.381Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/home/HomeActivity.cpp, HomeActivity::loadRecentCovers and HomeActivity::render), `coverDisabled` must ONLY be cleared (set to false) at two explicit points: (1) after a successful `generateThumbBmp()` call in `loadRecentCovers`, and (2) after an explicit user "Enable cover" / "Generate cover" COVER_ACTION in EpubReaderActivity. Do NOT clear `coverDisabled` based on the mere existence of a cached cover BMP (neither in `loadRecentCovers` nor in `render`). A stale BMP on disk is not proof of user re-enable intent and must not override the per-book `coverDisabled` flag.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-02-27T22:49:59.600Z
Learnt from: ngxson
Repo: crosspoint-reader/crosspoint-reader PR: 1218
File: src/activities/ActivityManager.cpp:254-265
Timestamp: 2026-02-27T22:49:59.600Z
Learning: In this codebase, assertions are always enabled (no NDEBUG). Use assert() to crash on programmer errors and surface logic bugs during development and in production builds. Do not rely on asserts for runtime error handling; they should enforce invariants that must always hold. Keep asserts side-effect free and inexpensive, and avoid relying on them for user-visible failures. Include <cassert> where appropriate and document the invariant being tested.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-03-02T10:14:16.036Z
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 1245
File: lib/Epub/Epub/Section.cpp:277-308
Timestamp: 2026-03-02T10:14:16.036Z
Learning: Guideline: Strengthen serialization::readString to defend against unbounded growth when reading from disk data. Implement and enforce a maximum allowed length (e.g., a configured or reasonable constant) and validate the incoming length before resizing or allocating. Audit all call sites (e.g., BookMetadataCache, TextBlock, KOReaderCredentialStore, Section cache readers) to ensure they do not rely on unbounded len-based resizing. If the readString API must remain, add internal safeguards (bounds checks, length validation, and error handling) so per-call-site validations are not required. Ensure Section cache files remain versioned (SECTION_FILE_VERSION) and parameter mismatches invalidate caches, but do not rely on unsafe allocations; prefer safe, bounded reads with explicit errors on invalid data.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-03-28T11:06:29.611Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 1488
File: src/activities/home/HomeActivity.cpp:92-95
Timestamp: 2026-03-28T11:06:29.611Z
Learning: When reviewing crosspoint-reader code, avoid flagging a missing `renderer.displayBuffer()` call immediately after `GUI.drawPopup()` / `BaseTheme::drawPopup()`: `BaseTheme::drawPopup()` already calls `renderer.displayBuffer()` before returning, so the popup is guaranteed to be flushed to the e-ink panel before subsequent blocking work begins. Conversely, do not require a `renderer.displayBuffer()` call after `fillPopupProgress()`; it intentionally does not flush, so intermediate progress-bar updates may not appear unless the update granularity warrants an explicit flush elsewhere.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
📚 Learning: 2026-04-12T12:28:33.205Z
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 1629
File: src/activities/home/HomeActivity.cpp:119-120
Timestamp: 2026-04-12T12:28:33.205Z
Learning: When reviewing code in this repository (C/C++ sources), set review comment severity according to this policy:
- Use **Major** (🟠) only for defects with realistic risk of crash, out-of-memory (OOM), invalid pointer dereference, data corruption, or other severe issues that are unlikely to be caught in casual/manual device testing.
- Use **Minor** or informational for UX gaps, logic edge-cases, style issues, or missing feature completeness that the author can verify (or has verified) through normal device use.
- Do **not** escalate severity to Major based on behavioral/UX observations alone; assume the author has already tested the feature on their own device and only treat issues as Major if they match the high-risk defect categories above.
Applied to files:
src/activities/reader/EpubReaderFootnotesActivity.cpp
🔇 Additional comments (1)
src/activities/reader/EpubReaderFootnotesActivity.cpp (1)
55-72: Good orientation-aware guttering and header placement.This block cleanly applies landscape/inverted offsets and keeps title centering inside the content region.
|
|
||
| constexpr int startY = 50; | ||
| constexpr int lineHeight = 36; | ||
| const int screenWidth = renderer.getScreenWidth(); |
There was a problem hiding this comment.
Minor: redundant with const auto pageWidth = renderer.getScreenWidth(); above
| const bool isSelected = (i == selectedIndex); | ||
|
|
||
| if (isSelected) { | ||
| renderer.fillRect(0, y, screenWidth, lineHeight, true); |
There was a problem hiding this comment.
Not critical, since this seems to render correctly, but EpubReaderChapterSelectionActivity.cpp reduces the highlight rect area to account for gutters:
| const int marginLeft = contentX + 20; | ||
|
|
||
| const int visibleCount = std::max(1, (renderer.getScreenHeight() - startY) / lineHeight); | ||
| const int visibleCount = std::max(1, (renderer.getScreenHeight() - contentY) / lineHeight); |
There was a problem hiding this comment.
Do you need to adjust the visibleCount calculation to account for the reserved gutter space? Can you test with a section with enough footnotes that this page needs to scroll?
…er#1665) ## Summary * **What is the goal of this PR?** Noticed that the footnotes selection screen does not add proper margins to accommodate screen orientation * **What changes are included?** Copied some code over from `EpubReaderChapterSelectionActivity` to calculate the proper margins in `CW` and `Inverted` orientation | Before | After | |--------|--------| | <img width="578" height="435" alt="image" src="https://github.com/user-attachments/assets/0518a0c6-13d2-48a1-9283-90c83861e4c2" /> | <img width="578" height="435" alt="image" src="https://github.com/user-attachments/assets/ac34365c-72d0-4f07-85a6-17e966b28909" /> | | <img width="328" height="435" alt="image" src="https://github.com/user-attachments/assets/0614f19b-1000-4efe-8ef9-b533d2763a53" /> | <img width="328" height="435" alt="image" src="https://github.com/user-attachments/assets/ce9add2f-88e8-4032-a59c-efb55f366604" /> | --- ### 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? _**NO**_ --------- Co-authored-by: Jan Ivanov <jan.ivanov@sirma.com> (cherry picked from commit 3b12c08)
…er#1665) ## Summary * **What is the goal of this PR?** Noticed that the footnotes selection screen does not add proper margins to accommodate screen orientation * **What changes are included?** Copied some code over from `EpubReaderChapterSelectionActivity` to calculate the proper margins in `CW` and `Inverted` orientation | Before | After | |--------|--------| | <img width="578" height="435" alt="image" src="https://github.com/user-attachments/assets/0518a0c6-13d2-48a1-9283-90c83861e4c2" /> | <img width="578" height="435" alt="image" src="https://github.com/user-attachments/assets/ac34365c-72d0-4f07-85a6-17e966b28909" /> | | <img width="328" height="435" alt="image" src="https://github.com/user-attachments/assets/0614f19b-1000-4efe-8ef9-b533d2763a53" /> | <img width="328" height="435" alt="image" src="https://github.com/user-attachments/assets/ce9add2f-88e8-4032-a59c-efb55f366604" /> | --- ### 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? _**NO**_ --------- Co-authored-by: Jan Ivanov <jan.ivanov@sirma.com>
## Summary This release adds SD card fonts — the most-requested feature since launch — brings the X3 to first-class status, redesigns the on-screen keyboard, overhauls OPDS, and ships SD-card firmware updates. 144 changes from 53 contributors, 32 of whom are new to the project. **🔠 SD Card Fonts** Custom fonts are here. A complete font subsystem lets you install and use fonts beyond the three built-in families. A new `.cpfont` binary format packs multiple styles (regular, bold, italic, bold-italic) into a single file per size, with on-demand glyph loading from the SD card. A two-pass prewarm renderer bulk-reads glyphs per page, achieving near-flash performance for Latin text and viable CJK rendering. Fonts can be downloaded over WiFi directly from the device, uploaded via the web interface, or copied manually to the SD card. The build pipeline ships a 17-family font library (serif, sans, mono, accessibility) with CI distribution via a dedicated crosspoint-fonts repository. As a bonus, CJK characters no longer get spurious hyphens at line breaks, and an advance-table cache eliminates 30+ second stalls during CJK section indexing. **📱 X3 Comes of Age** The X3 graduates from initial bring-up to a proper target. Grayscale antialiasing is sharper, EPUB images render correctly, OTA updates work, and sleep screen dimensions are dialed in. The headline addition: gyroscope-based tilt page turning via the QMI8658 IMU — tilt the device to turn pages hands-free. SD-card firmware update support and X3 bootloader compatibility mean users can update without a USB connection. **⌨️ Redesigned On-Screen Keyboard** The keyboard has been completely redesigned with improved layout, better key feedback, and a fix for the space key barely moving the cursor. Text entry across WiFi setup, OPDS search, and KOSync login is noticeably smoother. **👁️ Focus Reading** A new reading mode bolds the initial characters of each word (similar to Bionic Reading) to create artificial fixation points, helping improve reading speed and focus. The bolding ratio is 45%, with a minimum of 1 character and a maximum of 9, applied dynamically during indexing. **📚 OPDS Overhaul** OPDS gains in-catalog search with next/prev page navigation, support for multiple servers, correct handling of relative paths and query parameters (fixing CopyParty compatibility), and KOReader-compatible download filenames. **🔤 Text Rendering Refinements** Combining marks (diacritics) now use font metrics for positioning instead of heuristics, proportional numeral spacing is supported, and differential rounding eliminates uneven inter-glyph gaps. Hyphenation now recognizes ISO 639-2 language codes, nested block-level CSS styles are tracked correctly, and horizontal CSS insets are capped at 2em to prevent runaway margins. Bookerly has been replaced with Noto Serif for licensing reasons. **🎨 New Theme: RoundedRaff** A new rounded theme joins the theme picker, with fixes for sleep cover crop grid artifacts. **🔋 Battery & Power** Battery percentage smoothing on the X4 eliminates jittery readings. A short press on the power button can be set to trigger a manual screen refresh — handy for clearing ghosting. **📶 WiFi & Networking** WiFi connections now self-heal from transient drops without manual intervention, and a dBm signal strength indicator appears during web server sessions. WiFi networks can be edited directly from the web UI. **🔄 KOSync** Reading position sync is significantly more accurate. The old character-offset approach frequently landed on the wrong paragraph after syncing between devices — the new xpath-based mapping syncs at the paragraph level, matching KOReader's own behavior. A separate fix switches the HTTP layer to `esp_http_client`, and the reader now releases ~65KB of EPUB heap before the TLS handshake — together these eliminate the out-of-memory crashes that plagued KOSync on large books. **🛡️ Stability** Two memory leaks patched, a wild pointer crash in JPEGDEC MCU_SKIP handling fixed, boot loops with large XTC files eliminated, legacy XTC headers supported, the OTA updater now streams GitHub release JSON instead of buffering it in RAM, and a JPEG downscaler y-axis scale factor bug is corrected. **🌐 Languages** Slovenian is new. Russian, Ukrainian, Swedish, Italian, and Spanish translations received significant updates. --- Also in this release: **SD-card firmware updates without USB**, **file extensions in the file browser**, **full path bar navigation**, **end-of-book navigation improvements**, **XTC status bar**, **smarter "Cover + Custom" sleep screens**, **set sleep cover from the BMP viewer**, **orientation-aware popups**, **page turn buttons that follow orientation**, **long-press delete for directories**, **context-aware screenshot filenames with book title**, **crash reason displayed on boot**, **empty line rendering in the TXT reader**, **wallpaper recency buffer to prevent clustering**, **font family deletion from the device**, **next/prev labels in the BMP viewer**, **non-breaking space justification fix**, **README guidance for USB-locked third-party Xteink units**, and a long tail of web UI polish, i18n memory optimizations, and code quality improvements. ## What's Changed ### Features * feat: add SD card font support with on-device download and web management by @adriancaruana, @znelson, @itsthisjustin, @jpirnay, and @mcrosson * feat: Initial support for the x3 by @itsthisjustin in #875 * feat: X3 grayscale antialiasing improvements by @juicecultus in #1607 * feat: X3 gyroscope-based tilt page turning via QMI8658 IMU by @juicecultus in #1636 * feat(update): SD-card firmware update + X3 bootloader compatibility by @eunchurn in #1786 * feat: self-heal from transient WiFi loss, add dBm indicator during WebServerActivity by @jeremydk in #1780 * feat: edit wifi networks in webui by @osteotek in #1743 * feat: add OPDS search support & next/prev page navigation by @rxmmah in #1462 * feat: Support for multiple OPDS servers by @osteotek in #1209 * feat: Adjust Navigation at End of Book by @nscheung in #1425 * feat: Display file extensions in File Browser by @CaptainFrito in #1019 * feat: show full path bar in file browser by @zgredex in #1411 * feat: enable manual screen refresh on power button short press by @bdeshi in #1626 * feat: Rework "Cover + Custom" sleep screens to show covers only when currently reading by @iandchasse in #1256 * feat: Set sleep cover from BMP viewer by @el in #1104 * feat: show crash reason on boot by @ngxson in #1453 * feat: Support for proportional numeral spacing by @znelson in #1414 * feat: add orientation-aware popups for reader activities by @mrtnvgr in #1428 * feat: smooth battery percentage for x4 by @jonvex in #1635 * feat: context-aware screenshot filenames with book title by @jonstieglitz in #1589 * feat(theme): add roundedraff theme and fix sleep cover crop grid artifacts by @bunsoootchi in #918 * feat: Page turn button orientation change by @mchuck in #1069 * feat: Status bar for XTC files by @leecming82 in #1849 * feat: enhance long press action to delete both files and directories by @WuTofu in #1803 * feat: Added Slovenian translation by @thehijacker in #1551 * feat: focus reading by @vjapolitzer in #1670 * feat: add next / prev labels to bmp viewer by @Telemaniaka in #1852 * feat: add font family deletion functionality by @WuTofu in #1919 * feat: separate into "Download All" and "Update All" in font manager by @WuTofu in #1955 * feat: verify CRC32 checksum for font files by @WuTofu in #1904 * feat: increase default weight of Bitter font for improved rendering by @uxjulia in #1922 * feat: allow unnamed intervals by @steka in #1903 ### Fixes * fix: epub images not rendering correctly on x3 by @itsthisjustin in #1572 * fix: OTA update on x3 and progress bar on x4 and x3 by @itsthisjustin in #1805 * fix: boot looping when opening large XTC files by @itsthisjustin in #1648 * fix: Wild pointer crash in JPEGDEC MCU_SKIP handling by @itsthisjustin in #1627 * fix: two small memory leaks by @Uri-Tauber in #1628 * fix: use esp_http_client for KOSync to prevent TLS OOM on ESP32-C3 by @trilwu in #1381 * fix: Read GH release JSON as stream in OTA updater by @znelson in #1810 * fix: support legacy XTC file headers where pageTableOffset=48 by @uxjulia in #1816 * fix: Use font metrics for combining mark positioning by @znelson in #1310 * fix: Use differential rounding for consistent inter-glyph spacing by @znelson in #1413 * fix: Support hyphenation for EPUBs using ISO 639-2 language codes by @znelson in #1461 * fix: Track block style stack for nested styles by @daveallie in #1582 * fix: cap per-side horizontal CSS inset at 2em by @rhoopr in #1694 * fix: increase loadable epub size by @CSCMe in #1638 * fix: Switch to xpath map for paragraph level syncing in KOSync by @itsthisjustin in #1686 * fix: free Epub RAM and simplify KOSync navigation via ActivityManager by @wylanswets in #1860 * fix: improve KOSync bidirectional position matching accuracy by @wylanswets in #1897 * fix: Fix failing very first wifi connection attempt by @jpirnay in #1521 * fix: avoid skipping chapter after screenshot by @Mraulio in #1625 * fix: back navigation from BMPViewer by @Telemaniaka in #1597 * fix: Fix ghosting on exit of BMPViewer by @jpirnay in #1432 * fix: make footnotes consider orientation for gutters by @Telemaniaka in #1665 * fix: footnote link text by @steka in #1666 * fix: Erroneous navigation with long filenames in footnote links by @CSCMe in #1723 * fix: prevent wallpaper clustering with 16-entry recency buffer by @zgredex in #1606 * fix: webserver /delete API backward compatibility by @DianaNites in #1475 * fix: relative opds paths and query param with copyparty by @philips in #1535 * fix: use same file name as KOReader for OPDS downloads by @spfenwick in #1286 * fix: pressing space barely moves input cursor (#1729) by @pablohc in #1733 * fix: keyboard feedback #1644 by @pablohc in #1697 * fix: pluralize folder/file counts correctly in file list summary by @fain182 in #1701 * fix: rendering bug of scrollbar in RoundedRaff theme by @Uri-Tauber in #1814 * fix: two roundedraff bugs by @Uri-Tauber in #1851 * fix: overlap in download font list layout by @pablohc in #1900 * fix: remove duplicate 'Download Fonts' menu entry and improve navigation by @zgredex in #1893 * fix: Add common ligatures to SD font conversion ranges by @znelson * fix: capture instantiateVariableFont return value by @jpirnay in #1911 * fix: Roundraff theme home menu offset with no recent books by @znelson in #1845 * fix: Missing navigation button labels in Roundedraff theme by @Uri-Tauber in #1905 * fix: gracefully resolve fonts missing variants by @Uri-Tauber in #1921 * fix: distribute justifyExtra to non-breaking space tokens by @prawnwhoyawns in #1783 * fix: remove percent rendering from activities by @mcrosson in #1901 * fix: Restore performance in fontconvert_sdcard.py by @znelson in #1924 * fix: Prepare SD card font caches from txt reader by @znelson in #1973 * fix: make script help paths lightweight by @sabraman in #1937 * fix: Replaced Bookerly with Noto Serif for licensing reasons by @znelson in #1736 * fix: incorrect y-axis scale factor in jpeg nearest-neighbor downscaler by @WuTofu in #1807 * fix: display empty lines in txt reader by @Uri-Tauber in #1841 * fix: short-press power action triggered after screenshot combo release by @pablohc in #1853 * fix: correct Russian auto-turn translations by @a-ignatev in #1566 * fix: Update Ukrainian translations for footnotes (issue 1409) by @mirus-ua in #1585 * fix: missing swedish translations by @steka in #1667 * fix: Add swedish keyboard translations by @steka in #1726 * fix: swedish translations by @steka in #1762 * fix: swedish translation by @steka in #1829 * fix: swedish translation by @steka in #1888 * fix: Polish translation by @th0m4sek in #1909 * fix: Ukrainian-translation by @KymAndriy in #1946 * fix: Ukrainian translation by @KymAndriy in #1939 * fix: python requirements files by @steka in #1768 * fix: missing requirement by @steka in #1896 * fix: Use LOG_ macros in loc functions by @znelson in #1794 ### Internal * refactor: redesign on-screen keyboard by @pablohc in #1644 * refactor: replace picojpeg with JPEGDEC for cover art conversion by @jpirnay in #1517 * refactor: Refactor drawArc / fillArc for faster execution by @jpirnay in #1540 * perf: replace i18n pointer tables with offset tables, strip unused strings by @jpirnay in #1408 * refactor: Store only unique localization strings in offset buffers by @znelson in #1802 * refactor: Move language setting into JSON settings by @znelson in #1796 * refactor: Use C++20 'requires' in ActivityResult constructor by @znelson in #1420 * refactor: Use default member initializers for JpegContext and PngContext by @znelson in #1435 * refactor: logPrintf and predefined log level strings by @CSCMe in #1546 * refactor: RAII scoped open/close for ZipFile by @znelson in #1433 * refactor: Deduplicated BMP header writing in Xtc by @znelson in #1439 * refactor: Added shared XML parser teardown helper by @znelson in #1438 * refactor: Removed redundant FsFile close() calls by @znelson in #1434 * refactor: Deduplicate battery drawing code and fix Lyra charging indicator by @znelson in #1437 * refactor: Deduplicate Roundraff battery drawing by @znelson in #1847 * refactor: Simplify sort in GfxRenderer::fillPolygon by @znelson in #1817 * refactor: Avoid vector for page turn rates list by @znelson in #1818 * refactor: Use std::size instead of sizeof/sizeof by @znelson in #1819 * refactor: Use fixed-size integers for BookMetadataCache data by @znelson in #1844 * refactor: Simplify isReaderActivity bookkeeping by @znelson in #1838 * refactor: Simplify XtcReaderActivity with detectPageTurn by @znelson in #1837 * refactor: change ukrainian translation to adaptation and add missing lines by @KymAndriy in #1828 * chore: drop JPEGDEC patch in favour of upstream fix by @martinbrook in #1465 * chore: clang-format.fix.ps1 script: Add .venv to list of path exclusions by @jpirnay in #1515 * chore: Updating sleep screen dimensions for X3 by @jensechu in #1688 * chore: Clarify X3 RTC in SCOPE.md by @znelson in #1687 * chore: Improved Italian translations by @znelson in #1685 * chore: change ukrainian translation to adaptation by @KymAndriy in #1684 * chore: Update spanish.yaml by @mvidelatraduc in #1717 * chore: One Italian translation tweak by @znelson in #1718 * chore: git pre-commit hook for format fix by @osteotek in #1730 * chore: Update SDK to fork in CrossPoint org by @znelson in #1836 * chore: Added RAM to firmware_size_history.py script by @znelson in #1830 * chore: Updated docs to reflect DESTRUCTOR_CLOSES_FILE=1 by @znelson in #1878 * feat: cap compressed group size at 64 KB by @jpirnay in #1913 * fix: build-script bug fixes for fontconvert{,_sdcard}.py by @jpirnay in #1910 * feat: include short SHA in CROSSPOINT_VERSION by @osteotek in #1728 * feat: show long branch names by @steka in #1727 * feat: enable pio build cache by @Uri-Tauber in #1769 * style: put page name first in browser titles by @fain182 in #1703 * style: unify page headers across web UI by @fain182 in #1702 * style: move file type badges into Type column by @fain182 in #1793 * style: align action buttons vertically with page title by @fain182 in #1795 * docs: Update README with firmware flashing instructions by @ryneches in #1654 * docs: fix typos by @kianmeng in #1705 * docs: update README.md to reflect the current state of crosspoint by @Uri-Tauber in #1812 * docs: Add documentation for USB-locked Xteink devices by @itsthisjustin in #1990 * docs: expand first use of OPDS acronym and provide a wikipedia link by @sizezero in #1824 * docs: fix KOReader sync guide link by @sabraman in #1930 * docs: fix hyphenation updater script name by @sabraman in #1931 * fix: sd font download urls in docs by @mcrosson in #1945 * fix: sd font folder paths in documentation by @mcrosson in #1944 * chore: Add verbose mode to build-sd-fonts.py by @znelson in #1923 ## New Contributors * @a-ignatev made their first contribution in #1566 * @CSCMe made their first contribution in #1546 * @thehijacker made their first contribution in #1551 * @Telemaniaka made their first contribution in #1597 * @Mraulio made their first contribution in #1625 * @rxmmah made their first contribution in #1462 * @bdeshi made their first contribution in #1626 * @DianaNites made their first contribution in #1475 * @ryneches made their first contribution in #1654 * @zgredex made their first contribution in #1411 * @jonvex made their first contribution in #1635 * @KymAndriy made their first contribution in #1684 * @jensechu made their first contribution in #1688 * @kianmeng made their first contribution in #1705 * @philips made their first contribution in #1535 * @fain182 made their first contribution in #1701 * @mvidelatraduc made their first contribution in #1717 * @bunsoootchi made their first contribution in #918 * @rhoopr made their first contribution in #1694 * @spfenwick made their first contribution in #1286 * @trilwu made their first contribution in #1381 * @jonstieglitz made their first contribution in #1589 * @uxjulia made their first contribution in #1816 * @mchuck made their first contribution in #1069 * @sizezero made their first contribution in #1824 * @leecming82 made their first contribution in #1849 * @jeremydk made their first contribution in #1780 * @WuTofu made their first contribution in #1803 * @wylanswets made their first contribution in #1860 * @sabraman made their first contribution in #1930 * @prawnwhoyawns made their first contribution in #1783 * @mcrosson made their first contribution as co-author on SD card font support **Full Changelog**: 1.2.0...release/1.3.0 --------- Co-authored-by: Justin Mitchell <justin@jmitch.com> Co-authored-by: Chun Ming Lee <95391408+leecming82@users.noreply.github.com> Co-authored-by: Uri Tauber <uritaube@gmail.com>
* fix: Prepare SD card font caches from txt reader (crosspoint-reader#1973) ## Summary SD card font fixes: - `TxtReaderActivity` needs to call `renderer.ensureSdCardFontReady` to build the advance lookup table to support rendering with SD card fonts. This revealed that `TxtReaderActivity` was inconsistently performing layout with `getTextWidth`, when the renderer actually uses `getTextAdvanceX`, which can lead to minor inconsistencies in alignment. - Avoid allocating one big `allText` string in `ParsedText::layoutAndExtractLines`. Instead, pass the vector of word strings directly to `SdCardFont::buildAdvanceTable`, where the algorithm just needs to iterate codepoints anyway. --- ### 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? _**PARTIALLY**_ --------- Co-authored-by: Justin Mitchell <justin@jmitch.com> * fix: Add documentation for USB-locked Xteink devices (crosspoint-reader#1990) Document the Xteink Unlocker tool requirement for third-party purchased xteink units that ship with USB flashing locked. Include warnings about bricking risks when flashing unsupported firmwares (e.g. Papyrix) on locked devices, as they may permanently lock the device with no recovery path. * fix: update README.md to reflect the current state of crosspoint (crosspoint-reader#1812) ## Summary As noted in [crosspoint-reader#1680](crosspoint-reader#1680 (comment)), the README hasn't been updated in a while and has fallen behind the actual firmware. This PR brings it up to date. Beyond the feature list, I added a section acknowledging community forks worth knowing about. I also took some deliberate editorial choices around how CrossPoint is framed — I think it has the potential to be more than just "an alternative Xteink firmware", and the wording reflects that. A note on process: I wrote the bulk of the text myself, but used AI tools to scan the codebase and catch features I might have missed, and to clean up my English (I'm fluent but not a native speaker). If any line reads as unnatural or AI-sounding, please flag it — I'd rather fix it than leave it. --- One thing outside the scope of this PR: I think the cover photo could use a refresh, ideally replaced with a small gallery showing different CrossPoint screens. If you have a professional camera and an Xteink device and want to help with that, let me know. --------- Co-authored-by: Zach Nelson <zach@zdnelson.com> * chore: Update version to 1.3.0 (crosspoint-reader#1827) ## Summary This release adds SD card fonts — the most-requested feature since launch — brings the X3 to first-class status, redesigns the on-screen keyboard, overhauls OPDS, and ships SD-card firmware updates. 144 changes from 53 contributors, 32 of whom are new to the project. **🔠 SD Card Fonts** Custom fonts are here. A complete font subsystem lets you install and use fonts beyond the three built-in families. A new `.cpfont` binary format packs multiple styles (regular, bold, italic, bold-italic) into a single file per size, with on-demand glyph loading from the SD card. A two-pass prewarm renderer bulk-reads glyphs per page, achieving near-flash performance for Latin text and viable CJK rendering. Fonts can be downloaded over WiFi directly from the device, uploaded via the web interface, or copied manually to the SD card. The build pipeline ships a 17-family font library (serif, sans, mono, accessibility) with CI distribution via a dedicated crosspoint-fonts repository. As a bonus, CJK characters no longer get spurious hyphens at line breaks, and an advance-table cache eliminates 30+ second stalls during CJK section indexing. **📱 X3 Comes of Age** The X3 graduates from initial bring-up to a proper target. Grayscale antialiasing is sharper, EPUB images render correctly, OTA updates work, and sleep screen dimensions are dialed in. The headline addition: gyroscope-based tilt page turning via the QMI8658 IMU — tilt the device to turn pages hands-free. SD-card firmware update support and X3 bootloader compatibility mean users can update without a USB connection. **⌨️ Redesigned On-Screen Keyboard** The keyboard has been completely redesigned with improved layout, better key feedback, and a fix for the space key barely moving the cursor. Text entry across WiFi setup, OPDS search, and KOSync login is noticeably smoother. **👁️ Focus Reading** A new reading mode bolds the initial characters of each word (similar to Bionic Reading) to create artificial fixation points, helping improve reading speed and focus. The bolding ratio is 45%, with a minimum of 1 character and a maximum of 9, applied dynamically during indexing. **📚 OPDS Overhaul** OPDS gains in-catalog search with next/prev page navigation, support for multiple servers, correct handling of relative paths and query parameters (fixing CopyParty compatibility), and KOReader-compatible download filenames. **🔤 Text Rendering Refinements** Combining marks (diacritics) now use font metrics for positioning instead of heuristics, proportional numeral spacing is supported, and differential rounding eliminates uneven inter-glyph gaps. Hyphenation now recognizes ISO 639-2 language codes, nested block-level CSS styles are tracked correctly, and horizontal CSS insets are capped at 2em to prevent runaway margins. Bookerly has been replaced with Noto Serif for licensing reasons. **🎨 New Theme: RoundedRaff** A new rounded theme joins the theme picker, with fixes for sleep cover crop grid artifacts. **🔋 Battery & Power** Battery percentage smoothing on the X4 eliminates jittery readings. A short press on the power button can be set to trigger a manual screen refresh — handy for clearing ghosting. **📶 WiFi & Networking** WiFi connections now self-heal from transient drops without manual intervention, and a dBm signal strength indicator appears during web server sessions. WiFi networks can be edited directly from the web UI. **🔄 KOSync** Reading position sync is significantly more accurate. The old character-offset approach frequently landed on the wrong paragraph after syncing between devices — the new xpath-based mapping syncs at the paragraph level, matching KOReader's own behavior. A separate fix switches the HTTP layer to `esp_http_client`, and the reader now releases ~65KB of EPUB heap before the TLS handshake — together these eliminate the out-of-memory crashes that plagued KOSync on large books. **🛡️ Stability** Two memory leaks patched, a wild pointer crash in JPEGDEC MCU_SKIP handling fixed, boot loops with large XTC files eliminated, legacy XTC headers supported, the OTA updater now streams GitHub release JSON instead of buffering it in RAM, and a JPEG downscaler y-axis scale factor bug is corrected. **🌐 Languages** Slovenian is new. Russian, Ukrainian, Swedish, Italian, and Spanish translations received significant updates. --- Also in this release: **SD-card firmware updates without USB**, **file extensions in the file browser**, **full path bar navigation**, **end-of-book navigation improvements**, **XTC status bar**, **smarter "Cover + Custom" sleep screens**, **set sleep cover from the BMP viewer**, **orientation-aware popups**, **page turn buttons that follow orientation**, **long-press delete for directories**, **context-aware screenshot filenames with book title**, **crash reason displayed on boot**, **empty line rendering in the TXT reader**, **wallpaper recency buffer to prevent clustering**, **font family deletion from the device**, **next/prev labels in the BMP viewer**, **non-breaking space justification fix**, **README guidance for USB-locked third-party Xteink units**, and a long tail of web UI polish, i18n memory optimizations, and code quality improvements. ## What's Changed ### Features * feat: add SD card font support with on-device download and web management by @adriancaruana, @znelson, @itsthisjustin, @jpirnay, and @mcrosson * feat: Initial support for the x3 by @itsthisjustin in crosspoint-reader#875 * feat: X3 grayscale antialiasing improvements by @juicecultus in crosspoint-reader#1607 * feat: X3 gyroscope-based tilt page turning via QMI8658 IMU by @juicecultus in crosspoint-reader#1636 * feat(update): SD-card firmware update + X3 bootloader compatibility by @eunchurn in crosspoint-reader#1786 * feat: self-heal from transient WiFi loss, add dBm indicator during WebServerActivity by @jeremydk in crosspoint-reader#1780 * feat: edit wifi networks in webui by @osteotek in crosspoint-reader#1743 * feat: add OPDS search support & next/prev page navigation by @rxmmah in crosspoint-reader#1462 * feat: Support for multiple OPDS servers by @osteotek in crosspoint-reader#1209 * feat: Adjust Navigation at End of Book by @nscheung in crosspoint-reader#1425 * feat: Display file extensions in File Browser by @CaptainFrito in crosspoint-reader#1019 * feat: show full path bar in file browser by @zgredex in crosspoint-reader#1411 * feat: enable manual screen refresh on power button short press by @bdeshi in crosspoint-reader#1626 * feat: Rework "Cover + Custom" sleep screens to show covers only when currently reading by @iandchasse in crosspoint-reader#1256 * feat: Set sleep cover from BMP viewer by @el in crosspoint-reader#1104 * feat: show crash reason on boot by @ngxson in crosspoint-reader#1453 * feat: Support for proportional numeral spacing by @znelson in crosspoint-reader#1414 * feat: add orientation-aware popups for reader activities by @mrtnvgr in crosspoint-reader#1428 * feat: smooth battery percentage for x4 by @jonvex in crosspoint-reader#1635 * feat: context-aware screenshot filenames with book title by @jonstieglitz in crosspoint-reader#1589 * feat(theme): add roundedraff theme and fix sleep cover crop grid artifacts by @bunsoootchi in crosspoint-reader#918 * feat: Page turn button orientation change by @mchuck in crosspoint-reader#1069 * feat: Status bar for XTC files by @leecming82 in crosspoint-reader#1849 * feat: enhance long press action to delete both files and directories by @WuTofu in crosspoint-reader#1803 * feat: Added Slovenian translation by @thehijacker in crosspoint-reader#1551 * feat: focus reading by @vjapolitzer in crosspoint-reader#1670 * feat: add next / prev labels to bmp viewer by @Telemaniaka in crosspoint-reader#1852 * feat: add font family deletion functionality by @WuTofu in crosspoint-reader#1919 * feat: separate into "Download All" and "Update All" in font manager by @WuTofu in crosspoint-reader#1955 * feat: verify CRC32 checksum for font files by @WuTofu in crosspoint-reader#1904 * feat: increase default weight of Bitter font for improved rendering by @uxjulia in crosspoint-reader#1922 * feat: allow unnamed intervals by @steka in crosspoint-reader#1903 ### Fixes * fix: epub images not rendering correctly on x3 by @itsthisjustin in crosspoint-reader#1572 * fix: OTA update on x3 and progress bar on x4 and x3 by @itsthisjustin in crosspoint-reader#1805 * fix: boot looping when opening large XTC files by @itsthisjustin in crosspoint-reader#1648 * fix: Wild pointer crash in JPEGDEC MCU_SKIP handling by @itsthisjustin in crosspoint-reader#1627 * fix: two small memory leaks by @Uri-Tauber in crosspoint-reader#1628 * fix: use esp_http_client for KOSync to prevent TLS OOM on ESP32-C3 by @trilwu in crosspoint-reader#1381 * fix: Read GH release JSON as stream in OTA updater by @znelson in crosspoint-reader#1810 * fix: support legacy XTC file headers where pageTableOffset=48 by @uxjulia in crosspoint-reader#1816 * fix: Use font metrics for combining mark positioning by @znelson in crosspoint-reader#1310 * fix: Use differential rounding for consistent inter-glyph spacing by @znelson in crosspoint-reader#1413 * fix: Support hyphenation for EPUBs using ISO 639-2 language codes by @znelson in crosspoint-reader#1461 * fix: Track block style stack for nested styles by @daveallie in crosspoint-reader#1582 * fix: cap per-side horizontal CSS inset at 2em by @rhoopr in crosspoint-reader#1694 * fix: increase loadable epub size by @CSCMe in crosspoint-reader#1638 * fix: Switch to xpath map for paragraph level syncing in KOSync by @itsthisjustin in crosspoint-reader#1686 * fix: free Epub RAM and simplify KOSync navigation via ActivityManager by @wylanswets in crosspoint-reader#1860 * fix: improve KOSync bidirectional position matching accuracy by @wylanswets in crosspoint-reader#1897 * fix: Fix failing very first wifi connection attempt by @jpirnay in crosspoint-reader#1521 * fix: avoid skipping chapter after screenshot by @Mraulio in crosspoint-reader#1625 * fix: back navigation from BMPViewer by @Telemaniaka in crosspoint-reader#1597 * fix: Fix ghosting on exit of BMPViewer by @jpirnay in crosspoint-reader#1432 * fix: make footnotes consider orientation for gutters by @Telemaniaka in crosspoint-reader#1665 * fix: footnote link text by @steka in crosspoint-reader#1666 * fix: Erroneous navigation with long filenames in footnote links by @CSCMe in crosspoint-reader#1723 * fix: prevent wallpaper clustering with 16-entry recency buffer by @zgredex in crosspoint-reader#1606 * fix: webserver /delete API backward compatibility by @DianaNites in crosspoint-reader#1475 * fix: relative opds paths and query param with copyparty by @philips in crosspoint-reader#1535 * fix: use same file name as KOReader for OPDS downloads by @spfenwick in crosspoint-reader#1286 * fix: pressing space barely moves input cursor (crosspoint-reader#1729) by @pablohc in crosspoint-reader#1733 * fix: keyboard feedback crosspoint-reader#1644 by @pablohc in crosspoint-reader#1697 * fix: pluralize folder/file counts correctly in file list summary by @fain182 in crosspoint-reader#1701 * fix: rendering bug of scrollbar in RoundedRaff theme by @Uri-Tauber in crosspoint-reader#1814 * fix: two roundedraff bugs by @Uri-Tauber in crosspoint-reader#1851 * fix: overlap in download font list layout by @pablohc in crosspoint-reader#1900 * fix: remove duplicate 'Download Fonts' menu entry and improve navigation by @zgredex in crosspoint-reader#1893 * fix: Add common ligatures to SD font conversion ranges by @znelson * fix: capture instantiateVariableFont return value by @jpirnay in crosspoint-reader#1911 * fix: Roundraff theme home menu offset with no recent books by @znelson in crosspoint-reader#1845 * fix: Missing navigation button labels in Roundedraff theme by @Uri-Tauber in crosspoint-reader#1905 * fix: gracefully resolve fonts missing variants by @Uri-Tauber in crosspoint-reader#1921 * fix: distribute justifyExtra to non-breaking space tokens by @prawnwhoyawns in crosspoint-reader#1783 * fix: remove percent rendering from activities by @mcrosson in crosspoint-reader#1901 * fix: Restore performance in fontconvert_sdcard.py by @znelson in crosspoint-reader#1924 * fix: Prepare SD card font caches from txt reader by @znelson in crosspoint-reader#1973 * fix: make script help paths lightweight by @sabraman in crosspoint-reader#1937 * fix: Replaced Bookerly with Noto Serif for licensing reasons by @znelson in crosspoint-reader#1736 * fix: incorrect y-axis scale factor in jpeg nearest-neighbor downscaler by @WuTofu in crosspoint-reader#1807 * fix: display empty lines in txt reader by @Uri-Tauber in crosspoint-reader#1841 * fix: short-press power action triggered after screenshot combo release by @pablohc in crosspoint-reader#1853 * fix: correct Russian auto-turn translations by @a-ignatev in crosspoint-reader#1566 * fix: Update Ukrainian translations for footnotes (issue 1409) by @mirus-ua in crosspoint-reader#1585 * fix: missing swedish translations by @steka in crosspoint-reader#1667 * fix: Add swedish keyboard translations by @steka in crosspoint-reader#1726 * fix: swedish translations by @steka in crosspoint-reader#1762 * fix: swedish translation by @steka in crosspoint-reader#1829 * fix: swedish translation by @steka in crosspoint-reader#1888 * fix: Polish translation by @th0m4sek in crosspoint-reader#1909 * fix: Ukrainian-translation by @KymAndriy in crosspoint-reader#1946 * fix: Ukrainian translation by @KymAndriy in crosspoint-reader#1939 * fix: python requirements files by @steka in crosspoint-reader#1768 * fix: missing requirement by @steka in crosspoint-reader#1896 * fix: Use LOG_ macros in loc functions by @znelson in crosspoint-reader#1794 ### Internal * refactor: redesign on-screen keyboard by @pablohc in crosspoint-reader#1644 * refactor: replace picojpeg with JPEGDEC for cover art conversion by @jpirnay in crosspoint-reader#1517 * refactor: Refactor drawArc / fillArc for faster execution by @jpirnay in crosspoint-reader#1540 * perf: replace i18n pointer tables with offset tables, strip unused strings by @jpirnay in crosspoint-reader#1408 * refactor: Store only unique localization strings in offset buffers by @znelson in crosspoint-reader#1802 * refactor: Move language setting into JSON settings by @znelson in crosspoint-reader#1796 * refactor: Use C++20 'requires' in ActivityResult constructor by @znelson in crosspoint-reader#1420 * refactor: Use default member initializers for JpegContext and PngContext by @znelson in crosspoint-reader#1435 * refactor: logPrintf and predefined log level strings by @CSCMe in crosspoint-reader#1546 * refactor: RAII scoped open/close for ZipFile by @znelson in crosspoint-reader#1433 * refactor: Deduplicated BMP header writing in Xtc by @znelson in crosspoint-reader#1439 * refactor: Added shared XML parser teardown helper by @znelson in crosspoint-reader#1438 * refactor: Removed redundant FsFile close() calls by @znelson in crosspoint-reader#1434 * refactor: Deduplicate battery drawing code and fix Lyra charging indicator by @znelson in crosspoint-reader#1437 * refactor: Deduplicate Roundraff battery drawing by @znelson in crosspoint-reader#1847 * refactor: Simplify sort in GfxRenderer::fillPolygon by @znelson in crosspoint-reader#1817 * refactor: Avoid vector for page turn rates list by @znelson in crosspoint-reader#1818 * refactor: Use std::size instead of sizeof/sizeof by @znelson in crosspoint-reader#1819 * refactor: Use fixed-size integers for BookMetadataCache data by @znelson in crosspoint-reader#1844 * refactor: Simplify isReaderActivity bookkeeping by @znelson in crosspoint-reader#1838 * refactor: Simplify XtcReaderActivity with detectPageTurn by @znelson in crosspoint-reader#1837 * refactor: change ukrainian translation to adaptation and add missing lines by @KymAndriy in crosspoint-reader#1828 * chore: drop JPEGDEC patch in favour of upstream fix by @martinbrook in crosspoint-reader#1465 * chore: clang-format.fix.ps1 script: Add .venv to list of path exclusions by @jpirnay in crosspoint-reader#1515 * chore: Updating sleep screen dimensions for X3 by @jensechu in crosspoint-reader#1688 * chore: Clarify X3 RTC in SCOPE.md by @znelson in crosspoint-reader#1687 * chore: Improved Italian translations by @znelson in crosspoint-reader#1685 * chore: change ukrainian translation to adaptation by @KymAndriy in crosspoint-reader#1684 * chore: Update spanish.yaml by @mvidelatraduc in crosspoint-reader#1717 * chore: One Italian translation tweak by @znelson in crosspoint-reader#1718 * chore: git pre-commit hook for format fix by @osteotek in crosspoint-reader#1730 * chore: Update SDK to fork in CrossPoint org by @znelson in crosspoint-reader#1836 * chore: Added RAM to firmware_size_history.py script by @znelson in crosspoint-reader#1830 * chore: Updated docs to reflect DESTRUCTOR_CLOSES_FILE=1 by @znelson in crosspoint-reader#1878 * feat: cap compressed group size at 64 KB by @jpirnay in crosspoint-reader#1913 * fix: build-script bug fixes for fontconvert{,_sdcard}.py by @jpirnay in crosspoint-reader#1910 * feat: include short SHA in CROSSPOINT_VERSION by @osteotek in crosspoint-reader#1728 * feat: show long branch names by @steka in crosspoint-reader#1727 * feat: enable pio build cache by @Uri-Tauber in crosspoint-reader#1769 * style: put page name first in browser titles by @fain182 in crosspoint-reader#1703 * style: unify page headers across web UI by @fain182 in crosspoint-reader#1702 * style: move file type badges into Type column by @fain182 in crosspoint-reader#1793 * style: align action buttons vertically with page title by @fain182 in crosspoint-reader#1795 * docs: Update README with firmware flashing instructions by @ryneches in crosspoint-reader#1654 * docs: fix typos by @kianmeng in crosspoint-reader#1705 * docs: update README.md to reflect the current state of crosspoint by @Uri-Tauber in crosspoint-reader#1812 * docs: Add documentation for USB-locked Xteink devices by @itsthisjustin in crosspoint-reader#1990 * docs: expand first use of OPDS acronym and provide a wikipedia link by @sizezero in crosspoint-reader#1824 * docs: fix KOReader sync guide link by @sabraman in crosspoint-reader#1930 * docs: fix hyphenation updater script name by @sabraman in crosspoint-reader#1931 * fix: sd font download urls in docs by @mcrosson in crosspoint-reader#1945 * fix: sd font folder paths in documentation by @mcrosson in crosspoint-reader#1944 * chore: Add verbose mode to build-sd-fonts.py by @znelson in crosspoint-reader#1923 ## New Contributors * @a-ignatev made their first contribution in crosspoint-reader#1566 * @CSCMe made their first contribution in crosspoint-reader#1546 * @thehijacker made their first contribution in crosspoint-reader#1551 * @Telemaniaka made their first contribution in crosspoint-reader#1597 * @Mraulio made their first contribution in crosspoint-reader#1625 * @rxmmah made their first contribution in crosspoint-reader#1462 * @bdeshi made their first contribution in crosspoint-reader#1626 * @DianaNites made their first contribution in crosspoint-reader#1475 * @ryneches made their first contribution in crosspoint-reader#1654 * @zgredex made their first contribution in crosspoint-reader#1411 * @jonvex made their first contribution in crosspoint-reader#1635 * @KymAndriy made their first contribution in crosspoint-reader#1684 * @jensechu made their first contribution in crosspoint-reader#1688 * @kianmeng made their first contribution in crosspoint-reader#1705 * @philips made their first contribution in crosspoint-reader#1535 * @fain182 made their first contribution in crosspoint-reader#1701 * @mvidelatraduc made their first contribution in crosspoint-reader#1717 * @bunsoootchi made their first contribution in crosspoint-reader#918 * @rhoopr made their first contribution in crosspoint-reader#1694 * @spfenwick made their first contribution in crosspoint-reader#1286 * @trilwu made their first contribution in crosspoint-reader#1381 * @jonstieglitz made their first contribution in crosspoint-reader#1589 * @uxjulia made their first contribution in crosspoint-reader#1816 * @mchuck made their first contribution in crosspoint-reader#1069 * @sizezero made their first contribution in crosspoint-reader#1824 * @leecming82 made their first contribution in crosspoint-reader#1849 * @jeremydk made their first contribution in crosspoint-reader#1780 * @WuTofu made their first contribution in crosspoint-reader#1803 * @wylanswets made their first contribution in crosspoint-reader#1860 * @sabraman made their first contribution in crosspoint-reader#1930 * @prawnwhoyawns made their first contribution in crosspoint-reader#1783 * @mcrosson made their first contribution as co-author on SD card font support **Full Changelog**: crosspoint-reader/crosspoint-reader@1.2.0...release/1.3.0 --------- Co-authored-by: Justin Mitchell <justin@jmitch.com> Co-authored-by: Chun Ming Lee <95391408+leecming82@users.noreply.github.com> Co-authored-by: Uri Tauber <uritaube@gmail.com> * Add package settings UI --------- Co-authored-by: Zach Nelson <zach@zdnelson.com> Co-authored-by: Justin Mitchell <justin@jmitch.com> Co-authored-by: Uri Tauber <uritaube@gmail.com> Co-authored-by: Chun Ming Lee <95391408+leecming82@users.noreply.github.com>
Summary
What is the goal of this PR?
Noticed that the footnotes selection screen does not add proper margins to accommodate screen orientation
What changes are included?
Copied some code over from
EpubReaderChapterSelectionActivityto calculate the proper margins inCWandInvertedorientationAI 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? NO