Yet another bionic reading implementation

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
jpirnay
2026-04-30 09:03:41 +02:00
co-authored by Copilot
parent 4cc793397f
commit 92ef1dcaf6
11 changed files with 47 additions and 20 deletions
+7 -2
View File
@@ -26,8 +26,10 @@ class ParsedText {
BlockStyle blockStyle; BlockStyle blockStyle;
bool extraParagraphSpacing; bool extraParagraphSpacing;
bool hyphenationEnabled; bool hyphenationEnabled;
bool bionicReadingEnabled;
void applyParagraphIndent(); void applyParagraphIndent();
void applyBionicReadingTransform();
std::vector<size_t> computeLineBreaks(const GfxRenderer& renderer, int fontId, int pageWidth, std::vector<size_t> computeLineBreaks(const GfxRenderer& renderer, int fontId, int pageWidth,
std::vector<uint16_t>& wordWidths, std::vector<bool>& continuesVec); std::vector<uint16_t>& wordWidths, std::vector<bool>& continuesVec);
std::vector<size_t> computeHyphenatedLineBreaks(const GfxRenderer& renderer, int fontId, int pageWidth, std::vector<size_t> computeHyphenatedLineBreaks(const GfxRenderer& renderer, int fontId, int pageWidth,
@@ -60,8 +62,11 @@ class ParsedText {
public: public:
explicit ParsedText(const bool extraParagraphSpacing, const bool hyphenationEnabled = false, explicit ParsedText(const bool extraParagraphSpacing, const bool hyphenationEnabled = false,
const BlockStyle& blockStyle = BlockStyle()) const BlockStyle& blockStyle = BlockStyle(), const bool bionicReadingEnabled = false)
: blockStyle(blockStyle), extraParagraphSpacing(extraParagraphSpacing), hyphenationEnabled(hyphenationEnabled) {} : blockStyle(blockStyle),
extraParagraphSpacing(extraParagraphSpacing),
hyphenationEnabled(hyphenationEnabled),
bionicReadingEnabled(bionicReadingEnabled) {}
~ParsedText() = default; ~ParsedText() = default;
void addWord(std::string word, EpdFontFamily::Style fontStyle, bool underline = false, bool attachToPrevious = false); void addWord(std::string word, EpdFontFamily::Style fontStyle, bool underline = false, bool attachToPrevious = false);
+2
View File
@@ -598,3 +598,5 @@ STR_KB_HINT_SECONDARY_CHAR: "Hold SELECT for secondary char"
STR_KB_HINT_UPPER_SECONDARY: "Hold SELECT for UPPERCASE or secondary char" STR_KB_HINT_UPPER_SECONDARY: "Hold SELECT for UPPERCASE or secondary char"
STR_KB_HINT_LOWER_SECONDARY: "Hold SELECT for lowercase or secondary char" STR_KB_HINT_LOWER_SECONDARY: "Hold SELECT for lowercase or secondary char"
STR_KB_HINT_URL_SNIPPETS: "Press URL for snippets" STR_KB_HINT_URL_SNIPPETS: "Press URL for snippets"
STR_BIONIC_READING: "Bionic Reading"
STR_BTN_ACT_TOGGLE_BIONIC_READING: "Toggle Bionic Reading"
+1
View File
@@ -284,6 +284,7 @@ class CrossPointSettings {
BTN_PREV_SECTION, BTN_PREV_SECTION,
BTN_EXIT_READER, BTN_EXIT_READER,
BTN_READER_MENU, BTN_READER_MENU,
BTN_TOGGLE_BIONIC_READING,
BTN_KOREADER_SYNC, BTN_KOREADER_SYNC,
BUTTON_ACTION_COUNT BUTTON_ACTION_COUNT
}; };
+2
View File
@@ -418,6 +418,7 @@ bool JsonSettingsIO::saveRecentBooks(const RecentBooksStore& store, const char*
obj["imageRenderingOverride"] = book.imageRenderingOverride; obj["imageRenderingOverride"] = book.imageRenderingOverride;
obj["fontFamilyOverride"] = book.fontFamilyOverride; obj["fontFamilyOverride"] = book.fontFamilyOverride;
obj["fontSizeOverride"] = book.fontSizeOverride; obj["fontSizeOverride"] = book.fontSizeOverride;
obj["bionicReadingOverride"] = book.bionicReadingOverride;
} }
String json; String json;
@@ -455,6 +456,7 @@ bool JsonSettingsIO::loadRecentBooks(RecentBooksStore& store, const char* json)
book.fontFamilyOverride = book.fontFamilyOverride =
clampInt8(obj["fontFamilyOverride"] | -1, -1, CrossPointSettings::FONT_FAMILY_COUNT - 1, -1); clampInt8(obj["fontFamilyOverride"] | -1, -1, CrossPointSettings::FONT_FAMILY_COUNT - 1, -1);
book.fontSizeOverride = clampInt8(obj["fontSizeOverride"] | -1, -1, CrossPointSettings::FONT_SIZE_COUNT - 1, -1); book.fontSizeOverride = clampInt8(obj["fontSizeOverride"] | -1, -1, CrossPointSettings::FONT_SIZE_COUNT - 1, -1);
book.bionicReadingOverride = obj["bionicReadingOverride"] | false;
store.recentBooks.push_back(book); store.recentBooks.push_back(book);
} }
+9 -5
View File
@@ -26,6 +26,7 @@ void RecentBooksStore::addBook(const std::string& path, const std::string& title
int8_t imageRenderingOverride = -1; int8_t imageRenderingOverride = -1;
int8_t fontFamilyOverride = -1; int8_t fontFamilyOverride = -1;
int8_t fontSizeOverride = -1; int8_t fontSizeOverride = -1;
bool bionicReadingOverride = false;
// Remove existing entry if present // Remove existing entry if present
auto it = auto it =
@@ -35,12 +36,14 @@ void RecentBooksStore::addBook(const std::string& path, const std::string& title
imageRenderingOverride = it->imageRenderingOverride; imageRenderingOverride = it->imageRenderingOverride;
fontFamilyOverride = it->fontFamilyOverride; fontFamilyOverride = it->fontFamilyOverride;
fontSizeOverride = it->fontSizeOverride; fontSizeOverride = it->fontSizeOverride;
bionicReadingOverride = it->bionicReadingOverride;
recentBooks.erase(it); recentBooks.erase(it);
} }
// Add to front // Add to front
recentBooks.insert(recentBooks.begin(), {path, title, author, series, coverBmpPath, embeddedStyleOverride, recentBooks.insert(recentBooks.begin(),
imageRenderingOverride, fontFamilyOverride, fontSizeOverride}); {path, title, author, series, coverBmpPath, embeddedStyleOverride, imageRenderingOverride,
fontFamilyOverride, fontSizeOverride, bionicReadingOverride});
// Trim to max size // Trim to max size
if (recentBooks.size() > MAX_RECENT_BOOKS) { if (recentBooks.size() > MAX_RECENT_BOOKS) {
@@ -83,19 +86,19 @@ RecentBook RecentBooksStore::getBookByPath(const std::string& path) const {
} }
bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t embeddedStyleOverride, bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t embeddedStyleOverride,
const int8_t imageRenderingOverride) { const int8_t imageRenderingOverride, const bool bionicReadingOverride) {
auto it = auto it =
std::find_if(recentBooks.begin(), recentBooks.end(), [&](const RecentBook& book) { return book.path == path; }); std::find_if(recentBooks.begin(), recentBooks.end(), [&](const RecentBook& book) { return book.path == path; });
if (it == recentBooks.end()) { if (it == recentBooks.end()) {
return false; return false;
} }
return setReaderOverrides(path, embeddedStyleOverride, imageRenderingOverride, it->fontFamilyOverride, return setReaderOverrides(path, embeddedStyleOverride, imageRenderingOverride, it->fontFamilyOverride,
it->fontSizeOverride); it->fontSizeOverride, bionicReadingOverride);
} }
bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t embeddedStyleOverride, bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t embeddedStyleOverride,
const int8_t imageRenderingOverride, const int8_t fontFamilyOverride, const int8_t imageRenderingOverride, const int8_t fontFamilyOverride,
const int8_t fontSizeOverride) { const int8_t fontSizeOverride, const bool bionicReadingOverride) {
auto it = auto it =
std::find_if(recentBooks.begin(), recentBooks.end(), [&](const RecentBook& book) { return book.path == path; }); std::find_if(recentBooks.begin(), recentBooks.end(), [&](const RecentBook& book) { return book.path == path; });
if (it == recentBooks.end()) { if (it == recentBooks.end()) {
@@ -106,6 +109,7 @@ bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t
it->imageRenderingOverride = imageRenderingOverride; it->imageRenderingOverride = imageRenderingOverride;
it->fontFamilyOverride = fontFamilyOverride; it->fontFamilyOverride = fontFamilyOverride;
it->fontSizeOverride = fontSizeOverride; it->fontSizeOverride = fontSizeOverride;
it->bionicReadingOverride = bionicReadingOverride;
return saveToFile(); return saveToFile();
} }
+1
View File
@@ -17,6 +17,7 @@ struct RecentBook {
int8_t fontFamilyOverride = -1; int8_t fontFamilyOverride = -1;
// -1 = use global setting, otherwise CrossPointSettings::FONT_SIZE value. // -1 = use global setting, otherwise CrossPointSettings::FONT_SIZE value.
int8_t fontSizeOverride = -1; int8_t fontSizeOverride = -1;
bool bionicReadingOverride = false;
bool operator==(const RecentBook& other) const { return path == other.path; } bool operator==(const RecentBook& other) const { return path == other.path; }
}; };
+1 -1
View File
@@ -145,7 +145,7 @@ inline const std::vector<SettingInfo> list = {
StrId::STR_BTN_ACT_FORCE_REFRESH, StrId::STR_BTN_ACT_OPEN_TOC, StrId::STR_BTN_ACT_OPEN_BOOKMARKS, \ StrId::STR_BTN_ACT_FORCE_REFRESH, StrId::STR_BTN_ACT_OPEN_TOC, StrId::STR_BTN_ACT_OPEN_BOOKMARKS, \
StrId::STR_BTN_ACT_STAR_PAGE, StrId::STR_BTN_ACT_FOOTNOTES, StrId::STR_BTN_ACT_NEXT_SECTION, \ StrId::STR_BTN_ACT_STAR_PAGE, StrId::STR_BTN_ACT_FOOTNOTES, StrId::STR_BTN_ACT_NEXT_SECTION, \
StrId::STR_BTN_ACT_PREV_SECTION, StrId::STR_BTN_ACT_EXIT_READER, StrId::STR_BTN_ACT_READER_MENU, \ StrId::STR_BTN_ACT_PREV_SECTION, StrId::STR_BTN_ACT_EXIT_READER, StrId::STR_BTN_ACT_READER_MENU, \
StrId::STR_BTN_ACT_KOREADER_SYNC StrId::STR_BTN_ACT_TOGGLE_BIONIC_READING, StrId::STR_BTN_ACT_KOREADER_SYNC
// Back button: short=exit reader, double=ignore, long=go home // Back button: short=exit reader, double=ignore, long=go home
SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortBack, {StrId::STR_BTN_DEF_EXIT_READER}, SettingInfo::Enum(StrId::STR_BTN_SHORT_PRESS, &CrossPointSettings::btnShortBack, {StrId::STR_BTN_DEF_EXIT_READER},
+1
View File
@@ -28,6 +28,7 @@ struct MenuResult {
int8_t fontFamilyOverride = -1; int8_t fontFamilyOverride = -1;
int8_t fontSizeOverride = -1; int8_t fontSizeOverride = -1;
uint8_t textDarkness = 1; uint8_t textDarkness = 1;
uint8_t bionicReadingOverride = 0;
}; };
struct ChapterResult { struct ChapterResult {
@@ -135,6 +135,7 @@ class EpubReaderActivity final : public Activity {
int8_t bookImageRenderingOverride = -1; int8_t bookImageRenderingOverride = -1;
int8_t bookFontFamilyOverride = -1; int8_t bookFontFamilyOverride = -1;
int8_t bookFontSizeOverride = -1; int8_t bookFontSizeOverride = -1;
bool bookBionicReadingOverride = false;
// Bookmarks (starred pages) // Bookmarks (starred pages)
BookmarkStore bookmarkStore; BookmarkStore bookmarkStore;
@@ -30,14 +30,12 @@ std::string defaultFontFamilyLabel(const SettingInfo& item) {
} }
} // namespace } // namespace
EpubReaderMenuActivity::EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, EpubReaderMenuActivity::EpubReaderMenuActivity(
const std::string& title, const int currentPage, const int totalPages, GfxRenderer& renderer, MappedInputManager& mappedInput, const std::string& title, const int currentPage,
const int bookProgressPercent, const uint8_t currentOrientation, const int totalPages, const int bookProgressPercent, const uint8_t currentOrientation, const bool hasFootnotes,
const bool hasFootnotes, const int8_t initialEmbeddedStyleOverride, const int8_t initialEmbeddedStyleOverride, const int8_t initialImageRenderingOverride,
const int8_t initialImageRenderingOverride, const int8_t initialFontFamilyOverride, const int8_t initialFontSizeOverride, const uint8_t initialTextDarkness,
const int8_t initialFontFamilyOverride, const bool initialBionicReadingOverride, const bool hasStarredPages, const bool isCurrentPageStarred)
const int8_t initialFontSizeOverride, const uint8_t initialTextDarkness,
const bool hasStarredPages, const bool isCurrentPageStarred)
: MenuListActivity("EpubReaderMenu", renderer, mappedInput), : MenuListActivity("EpubReaderMenu", renderer, mappedInput),
currentPageStarred(isCurrentPageStarred), currentPageStarred(isCurrentPageStarred),
pendingOrientation(currentOrientation), pendingOrientation(currentOrientation),
@@ -46,6 +44,7 @@ EpubReaderMenuActivity::EpubReaderMenuActivity(GfxRenderer& renderer, MappedInpu
pendingFontFamilyOverride(initialFontFamilyOverride), pendingFontFamilyOverride(initialFontFamilyOverride),
pendingFontSizeOverride(initialFontSizeOverride), pendingFontSizeOverride(initialFontSizeOverride),
pendingTextDarkness(initialTextDarkness), pendingTextDarkness(initialTextDarkness),
pendingBionicReading(initialBionicReadingOverride),
title(title), title(title),
currentPage(currentPage), currentPage(currentPage),
totalPages(totalPages), totalPages(totalPages),
@@ -161,6 +160,15 @@ void EpubReaderMenuActivity::buildMenuItems(bool hasFootnotes, bool hasStarredPa
[](void* ctx, uint8_t v) { static_cast<EpubReaderMenuActivity*>(ctx)->pendingTextDarkness = v; }) [](void* ctx, uint8_t v) { static_cast<EpubReaderMenuActivity*>(ctx)->pendingTextDarkness = v; })
.withSubmenu(StrId::STR_READER_OVERRIDES)); .withSubmenu(StrId::STR_READER_OVERRIDES));
menuItems.push_back(
SettingInfo::DynamicEnumCtx(
StrId::STR_BIONIC_READING, {StrId::STR_STATE_OFF, StrId::STR_STATE_ON}, self,
[](const void* ctx) -> uint8_t {
return static_cast<const EpubReaderMenuActivity*>(ctx)->pendingBionicReading ? 1 : 0;
},
[](void* ctx, uint8_t v) { static_cast<EpubReaderMenuActivity*>(ctx)->pendingBionicReading = (v != 0); })
.withSubmenu(StrId::STR_READER_OVERRIDES));
// Helper functions, reading ruler, auto page turn, orientation // Helper functions, reading ruler, auto page turn, orientation
menuItems.push_back(SettingInfo::Separator(StrId::STR_READER_UTILS)); menuItems.push_back(SettingInfo::Separator(StrId::STR_READER_UTILS));
// Auto page turn: ACTION type with custom cycling in onActionSelected // Auto page turn: ACTION type with custom cycling in onActionSelected
@@ -240,7 +248,7 @@ EpubReaderMenuActivity::MenuAction EpubReaderMenuActivity::actionForSettingActio
void EpubReaderMenuActivity::finishWithAction(MenuAction action) { void EpubReaderMenuActivity::finishWithAction(MenuAction action) {
setResult(MenuResult{static_cast<int>(action), -1, pendingOrientation, selectedPageTurnOption, setResult(MenuResult{static_cast<int>(action), -1, pendingOrientation, selectedPageTurnOption,
pendingEmbeddedStyleOverride, pendingImageRenderingOverride, pendingFontFamilyOverride, pendingEmbeddedStyleOverride, pendingImageRenderingOverride, pendingFontFamilyOverride,
pendingFontSizeOverride, pendingTextDarkness}); pendingFontSizeOverride, pendingTextDarkness, static_cast<uint8_t>(pendingBionicReading)});
finish(); finish();
} }
@@ -273,7 +281,8 @@ void EpubReaderMenuActivity::onBackPressed() {
pendingImageRenderingOverride, pendingImageRenderingOverride,
pendingFontFamilyOverride, pendingFontFamilyOverride,
pendingFontSizeOverride, pendingFontSizeOverride,
pendingTextDarkness}; pendingTextDarkness,
static_cast<uint8_t>(pendingBionicReading)};
setResult(std::move(result)); setResult(std::move(result));
finish(); finish();
} }
@@ -37,8 +37,8 @@ class EpubReaderMenuActivity final : public MenuListActivity {
const uint8_t currentOrientation, const bool hasFootnotes, const uint8_t currentOrientation, const bool hasFootnotes,
const int8_t initialEmbeddedStyleOverride, const int8_t initialImageRenderingOverride, const int8_t initialEmbeddedStyleOverride, const int8_t initialImageRenderingOverride,
const int8_t initialFontFamilyOverride, const int8_t initialFontSizeOverride, const int8_t initialFontFamilyOverride, const int8_t initialFontSizeOverride,
const uint8_t initialTextDarkness, const bool hasStarredPages, const uint8_t initialTextDarkness, const bool initialBionicReadingOverride,
const bool isCurrentPageStarred); const bool hasStarredPages, const bool isCurrentPageStarred);
void onEnter() override; void onEnter() override;
void render(RenderLock&&) override; void render(RenderLock&&) override;
@@ -69,6 +69,7 @@ class EpubReaderMenuActivity final : public MenuListActivity {
int8_t pendingFontFamilyOverride = -1; int8_t pendingFontFamilyOverride = -1;
int8_t pendingFontSizeOverride = -1; int8_t pendingFontSizeOverride = -1;
uint8_t pendingTextDarkness = 1; uint8_t pendingTextDarkness = 1;
bool pendingBionicReading = false;
static constexpr const char* pageTurnLabels[] = {"", "1", "3", "6", "12"}; static constexpr const char* pageTurnLabels[] = {"", "1", "3", "6", "12"};