feat: focus reading#1670
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a focusReadingEnabled flag through settings, section serialization, parser construction, and reader call sites; implements UTF‑8 codepoint-aware tokenization in ParsedText to emit bold-prefix + remainder tokens for focus reading; and adds localized STR_FOCUS_READING entries. Changes
Sequence DiagramsequenceDiagram
participant Settings as App Settings
participant Reader as EpubReaderActivity
participant Section as Section
participant Parser as ChapterHtmlSlimParser
participant Parsed as ParsedText
Settings->>Reader: provide focusReadingEnabled
Reader->>Section: create/loadSectionFile(..., focusReadingEnabled)
Section->>Section: write/load header (includes focusReadingEnabled)
Section->>Parser: construct ChapterHtmlSlimParser(..., focusReadingEnabled)
Parser->>Parsed: startNewTextBlock(..., focusReadingEnabled)
Parsed->>Parsed: addWord(text) — classify via isWordCharacter(), UTF-8 codepoint segmentation
Parsed->>Parser: emit tokens (first segment may be bold-prefix + remainder)
Parser-->>Section: assembled styled tokens
Section-->>Reader: serialized section / pages
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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 |
55f4532 to
2bf8732
Compare
2bf8732 to
f9be757
Compare
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
lib/Epub/Epub/ParsedText.cpp (1)
113-201: LGTM, with a small operational note on memory.Segmentation, UTF-8 slicing, and attach-flag propagation all look correct:
- First segment inherits
attachToPrevious, every subsequent segment is forced toattach=trueso the sub-tokens glue back into a single visual word.targetBoldCharsis clamped to [1, 9] and the>= charCountbranch correctly emits a single fully-bold entry instead of an empty remainder.std::string_view-based slicing avoids intermediatestd::stringallocations on the hot path; the only allocations are the finalemplace_backcopies intowords.Operational note (not blocking): when
focusReadingEnabledis true, each split word produces two entries inwords/wordStyles/wordContinues(plus additional entries per punctuation transition). For a chapter that normally sits near the mid-paragraph flush at 750 words, the effective vector size can roughly double, which is worth keeping an eye on given the ESP32-C3 RAM budget. If you start seeing OOM on large chapters with focus reading on, reserving capacity up-front inaddWordor pre-estimating the split count could reduce reallocations.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/Epub/Epub/ParsedText.cpp` around lines 113 - 201, When focusReadingEnabled is true the split logic can double (or more) the number of entries and cause many reallocations; in the addWord method (where focusReadingEnabled, words, wordStyles, wordContinues and the processSegment lambda live) pre-reserve capacity for the vectors before running the splitter: compute a cheap upper-bound extra entries (e.g. +2 for a simple word split or estimate by counting UTF‑8 codepoints in the input to add that many entries) and call words.reserve(words.size()+extra), wordStyles.reserve(...), wordContinues.reserve(...). This avoids repeated reallocations while leaving the splitting logic otherwise unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/Epub/Epub/ParsedText.cpp`:
- Around line 77-100: isWordCharacter currently returns true for all cp values
outside a few punctuation ranges, causing symbols like currency, arrows, math
operators, dingbats and emoji (e.g., U+20AC) to be treated as letters; update
isWordCharacter to explicitly exclude symbol blocks by returning false for
ranges such as Currency Symbols (0x20A0–0x20CF), Arrows (0x2190–0x21FF),
Mathematical Operators (0x2200–0x22FF), Misc Symbols (0x2600–0x26FF), Dingbats
(0x2700–0x27BF) and the common emoji blocks (e.g.,
0x1F300–0x1F5FF/0x1F600–0x1F64F), while preserving the existing ASCII, General
Punctuation (0x2000–0x206F) and Latin-1 checks so that letter blocks
(CJK/Greek/Cyrillic/etc.) still default to word characters.
---
Nitpick comments:
In `@lib/Epub/Epub/ParsedText.cpp`:
- Around line 113-201: When focusReadingEnabled is true the split logic can
double (or more) the number of entries and cause many reallocations; in the
addWord method (where focusReadingEnabled, words, wordStyles, wordContinues and
the processSegment lambda live) pre-reserve capacity for the vectors before
running the splitter: compute a cheap upper-bound extra entries (e.g. +2 for a
simple word split or estimate by counting UTF‑8 codepoints in the input to add
that many entries) and call words.reserve(words.size()+extra),
wordStyles.reserve(...), wordContinues.reserve(...). This avoids repeated
reallocations while leaving the splitting logic otherwise unchanged.
🪄 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: 286b00e2-4d5e-4011-986a-c1d64006d15f
📒 Files selected for processing (31)
lib/Epub/Epub/ParsedText.cpplib/Epub/Epub/ParsedText.hlib/Epub/Epub/Section.cpplib/Epub/Epub/Section.hlib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpplib/Epub/Epub/parsers/ChapterHtmlSlimParser.hlib/I18n/translations/belarusian.yamllib/I18n/translations/catalan.yamllib/I18n/translations/czech.yamllib/I18n/translations/danish.yamllib/I18n/translations/dutch.yamllib/I18n/translations/english.yamllib/I18n/translations/finnish.yamllib/I18n/translations/french.yamllib/I18n/translations/german.yamllib/I18n/translations/hungarian.yamllib/I18n/translations/italian.yamllib/I18n/translations/kazakh.yamllib/I18n/translations/lithuanian.yamllib/I18n/translations/polish.yamllib/I18n/translations/portuguese.yamllib/I18n/translations/romanian.yamllib/I18n/translations/russian.yamllib/I18n/translations/slovenian.yamllib/I18n/translations/spanish.yamllib/I18n/translations/swedish.yamllib/I18n/translations/turkish.yamllib/I18n/translations/ukrainian.yamlsrc/CrossPointSettings.hsrc/SettingsList.hsrc/activities/reader/EpubReaderActivity.cpp
📜 Review details
🧰 Additional context used
🧠 Learnings (33)
📚 Learning: 2026-02-23T22:11:42.181Z
Learnt from: ariel-lindemann
Repo: crosspoint-reader/crosspoint-reader PR: 1133
File: lib/I18n/translations/finnish.yaml:92-92
Timestamp: 2026-02-23T22:11:42.181Z
Learning: In translation YAML files located in lib/I18n/translations/, literal percent signs (%) in strings (e.g., STR_HIDE_BATTERY, STR_GO_TO_PERCENT, STR_PERCENT_STEP_HINT) do not require escaping as %% and this convention is consistent across all language translations. Ensure that this pattern is followed for all YAML files in this directory to avoid introducing incorrect formatting markers in localized strings.
Applied to files:
lib/I18n/translations/dutch.yamllib/I18n/translations/swedish.yamllib/I18n/translations/kazakh.yamllib/I18n/translations/portuguese.yamllib/I18n/translations/romanian.yamllib/I18n/translations/slovenian.yamllib/I18n/translations/danish.yamllib/I18n/translations/hungarian.yamllib/I18n/translations/ukrainian.yamllib/I18n/translations/czech.yamllib/I18n/translations/polish.yamllib/I18n/translations/turkish.yamllib/I18n/translations/lithuanian.yamllib/I18n/translations/spanish.yamllib/I18n/translations/finnish.yamllib/I18n/translations/russian.yamllib/I18n/translations/german.yamllib/I18n/translations/english.yamllib/I18n/translations/catalan.yamllib/I18n/translations/belarusian.yamllib/I18n/translations/italian.yamllib/I18n/translations/french.yaml
📚 Learning: 2026-03-25T10:28:30.516Z
Learnt from: jpirnay
Repo: crosspoint-reader/crosspoint-reader PR: 1495
File: lib/I18n/translations/german.yaml:294-322
Timestamp: 2026-03-25T10:28:30.516Z
Learning: In crosspoint-reader, it is expected to include “pre-emptive” translation keys in non-English YAML files before the corresponding English keys exist (e.g., german.yaml ahead of en.yaml). When reviewing changes to files under lib/I18n/translations/*.yaml, do not flag missing corresponding English keys in non-English locales as an error if the repo’s gen_i18n.py fallback mechanism is intended to handle missing English keys safely until the pending PRs land.
Applied to files:
lib/I18n/translations/dutch.yamllib/I18n/translations/swedish.yamllib/I18n/translations/kazakh.yamllib/I18n/translations/portuguese.yamllib/I18n/translations/romanian.yamllib/I18n/translations/slovenian.yamllib/I18n/translations/danish.yamllib/I18n/translations/hungarian.yamllib/I18n/translations/ukrainian.yamllib/I18n/translations/czech.yamllib/I18n/translations/polish.yamllib/I18n/translations/turkish.yamllib/I18n/translations/lithuanian.yamllib/I18n/translations/spanish.yamllib/I18n/translations/finnish.yamllib/I18n/translations/russian.yamllib/I18n/translations/german.yamllib/I18n/translations/english.yamllib/I18n/translations/catalan.yamllib/I18n/translations/belarusian.yamllib/I18n/translations/italian.yamllib/I18n/translations/french.yaml
📚 Learning: 2026-04-17T18:59:20.878Z
Learnt from: KymAndriy
Repo: crosspoint-reader/crosspoint-reader PR: 1684
File: lib/I18n/translations/ukrainian.yaml:29-29
Timestamp: 2026-04-17T18:59:20.878Z
Learning: For Ukrainian (and other Slavic-language) translation YAML strings in lib/I18n/translations/*.yaml: if a format string contains a numeric placeholder produced via snprintf-style formatting (e.g., %zu) and runtime noun inflection is not feasible, place the relevant noun in genitive plural before the number in the string (e.g., "мереж %zu") rather than after it (e.g., "мережу %zu"). This avoids incorrect case forms that would otherwise result from the value affecting inflection (e.g., wrong forms like "мережу/мережі/мереж"). In such cases, do not flag the word order as incorrect.
Applied to files:
lib/I18n/translations/dutch.yamllib/I18n/translations/swedish.yamllib/I18n/translations/kazakh.yamllib/I18n/translations/portuguese.yamllib/I18n/translations/romanian.yamllib/I18n/translations/slovenian.yamllib/I18n/translations/danish.yamllib/I18n/translations/hungarian.yamllib/I18n/translations/ukrainian.yamllib/I18n/translations/czech.yamllib/I18n/translations/polish.yamllib/I18n/translations/turkish.yamllib/I18n/translations/lithuanian.yamllib/I18n/translations/spanish.yamllib/I18n/translations/finnish.yamllib/I18n/translations/russian.yamllib/I18n/translations/german.yamllib/I18n/translations/english.yamllib/I18n/translations/catalan.yamllib/I18n/translations/belarusian.yamllib/I18n/translations/italian.yamllib/I18n/translations/french.yaml
📚 Learning: 2026-02-16T22:25:35.674Z
Learnt from: whyte-j
Repo: crosspoint-reader/crosspoint-reader PR: 733
File: lib/I18n/I18nKeys.h:271-272
Timestamp: 2026-02-16T22:25:35.674Z
Learning: In the crosspoint-reader i18n system (lib/I18n/), missing translation keys in non-English YAML files are automatically filled with English strings at build time by the gen_i18n.py script. All generated string arrays (STRINGS_EN, STRINGS_ES, etc.) have identical lengths, so runtime array indexing is safe without per-string fallback logic. The system prints "INFO: '{key}' missing in {lang_code}, using English fallback" during code generation when this occurs.
Applied to files:
lib/I18n/translations/kazakh.yamllib/I18n/translations/portuguese.yamllib/I18n/translations/hungarian.yamllib/I18n/translations/ukrainian.yamllib/I18n/translations/czech.yamllib/I18n/translations/polish.yamllib/I18n/translations/lithuanian.yamllib/I18n/translations/spanish.yamllib/I18n/translations/russian.yamllib/I18n/translations/german.yamllib/I18n/translations/english.yamllib/I18n/translations/catalan.yamllib/I18n/translations/belarusian.yamllib/I18n/translations/italian.yamllib/I18n/translations/french.yaml
📚 Learning: 2026-04-05T10:43:16.171Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 1534
File: src/activities/boot_sleep/SleepActivity.cpp:0-0
Timestamp: 2026-04-05T10:43:16.171Z
Learning: In crosspoint-reader/crosspoint-reader (src/CrossPointSettings.h), CrossPointSettings::SLEEP_COVER_OVERLAY enum has values: OVERLAY_OFF=0, OVERLAY_WHITE=1, OVERLAY_GRAY=2, OVERLAY_BLACK=3 (as of commit 2471b5a in PR `#1534`). The default sleepCoverOverlay byte is 0 (OVERLAY_OFF). Do not use the old ordering WHITE=0/GRAY=1/BLACK=2/OFF=3 in future reviews.
Applied to files:
src/CrossPointSettings.h
📚 Learning: 2026-04-02T15:35:02.228Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 1554
File: src/activities/boot_sleep/SleepActivity.cpp:40-63
Timestamp: 2026-04-02T15:35:02.228Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/boot_sleep/SleepActivity.cpp, SleepActivity::renderBitmapSleepScreen), CrossPointSettings::SLEEP_SCREEN_FILTER::FILTER_CONTRAST is intentionally handled implicitly: `hasGreyscale` is computed as `bitmap.hasGreyscale() && SETTINGS.sleepScreenFilter == FILTER_NONE`. When FILTER_CONTRAST is selected, `hasGreyscale` evaluates to false, which skips the 4-level greyscale rendering path and falls back to 1-bit B&W dithered rendering, producing a visibly higher-contrast result on bitmaps with greyscale data. LOGO and BLANK screens are unaffected by FILTER_CONTRAST by design — they contain no bitmap content where a contrast effect would be meaningful. Do not flag FILTER_CONTRAST as unimplemented or a no-op in future reviews.
Applied to files:
src/CrossPointSettings.h
📚 Learning: 2026-03-25T01:30:23.778Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 1488
File: src/activities/home/HomeActivity.cpp:64-68
Timestamp: 2026-03-25T01:30:23.778Z
Learning: In crosspoint-reader/crosspoint-reader, `CrossPointState::pendingCoverGeneration` (bool, default false) and `CrossPointState::forceRenderCoverPath` (std::string, default "") are purely transient in-memory fields. They are NOT serialized in `JsonSettingsIO::saveState`/`loadState`. On every reboot they revert to their defaults, so there is no reboot-replay risk and no need to call `APP_STATE.saveToFile()` after consuming them in `HomeActivity::loadRecentCovers()`. Do not flag their in-memory consumption as a persistence bug.
Applied to files:
src/CrossPointSettings.h
📚 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/CrossPointSettings.h
📚 Learning: 2026-03-25T01:15:06.744Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 1488
File: src/components/themes/lyra/Lyra3CoversTheme.cpp:39-40
Timestamp: 2026-03-25T01:15:06.744Z
Learning: In crosspoint-reader/crosspoint-reader (src/components/themes/lyra/Lyra3CoversTheme.cpp and other theme drawing functions), `COVER_DISABLED_MODE` (global setting) only prevents new cover *generation* on HOME entry to avoid blocking the main thread. It does NOT suppress the display of already-cached covers. Only the per-book `coverDisabled` flag (RecentBook::coverDisabled) controls whether a cached cover BMP is rendered. Do not flag theme draw paths for not checking COVER_DISABLED_MODE; checking only coverDisabled is correct by design.
Applied to files:
src/CrossPointSettings.hsrc/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-02-21T16:47:45.578Z
Learnt from: Uri-Tauber
Repo: crosspoint-reader/crosspoint-reader PR: 1031
File: src/activities/reader/EpubReaderActivity.cpp:816-853
Timestamp: 2026-02-21T16:47:45.578Z
Learning: In src/activities/reader/EpubReaderActivity.cpp, the navigateToHref() function uses a fixed-size mini-stack (MAX_FOOTNOTE_DEPTH = 3) for saved positions rather than a dynamic stack. This is intentional to minimize RAM usage on ESP32-C3, which has limited memory. The design accepts that failed navigation at max depth may decrement footnoteDepth even when no push occurred, as a tradeoff for memory efficiency. Users exit by pressing Back three times maximum.
Applied to files:
src/CrossPointSettings.hsrc/activities/reader/EpubReaderActivity.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/CrossPointSettings.hsrc/activities/reader/EpubReaderActivity.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/CrossPointSettings.h
📚 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/CrossPointSettings.hsrc/activities/reader/EpubReaderActivity.cpplib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpplib/Epub/Epub/ParsedText.hlib/Epub/Epub/parsers/ChapterHtmlSlimParser.hsrc/SettingsList.hlib/Epub/Epub/Section.hlib/Epub/Epub/ParsedText.cpplib/Epub/Epub/Section.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/EpubReaderActivity.cpplib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpplib/Epub/Epub/parsers/ChapterHtmlSlimParser.hlib/Epub/Epub/Section.hlib/Epub/Epub/Section.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/EpubReaderActivity.cpp
📚 Learning: 2026-02-19T17:46:26.871Z
Learnt from: Tritlo
Repo: crosspoint-reader/crosspoint-reader PR: 1003
File: src/activities/reader/EpubReaderActivity.cpp:657-674
Timestamp: 2026-02-19T17:46:26.871Z
Learning: In EpubReaderActivity.cpp, renderContents() behavior is intentional: if uncached images exist, Phase 1 should call displayBuffer(forceFullRefresh) to perform a HALF_REFRESH and clear ghosting; Phase 2 should call renderer.displayBuffer() without forceFullRefresh for a faster refresh. Grayscale anti-aliasing is applied separately after renderContents() via displayGrayBuffer(). This guidance applies specifically to this file and situation.
Applied to files:
src/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-03-28T11:14:39.035Z
Learnt from: pablohc
Repo: crosspoint-reader/crosspoint-reader PR: 1488
File: src/activities/home/HomeActivity.cpp:144-145
Timestamp: 2026-03-28T11:14:39.035Z
Learning: In crosspoint-reader/crosspoint-reader (src/activities/home/HomeActivity.cpp, HomeActivity), `recentsLoaded` must NOT be reset in `onEnter()`. It defaults to `false` for new instances, but for retained instances it must stay `true` between HOME visits to prevent redundant cover regeneration on every entry. Cover generation is gated on `APP_STATE.pendingCoverGeneration` (set in `EpubReaderActivity::onExit()`) and `APP_STATE.forceRenderCoverPath` (set by Reader Menu cover actions) — these are the only authoritative triggers. Resetting `recentsLoaded` in `onEnter()` would cause generation to fire on every HOME visit (e.g., returning from Settings). Only `coverRendered` and `coverBufferStored` are correctly reset in `onEnter()`.
Applied to files:
src/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-03-28T20:57:09.493Z
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.
Applied to files:
src/activities/reader/EpubReaderActivity.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/EpubReaderActivity.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/EpubReaderActivity.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/EpubReaderActivity.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), orphan/post-TOC spines always inherit a non-negative `tocIndex` from the previous chapter via `BookMetadataCache`. Therefore `getTocIndexForSpineIndex` and `getTocIndexForPage` (which falls back to `getTocIndexForSpineIndex` when `tocBoundaries` is empty) will never return -1 for post-TOC spines. The only case where `pageTocIndex == -1` can occur is a genuine pre-TOC spine (e.g. the very first spine) that has no TOC entry at all and no predecessor to inherit from.
Applied to files:
src/activities/reader/EpubReaderActivity.cpp
📚 Learning: 2026-04-16T06:13:29.082Z
Learnt from: znelson
Repo: crosspoint-reader/crosspoint-reader PR: 1582
File: lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp:497-501
Timestamp: 2026-04-16T06:13:29.082Z
Learning: In `lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp`, when the image consumes the empty block's accumulated vertical spacing, the reset of `currentTextBlock`'s `BlockStyle` to a fresh `BlockStyle` (with only `alignment` set) is intentionally correct. `getCombinedBlockStyle(..., CombineAxis::Vertical)` initializes `BlockStyle result = child`, so horizontal properties on the empty block are never read — the block-style stack provides horizontal context for the next element. Only the vertical margins/padding from the empty block are read during the merge, and the reset zeroes exactly those. Preserving horizontal insets from the empty block would add complexity for zero behavioral difference. Do not flag this reset as dropping inherited horizontal style in future reviews.
Applied to files:
src/activities/reader/EpubReaderActivity.cpplib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpplib/Epub/Epub/ParsedText.hlib/Epub/Epub/parsers/ChapterHtmlSlimParser.hlib/Epub/Epub/Section.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/EpubReaderActivity.cpplib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpplib/Epub/Epub/ParsedText.cpplib/Epub/Epub/Section.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/EpubReaderActivity.cpplib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpplib/Epub/Epub/ParsedText.cpplib/Epub/Epub/Section.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/EpubReaderActivity.cpplib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpplib/Epub/Epub/ParsedText.cpplib/Epub/Epub/Section.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:
lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpplib/Epub/Epub/parsers/ChapterHtmlSlimParser.hlib/Epub/Epub/ParsedText.cpplib/Epub/Epub/Section.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:
lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpplib/Epub/Epub/parsers/ChapterHtmlSlimParser.hlib/Epub/Epub/Section.hlib/Epub/Epub/Section.cpp
📚 Learning: 2026-03-21T19:30:33.664Z
Learnt from: znelson
Repo: crosspoint-reader/crosspoint-reader PR: 1454
File: lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp:101-105
Timestamp: 2026-03-21T19:30:33.664Z
Learning: In `lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp`, the `blockStyleStack` is a fixed-capacity array (`MAX_BLOCK_STYLE_DEPTH`, currently 8). On overflow, `pushBlockStyle()` only logs an error and increments `blockStyleStackSize` without writing a new entry; `getAccumulatedBlockStyle()` returns the deepest stored accumulated entry as a fallback. This is intentional — real EPUBs are expected to have ≤ 8 block-style nesting levels. Inline styles use a `vector`-based stack and can nest more deeply. The overflow threshold or container type can be revisited if 8 proves insufficient in practice. Do not flag the overflow fallback behavior as a bug in future reviews.
Applied to files:
lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpplib/Epub/Epub/parsers/ChapterHtmlSlimParser.hlib/Epub/Epub/Section.cpp
📚 Learning: 2026-04-13T09:12:18.349Z
Learnt from: jpirnay
Repo: crosspoint-reader/crosspoint-reader PR: 1646
File: lib/KOReaderSync/ChapterXPathIndexerState.h:80-80
Timestamp: 2026-04-13T09:12:18.349Z
Learning: In `lib/KOReaderSync/ChapterXPathIndexerState.h`, `shouldSkipText(len)` is intentionally a fast structural gate only (skipDepth, zero-length, outside body). Both callers (`ChapterXPathForwardMapper.cpp` and `ChapterXPathReverseMapper.cpp`) check `isWhitespaceOnly(text, len)` separately and later, because whitespace-only nodes may still require partial processing before being discarded — in particular, the forward mapper accumulates `codepointsInBodyTextNode` for body-level whitespace text nodes. Do not suggest merging `isWhitespaceOnly` into `shouldSkipText` in future reviews.
Applied to files:
lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpplib/Epub/Epub/ParsedText.cpp
📚 Learning: 2026-03-02T09:58:47.539Z
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.
Applied to files:
lib/Epub/Epub/Section.hlib/Epub/Epub/Section.cpp
📚 Learning: 2026-03-20T19:43:16.969Z
Learnt from: znelson
Repo: crosspoint-reader/crosspoint-reader PR: 1447
File: lib/Epub/Epub/Section.cpp:115-117
Timestamp: 2026-03-20T19:43:16.969Z
Learning: In crosspoint-reader/crosspoint-reader, `Section::buildTocBoundariesFromFile()` and `Section::buildTocBoundaries()` are always called on freshly constructed `Section` objects (created via `std::make_unique<Section>(...)` in `prepareSection`). The `tocBoundaries` member vector is therefore always default-initialized (empty) before either builder runs — no explicit `tocBoundaries.clear()` is needed inside these methods. Do not flag missing clears as a bug in future reviews.
Applied to files:
lib/Epub/Epub/Section.hlib/Epub/Epub/Section.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:
lib/Epub/Epub/ParsedText.cpp
🔇 Additional comments (31)
lib/I18n/translations/finnish.yaml (1)
261-261: LGTM!The Finnish translation for the Focus Reading feature is correctly formatted and appropriately placed in alphabetical order. The translation "Keskittynyt lukeminen" (Focused reading) accurately conveys the feature's purpose.
lib/I18n/translations/swedish.yaml (1)
290-290: Looks good — Swedish key added correctly.
STR_FOCUS_READINGis present with a clear localized value and matches the new setting key naming.lib/I18n/translations/danish.yaml (1)
286-286: Looks good — Danish key added correctly.The new
STR_FOCUS_READINGentry is consistent with the i18n key structure.lib/I18n/translations/slovenian.yaml (1)
286-286: Looks good — Slovenian key added correctly.
STR_FOCUS_READINGis correctly introduced with a valid localized string.lib/I18n/translations/turkish.yaml (1)
261-261: Looks good — Turkish key added correctly.The
STR_FOCUS_READINGtranslation is properly defined and aligned with the feature toggle key.lib/I18n/translations/dutch.yaml (1)
286-286: Looks good — Dutch key added correctly.
STR_FOCUS_READINGis added in the expected format with an appropriate localized value.lib/I18n/translations/czech.yaml (1)
262-262: Looks good — Czech key added correctly.The new
STR_FOCUS_READINGentry is consistent with the existing translation schema.lib/I18n/translations/portuguese.yaml (1)
262-262: Looks good — Portuguese key added correctly.
STR_FOCUS_READINGis properly added and matches the expected i18n key pattern.lib/I18n/translations/kazakh.yaml (1)
260-260: Looks good — Kazakh key added correctly.The new
STR_FOCUS_READINGtranslation is well-formed and aligned with the new setting key.lib/I18n/translations/catalan.yaml (1)
286-286: LGTM! Translation addition is clean and properly formatted.The new
STR_FOCUS_READINGkey with Catalan translation "Lectura enfocada" is correctly placed and formatted.lib/I18n/translations/romanian.yaml (1)
286-286: LGTM! Translation addition is clean and properly formatted.The new
STR_FOCUS_READINGkey with Romanian translation "Lectură concentrată" is correctly placed and formatted.lib/I18n/translations/belarusian.yaml (1)
261-261: LGTM! Translation addition is clean and properly formatted.The new
STR_FOCUS_READINGkey with Belarusian translation "Фокуснае чытанне" is correctly placed and formatted.lib/I18n/translations/german.yaml (1)
287-287: LGTM! Translation addition is clean and properly formatted.The new
STR_FOCUS_READINGkey with German translation "Fokus-Lesen" is correctly placed and formatted.lib/I18n/translations/russian.yaml (1)
289-289: LGTM! Translation addition is clean and properly formatted.The new
STR_FOCUS_READINGkey with Russian translation "Фокусное чтение" is correctly placed and formatted.lib/I18n/translations/hungarian.yaml (1)
286-286: LGTM! Translation addition is clean and properly formatted.The new
STR_FOCUS_READINGkey with Hungarian translation "Fókuszált olvasás" is correctly placed and formatted.lib/I18n/translations/polish.yaml (1)
286-286: LGTM! Translation addition is clean and properly formatted.The new
STR_FOCUS_READINGkey with Polish translation "Czytanie skupione" is correctly placed and formatted.lib/I18n/translations/english.yaml (1)
290-290: LGTM! Translation addition is clean and properly formatted.The new
STR_FOCUS_READINGkey with English translation "Focus Reading" is correctly placed and formatted. This serves as the base translation for the i18n fallback system.lib/I18n/translations/ukrainian.yaml (1)
286-286: LGTM. Translation reads naturally.lib/I18n/translations/lithuanian.yaml (1)
286-286: LGTM.lib/I18n/translations/spanish.yaml (1)
286-286: LGTM.lib/I18n/translations/italian.yaml (1)
262-262: LGTM.lib/I18n/translations/french.yaml (1)
286-286: LGTM.src/CrossPointSettings.h (1)
198-199: LGTM. New setting field with safe default (disabled). Consistent with other boolean toggles stored asuint8_t.lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp (1)
152-152: Constructor arguments align withParsedTextsignature.Positional order (
extraParagraphSpacing, hyphenationEnabled, focusReadingEnabled, blockStyle) matches the declaration inlib/Epub/Epub/ParsedText.h:38-42. No concerns.src/activities/reader/EpubReaderActivity.cpp (1)
549-560: Flag threaded consistently through both cache load and build paths.
focusReadingEnabledis passed toloadSectionFileandcreateSectionFileat all four call sites (main render at lines 552/560 and silent pre-index at lines 674/682) using the sameSETTINGS.focusReadingEnabledvalue, which is required so the cache-hit check and subsequent build agree on the parameter and don't trigger a rebuild every open. Argument order matchesSection.h:34-41.Also applies to: 671-682
src/SettingsList.h (1)
56-57: LGTM.Toggle is correctly placed in the Reader category and wired to
CrossPointSettings::focusReadingEnabledwith a stable JSON key.lib/Epub/Epub/ParsedText.h (1)
38-43: Parameter insertion position is safe.Inserting
focusReadingEnabledbeforeblockStyleis source-compatible: any existing caller passing aBlockStylepositionally as the 3rd argument would fail to compile (BlockStyle is not bool-convertible), so no silent regressions are possible. Initializer list is clean.lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h (1)
50-126: LGTM.
focusReadingEnabledplumbed through cleanly; signature and initializer list are consistent with how other trailing config flags (e.g.,hyphenationEnabled) are handled.lib/Epub/Epub/Section.h (1)
19-41: LGTM.
focusReadingEnabledis inserted consistently as a trailing mandatory parameter across all three methods, and is placed beforepopupFnincreateSectionFileso the default-argument tail is preserved.lib/Epub/Epub/Section.cpp (2)
13-16: Version bump + header layout are correct.Bumping
SECTION_FILE_VERSIONto 20 ensures existing caches are invalidated on first boot after upgrade, and theHEADER_SIZEaddition plus thestatic_assertat lines 45-50 keep the header layout verifiable at compile time. The parameter-mismatch guard at line 111 also includesfocusReadingEnabled, so toggling the setting invalidates the cache cleanly.
215-219: Argument position verified against parser signature.
focusReadingEnabledis passed in the 11th position (right afterhyphenationEnabled, before thecompletePageFnlambda), which matches theChapterHtmlSlimParserconstructor declaration in the header. No misalignment with surrounding positional arguments.
Expanded the range of characters in the punctuation check to include currency, math symbols, arrows, and other symbols.
Added logic to pre-reserve capacity for word splits to avoid reallocation mid-word
18cc95d to
9e99759
Compare
|
This is SO COOL! I was hoping the firmware would have this when I first installed it and I'm glad someone beat me to adding it!! Awesome job :D |
|
While bionic reading doesn't make one faster, and supposedly marginally decreases text comprehension, it is so much more pleasant to read on a small screen with it. |
|
Thank you so much for this feature, @vjapolitzer, It looks amazing!! |
|
Thanks @pablohc |
Adjust target bold character calculation to use a fixed percentage for better readability and improve tuning for desired bolding
I'm not used to this font, but I saw it has 5 levels. I thought F4 was intentionally set by default, rather than a bug 🤣 I was going to suggest being able to choose the level between F1 and F5, but I see you've already implemented it. I'll try it again today with the latest changes. Amazing work, @vjapolitzer! |
Thank you! I can certainly change the settings toggle to an Off/1/2/3/4/5 enum if folks would like that. I was guessing that something similar to F3 might be the most popular and went simple, for now. |
|
Oh no, no, I misread the commit description: ec4639f
I had understood that it was adjustable, but if it is not, I also think that the F3 is the most balanced option and avoids greater complexity. If the truncation bug is already resolved, it will be perfect. I have done a quick test based on 9e99759 and I don't know if it is solved in the following commits, if so, don't take it into account.
|
Remove hyphen whitelist in isWordCharacter
Ah I see! Yes, I have tweaked the truncation to yield a visual anchor weight similar to F3. Took a bit of fiddling since we aren't rendering the half-bold characters like Bionic Reading does. It wouldn't be too much more work to make it configurable to F1-F5, but I've been happy with F3. Good catch on compound words! When I whitelisted apostrophes, I also mistakenly added hyphens (compound words) as well. |
Added checks to reject specific dash and hyphen characters.
|
Thanks for this feature, very much appreciated! |
pablohc
left a comment
There was a problem hiding this comment.
I've been testing it directly on the device for several days now, and I haven't had any issues so far with the latest updates. Thank you so much for this amazing work, @vjapolitzer
|
I'm testing on device and reading (English) successfully. Very nice; thanks! I want to test a bit more before my signoff, and note as always I'm not doing code review. Also, would like to make sure that if this gets merged it doesn't interfere with the ongoing custom font progress being made. |
Thank you! It works by applying the bold font styling property during indexing, so I'm hopeful it shouldn't interfere. I've been testing it with the custom font PRs cherry picked in with good results. |
|
Hi @vjapolitzer … excited to test this with the new SD card font updates |
# Conflicts: # lib/Epub/Epub/Section.cpp # lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h # src/CrossPointSettings.h
1bab9d0 to
33e0d6b
Compare
|
I'm adding @mcrosson to the conversation in the hope that he might be able to share his thoughts once version 1.3.0 and the downloadable fonts have been released. |
|
Some thoughts after a quick review
|
|
Understood. Is this ~X% tunable. You mention below you tweaked the crossink implementation. This is fine but speaks to a 'this may be best as a tunable' since we almost immediately have 2 slightly different values in play (your and crossink).
Thank you :)
Thank you :)
Gotcha, so there are some improvements over the crossink form? Asking to ensure I read this properly.
Given we have a variety of fonts with a variety of weights and sizes... I think it wise to have a reasonable default but allow tuning. There is just too much variability to fonts and way too much personal need/preference in this world to be able to say 'this is the way'. Also: what exactly do you mean by 'F3' here?
Having some eyeball needs of my own... Loads of folk don't get why I'm so aggressive about applying Atkinson Hyperlegible to my text. It's so I can read easier, I really don't give a crap about others thoughts unless they know a font that I should try. ;) I think this being an accessibility win is a good thing and we should work to make it flexible and adaptable so folk who do need/want this form of reading can adjust it to their own needs. One other thing for @crosspoint-reader/firmware-maintainers in particular: before we proceed with a merge, I want to get @uxjulia 's input. We are going to collide with crossink and they've been an asset to our community. I'd like to ensure we dont create a mess for them. |
|
I think the improvements that @vjapolitzer implemented are warranted. I also thought about bumping the percentage up to 45% to more closely match true bionic but decided against it (just boiled down to the fact I wasn't sure if I liked 3-char words having 2-bolded chars so I left it alone). The change in boundary vector also makes sense from a memory perspective and I'd fold in these changes into Crossink as-is. Edit: I'm not sure we need to provide fine-tuning for bionic reading. F3 is generally noted for being the preferred middle-ground, and honestly most people probably don't even know it's tunable. I'd say as a first release, no custom tuning is fine. If there is a demand for tuning, then consider it (I have yet to have a single person ask for this). Don't need to bloat the firmware for something that may not be necessary.
|
|
Given @uxjulia 's feedback, I'm comfortable with this being added. However I will defer 2nd review to @lukestein as they are more familiar with the feature and code. |
lukestein
left a comment
There was a problem hiding this comment.
Thank you to @vjapolitzer and many others for great work and productive conversation on this. As always, I note that I am not competent to comment on code quality. But my testing of this most recent version against current main suggests a continued great reader experience.
I've tried several different epubs (note: all English-language), fonts (built in and SD card), and other settings combos (e.g., hyphenation).
|
Alright, as requested, I have added a user guide with examples of the feature with various fonts. I am happy to see the function working as intended with the downloaded SD card fonts. |
|
@uxjulia / @vjapolitzer / @lukestein / @pablohc would this benefit from a 'per book option' as well? We've been slowly moving in the direction of book level overrides and this may be a candidate. Thoughts? Edit: if so, would you be open to an additional PR for this? |
|
Thank you very much for all your hard work on this PR @vjapolitzer
I totally agree with that, @mcrosson. I think the In-Reader menu should be completely redesigned to offer more granular settings and eliminate the need to exit reading mode and go into global settings just to change a current reading setting. |
Yes, I agree with @pablohc. If we move to non-global, per-book font settings, this setting should also follow. |
As @pablohc mentioned, adding quick access to all reader options (not just bionic reading) within a book would be a huge win. |
|
While I agree 'all' is the better option, if we can start small that will hopefully help nudge others and we can pacman our way through it instead of trying to get a huge PR merged. |
I understand your point, @mcrosson, and maybe we should start a new thread to avoid spamming this PR. |


Summary
This PR introduces Focus Reading, a generic implementation of artificial fixation points (similar to Bionic Reading) designed to improve reading speed and focus by bolding the initial characters of words. This is achieved by dynamically bolding characters during indexing.
Implementation Details
Core Text Engine (
ParsedText)ParsedText::addWordto implement a custom bolding algorithm. It uses a 45% ratio for bolding, with a minimum of 1 character and a maximum of 9.utf8NextCodepointto ensure character counting and string slicing occur at safe byte boundaries, preventing corruption of multi-byte characters (e.g., accented letters or smart quotes).wordIsFocusSuffix. After splitting and layout, suffixes are merged back into their preceding word entries to prevent a doubling of RAM usage.Settings and UI
SECTION_FILE_VERSIONto21focusReadingEnabledtoCrossPointSettings.STR_FOCUS_READINGstringPlumbing
focusReadingEnabledboolean throughEpubReaderActivity,Section, andChapterHtmlSlimParserto ensure the user's setting reaches theParsedTextconstructor during chapter indexing.Additional Context
Files Changed
lib/Epub/Epub/ParsedText.h / .cpp: Core fixation logic and UTF-8 tokenization.lib/Epub/Epub/Section.h / .cpp: Cache header updates and invalidation logic.lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h / .cpp: Plumbing the setting to text blocks.src/CrossPointSettings.h: Data persistence for the new setting.src/SettingsList.h: UI toggle implementation.src/activities/reader/EpubReaderActivity.cpp: Handling settings changes during reading sessions.lib/I18n/translations/*.yaml: UI strings.AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? YES