Port 6 upstream PRs (PR #939 was already ported): - #852: Complete HalPowerManager with RAII Lock class, WiFi check in setPowerSaving, skipLoopDelay overrides for ClearCache/OtaUpdate, and power lock in Activity render task loops - #965: Fix paragraph formatting inside list items by tracking listItemUntilDepth to prevent unwanted line breaks - #972: Micro-optimizations: std::move in insertFont, const ref for getDataFromBook parameter - #971: Remove redundant hasPrintableChars pre-rendering pass from EpdFont, EpdFontFamily, and GfxRenderer - #977: Skip unsupported image formats before extraction, add PARSE_BUFFER_SIZE constant and chapter parse timing - #975: Fix UITheme memory leak by replacing raw pointer with std::unique_ptr for currentTheme Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -19,6 +19,7 @@ constexpr int NUM_HEADER_TAGS = sizeof(HEADER_TAGS) / sizeof(HEADER_TAGS[0]);
|
||||
|
||||
// Minimum file size (in bytes) to show indexing popup - smaller chapters don't benefit from it
|
||||
constexpr size_t MIN_SIZE_FOR_POPUP = 10 * 1024; // 10KB
|
||||
constexpr size_t PARSE_BUFFER_SIZE = 1024;
|
||||
|
||||
const char* BLOCK_TAGS[] = {"p", "li", "div", "br", "blockquote"};
|
||||
constexpr int NUM_BLOCK_TAGS = sizeof(BLOCK_TAGS) / sizeof(BLOCK_TAGS[0]);
|
||||
@@ -389,6 +390,9 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
||||
// Resolve the image path relative to the HTML file
|
||||
std::string resolvedPath = FsHelpers::normalisePath(self->contentBase + src);
|
||||
|
||||
// Check format support before any file I/O
|
||||
ImageToFramebufferDecoder* decoder = ImageDecoderFactory::getDecoder(resolvedPath);
|
||||
if (decoder) {
|
||||
// Create a unique filename for the cached image
|
||||
std::string ext;
|
||||
size_t extPos = resolvedPath.rfind('.');
|
||||
@@ -410,8 +414,7 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
||||
if (extractSuccess) {
|
||||
// Get image dimensions
|
||||
ImageDimensions dims = {0, 0};
|
||||
ImageToFramebufferDecoder* decoder = ImageDecoderFactory::getDecoder(cachedImagePath);
|
||||
if (decoder && decoder->getDimensions(cachedImagePath, dims)) {
|
||||
if (decoder->getDimensions(cachedImagePath, dims)) {
|
||||
LOG_DBG("EHP", "Image dimensions: %dx%d", dims.width, dims.height);
|
||||
|
||||
// Scale to fit viewport while maintaining aspect ratio
|
||||
@@ -470,6 +473,7 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
||||
} else {
|
||||
LOG_ERR("EHP", "Failed to extract image");
|
||||
}
|
||||
} // if (decoder)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -540,18 +544,24 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
||||
} else if (matches(name, BLOCK_TAGS, NUM_BLOCK_TAGS)) {
|
||||
if (strcmp(name, "br") == 0) {
|
||||
if (self->partWordBufferIndex > 0) {
|
||||
// flush word preceding <br/> to currentTextBlock before calling startNewTextBlock
|
||||
self->flushPartWordBuffer();
|
||||
}
|
||||
self->startNewTextBlock(self->currentTextBlock->getBlockStyle());
|
||||
} else if (strcmp(name, "li") == 0) {
|
||||
self->currentCssStyle = cssStyle;
|
||||
self->startNewTextBlock(userAlignmentBlockStyle);
|
||||
self->updateEffectiveInlineStyle();
|
||||
self->currentTextBlock->addWord("\xe2\x80\xa2", EpdFontFamily::REGULAR);
|
||||
self->listItemUntilDepth = std::min(self->listItemUntilDepth, self->depth);
|
||||
} else if (strcmp(name, "p") == 0 && self->listItemUntilDepth < self->depth) {
|
||||
// Inside a <li> element - don't start a new text block for <p>
|
||||
// This prevents bullet points from appearing on their own line
|
||||
self->currentCssStyle = cssStyle;
|
||||
self->updateEffectiveInlineStyle();
|
||||
} else {
|
||||
self->currentCssStyle = cssStyle;
|
||||
self->startNewTextBlock(userAlignmentBlockStyle);
|
||||
self->updateEffectiveInlineStyle();
|
||||
|
||||
if (strcmp(name, "li") == 0) {
|
||||
self->currentTextBlock->addWord("\xe2\x80\xa2", EpdFontFamily::REGULAR);
|
||||
}
|
||||
}
|
||||
} else if (matches(name, UNDERLINE_TAGS, NUM_UNDERLINE_TAGS)) {
|
||||
// Flush buffer before style change so preceding text gets current style
|
||||
@@ -807,6 +817,7 @@ void XMLCALL ChapterHtmlSlimParser::endElement(void* userData, const XML_Char* n
|
||||
if (self->boldUntilDepth == self->depth) self->boldUntilDepth = INT_MAX;
|
||||
if (self->italicUntilDepth == self->depth) self->italicUntilDepth = INT_MAX;
|
||||
if (self->underlineUntilDepth == self->depth) self->underlineUntilDepth = INT_MAX;
|
||||
if (self->listItemUntilDepth == self->depth) self->listItemUntilDepth = INT_MAX;
|
||||
if (!self->inlineStyleStack.empty() && self->inlineStyleStack.back().depth == self->depth) {
|
||||
self->inlineStyleStack.pop_back();
|
||||
self->updateEffectiveInlineStyle();
|
||||
@@ -852,6 +863,11 @@ void XMLCALL ChapterHtmlSlimParser::endElement(void* userData, const XML_Char* n
|
||||
self->underlineUntilDepth = INT_MAX;
|
||||
}
|
||||
|
||||
// Leaving list item
|
||||
if (self->listItemUntilDepth == self->depth) {
|
||||
self->listItemUntilDepth = INT_MAX;
|
||||
}
|
||||
|
||||
// Pop from inline style stack if we pushed an entry at this depth
|
||||
// This handles all inline elements: b, i, u, span, etc.
|
||||
if (!self->inlineStyleStack.empty() && self->inlineStyleStack.back().depth == self->depth) {
|
||||
@@ -867,6 +883,7 @@ void XMLCALL ChapterHtmlSlimParser::endElement(void* userData, const XML_Char* n
|
||||
}
|
||||
|
||||
bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
||||
unsigned long chapterStartTime = millis();
|
||||
auto paragraphAlignmentBlockStyle = BlockStyle();
|
||||
paragraphAlignmentBlockStyle.textAlignDefined = true;
|
||||
// Resolve None sentinel to Justify for initial block (no CSS context yet)
|
||||
@@ -904,7 +921,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
||||
XML_SetCharacterDataHandler(parser, characterData);
|
||||
|
||||
do {
|
||||
void* const buf = XML_GetBuffer(parser, 1024);
|
||||
void* const buf = XML_GetBuffer(parser, PARSE_BUFFER_SIZE);
|
||||
if (!buf) {
|
||||
LOG_ERR("EHP", "Couldn't allocate memory for buffer");
|
||||
XML_StopParser(parser, XML_FALSE); // Stop any pending processing
|
||||
@@ -915,7 +932,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t len = file.read(buf, 1024);
|
||||
const size_t len = file.read(buf, PARSE_BUFFER_SIZE);
|
||||
|
||||
if (len == 0 && file.available() > 0) {
|
||||
LOG_ERR("EHP", "File read error");
|
||||
@@ -955,6 +972,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
||||
currentTextBlock.reset();
|
||||
}
|
||||
|
||||
LOG_DBG("EHP", "Chapter parsed in %lu ms", millis() - chapterStartTime);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ class ChapterHtmlSlimParser {
|
||||
int boldUntilDepth = INT_MAX;
|
||||
int italicUntilDepth = INT_MAX;
|
||||
int underlineUntilDepth = INT_MAX;
|
||||
int listItemUntilDepth = INT_MAX;
|
||||
// buffer for building up words from characters, will auto break if longer than this
|
||||
// leave one char at end for null pointer
|
||||
char partWordBuffer[MAX_WORD_SIZE + 1] = {};
|
||||
|
||||
Reference in New Issue
Block a user