#pragma once #include #include #include #include #include #include "KOReaderSyncClient.h" /** * CrossPoint position representation. */ struct CrossPointPosition { int spineIndex; // Current spine item (chapter) index int pageNumber; // Current page within the spine item int totalPages; // Total pages in the current spine item uint16_t paragraphIndex = 0; // 1-based synthetic paragraph index from XPath p[N] bool hasParagraphIndex = false; // True when paragraphIndex was resolved from XPath uint16_t liIndex = 0; // Running
  • count at the matched XPath element bool hasLiIndex = false; // True when target element is
  • and liIndex was resolved char xpathAnchorId[64] = {}; // First captured inside the matched XPath element }; /** * Progress position representation. */ struct SavedProgressPosition { std::string xpath; // XPath-like progress string float percentage; // Progress percentage (0.0 to 1.0) }; /** * Maps between CrossPoint and SavedProgress position formats, such as those used by KOReader. * * CrossPoint tracks position as (spineIndex, pageNumber). * SavedProgress uses XPath-like strings + percentage. * * Since CrossPoint discards HTML structure during parsing, we generate * synthetic XPath strings based on spine index, using percentage as the * primary sync mechanism. */ class ProgressMapper { public: /** * Convert CrossPoint position to SavedProgress format. * * @param epub The EPUB book * @param pos CrossPoint position * @return SavedProgress position */ static SavedProgressPosition toSavedProgress(const std::shared_ptr& epub, const CrossPointPosition& pos); /** * Convert SavedProgress position to CrossPoint format. * * Note: The returned pageNumber may be approximate since different * rendering settings produce different page counts. * * @param epub The EPUB book * @param savedPos SavedProgress position * @param renderer GfxRenderer for page count 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 SavedProgressPosition& savedPos, GfxRenderer& renderer, int currentSpineIndex = -1, int totalPagesInCurrentSpine = 0, int fallbackTotalPages = 0); /** * Convert a rich CrossPoint position (downloaded from a crosspoint-sync * server) directly to a CrossPoint position, without XPath approximation. * When the local layout matches the uploader's (same spine page count) the * page transfers losslessly; otherwise the paragraph LUT or the intra-spine * page fraction is used. * * @return The position, or std::nullopt when the rich position cannot be * applied (spine out of range, no section cache) and the caller * should fall back to toCrossPoint(). */ static std::optional fromRichPosition(const std::shared_ptr& epub, const KOReaderRichPosition& rich, GfxRenderer& renderer); private: /** * Generate a fallback XPath by streaming the spine item's XHTML and resolving * a paragraph/text position from intra-spine progress. * Produces a full ancestry path such as * /body/DocFragment[3]/body/p[42]/text().17. */ static std::string generateXPath(const std::shared_ptr& epub, int spineIndex, float intraSpineProgress); };