Improving KOReader sync
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
#include "parsers/ChapterHtmlSlimParser.h"
|
||||
|
||||
namespace {
|
||||
constexpr uint8_t SECTION_FILE_VERSION = 25;
|
||||
constexpr uint8_t SECTION_FILE_VERSION = 26;
|
||||
constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + // SECTION_FILE_VERSION
|
||||
sizeof(int) + // fontId
|
||||
sizeof(float) + // lineCompression
|
||||
@@ -29,8 +29,11 @@ constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + // SECTION_FILE_VERSION
|
||||
sizeof(uint32_t) + // anchor map offset
|
||||
sizeof(uint32_t); // paragraph LUT offset
|
||||
|
||||
// On-disk paragraph LUT entry: u32 xhtmlByteOffset + u16 paragraphIndex.
|
||||
constexpr uint32_t PARAGRAPH_LUT_ENTRY_SIZE = sizeof(uint32_t) + sizeof(uint16_t);
|
||||
// On-disk paragraph LUT entry: u32 xhtmlByteOffset + u16 paragraphIndex + u16 listItemIndex.
|
||||
// listItemIndex is the running <li> count at page-break time; together with
|
||||
// paragraphIndex it lets KOReader-supplied <p>- and <li>-anchored XPaths snap to
|
||||
// the exact page on download.
|
||||
constexpr uint32_t PARAGRAPH_LUT_ENTRY_SIZE = sizeof(uint32_t) + sizeof(uint16_t) + sizeof(uint16_t);
|
||||
inline uint32_t paragraphLutEntryOffset(uint32_t lutStart, uint16_t page) {
|
||||
return lutStart + page * PARAGRAPH_LUT_ENTRY_SIZE;
|
||||
}
|
||||
@@ -471,6 +474,7 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
|
||||
for (const auto& entry : paragraphLut) {
|
||||
serialization::writePod(file, entry.xhtmlByteOffset);
|
||||
serialization::writePod(file, entry.paragraphIndex);
|
||||
serialization::writePod(file, entry.listItemIndex);
|
||||
}
|
||||
|
||||
// Patch header with final pageCount, lutOffset, anchorMapOffset, and paragraphLutOffset
|
||||
@@ -803,6 +807,42 @@ std::optional<uint16_t> Section::getParagraphIndexForPage(const uint16_t page) c
|
||||
return pIdx;
|
||||
}
|
||||
|
||||
std::optional<uint16_t> Section::getPageForListItemIndex(const uint16_t liIndex) const {
|
||||
if (liIndex == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
FsFile f;
|
||||
uint16_t count = 0;
|
||||
uint32_t lutStart = 0;
|
||||
if (!readParagraphLutHeader(f, count, lutStart)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
const uint32_t fileSize = f.size();
|
||||
|
||||
// Mirror getPageForParagraphIndex: each entry stores the running li count at page-break
|
||||
// time, so the target li first appears on the smallest i where storedLiIdx[i] >= liIndex.
|
||||
// The listItemIndex field follows xhtmlByteOffset + paragraphIndex within each entry.
|
||||
for (uint16_t i = 0; i < count; i++) {
|
||||
const uint32_t entryOffset = paragraphLutEntryOffset(lutStart, i) + sizeof(uint32_t) + sizeof(uint16_t);
|
||||
const uint64_t requiredOffset = static_cast<uint64_t>(entryOffset) + sizeof(uint16_t);
|
||||
if (requiredOffset > fileSize) {
|
||||
f.close();
|
||||
return std::nullopt;
|
||||
}
|
||||
f.seek(entryOffset);
|
||||
uint16_t pageLiIdx;
|
||||
serialization::readPod(f, pageLiIdx);
|
||||
if (pageLiIdx >= liIndex) {
|
||||
f.close();
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
f.close();
|
||||
return static_cast<uint16_t>(count - 1);
|
||||
}
|
||||
|
||||
std::optional<uint32_t> Section::getXhtmlByteOffsetForPage(const uint16_t page) const {
|
||||
FsFile f;
|
||||
uint16_t count = 0;
|
||||
|
||||
@@ -90,6 +90,12 @@ class Section {
|
||||
// Returns nullopt if the paragraph LUT is not available (old cache format).
|
||||
std::optional<uint16_t> getPageForParagraphIndex(uint16_t pIndex) const;
|
||||
|
||||
// Look up the page number for a running <li> index (1-based, the Nth <li> at any depth
|
||||
// in the chapter). Used to snap KOReader-supplied list-item XPaths to a precise page
|
||||
// the same way getPageForParagraphIndex handles <p>-anchored XPaths.
|
||||
// Returns nullopt if the LUT is not available or the index is out of range.
|
||||
std::optional<uint16_t> getPageForListItemIndex(uint16_t liIndex) const;
|
||||
|
||||
// Look up the paragraph index for a given page number.
|
||||
// Returns the 1-based paragraph index of the last <p> element on or before the page.
|
||||
// Returns nullopt if the paragraph LUT is not available (old cache format).
|
||||
|
||||
@@ -250,7 +250,7 @@ void ChapterHtmlSlimParser::flushPartWordBuffer() {
|
||||
// Callers must ensure currentPage is non-null and carries content; the helper resets
|
||||
// currentPage to a fresh Page and zeroes currentPageNextY so the caller can keep building.
|
||||
void ChapterHtmlSlimParser::emitPage(uint32_t xhtmlByteOffset) {
|
||||
paragraphLutPerPage.push_back({xhtmlByteOffset, xpathParagraphIndex});
|
||||
paragraphLutPerPage.push_back({xhtmlByteOffset, xpathParagraphIndex, xpathListItemIndex});
|
||||
completePageFn(std::move(currentPage));
|
||||
completedPageCount++;
|
||||
currentPage.reset(new Page());
|
||||
@@ -814,6 +814,13 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
||||
}
|
||||
}
|
||||
|
||||
// <li> can appear nested inside <ul>/<ol> at any depth, so count it globally —
|
||||
// not at body-child level. The running count must match what the runtime reverse
|
||||
// mapper sees so getPageForListItemIndex can snap a KOReader li XPath to a page.
|
||||
if (self->xpathBodyDepth >= 0 && strcmp(name, "li") == 0) {
|
||||
self->xpathListItemIndex++;
|
||||
}
|
||||
|
||||
if (matches(name, SKIP_TAGS, NUM_SKIP_TAGS)) {
|
||||
// start skip
|
||||
self->skipUntilDepth = self->depth;
|
||||
|
||||
@@ -108,7 +108,11 @@ class ChapterHtmlSlimParser final : public Print {
|
||||
// Stored per page in the section cache so that XPath p[N] can be resolved to a page
|
||||
// without reparsing, and current page can generate an XPath without reparsing.
|
||||
uint16_t xpathParagraphIndex = 0; // current <p> sibling index (1-based)
|
||||
int xpathBodyDepth = -1; // depth of the <body> element (-1 = not yet seen)
|
||||
// Running count of <li> elements opened anywhere in the chapter (1-based, any depth).
|
||||
// Used by the section LUT so KOReader-supplied list-item XPaths can snap to the exact
|
||||
// page on download, the same way <p>-anchored XPaths use xpathParagraphIndex.
|
||||
uint16_t xpathListItemIndex = 0;
|
||||
int xpathBodyDepth = -1; // depth of the <body> element (-1 = not yet seen)
|
||||
// Byte offset of the most recent direct-body-child element start (any tag at xpathBodyDepth+1).
|
||||
// Recorded at the same depth condition that increments xpathParagraphIndex, so the stored
|
||||
// offset is guaranteed to land on a body-child element boundary. This keeps the XPath forward
|
||||
@@ -119,6 +123,7 @@ class ChapterHtmlSlimParser final : public Print {
|
||||
struct ParagraphLutEntry {
|
||||
uint32_t xhtmlByteOffset; // byte offset of most recent body-child element start at page break
|
||||
uint16_t paragraphIndex; // 1-based <p> index at page completion
|
||||
uint16_t listItemIndex; // running <li> count at page completion (any depth)
|
||||
};
|
||||
std::vector<ParagraphLutEntry> paragraphLutPerPage; // deep LUT: one entry per page
|
||||
|
||||
|
||||
Reference in New Issue
Block a user