Merge branch 'master' into feat-supsub

This commit is contained in:
jpirnay
2026-05-15 20:54:49 +02:00
committed by GitHub
12 changed files with 179 additions and 69 deletions
+23
View File
@@ -49,6 +49,25 @@ std::unique_ptr<PageImage> PageImage::deserialize(FsFile& file) {
return std::unique_ptr<PageImage>(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> 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<PageHR>(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> 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;
+12
View File
@@ -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<PageHR> deserialize(FsFile& file);
};
struct TableCell {
std::vector<std::shared_ptr<TextBlock>> lines;
bool isHeader = false;
+33 -36
View File
@@ -14,30 +14,27 @@
#include "parsers/ChapterHtmlSlimParser.h"
namespace {
constexpr uint8_t SECTION_FILE_VERSION = 27;
constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + // SECTION_FILE_VERSION
sizeof(int) + // fontId
sizeof(float) + // lineCompression
sizeof(bool) + // extraParagraphSpacing
sizeof(uint8_t) + // paragraphAlignment
sizeof(uint16_t) + // viewportWidth
sizeof(uint16_t) + // viewportHeight
sizeof(bool) + // parseComplete
sizeof(uint16_t) + // pageCount (stored as 16-bit in header)
sizeof(bool) + // hyphenationEnabled
sizeof(bool) + // embeddedStyle
sizeof(bool) + // bionicReadingEnabled
sizeof(uint8_t) + // imageRendering
sizeof(uint32_t) + // page LUT offset
sizeof(uint32_t) + // anchor map offset
sizeof(uint32_t); // paragraph LUT offset
constexpr uint8_t SECTION_FILE_VERSION = 28;
constexpr uint32_t HEADER_TAIL_PARSE_COMPLETE_OFFSET =
HEADER_SIZE - sizeof(uint32_t) * 3 - sizeof(uint16_t) - sizeof(bool);
constexpr uint32_t HEADER_TAIL_PAGE_COUNT_OFFSET = HEADER_SIZE - sizeof(uint32_t) * 3 - sizeof(uint16_t);
constexpr uint32_t HEADER_TAIL_PAGE_LUT_OFFSET = HEADER_SIZE - sizeof(uint32_t) * 3;
constexpr uint32_t HEADER_TAIL_ANCHOR_OFFSET = HEADER_SIZE - sizeof(uint32_t) * 2;
constexpr uint32_t HEADER_TAIL_PARAGRAPH_LUT_OFFSET = HEADER_SIZE - sizeof(uint32_t);
namespace header {
constexpr uint32_t kVersion = 0;
constexpr uint32_t kFontId = kVersion + sizeof(uint8_t);
constexpr uint32_t kLineCompression = kFontId + sizeof(int);
constexpr uint32_t kExtraParagraphSpacing = kLineCompression + sizeof(float);
constexpr uint32_t kParagraphAlignment = kExtraParagraphSpacing + sizeof(bool);
constexpr uint32_t kViewportWidth = kParagraphAlignment + sizeof(uint8_t);
constexpr uint32_t kViewportHeight = kViewportWidth + sizeof(uint16_t);
constexpr uint32_t kHyphenationEnabled = kViewportHeight + sizeof(uint16_t);
constexpr uint32_t kEmbeddedStyle = kHyphenationEnabled + sizeof(bool);
constexpr uint32_t kBionicReadingEnabled = kEmbeddedStyle + sizeof(bool);
constexpr uint32_t kImageRendering = kBionicReadingEnabled + sizeof(bool);
constexpr uint32_t kParseComplete = kImageRendering + sizeof(uint8_t);
constexpr uint32_t kPageCount = kParseComplete + sizeof(bool);
constexpr uint32_t kPageLut = kPageCount + sizeof(uint16_t);
constexpr uint32_t kAnchorMap = kPageLut + sizeof(uint32_t);
constexpr uint32_t kParagraphLut = kAnchorMap + sizeof(uint32_t);
constexpr uint32_t kSize = kParagraphLut + sizeof(uint32_t);
} // namespace header
// On-disk paragraph LUT entry: u32 xhtmlByteOffset + u16 paragraphIndex + u16 listItemIndex.
// listItemIndex is the running <li> count at page-break time; together with
@@ -217,11 +214,12 @@ void Section::writeSectionFileHeader(const int fontId, const float lineCompressi
LOG_DBG("SCT", "File not open for writing header");
return;
}
static_assert(HEADER_SIZE == sizeof(SECTION_FILE_VERSION) + sizeof(fontId) + sizeof(lineCompression) +
sizeof(extraParagraphSpacing) + sizeof(paragraphAlignment) + sizeof(viewportWidth) +
sizeof(viewportHeight) + sizeof(bool) + sizeof(pageCount) +
sizeof(hyphenationEnabled) + sizeof(embeddedStyle) + sizeof(bionicReadingEnabled) +
sizeof(imageRendering) + sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint32_t),
static_assert(header::kSize == sizeof(SECTION_FILE_VERSION) + sizeof(fontId) + sizeof(lineCompression) +
sizeof(extraParagraphSpacing) + sizeof(paragraphAlignment) +
sizeof(viewportWidth) + sizeof(viewportHeight) + sizeof(hyphenationEnabled) +
sizeof(embeddedStyle) + sizeof(bionicReadingEnabled) + sizeof(imageRendering) +
sizeof(bool) + sizeof(pageCount) + sizeof(uint32_t) + sizeof(uint32_t) +
sizeof(uint32_t),
"Header size mismatch");
serialization::writePod(file, SECTION_FILE_VERSION);
serialization::writePod(file, fontId);
@@ -338,8 +336,8 @@ bool Section::loadSectionFile(const int fontId, const float lineCompression, con
}
for (uint32_t& pos : lut) {
serialization::readPod(file, pos);
if (pos < HEADER_SIZE || pos >= lutOffset) {
LOG_ERR("SCT", "Deserialization failed: LUT entry %u out of range [%u, %u)", pos, HEADER_SIZE, lutOffset);
if (pos < header::kSize || pos >= lutOffset) {
LOG_ERR("SCT", "Deserialization failed: LUT entry %u out of range [%u, %u)", pos, header::kSize, lutOffset);
clearCache();
return false;
}
@@ -572,9 +570,9 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
}
// Patch header with final parseComplete/pageCount and offsets.
const size_t headerPatchStart = HEADER_TAIL_PARSE_COMPLETE_OFFSET;
const size_t headerPatchStart = header::kParseComplete;
if (!file.seek(headerPatchStart)) {
LOG_ERR("SCT", "Failed to seek to section header patch offset %u", HEADER_TAIL_PARSE_COMPLETE_OFFSET);
LOG_ERR("SCT", "Failed to seek to section header patch offset %u", header::kParseComplete);
file.close();
Storage.remove(filePath.c_str());
return false;
@@ -730,8 +728,7 @@ void Section::buildTocBoundariesFromFile(FsFile& f) {
// Single pass through on-disk anchors, matching against cached TOC anchors.
// Stop early once all TOC anchors are resolved.
// Header layout: ... | lutOffset (u32) | anchorMapOffset (u32) | paragraphLutOffset (u32) |
f.seek(HEADER_TAIL_ANCHOR_OFFSET);
f.seek(header::kAnchorMap);
uint32_t anchorMapOffset;
serialization::readPod(f, anchorMapOffset);
@@ -801,7 +798,7 @@ std::optional<uint16_t> Section::getPageForAnchor(const std::string& anchor) con
}
const uint32_t fileSize = f.size();
f.seek(HEADER_TAIL_ANCHOR_OFFSET);
f.seek(header::kAnchorMap);
uint32_t anchorMapOffset;
serialization::readPod(f, anchorMapOffset);
if (anchorMapOffset == 0 || anchorMapOffset >= fileSize) {
@@ -834,7 +831,7 @@ bool Section::readParagraphLutHeader(FsFile& outFile, uint16_t& outCount, uint32
const uint32_t fileSize = outFile.size();
outFile.seek(HEADER_TAIL_PARAGRAPH_LUT_OFFSET);
outFile.seek(header::kParagraphLut);
uint32_t paragraphLutOffset;
serialization::readPod(outFile, paragraphLutOffset);
if (fileSize < sizeof(uint16_t) || paragraphLutOffset == 0 || paragraphLutOffset > fileSize - sizeof(uint16_t)) {
@@ -1064,6 +1064,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<int>(self->renderer.getLineHeight(self->fontId) * self->lineCompression + 0.5f);
const int16_t marginV = static_cast<int16_t>(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<PageHR>(0, self->currentPageNextY, static_cast<int16_t>(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