feat: ports hr tag rendering from crossink (#2117)
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
#include "Page.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <Logging.h>
|
||||
#include <Serialization.h>
|
||||
|
||||
#include <new>
|
||||
|
||||
void PageLine::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) {
|
||||
block->render(renderer, fontId, xPos + xOffset, yPos + yOffset);
|
||||
}
|
||||
@@ -48,6 +51,47 @@ std::unique_ptr<PageImage> PageImage::deserialize(FsFile& file) {
|
||||
return std::unique_ptr<PageImage>(new PageImage(std::move(ib), xPos, yPos));
|
||||
}
|
||||
|
||||
void PageHorizontalRule::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) {
|
||||
(void)fontId;
|
||||
if (width == 0 || thickness == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderer.drawLine(xPos + xOffset, yPos + yOffset, xPos + xOffset + width - 1, yPos + yOffset, thickness, true);
|
||||
}
|
||||
|
||||
bool PageHorizontalRule::serialize(FsFile& file) {
|
||||
serialization::writePod(file, xPos);
|
||||
serialization::writePod(file, yPos);
|
||||
serialization::writePod(file, width);
|
||||
serialization::writePod(file, thickness);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<PageHorizontalRule> PageHorizontalRule::deserialize(FsFile& file) {
|
||||
int16_t xPos = 0;
|
||||
int16_t yPos = 0;
|
||||
uint16_t width = 0;
|
||||
uint8_t thickness = 0;
|
||||
serialization::readPod(file, xPos);
|
||||
serialization::readPod(file, yPos);
|
||||
serialization::readPod(file, width);
|
||||
serialization::readPod(file, thickness);
|
||||
|
||||
if (width == 0 || thickness == 0) {
|
||||
LOG_ERR("PGE", "Deserialization failed: invalid horizontal rule metadata (width=%u thickness=%u)", width,
|
||||
thickness);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto* rule = new (std::nothrow) PageHorizontalRule(width, thickness, xPos, yPos);
|
||||
if (!rule) {
|
||||
LOG_ERR("PGE", "Deserialization failed: could not allocate PageHorizontalRule");
|
||||
return nullptr;
|
||||
}
|
||||
return std::unique_ptr<PageHorizontalRule>(rule);
|
||||
}
|
||||
|
||||
void Page::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) const {
|
||||
for (auto& element : elements) {
|
||||
element->render(renderer, fontId, xOffset, yOffset);
|
||||
@@ -98,6 +142,12 @@ std::unique_ptr<Page> Page::deserialize(FsFile& file) {
|
||||
} else if (tag == TAG_PageImage) {
|
||||
auto pi = PageImage::deserialize(file);
|
||||
page->elements.push_back(std::move(pi));
|
||||
} else if (tag == TAG_PageHorizontalRule) {
|
||||
auto rule = PageHorizontalRule::deserialize(file);
|
||||
if (!rule) {
|
||||
return nullptr;
|
||||
}
|
||||
page->elements.push_back(std::move(rule));
|
||||
} else {
|
||||
LOG_ERR("PGE", "Deserialization failed: Unknown tag %u", tag);
|
||||
return nullptr;
|
||||
|
||||
+16
-1
@@ -12,7 +12,8 @@
|
||||
|
||||
enum PageElementTag : uint8_t {
|
||||
TAG_PageLine = 1,
|
||||
TAG_PageImage = 2, // New tag
|
||||
TAG_PageImage = 2,
|
||||
TAG_PageHorizontalRule = 3,
|
||||
};
|
||||
|
||||
// represents something that has been added to a page
|
||||
@@ -55,6 +56,20 @@ class PageImage final : public PageElement {
|
||||
const ImageBlock& getImageBlock() const { return *imageBlock; }
|
||||
};
|
||||
|
||||
class PageHorizontalRule final : public PageElement {
|
||||
uint16_t width;
|
||||
uint8_t thickness;
|
||||
|
||||
public:
|
||||
PageHorizontalRule(uint16_t width, uint8_t thickness, const int16_t xPos, const int16_t yPos)
|
||||
: PageElement(xPos, yPos), width(width), thickness(thickness) {}
|
||||
|
||||
void render(GfxRenderer& renderer, int fontId, int xOffset, int yOffset) override;
|
||||
bool serialize(FsFile& file) override;
|
||||
PageElementTag getTag() const override { return TAG_PageHorizontalRule; }
|
||||
static std::unique_ptr<PageHorizontalRule> deserialize(FsFile& file);
|
||||
};
|
||||
|
||||
class Page {
|
||||
public:
|
||||
// the list of block index and line numbers on this page
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
#include "parsers/ChapterHtmlSlimParser.h"
|
||||
|
||||
namespace {
|
||||
constexpr uint8_t SECTION_FILE_VERSION = 23;
|
||||
constexpr uint8_t SECTION_FILE_VERSION = 24;
|
||||
constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + sizeof(int) + sizeof(float) + sizeof(bool) + sizeof(uint8_t) +
|
||||
sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(bool) + sizeof(bool) +
|
||||
sizeof(uint8_t) + sizeof(bool) + sizeof(uint32_t) + sizeof(uint32_t) +
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
#include <XmlParserUtils.h>
|
||||
#include <expat.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <new>
|
||||
|
||||
#include "Epub.h"
|
||||
#include "Epub/Page.h"
|
||||
@@ -145,6 +147,68 @@ void ChapterHtmlSlimParser::startNewTextBlock(const BlockStyle& blockStyle) {
|
||||
wordsExtractedInBlock = 0;
|
||||
}
|
||||
|
||||
void ChapterHtmlSlimParser::emitHorizontalRule(const BlockStyle& blockStyle) {
|
||||
if (partWordBufferIndex > 0) {
|
||||
flushPartWordBuffer();
|
||||
}
|
||||
|
||||
if (currentTextBlock) {
|
||||
const BlockStyle parentBlockStyle = currentTextBlock->getBlockStyle();
|
||||
startNewTextBlock(parentBlockStyle);
|
||||
}
|
||||
|
||||
if (!currentPage) {
|
||||
currentPage.reset(new (std::nothrow) Page());
|
||||
if (!currentPage) {
|
||||
LOG_ERR("EHP", "Failed to create page for horizontal rule");
|
||||
return;
|
||||
}
|
||||
currentPageNextY = 0;
|
||||
}
|
||||
|
||||
const int16_t lineHeight = static_cast<int16_t>(renderer.getLineHeight(fontId) * lineCompression + 0.5f);
|
||||
const int16_t defaultVerticalSpacing = static_cast<int16_t>(lineHeight / 2);
|
||||
const int16_t topSpacing =
|
||||
static_cast<int16_t>((blockStyle.marginTop > 0 ? blockStyle.marginTop : defaultVerticalSpacing) +
|
||||
(blockStyle.paddingTop > 0 ? blockStyle.paddingTop : 0));
|
||||
const int16_t bottomSpacing =
|
||||
static_cast<int16_t>((blockStyle.marginBottom > 0 ? blockStyle.marginBottom : defaultVerticalSpacing) +
|
||||
(blockStyle.paddingBottom > 0 ? blockStyle.paddingBottom : 0));
|
||||
constexpr uint8_t ruleThickness = 2;
|
||||
const int16_t availableWidth =
|
||||
std::max<int16_t>(1, static_cast<int16_t>(viewportWidth - blockStyle.totalHorizontalInset()));
|
||||
const int16_t width = std::max<int16_t>(1, static_cast<int16_t>(availableWidth / 4));
|
||||
const int16_t xPos = static_cast<int16_t>(blockStyle.leftInset() + ((availableWidth - width) / 2));
|
||||
const int16_t totalHeight = static_cast<int16_t>(topSpacing + ruleThickness + bottomSpacing);
|
||||
|
||||
if (!currentPage->elements.empty() && currentPageNextY + totalHeight > viewportHeight) {
|
||||
completePageFn(std::move(currentPage), xpathParagraphIndex, xpathListItemIndex);
|
||||
completedPageCount++;
|
||||
currentPage.reset(new (std::nothrow) Page());
|
||||
if (!currentPage) {
|
||||
LOG_ERR("EHP", "Failed to create page after horizontal-rule page break");
|
||||
return;
|
||||
}
|
||||
currentPageNextY = 0;
|
||||
}
|
||||
|
||||
currentPageNextY += topSpacing;
|
||||
|
||||
auto pageRule = std::shared_ptr<PageHorizontalRule>(
|
||||
new (std::nothrow) PageHorizontalRule(width, ruleThickness, xPos, currentPageNextY));
|
||||
if (!pageRule) {
|
||||
LOG_ERR("EHP", "Failed to create PageHorizontalRule");
|
||||
return;
|
||||
}
|
||||
currentPage->elements.push_back(pageRule);
|
||||
currentPageNextY = static_cast<int16_t>(currentPageNextY + ruleThickness + bottomSpacing);
|
||||
|
||||
if (!pendingAnchorId.empty()) {
|
||||
anchorData.push_back({std::move(pendingAnchorId), static_cast<uint16_t>(completedPageCount)});
|
||||
pendingAnchorId.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* name, const XML_Char** atts) {
|
||||
auto* self = static_cast<ChapterHtmlSlimParser*>(userData);
|
||||
|
||||
@@ -262,6 +326,11 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->tableDepth == 1 && strcmp(name, "hr") == 0) {
|
||||
self->depth += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (matches(name, IMAGE_TAGS, std::size(IMAGE_TAGS))) {
|
||||
std::string src;
|
||||
std::string alt;
|
||||
@@ -600,6 +669,25 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
||||
const auto userAlignmentBlockStyle = BlockStyle::fromCssStyle(
|
||||
cssStyle, emSize, static_cast<CssTextAlign>(self->paragraphAlignment), self->viewportWidth);
|
||||
|
||||
if (strcmp(name, "hr") == 0) {
|
||||
auto hrBlockStyle = BlockStyle::fromCssStyle(cssStyle, emSize, CssTextAlign::Left, self->viewportWidth);
|
||||
if (!self->embeddedStyle) {
|
||||
hrBlockStyle.marginLeft = 0;
|
||||
hrBlockStyle.marginRight = 0;
|
||||
hrBlockStyle.marginTop = 0;
|
||||
hrBlockStyle.marginBottom = 0;
|
||||
hrBlockStyle.paddingLeft = 0;
|
||||
hrBlockStyle.paddingRight = 0;
|
||||
hrBlockStyle.paddingTop = 0;
|
||||
hrBlockStyle.paddingBottom = 0;
|
||||
hrBlockStyle.textIndentDefined = false;
|
||||
hrBlockStyle.textIndent = 0;
|
||||
}
|
||||
self->emitHorizontalRule(hrBlockStyle);
|
||||
self->depth += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (matches(name, HEADER_TAGS, std::size(HEADER_TAGS))) {
|
||||
self->currentCssStyle = cssStyle;
|
||||
auto headerBlockStyle = BlockStyle::fromCssStyle(cssStyle, emSize, CssTextAlign::Center, self->viewportWidth);
|
||||
|
||||
@@ -91,6 +91,7 @@ class ChapterHtmlSlimParser {
|
||||
void startNewTextBlock(const BlockStyle& blockStyle);
|
||||
void flushPartWordBuffer();
|
||||
void makePages();
|
||||
void emitHorizontalRule(const BlockStyle& blockStyle);
|
||||
// XML callbacks
|
||||
static void XMLCALL startElement(void* userData, const XML_Char* name, const XML_Char** atts);
|
||||
static void XMLCALL characterData(void* userData, const XML_Char* s, int len);
|
||||
|
||||
Reference in New Issue
Block a user