feat: ports hr tag rendering from crossink (#2117)
This commit is contained in:
+36
-15
@@ -61,29 +61,29 @@ struct TocEntry {
|
||||
struct BookBin {
|
||||
// Header
|
||||
u8 version [[comment("Format version"), color("FFD93D")]];
|
||||
|
||||
|
||||
// Version validation
|
||||
if (version != EXPECTED_VERSION) {
|
||||
std::error(std::format("Unsupported version: {} (expected {})", version, EXPECTED_VERSION));
|
||||
}
|
||||
|
||||
|
||||
u32 lutOffset [[comment("Offset to lookup tables"), color("6BCB77")]];
|
||||
u16 spineCount [[comment("Number of spine entries"), color("4D96FF")]];
|
||||
u16 tocCount [[comment("Number of TOC entries"), color("FF6B9D")]];
|
||||
|
||||
|
||||
// Metadata section
|
||||
Metadata metadata [[comment("Book metadata")]];
|
||||
|
||||
|
||||
// Validate LUT offset alignment
|
||||
u32 currentOffset = $;
|
||||
if (currentOffset != lutOffset) {
|
||||
std::warning(std::format("LUT offset mismatch: expected 0x{:X}, got 0x{:X}", lutOffset, currentOffset));
|
||||
}
|
||||
|
||||
|
||||
// Lookup Tables
|
||||
u32 spineLut[spineCount] [[comment("Spine entry offsets"), color("4D96FF")]];
|
||||
u32 tocLut[tocCount] [[comment("TOC entry offsets"), color("FF6B9D")]];
|
||||
|
||||
|
||||
// Data Entries
|
||||
SpineEntry spines[spineCount] [[comment("Spine entries (reading order)")]];
|
||||
TocEntry toc[tocCount] [[comment("Table of contents entries")]];
|
||||
@@ -104,7 +104,7 @@ if (parsedSize != fileSize) {
|
||||
|
||||
## `section.bin`
|
||||
|
||||
### Version 8
|
||||
### Version 24
|
||||
|
||||
ImHex Pattern:
|
||||
|
||||
@@ -114,7 +114,7 @@ import std.string;
|
||||
import std.core;
|
||||
|
||||
// === Configuration ===
|
||||
#define EXPECTED_VERSION 8
|
||||
#define EXPECTED_VERSION 24
|
||||
#define MAX_STRING_LENGTH 65535
|
||||
|
||||
// === String Structure ===
|
||||
@@ -133,8 +133,10 @@ fn format_string(String s) {
|
||||
|
||||
// === Page Structure ===
|
||||
|
||||
enum StorageType : u8 {
|
||||
PageLine = 1
|
||||
enum PageElementTag : u8 {
|
||||
PageLine = 1,
|
||||
PageImage = 2,
|
||||
PageHorizontalRule = 3
|
||||
};
|
||||
|
||||
enum WordStyle : u8 {
|
||||
@@ -161,10 +163,29 @@ struct PageLine {
|
||||
BlockStyle blockStyle;
|
||||
};
|
||||
|
||||
struct PageImage {
|
||||
s16 xPos;
|
||||
s16 yPos;
|
||||
String imagePath;
|
||||
s16 width;
|
||||
s16 height;
|
||||
};
|
||||
|
||||
struct PageHorizontalRule {
|
||||
s16 xPos;
|
||||
s16 yPos;
|
||||
u16 width;
|
||||
u8 thickness;
|
||||
};
|
||||
|
||||
struct PageElement {
|
||||
u8 pageElementType;
|
||||
if (pageElementType == 1) {
|
||||
PageLine pageLine [[inline]];
|
||||
} else if (pageElementType == 2) {
|
||||
PageImage pageImage [[inline]];
|
||||
} else if (pageElementType == 3) {
|
||||
PageHorizontalRule horizontalRule [[inline]];
|
||||
} else {
|
||||
std::error(std::format("Unknown page element type: {}", pageElementType));
|
||||
}
|
||||
@@ -180,12 +201,12 @@ struct Page {
|
||||
struct SectionBin {
|
||||
// Header
|
||||
u8 version [[comment("Format version"), color("FFD93D")]];
|
||||
|
||||
|
||||
// Version validation
|
||||
if (version != EXPECTED_VERSION) {
|
||||
std::error(std::format("Unsupported version: {} (expected {})", version, EXPECTED_VERSION));
|
||||
}
|
||||
|
||||
|
||||
// Cache busting parameters
|
||||
s32 fontId;
|
||||
float lineCompression;
|
||||
@@ -194,15 +215,15 @@ struct SectionBin {
|
||||
u16 vieportHeight;
|
||||
u16 pageCount;
|
||||
u32 lutOffset;
|
||||
|
||||
|
||||
Page page[pageCount];
|
||||
|
||||
|
||||
// Validate LUT offset alignment
|
||||
u32 currentOffset = $;
|
||||
if (currentOffset != lutOffset) {
|
||||
std::warning(std::format("LUT offset mismatch: expected 0x{:X}, got 0x{:X}", lutOffset, currentOffset));
|
||||
}
|
||||
|
||||
|
||||
// Lookup Tables
|
||||
u32 lut[pageCount];
|
||||
};
|
||||
|
||||
@@ -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