Picking up some itsthisjutin ideas
This commit is contained in:
+59
-13
@@ -12,7 +12,7 @@
|
||||
#include "parsers/ChapterHtmlSlimParser.h"
|
||||
|
||||
namespace {
|
||||
constexpr uint8_t SECTION_FILE_VERSION = 20;
|
||||
constexpr uint8_t SECTION_FILE_VERSION = 21;
|
||||
constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + // SECTION_FILE_VERSION
|
||||
sizeof(int) + // fontId
|
||||
sizeof(float) + // lineCompression
|
||||
@@ -309,12 +309,15 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
|
||||
serialization::writePod(file, page);
|
||||
}
|
||||
|
||||
// Write per-page paragraph index LUT for XPath-to-page resolution
|
||||
// Write per-page paragraph LUT: count + array of {xhtmlByteOffset(u32), paragraphIndex(u16)}.
|
||||
// The byte offset lets findXPathForParagraph seek near the target paragraph without scanning
|
||||
// from the beginning of the XHTML file, reducing SD reads on large chapters.
|
||||
const uint32_t paragraphLutOffset = file.position();
|
||||
const auto& paragraphPerPage = visitor.getParagraphIndexPerPage();
|
||||
serialization::writePod(file, static_cast<uint16_t>(paragraphPerPage.size()));
|
||||
for (const uint16_t& pIdx : paragraphPerPage) {
|
||||
serialization::writePod(file, pIdx);
|
||||
const auto& paragraphLut = visitor.getParagraphLutPerPage();
|
||||
serialization::writePod(file, static_cast<uint16_t>(paragraphLut.size()));
|
||||
for (const auto& entry : paragraphLut) {
|
||||
serialization::writePod(file, entry.xhtmlByteOffset);
|
||||
serialization::writePod(file, entry.paragraphIndex);
|
||||
}
|
||||
|
||||
// Patch header with final pageCount, lutOffset, anchorMapOffset, and paragraphLutOffset
|
||||
@@ -573,18 +576,20 @@ std::optional<uint16_t> Section::getPageForParagraphIndex(const uint16_t pIndex)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Validate that all entries fit within the file
|
||||
const uint32_t lutEnd = paragraphLutOffset + sizeof(uint16_t) + count * sizeof(uint16_t);
|
||||
// Each entry: uint32_t xhtmlByteOffset + uint16_t paragraphIndex
|
||||
constexpr uint32_t ENTRY_SIZE = sizeof(uint32_t) + sizeof(uint16_t);
|
||||
const uint32_t lutEnd = paragraphLutOffset + sizeof(uint16_t) + count * ENTRY_SIZE;
|
||||
if (lutEnd > fileSize) {
|
||||
f.close();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Find the first page whose paragraph index >= pIndex.
|
||||
// Each entry stores the <p> index at the time that page was completed.
|
||||
uint16_t resultPage = count - 1; // default to last page
|
||||
for (uint16_t i = 0; i < count; i++) {
|
||||
uint32_t byteOffset;
|
||||
uint16_t pagePIdx;
|
||||
serialization::readPod(f, byteOffset);
|
||||
serialization::readPod(f, pagePIdx);
|
||||
if (pagePIdx >= pIndex) {
|
||||
resultPage = i;
|
||||
@@ -620,18 +625,59 @@ std::optional<uint16_t> Section::getParagraphIndexForPage(const uint16_t page) c
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Validate that the target entry fits within the file
|
||||
const uint32_t entryEnd = paragraphLutOffset + sizeof(uint16_t) + (page + 1) * sizeof(uint16_t);
|
||||
// Each entry: uint32_t xhtmlByteOffset + uint16_t paragraphIndex
|
||||
constexpr uint32_t ENTRY_SIZE = sizeof(uint32_t) + sizeof(uint16_t);
|
||||
const uint32_t entryEnd = paragraphLutOffset + sizeof(uint16_t) + (page + 1) * ENTRY_SIZE;
|
||||
if (entryEnd > fileSize) {
|
||||
f.close();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Seek to the entry for the requested page
|
||||
f.seek(paragraphLutOffset + sizeof(uint16_t) + page * sizeof(uint16_t));
|
||||
// Seek directly to the paragraphIndex field of the requested entry (skip xhtmlByteOffset)
|
||||
f.seek(paragraphLutOffset + sizeof(uint16_t) + page * ENTRY_SIZE + sizeof(uint32_t));
|
||||
uint16_t pIdx;
|
||||
serialization::readPod(f, pIdx);
|
||||
|
||||
f.close();
|
||||
return pIdx;
|
||||
}
|
||||
|
||||
std::optional<uint32_t> Section::getXhtmlByteOffsetForPage(const uint16_t page) const {
|
||||
FsFile f;
|
||||
if (!Storage.openFileForRead("SCT", filePath, f)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const uint32_t fileSize = f.size();
|
||||
|
||||
f.seek(HEADER_SIZE - sizeof(uint32_t));
|
||||
uint32_t paragraphLutOffset;
|
||||
serialization::readPod(f, paragraphLutOffset);
|
||||
if (paragraphLutOffset == 0 || paragraphLutOffset >= fileSize) {
|
||||
f.close();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
f.seek(paragraphLutOffset);
|
||||
uint16_t count;
|
||||
serialization::readPod(f, count);
|
||||
if (count == 0 || page >= count) {
|
||||
f.close();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
constexpr uint32_t ENTRY_SIZE = sizeof(uint32_t) + sizeof(uint16_t);
|
||||
const uint32_t entryEnd = paragraphLutOffset + sizeof(uint16_t) + (page + 1) * ENTRY_SIZE;
|
||||
if (entryEnd > fileSize) {
|
||||
f.close();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
f.seek(paragraphLutOffset + sizeof(uint16_t) + page * ENTRY_SIZE);
|
||||
uint32_t byteOffset;
|
||||
serialization::readPod(f, byteOffset);
|
||||
|
||||
f.close();
|
||||
// A zero offset means the entry was recorded post-parse (last page), so it's unusable as a hint.
|
||||
return byteOffset > 0 ? std::optional<uint32_t>{byteOffset} : std::nullopt;
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ class Section {
|
||||
std::optional<uint16_t> getPageForAnchor(const std::string& anchor) const;
|
||||
|
||||
// Look up the page number for a paragraph index (1-based, from XPath p[N]).
|
||||
// Uses the per-page paragraph index LUT stored in the section cache.
|
||||
// Uses the per-page paragraph LUT stored in the section cache.
|
||||
// Returns nullopt if the paragraph LUT is not available (old cache format).
|
||||
std::optional<uint16_t> getPageForParagraphIndex(uint16_t pIndex) const;
|
||||
|
||||
@@ -76,4 +76,11 @@ class Section {
|
||||
// Returns the 1-based paragraph index of the last <p> element on or before the page.
|
||||
// Returns nullopt if the paragraph LUT is not available (old cache format).
|
||||
std::optional<uint16_t> getParagraphIndexForPage(uint16_t page) const;
|
||||
|
||||
// Look up the XHTML byte offset recorded at the page break that started the given page.
|
||||
// This is the Expat byte position within the decompressed spine XHTML file — useful as a
|
||||
// seek hint for findXPathForParagraph to avoid scanning from byte 0 on large chapters.
|
||||
// Returns nullopt if the paragraph LUT is unavailable (old cache format) or offset is 0
|
||||
// (last page, recorded after parse completion).
|
||||
std::optional<uint32_t> getXhtmlByteOffsetForPage(uint16_t page) const;
|
||||
};
|
||||
|
||||
@@ -593,7 +593,9 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
||||
(self->currentPageNextY + totalImageHeightWithSpacing > self->viewportHeight)) {
|
||||
LOG_DBG("EHP", "Image page break: currentY=%d needed=%d viewportH=%d", self->currentPageNextY,
|
||||
totalImageHeightWithSpacing, self->viewportHeight);
|
||||
self->paragraphIndexPerPage.push_back(self->xpathParagraphIndex);
|
||||
const uint32_t byteOff =
|
||||
self->activeParser ? static_cast<uint32_t>(XML_GetCurrentByteIndex(self->activeParser)) : 0;
|
||||
self->paragraphLutPerPage.push_back({byteOff, self->xpathParagraphIndex});
|
||||
self->completePageFn(std::move(self->currentPage));
|
||||
self->completedPageCount++;
|
||||
self->currentPage.reset(new Page());
|
||||
@@ -1344,6 +1346,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
||||
XML_SetUserData(parser, this);
|
||||
XML_SetElementHandler(parser, startElement, endElement);
|
||||
XML_SetCharacterDataHandler(parser, characterData);
|
||||
activeParser = parser;
|
||||
|
||||
// Compute the time taken to parse and build pages
|
||||
const uint32_t chapterStartTime = millis();
|
||||
@@ -1354,6 +1357,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
||||
XML_StopParser(parser, XML_FALSE); // Stop any pending processing
|
||||
XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks
|
||||
XML_SetCharacterDataHandler(parser, nullptr);
|
||||
activeParser = nullptr;
|
||||
XML_ParserFree(parser);
|
||||
file.close();
|
||||
return false;
|
||||
@@ -1376,6 +1380,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
||||
XML_StopParser(parser, XML_FALSE); // Stop any pending processing
|
||||
XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks
|
||||
XML_SetCharacterDataHandler(parser, nullptr);
|
||||
activeParser = nullptr;
|
||||
XML_ParserFree(parser);
|
||||
file.close();
|
||||
return false;
|
||||
@@ -1389,6 +1394,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
||||
XML_StopParser(parser, XML_FALSE); // Stop any pending processing
|
||||
XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks
|
||||
XML_SetCharacterDataHandler(parser, nullptr);
|
||||
activeParser = nullptr;
|
||||
XML_ParserFree(parser);
|
||||
file.close();
|
||||
return false;
|
||||
@@ -1400,6 +1406,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
||||
XML_StopParser(parser, XML_FALSE); // Stop any pending processing
|
||||
XML_SetElementHandler(parser, nullptr, nullptr); // Clear callbacks
|
||||
XML_SetCharacterDataHandler(parser, nullptr);
|
||||
activeParser = nullptr;
|
||||
XML_ParserFree(parser);
|
||||
file.close();
|
||||
|
||||
@@ -1410,7 +1417,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
||||
anchorData.push_back({std::move(pendingAnchorId), static_cast<uint16_t>(completedPageCount)});
|
||||
pendingAnchorId.clear();
|
||||
}
|
||||
paragraphIndexPerPage.push_back(xpathParagraphIndex);
|
||||
paragraphLutPerPage.push_back({0u, xpathParagraphIndex}); // post-parse: no byte offset available
|
||||
completePageFn(std::move(currentPage));
|
||||
completedPageCount++;
|
||||
currentPage.reset();
|
||||
@@ -1431,7 +1438,8 @@ ParsedText::LineProcessResult ChapterHtmlSlimParser::addLineToPage(std::shared_p
|
||||
}
|
||||
|
||||
if (currentPageNextY + lineHeight > viewportHeight) {
|
||||
paragraphIndexPerPage.push_back(xpathParagraphIndex);
|
||||
const uint32_t byteOff = activeParser ? static_cast<uint32_t>(XML_GetCurrentByteIndex(activeParser)) : 0;
|
||||
paragraphLutPerPage.push_back({byteOff, xpathParagraphIndex});
|
||||
completePageFn(std::move(currentPage));
|
||||
completedPageCount++;
|
||||
currentPage.reset(new Page());
|
||||
|
||||
@@ -90,9 +90,19 @@ class ChapterHtmlSlimParser {
|
||||
// Counts <p> sibling indices (1-based, matching XPath convention) during page building.
|
||||
// Stored per page in the section cache so that XPath p[N] can be resolved to a page
|
||||
// without reparsing, and current page can generate an XPath without reparsing.
|
||||
uint16_t xpathParagraphIndex = 0; // current <p> sibling index (1-based)
|
||||
int xpathBodyDepth = -1; // depth of the <body> element (-1 = not yet seen)
|
||||
std::vector<uint16_t> paragraphIndexPerPage; // <p> index at each page completion
|
||||
uint16_t xpathParagraphIndex = 0; // current <p> sibling index (1-based)
|
||||
int xpathBodyDepth = -1; // depth of the <body> element (-1 = not yet seen)
|
||||
|
||||
struct ParagraphLutEntry {
|
||||
uint32_t xhtmlByteOffset; // Expat byte offset at page break — used to seek near target paragraph
|
||||
uint16_t paragraphIndex; // 1-based <p> index at page completion
|
||||
};
|
||||
std::vector<ParagraphLutEntry> paragraphLutPerPage; // deep LUT: one entry per page
|
||||
|
||||
// Active parser handle during parseAndBuildPages(), nullptr otherwise.
|
||||
// Stored as a member so page-break sites (addLineToPage, image breaks) can call
|
||||
// XML_GetCurrentByteIndex without needing the parser threaded through every call.
|
||||
XML_Parser activeParser = nullptr;
|
||||
|
||||
// Footnote link tracking
|
||||
bool insideFootnoteLink = false;
|
||||
@@ -154,5 +164,5 @@ class ChapterHtmlSlimParser {
|
||||
ParsedText::LineProcessResult addLineToPage(std::shared_ptr<TextBlock> line, bool lineEndsWithHyphenatedWord,
|
||||
bool suppressHyphenationRetry);
|
||||
const std::vector<std::pair<std::string, uint16_t>>& getAnchors() const { return anchorData; }
|
||||
const std::vector<uint16_t>& getParagraphIndexPerPage() const { return paragraphIndexPerPage; }
|
||||
const std::vector<ParagraphLutEntry>& getParagraphLutPerPage() const { return paragraphLutPerPage; }
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user