From 7533ed1a9c175a8d669f66a243177f158af1e0dd Mon Sep 17 00:00:00 2001 From: jpirnay Date: Fri, 24 Apr 2026 11:38:55 +0200 Subject: [PATCH] Add font foverrides to reader menu Co-authored-by: Copilot --- lib/I18n/translations/english.yaml | 1 + src/JsonSettingsIO.cpp | 5 + src/RecentBooksStore.cpp | 16 +- src/RecentBooksStore.h | 6 + src/activities/ActivityResult.h | 2 + src/activities/reader/EpubReaderActivity.cpp | 174 ++++++++++++++---- src/activities/reader/EpubReaderActivity.h | 6 +- .../reader/EpubReaderMenuActivity.cpp | 139 +++++++++----- .../reader/EpubReaderMenuActivity.h | 3 + 9 files changed, 265 insertions(+), 87 deletions(-) diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index 948ec599..6635e158 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -518,6 +518,7 @@ STR_IMAGE_DISPLAY_GRAYSCALE: ">> Gray" STR_WEATHER_MOON_INFO: "Moon" STR_WEATHER_SUN_INFO: "Sun" STR_READER_BOOKMARKS: "Bookmarks & Footnotes" +STR_READER_OVERRIDES: "Book-specific overrides" STR_READER_UTILS: "Helper" STR_READER_TOOLS: "Tools" STR_READER_NAVIGATION: "Navigation" diff --git a/src/JsonSettingsIO.cpp b/src/JsonSettingsIO.cpp index 1b818a12..d095010b 100644 --- a/src/JsonSettingsIO.cpp +++ b/src/JsonSettingsIO.cpp @@ -394,6 +394,8 @@ bool JsonSettingsIO::saveRecentBooks(const RecentBooksStore& store, const char* obj["coverBmpPath"] = book.coverBmpPath; obj["embeddedStyleOverride"] = book.embeddedStyleOverride; obj["imageRenderingOverride"] = book.imageRenderingOverride; + obj["fontFamilyOverride"] = book.fontFamilyOverride; + obj["fontSizeOverride"] = book.fontSizeOverride; } String json; @@ -428,6 +430,9 @@ bool JsonSettingsIO::loadRecentBooks(RecentBooksStore& store, const char* json) book.coverBmpPath = obj["coverBmpPath"] | std::string(""); book.embeddedStyleOverride = clampInt8(obj["embeddedStyleOverride"] | -1, -1, 1, -1); book.imageRenderingOverride = clampInt8(obj["imageRenderingOverride"] | -1, -1, 2, -1); + book.fontFamilyOverride = + clampInt8(obj["fontFamilyOverride"] | -1, -1, CrossPointSettings::FONT_FAMILY_COUNT - 1, -1); + book.fontSizeOverride = clampInt8(obj["fontSizeOverride"] | -1, -1, CrossPointSettings::FONT_SIZE_COUNT - 1, -1); store.recentBooks.push_back(book); } diff --git a/src/RecentBooksStore.cpp b/src/RecentBooksStore.cpp index 08e43168..fb222150 100644 --- a/src/RecentBooksStore.cpp +++ b/src/RecentBooksStore.cpp @@ -24,6 +24,8 @@ void RecentBooksStore::addBook(const std::string& path, const std::string& title const std::string& series, const std::string& coverBmpPath) { int8_t embeddedStyleOverride = -1; int8_t imageRenderingOverride = -1; + int8_t fontFamilyOverride = -1; + int8_t fontSizeOverride = -1; // Remove existing entry if present auto it = @@ -31,12 +33,14 @@ void RecentBooksStore::addBook(const std::string& path, const std::string& title if (it != recentBooks.end()) { embeddedStyleOverride = it->embeddedStyleOverride; imageRenderingOverride = it->imageRenderingOverride; + fontFamilyOverride = it->fontFamilyOverride; + fontSizeOverride = it->fontSizeOverride; recentBooks.erase(it); } // Add to front - recentBooks.insert(recentBooks.begin(), - {path, title, author, series, coverBmpPath, embeddedStyleOverride, imageRenderingOverride}); + recentBooks.insert(recentBooks.begin(), {path, title, author, series, coverBmpPath, embeddedStyleOverride, + imageRenderingOverride, fontFamilyOverride, fontSizeOverride}); // Trim to max size if (recentBooks.size() > MAX_RECENT_BOOKS) { @@ -80,6 +84,12 @@ RecentBook RecentBooksStore::getBookByPath(const std::string& path) const { bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t embeddedStyleOverride, const int8_t imageRenderingOverride) { + return setReaderOverrides(path, embeddedStyleOverride, imageRenderingOverride, -1, -1); +} + +bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t embeddedStyleOverride, + const int8_t imageRenderingOverride, const int8_t fontFamilyOverride, + const int8_t fontSizeOverride) { auto it = std::find_if(recentBooks.begin(), recentBooks.end(), [&](const RecentBook& book) { return book.path == path; }); if (it == recentBooks.end()) { @@ -88,6 +98,8 @@ bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t it->embeddedStyleOverride = embeddedStyleOverride; it->imageRenderingOverride = imageRenderingOverride; + it->fontFamilyOverride = fontFamilyOverride; + it->fontSizeOverride = fontSizeOverride; return saveToFile(); } diff --git a/src/RecentBooksStore.h b/src/RecentBooksStore.h index afc6c3de..6ca8f7a6 100644 --- a/src/RecentBooksStore.h +++ b/src/RecentBooksStore.h @@ -13,6 +13,10 @@ struct RecentBook { int8_t embeddedStyleOverride = -1; // -1 = use global setting, otherwise CrossPointSettings::IMAGE_RENDERING value. int8_t imageRenderingOverride = -1; + // -1 = use global setting, otherwise CrossPointSettings::FONT_FAMILY value. + int8_t fontFamilyOverride = -1; + // -1 = use global setting, otherwise CrossPointSettings::FONT_SIZE value. + int8_t fontSizeOverride = -1; bool operator==(const RecentBook& other) const { return path == other.path; } }; @@ -58,6 +62,8 @@ class RecentBooksStore { RecentBook getDataFromBook(std::string path) const; RecentBook getBookByPath(const std::string& path) const; bool setReaderOverrides(const std::string& path, int8_t embeddedStyleOverride, int8_t imageRenderingOverride); + bool setReaderOverrides(const std::string& path, int8_t embeddedStyleOverride, int8_t imageRenderingOverride, + int8_t fontFamilyOverride, int8_t fontSizeOverride); private: bool loadFromBinaryFile(); diff --git a/src/activities/ActivityResult.h b/src/activities/ActivityResult.h index c7c716fe..ae163500 100644 --- a/src/activities/ActivityResult.h +++ b/src/activities/ActivityResult.h @@ -24,6 +24,8 @@ struct MenuResult { uint8_t pageTurnOption = 0; int8_t embeddedStyleOverride = -1; int8_t imageRenderingOverride = -1; + int8_t fontFamilyOverride = -1; + int8_t fontSizeOverride = -1; uint8_t textDarkness = 1; }; diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index e2711c7b..8293f805 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -148,6 +148,8 @@ void EpubReaderActivity::onEnter() { const RecentBook currentBook = RECENT_BOOKS.getBookByPath(epub->getPath()); bookEmbeddedStyleOverride = currentBook.embeddedStyleOverride; bookImageRenderingOverride = currentBook.imageRenderingOverride; + bookFontFamilyOverride = currentBook.fontFamilyOverride; + bookFontSizeOverride = currentBook.fontSizeOverride; logReaderMemSnapshot("onEnter_after_recent_books"); // Trigger first update @@ -239,22 +241,23 @@ void EpubReaderActivity::loop() { const bool isCurrentPageStarred = section && bookmarkStore.has(static_cast(currentSpineIndex), static_cast(section->currentPage)); ReaderUtils::enforceExitFullRefresh(renderer); - startActivityForResult( - std::make_unique( - renderer, mappedInput, epub->getTitle(), currentPage, totalPages, bookProgressPercent, SETTINGS.orientation, - !currentPageFootnotes.empty(), bookEmbeddedStyleOverride, bookImageRenderingOverride, SETTINGS.textDarkness, - !bookmarkStore.isEmpty(), isCurrentPageStarred), - [this](const ActivityResult& result) { - // Always apply orientation/darkness change even if the menu was cancelled - const auto& menu = std::get(result.data); - applyOrientation(menu.orientation); - applyTextDarkness(menu.textDarkness); - toggleAutoPageTurn(menu.pageTurnOption); - applyBookReaderOverrides(menu.embeddedStyleOverride, menu.imageRenderingOverride); - if (!result.isCancelled) { - onReaderMenuConfirm(static_cast(menu.action)); - } - }); + startActivityForResult(std::make_unique( + renderer, mappedInput, epub->getTitle(), currentPage, totalPages, bookProgressPercent, + SETTINGS.orientation, !currentPageFootnotes.empty(), bookEmbeddedStyleOverride, + bookImageRenderingOverride, bookFontFamilyOverride, bookFontSizeOverride, + SETTINGS.textDarkness, !bookmarkStore.isEmpty(), isCurrentPageStarred), + [this](const ActivityResult& result) { + // Always apply orientation/darkness change even if the menu was cancelled + const auto& menu = std::get(result.data); + applyOrientation(menu.orientation); + applyTextDarkness(menu.textDarkness); + toggleAutoPageTurn(menu.pageTurnOption); + applyBookReaderOverrides(menu.embeddedStyleOverride, menu.imageRenderingOverride, + menu.fontFamilyOverride, menu.fontSizeOverride); + if (!result.isCancelled) { + onReaderMenuConfirm(static_cast(menu.action)); + } + }); } // Long press BACK (1s+) goes to home screen @@ -814,18 +817,23 @@ void EpubReaderActivity::toggleAutoPageTurn(const uint8_t selectedPageTurnOption } void EpubReaderActivity::applyBookReaderOverrides(const int8_t embeddedStyleOverride, - const int8_t imageRenderingOverride) { + const int8_t imageRenderingOverride, const int8_t fontFamilyOverride, + const int8_t fontSizeOverride) { if (!epub) { return; } - if (bookEmbeddedStyleOverride == embeddedStyleOverride && bookImageRenderingOverride == imageRenderingOverride) { + if (bookEmbeddedStyleOverride == embeddedStyleOverride && bookImageRenderingOverride == imageRenderingOverride && + bookFontFamilyOverride == fontFamilyOverride && bookFontSizeOverride == fontSizeOverride) { return; } bookEmbeddedStyleOverride = embeddedStyleOverride; bookImageRenderingOverride = imageRenderingOverride; - RECENT_BOOKS.setReaderOverrides(epub->getPath(), bookEmbeddedStyleOverride, bookImageRenderingOverride); + bookFontFamilyOverride = fontFamilyOverride; + bookFontSizeOverride = fontSizeOverride; + RECENT_BOOKS.setReaderOverrides(epub->getPath(), bookEmbeddedStyleOverride, bookImageRenderingOverride, + bookFontFamilyOverride, bookFontSizeOverride); RenderLock lock(*this); if (section) { @@ -850,6 +858,51 @@ uint8_t EpubReaderActivity::getEffectiveImageRendering() const { return SETTINGS.imageRendering; } +int EpubReaderActivity::getEffectiveReaderFontId() const { + const uint8_t fontFamily = + (bookFontFamilyOverride >= 0) ? static_cast(bookFontFamilyOverride) : SETTINGS.fontFamily; + const uint8_t fontSize = (bookFontSizeOverride >= 0) ? static_cast(bookFontSizeOverride) : SETTINGS.fontSize; + switch (fontFamily) { + case CrossPointSettings::NOTOSANS: + switch (fontSize) { + case CrossPointSettings::SMALL: + return NOTOSANS_12_FONT_ID; + case CrossPointSettings::MEDIUM: + default: + return NOTOSANS_14_FONT_ID; + case CrossPointSettings::LARGE: + return NOTOSANS_16_FONT_ID; + case CrossPointSettings::EXTRA_LARGE: + return NOTOSANS_18_FONT_ID; + } + case CrossPointSettings::OPENDYSLEXIC: + switch (fontSize) { + case CrossPointSettings::SMALL: + return OPENDYSLEXIC_8_FONT_ID; + case CrossPointSettings::MEDIUM: + default: + return OPENDYSLEXIC_10_FONT_ID; + case CrossPointSettings::LARGE: + return OPENDYSLEXIC_12_FONT_ID; + case CrossPointSettings::EXTRA_LARGE: + return OPENDYSLEXIC_14_FONT_ID; + } + case CrossPointSettings::BOOKERLY: + default: + switch (fontSize) { + case CrossPointSettings::SMALL: + return BOOKERLY_12_FONT_ID; + case CrossPointSettings::MEDIUM: + default: + return BOOKERLY_14_FONT_ID; + case CrossPointSettings::LARGE: + return BOOKERLY_16_FONT_ID; + case CrossPointSettings::EXTRA_LARGE: + return BOOKERLY_18_FONT_ID; + } + } +} + void EpubReaderActivity::pageTurn(bool isForwardTurn) { if (isForwardTurn) { if (section->currentPage < section->pageCount - 1) { @@ -934,7 +987,7 @@ void EpubReaderActivity::render(RenderLock&& lock) { LOG_DBG("ERS", "Loading file: %s, index: %d", filepath.c_str(), currentSpineIndex); section = std::make_unique
(epub, currentSpineIndex, renderer); - if (!section->loadSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(), + if (!section->loadSectionFile(getEffectiveReaderFontId(), SETTINGS.getReaderLineCompression(), SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, imageRendering)) { LOG_DBG("ERS", "Cache not found, building..."); @@ -947,7 +1000,7 @@ void EpubReaderActivity::render(RenderLock&& lock) { GUI.fillPopupProgress(renderer, popupRect, progress); }; - if (!section->createSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(), + if (!section->createSectionFile(getEffectiveReaderFontId(), SETTINGS.getReaderLineCompression(), SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, imageRendering, progressFn)) { @@ -1090,14 +1143,14 @@ void EpubReaderActivity::silentIndexNextChapterIfNeeded(const uint16_t viewportW const uint8_t imageRendering = getEffectiveImageRendering(); Section nextSection(epub, nextSpineIndex, renderer); - if (nextSection.loadSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(), + if (nextSection.loadSectionFile(getEffectiveReaderFontId(), SETTINGS.getReaderLineCompression(), SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, imageRendering)) { return; } LOG_DBG("ERS", "Silently indexing next chapter: %d", nextSpineIndex); - if (!nextSection.createSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(), + if (!nextSection.createSectionFile(getEffectiveReaderFontId(), SETTINGS.getReaderLineCompression(), SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, imageRendering)) { LOG_ERR("ERS", "Failed silent indexing for chapter: %d", nextSpineIndex); @@ -1133,7 +1186,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or // Font prewarm: scan pass accumulates text, then prewarm, then real render const uint32_t heapBefore = esp_get_free_heap_size(); auto scope = fcm->createPrewarmScope(); - page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop); // scan pass + page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, orientedMarginTop); // scan pass scope.endScanAndPrewarm(); const uint32_t heapAfter = esp_get_free_heap_size(); fcm->logStats("prewarm"); @@ -1149,7 +1202,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or pendingHalfRefreshAfterImagePage = false; logReaderMemSnapshot("before_bw_render"); - page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop); + page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, orientedMarginTop); renderStatusBar(); fcm->logStats("bw_render"); const auto tBwRender = millis(); @@ -1168,7 +1221,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or // Re-render page content to restore images into the blanked area // Status bar is not re-rendered here to avoid reading stale dynamic values (e.g. battery %) - page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop); + page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, orientedMarginTop); renderer.displayBuffer(HalDisplay::FAST_REFRESH); } else { renderer.displayBuffer(HalDisplay::HALF_REFRESH); @@ -1201,7 +1254,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or logReaderMemSnapshot("gray_lsb_begin"); renderer.clearScreen(0x00); renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB); - page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop); + page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, orientedMarginTop); renderer.copyGrayscaleLsbBuffers(); const auto tGrayLsb = millis(); logReaderMemSnapshot("gray_lsb_end"); @@ -1210,7 +1263,7 @@ void EpubReaderActivity::renderContents(std::unique_ptr page, const int or logReaderMemSnapshot("gray_msb_begin"); renderer.clearScreen(0x00); renderer.setRenderMode(GfxRenderer::GRAYSCALE_MSB); - page->render(renderer, SETTINGS.getReaderFontId(), orientedMarginLeft, orientedMarginTop); + page->render(renderer, getEffectiveReaderFontId(), orientedMarginLeft, orientedMarginTop); renderer.copyGrayscaleMsbBuffers(); const auto tGrayMsb = millis(); logReaderMemSnapshot("gray_msb_end"); @@ -1385,16 +1438,63 @@ bool EpubReaderActivity::drawCurrentPageToBuffer(const std::string& filePath, Gf // Load or rebuild the section cache. Rebuilding is needed when the cache is missing or stale // (e.g. after a firmware update). A no-op popup callback avoids any UI during sleep preparation. + const RecentBook currentBook = RECENT_BOOKS.getBookByPath(filePath); + const uint8_t effectiveFontFamily = + currentBook.fontFamilyOverride >= 0 ? static_cast(currentBook.fontFamilyOverride) : SETTINGS.fontFamily; + const uint8_t effectiveFontSize = + currentBook.fontSizeOverride >= 0 ? static_cast(currentBook.fontSizeOverride) : SETTINGS.fontSize; + auto getEffectiveFontId = [&](uint8_t family, uint8_t size) { + switch (family) { + case CrossPointSettings::NOTOSANS: + switch (size) { + case CrossPointSettings::SMALL: + return NOTOSANS_12_FONT_ID; + case CrossPointSettings::LARGE: + return NOTOSANS_16_FONT_ID; + case CrossPointSettings::EXTRA_LARGE: + return NOTOSANS_18_FONT_ID; + case CrossPointSettings::MEDIUM: + default: + return NOTOSANS_14_FONT_ID; + } + case CrossPointSettings::OPENDYSLEXIC: + switch (size) { + case CrossPointSettings::SMALL: + return OPENDYSLEXIC_8_FONT_ID; + case CrossPointSettings::LARGE: + return OPENDYSLEXIC_12_FONT_ID; + case CrossPointSettings::EXTRA_LARGE: + return OPENDYSLEXIC_14_FONT_ID; + case CrossPointSettings::MEDIUM: + default: + return OPENDYSLEXIC_10_FONT_ID; + } + case CrossPointSettings::BOOKERLY: + default: + switch (size) { + case CrossPointSettings::SMALL: + return BOOKERLY_12_FONT_ID; + case CrossPointSettings::LARGE: + return BOOKERLY_16_FONT_ID; + case CrossPointSettings::EXTRA_LARGE: + return BOOKERLY_18_FONT_ID; + case CrossPointSettings::MEDIUM: + default: + return BOOKERLY_14_FONT_ID; + } + } + }; + auto section = std::make_unique
(epub, spineIndex, renderer); - if (!section->loadSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(), - SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, - viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle, - SETTINGS.imageRendering)) { + if (!section->loadSectionFile(getEffectiveFontId(effectiveFontFamily, effectiveFontSize), + SETTINGS.getReaderLineCompression(), SETTINGS.extraParagraphSpacing, + SETTINGS.paragraphAlignment, viewportWidth, viewportHeight, SETTINGS.hyphenationEnabled, + SETTINGS.embeddedStyle, SETTINGS.imageRendering)) { LOG_DBG("SLP", "EPUB: section cache not found for spine %d, rebuilding", spineIndex); - if (!section->createSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(), - SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, - viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle, - SETTINGS.imageRendering)) { + if (!section->createSectionFile(getEffectiveFontId(effectiveFontFamily, effectiveFontSize), + SETTINGS.getReaderLineCompression(), SETTINGS.extraParagraphSpacing, + SETTINGS.paragraphAlignment, viewportWidth, viewportHeight, + SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle, SETTINGS.imageRendering)) { LOG_ERR("SLP", "EPUB: failed to rebuild section cache for spine %d", spineIndex); return false; } @@ -1410,7 +1510,7 @@ bool EpubReaderActivity::drawCurrentPageToBuffer(const std::string& filePath, Gf } renderer.clearScreen(); - page->render(renderer, SETTINGS.getReaderFontId(), marginLeft, marginTop); + page->render(renderer, getEffectiveFontId(effectiveFontFamily, effectiveFontSize), marginLeft, marginTop); // No displayBuffer call — caller (SleepActivity) handles that after compositing the overlay return true; } diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index 8fa2d167..8a792860 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -53,6 +53,8 @@ class EpubReaderActivity final : public Activity { // -1 means use global SETTINGS value. int8_t bookEmbeddedStyleOverride = -1; int8_t bookImageRenderingOverride = -1; + int8_t bookFontFamilyOverride = -1; + int8_t bookFontSizeOverride = -1; // Bookmarks (starred pages) BookmarkStore bookmarkStore; @@ -88,9 +90,11 @@ class EpubReaderActivity final : public Activity { void applyOrientation(uint8_t orientation); void applyTextDarkness(uint8_t textDarkness); void toggleAutoPageTurn(uint8_t selectedPageTurnOption); - void applyBookReaderOverrides(int8_t embeddedStyleOverride, int8_t imageRenderingOverride); + void applyBookReaderOverrides(int8_t embeddedStyleOverride, int8_t imageRenderingOverride, int8_t fontFamilyOverride, + int8_t fontSizeOverride); bool getEffectiveEmbeddedStyle() const; uint8_t getEffectiveImageRendering() const; + int getEffectiveReaderFontId() const; void pageTurn(bool isForwardTurn); // Footnote navigation diff --git a/src/activities/reader/EpubReaderMenuActivity.cpp b/src/activities/reader/EpubReaderMenuActivity.cpp index 7c1d3091..93e08a6f 100644 --- a/src/activities/reader/EpubReaderMenuActivity.cpp +++ b/src/activities/reader/EpubReaderMenuActivity.cpp @@ -13,13 +13,16 @@ EpubReaderMenuActivity::EpubReaderMenuActivity(GfxRenderer& renderer, MappedInpu const int bookProgressPercent, const uint8_t currentOrientation, const bool hasFootnotes, const int8_t initialEmbeddedStyleOverride, const int8_t initialImageRenderingOverride, - const uint8_t initialTextDarkness, const bool hasStarredPages, - const bool isCurrentPageStarred) + const int8_t initialFontFamilyOverride, + const int8_t initialFontSizeOverride, const uint8_t initialTextDarkness, + const bool hasStarredPages, const bool isCurrentPageStarred) : MenuListActivity("EpubReaderMenu", renderer, mappedInput), currentPageStarred(isCurrentPageStarred), pendingOrientation(currentOrientation), pendingEmbeddedStyleOverride(initialEmbeddedStyleOverride), pendingImageRenderingOverride(initialImageRenderingOverride), + pendingFontFamilyOverride(initialFontFamilyOverride), + pendingFontSizeOverride(initialFontSizeOverride), pendingTextDarkness(initialTextDarkness), title(title), currentPage(currentPage), @@ -51,50 +54,6 @@ void EpubReaderMenuActivity::buildMenuItems(bool hasFootnotes, bool hasStarredPa menuItems.push_back(SettingInfo::Separator(StrId::STR_READER_APPEARANCE)); auto* self = this; - - // Embedded style: cycles default(-1) -> ON(1) -> OFF(0) via DynamicEnum indices 0/1/2 - menuItems.push_back(SettingInfo::DynamicEnumCtx( - StrId::STR_EMBEDDED_STYLE, {StrId::STR_DEFAULT_VALUE, StrId::STR_STATE_ON, StrId::STR_STATE_OFF}, self, - [](const void* ctx) -> uint8_t { - const auto* s = static_cast(ctx); - if (s->pendingEmbeddedStyleOverride < 0) return 0; - if (s->pendingEmbeddedStyleOverride > 0) return 1; - return 2; - }, - [](void* ctx, uint8_t v) { - auto* s = static_cast(ctx); - if (v == 0) - s->pendingEmbeddedStyleOverride = -1; - else if (v == 1) - s->pendingEmbeddedStyleOverride = 1; - else - s->pendingEmbeddedStyleOverride = 0; - })); - - // Image rendering: cycles default(-1) -> display(0) -> placeholder(1) -> suppress(2) - menuItems.push_back(SettingInfo::DynamicEnumCtx( - StrId::STR_IMAGES, - {StrId::STR_DEFAULT_VALUE, StrId::STR_IMAGES_DISPLAY, StrId::STR_IMAGES_PLACEHOLDER, StrId::STR_IMAGES_SUPPRESS}, - self, - [](const void* ctx) -> uint8_t { - const auto* s = static_cast(ctx); - return (s->pendingImageRenderingOverride < 0) ? 0 : (s->pendingImageRenderingOverride + 1); - }, - [](void* ctx, uint8_t v) { - auto* s = static_cast(ctx); - s->pendingImageRenderingOverride = (v == 0) ? -1 : static_cast(v - 1); - })); - - // Text darkness: straightforward 0-3 cycle - menuItems.push_back(SettingInfo::DynamicEnumCtx( - StrId::STR_TEXT_DARKNESS, {StrId::STR_NORMAL, StrId::STR_DARK, StrId::STR_EXTRA_DARK, StrId::STR_MAX_DARK}, self, - [](const void* ctx) -> uint8_t { return static_cast(ctx)->pendingTextDarkness; }, - [](void* ctx, uint8_t v) { static_cast(ctx)->pendingTextDarkness = v; })); - - // Helper functions, reading ruler, auto page turn, orientation - menuItems.push_back(SettingInfo::Separator(StrId::STR_READER_UTILS)); - // Auto page turn: ACTION type with custom cycling in onActionSelected - menuItems.push_back(SettingInfo::Action(StrId::STR_AUTO_TURN_PAGES_PER_MIN, SettingAction::None)); // Orientation: straightforward 0-3 cycle menuItems.push_back(SettingInfo::DynamicEnumCtx( StrId::STR_ORIENTATION, @@ -102,6 +61,89 @@ void EpubReaderMenuActivity::buildMenuItems(bool hasFootnotes, bool hasStarredPa [](const void* ctx) -> uint8_t { return static_cast(ctx)->pendingOrientation; }, [](void* ctx, uint8_t v) { static_cast(ctx)->pendingOrientation = v; })); + // Embedded style: cycles default(-1) -> ON(1) -> OFF(0) via DynamicEnum indices 0/1/2 + menuItems.push_back(SettingInfo::DynamicEnumCtx( + StrId::STR_EMBEDDED_STYLE, + {StrId::STR_DEFAULT_VALUE, StrId::STR_STATE_ON, StrId::STR_STATE_OFF}, self, + [](const void* ctx) -> uint8_t { + const auto* s = static_cast(ctx); + if (s->pendingEmbeddedStyleOverride < 0) return 0; + if (s->pendingEmbeddedStyleOverride > 0) return 1; + return 2; + }, + [](void* ctx, uint8_t v) { + auto* s = static_cast(ctx); + if (v == 0) + s->pendingEmbeddedStyleOverride = -1; + else if (v == 1) + s->pendingEmbeddedStyleOverride = 1; + else + s->pendingEmbeddedStyleOverride = 0; + }) + .withSubmenu(StrId::STR_READER_OVERRIDES)); + + // Image rendering: cycles default(-1) -> display(0) -> placeholder(1) -> suppress(2) + menuItems.push_back(SettingInfo::DynamicEnumCtx( + StrId::STR_IMAGES, + {StrId::STR_DEFAULT_VALUE, StrId::STR_IMAGES_DISPLAY, StrId::STR_IMAGES_PLACEHOLDER, + StrId::STR_IMAGES_SUPPRESS}, + self, + [](const void* ctx) -> uint8_t { + const auto* s = static_cast(ctx); + return (s->pendingImageRenderingOverride < 0) ? 0 : (s->pendingImageRenderingOverride + 1); + }, + [](void* ctx, uint8_t v) { + auto* s = static_cast(ctx); + s->pendingImageRenderingOverride = (v == 0) ? -1 : static_cast(v - 1); + }) + .withSubmenu(StrId::STR_READER_OVERRIDES)); + + // Reader font family: cycles default(-1) -> Bookerly(0) -> Noto Sans(1) -> Open Dyslexic(2) + menuItems.push_back( + SettingInfo::DynamicEnumCtx( + StrId::STR_FONT_FAMILY, + {StrId::STR_DEFAULT_VALUE, StrId::STR_BOOKERLY, StrId::STR_NOTO_SANS, StrId::STR_OPEN_DYSLEXIC}, self, + [](const void* ctx) -> uint8_t { + const auto* s = static_cast(ctx); + return (s->pendingFontFamilyOverride < 0) ? 0 : static_cast(s->pendingFontFamilyOverride + 1); + }, + [](void* ctx, uint8_t v) { + auto* s = static_cast(ctx); + s->pendingFontFamilyOverride = (v == 0) ? -1 : static_cast(v - 1); + }) + .withSubmenu(StrId::STR_READER_OVERRIDES)); + + // Reader font size: cycles default(-1) -> Small(0) -> Medium(1) -> Large(2) -> X Large(3) + menuItems.push_back( + SettingInfo::DynamicEnumCtx( + StrId::STR_FONT_SIZE, + {StrId::STR_DEFAULT_VALUE, StrId::STR_SMALL, StrId::STR_MEDIUM, StrId::STR_LARGE, StrId::STR_X_LARGE}, self, + [](const void* ctx) -> uint8_t { + const auto* s = static_cast(ctx); + return (s->pendingFontSizeOverride < 0) ? 0 : static_cast(s->pendingFontSizeOverride + 1); + }, + [](void* ctx, uint8_t v) { + auto* s = static_cast(ctx); + s->pendingFontSizeOverride = (v == 0) ? -1 : static_cast(v - 1); + }) + .withSubmenu(StrId::STR_READER_OVERRIDES)); + + // Text darkness: straightforward 0-3 cycle + menuItems.push_back( + SettingInfo::DynamicEnumCtx( + StrId::STR_TEXT_DARKNESS, {StrId::STR_NORMAL, StrId::STR_DARK, StrId::STR_EXTRA_DARK, StrId::STR_MAX_DARK}, + self, + [](const void* ctx) -> uint8_t { + return static_cast(ctx)->pendingTextDarkness; + }, + [](void* ctx, uint8_t v) { static_cast(ctx)->pendingTextDarkness = v; }) + .withSubmenu(StrId::STR_READER_OVERRIDES)); + + // Helper functions, reading ruler, auto page turn, orientation + menuItems.push_back(SettingInfo::Separator(StrId::STR_READER_UTILS)); + // Auto page turn: ACTION type with custom cycling in onActionSelected + menuItems.push_back(SettingInfo::Action(StrId::STR_AUTO_TURN_PAGES_PER_MIN, SettingAction::None)); + // --- Synchronisation (only if credentials are set) --- if (KOREADER_STORE.hasCredentials()) { menuItems.push_back(SettingInfo::Separator(StrId::STR_KOREADER_SYNC)); @@ -158,7 +200,8 @@ EpubReaderMenuActivity::MenuAction EpubReaderMenuActivity::actionForNameId(StrId void EpubReaderMenuActivity::finishWithAction(MenuAction action) { setResult(MenuResult{static_cast(action), pendingOrientation, selectedPageTurnOption, - pendingEmbeddedStyleOverride, pendingImageRenderingOverride, pendingTextDarkness}); + pendingEmbeddedStyleOverride, pendingImageRenderingOverride, pendingFontFamilyOverride, + pendingFontSizeOverride, pendingTextDarkness}); finish(); } @@ -188,6 +231,8 @@ void EpubReaderMenuActivity::onBackPressed() { selectedPageTurnOption, pendingEmbeddedStyleOverride, pendingImageRenderingOverride, + pendingFontFamilyOverride, + pendingFontSizeOverride, pendingTextDarkness}; setResult(std::move(result)); finish(); diff --git a/src/activities/reader/EpubReaderMenuActivity.h b/src/activities/reader/EpubReaderMenuActivity.h index d5fc72c9..b052714f 100644 --- a/src/activities/reader/EpubReaderMenuActivity.h +++ b/src/activities/reader/EpubReaderMenuActivity.h @@ -35,6 +35,7 @@ class EpubReaderMenuActivity final : public MenuListActivity { const int currentPage, const int totalPages, const int bookProgressPercent, const uint8_t currentOrientation, const bool hasFootnotes, const int8_t initialEmbeddedStyleOverride, const int8_t initialImageRenderingOverride, + const int8_t initialFontFamilyOverride, const int8_t initialFontSizeOverride, const uint8_t initialTextDarkness, const bool hasStarredPages, const bool isCurrentPageStarred); @@ -61,6 +62,8 @@ class EpubReaderMenuActivity final : public MenuListActivity { uint8_t selectedPageTurnOption = 0; int8_t pendingEmbeddedStyleOverride = -1; int8_t pendingImageRenderingOverride = -1; + int8_t pendingFontFamilyOverride = -1; + int8_t pendingFontSizeOverride = -1; uint8_t pendingTextDarkness = 1; static constexpr const char* pageTurnLabels[] = {"", "1", "3", "6", "12"};