Add paragraph index LUT for accurate KOReader position sync
Store per-page paragraph indices in section cache to enable precise XPath-to-page and page-to-XPath mapping without reparsing XHTML. Forward path (upload): generates XPath directly from paragraph LUT instead of byte-offset estimation, eliminating drift in chapters with non-uniform content density. Reverse path (download): resolves incoming KOReader XPath p[N] to the exact page via paragraph LUT lookup. Paragraph counter counts all <p> elements including display:none to match ChapterXPathIndexer and crengine's standard XPath counting. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
dccb82642d
commit
b625b8bd26
@@ -491,6 +491,19 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
||||
}
|
||||
}
|
||||
|
||||
// Track body element depth for paragraph index counting
|
||||
if (strcmp(name, "body") == 0 && self->xpathBodyDepth < 0) {
|
||||
self->xpathBodyDepth = self->depth;
|
||||
}
|
||||
|
||||
// Count <p> sibling indices at body-child level. Must happen BEFORE the display:none
|
||||
// check so that hidden <p> elements are still counted, matching ChapterXPathIndexer's
|
||||
// counting (pure XML, no CSS). This ensures paragraph indices in the section cache LUT
|
||||
// align with KOReader's crengine XPath indices.
|
||||
if (self->xpathBodyDepth >= 0 && self->depth == self->xpathBodyDepth + 1 && strcmp(name, "p") == 0) {
|
||||
self->xpathParagraphIndex++;
|
||||
}
|
||||
|
||||
if (matches(name, SKIP_TAGS, NUM_SKIP_TAGS)) {
|
||||
// start skip
|
||||
self->skipUntilDepth = self->depth;
|
||||
@@ -1062,6 +1075,7 @@ bool ChapterHtmlSlimParser::parseAndBuildPages() {
|
||||
anchorData.push_back({std::move(pendingAnchorId), static_cast<uint16_t>(completedPageCount)});
|
||||
pendingAnchorId.clear();
|
||||
}
|
||||
paragraphIndexPerPage.push_back(xpathParagraphIndex);
|
||||
completePageFn(std::move(currentPage));
|
||||
completedPageCount++;
|
||||
currentPage.reset();
|
||||
@@ -1080,6 +1094,7 @@ void ChapterHtmlSlimParser::addLineToPage(std::shared_ptr<TextBlock> line) {
|
||||
}
|
||||
|
||||
if (currentPageNextY + lineHeight > viewportHeight) {
|
||||
paragraphIndexPerPage.push_back(xpathParagraphIndex);
|
||||
completePageFn(std::move(currentPage));
|
||||
completedPageCount++;
|
||||
currentPage.reset(new Page());
|
||||
|
||||
@@ -75,6 +75,14 @@ class ChapterHtmlSlimParser {
|
||||
std::vector<std::pair<std::string, uint16_t>> anchorData;
|
||||
std::string pendingAnchorId; // deferred until after previous text block is flushed
|
||||
|
||||
// Paragraph index tracking for XPath-to-page lookup table.
|
||||
// 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
|
||||
|
||||
// Footnote link tracking
|
||||
bool insideFootnoteLink = false;
|
||||
int footnoteLinkDepth = -1;
|
||||
@@ -126,4 +134,5 @@ class ChapterHtmlSlimParser {
|
||||
bool parseAndBuildPages();
|
||||
void addLineToPage(std::shared_ptr<TextBlock> line);
|
||||
const std::vector<std::pair<std::string, uint16_t>>& getAnchors() const { return anchorData; }
|
||||
const std::vector<uint16_t>& getParagraphIndexPerPage() const { return paragraphIndexPerPage; }
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user