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:
jpirnay
2026-03-22 12:29:30 +01:00
co-authored by Claude Opus 4.6
parent 5b710e960f
commit fd70b3a231
17 changed files with 354 additions and 43 deletions
@@ -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; }
};