From c65863cee5caf08d99f21ea12bb585d509be9a82 Mon Sep 17 00:00:00 2001 From: jpirnay Date: Mon, 4 May 2026 19:55:18 +0200 Subject: [PATCH] Adopt crossinks low mem guard idea --- .../Epub/parsers/ChapterHtmlSlimParser.cpp | 68 ++++++++++++++++++- lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h | 1 + 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp index f4b42591..b55e2d37 100644 --- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp +++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp @@ -1,5 +1,6 @@ #include "ChapterHtmlSlimParser.h" +#include #include #include #include @@ -30,6 +31,9 @@ constexpr size_t MIN_SIZE_FOR_POPUP = 15 * 1024; constexpr size_t SIZE_FOR_PROGRESS_HEARTBEAT = 30 * 1024; constexpr size_t SIZE_FOR_PROGRESS_FINE = 80 * 1024; constexpr size_t PARSE_BUFFER_SIZE = 1024; +constexpr size_t IMAGE_EXTRACT_CHUNK_SIZE = 1024; +constexpr size_t MIN_FREE_HEAP_FOR_IMAGE_EXTRACT = 48 * 1024; +constexpr size_t MIN_MAX_ALLOC_FOR_IMAGE_EXTRACT = 36 * 1024; const char* BLOCK_TAGS[] = {"p", "li", "div", "br", "blockquote", "pre"}; constexpr int NUM_BLOCK_TAGS = sizeof(BLOCK_TAGS) / sizeof(BLOCK_TAGS[0]); @@ -466,10 +470,48 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* if (!src.empty() && self->imageRendering != 1) { LOG_DBG("EHP", "Found image: src=%s", src.c_str()); + if (self->lowMemoryImageFallback) { + if (self->currentTextBlock && self->currentTextBlock->isEmpty()) { + BlockStyle resetStyle; + resetStyle.textAlignDefined = true; + const auto align = (self->paragraphAlignment == static_cast(CssTextAlign::None)) + ? CssTextAlign::Justify + : static_cast(self->paragraphAlignment); + resetStyle.alignment = align; + self->currentTextBlock->setBlockStyle(resetStyle); + } + self->skipUntilDepth = self->depth; + self->depth += 1; + return; + } + { // Resolve the image path relative to the HTML file std::string resolvedPath = FsHelpers::normalisePath(self->contentBase + src); + const uint32_t freeHeap = ESP.getFreeHeap(); + const uint32_t maxAllocHeap = ESP.getMaxAllocHeap(); + if (!self->lowMemoryImageFallback && + (freeHeap < MIN_FREE_HEAP_FOR_IMAGE_EXTRACT || maxAllocHeap < MIN_MAX_ALLOC_FOR_IMAGE_EXTRACT)) { + self->lowMemoryImageFallback = true; + LOG_ERR("EHP", "Low heap before image extraction (%u free, %u max alloc); suppressing inline images", + freeHeap, maxAllocHeap); + } + if (self->lowMemoryImageFallback) { + if (self->currentTextBlock && self->currentTextBlock->isEmpty()) { + BlockStyle resetStyle; + resetStyle.textAlignDefined = true; + const auto align = (self->paragraphAlignment == static_cast(CssTextAlign::None)) + ? CssTextAlign::Justify + : static_cast(self->paragraphAlignment); + resetStyle.alignment = align; + self->currentTextBlock->setBlockStyle(resetStyle); + } + self->skipUntilDepth = self->depth; + self->depth += 1; + return; + } + if (ImageDecoderFactory::isFormatSupported(resolvedPath)) { // Create a unique filename for the cached image std::string ext; @@ -483,13 +525,37 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* FsFile cachedImageFile; bool extractSuccess = false; if (Storage.openFileForWrite("EHP", cachedImagePath, cachedImageFile)) { - extractSuccess = self->epub->readItemContentsToStream(resolvedPath, cachedImageFile, 4096); + extractSuccess = + self->epub->readItemContentsToStream(resolvedPath, cachedImageFile, IMAGE_EXTRACT_CHUNK_SIZE); cachedImageFile.flush(); cachedImageFile.close(); delay(50); // Give SD card time to sync } if (extractSuccess) { + const uint32_t postExtractFreeHeap = ESP.getFreeHeap(); + const uint32_t postExtractMaxAllocHeap = ESP.getMaxAllocHeap(); + if (postExtractFreeHeap < MIN_FREE_HEAP_FOR_IMAGE_EXTRACT || + postExtractMaxAllocHeap < MIN_MAX_ALLOC_FOR_IMAGE_EXTRACT) { + self->lowMemoryImageFallback = true; + LOG_ERR("EHP", + "Low heap after image extraction (%u free, %u max alloc); suppressing remaining inline images", + postExtractFreeHeap, postExtractMaxAllocHeap); + Storage.remove(cachedImagePath.c_str()); + if (self->currentTextBlock && self->currentTextBlock->isEmpty()) { + BlockStyle resetStyle; + resetStyle.textAlignDefined = true; + const auto align = (self->paragraphAlignment == static_cast(CssTextAlign::None)) + ? CssTextAlign::Justify + : static_cast(self->paragraphAlignment); + resetStyle.alignment = align; + self->currentTextBlock->setBlockStyle(resetStyle); + } + self->skipUntilDepth = self->depth; + self->depth += 1; + return; + } + // Get image dimensions // Get image dimensions ImageDimensions dims = {0, 0}; ImageToFramebufferDecoder* decoder = ImageDecoderFactory::getDecoder(cachedImagePath); diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h index cb2df47a..5e5e4250 100644 --- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h +++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h @@ -57,6 +57,7 @@ class ChapterHtmlSlimParser final : public Print { std::string contentBase; std::string imageBasePath; int imageCounter = 0; + bool lowMemoryImageFallback = false; // Style tracking (replaces depth-based approach) struct StyleStackEntry {