diff --git a/lib/Epub/Epub/Page.cpp b/lib/Epub/Epub/Page.cpp index 907ad492..33379f26 100644 --- a/lib/Epub/Epub/Page.cpp +++ b/lib/Epub/Epub/Page.cpp @@ -49,6 +49,25 @@ std::unique_ptr PageImage::deserialize(FsFile& file) { return std::unique_ptr(new PageImage(std::move(ib), xPos, yPos)); } +void PageHR::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) { + renderer.drawLine(xPos + xOffset, yPos + yOffset, xPos + xOffset + width - 1, yPos + yOffset); +} + +bool PageHR::serialize(FsFile& file) { + serialization::writePod(file, xPos); + serialization::writePod(file, yPos); + serialization::writePod(file, width); + return true; +} + +std::unique_ptr PageHR::deserialize(FsFile& file) { + int16_t xPos, yPos, width; + serialization::readPod(file, xPos); + serialization::readPod(file, yPos); + serialization::readPod(file, width); + return std::unique_ptr(new PageHR(xPos, yPos, width)); +} + void PageTableFragment::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) { const int drawX = xPos + xOffset; const int drawY = yPos + yOffset; @@ -237,6 +256,10 @@ std::unique_ptr Page::deserialize(FsFile& file) { auto pt = PageTableFragment::deserialize(file); if (!pt) return nullptr; page->elements.push_back(std::move(pt)); + } else if (tag == TAG_PageHR) { + auto hr = PageHR::deserialize(file); + if (!hr) return nullptr; + page->elements.push_back(std::move(hr)); } else { LOG_ERR("PGE", "Deserialization failed: Unknown tag %u", tag); return nullptr; diff --git a/lib/Epub/Epub/Page.h b/lib/Epub/Epub/Page.h index 8b91fb92..828868ff 100644 --- a/lib/Epub/Epub/Page.h +++ b/lib/Epub/Epub/Page.h @@ -21,6 +21,7 @@ enum PageElementTag : uint8_t { TAG_PageLine = 1, TAG_PageImage = 2, TAG_PageTable = 3, + TAG_PageHR = 4, }; // represents something that has been added to a page @@ -63,6 +64,17 @@ class PageImage final : public PageElement { const ImageBlock& getImageBlock() const { return *imageBlock; } }; +class PageHR final : public PageElement { + int16_t width; + + public: + PageHR(const int16_t xPos, const int16_t yPos, const int16_t width) : PageElement(xPos, yPos), width(width) {} + void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override; + bool serialize(FsFile& file) override; + PageElementTag getTag() const override { return TAG_PageHR; } + static std::unique_ptr deserialize(FsFile& file); +}; + struct TableCell { std::vector> lines; bool isHeader = false; diff --git a/lib/Epub/Epub/Section.cpp b/lib/Epub/Epub/Section.cpp index c44741d9..e4a047b6 100644 --- a/lib/Epub/Epub/Section.cpp +++ b/lib/Epub/Epub/Section.cpp @@ -14,7 +14,7 @@ #include "parsers/ChapterHtmlSlimParser.h" namespace { -constexpr uint8_t SECTION_FILE_VERSION = 27; +constexpr uint8_t SECTION_FILE_VERSION = 28; constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + // SECTION_FILE_VERSION sizeof(int) + // fontId sizeof(float) + // lineCompression diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp index 179f03f3..7abc5617 100644 --- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp +++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp @@ -1049,6 +1049,28 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* self->preUntilDepth = std::min(self->preUntilDepth, self->depth); } } + } else if (strcmp(name, "hr") == 0) { + if (self->partWordBufferIndex > 0) { + if (!self->flushPartWordBuffer()) return; + } + self->makePages(); + if (!self->currentPage) { + self->currentPage.reset(new Page()); + self->currentPageNextY = 0; + } + const int lineHeight = static_cast(self->renderer.getLineHeight(self->fontId) * self->lineCompression + 0.5f); + const int16_t marginV = static_cast(lineHeight / 2); + self->currentPageNextY += marginV; + if (self->currentPageNextY + 1 + marginV > self->viewportHeight) { + self->emitPage(self->lastBodyChildByteOffset); + self->currentPage.reset(new Page()); + self->currentPageNextY = 0; + } + self->currentPage->elements.push_back( + std::make_shared(0, self->currentPageNextY, static_cast(self->viewportWidth))); + self->currentPageNextY += 1 + marginV; + BlockStyle emptyStyle; + self->startNewTextBlock(emptyStyle); } else if (matches(name, UNDERLINE_TAGS, NUM_UNDERLINE_TAGS) || matches(name, STRIKETHROUGH_TAGS, NUM_STRIKETHROUGH_TAGS)) { // Flush buffer before style change so preceding text gets current style diff --git a/scripts/generate_test_epub.py b/scripts/generate_test_epub.py index 99868cf6..f13bb192 100644 --- a/scripts/generate_test_epub.py +++ b/scripts/generate_test_epub.py @@ -1014,6 +1014,7 @@ def main():
  • pre element: intrinsic line breaks preserved
  • pre element: leading/trailing blank lines
  • pre with inline code element
  • +
  • horizontal rules between paragraphs
  • """, ), @@ -1061,6 +1062,29 @@ Fifth line (blank line above) greet("World");

    Normal paragraph after pre/code block.

    +""", + ), + [], + ), + ( + "4. Horizontal Rules", + make_chapter( + "Horizontal Rule Tests", + """ +

    A plain <hr> between two paragraphs. A thin line should appear between the two blocks of text.

    +

    Paragraph before the first rule. Lorem ipsum dolor sit amet, consectetur adipiscing elit.

    +
    +

    Paragraph after the first rule. The rule above should be a full-width horizontal line.

    +
    +

    Second rule above. Two rules in a row with no text between them:

    +
    +
    +

    Two rules appeared above. Now a rule right after the heading:

    +
    +

    Rule appeared right after the paragraph above. Finally, a rule near the end of the page to verify it does not cause a spurious page break when there is still room:

    +

    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

    +
    +

    End of horizontal rule tests.

    """, ), [], diff --git a/test/epubs/test_jpeg_images.epub b/test/epubs/test_jpeg_images.epub index a99251f0..600bdbfb 100644 Binary files a/test/epubs/test_jpeg_images.epub and b/test/epubs/test_jpeg_images.epub differ diff --git a/test/epubs/test_mixed_images.epub b/test/epubs/test_mixed_images.epub index 6d0a41f1..9c3826f6 100644 Binary files a/test/epubs/test_mixed_images.epub and b/test/epubs/test_mixed_images.epub differ diff --git a/test/epubs/test_png_images.epub b/test/epubs/test_png_images.epub index 5a405f88..4f1a548a 100644 Binary files a/test/epubs/test_png_images.epub and b/test/epubs/test_png_images.epub differ diff --git a/test/epubs/test_text_rendering.epub b/test/epubs/test_text_rendering.epub index 2067f55c..74be4b31 100644 Binary files a/test/epubs/test_text_rendering.epub and b/test/epubs/test_text_rendering.epub differ