From af9e37f5614cb3605057ef8590e5f5e5ce882104 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Mon, 18 May 2026 21:10:34 +0200 Subject: [PATCH] Some fixes around svg embedded images --- lib/Epub/Epub.cpp | 42 +++++++++++++++++-- lib/Epub/Epub.h | 2 +- .../converters/JpegToFramebufferConverter.cpp | 33 +++++---------- src/activities/reader/EpubReaderActivity.cpp | 2 +- 4 files changed, 51 insertions(+), 28 deletions(-) diff --git a/lib/Epub/Epub.cpp b/lib/Epub/Epub.cpp index 2bc43a3c..cf5ef5d4 100644 --- a/lib/Epub/Epub.cpp +++ b/lib/Epub/Epub.cpp @@ -649,19 +649,53 @@ bool Epub::load(const bool buildIfMissing, const bool skipLoadingCss) { return true; } -bool Epub::clearCache() const { +bool Epub::clearCache(const bool preserveThumbs) const { if (!Storage.exists(cachePath.c_str())) { LOG_DBG("EPB", "Cache does not exist, no action needed"); return true; } - if (!Storage.removeDir(cachePath.c_str())) { - LOG_ERR("EPB", "Failed to clear cache"); + if (!preserveThumbs) { + if (!Storage.removeDir(cachePath.c_str())) { + LOG_ERR("EPB", "Failed to clear cache"); + return false; + } + LOG_DBG("EPB", "Cache cleared successfully"); + return true; + } + + // Delete sections subdirectory (bulk removal). + Storage.removeDir((cachePath + "/sections").c_str()); + + // Iterate the cache root and remove parsing artifacts, but preserve thumbnail + // and cover BMPs so the home screen doesn't have to regenerate them (slow). + FsFile dir = Storage.open(cachePath.c_str()); + if (!dir || !dir.isDirectory()) { + LOG_ERR("EPB", "Failed to open cache dir for selective clear"); + if (dir) dir.close(); return false; } + char nameBuf[128]; + bool anyFailed = false; + for (FsFile f = dir.openNextFile(); f; f = dir.openNextFile()) { + f.getName(nameBuf, sizeof(nameBuf)); + f.close(); + + const std::string name(nameBuf); + // Keep thumbnail and cover BMPs — regenerating them is expensive. + if (FsHelpers::hasBmpExtension(name)) continue; + + const std::string fullPath = cachePath + "/" + name; + if (!Storage.remove(fullPath.c_str())) { + LOG_ERR("EPB", "Failed to remove cache file: %s", fullPath.c_str()); + anyFailed = true; + } + } + dir.close(); + LOG_DBG("EPB", "Cache cleared successfully"); - return true; + return !anyFailed; } void Epub::setupCacheDir() const { diff --git a/lib/Epub/Epub.h b/lib/Epub/Epub.h index 15b77d77..07168716 100644 --- a/lib/Epub/Epub.h +++ b/lib/Epub/Epub.h @@ -48,7 +48,7 @@ class Epub { ~Epub() = default; std::string& getBasePath() { return contentBasePath; } bool load(bool buildIfMissing = true, bool skipLoadingCss = false); - bool clearCache() const; + bool clearCache(bool preserveThumbs = false) const; void setupCacheDir() const; const std::string& getCachePath() const; const std::string& getPath() const; diff --git a/lib/Epub/Epub/converters/JpegToFramebufferConverter.cpp b/lib/Epub/Epub/converters/JpegToFramebufferConverter.cpp index cbfe1735..06ed2095 100644 --- a/lib/Epub/Epub/converters/JpegToFramebufferConverter.cpp +++ b/lib/Epub/Epub/converters/JpegToFramebufferConverter.cpp @@ -189,10 +189,6 @@ constexpr size_t MIN_FREE_HEAP_FOR_JPEG = JPEG_DECODER_APPROX_SIZE + 16 * 1024; #define JPEG_CACHE_MIN_FREE_HEAP_MARGIN (24 * 1024) #endif -#ifndef JPEG_CACHE_MIN_MAX_ALLOC_MARGIN -#define JPEG_CACHE_MIN_MAX_ALLOC_MARGIN (20 * 1024) -#endif - #ifndef JPEG_DITHER_LOW_MEM_MIN_FREE_HEAP #define JPEG_DITHER_LOW_MEM_MIN_FREE_HEAP (MIN_FREE_HEAP_FOR_JPEG + 8 * 1024) #endif @@ -216,9 +212,7 @@ bool shouldEnableJpegCache(const RenderConfig& config, int width, int height) { const size_t cacheBytes = jpegCacheBytes(width, height); const size_t freeHeap = ESP.getFreeHeap(); - const size_t maxAlloc = ESP.getMaxAllocHeap(); const size_t minFreeForCaching = MIN_FREE_HEAP_FOR_JPEG + JPEG_CACHE_MIN_FREE_HEAP_MARGIN; - const size_t minMaxAllocForCaching = cacheBytes + JPEG_CACHE_MIN_MAX_ALLOC_MARGIN; if (freeHeap < minFreeForCaching) { LOG_DBG("JPG", "Skipping cache: free heap %u < %u (cache %u bytes)", static_cast(freeHeap), @@ -226,12 +220,9 @@ bool shouldEnableJpegCache(const RenderConfig& config, int width, int height) { return false; } - if (maxAlloc < minMaxAllocForCaching) { - LOG_DBG("JPG", "Skipping cache: max alloc %u < %u (cache %u bytes)", static_cast(maxAlloc), - static_cast(minMaxAllocForCaching), static_cast(cacheBytes)); - return false; - } - + // Don't pre-check maxAlloc: the JPEG decoder (~20 KB) is already allocated here so + // maxAlloc already reflects that. Let allocate() attempt malloc and fail gracefully + // rather than refusing on a conservative margin that double-counts live allocations. return true; } @@ -306,12 +297,8 @@ bool readJpegDimensionsFromHeader(const std::string& imagePath, ImageDimensions& return false; } - constexpr int MAX_SOURCE_PIXELS = 3145728; // Keep in sync with ImageToFramebufferDecoder contract. - const int widthInt = static_cast(width); - const int heightInt = static_cast(height); if (width > static_cast(std::numeric_limits::max()) || - height > static_cast(std::numeric_limits::max()) || - widthInt * heightInt > MAX_SOURCE_PIXELS) { + height > static_cast(std::numeric_limits::max())) { LOG_ERR("JPG", "JPEG dimensions out of supported range %ux%u: %s", width, height, imagePath.c_str()); return false; } @@ -608,11 +595,6 @@ bool JpegToFramebufferConverter::decodeToFramebuffer(const std::string& imagePat return false; } - if (!validateImageDimensions(srcWidth, srcHeight, "JPEG")) { - jpeg->close(); - return false; - } - bool isProgressive = jpeg->getJPEGType() == JPEG_MODE_PROGRESSIVE; if (isProgressive) { LOG_INF("JPG", "Progressive JPEG detected - decoding DC coefficients only (lower quality)"); @@ -651,6 +633,13 @@ bool JpegToFramebufferConverter::decodeToFramebuffer(const std::string& imagePat ctx.scaledSrcWidth = (srcWidth + jpegScaleDenom - 1) / jpegScaleDenom; ctx.scaledSrcHeight = (srcHeight + jpegScaleDenom - 1) / jpegScaleDenom; + + // Validate memory footprint against the post-scaling decode size, not raw dimensions. + // A 1447x2200 image decoded at 1/4 scale is only ~362x550 — well within limits. + if (!validateImageDimensions(ctx.scaledSrcWidth, ctx.scaledSrcHeight, "JPEG")) { + jpeg->close(); + return false; + } ctx.dstWidth = destWidth; ctx.dstHeight = destHeight; if (destWidth <= 0 || destHeight <= 0) { diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 7990f6ee..3f790fdb 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -765,7 +765,7 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction uint16_t backupPage = section->currentPage; uint16_t backupPageCount = section->pageCount; section.reset(); - epub->clearCache(); + epub->clearCache(true); epub->setupCacheDir(); saveProgress(backupSpine, backupPage, backupPageCount); if (!bookmarkStore.isEmpty()) {