diff --git a/lib/KOReaderSync/ProgressMapper.cpp b/lib/KOReaderSync/ProgressMapper.cpp index bc0adf56..ef542ff4 100644 --- a/lib/KOReaderSync/ProgressMapper.cpp +++ b/lib/KOReaderSync/ProgressMapper.cpp @@ -30,49 +30,73 @@ KOReaderPosition ProgressMapper::toKOReader(const std::shared_ptr& epub, c } CrossPointPosition ProgressMapper::toCrossPoint(const std::shared_ptr& epub, const KOReaderPosition& koPos, - int totalPagesInSpine) { + int currentSpineIndex, int totalPagesInCurrentSpine) { CrossPointPosition result; result.spineIndex = 0; result.pageNumber = 0; - result.totalPages = totalPagesInSpine; + result.totalPages = 0; const size_t bookSize = epub->getBookSize(); if (bookSize == 0) { return result; } - // First, try to get spine index from XPath (DocFragment) - int xpathSpineIndex = parseDocFragmentIndex(koPos.xpath); - if (xpathSpineIndex >= 0 && xpathSpineIndex < epub->getSpineItemsCount()) { - result.spineIndex = xpathSpineIndex; - // When we have XPath, go to page 0 of the spine - byte-based page calculation is unreliable - result.pageNumber = 0; - } else { - // Fall back to percentage-based lookup for both spine and page - const size_t targetBytes = static_cast(bookSize * koPos.percentage); + // 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(bookSize * koPos.percentage); - // Find the spine item that contains this byte position - for (int i = 0; i < epub->getSpineItemsCount(); i++) { - const size_t cumulativeSize = epub->getCumulativeSpineItemSize(i); - if (cumulativeSize >= targetBytes) { - result.spineIndex = i; - break; + // 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(spineSize) / static_cast(currSpineSize); + estimatedTotalPages = static_cast(totalPagesInCurrentSpine * ratio); + if (estimatedTotalPages < 1) estimatedTotalPages = 1; } } - // Estimate page number within the spine item using percentage (only when no XPath) - if (totalPagesInSpine > 0 && 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; + result.totalPages = estimatedTotalPages; - if (spineSize > 0) { - const size_t bytesIntoSpine = (targetBytes > prevCumSize) ? (targetBytes - prevCumSize) : 0; - const float intraSpineProgress = static_cast(bytesIntoSpine) / static_cast(spineSize); - const float clampedProgress = std::max(0.0f, std::min(1.0f, intraSpineProgress)); - result.pageNumber = static_cast(clampedProgress * totalPagesInSpine); - result.pageNumber = std::max(0, std::min(result.pageNumber, totalPagesInSpine - 1)); - } + if (spineSize > 0 && estimatedTotalPages > 0) { + const size_t bytesIntoSpine = (targetBytes > prevCumSize) ? (targetBytes - prevCumSize) : 0; + const float intraSpineProgress = static_cast(bytesIntoSpine) / static_cast(spineSize); + const float clampedProgress = std::max(0.0f, std::min(1.0f, intraSpineProgress)); + result.pageNumber = static_cast(clampedProgress * estimatedTotalPages); + result.pageNumber = std::max(0, std::min(result.pageNumber, estimatedTotalPages - 1)); } } @@ -83,30 +107,8 @@ CrossPointPosition ProgressMapper::toCrossPoint(const std::shared_ptr& epu } std::string ProgressMapper::generateXPath(int spineIndex, int pageNumber, int totalPages) { - // KOReader uses 1-based DocFragment indices - // Use a simple xpath pointing to the DocFragment - KOReader will use the percentage for fine positioning + // 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 + 1) + "]/body"; -} - -int ProgressMapper::parseDocFragmentIndex(const std::string& xpath) { - // Look for DocFragment[N] pattern - const size_t start = xpath.find("DocFragment["); - if (start == std::string::npos) { - return -1; - } - - const size_t numStart = start + 12; // Length of "DocFragment[" - const size_t numEnd = xpath.find(']', numStart); - if (numEnd == std::string::npos) { - return -1; - } - - try { - const int docFragmentIndex = std::stoi(xpath.substr(numStart, numEnd - numStart)); - // KOReader uses 1-based indices, we use 0-based - return docFragmentIndex - 1; - } catch (...) { - return -1; - } + return "/body/DocFragment[" + std::to_string(spineIndex) + "]/body"; } diff --git a/lib/KOReaderSync/ProgressMapper.h b/lib/KOReaderSync/ProgressMapper.h index 694549da..53ff7696 100644 --- a/lib/KOReaderSync/ProgressMapper.h +++ b/lib/KOReaderSync/ProgressMapper.h @@ -50,23 +50,18 @@ class ProgressMapper { * * @param epub The EPUB book * @param koPos KOReader position - * @param totalPagesInSpine Total pages in the target spine item (for page estimation) + * @param currentSpineIndex Index of the currently open spine item (for density estimation) + * @param totalPagesInCurrentSpine Total pages in the current spine item (for density estimation) * @return CrossPoint position */ static CrossPointPosition toCrossPoint(const std::shared_ptr& epub, const KOReaderPosition& koPos, - int totalPagesInSpine = 0); + int currentSpineIndex = -1, int totalPagesInCurrentSpine = 0); private: /** * Generate XPath for KOReader compatibility. - * Format: /body/DocFragment[spineIndex+1]/body/p[estimatedParagraph] - * Paragraph is estimated based on page position within the chapter. + * Format: /body/DocFragment[spineIndex+1]/body + * Since CrossPoint doesn't preserve HTML structure, we rely on percentage for positioning. */ static std::string generateXPath(int spineIndex, int pageNumber, int totalPages); - - /** - * Parse DocFragment index from XPath string. - * Returns -1 if not found. - */ - static int parseDocFragmentIndex(const std::string& xpath); }; diff --git a/src/activities/reader/KOReaderSyncActivity.cpp b/src/activities/reader/KOReaderSyncActivity.cpp index d335786f..5a64231c 100644 --- a/src/activities/reader/KOReaderSyncActivity.cpp +++ b/src/activities/reader/KOReaderSyncActivity.cpp @@ -123,7 +123,7 @@ void KOReaderSyncActivity::performSync() { // Convert remote progress to CrossPoint position hasRemoteProgress = true; KOReaderPosition koPos = {remoteProgress.progress, remoteProgress.percentage}; - remotePosition = ProgressMapper::toCrossPoint(epub, koPos, totalPagesInSpine); + remotePosition = ProgressMapper::toCrossPoint(epub, koPos, currentSpineIndex, totalPagesInSpine); // Calculate local progress in KOReader format (for display) CrossPointPosition localPos = {currentSpineIndex, currentPage, totalPagesInSpine}; @@ -132,7 +132,13 @@ void KOReaderSyncActivity::performSync() { { RenderLock lock(*this); state = SHOWING_RESULT; - selectedOption = 0; // Default to "Apply" + + // Default to the option that corresponds to the furthest progress + if (localProgress.percentage > remoteProgress.percentage) { + selectedOption = 1; // Upload local progress + } else { + selectedOption = 0; // Apply remote progress + } } requestUpdate(); }