From 09e65c30a3f0c5d2b23ea7e3cba0d4e90f33a40d Mon Sep 17 00:00:00 2001 From: jpirnay Date: Fri, 20 Mar 2026 18:17:34 +0100 Subject: [PATCH] Local overrides for css and img display --- src/JsonSettingsIO.cpp | 11 +++ src/RecentBooksStore.cpp | 30 ++++++- src/RecentBooksStore.h | 8 ++ src/activities/ActivityResult.h | 2 + src/activities/reader/EpubReaderActivity.cpp | 84 ++++++++++++++----- src/activities/reader/EpubReaderActivity.h | 6 ++ .../reader/EpubReaderMenuActivity.cpp | 59 ++++++++++++- .../reader/EpubReaderMenuActivity.h | 10 ++- 8 files changed, 185 insertions(+), 25 deletions(-) diff --git a/src/JsonSettingsIO.cpp b/src/JsonSettingsIO.cpp index a0458043..a364d32e 100644 --- a/src/JsonSettingsIO.cpp +++ b/src/JsonSettingsIO.cpp @@ -302,6 +302,8 @@ bool JsonSettingsIO::saveRecentBooks(const RecentBooksStore& store, const char* obj["title"] = book.title; obj["author"] = book.author; obj["coverBmpPath"] = book.coverBmpPath; + obj["embeddedStyleOverride"] = book.embeddedStyleOverride; + obj["imageRenderingOverride"] = book.imageRenderingOverride; } String json; @@ -319,6 +321,13 @@ bool JsonSettingsIO::loadRecentBooks(RecentBooksStore& store, const char* json) store.recentBooks.clear(); JsonArray arr = doc["books"].as(); + auto clampInt8 = [](int value, int minValue, int maxValue, int8_t fallback) -> int8_t { + if (value < minValue || value > maxValue) { + return fallback; + } + return static_cast(value); + }; + for (JsonObject obj : arr) { if (store.getCount() >= 10) break; RecentBook book; @@ -326,6 +335,8 @@ bool JsonSettingsIO::loadRecentBooks(RecentBooksStore& store, const char* json) book.title = obj["title"] | std::string(""); book.author = obj["author"] | std::string(""); book.coverBmpPath = obj["coverBmpPath"] | std::string(""); + book.embeddedStyleOverride = clampInt8(obj["embeddedStyleOverride"] | -1, -1, 1, -1); + book.imageRenderingOverride = clampInt8(obj["imageRenderingOverride"] | -1, -1, 2, -1); store.recentBooks.push_back(book); } diff --git a/src/RecentBooksStore.cpp b/src/RecentBooksStore.cpp index f5a2c048..24d99739 100644 --- a/src/RecentBooksStore.cpp +++ b/src/RecentBooksStore.cpp @@ -22,15 +22,21 @@ RecentBooksStore RecentBooksStore::instance; void RecentBooksStore::addBook(const std::string& path, const std::string& title, const std::string& author, const std::string& coverBmpPath) { + int8_t embeddedStyleOverride = -1; + int8_t imageRenderingOverride = -1; + // Remove existing entry if present auto it = std::find_if(recentBooks.begin(), recentBooks.end(), [&](const RecentBook& book) { return book.path == path; }); if (it != recentBooks.end()) { + embeddedStyleOverride = it->embeddedStyleOverride; + imageRenderingOverride = it->imageRenderingOverride; recentBooks.erase(it); } // Add to front - recentBooks.insert(recentBooks.begin(), {path, title, author, coverBmpPath}); + recentBooks.insert(recentBooks.begin(), + {path, title, author, coverBmpPath, embeddedStyleOverride, imageRenderingOverride}); // Trim to max size if (recentBooks.size() > MAX_RECENT_BOOKS) { @@ -53,6 +59,28 @@ void RecentBooksStore::updateBook(const std::string& path, const std::string& ti } } +RecentBook RecentBooksStore::getBookByPath(const std::string& path) const { + auto it = + std::find_if(recentBooks.begin(), recentBooks.end(), [&](const RecentBook& book) { return book.path == path; }); + if (it != recentBooks.end()) { + return *it; + } + return RecentBook{}; +} + +bool RecentBooksStore::setReaderOverrides(const std::string& path, const int8_t embeddedStyleOverride, + const int8_t imageRenderingOverride) { + 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; + return saveToFile(); +} + bool RecentBooksStore::saveToFile() const { Storage.mkdir("/.crosspoint"); return JsonSettingsIO::saveRecentBooks(*this, RECENT_BOOKS_FILE_JSON); diff --git a/src/RecentBooksStore.h b/src/RecentBooksStore.h index 5d98ce83..4e00c565 100644 --- a/src/RecentBooksStore.h +++ b/src/RecentBooksStore.h @@ -1,12 +1,18 @@ #pragma once +#include #include #include + struct RecentBook { std::string path; std::string title; std::string author; std::string coverBmpPath; + // -1 = use global setting, otherwise explicit per-book override. + int8_t embeddedStyleOverride = -1; + // -1 = use global setting, otherwise CrossPointSettings::IMAGE_RENDERING value. + int8_t imageRenderingOverride = -1; bool operator==(const RecentBook& other) const { return path == other.path; } }; @@ -47,6 +53,8 @@ class RecentBooksStore { bool loadFromFile(); 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); private: bool loadFromBinaryFile(); diff --git a/src/activities/ActivityResult.h b/src/activities/ActivityResult.h index 4062fce9..e192fe5e 100644 --- a/src/activities/ActivityResult.h +++ b/src/activities/ActivityResult.h @@ -21,6 +21,8 @@ struct MenuResult { int action = -1; uint8_t orientation = 0; uint8_t pageTurnOption = 0; + int8_t embeddedStyleOverride = -1; + int8_t imageRenderingOverride = -1; }; struct ChapterResult { diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 0ce1c5b1..f2bd4253 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -85,6 +85,9 @@ void EpubReaderActivity::onEnter() { APP_STATE.openEpubPath = epub->getPath(); APP_STATE.saveToFile(); RECENT_BOOKS.addBook(epub->getPath(), epub->getTitle(), epub->getAuthor(), epub->getThumbBmpPath()); + const RecentBook currentBook = RECENT_BOOKS.getBookByPath(epub->getPath()); + bookEmbeddedStyleOverride = currentBook.embeddedStyleOverride; + bookImageRenderingOverride = currentBook.imageRenderingOverride; // Trigger first update requestUpdate(); @@ -145,18 +148,20 @@ void EpubReaderActivity::loop() { bookProgress = epub->calculateProgress(currentSpineIndex, chapterProgress) * 100.0f; } const int bookProgressPercent = clampPercent(static_cast(bookProgress + 0.5f)); - startActivityForResult(std::make_unique( - renderer, mappedInput, epub->getTitle(), currentPage, totalPages, bookProgressPercent, - SETTINGS.orientation, !currentPageFootnotes.empty()), - [this](const ActivityResult& result) { - // Always apply orientation change even if the menu was cancelled - const auto& menu = std::get(result.data); - applyOrientation(menu.orientation); - toggleAutoPageTurn(menu.pageTurnOption); - 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), + [this](const ActivityResult& result) { + // Always apply orientation change even if the menu was cancelled + const auto& menu = std::get(result.data); + applyOrientation(menu.orientation); + toggleAutoPageTurn(menu.pageTurnOption); + applyBookReaderOverrides(menu.embeddedStyleOverride, menu.imageRenderingOverride); + if (!result.isCancelled) { + onReaderMenuConfirm(static_cast(menu.action)); + } + }); } // Long press BACK (1s+) goes to file selection @@ -456,6 +461,43 @@ void EpubReaderActivity::toggleAutoPageTurn(const uint8_t selectedPageTurnOption } } +void EpubReaderActivity::applyBookReaderOverrides(const int8_t embeddedStyleOverride, + const int8_t imageRenderingOverride) { + if (!epub) { + return; + } + + if (bookEmbeddedStyleOverride == embeddedStyleOverride && bookImageRenderingOverride == imageRenderingOverride) { + return; + } + + bookEmbeddedStyleOverride = embeddedStyleOverride; + bookImageRenderingOverride = imageRenderingOverride; + RECENT_BOOKS.setReaderOverrides(epub->getPath(), bookEmbeddedStyleOverride, bookImageRenderingOverride); + + RenderLock lock(*this); + if (section) { + cachedSpineIndex = currentSpineIndex; + cachedChapterTotalPageCount = section->pageCount; + nextPageNumber = section->currentPage; + } + section.reset(); +} + +bool EpubReaderActivity::getEffectiveEmbeddedStyle() const { + if (bookEmbeddedStyleOverride >= 0) { + return bookEmbeddedStyleOverride != 0; + } + return SETTINGS.embeddedStyle != 0; +} + +uint8_t EpubReaderActivity::getEffectiveImageRendering() const { + if (bookImageRenderingOverride >= 0) { + return static_cast(bookImageRenderingOverride); + } + return SETTINGS.imageRendering; +} + void EpubReaderActivity::pageTurn(bool isForwardTurn) { if (isForwardTurn) { if (section->currentPage < section->pageCount - 1) { @@ -534,22 +576,23 @@ void EpubReaderActivity::render(RenderLock&& lock) { const uint16_t viewportHeight = renderer.getScreenHeight() - orientedMarginTop - orientedMarginBottom; if (!section) { + const bool embeddedStyle = getEffectiveEmbeddedStyle(); + const uint8_t imageRendering = getEffectiveImageRendering(); const auto filepath = epub->getSpineItem(currentSpineIndex).href; LOG_DBG("ERS", "Loading file: %s, index: %d", filepath.c_str(), currentSpineIndex); section = std::unique_ptr
(new Section(epub, currentSpineIndex, renderer)); if (!section->loadSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(), SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, - viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle, - SETTINGS.imageRendering)) { + viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, imageRendering)) { LOG_DBG("ERS", "Cache not found, building..."); const auto popupFn = [this]() { GUI.drawPopup(renderer, tr(STR_INDEXING)); }; if (!section->createSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(), SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, - viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle, - SETTINGS.imageRendering, popupFn)) { + viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, imageRendering, + popupFn)) { LOG_ERR("ERS", "Failed to persist page data to SD"); section.reset(); return; @@ -659,19 +702,20 @@ void EpubReaderActivity::silentIndexNextChapterIfNeeded(const uint16_t viewportW return; } + const bool embeddedStyle = getEffectiveEmbeddedStyle(); + const uint8_t imageRendering = getEffectiveImageRendering(); + Section nextSection(epub, nextSpineIndex, renderer); if (nextSection.loadSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(), SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, - viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle, - SETTINGS.imageRendering)) { + viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, imageRendering)) { return; } LOG_DBG("ERS", "Silently indexing next chapter: %d", nextSpineIndex); if (!nextSection.createSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(), SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, - viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle, - SETTINGS.imageRendering)) { + viewportHeight, SETTINGS.hyphenationEnabled, embeddedStyle, imageRendering)) { LOG_ERR("ERS", "Failed silent indexing for chapter: %d", nextSpineIndex); } } diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h index bc6c8354..51519ce7 100644 --- a/src/activities/reader/EpubReaderActivity.h +++ b/src/activities/reader/EpubReaderActivity.h @@ -27,6 +27,9 @@ class EpubReaderActivity final : public Activity { bool pendingScreenshot = false; bool skipNextButtonCheck = false; // Skip button processing for one frame after subactivity exit bool automaticPageTurnActive = false; + // -1 means use global SETTINGS value. + int8_t bookEmbeddedStyleOverride = -1; + int8_t bookImageRenderingOverride = -1; // Footnote support std::vector currentPageFootnotes; @@ -48,6 +51,9 @@ class EpubReaderActivity final : public Activity { void onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction action); void applyOrientation(uint8_t orientation); void toggleAutoPageTurn(uint8_t selectedPageTurnOption); + void applyBookReaderOverrides(int8_t embeddedStyleOverride, int8_t imageRenderingOverride); + bool getEffectiveEmbeddedStyle() const; + uint8_t getEffectiveImageRendering() const; void pageTurn(bool isForwardTurn); // Footnote navigation diff --git a/src/activities/reader/EpubReaderMenuActivity.cpp b/src/activities/reader/EpubReaderMenuActivity.cpp index 1d95d9b7..4adcdfb5 100644 --- a/src/activities/reader/EpubReaderMenuActivity.cpp +++ b/src/activities/reader/EpubReaderMenuActivity.cpp @@ -10,11 +10,14 @@ EpubReaderMenuActivity::EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::string& title, const int currentPage, const int totalPages, const int bookProgressPercent, const uint8_t currentOrientation, - const bool hasFootnotes) + const bool hasFootnotes, const int8_t initialEmbeddedStyleOverride, + const int8_t initialImageRenderingOverride) : Activity("EpubReaderMenu", renderer, mappedInput), menuItems(buildMenuItems(hasFootnotes)), title(title), pendingOrientation(currentOrientation), + pendingEmbeddedStyleOverride(initialEmbeddedStyleOverride), + pendingImageRenderingOverride(initialImageRenderingOverride), currentPage(currentPage), totalPages(totalPages), bookProgressPercent(bookProgressPercent) {} @@ -26,6 +29,8 @@ std::vector EpubReaderMenuActivity::buildMenuI if (hasFootnotes) { items.push_back({MenuAction::FOOTNOTES, StrId::STR_FOOTNOTES}); } + items.push_back({MenuAction::EMBEDDED_STYLE, StrId::STR_EMBEDDED_STYLE}); + items.push_back({MenuAction::IMAGE_RENDERING, StrId::STR_IMAGES}); items.push_back({MenuAction::ROTATE_SCREEN, StrId::STR_ORIENTATION}); items.push_back({MenuAction::AUTO_PAGE_TURN, StrId::STR_AUTO_TURN_PAGES_PER_MIN}); items.push_back({MenuAction::GO_TO_PERCENT, StrId::STR_GO_TO_PERCENT}); @@ -71,13 +76,41 @@ void EpubReaderMenuActivity::loop() { return; } - setResult(MenuResult{static_cast(selectedAction), pendingOrientation, selectedPageTurnOption}); + if (selectedAction == MenuAction::EMBEDDED_STYLE) { + // Cycle per-book override: default -> ON -> OFF -> default. + if (pendingEmbeddedStyleOverride < 0) { + pendingEmbeddedStyleOverride = 1; + } else if (pendingEmbeddedStyleOverride > 0) { + pendingEmbeddedStyleOverride = 0; + } else { + pendingEmbeddedStyleOverride = -1; + } + requestUpdate(); + return; + } + + if (selectedAction == MenuAction::IMAGE_RENDERING) { + // Cycle per-book override: default -> display -> placeholder -> suppress -> default. + if (pendingImageRenderingOverride < 0) { + pendingImageRenderingOverride = 0; + } else if (pendingImageRenderingOverride >= 2) { + pendingImageRenderingOverride = -1; + } else { + pendingImageRenderingOverride++; + } + requestUpdate(); + return; + } + + setResult(MenuResult{static_cast(selectedAction), pendingOrientation, selectedPageTurnOption, + pendingEmbeddedStyleOverride, pendingImageRenderingOverride}); finish(); return; } else if (mappedInput.wasReleased(MappedInputManager::Button::Back)) { ActivityResult result; result.isCancelled = true; - result.data = MenuResult{-1, pendingOrientation, selectedPageTurnOption}; + result.data = MenuResult{-1, pendingOrientation, selectedPageTurnOption, pendingEmbeddedStyleOverride, + pendingImageRenderingOverride}; setResult(std::move(result)); finish(); return; @@ -147,6 +180,26 @@ void EpubReaderMenuActivity::render(RenderLock&&) { const auto width = renderer.getTextWidth(UI_10_FONT_ID, value); renderer.drawText(UI_10_FONT_ID, contentX + contentWidth - 20 - width, displayY, value, !isSelected); } + + if (menuItems[i].action == MenuAction::EMBEDDED_STYLE) { + const char* value = tr(STR_DEFAULT_VALUE); + if (pendingEmbeddedStyleOverride == 1) { + value = tr(STR_STATE_ON); + } else if (pendingEmbeddedStyleOverride == 0) { + value = tr(STR_STATE_OFF); + } + const auto width = renderer.getTextWidth(UI_10_FONT_ID, value); + renderer.drawText(UI_10_FONT_ID, contentX + contentWidth - 20 - width, displayY, value, !isSelected); + } + + if (menuItems[i].action == MenuAction::IMAGE_RENDERING) { + const char* value = tr(STR_DEFAULT_VALUE); + if (pendingImageRenderingOverride >= 0 && pendingImageRenderingOverride < imageRenderingLabels.size()) { + value = I18N.get(imageRenderingLabels[pendingImageRenderingOverride]); + } + const auto width = renderer.getTextWidth(UI_10_FONT_ID, value); + renderer.drawText(UI_10_FONT_ID, contentX + contentWidth - 20 - width, displayY, value, !isSelected); + } } // Footer / Hints diff --git a/src/activities/reader/EpubReaderMenuActivity.h b/src/activities/reader/EpubReaderMenuActivity.h index 9ddba93d..ecb2d308 100644 --- a/src/activities/reader/EpubReaderMenuActivity.h +++ b/src/activities/reader/EpubReaderMenuActivity.h @@ -14,6 +14,8 @@ class EpubReaderMenuActivity final : public Activity { enum class MenuAction { SELECT_CHAPTER, FOOTNOTES, + EMBEDDED_STYLE, + IMAGE_RENDERING, GO_TO_PERCENT, AUTO_PAGE_TURN, ROTATE_SCREEN, @@ -26,7 +28,9 @@ class EpubReaderMenuActivity final : public Activity { explicit EpubReaderMenuActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, const std::string& title, const int currentPage, const int totalPages, const int bookProgressPercent, - const uint8_t currentOrientation, const bool hasFootnotes); + const uint8_t currentOrientation, const bool hasFootnotes, + const int8_t initialEmbeddedStyleOverride, + const int8_t initialImageRenderingOverride); void onEnter() override; void onExit() override; @@ -50,8 +54,12 @@ class EpubReaderMenuActivity final : public Activity { std::string title = "Reader Menu"; uint8_t pendingOrientation = 0; uint8_t selectedPageTurnOption = 0; + int8_t pendingEmbeddedStyleOverride = -1; + int8_t pendingImageRenderingOverride = -1; const std::vector orientationLabels = {StrId::STR_PORTRAIT, StrId::STR_LANDSCAPE_CW, StrId::STR_INVERTED, StrId::STR_LANDSCAPE_CCW}; + const std::vector imageRenderingLabels = {StrId::STR_IMAGES_DISPLAY, StrId::STR_IMAGES_PLACEHOLDER, + StrId::STR_IMAGES_SUPPRESS}; const std::vector pageTurnLabels = {I18N.get(StrId::STR_STATE_OFF), "1", "3", "6", "12"}; int currentPage = 0; int totalPages = 0;