Add paragraph index LUT for accurate KOReader position sync
Store per-page paragraph indices in section cache to enable precise XPath-to-page and page-to-XPath mapping without reparsing XHTML. Forward path (upload): generates XPath directly from paragraph LUT instead of byte-offset estimation, eliminating drift in chapters with non-uniform content density. Reverse path (download): resolves incoming KOReader XPath p[N] to the exact page via paragraph LUT lookup. Paragraph counter counts all <p> elements including display:none to match ChapterXPathIndexer and crengine's standard XPath counting. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
5b710e960f
commit
fd70b3a231
@@ -37,7 +37,9 @@ struct PageResult {
|
||||
|
||||
struct SyncResult {
|
||||
int spineIndex = 0;
|
||||
int page = 0;
|
||||
int page = 0; // estimated page (fallback)
|
||||
uint16_t paragraphIndex = 0; // 1-based <p> index from XPath
|
||||
bool hasParagraphIndex = false; // true when paragraphIndex is available
|
||||
};
|
||||
|
||||
enum class NetworkMode;
|
||||
|
||||
@@ -384,9 +384,18 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
|
||||
if (KOREADER_STORE.hasCredentials()) {
|
||||
const int currentPage = section ? section->currentPage : 0;
|
||||
const int totalPages = section ? section->pageCount : 0;
|
||||
// Look up paragraph index from section cache for accurate XPath generation on upload
|
||||
uint16_t paragraphIdx = 0;
|
||||
bool hasParagraphIdx = false;
|
||||
if (section) {
|
||||
if (const auto pIdx = section->getParagraphIndexForPage(currentPage)) {
|
||||
paragraphIdx = *pIdx;
|
||||
hasParagraphIdx = true;
|
||||
}
|
||||
}
|
||||
startActivityForResult(
|
||||
std::make_unique<KOReaderSyncActivity>(renderer, mappedInput, epub, epub->getPath(), currentSpineIndex,
|
||||
currentPage, totalPages),
|
||||
currentPage, totalPages, paragraphIdx, hasParagraphIdx),
|
||||
[this](const ActivityResult& result) {
|
||||
if (!result.isCancelled) {
|
||||
const auto& sync = std::get<SyncResult>(result.data);
|
||||
@@ -394,6 +403,10 @@ void EpubReaderActivity::onReaderMenuConfirm(EpubReaderMenuActivity::MenuAction
|
||||
RenderLock lock(*this);
|
||||
currentSpineIndex = sync.spineIndex;
|
||||
nextPageNumber = sync.page;
|
||||
if (sync.hasParagraphIndex) {
|
||||
pendingParagraphLookup = true;
|
||||
pendingParagraphIndex = sync.paragraphIndex;
|
||||
}
|
||||
section.reset();
|
||||
}
|
||||
}
|
||||
@@ -574,6 +587,17 @@ void EpubReaderActivity::render(RenderLock&& lock) {
|
||||
pendingAnchor.clear();
|
||||
}
|
||||
|
||||
// Resolve pending KOReader sync paragraph index to accurate page via Section paragraph LUT
|
||||
if (pendingParagraphLookup) {
|
||||
if (const auto page = section->getPageForParagraphIndex(pendingParagraphIndex)) {
|
||||
section->currentPage = *page;
|
||||
LOG_DBG("ERS", "Resolved p[%u] to page %d (was %d)", pendingParagraphIndex, *page, nextPageNumber);
|
||||
} else {
|
||||
LOG_DBG("ERS", "Paragraph LUT not available, using estimated page %d", nextPageNumber);
|
||||
}
|
||||
pendingParagraphLookup = false;
|
||||
}
|
||||
|
||||
// handles changes in reader settings and reset to approximate position based on cached progress
|
||||
if (cachedChapterTotalPageCount > 0) {
|
||||
// only goes to relative position if spine index matches cached value
|
||||
|
||||
@@ -24,6 +24,9 @@ class EpubReaderActivity final : public Activity {
|
||||
bool pendingPercentJump = false;
|
||||
// Normalized 0.0-1.0 progress within the target spine item, computed from book percentage.
|
||||
float pendingSpineProgress = 0.0f;
|
||||
// Pending paragraph index from KOReader sync (resolved to page via Section paragraph LUT)
|
||||
bool pendingParagraphLookup = false;
|
||||
uint16_t pendingParagraphIndex = 0;
|
||||
bool pendingScreenshot = false;
|
||||
bool skipNextButtonCheck = false; // Skip button processing for one frame after subactivity exit
|
||||
bool automaticPageTurnActive = false;
|
||||
|
||||
@@ -132,11 +132,24 @@ void KOReaderSyncActivity::performSync() {
|
||||
|
||||
// Convert remote progress to CrossPoint position
|
||||
hasRemoteProgress = true;
|
||||
{
|
||||
RenderLock lock(*this);
|
||||
statusMessage = tr(STR_MAPPING_REMOTE);
|
||||
}
|
||||
requestUpdateAndWait();
|
||||
|
||||
KOReaderPosition koPos = {remoteProgress.progress, remoteProgress.percentage};
|
||||
remotePosition = ProgressMapper::toCrossPoint(epub, koPos, currentSpineIndex, totalPagesInSpine);
|
||||
|
||||
// Calculate local progress in KOReader format (for display)
|
||||
CrossPointPosition localPos = {currentSpineIndex, currentPage, totalPagesInSpine};
|
||||
{
|
||||
RenderLock lock(*this);
|
||||
statusMessage = tr(STR_MAPPING_LOCAL);
|
||||
}
|
||||
requestUpdateAndWait();
|
||||
|
||||
CrossPointPosition localPos = {currentSpineIndex, currentPage, totalPagesInSpine, localParagraphIndex,
|
||||
hasLocalParagraphIndex};
|
||||
localProgress = ProgressMapper::toKOReader(epub, localPos);
|
||||
|
||||
{
|
||||
@@ -162,7 +175,8 @@ void KOReaderSyncActivity::performUpload() {
|
||||
requestUpdateAndWait();
|
||||
|
||||
// Convert current position to KOReader format
|
||||
CrossPointPosition localPos = {currentSpineIndex, currentPage, totalPagesInSpine};
|
||||
CrossPointPosition localPos = {currentSpineIndex, currentPage, totalPagesInSpine, localParagraphIndex,
|
||||
hasLocalParagraphIndex};
|
||||
KOReaderPosition koPos = ProgressMapper::toKOReader(epub, localPos);
|
||||
|
||||
KOReaderProgress progress;
|
||||
@@ -360,7 +374,8 @@ void KOReaderSyncActivity::loop() {
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||
if (selectedOption == 0) {
|
||||
// Wifi will be turned off in onExit()
|
||||
setResult(SyncResult{remotePosition.spineIndex, remotePosition.pageNumber});
|
||||
setResult(SyncResult{remotePosition.spineIndex, remotePosition.pageNumber, remotePosition.paragraphIndex,
|
||||
remotePosition.hasParagraphIndex});
|
||||
finish();
|
||||
} else if (selectedOption == 1) {
|
||||
// Upload local progress
|
||||
|
||||
@@ -22,13 +22,16 @@ class KOReaderSyncActivity final : public Activity {
|
||||
public:
|
||||
explicit KOReaderSyncActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::shared_ptr<Epub>& epub, const std::string& epubPath, int currentSpineIndex,
|
||||
int currentPage, int totalPagesInSpine)
|
||||
int currentPage, int totalPagesInSpine, uint16_t paragraphIndex = 0,
|
||||
bool hasParagraphIndex = false)
|
||||
: Activity("KOReaderSync", renderer, mappedInput),
|
||||
epub(epub),
|
||||
epubPath(epubPath),
|
||||
currentSpineIndex(currentSpineIndex),
|
||||
currentPage(currentPage),
|
||||
totalPagesInSpine(totalPagesInSpine),
|
||||
localParagraphIndex(paragraphIndex),
|
||||
hasLocalParagraphIndex(hasParagraphIndex),
|
||||
remoteProgress{},
|
||||
remotePosition{},
|
||||
localProgress{} {}
|
||||
@@ -57,6 +60,8 @@ class KOReaderSyncActivity final : public Activity {
|
||||
int currentSpineIndex;
|
||||
int currentPage;
|
||||
int totalPagesInSpine;
|
||||
uint16_t localParagraphIndex;
|
||||
bool hasLocalParagraphIndex;
|
||||
|
||||
State state = WIFI_SELECTION;
|
||||
std::string statusMessage;
|
||||
|
||||
Reference in New Issue
Block a user