diff --git a/lib/Epub/Epub/blocks/TextBlock.cpp b/lib/Epub/Epub/blocks/TextBlock.cpp index 750d7b0e..a2baa39b 100644 --- a/lib/Epub/Epub/blocks/TextBlock.cpp +++ b/lib/Epub/Epub/blocks/TextBlock.cpp @@ -18,17 +18,22 @@ void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int renderer.drawText(fontId, wordX, y, words[i].c_str(), true, currentStyle); const std::string& w = words[i]; - const int fullWordWidth = renderer.getTextWidth(fontId, w.c_str(), currentStyle); - int startX = wordX; - int lineWidth = fullWordWidth; const bool hasEmSpacePrefix = w.size() >= 3 && static_cast(w[0]) == 0xE2 && static_cast(w[1]) == 0x80 && static_cast(w[2]) == 0x83; - if (hasEmSpacePrefix) { - const char* visiblePtr = w.c_str() + 3; - const int prefixWidth = renderer.getTextAdvanceX(fontId, "\xe2\x80\x83", currentStyle); - const int visibleWidth = renderer.getTextWidth(fontId, visiblePtr, currentStyle); - startX = wordX + prefixWidth; - lineWidth = visibleWidth; + const bool hasDecoration = (currentStyle & (EpdFontFamily::UNDERLINE | EpdFontFamily::STRIKETHROUGH)) != 0; + int startX = wordX; + int lineWidth = 0; + + if (hasEmSpacePrefix || hasDecoration) { + const int fullWordWidth = renderer.getTextWidth(fontId, w.c_str(), currentStyle); + lineWidth = fullWordWidth; + if (hasEmSpacePrefix) { + const char* visiblePtr = w.c_str() + 3; + const int prefixWidth = renderer.getTextAdvanceX(fontId, "\xe2\x80\x83", currentStyle); + const int visibleWidth = renderer.getTextWidth(fontId, visiblePtr, currentStyle); + startX = wordX + prefixWidth; + lineWidth = visibleWidth; + } } if ((currentStyle & EpdFontFamily::UNDERLINE) != 0) { diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp index fbcd39d4..8f74612b 100644 --- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp +++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp @@ -894,6 +894,19 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* entry.hasStrikethrough = true; entry.strikethrough = true; } + if (cssStyle.hasTextDecoration()) { + const uint8_t dec = static_cast(cssStyle.textDecoration); + if (dec & static_cast(CssTextDecoration::Underline)) { + entry.hasUnderline = true; + entry.underline = true; + self->underlineUntilDepth = std::min(self->underlineUntilDepth, self->depth); + } + if (dec & static_cast(CssTextDecoration::LineThrough)) { + entry.hasStrikethrough = true; + entry.strikethrough = true; + self->strikethroughUntilDepth = std::min(self->strikethroughUntilDepth, self->depth); + } + } if (cssStyle.hasFontWeight()) { entry.hasBold = true; entry.bold = cssStyle.fontWeight == CssFontWeight::Bold; diff --git a/lib/Md/MdParser.cpp b/lib/Md/MdParser.cpp index 2ee7a598..8d7d7a5f 100644 --- a/lib/Md/MdParser.cpp +++ b/lib/Md/MdParser.cpp @@ -93,7 +93,7 @@ std::vector parseInline(const std::string& text) { // Escaped character if (c == '\\' && i + 1 < text.size()) { char next = text[i + 1]; - if (next == '*' || next == '_' || next == '`' || next == '[' || next == '!' || next == '\\') { + if (next == '*' || next == '_' || next == '`' || next == '[' || next == '!' || next == '~' || next == '\\') { current += next; i += 2; continue; @@ -329,12 +329,10 @@ ParsedLine parseLine(const std::string& rawLine, bool inCodeBlock) { result.blockType = BlockType::Header3; result.spans = parseInline(content); - // Force bold on all header spans + // Force bold on all header spans while preserving any existing decoration bits. for (auto& span : result.spans) { - if (span.style == EpdFontFamily::REGULAR) - span.style = EpdFontFamily::BOLD; - else if (span.style == EpdFontFamily::ITALIC) - span.style = EpdFontFamily::BOLD_ITALIC; + span.style = static_cast(static_cast(span.style) | + static_cast(EpdFontFamily::BOLD)); } return result; } diff --git a/test/epub_css/CssParserTest.cpp b/test/epub_css/CssParserTest.cpp index ce91c30f..6081e2ff 100644 --- a/test/epub_css/CssParserTest.cpp +++ b/test/epub_css/CssParserTest.cpp @@ -15,13 +15,13 @@ static int testsFailed = 0; } \ } while (0) -#define ASSERT_TRUE(cond) \ - do { \ - if (!(cond)) { \ +#define ASSERT_TRUE(cond) \ + do { \ + if (!(cond)) { \ fprintf(stderr, " FAIL: %s:%d: %s is false\n", __FILE__, __LINE__, #cond); \ - testsFailed++; \ - return; \ - } \ + testsFailed++; \ + return; \ + } \ } while (0) #define PASS() testsPassed++ @@ -42,6 +42,14 @@ void testInlineUnderlineLineThrough() { PASS(); } +void testInlineLineThroughUnderlineOrderInsensitive() { + printf("testInlineLineThroughUnderlineOrderInsensitive...\n"); + const CssStyle style = CssParser::parseInlineStyle("text-decoration: line-through underline"); + ASSERT_TRUE(style.hasTextDecoration()); + ASSERT_EQ(style.textDecoration, CssTextDecoration::UnderlineLineThrough); + PASS(); +} + void testInlineTextDecorationNormalization() { printf("testInlineTextDecorationNormalization...\n"); const CssStyle style = CssParser::parseInlineStyle("TEXT-DECORATION : LINE-THROUGH ;"); @@ -55,6 +63,7 @@ int main() { testInlineLineThrough(); testInlineUnderlineLineThrough(); + testInlineLineThroughUnderlineOrderInsensitive(); testInlineTextDecorationNormalization(); printf("\n=== Results: %d passed, %d failed ===\n", testsPassed, testsFailed); diff --git a/test/md_parser/MdParserTest.cpp b/test/md_parser/MdParserTest.cpp index 2ab3fb2f..a42c3fc8 100644 --- a/test/md_parser/MdParserTest.cpp +++ b/test/md_parser/MdParserTest.cpp @@ -78,6 +78,15 @@ void testStrikethroughWorks() { PASS(); } +void testEscapedTildeDoesNotToggleStrikethrough() { + printf("testEscapedTildeDoesNotToggleStrikethrough...\n"); + auto spans = MdParser::parseInline("foo \\~~bar~~ baz"); + ASSERT_EQ(flattenText(spans), "foo ~~bar~~ baz"); + ASSERT_EQ(spans.size(), 1); + ASSERT_EQ(spans[0].style, EpdFontFamily::REGULAR); + PASS(); +} + void testUnderscoreBoldWorks() { printf("testUnderscoreBoldWorks...\n"); auto spans = MdParser::parseInline("foo __bar__ baz"); @@ -116,6 +125,7 @@ int main() { testAsteriskEmphasisStillWorks(); testStrikethroughWorks(); testUnderscoreBoldWorks(); + testEscapedTildeDoesNotToggleStrikethrough(); testNestedUnorderedListIndentLevel(); testNestedOrderedListIndentLevel(); diff --git a/test/run_epub_css_test.sh b/test/run_epub_css_test.sh index e9c6c023..6b1f0eab 100644 --- a/test/run_epub_css_test.sh +++ b/test/run_epub_css_test.sh @@ -7,6 +7,16 @@ BINARY="$BUILD_DIR/CssParserTest" PLATFORMIO_DIR="${PLATFORMIO_CORE_DIR:-$HOME/.platformio}" ARDUINO_FRAMEWORK_DIR="$PLATFORMIO_DIR/packages/framework-arduinoespressif32" +if [ ! -d "$PLATFORMIO_DIR" ]; then + echo "ERROR: PLATFORMIO_DIR does not exist: $PLATFORMIO_DIR" >&2 + exit 1 +fi + +if [ ! -d "$ARDUINO_FRAMEWORK_DIR" ]; then + echo "ERROR: ARDUINO_FRAMEWORK_DIR does not exist: $ARDUINO_FRAMEWORK_DIR" >&2 + exit 1 +fi + mkdir -p "$BUILD_DIR" SOURCES=(