fix: KOReader sync drift when syncing at chapter start (#2245)

This commit is contained in:
Julia
2026-06-04 15:23:52 +03:00
committed by GitHub
parent 2d65808302
commit bb078bae09
+68 -2
View File
@@ -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<Epub>& 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<uint16_t>(pAtMatch);
@@ -615,11 +675,17 @@ CrossPointPosition ProgressMapper::toCrossPoint(const std::shared_ptr<Epub>& 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<float>(bytesIn) / static_cast<float>(spineSize)));
}
@@ -696,4 +762,4 @@ std::string ProgressMapper::generateXPath(const std::shared_ptr<Epub>& epub, int
const int p = s.paragraphCount();
return (p > 0) ? base + "/p[" + std::to_string(p) + "]" : base;
}
}