diff --git a/src/JsonSettingsIO.cpp b/src/JsonSettingsIO.cpp index e7ceb177..76bdacf3 100644 --- a/src/JsonSettingsIO.cpp +++ b/src/JsonSettingsIO.cpp @@ -473,6 +473,7 @@ bool JsonSettingsIO::saveRecentBooks(const RecentBooksStore& store, const char* } obj["fontSizeOverride"] = book.fontSizeOverride; obj["bionicReadingOverride"] = book.bionicReadingOverride; + obj["paragraphAlignmentOverride"] = book.paragraphAlignmentOverride; } String json; @@ -516,6 +517,8 @@ bool JsonSettingsIO::loadRecentBooks(RecentBooksStore& store, const char* json) } book.fontSizeOverride = clampInt8(obj["fontSizeOverride"] | -1, -1, CrossPointSettings::FONT_SIZE_COUNT - 1, -1); book.bionicReadingOverride = clampInt8(obj["bionicReadingOverride"] | -1, -1, 1, -1); + book.paragraphAlignmentOverride = + clampInt8(obj["paragraphAlignmentOverride"] | -1, -1, CrossPointSettings::PARAGRAPH_ALIGNMENT_COUNT - 1, -1); store.recentBooks.push_back(book); } diff --git a/src/RecentBooksStore.cpp b/src/RecentBooksStore.cpp index a4c069d5..b660a3ce 100644 --- a/src/RecentBooksStore.cpp +++ b/src/RecentBooksStore.cpp @@ -28,6 +28,7 @@ void RecentBooksStore::addBook(const std::string& path, const std::string& title std::string sdFontFamilyOverride; int8_t fontSizeOverride = -1; int8_t bionicReadingOverride = -1; + int8_t paragraphAlignmentOverride = -1; // Remove existing entry if present auto it = @@ -39,13 +40,14 @@ void RecentBooksStore::addBook(const std::string& path, const std::string& title sdFontFamilyOverride = it->sdFontFamilyOverride; fontSizeOverride = it->fontSizeOverride; bionicReadingOverride = it->bionicReadingOverride; + paragraphAlignmentOverride = it->paragraphAlignmentOverride; recentBooks.erase(it); } // Add to front - recentBooks.insert(recentBooks.begin(), - {path, title, author, series, coverBmpPath, embeddedStyleOverride, imageRenderingOverride, - fontFamilyOverride, sdFontFamilyOverride, fontSizeOverride, bionicReadingOverride}); + recentBooks.insert(recentBooks.begin(), {path, title, author, series, coverBmpPath, embeddedStyleOverride, + imageRenderingOverride, fontFamilyOverride, sdFontFamilyOverride, + fontSizeOverride, bionicReadingOverride, paragraphAlignmentOverride}); // Trim to max size if (recentBooks.size() > MAX_RECENT_BOOKS) { @@ -95,7 +97,8 @@ bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t return false; } return setReaderOverrides(path, embeddedStyleOverride, imageRenderingOverride, it->fontFamilyOverride, - it->sdFontFamilyOverride, it->fontSizeOverride, it->bionicReadingOverride); + it->sdFontFamilyOverride, it->fontSizeOverride, it->bionicReadingOverride, + it->paragraphAlignmentOverride); } bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t embeddedStyleOverride, @@ -108,7 +111,7 @@ bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t } const std::string sdOverride = (fontFamilyOverride >= 0) ? std::string() : it->sdFontFamilyOverride; return setReaderOverrides(path, embeddedStyleOverride, imageRenderingOverride, fontFamilyOverride, sdOverride, - fontSizeOverride, it->bionicReadingOverride); + fontSizeOverride, it->bionicReadingOverride, it->paragraphAlignmentOverride); } bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t embeddedStyleOverride, @@ -120,7 +123,8 @@ bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t return false; } return setReaderOverrides(path, embeddedStyleOverride, imageRenderingOverride, fontFamilyOverride, - sdFontFamilyOverride, fontSizeOverride, it->bionicReadingOverride); + sdFontFamilyOverride, fontSizeOverride, it->bionicReadingOverride, + it->paragraphAlignmentOverride); } bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t embeddedStyleOverride, @@ -131,7 +135,8 @@ bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t return false; } return setReaderOverrides(path, embeddedStyleOverride, imageRenderingOverride, it->fontFamilyOverride, - it->sdFontFamilyOverride, it->fontSizeOverride, bionicReadingOverride); + it->sdFontFamilyOverride, it->fontSizeOverride, bionicReadingOverride, + it->paragraphAlignmentOverride); } bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t embeddedStyleOverride, @@ -144,7 +149,7 @@ bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t } const std::string sdOverride = (fontFamilyOverride >= 0) ? std::string() : it->sdFontFamilyOverride; return setReaderOverrides(path, embeddedStyleOverride, imageRenderingOverride, fontFamilyOverride, sdOverride, - fontSizeOverride, bionicReadingOverride); + fontSizeOverride, bionicReadingOverride, it->paragraphAlignmentOverride); } bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t embeddedStyleOverride, @@ -156,6 +161,20 @@ bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t if (it == recentBooks.end()) { return false; } + return setReaderOverrides(path, embeddedStyleOverride, imageRenderingOverride, fontFamilyOverride, + sdFontFamilyOverride, fontSizeOverride, bionicReadingOverride, + it->paragraphAlignmentOverride); +} + +bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t embeddedStyleOverride, + const int8_t imageRenderingOverride, const int8_t fontFamilyOverride, + const std::string& sdFontFamilyOverride, const int8_t fontSizeOverride, + const bool bionicReadingOverride, const int8_t paragraphAlignmentOverride) { + auto it = + std::find_if(recentBooks.begin(), recentBooks.end(), [&](const RecentBook& book) { return book.path == path; }); + if (it == recentBooks.end()) { + return false; + } it->embeddedStyleOverride = embeddedStyleOverride; it->imageRenderingOverride = imageRenderingOverride; @@ -163,6 +182,7 @@ bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t it->sdFontFamilyOverride = sdFontFamilyOverride; it->fontSizeOverride = fontSizeOverride; it->bionicReadingOverride = bionicReadingOverride; + it->paragraphAlignmentOverride = paragraphAlignmentOverride; return saveToFile(); } diff --git a/src/RecentBooksStore.h b/src/RecentBooksStore.h index 6940006a..2df63c25 100644 --- a/src/RecentBooksStore.h +++ b/src/RecentBooksStore.h @@ -21,6 +21,8 @@ struct RecentBook { int8_t fontSizeOverride = -1; // -1 = use global default, otherwise explicit per-book override (0 = off, 1 = on). int8_t bionicReadingOverride = -1; + // -1 = use global setting, otherwise CrossPointSettings::PARAGRAPH_ALIGNMENT value. + int8_t paragraphAlignmentOverride = -1; bool operator==(const RecentBook& other) const { return path == other.path; } }; @@ -77,6 +79,9 @@ class RecentBooksStore { bool setReaderOverrides(const std::string& path, int8_t embeddedStyleOverride, int8_t imageRenderingOverride, int8_t fontFamilyOverride, const std::string& sdFontFamilyOverride, int8_t fontSizeOverride, bool bionicReadingOverride); + bool setReaderOverrides(const std::string& path, int8_t embeddedStyleOverride, int8_t imageRenderingOverride, + int8_t fontFamilyOverride, const std::string& sdFontFamilyOverride, int8_t fontSizeOverride, + bool bionicReadingOverride, int8_t paragraphAlignmentOverride); private: bool loadFromBinaryFile(); diff --git a/src/activities/ActivityResult.h b/src/activities/ActivityResult.h index 4c6b2403..25e4a41e 100644 --- a/src/activities/ActivityResult.h +++ b/src/activities/ActivityResult.h @@ -30,6 +30,7 @@ struct MenuResult { int8_t fontSizeOverride = -1; uint8_t textDarkness = 1; uint8_t bionicReadingOverride = 0; + int8_t paragraphAlignmentOverride = -1; }; struct ChapterResult { diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index a1acffc7..af332c66 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -321,6 +321,7 @@ void EpubReaderActivity::onEnter() { bookBionicReadingOverride = (currentBook.bionicReadingOverride >= 0) ? static_cast(currentBook.bionicReadingOverride) : static_cast(SETTINGS.bionicReading); + bookParagraphAlignmentOverride = currentBook.paragraphAlignmentOverride; logReaderMemSnapshot("onEnter_after_recent_books"); // Trigger first update @@ -1291,7 +1292,8 @@ void EpubReaderActivity::toggleAutoPageTurn(const uint8_t selectedPageTurnOption void EpubReaderActivity::applyBookReaderOverrides(const int8_t embeddedStyleOverride, const int8_t imageRenderingOverride, const int8_t fontFamilyOverride, const std::string& sdFontFamilyOverride, - const int8_t fontSizeOverride, const bool bionicReadingOverride) { + const int8_t fontSizeOverride, const bool bionicReadingOverride, + const int8_t paragraphAlignmentOverride) { if (!epub) { return; } @@ -1308,7 +1310,8 @@ void EpubReaderActivity::applyBookReaderOverrides(const int8_t embeddedStyleOver if (bookEmbeddedStyleOverride == embeddedStyleOverride && bookImageRenderingOverride == imageRenderingOverride && bookFontFamilyOverride == normalizedFontFamilyOverride && bookSdFontFamilyOverride == normalizedSdFontFamilyOverride && bookFontSizeOverride == fontSizeOverride && - bookBionicReadingOverride == bionicReadingOverride) { + bookBionicReadingOverride == bionicReadingOverride && + bookParagraphAlignmentOverride == paragraphAlignmentOverride) { return; } @@ -1318,9 +1321,10 @@ void EpubReaderActivity::applyBookReaderOverrides(const int8_t embeddedStyleOver bookSdFontFamilyOverride = normalizedSdFontFamilyOverride; bookFontSizeOverride = fontSizeOverride; bookBionicReadingOverride = bionicReadingOverride; + bookParagraphAlignmentOverride = paragraphAlignmentOverride; RECENT_BOOKS.setReaderOverrides(epub->getPath(), bookEmbeddedStyleOverride, bookImageRenderingOverride, bookFontFamilyOverride, bookSdFontFamilyOverride, bookFontSizeOverride, - bookBionicReadingOverride); + bookBionicReadingOverride, bookParagraphAlignmentOverride); RenderLock lock(*this); if (section) { @@ -1345,6 +1349,13 @@ uint8_t EpubReaderActivity::getEffectiveImageRendering() const { return SETTINGS.imageRendering; } +uint8_t EpubReaderActivity::getEffectiveParagraphAlignment() const { + if (bookParagraphAlignmentOverride >= 0) { + return static_cast(bookParagraphAlignmentOverride); + } + return SETTINGS.paragraphAlignment; +} + float EpubReaderActivity::getEffectiveReaderLineCompression() const { const uint8_t fontSize = (bookFontSizeOverride >= 0) ? static_cast(bookFontSizeOverride) : SETTINGS.fontSize; const int effectiveFontId = getEffectiveReaderFontId(); @@ -1564,7 +1575,7 @@ void EpubReaderActivity::render(RenderLock&& lock) { const unsigned long sectionStart = millis(); if (!section->loadSectionFile(getEffectiveReaderFontId(), getEffectiveReaderLineCompression(), - SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, + SETTINGS.extraParagraphSpacing, getEffectiveParagraphAlignment(), viewportWidth, viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, bookBionicReadingOverride, imageRendering)) { LOG_DBG("ERS", "Cache not found, building..."); @@ -1586,7 +1597,7 @@ void EpubReaderActivity::render(RenderLock&& lock) { // by MAX_PAGE_GLYPHS per style. renderer.clearSdCardFontAccumulation(); if (!section->createSectionFile(getEffectiveReaderFontId(), getEffectiveReaderLineCompression(), - SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, + SETTINGS.extraParagraphSpacing, getEffectiveParagraphAlignment(), viewportWidth, viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, bookBionicReadingOverride, imageRendering, progressFn)) { LOG_ERR("ERS", "Failed to persist page data to SD"); @@ -2249,7 +2260,7 @@ void EpubReaderActivity::openReaderMenu() { renderer, mappedInput, epub->getTitle(), currentPage, totalPages, bookProgressPercent, SETTINGS.orientation, !currentPageFootnotes.empty(), bookEmbeddedStyleOverride, bookImageRenderingOverride, bookFontFamilyOverride, bookSdFontFamilyOverride, bookFontSizeOverride, SETTINGS.textDarkness, bookBionicReadingOverride, - !bookmarkStore.isEmpty(), isCurrentPageStarred), + bookParagraphAlignmentOverride, !bookmarkStore.isEmpty(), isCurrentPageStarred), [this](const ActivityResult& result) { const auto& menu = std::get(result.data); applyOrientation(menu.orientation); @@ -2257,7 +2268,7 @@ void EpubReaderActivity::openReaderMenu() { toggleAutoPageTurn(menu.pageTurnOption); applyBookReaderOverrides(menu.embeddedStyleOverride, menu.imageRenderingOverride, menu.fontFamilyOverride, menu.sdFontFamilyOverride, menu.fontSizeOverride, - static_cast(menu.bionicReadingOverride)); + static_cast(menu.bionicReadingOverride), menu.paragraphAlignmentOverride); if (!result.isCancelled) { onReaderMenuConfirm(static_cast(menu.action)); } @@ -2389,7 +2400,8 @@ void EpubReaderActivity::onButtonAction(const CrossPointSettings::BUTTON_ACTION case BA::BTN_TOGGLE_BIONIC_READING: if (epub) { applyBookReaderOverrides(bookEmbeddedStyleOverride, bookImageRenderingOverride, bookFontFamilyOverride, - bookSdFontFamilyOverride, bookFontSizeOverride, !bookBionicReadingOverride); + bookSdFontFamilyOverride, bookFontSizeOverride, !bookBionicReadingOverride, + bookParagraphAlignmentOverride); requestUpdate(); } break; @@ -2399,7 +2411,8 @@ void EpubReaderActivity::onButtonAction(const CrossPointSettings::BUTTON_ACTION (bookFontSizeOverride >= 0) ? static_cast(bookFontSizeOverride) : SETTINGS.fontSize; const int8_t next = static_cast((current + 1) % CrossPointSettings::FONT_SIZE_COUNT); applyBookReaderOverrides(bookEmbeddedStyleOverride, bookImageRenderingOverride, bookFontFamilyOverride, - bookSdFontFamilyOverride, next, bookBionicReadingOverride); + bookSdFontFamilyOverride, next, bookBionicReadingOverride, + bookParagraphAlignmentOverride); requestUpdate(); } break; diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index 40f8604e..9d8747f1 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -149,6 +149,7 @@ class EpubReaderActivity final : public Activity { std::string bookSdFontFamilyOverride; int8_t bookFontSizeOverride = -1; bool bookBionicReadingOverride = false; + int8_t bookParagraphAlignmentOverride = -1; // Bookmarks (starred pages) BookmarkStore bookmarkStore; @@ -192,10 +193,11 @@ class EpubReaderActivity final : public Activity { void stopAutomaticPageTurn(); void applyBookReaderOverrides(int8_t embeddedStyleOverride, int8_t imageRenderingOverride, int8_t fontFamilyOverride, const std::string& sdFontFamilyOverride, int8_t fontSizeOverride, - bool bionicReadingOverride); + bool bionicReadingOverride, int8_t paragraphAlignmentOverride); void openReaderMenu(); bool getEffectiveEmbeddedStyle() const; uint8_t getEffectiveImageRendering() const; + uint8_t getEffectiveParagraphAlignment() const; int getEffectiveReaderFontId() const; float getEffectiveReaderLineCompression() const; bool stepPageState(bool isForwardTurn); diff --git a/src/activities/reader/EpubReaderMenuActivity.cpp b/src/activities/reader/EpubReaderMenuActivity.cpp index 1f315db4..7633e66f 100644 --- a/src/activities/reader/EpubReaderMenuActivity.cpp +++ b/src/activities/reader/EpubReaderMenuActivity.cpp @@ -40,7 +40,7 @@ EpubReaderMenuActivity::EpubReaderMenuActivity( const int8_t initialEmbeddedStyleOverride, const int8_t initialImageRenderingOverride, const int8_t initialFontFamilyOverride, const std::string& initialSdFontFamilyOverride, const int8_t initialFontSizeOverride, const uint8_t initialTextDarkness, const bool initialBionicReadingOverride, - const bool hasStarredPages, const bool isCurrentPageStarred) + const int8_t initialParagraphAlignmentOverride, const bool hasStarredPages, const bool isCurrentPageStarred) : MenuListActivity("EpubReaderMenu", renderer, mappedInput), currentPageStarred(isCurrentPageStarred), pendingOrientation(currentOrientation), @@ -51,6 +51,7 @@ EpubReaderMenuActivity::EpubReaderMenuActivity( pendingFontSizeOverride(initialFontSizeOverride), pendingTextDarkness(initialTextDarkness), pendingBionicReading(initialBionicReadingOverride), + pendingParagraphAlignmentOverride(initialParagraphAlignmentOverride), title(title), currentPage(currentPage), totalPages(totalPages), @@ -219,6 +220,24 @@ void EpubReaderMenuActivity::buildMenuItems(bool hasFootnotes, bool hasStarredPa [](void* ctx, uint8_t v) { static_cast(ctx)->pendingBionicReading = (v != 0); }) .withSubmenu(StrId::STR_READER_OVERRIDES)); + // Paragraph alignment: default(-1) + the 5 global options + menuItems.push_back(SettingInfo::DynamicEnumCtx( + StrId::STR_PARA_ALIGNMENT, + {StrId::STR_DEFAULT_VALUE, StrId::STR_JUSTIFY, StrId::STR_ALIGN_LEFT, StrId::STR_CENTER, + StrId::STR_ALIGN_RIGHT, StrId::STR_BOOK_S_STYLE}, + self, + [](const void* ctx) -> uint8_t { + const auto* s = static_cast(ctx); + return (s->pendingParagraphAlignmentOverride < 0) + ? 0 + : static_cast(s->pendingParagraphAlignmentOverride + 1); + }, + [](void* ctx, uint8_t v) { + auto* s = static_cast(ctx); + s->pendingParagraphAlignmentOverride = (v == 0) ? -1 : static_cast(v - 1); + }) + .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 @@ -303,7 +322,7 @@ void EpubReaderMenuActivity::finishWithAction(MenuAction action) { setResult(MenuResult{static_cast(action), -1, pendingOrientation, selectedPageTurnOption, pendingEmbeddedStyleOverride, pendingImageRenderingOverride, pendingFontFamilyOverride, pendingSdFontFamilyOverride, pendingFontSizeOverride, pendingTextDarkness, - static_cast(pendingBionicReading)}); + static_cast(pendingBionicReading), pendingParagraphAlignmentOverride}); finish(); } @@ -338,7 +357,8 @@ void EpubReaderMenuActivity::onBackPressed() { pendingSdFontFamilyOverride, pendingFontSizeOverride, pendingTextDarkness, - static_cast(pendingBionicReading)}; + static_cast(pendingBionicReading), + pendingParagraphAlignmentOverride}; setResult(std::move(result)); finish(); } @@ -387,6 +407,12 @@ std::string EpubReaderMenuActivity::getItemValueString(int index) const { return std::string(tr(STR_DEFAULT_VALUE)) + " (" + I18N.get(item.enumValues[defaultIndex]) + ")"; } } + if (item.nameId == StrId::STR_PARA_ALIGNMENT && pendingParagraphAlignmentOverride < 0) { + const auto defaultIndex = static_cast(SETTINGS.paragraphAlignment + 1); + if (defaultIndex < item.enumValues.size()) { + return std::string(tr(STR_DEFAULT_VALUE)) + " (" + I18N.get(item.enumValues[defaultIndex]) + ")"; + } + } } // DynamicEnum items use the standard display @@ -421,6 +447,12 @@ void EpubReaderMenuActivity::openSubmenu(const SettingInfo& submenuEntry) { return std::string(tr(STR_DEFAULT_VALUE)) + " (" + I18N.get(item.enumValues[valueIndex]) + ")"; } } + if (item.nameId == StrId::STR_PARA_ALIGNMENT && pendingParagraphAlignmentOverride < 0) { + const auto valueIndex = static_cast(SETTINGS.paragraphAlignment + 1); + if (valueIndex < item.enumValues.size()) { + return std::string(tr(STR_DEFAULT_VALUE)) + " (" + I18N.get(item.enumValues[valueIndex]) + ")"; + } + } return item.getDisplayValue(); }; diff --git a/src/activities/reader/EpubReaderMenuActivity.h b/src/activities/reader/EpubReaderMenuActivity.h index 0002285c..23fd41be 100644 --- a/src/activities/reader/EpubReaderMenuActivity.h +++ b/src/activities/reader/EpubReaderMenuActivity.h @@ -40,7 +40,8 @@ class EpubReaderMenuActivity final : public MenuListActivity { const int8_t initialFontFamilyOverride, const std::string& initialSdFontFamilyOverride, const int8_t initialFontSizeOverride, const uint8_t initialTextDarkness, const bool initialBionicReadingOverride, - const bool hasStarredPages, const bool isCurrentPageStarred); + const int8_t initialParagraphAlignmentOverride, const bool hasStarredPages, + const bool isCurrentPageStarred); void onEnter() override; void render(RenderLock&&) override; @@ -73,6 +74,7 @@ class EpubReaderMenuActivity final : public MenuListActivity { int8_t pendingFontSizeOverride = -1; uint8_t pendingTextDarkness = 1; bool pendingBionicReading = false; + int8_t pendingParagraphAlignmentOverride = -1; static constexpr const char* pageTurnLabels[] = {"", "1", "3", "6", "12"};