Skip to content

lvdocview: fix source page numbers with a page-list not in reading order - #711

Open
albertmichaelj wants to merge 3 commits into
koreader:masterfrom
albertmichaelj:fix-pagemap-order
Open

lvdocview: fix source page numbers with a page-list not in reading order#711
albertmichaelj wants to merge 3 commits into
koreader:masterfrom
albertmichaelj:fix-pagemap-order

Conversation

@albertmichaelj

@albertmichaelj albertmichaelj commented Jul 26, 2026

Copy link
Copy Markdown

Fixes #688.

Problem

The page map is built in page list order, and LVDocView::updatePageMapInfo() made _page/_doc_y non-decreasing in that list order. But a page list is not required to be in reading order: conversions commonly move front matter (a copyright page) to the end of the spine while the page list keeps referencing it among the front matter, which is what the DAISY page-list guidance asks for, and epubcheck dropped its reading order requirement in w3c/epubcheck@d0f12a9.

With such a page list, one out-of-order entry raises the clamping values to a near-the-end position, and every entry after it is clamped up to that. With the two sample EPUBs from #688 (identical spine title(i) ch1(1) ch2(2) ch3(3) copyright(ii), one with the page list in print order i, ii, 1, 2, 3):

rendered page 0 1 2 3 4
before i i i i ii
after i 1 2 3 ii

So the source page number freezes on the last label before the out-of-order entry, and the page numbers in the margin all pile up at that one position.

Fix

updatePageMapInfo() now resolves each item's position with no clamping, orders the items by that resolved position, and only then ensures page numbers don't go backward. Three commits:

  1. lvtinydom: add LVPageMap::sortByDocY() — private helper (LVDocView made a friend, as it already is of LVPageMapItem): insertion sort on _doc_y, tie-broken on _index.
  2. lvdocview: updatePageMapInfo(): order page map by resolved position — the fix itself.
  3. lvtinydom: LVPageMap::sortByDocY(): keep _index in sync — the extra pass you asked for, kept separate so it is easy to review or drop.

Notes for review

  • The implementation is @tachibana-shin's, from crenginex@5dd88b3 + its std::sort → insertion sort follow-up, used with their permission and credited via Co-authored-by:. Two changes to it: the _index pass above, and the handling of unresolvable entries below.
  • Sorting is skipped when the resolved positions are already ascending, so nothing changes for a page list in reading order, nor for synthetic page maps (buildSyntheticPageMap() walks the document in order).
  • Items whose XPointer doesn't resolve are given their list predecessor's position in pass 1, so they stay next to it and inherit its page number, as before. (Leaving them at _doc_y = -1 and filling them in after the sort would instead move them all to the start of the page map.)
  • The page map ends up in reading order, which is what consumers already expect: cre.cpp binary searches it by doc_y to find the current or a visible page label ("We expect items to be ordered by doc_y"), and reads the first and last items as the first and last page labels. The "go to page" list follows reading order too, so jumping to a page lands where the label is.
  • The page map is serialized in _children order, so a book already in cache keeps its old page map until its next re-render (any setting change triggering one, since render() calls invalidatePageInfo()). I did not bump CACHE_FILE_FORMAT_VERSION for this — your call if you'd rather have it fixed on first reopen.

Testing

  • Both translation units compile with no new diagnostics.
  • The two functions were also copied verbatim into a standalone harness with stubs, checking: the sample EPUB above (labels advance i 1 2 3 ii), a reading-order page list left untouched with no sort, stability and _index renumbering on entries sharing a position, unresolvable entries, an entry past the last rendered page, empty/single-item page maps, idempotency on a second run, and 2000 randomized page maps (ordered, stable, _index = 0..n-1, positions preserved).
  • Not yet exercised in KOReader itself — I'd need to build koreader-base against this branch; happy to do that if you want it before merging.

This change is Reviewable

albertmichaelj and others added 3 commits July 26, 2026 15:30
A publisher page list is not required to be in reading order, so the page
map items are not necessarily ordered by their position in the document.
Add a helper to order them by their resolved _doc_y, to be used by
LVDocView::updatePageMapInfo() once it has resolved these positions.

Items sharing the same position are tie-broken on their _index, so that
they keep their relative order.

Insertion sort: page lists are small (a few hundred items at most), and
are usually already ordered or nearly so, which insertion sort handles in
O(n) passes. It also avoids any new dependency: LVPtrVector::sort() uses
qsort(), which is not stable, and std::stable_sort() would pull in
<algorithm>.

Kept private (with LVDocView made a friend, as it already is of
LVPageMapItem): _doc_y is only known to be up-to-date inside
updatePageMapInfo(), so this is not something to expose more widely.

Co-authored-by: Tachibana Shin <tachibshin@duck.com>
The page map is built in page list order (epubfmt.cpp: ReadEpubNavPageMap(),
ReadEpubNcxPageList(), ReadEpubAdobePageMap()), and updatePageMapInfo() was
ensuring page numbers never go backward *in that list order*: any item whose
target is earlier in the document than its predecessor's had its resolved
position and page number replaced by the predecessor's.

But a page list is not required to be in reading order. Conversions commonly
move front matter (a copyright page) to the end of the spine while the page
list keeps referencing it among the front matter - and the page list is meant
to reference the equivalent location, not to be renumbered (see
https://kb.daisy.org/publishing/docs/navigation/pagelist.html). epubcheck
also dropped its reading order requirement (w3c/epubcheck commit d0f12a9).

With such a page list, a single out-of-order item raises the clamping values
to a near-the-end position, and every following item is clamped up to it. The
displayed source page number then freezes on the last label before it, and
the page numbers in the margin all pile up at that one position.

So, resolve each item's position from its XPointer with no clamping, order
the items by that resolved position, and only then ensure page numbers don't
go backward - which, positions being ordered, now only means having items
with no page number of their own get their predecessor's.

With the sample EPUBs from the report (a spine of title(i), ch1(1), ch2(2),
ch3(3), copyright(ii), with the page list in print order i, ii, 1, 2, 3):

  rendered page  0   1   2   3   4
  before         i   i   i   i   ii
  after          i   1   2   3   ii

Ordering the items also makes true what consumers already expect: KOReader's
cre.cpp binary searches the page map by doc_y to find the current or a
visible page label ("We expect items to be ordered by doc_y"), and takes the
first and last items as the first and last page labels.

The sorting is skipped when the resolved positions are already in ascending
order, which is the case for a page list in reading order and for synthetic
page maps (ldomDocument::buildSyntheticPageMap() walks the document in order).

Co-authored-by: Tachibana Shin <tachibshin@duck.com>
_index is set by addPage() to the index of the item in the page map, so
renumber the items after having reordered them, so it keeps that meaning
(and stays a valid tie-break if we are called again).

Nothing reads LVPageMapItem::getIndex() currently, but the original page
list order is of no use once we have ordered the items by their position
in the document.

@poire-z poire-z left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. Reads well.
@tachibana-shin : any comment ?

@tachibana-shin

Copy link
Copy Markdown
Contributor

I think it's good

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

EPUB page numbers freeze when the page-list nav is not in spine/reading order

3 participants