Fix parent-element “exact” preemption
This commit is contained in:
@@ -148,8 +148,17 @@ struct ReverseState : StackState {
|
|||||||
const std::string xpath = normalizeXPath(currentXPath(spineIndex));
|
const std::string xpath = normalizeXPath(currentXPath(spineIndex));
|
||||||
const int depth = pathDepth(xpath);
|
const int depth = pathDepth(xpath);
|
||||||
|
|
||||||
|
const bool targetIsTextSelector = targetTextNodeIndex > 0;
|
||||||
|
|
||||||
if (xpath == targetNorm) {
|
if (xpath == targetNorm) {
|
||||||
tryUpdate(MatchTier::EXACT, depth, "exact", true);
|
// For /text()[N].M targets, the normalized parent element path is equal to
|
||||||
|
// targetNorm. Treat that as an ancestor-level anchor so text-node exact
|
||||||
|
// matching can still determine the real intra-node offset.
|
||||||
|
if (targetIsTextSelector) {
|
||||||
|
tryUpdate(MatchTier::ANCESTOR, depth, "text-parent", false);
|
||||||
|
} else {
|
||||||
|
tryUpdate(MatchTier::EXACT, depth, "exact", true);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isAncestorPath(xpath, targetNorm)) {
|
if (isAncestorPath(xpath, targetNorm)) {
|
||||||
@@ -225,8 +234,15 @@ bool findProgressForXPathInternal(const std::shared_ptr<Epub>& epub, const int s
|
|||||||
outIntraSpineProgress = std::max(0.0f, std::min(1.0f, outIntraSpineProgress));
|
outIntraSpineProgress = std::max(0.0f, std::min(1.0f, outIntraSpineProgress));
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_DBG("KOX", "Reverse: spine=%d %s match offset=%zu/%zu -> progress=%.3f for '%s'", spineIndex, state.bestTierName,
|
if (state.targetTextNodeIndex > 0) {
|
||||||
state.bestOffset, state.totalTextBytes, outIntraSpineProgress, xpath.c_str());
|
LOG_DBG("KOX",
|
||||||
|
"Reverse: spine=%d %s match textNode=%d char=%d offset=%zu/%zu -> progress=%.3f for '%s'",
|
||||||
|
spineIndex, state.bestTierName, state.targetTextNodeIndex, state.targetCharOffset, state.bestOffset,
|
||||||
|
state.totalTextBytes, outIntraSpineProgress, xpath.c_str());
|
||||||
|
} else {
|
||||||
|
LOG_DBG("KOX", "Reverse: spine=%d %s match offset=%zu/%zu -> progress=%.3f for '%s'", spineIndex,
|
||||||
|
state.bestTierName, state.bestOffset, state.totalTextBytes, outIntraSpineProgress, xpath.c_str());
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,44 @@
|
|||||||
|
|
||||||
#include "ChapterXPathIndexer.h"
|
#include "ChapterXPathIndexer.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
bool resolveFromPercentage(const std::shared_ptr<Epub>& epub, const float percentage, const int spineCount,
|
||||||
|
int& outSpineIndex, float& outIntraSpineProgress) {
|
||||||
|
if (!std::isfinite(percentage) || !epub || spineCount <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t bookSize = epub->getBookSize();
|
||||||
|
if (bookSize == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float sanitizedPercentage = std::clamp(percentage, 0.0f, 1.0f);
|
||||||
|
const size_t targetBytes = static_cast<size_t>(bookSize * sanitizedPercentage);
|
||||||
|
|
||||||
|
outSpineIndex = spineCount - 1;
|
||||||
|
for (int i = 0; i < spineCount; i++) {
|
||||||
|
const size_t cumulativeSize = epub->getCumulativeSpineItemSize(i);
|
||||||
|
if (cumulativeSize >= targetBytes) {
|
||||||
|
outSpineIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outIntraSpineProgress = 0.0f;
|
||||||
|
const size_t prevCumSize = (outSpineIndex > 0) ? epub->getCumulativeSpineItemSize(outSpineIndex - 1) : 0;
|
||||||
|
const size_t currentCumSize = epub->getCumulativeSpineItemSize(outSpineIndex);
|
||||||
|
const size_t spineSize = currentCumSize - prevCumSize;
|
||||||
|
if (spineSize > 0) {
|
||||||
|
const size_t bytesIntoSpine = (targetBytes > prevCumSize) ? (targetBytes - prevCumSize) : 0;
|
||||||
|
outIntraSpineProgress = static_cast<float>(bytesIntoSpine) / static_cast<float>(spineSize);
|
||||||
|
outIntraSpineProgress = std::clamp(outIntraSpineProgress, 0.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
KOReaderPosition ProgressMapper::toKOReader(const std::shared_ptr<Epub>& epub, const CrossPointPosition& pos) {
|
KOReaderPosition ProgressMapper::toKOReader(const std::shared_ptr<Epub>& epub, const CrossPointPosition& pos) {
|
||||||
KOReaderPosition result;
|
KOReaderPosition result;
|
||||||
|
|
||||||
@@ -58,6 +96,7 @@ CrossPointPosition ProgressMapper::toCrossPoint(const std::shared_ptr<Epub>& epu
|
|||||||
float resolvedIntraSpineProgress = -1.0f;
|
float resolvedIntraSpineProgress = -1.0f;
|
||||||
bool xpathExactMatch = false;
|
bool xpathExactMatch = false;
|
||||||
bool usedXPathMapping = false;
|
bool usedXPathMapping = false;
|
||||||
|
bool usedPercentageReconcile = false;
|
||||||
|
|
||||||
int xpathSpineIndex = -1;
|
int xpathSpineIndex = -1;
|
||||||
if (ChapterXPathIndexer::tryExtractSpineIndexFromXPath(koPos.xpath, xpathSpineIndex) && xpathSpineIndex >= 0 &&
|
if (ChapterXPathIndexer::tryExtractSpineIndexFromXPath(koPos.xpath, xpathSpineIndex) && xpathSpineIndex >= 0 &&
|
||||||
@@ -68,6 +107,30 @@ CrossPointPosition ProgressMapper::toCrossPoint(const std::shared_ptr<Epub>& epu
|
|||||||
result.spineIndex = xpathSpineIndex;
|
result.spineIndex = xpathSpineIndex;
|
||||||
resolvedIntraSpineProgress = intraFromXPath;
|
resolvedIntraSpineProgress = intraFromXPath;
|
||||||
usedXPathMapping = true;
|
usedXPathMapping = true;
|
||||||
|
|
||||||
|
// KOReader's text-node indexing can differ across renderers/parsers in some
|
||||||
|
// XHTML shapes. When an XPath-resolved position disagrees materially with
|
||||||
|
// KOReader's percentage but points to the same spine, use percentage-derived
|
||||||
|
// intra-spine progress as a safer tie-breaker.
|
||||||
|
if (std::isfinite(koPos.percentage) && resolvedIntraSpineProgress >= 0.0f) {
|
||||||
|
const float sanitizedPercentage = std::clamp(koPos.percentage, 0.0f, 1.0f);
|
||||||
|
const float mappedPercentage = epub->calculateProgress(result.spineIndex, resolvedIntraSpineProgress);
|
||||||
|
const float delta = std::fabs(mappedPercentage - sanitizedPercentage);
|
||||||
|
|
||||||
|
constexpr float kReconcileThreshold = 0.01f; // 1% absolute book progress
|
||||||
|
if (delta > kReconcileThreshold) {
|
||||||
|
int percentageSpineIndex = -1;
|
||||||
|
float percentageIntraSpine = -1.0f;
|
||||||
|
if (resolveFromPercentage(epub, koPos.percentage, spineCount, percentageSpineIndex, percentageIntraSpine) &&
|
||||||
|
percentageSpineIndex == result.spineIndex && percentageIntraSpine >= 0.0f) {
|
||||||
|
LOG_DBG("ProgressMapper",
|
||||||
|
"Reconciling XPath position with percentage: spine=%d xpath=%.3f pct=%.3f delta=%.3f -> %.3f",
|
||||||
|
result.spineIndex, resolvedIntraSpineProgress, sanitizedPercentage, delta, percentageIntraSpine);
|
||||||
|
resolvedIntraSpineProgress = percentageIntraSpine;
|
||||||
|
usedPercentageReconcile = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Extract paragraph index from XPath for direct page lookup via section cache
|
// Extract paragraph index from XPath for direct page lookup via section cache
|
||||||
uint16_t pIndex = 0;
|
uint16_t pIndex = 0;
|
||||||
@@ -78,43 +141,14 @@ CrossPointPosition ProgressMapper::toCrossPoint(const std::shared_ptr<Epub>& epu
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!usedXPathMapping) {
|
if (!usedXPathMapping) {
|
||||||
const size_t bookSize = epub->getBookSize();
|
int percentageSpineIndex = -1;
|
||||||
if (bookSize == 0) {
|
float percentageIntraSpine = -1.0f;
|
||||||
|
if (!resolveFromPercentage(epub, koPos.percentage, spineCount, percentageSpineIndex, percentageIntraSpine)) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!std::isfinite(koPos.percentage)) {
|
result.spineIndex = percentageSpineIndex;
|
||||||
return result;
|
resolvedIntraSpineProgress = percentageIntraSpine;
|
||||||
}
|
|
||||||
|
|
||||||
const float sanitizedPercentage = std::clamp(koPos.percentage, 0.0f, 1.0f);
|
|
||||||
const size_t targetBytes = static_cast<size_t>(bookSize * sanitizedPercentage);
|
|
||||||
|
|
||||||
bool spineFound = false;
|
|
||||||
for (int i = 0; i < spineCount; i++) {
|
|
||||||
const size_t cumulativeSize = epub->getCumulativeSpineItemSize(i);
|
|
||||||
if (cumulativeSize >= targetBytes) {
|
|
||||||
result.spineIndex = i;
|
|
||||||
spineFound = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!spineFound && spineCount > 0) {
|
|
||||||
result.spineIndex = spineCount - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.spineIndex < epub->getSpineItemsCount()) {
|
|
||||||
const size_t prevCumSize = (result.spineIndex > 0) ? epub->getCumulativeSpineItemSize(result.spineIndex - 1) : 0;
|
|
||||||
const size_t currentCumSize = epub->getCumulativeSpineItemSize(result.spineIndex);
|
|
||||||
const size_t spineSize = currentCumSize - prevCumSize;
|
|
||||||
|
|
||||||
if (spineSize > 0) {
|
|
||||||
const size_t bytesIntoSpine = (targetBytes > prevCumSize) ? (targetBytes - prevCumSize) : 0;
|
|
||||||
resolvedIntraSpineProgress = static_cast<float>(bytesIntoSpine) / static_cast<float>(spineSize);
|
|
||||||
resolvedIntraSpineProgress = std::max(0.0f, std::min(1.0f, resolvedIntraSpineProgress));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Estimate page number within the selected spine item
|
// Estimate page number within the selected spine item
|
||||||
@@ -154,9 +188,15 @@ CrossPointPosition ProgressMapper::toCrossPoint(const std::shared_ptr<Epub>& epu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_DBG("ProgressMapper", "KOReader -> CrossPoint: %.2f%% at %s -> spine=%d, page=%d (%s, exact=%s)",
|
LOG_DBG("ProgressMapper", "Resolved KOReader position: spine=%d intra=%.3f hasPIdx=%s pIdx=%u",
|
||||||
koPos.percentage * 100, koPos.xpath.c_str(), result.spineIndex, result.pageNumber,
|
result.spineIndex, resolvedIntraSpineProgress, result.hasParagraphIndex ? "yes" : "no",
|
||||||
usedXPathMapping ? "xpath" : "percentage", xpathExactMatch ? "yes" : "no");
|
result.paragraphIndex);
|
||||||
|
|
||||||
|
const char* mappingSource = usedXPathMapping ? (usedPercentageReconcile ? "xpath+percentage" : "xpath")
|
||||||
|
: "percentage";
|
||||||
|
LOG_DBG("ProgressMapper", "KOReader -> CrossPoint: %.2f%% at %s -> spine=%d, page=%d (%s, exact=%s)",
|
||||||
|
koPos.percentage * 100, koPos.xpath.c_str(), result.spineIndex, result.pageNumber, mappingSource,
|
||||||
|
xpathExactMatch ? "yes" : "no");
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user