mirror of
https://github.com/crosspoint-reader/crosspoint-reader.git
synced 2026-08-13 14:21:57 -07:00
Ties bookmarks to the actual text on the page rather than to a page number. Page numbers shift whenever the book gets re-laid-out — changing font size, margins, line spacing, or rotating the screen — which could previously land you on the wrong spot. This is a followup of #2805, making bookmarks use the same mechanism. Several minor improvments ride along.
100 lines
4.3 KiB
C++
100 lines
4.3 KiB
C++
#pragma once
|
|
#include <Epub.h>
|
|
#include <GfxRenderer.h>
|
|
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <string>
|
|
|
|
#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
|
|
uint32_t visibleTextOffset = 0; // Authoritative zero-based visible codepoint offset
|
|
bool hasVisibleTextOffset = false;
|
|
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 <li> count at the matched XPath element
|
|
bool hasLiIndex = false; // True when target element is <li> and liIndex was resolved
|
|
char xpathAnchorId[64] = {}; // First <a id> 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, visibleTextOffset). Page number is
|
|
* derived from the current section layout.
|
|
* SavedProgress uses XPath-like strings + percentage.
|
|
*
|
|
* The section cache records page-start visible offsets during pagination. The
|
|
* same body-text counting rules are used to generate and resolve KOReader
|
|
* XPaths. Percentage remains metadata and a fallback only.
|
|
*/
|
|
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>& 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>& 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. Its standard KOReader XPath is
|
|
* resolved to a content offset first; legacy spine/page/paragraph hints are
|
|
* used only when that content anchor cannot be applied.
|
|
*
|
|
* @param xpathAlreadyTried when true, skip re-resolving rich.xpath and go straight to the
|
|
* legacy page hints. The caller sets this when it just resolved the identical XPath via
|
|
* toCrossPoint(), so retrying it here would decompress the chapter twice for nothing.
|
|
* @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<CrossPointPosition> fromRichPosition(const std::shared_ptr<Epub>& epub,
|
|
const KOReaderRichPosition& rich, GfxRenderer& renderer,
|
|
bool xpathAlreadyTried = false);
|
|
|
|
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>& epub, int spineIndex, float intraSpineProgress);
|
|
};
|