From ce0b439aa38c6dcaddeb7b2155115fcd04eb65c0 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Tue, 3 Mar 2026 16:59:06 +0100 Subject: [PATCH] feat: User setting for image display (#1291) ## Summary **What is the goal of this PR?** Add a user setting to decide image support: display, show placeholder instead, supress fully Fixes #1289 --- ### 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? _**< NO >**_ --- lib/Epub/Epub/Section.cpp | 23 +++++++++++-------- lib/Epub/Epub/Section.h | 7 +++--- .../Epub/parsers/ChapterHtmlSlimParser.cpp | 9 +++++++- lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h | 6 +++-- lib/I18n/translations/english.yaml | 4 ++++ src/CrossPointSettings.h | 7 ++++++ src/SettingsList.h | 3 +++ src/activities/reader/EpubReaderActivity.cpp | 6 +++-- 8 files changed, 48 insertions(+), 17 deletions(-) diff --git a/lib/Epub/Epub/Section.cpp b/lib/Epub/Epub/Section.cpp index 6e5aadf94..bc04d475c 100644 --- a/lib/Epub/Epub/Section.cpp +++ b/lib/Epub/Epub/Section.cpp @@ -10,10 +10,10 @@ #include "parsers/ChapterHtmlSlimParser.h" namespace { -constexpr uint8_t SECTION_FILE_VERSION = 16; +constexpr uint8_t SECTION_FILE_VERSION = 17; constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + sizeof(int) + sizeof(float) + sizeof(bool) + sizeof(uint8_t) + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(bool) + sizeof(bool) + - sizeof(uint32_t); + sizeof(uint8_t) + sizeof(uint32_t); } // namespace uint32_t Section::onPageComplete(std::unique_ptr page) { @@ -36,7 +36,7 @@ uint32_t Section::onPageComplete(std::unique_ptr page) { void Section::writeSectionFileHeader(const int fontId, const float lineCompression, const bool extraParagraphSpacing, const uint8_t paragraphAlignment, const uint16_t viewportWidth, const uint16_t viewportHeight, const bool hyphenationEnabled, - const bool embeddedStyle) { + const bool embeddedStyle, const uint8_t imageRendering) { if (!file) { LOG_DBG("SCT", "File not open for writing header"); return; @@ -44,7 +44,7 @@ void Section::writeSectionFileHeader(const int fontId, const float lineCompressi static_assert(HEADER_SIZE == sizeof(SECTION_FILE_VERSION) + sizeof(fontId) + sizeof(lineCompression) + sizeof(extraParagraphSpacing) + sizeof(paragraphAlignment) + sizeof(viewportWidth) + sizeof(viewportHeight) + sizeof(pageCount) + sizeof(hyphenationEnabled) + - sizeof(embeddedStyle) + sizeof(uint32_t), + sizeof(embeddedStyle) + sizeof(imageRendering) + sizeof(uint32_t), "Header size mismatch"); serialization::writePod(file, SECTION_FILE_VERSION); serialization::writePod(file, fontId); @@ -55,13 +55,15 @@ void Section::writeSectionFileHeader(const int fontId, const float lineCompressi serialization::writePod(file, viewportHeight); serialization::writePod(file, hyphenationEnabled); serialization::writePod(file, embeddedStyle); + serialization::writePod(file, imageRendering); serialization::writePod(file, pageCount); // Placeholder for page count (will be initially 0 when written) serialization::writePod(file, static_cast(0)); // Placeholder for LUT offset } bool Section::loadSectionFile(const int fontId, const float lineCompression, const bool extraParagraphSpacing, const uint8_t paragraphAlignment, const uint16_t viewportWidth, - const uint16_t viewportHeight, const bool hyphenationEnabled, const bool embeddedStyle) { + const uint16_t viewportHeight, const bool hyphenationEnabled, const bool embeddedStyle, + const uint8_t imageRendering) { if (!Storage.openFileForRead("SCT", filePath, file)) { return false; } @@ -84,6 +86,7 @@ bool Section::loadSectionFile(const int fontId, const float lineCompression, con uint8_t fileParagraphAlignment; bool fileHyphenationEnabled; bool fileEmbeddedStyle; + uint8_t fileImageRendering; serialization::readPod(file, fileFontId); serialization::readPod(file, fileLineCompression); serialization::readPod(file, fileExtraParagraphSpacing); @@ -92,11 +95,13 @@ bool Section::loadSectionFile(const int fontId, const float lineCompression, con serialization::readPod(file, fileViewportHeight); serialization::readPod(file, fileHyphenationEnabled); serialization::readPod(file, fileEmbeddedStyle); + serialization::readPod(file, fileImageRendering); if (fontId != fileFontId || lineCompression != fileLineCompression || extraParagraphSpacing != fileExtraParagraphSpacing || paragraphAlignment != fileParagraphAlignment || viewportWidth != fileViewportWidth || viewportHeight != fileViewportHeight || - hyphenationEnabled != fileHyphenationEnabled || embeddedStyle != fileEmbeddedStyle) { + hyphenationEnabled != fileHyphenationEnabled || embeddedStyle != fileEmbeddedStyle || + imageRendering != fileImageRendering) { file.close(); LOG_ERR("SCT", "Deserialization failed: Parameters do not match"); clearCache(); @@ -129,7 +134,7 @@ bool Section::clearCache() const { bool Section::createSectionFile(const int fontId, const float lineCompression, const bool extraParagraphSpacing, const uint8_t paragraphAlignment, const uint16_t viewportWidth, const uint16_t viewportHeight, const bool hyphenationEnabled, const bool embeddedStyle, - const std::function& popupFn) { + const uint8_t imageRendering, const std::function& popupFn) { const auto localPath = epub->getSpineItem(spineIndex).href; const auto tmpHtmlPath = epub->getCachePath() + "/.tmp_" + std::to_string(spineIndex) + ".html"; @@ -179,7 +184,7 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c return false; } writeSectionFileHeader(fontId, lineCompression, extraParagraphSpacing, paragraphAlignment, viewportWidth, - viewportHeight, hyphenationEnabled, embeddedStyle); + viewportHeight, hyphenationEnabled, embeddedStyle, imageRendering); std::vector lut = {}; // Derive the content base directory and image cache path prefix for the parser @@ -201,7 +206,7 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c epub, tmpHtmlPath, renderer, fontId, lineCompression, extraParagraphSpacing, paragraphAlignment, viewportWidth, viewportHeight, hyphenationEnabled, [this, &lut](std::unique_ptr page) { lut.emplace_back(this->onPageComplete(std::move(page))); }, - embeddedStyle, contentBase, imageBasePath, popupFn, cssParser); + embeddedStyle, contentBase, imageBasePath, imageRendering, popupFn, cssParser); Hyphenator::setPreferredLanguage(epub->getLanguage()); success = visitor.parseAndBuildPages(); diff --git a/lib/Epub/Epub/Section.h b/lib/Epub/Epub/Section.h index 42a6d9939..70fd0fe5e 100644 --- a/lib/Epub/Epub/Section.h +++ b/lib/Epub/Epub/Section.h @@ -16,7 +16,7 @@ class Section { void writeSectionFileHeader(int fontId, float lineCompression, bool extraParagraphSpacing, uint8_t paragraphAlignment, uint16_t viewportWidth, uint16_t viewportHeight, bool hyphenationEnabled, - bool embeddedStyle); + bool embeddedStyle, uint8_t imageRendering); uint32_t onPageComplete(std::unique_ptr page); public: @@ -30,10 +30,11 @@ class Section { filePath(epub->getCachePath() + "/sections/" + std::to_string(spineIndex) + ".bin") {} ~Section() = default; bool loadSectionFile(int fontId, float lineCompression, bool extraParagraphSpacing, uint8_t paragraphAlignment, - uint16_t viewportWidth, uint16_t viewportHeight, bool hyphenationEnabled, bool embeddedStyle); + uint16_t viewportWidth, uint16_t viewportHeight, bool hyphenationEnabled, bool embeddedStyle, + uint8_t imageRendering); bool clearCache() const; bool createSectionFile(int fontId, float lineCompression, bool extraParagraphSpacing, uint8_t paragraphAlignment, uint16_t viewportWidth, uint16_t viewportHeight, bool hyphenationEnabled, bool embeddedStyle, - const std::function& popupFn = nullptr); + uint8_t imageRendering, const std::function& popupFn = nullptr); std::unique_ptr loadPageFromSectionFile(); }; diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp index 2753f9285..6a0636bbc 100644 --- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp +++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp @@ -243,7 +243,14 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* } } - if (!src.empty()) { + // imageRendering: 0=display, 1=placeholder (alt text only), 2=suppress entirely + if (self->imageRendering == 2) { + self->skipUntilDepth = self->depth; + self->depth += 1; + return; + } + + if (!src.empty() && self->imageRendering != 1) { LOG_DBG("EHP", "Found image: src=%s", src.c_str()); { diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h index 663ce860c..ce5307978 100644 --- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h +++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h @@ -48,6 +48,7 @@ class ChapterHtmlSlimParser { bool hyphenationEnabled; const CssParser* cssParser; bool embeddedStyle; + uint8_t imageRendering; std::string contentBase; std::string imageBasePath; int imageCounter = 0; @@ -94,8 +95,8 @@ class ChapterHtmlSlimParser { const uint16_t viewportHeight, const bool hyphenationEnabled, const std::function)>& completePageFn, const bool embeddedStyle, const std::string& contentBase, - const std::string& imageBasePath, const std::function& popupFn = nullptr, - const CssParser* cssParser = nullptr) + const std::string& imageBasePath, const uint8_t imageRendering = 0, + const std::function& popupFn = nullptr, const CssParser* cssParser = nullptr) : epub(epub), filepath(filepath), @@ -111,6 +112,7 @@ class ChapterHtmlSlimParser { popupFn(popupFn), cssParser(cssParser), embeddedStyle(embeddedStyle), + imageRendering(imageRendering), contentBase(contentBase), imageBasePath(imageBasePath) {} diff --git a/lib/I18n/translations/english.yaml b/lib/I18n/translations/english.yaml index f5b80b8ee..38192cc9e 100644 --- a/lib/I18n/translations/english.yaml +++ b/lib/I18n/translations/english.yaml @@ -92,6 +92,10 @@ STR_STATUS_BAR: "Status Bar" STR_HIDE_BATTERY: "Hide Battery %" STR_EXTRA_SPACING: "Extra Paragraph Spacing" STR_TEXT_AA: "Text Anti-Aliasing" +STR_IMAGES: "Images" +STR_IMAGES_DISPLAY: "Display" +STR_IMAGES_PLACEHOLDER: "Placeholder" +STR_IMAGES_SUPPRESS: "Suppress" STR_SHORT_PWR_BTN: "Short Power Button Click" STR_ORIENTATION: "Reading Orientation" STR_FRONT_BTN_LAYOUT: "Front Button Layout" diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 5ba7bde2e..9a0e298b0 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -134,6 +134,9 @@ class CrossPointSettings { // UI Theme enum UI_THEME { CLASSIC = 0, LYRA = 1, LYRA_3_COVERS = 2 }; + // Image rendering in EPUB reader + enum IMAGE_RENDERING { IMAGES_DISPLAY = 0, IMAGES_PLACEHOLDER = 1, IMAGES_SUPPRESS = 2, IMAGE_RENDERING_COUNT }; + // Sleep screen settings uint8_t sleepScreen = DARK; // Sleep screen cover mode settings @@ -192,6 +195,10 @@ class CrossPointSettings { uint8_t fadingFix = 0; // Use book's embedded CSS styles for EPUB rendering (1 = enabled, 0 = disabled) uint8_t embeddedStyle = 1; + // Show hidden files/directories (starting with '.') in the file browser (0 = hidden, 1 = show) + uint8_t showHiddenFiles = 0; + // Image rendering mode in EPUB reader + uint8_t imageRendering = IMAGES_DISPLAY; ~CrossPointSettings() = default; diff --git a/src/SettingsList.h b/src/SettingsList.h index c10d58c6a..f69c9e93b 100644 --- a/src/SettingsList.h +++ b/src/SettingsList.h @@ -62,6 +62,9 @@ inline const std::vector& getSettingsList() { StrId::STR_CAT_READER), SettingInfo::Toggle(StrId::STR_TEXT_AA, &CrossPointSettings::textAntiAliasing, "textAntiAliasing", StrId::STR_CAT_READER), + SettingInfo::Enum(StrId::STR_IMAGES, &CrossPointSettings::imageRendering, + {StrId::STR_IMAGES_DISPLAY, StrId::STR_IMAGES_PLACEHOLDER, StrId::STR_IMAGES_SUPPRESS}, + "imageRendering", StrId::STR_CAT_READER), // --- Controls --- SettingInfo::Enum(StrId::STR_SIDE_BTN_LAYOUT, &CrossPointSettings::sideButtonLayout, {StrId::STR_PREV_NEXT, StrId::STR_NEXT_PREV}, "sideButtonLayout", StrId::STR_CAT_CONTROLS), diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index d889df5ff..059d3ea17 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -571,14 +571,16 @@ void EpubReaderActivity::render(RenderLock&& lock) { if (!section->loadSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(), SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, - viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle)) { + viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle, + SETTINGS.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, popupFn)) { + viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle, + SETTINGS.imageRendering, popupFn)) { LOG_ERR("ERS", "Failed to persist page data to SD"); section.reset(); return;