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 >**_
This commit is contained in:
jpirnay
2026-03-03 16:59:06 +01:00
committed by GitHub
parent 019587bb77
commit ce0b439aa3
8 changed files with 48 additions and 17 deletions

View File

@@ -10,10 +10,10 @@
#include "parsers/ChapterHtmlSlimParser.h" #include "parsers/ChapterHtmlSlimParser.h"
namespace { 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) + 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(uint16_t) + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(bool) + sizeof(bool) +
sizeof(uint32_t); sizeof(uint8_t) + sizeof(uint32_t);
} // namespace } // namespace
uint32_t Section::onPageComplete(std::unique_ptr<Page> page) { uint32_t Section::onPageComplete(std::unique_ptr<Page> page) {
@@ -36,7 +36,7 @@ uint32_t Section::onPageComplete(std::unique_ptr<Page> page) {
void Section::writeSectionFileHeader(const int fontId, const float lineCompression, const bool extraParagraphSpacing, void Section::writeSectionFileHeader(const int fontId, const float lineCompression, const bool extraParagraphSpacing,
const uint8_t paragraphAlignment, const uint16_t viewportWidth, const uint8_t paragraphAlignment, const uint16_t viewportWidth,
const uint16_t viewportHeight, const bool hyphenationEnabled, const uint16_t viewportHeight, const bool hyphenationEnabled,
const bool embeddedStyle) { const bool embeddedStyle, const uint8_t imageRendering) {
if (!file) { if (!file) {
LOG_DBG("SCT", "File not open for writing header"); LOG_DBG("SCT", "File not open for writing header");
return; 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) + static_assert(HEADER_SIZE == sizeof(SECTION_FILE_VERSION) + sizeof(fontId) + sizeof(lineCompression) +
sizeof(extraParagraphSpacing) + sizeof(paragraphAlignment) + sizeof(viewportWidth) + sizeof(extraParagraphSpacing) + sizeof(paragraphAlignment) + sizeof(viewportWidth) +
sizeof(viewportHeight) + sizeof(pageCount) + sizeof(hyphenationEnabled) + sizeof(viewportHeight) + sizeof(pageCount) + sizeof(hyphenationEnabled) +
sizeof(embeddedStyle) + sizeof(uint32_t), sizeof(embeddedStyle) + sizeof(imageRendering) + sizeof(uint32_t),
"Header size mismatch"); "Header size mismatch");
serialization::writePod(file, SECTION_FILE_VERSION); serialization::writePod(file, SECTION_FILE_VERSION);
serialization::writePod(file, fontId); serialization::writePod(file, fontId);
@@ -55,13 +55,15 @@ void Section::writeSectionFileHeader(const int fontId, const float lineCompressi
serialization::writePod(file, viewportHeight); serialization::writePod(file, viewportHeight);
serialization::writePod(file, hyphenationEnabled); serialization::writePod(file, hyphenationEnabled);
serialization::writePod(file, embeddedStyle); 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, pageCount); // Placeholder for page count (will be initially 0 when written)
serialization::writePod(file, static_cast<uint32_t>(0)); // Placeholder for LUT offset serialization::writePod(file, static_cast<uint32_t>(0)); // Placeholder for LUT offset
} }
bool Section::loadSectionFile(const int fontId, const float lineCompression, const bool extraParagraphSpacing, bool Section::loadSectionFile(const int fontId, const float lineCompression, const bool extraParagraphSpacing,
const uint8_t paragraphAlignment, const uint16_t viewportWidth, 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)) { if (!Storage.openFileForRead("SCT", filePath, file)) {
return false; return false;
} }
@@ -84,6 +86,7 @@ bool Section::loadSectionFile(const int fontId, const float lineCompression, con
uint8_t fileParagraphAlignment; uint8_t fileParagraphAlignment;
bool fileHyphenationEnabled; bool fileHyphenationEnabled;
bool fileEmbeddedStyle; bool fileEmbeddedStyle;
uint8_t fileImageRendering;
serialization::readPod(file, fileFontId); serialization::readPod(file, fileFontId);
serialization::readPod(file, fileLineCompression); serialization::readPod(file, fileLineCompression);
serialization::readPod(file, fileExtraParagraphSpacing); 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, fileViewportHeight);
serialization::readPod(file, fileHyphenationEnabled); serialization::readPod(file, fileHyphenationEnabled);
serialization::readPod(file, fileEmbeddedStyle); serialization::readPod(file, fileEmbeddedStyle);
serialization::readPod(file, fileImageRendering);
if (fontId != fileFontId || lineCompression != fileLineCompression || if (fontId != fileFontId || lineCompression != fileLineCompression ||
extraParagraphSpacing != fileExtraParagraphSpacing || paragraphAlignment != fileParagraphAlignment || extraParagraphSpacing != fileExtraParagraphSpacing || paragraphAlignment != fileParagraphAlignment ||
viewportWidth != fileViewportWidth || viewportHeight != fileViewportHeight || viewportWidth != fileViewportWidth || viewportHeight != fileViewportHeight ||
hyphenationEnabled != fileHyphenationEnabled || embeddedStyle != fileEmbeddedStyle) { hyphenationEnabled != fileHyphenationEnabled || embeddedStyle != fileEmbeddedStyle ||
imageRendering != fileImageRendering) {
file.close(); file.close();
LOG_ERR("SCT", "Deserialization failed: Parameters do not match"); LOG_ERR("SCT", "Deserialization failed: Parameters do not match");
clearCache(); clearCache();
@@ -129,7 +134,7 @@ bool Section::clearCache() const {
bool Section::createSectionFile(const int fontId, const float lineCompression, const bool extraParagraphSpacing, bool Section::createSectionFile(const int fontId, const float lineCompression, const bool extraParagraphSpacing,
const uint8_t paragraphAlignment, const uint16_t viewportWidth, 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 std::function<void()>& popupFn) { const uint8_t imageRendering, const std::function<void()>& popupFn) {
const auto localPath = epub->getSpineItem(spineIndex).href; const auto localPath = epub->getSpineItem(spineIndex).href;
const auto tmpHtmlPath = epub->getCachePath() + "/.tmp_" + std::to_string(spineIndex) + ".html"; 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; return false;
} }
writeSectionFileHeader(fontId, lineCompression, extraParagraphSpacing, paragraphAlignment, viewportWidth, writeSectionFileHeader(fontId, lineCompression, extraParagraphSpacing, paragraphAlignment, viewportWidth,
viewportHeight, hyphenationEnabled, embeddedStyle); viewportHeight, hyphenationEnabled, embeddedStyle, imageRendering);
std::vector<uint32_t> lut = {}; std::vector<uint32_t> lut = {};
// Derive the content base directory and image cache path prefix for the parser // 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, epub, tmpHtmlPath, renderer, fontId, lineCompression, extraParagraphSpacing, paragraphAlignment, viewportWidth,
viewportHeight, hyphenationEnabled, viewportHeight, hyphenationEnabled,
[this, &lut](std::unique_ptr<Page> page) { lut.emplace_back(this->onPageComplete(std::move(page))); }, [this, &lut](std::unique_ptr<Page> page) { lut.emplace_back(this->onPageComplete(std::move(page))); },
embeddedStyle, contentBase, imageBasePath, popupFn, cssParser); embeddedStyle, contentBase, imageBasePath, imageRendering, popupFn, cssParser);
Hyphenator::setPreferredLanguage(epub->getLanguage()); Hyphenator::setPreferredLanguage(epub->getLanguage());
success = visitor.parseAndBuildPages(); success = visitor.parseAndBuildPages();

View File

@@ -16,7 +16,7 @@ class Section {
void writeSectionFileHeader(int fontId, float lineCompression, bool extraParagraphSpacing, uint8_t paragraphAlignment, void writeSectionFileHeader(int fontId, float lineCompression, bool extraParagraphSpacing, uint8_t paragraphAlignment,
uint16_t viewportWidth, uint16_t viewportHeight, bool hyphenationEnabled, uint16_t viewportWidth, uint16_t viewportHeight, bool hyphenationEnabled,
bool embeddedStyle); bool embeddedStyle, uint8_t imageRendering);
uint32_t onPageComplete(std::unique_ptr<Page> page); uint32_t onPageComplete(std::unique_ptr<Page> page);
public: public:
@@ -30,10 +30,11 @@ class Section {
filePath(epub->getCachePath() + "/sections/" + std::to_string(spineIndex) + ".bin") {} filePath(epub->getCachePath() + "/sections/" + std::to_string(spineIndex) + ".bin") {}
~Section() = default; ~Section() = default;
bool loadSectionFile(int fontId, float lineCompression, bool extraParagraphSpacing, uint8_t paragraphAlignment, 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 clearCache() const;
bool createSectionFile(int fontId, float lineCompression, bool extraParagraphSpacing, uint8_t paragraphAlignment, bool createSectionFile(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,
const std::function<void()>& popupFn = nullptr); uint8_t imageRendering, const std::function<void()>& popupFn = nullptr);
std::unique_ptr<Page> loadPageFromSectionFile(); std::unique_ptr<Page> loadPageFromSectionFile();
}; };

View File

@@ -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()); LOG_DBG("EHP", "Found image: src=%s", src.c_str());
{ {

View File

@@ -48,6 +48,7 @@ class ChapterHtmlSlimParser {
bool hyphenationEnabled; bool hyphenationEnabled;
const CssParser* cssParser; const CssParser* cssParser;
bool embeddedStyle; bool embeddedStyle;
uint8_t imageRendering;
std::string contentBase; std::string contentBase;
std::string imageBasePath; std::string imageBasePath;
int imageCounter = 0; int imageCounter = 0;
@@ -94,8 +95,8 @@ class ChapterHtmlSlimParser {
const uint16_t viewportHeight, const bool hyphenationEnabled, const uint16_t viewportHeight, const bool hyphenationEnabled,
const std::function<void(std::unique_ptr<Page>)>& completePageFn, const std::function<void(std::unique_ptr<Page>)>& completePageFn,
const bool embeddedStyle, const std::string& contentBase, const bool embeddedStyle, const std::string& contentBase,
const std::string& imageBasePath, const std::function<void()>& popupFn = nullptr, const std::string& imageBasePath, const uint8_t imageRendering = 0,
const CssParser* cssParser = nullptr) const std::function<void()>& popupFn = nullptr, const CssParser* cssParser = nullptr)
: epub(epub), : epub(epub),
filepath(filepath), filepath(filepath),
@@ -111,6 +112,7 @@ class ChapterHtmlSlimParser {
popupFn(popupFn), popupFn(popupFn),
cssParser(cssParser), cssParser(cssParser),
embeddedStyle(embeddedStyle), embeddedStyle(embeddedStyle),
imageRendering(imageRendering),
contentBase(contentBase), contentBase(contentBase),
imageBasePath(imageBasePath) {} imageBasePath(imageBasePath) {}

View File

@@ -92,6 +92,10 @@ STR_STATUS_BAR: "Status Bar"
STR_HIDE_BATTERY: "Hide Battery %" STR_HIDE_BATTERY: "Hide Battery %"
STR_EXTRA_SPACING: "Extra Paragraph Spacing" STR_EXTRA_SPACING: "Extra Paragraph Spacing"
STR_TEXT_AA: "Text Anti-Aliasing" 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_SHORT_PWR_BTN: "Short Power Button Click"
STR_ORIENTATION: "Reading Orientation" STR_ORIENTATION: "Reading Orientation"
STR_FRONT_BTN_LAYOUT: "Front Button Layout" STR_FRONT_BTN_LAYOUT: "Front Button Layout"

View File

@@ -134,6 +134,9 @@ class CrossPointSettings {
// UI Theme // UI Theme
enum UI_THEME { CLASSIC = 0, LYRA = 1, LYRA_3_COVERS = 2 }; 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 // Sleep screen settings
uint8_t sleepScreen = DARK; uint8_t sleepScreen = DARK;
// Sleep screen cover mode settings // Sleep screen cover mode settings
@@ -192,6 +195,10 @@ class CrossPointSettings {
uint8_t fadingFix = 0; uint8_t fadingFix = 0;
// Use book's embedded CSS styles for EPUB rendering (1 = enabled, 0 = disabled) // Use book's embedded CSS styles for EPUB rendering (1 = enabled, 0 = disabled)
uint8_t embeddedStyle = 1; 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; ~CrossPointSettings() = default;

View File

@@ -62,6 +62,9 @@ inline const std::vector<SettingInfo>& getSettingsList() {
StrId::STR_CAT_READER), StrId::STR_CAT_READER),
SettingInfo::Toggle(StrId::STR_TEXT_AA, &CrossPointSettings::textAntiAliasing, "textAntiAliasing", SettingInfo::Toggle(StrId::STR_TEXT_AA, &CrossPointSettings::textAntiAliasing, "textAntiAliasing",
StrId::STR_CAT_READER), 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 --- // --- Controls ---
SettingInfo::Enum(StrId::STR_SIDE_BTN_LAYOUT, &CrossPointSettings::sideButtonLayout, SettingInfo::Enum(StrId::STR_SIDE_BTN_LAYOUT, &CrossPointSettings::sideButtonLayout,
{StrId::STR_PREV_NEXT, StrId::STR_NEXT_PREV}, "sideButtonLayout", StrId::STR_CAT_CONTROLS), {StrId::STR_PREV_NEXT, StrId::STR_NEXT_PREV}, "sideButtonLayout", StrId::STR_CAT_CONTROLS),

View File

@@ -571,14 +571,16 @@ void EpubReaderActivity::render(RenderLock&& lock) {
if (!section->loadSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(), if (!section->loadSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(),
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle)) { viewportHeight, SETTINGS.hyphenationEnabled, SETTINGS.embeddedStyle,
SETTINGS.imageRendering)) {
LOG_DBG("ERS", "Cache not found, building..."); LOG_DBG("ERS", "Cache not found, building...");
const auto popupFn = [this]() { GUI.drawPopup(renderer, tr(STR_INDEXING)); }; const auto popupFn = [this]() { GUI.drawPopup(renderer, tr(STR_INDEXING)); };
if (!section->createSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(), if (!section->createSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(),
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth, 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"); LOG_ERR("ERS", "Failed to persist page data to SD");
section.reset(); section.reset();
return; return;