## Summary * Original implementation had inconsistent positioning logic: - When XPath parsing succeeded: incorrectly set pageNumber = 0 (always beginning of chapter) - When XPath parsing failed: used percentage for positioning (worked correctly) - Result: Positions restored to wrong locations depending on XPath parsing success - Mentioned in Issue #581 * Solution - Unified ProgressMapper::toCrossPoint() to use percentage-based positioning exclusively for both spine identification and intra-chapter page calculation, eliminating unreliable XPath parsing entirely. ## Additional Context * ProgressMapper.cpp: Simplified toCrossPoint() to always use percentage for positioning, removed parseDocFragmentIndex() function * ProgressMapper.h: Updated comments and removed unused function declaration * Tests confirmed appropriate positioning * __Notabene: the syncing to another device will (most probably) end up at the current chapter of crosspoints reading position. There is not much we can do about it, as KOReader needs to have the correct XPath information - we can only provide an apporximate position (plus percentage) - the percentage information is not used in KOReaders current implementation__ --- ### AI Usage While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? YES
115 lines
4.9 KiB
C++
115 lines
4.9 KiB
C++
#include "ProgressMapper.h"
|
|
|
|
#include <Logging.h>
|
|
|
|
#include <cmath>
|
|
|
|
KOReaderPosition ProgressMapper::toKOReader(const std::shared_ptr<Epub>& epub, const CrossPointPosition& pos) {
|
|
KOReaderPosition result;
|
|
|
|
// Calculate page progress within current spine item
|
|
float intraSpineProgress = 0.0f;
|
|
if (pos.totalPages > 0) {
|
|
intraSpineProgress = static_cast<float>(pos.pageNumber) / static_cast<float>(pos.totalPages);
|
|
}
|
|
|
|
// Calculate overall book progress (0.0-1.0)
|
|
result.percentage = epub->calculateProgress(pos.spineIndex, intraSpineProgress);
|
|
|
|
// Generate XPath with estimated paragraph position based on page
|
|
result.xpath = generateXPath(pos.spineIndex, pos.pageNumber, pos.totalPages);
|
|
|
|
// Get chapter info for logging
|
|
const int tocIndex = epub->getTocIndexForSpineIndex(pos.spineIndex);
|
|
const std::string chapterName = (tocIndex >= 0) ? epub->getTocItem(tocIndex).title : "unknown";
|
|
|
|
LOG_DBG("ProgressMapper", "CrossPoint -> KOReader: chapter='%s', page=%d/%d -> %.2f%% at %s", chapterName.c_str(),
|
|
pos.pageNumber, pos.totalPages, result.percentage * 100, result.xpath.c_str());
|
|
|
|
return result;
|
|
}
|
|
|
|
CrossPointPosition ProgressMapper::toCrossPoint(const std::shared_ptr<Epub>& epub, const KOReaderPosition& koPos,
|
|
int currentSpineIndex, int totalPagesInCurrentSpine) {
|
|
CrossPointPosition result;
|
|
result.spineIndex = 0;
|
|
result.pageNumber = 0;
|
|
result.totalPages = 0;
|
|
|
|
const size_t bookSize = epub->getBookSize();
|
|
if (bookSize == 0) {
|
|
return result;
|
|
}
|
|
|
|
// Use percentage-based lookup for both spine and page positioning
|
|
// XPath parsing is unreliable since CrossPoint doesn't preserve detailed HTML structure
|
|
const size_t targetBytes = static_cast<size_t>(bookSize * koPos.percentage);
|
|
|
|
// Find the spine item that contains this byte position
|
|
const int spineCount = epub->getSpineItemsCount();
|
|
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 no spine item was found (e.g., targetBytes beyond last cumulative size),
|
|
// default to the last spine item so we map to the end of the book instead of the beginning.
|
|
if (!spineFound && spineCount > 0) {
|
|
result.spineIndex = spineCount - 1;
|
|
}
|
|
|
|
// Estimate page number within the spine item using percentage
|
|
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;
|
|
|
|
int estimatedTotalPages = 0;
|
|
|
|
// If we are in the same spine, use the known total pages
|
|
if (result.spineIndex == currentSpineIndex && totalPagesInCurrentSpine > 0) {
|
|
estimatedTotalPages = totalPagesInCurrentSpine;
|
|
}
|
|
// Otherwise try to estimate based on density from current spine
|
|
else if (currentSpineIndex >= 0 && currentSpineIndex < epub->getSpineItemsCount() && totalPagesInCurrentSpine > 0) {
|
|
const size_t prevCurrCumSize =
|
|
(currentSpineIndex > 0) ? epub->getCumulativeSpineItemSize(currentSpineIndex - 1) : 0;
|
|
const size_t currCumSize = epub->getCumulativeSpineItemSize(currentSpineIndex);
|
|
const size_t currSpineSize = currCumSize - prevCurrCumSize;
|
|
|
|
if (currSpineSize > 0) {
|
|
float ratio = static_cast<float>(spineSize) / static_cast<float>(currSpineSize);
|
|
estimatedTotalPages = static_cast<int>(totalPagesInCurrentSpine * ratio);
|
|
if (estimatedTotalPages < 1) estimatedTotalPages = 1;
|
|
}
|
|
}
|
|
|
|
result.totalPages = estimatedTotalPages;
|
|
|
|
if (spineSize > 0 && estimatedTotalPages > 0) {
|
|
const size_t bytesIntoSpine = (targetBytes > prevCumSize) ? (targetBytes - prevCumSize) : 0;
|
|
const float intraSpineProgress = static_cast<float>(bytesIntoSpine) / static_cast<float>(spineSize);
|
|
const float clampedProgress = std::max(0.0f, std::min(1.0f, intraSpineProgress));
|
|
result.pageNumber = static_cast<int>(clampedProgress * estimatedTotalPages);
|
|
result.pageNumber = std::max(0, std::min(result.pageNumber, estimatedTotalPages - 1));
|
|
}
|
|
}
|
|
|
|
LOG_DBG("ProgressMapper", "KOReader -> CrossPoint: %.2f%% at %s -> spine=%d, page=%d", koPos.percentage * 100,
|
|
koPos.xpath.c_str(), result.spineIndex, result.pageNumber);
|
|
|
|
return result;
|
|
}
|
|
|
|
std::string ProgressMapper::generateXPath(int spineIndex, int pageNumber, int totalPages) {
|
|
// Use 0-based DocFragment indices for KOReader
|
|
// Use a simple xpath pointing to the DocFragment - KOReader will use the percentage for fine positioning within it
|
|
// Avoid specifying paragraph numbers as they may not exist in the target document
|
|
return "/body/DocFragment[" + std::to_string(spineIndex) + "]/body";
|
|
}
|