diff --git a/lib/KOReaderSync/ProgressMapper.cpp b/lib/KOReaderSync/ProgressMapper.cpp index da3f812e..146d5abe 100644 --- a/lib/KOReaderSync/ProgressMapper.cpp +++ b/lib/KOReaderSync/ProgressMapper.cpp @@ -56,6 +56,64 @@ int parseTextNodeIndex(const std::string& xpath) { return val > 0 ? val : 1; } +bool isChapterStartXPath(const std::string& xpath) { + if (xpath.find("/p[") != std::string::npos || xpath.find("/li[") != std::string::npos) { + return false; + } + + static constexpr char kDocFragment[] = "/body/DocFragment["; + const size_t docFragPos = xpath.find(kDocFragment); + if (docFragPos == std::string::npos) { + return false; + } + const size_t docFragEnd = xpath.find(']', docFragPos + strlen(kDocFragment)); + if (docFragEnd == std::string::npos) { + return false; + } + if (docFragEnd + 1 == xpath.size()) { + return true; + } + if (xpath[docFragEnd + 1] == '.') { + if (docFragEnd + 2 >= xpath.size()) { + return false; + } + for (size_t i = docFragEnd + 2; i < xpath.size(); i++) { + if (xpath[i] != '0') return false; + } + return true; + } + + static constexpr char kDocBody[] = "]/body"; + const size_t docBodyPos = xpath.find(kDocBody); + if (docBodyPos == std::string::npos) { + return false; + } + size_t bodyContentStart = docBodyPos + strlen(kDocBody); + if (bodyContentStart == xpath.size()) { + return true; + } + if (xpath[bodyContentStart] != '/') { + return false; + } + bodyContentStart++; + if (bodyContentStart == xpath.size()) { + return true; + } + + const size_t dotPos = xpath.rfind('.'); + if (dotPos == std::string::npos || dotPos <= bodyContentStart || dotPos + 1 >= xpath.size()) { + return false; + } + if (xpath.find('/', bodyContentStart) != std::string::npos) { + return false; + } + + for (size_t i = dotPos + 1; i < xpath.size(); i++) { + if (xpath[i] != '0') return false; + } + return true; +} + // Parsed representation of one step in the XPath ancestry. struct XPathStep { char tag[12]; // element name, null-terminated @@ -586,10 +644,12 @@ CrossPointPosition ProgressMapper::toCrossPoint(const std::shared_ptr& epu } float intra = 0.0f; + bool resolvedIntra = false; if (useAncestry) { ParagraphStreamer s(xpathSteps, xpathStepCount, xpathChar, xpathTextNode); if (streamSpine(epub, result.spineIndex, s) && s.found()) { intra = s.progress(); + resolvedIntra = true; const int pAtMatch = s.getParagraphAtMatch(); if (pAtMatch > 0) { result.paragraphIndex = static_cast(pAtMatch); @@ -615,11 +675,17 @@ CrossPointPosition ProgressMapper::toCrossPoint(const std::shared_ptr& epu ParagraphStreamer s(xpathP, xpathChar, xpathTextNode); if (streamSpine(epub, result.spineIndex, s) && s.found()) { intra = s.progress(); + resolvedIntra = true; LOG_DBG("PM", "XPath p[%d]/text()[%d]+%d -> %.1f%% (target=%zu total=%zu)", xpathP, xpathTextNode, xpathChar, intra * 100, s.getTargetVisChars(), s.getTotalVisChars()); } } - if (intra <= 0.0f) { + if (!resolvedIntra && xpathSpine >= 0 && xpathSpine < spineCount && isChapterStartXPath(koPos.xpath)) { + intra = 0.0f; + resolvedIntra = true; + LOG_DBG("PM", "Chapter-start XPath %s -> spine=%d page start", koPos.xpath.c_str(), result.spineIndex); + } + if (!resolvedIntra) { const size_t bytesIn = (targetBytes > prevCum) ? (targetBytes - prevCum) : 0; intra = std::max(0.0f, std::min(1.0f, static_cast(bytesIn) / static_cast(spineSize))); } @@ -696,4 +762,4 @@ std::string ProgressMapper::generateXPath(const std::shared_ptr& epub, int const int p = s.paragraphCount(); return (p > 0) ? base + "/p[" + std::to_string(p) + "]" : base; -} +} \ No newline at end of file