Files
crosspoint-reader-mod/lib/KOReaderSync/ProgressMapper.cpp

115 lines
4.9 KiB
C++
Raw Normal View History

#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,
2026-02-19 11:38:46 +01:00
int currentSpineIndex, int totalPagesInCurrentSpine) {
CrossPointPosition result;
result.spineIndex = 0;
result.pageNumber = 0;
2026-02-19 11:38:46 +01:00
result.totalPages = 0;
const size_t bookSize = epub->getBookSize();
if (bookSize == 0) {
return result;
}
2026-02-19 11:38:46 +01:00
// 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;
}
2026-02-19 11:38:46 +01:00
}
// 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;
2026-02-19 11:38:46 +01:00
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;
}
}
2026-02-19 11:38:46 +01:00
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) {
2026-02-19 11:38:46 +01:00
// 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
2026-02-19 11:38:46 +01:00
return "/body/DocFragment[" + std::to_string(spineIndex) + "]/body";
}