diff --git a/lib/EpdFont/EpdFont.cpp b/lib/EpdFont/EpdFont.cpp
index 8550cba4..5b770462 100644
--- a/lib/EpdFont/EpdFont.cpp
+++ b/lib/EpdFont/EpdFont.cpp
@@ -47,14 +47,6 @@ void EpdFont::getTextDimensions(const char* string, int* w, int* h) const {
*h = maxY - minY;
}
-bool EpdFont::hasPrintableChars(const char* string) const {
- int w = 0, h = 0;
-
- getTextDimensions(string, &w, &h);
-
- return w > 0 || h > 0;
-}
-
const EpdGlyph* EpdFont::getGlyph(const uint32_t cp) const {
const EpdUnicodeInterval* intervals = data->intervals;
const int count = data->intervalCount;
diff --git a/lib/EpdFont/EpdFont.h b/lib/EpdFont/EpdFont.h
index c8473fc0..5b0e2f9b 100644
--- a/lib/EpdFont/EpdFont.h
+++ b/lib/EpdFont/EpdFont.h
@@ -9,7 +9,6 @@ class EpdFont {
explicit EpdFont(const EpdFontData* data) : data(data) {}
~EpdFont() = default;
void getTextDimensions(const char* string, int* w, int* h) const;
- bool hasPrintableChars(const char* string) const;
const EpdGlyph* getGlyph(uint32_t cp) const;
};
diff --git a/lib/EpdFont/EpdFontFamily.cpp b/lib/EpdFont/EpdFontFamily.cpp
index 821153e3..5a1f8cef 100644
--- a/lib/EpdFont/EpdFontFamily.cpp
+++ b/lib/EpdFont/EpdFontFamily.cpp
@@ -22,10 +22,6 @@ void EpdFontFamily::getTextDimensions(const char* string, int* w, int* h, const
getFont(style)->getTextDimensions(string, w, h);
}
-bool EpdFontFamily::hasPrintableChars(const char* string, const Style style) const {
- return getFont(style)->hasPrintableChars(string);
-}
-
const EpdFontData* EpdFontFamily::getData(const Style style) const { return getFont(style)->data; }
const EpdGlyph* EpdFontFamily::getGlyph(const uint32_t cp, const Style style) const {
diff --git a/lib/EpdFont/EpdFontFamily.h b/lib/EpdFont/EpdFontFamily.h
index 64fd9953..746cc507 100644
--- a/lib/EpdFont/EpdFontFamily.h
+++ b/lib/EpdFont/EpdFontFamily.h
@@ -10,7 +10,6 @@ class EpdFontFamily {
: regular(regular), bold(bold), italic(italic), boldItalic(boldItalic) {}
~EpdFontFamily() = default;
void getTextDimensions(const char* string, int* w, int* h, Style style = REGULAR) const;
- bool hasPrintableChars(const char* string, Style style = REGULAR) const;
const EpdFontData* getData(Style style = REGULAR) const;
const EpdGlyph* getGlyph(uint32_t cp, Style style = REGULAR) const;
diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
index 560c730c..d7adb4fa 100644
--- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
+++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
@@ -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
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
+ // 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;
}
diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h
index 51b65592..4e146a64 100644
--- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h
+++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h
@@ -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] = {};
diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp
index ade9883e..2659ec19 100644
--- a/lib/GfxRenderer/GfxRenderer.cpp
+++ b/lib/GfxRenderer/GfxRenderer.cpp
@@ -11,7 +11,7 @@ void GfxRenderer::begin() {
}
}
-void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { fontMap.insert({fontId, font}); }
+void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { fontMap.insert({fontId, std::move(font)}); }
// Translate logical (x,y) coordinates to physical panel coordinates based on current orientation
// This should always be inlined for better performance
@@ -116,11 +116,6 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y, const cha
}
const auto font = fontMap.at(fontId);
- // no printable characters
- if (!font.hasPrintableChars(text, style)) {
- return;
- }
-
uint32_t cp;
while ((cp = utf8NextCodepoint(reinterpret_cast