Skip to content

Reference pages: add synthetic pagemap#14405

Merged
Frenzie merged 23 commits into
koreader:masterfrom
hius07:synthetic-pagemap
Oct 14, 2025
Merged

Reference pages: add synthetic pagemap#14405
Frenzie merged 23 commits into
koreader:masterfrom
hius07:synthetic-pagemap

Conversation

@hius07

@hius07 hius07 commented Oct 3, 2025

Copy link
Copy Markdown
Member

Discussed in and closes #9020.

A book with built-in reference pages.
1


A book without built-in and without synthetic reference pages.
2


A book with synthetic reference pages.
3


Default settings menu (for newly opened books).
4


This change is Reviewable

@Frenzie Frenzie left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Alright

@mergen3107

Copy link
Copy Markdown
Contributor

Cool! There is a user on MobileRead who asked about this just a few days ago: https://www.mobileread.com/forums/showthread.php?t=370154

@jonnyl2

jonnyl2 commented Oct 3, 2025

Copy link
Copy Markdown
Contributor

IMO, synthetic page numbering should be available even if the book has built-in reference pages. Built-in reference pages are mostly useful for just that -- referencing the page number in the physical copy. And they often contain alphanumeric characters, or are lacking altogether in appendices etc, which makes them less suitable (or unsuitable) for things like statistics tracking or 'go to page' functions etc. Wheareas KOReader-produced synthetic page numbers will be predictable across all books and use cases.

@hius07

hius07 commented Oct 3, 2025

Copy link
Copy Markdown
Member Author

Current implementation in crengine doesn't allow that.

@hius07

hius07 commented Oct 3, 2025

Copy link
Copy Markdown
Member Author

Statistics and GoTo work fine with non-numeric labels, see recent changes.

@jonnyl2

jonnyl2 commented Oct 3, 2025

Copy link
Copy Markdown
Contributor

I see, thanks.

@poire-z

poire-z commented Oct 3, 2025

Copy link
Copy Markdown
Contributor

A few points to discuss:

Previously, Reference pages only appeared in the first top menu when there were real reference pages. It was a reason for me to insist keeping it there (vs. users like @Commodore64user who thought it was wasting/adding a useless item there), as it was THE indicator that the book has reference pages.
With this PR, it will always be there, and there's no quick way to see if the book has real ones.
I personally don't really care about them - and I can see them on newly opened books because I have "show in margins" enabled - so I can usually just go disabling them.
So, if there is no other solution to quickly show the book has real ref pages, I'd rather have this menu moved under Settings (below ToC and Bookmarks).

Also, and this is I think a first: you have a submenu for the global setting (Newly opened books), where we find Use ref pages and Show ref page labels in margins. And there is alongside this menu, these same items that apply to the current book. I don't think we have that anywhere else. I don't know yet if I like it or not :)
Usually, we handle global vs current book with long-press and the star icon - and I admit it's not always clear and the best UX (and it is quite hard to code and get right). But it's consistent.
So, I think we need to think about that more, and decide if this could/should be a new trend, or just an exception in this case, or just not be there.

Current implementation in crengine doesn't allow that.

I did the current implementation quickly, just so we can experiment with it.
If you have some idea about a better high-level API. I can work on it (not to keen on digging into that, but if I must, I will :)).
For example, we could have another boolean in the crengine pagemap object to indicate if the current data is from publisher real page numbers or synthetic ones (switching from synthetic to real would probably need a full reload, like we need with Alt table of content (as we overwrite the ToC items parsed from the book with the ones we build).

@hius07

hius07 commented Oct 3, 2025

Copy link
Copy Markdown
Member Author

you have a submenu for the global setting

The reasons:
(1) current book items may be disabled (no built-in pagemap, disabled synthetic), screenshot No. 2; it's not obvious to try to long-press a disabled item
(2) current book chars_per_page may differ from the default one, the star wouldn't help, it's convenient to see both figures; so at least this menu item should be duplicated

In-built pagemap indicator: "Characters per page" menu item is disabled.
UPD: and of course "Reference source info" menu item.

I think it may be useful to allow synthetic pagemap to override the built-in one.
When (if) you implement that, we can think about other indicator.
UPD: Maybe, some indicator in the status bar, as I wrote earlier.

@hius07

hius07 commented Oct 3, 2025

Copy link
Copy Markdown
Member Author

The API is good.
New self.synthetic_chars_per_page indicates the source.
So the only improvement could be to override the built-in pagemap by the synthetic one.

@poire-z

poire-z commented Oct 3, 2025

Copy link
Copy Markdown
Contributor

So the only improvement could be to override the built-in pagemap by the synthetic one.

So, the API is not good :) buildSyntheticPageMapIfNoneDocumentProvided() needs another name.

New self.synthetic_chars_per_page indicates the source.

That's in frontend. In case things get desync'ed (cr3cache from the device with synthetic pagemap vs. metadata.epub.lua sync'ed from another device without synthetic_chars_per_page), you'd need to be sure to know if they differ and make them coherent. Is isPageMapSynthetic() (returning crengine's own idea of synthetic_chars_per_page enough for you in frontend to figure it all ?
No need for crengine to be itself more clever about what it's pagemap is ?

@hius07

hius07 commented Oct 3, 2025

Copy link
Copy Markdown
Member Author

In case things get desync'ed

Yes, that may be a problem.
It should be solved by invalidating/deleting the cache file, I even thought about adding this option to the "Reset document" dialog, to allow deleting it separately from resetting doc_settings.

(By the way, currently cr3 cache files are never deleted, neither while deleting nor resetting a book, the cache folder grows up without control)

@Commodore64user

Copy link
Copy Markdown
Member

who thought it was wasting/adding a useless item there

has reason finally caught up with you? about time ;)

So, if there is no other solution to quickly show the book has real ref pages, I'd rather have this menu moved under Settings (below ToC and Bookmarks).

that the green light to finally fix that whole menu?

can I put this forward too #9020 (comment), maybe you can find a way to get it working... ;)

@poire-z

poire-z commented Oct 3, 2025

Copy link
Copy Markdown
Contributor

(By the way, currently cr3 cache files are never deleted, neither while deleting nor resetting a book, the cache folder grows up without control)

It's supposed to keep at max 64MB. There's code in crengine to clean up on opening a new book. That's why we don't really need to care, and it's good enough to just delete it when deleting a book. (If that is still working, you think it doesn't ?)

-- crengine saves caches on disk for faster re-openings, and cleans
-- the less recently used ones when this limit is reached
local default_cre_disk_cache_max_size = 64 -- in MB units

that the green light to finally fix that whole menu?

No. No decision yet. No green light. Read a book.

can I put this forward too #9020 (comment), maybe you can find a way to get it working... ;)

As I answered:

Probably very hard (chapter depends on the ToC (which is elsewhere when we count text nodes) or on styles (which are not set yet)).

Moreover, at this level, we don't/can't know the chapter level for which you would want a new page. If there's 1. THIS followed by 1.1 That and 1.1.1 Those, you would not want 3 page numbers. Any more clever logic put here (which shouldn't be here) would just find cases where it would not behave. So forget it.

@hius07

hius07 commented Oct 4, 2025

Copy link
Copy Markdown
Member Author

To summarize:
(1) will we go with overriding the built-in pagemap with the synthetic one?
(2) where to place the "Reference pages" menu item: the 1st level or the Settings submenu?

@hius07

hius07 commented Oct 4, 2025

Copy link
Copy Markdown
Member Author

it's good enough to just delete it when deleting a book

You are right, we do

else -- delete
if has_sidecar_file then
local cache_file_path = doc_settings:readSetting("cache_file_path")
if cache_file_path then
os.remove(cache_file_path)
end

@poire-z

poire-z commented Oct 4, 2025

Copy link
Copy Markdown
Contributor

If you have some idea about a better high-level API. I can work on it (not to keen on digging into that, but if I must, I will :)).
For example, we could have another boolean in the crengine pagemap object to indicate if the current data is from publisher real page numbers or synthetic ones

Looks like in crengine, all this was already possible:
https://github.com/koreader/crengine/blob/1f59845878fdbdd5099d13537472b911b61bf258/crengine/include/lvtinydom.h#L2418-L2426
It's just in cre.cpp that I had put this "clever" wrapper:
https://github.com/koreader/koreader-base/blob/2d56af062b92281960a765a25f3bcd5865917228/cre.cpp#L1210-L1228

I can keep it for the transition time, and I could easily make these crengine pagemap methods (isDocumentProvided, hasDocumentProvided, isSynthetic, buildSyntheticPageMap(chars_per_synthetic_page)) directly available to frontend - if you feel these are enough, and it's right to copilot that from frontend without anything more clever in cre.cpp.
You'd still have to handle in frontend the case where we need to reload the document to rebuilt the documentprovided pagemap.

@hius07

hius07 commented Oct 4, 2025

Copy link
Copy Markdown
Member Author

I think having all crengine pagemap methods in frontend would be good.

I see in crengine:
int isSynthetic() const { return _chars_per_synthetic_page; }

Can we have getCharsPerSyntheticPage()? May be useful for desync'ed cases.

@hius07

hius07 commented Oct 4, 2025

Copy link
Copy Markdown
Member Author

Changed the property name to chars_per_synthetic_page, in accordance with crengine.

@poire-z

poire-z commented Oct 4, 2025

Copy link
Copy Markdown
Contributor

Does this look ok to you ? (the names sounds a bit inconsistent... but they read gramatically correct)

--- a/cre.cpp
+++ b/cre.cpp
@@ -1227,6 +1227,36 @@ static int isPageMapSynthetic(lua_State *L) {
     return 1;
 }

+static int buildSyntheticPageMap(lua_State *L) {
+    CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
+    int chars_per_synthetic_page = luaL_checkint(L, 2);
+    if (doc->dom_doc) {
+        doc->dom_doc->buildSyntheticPageMap(chars_per_synthetic_page);
+    }
+    return 0;
+}
+
+static int getSyntheticPageMapCharsPerPage(lua_State *L) {
+    CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
+    LVPageMap * pagemap = doc->text_view->getPageMap();
+    lua_pushinteger(L, pagemap->isSynthetic());
+    return 1;
+}
+
+static int hasPageMapDocumentProvided(lua_State *L) {
+    CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
+    LVPageMap * pagemap = doc->text_view->getPageMap();
+    lua_pushboolean(L, pagemap->hasDocumentProvided());
+    return 1;
+}
+
+static int isPageMapDocumentProvided(lua_State *L) {
+    CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");
+    LVPageMap * pagemap = doc->text_view->getPageMap();
+    lua_pushboolean(L, pagemap->isDocumentProvided());
+    return 1;
+}
+
 static int hasPageMap(lua_State *L) {
     CreDocument *doc = (CreDocument*) luaL_checkudata(L, 1, "credocument");

@@ -4311,6 +4341,10 @@ static const struct luaL_Reg credocument_meth[] = {
     {"getPageMapVisiblePageLabels", getPageMapVisiblePageLabels},
     {"buildSyntheticPageMapIfNoneDocumentProvided", buildSyntheticPageMapIfNoneDocumentProvided},
     {"isPageMapSynthetic", isPageMapSynthetic},
+    {"buildSyntheticPageMap", buildSyntheticPageMap},
+    {"getSyntheticPageMapCharsPerPage", getSyntheticPageMapCharsPerPage},
+    {"hasPageMapDocumentProvided", hasPageMapDocumentProvided},
+    {"isPageMapDocumentProvided", isPageMapDocumentProvided},
     {"hasNonLinearFlows", hasNonLinearFlows},
     {"checkRegex", checkRegex},
     {"getAndClearRegexSearchError", getAndClearRegexSearchError},

On a book with provided reference pages:

function ReaderPageMap:_postInit()
    local logger = require("logger")
    logger.warn("getSyntheticPageMapCharsPerPage", self.ui.document._document:getSyntheticPageMapCharsPerPage())
    logger.warn("hasPageMapDocumentProvided", self.ui.document._document:hasPageMapDocumentProvided())
    logger.warn("isPageMapDocumentProvided", self.ui.document._document:isPageMapDocumentProvided())
    logger.warn("buildSyntheticPageMap", self.ui.document._document:buildSyntheticPageMap(1400))
    -- logger.warn("buildSyntheticPageMap", self.ui.document._document:buildSyntheticPageMap(0))
    logger.warn("getSyntheticPageMapCharsPerPage", self.ui.document._document:getSyntheticPageMapCharsPerPage())
    logger.warn("hasPageMapDocumentProvided", self.ui.document._document:hasPageMapDocumentProvided())
    logger.warn("isPageMapDocumentProvided", self.ui.document._document:isPageMapDocumentProvided())
10/04/25-16:52:46 WARN  getSyntheticPageMapCharsPerPage 0
10/04/25-16:52:46 WARN  hasPageMapDocumentProvided true
10/04/25-16:52:46 WARN  isPageMapDocumentProvided true
CRE: building synthetic page map (1400)
10/04/25-16:52:46 WARN  buildSyntheticPageMap
10/04/25-16:52:46 WARN  getSyntheticPageMapCharsPerPage 1400
10/04/25-16:52:46 WARN  hasPageMapDocumentProvided true
10/04/25-16:52:46 WARN  isPageMapDocumentProvided false
10/04/25-16:53:45 WARN  getSyntheticPageMapCharsPerPage 1400
10/04/25-16:53:45 WARN  hasPageMapDocumentProvided true
10/04/25-16:53:45 WARN  isPageMapDocumentProvided false
10/04/25-16:53:45 WARN  buildSyntheticPageMap
10/04/25-16:53:45 WARN  getSyntheticPageMapCharsPerPage 0
10/04/25-16:53:45 WARN  hasPageMapDocumentProvided true
10/04/25-16:53:45 WARN  isPageMapDocumentProvided false  i guess this will become true after a reload

@hius07

hius07 commented Oct 4, 2025

Copy link
Copy Markdown
Member Author

Very good, thanks.

I didn't know this worked: buildSyntheticPageMap(0)
UPD: but since it requires reload anyway, maybe my solution is good as well (invalidate the cache + reload)

@poire-z

poire-z commented Oct 4, 2025

Copy link
Copy Markdown
Contributor

I didn't know this worked: buildSyntheticPageMap(0)

It will just do:

    m_pagemap.clear();
    m_pagemap.invalidatePageInfo();
    if ( chars_per_synthetic_page <= 0 ) {
        m_pagemap._chars_per_synthetic_page = 0;
        return;
    }
10/04/25-17:12:34 WARN  hasPageMapDocumentProvided false
10/04/25-17:12:34 WARN  isPageMapDocumentProvided false
10/04/25-17:12:34 WARN  hasPageMap false
CRE: building synthetic page map (1400)
10/04/25-17:12:34 WARN  buildSyntheticPageMap
10/04/25-17:12:34 WARN  getSyntheticPageMapCharsPerPage 1400
10/04/25-17:12:34 WARN  hasPageMapDocumentProvided false
10/04/25-17:12:34 WARN  isPageMapDocumentProvided false
10/04/25-17:12:34 WARN  hasPageMap true

10/04/25-17:13:07 WARN  getSyntheticPageMapCharsPerPage 1400
10/04/25-17:13:07 WARN  hasPageMapDocumentProvided false
10/04/25-17:13:07 WARN  isPageMapDocumentProvided false
10/04/25-17:13:07 WARN  hasPageMap true
10/04/25-17:13:07 WARN  buildSyntheticPageMap
10/04/25-17:13:07 WARN  getSyntheticPageMapCharsPerPage 0
10/04/25-17:13:07 WARN  hasPageMapDocumentProvided false
10/04/25-17:13:07 WARN  isPageMapDocumentProvided false
10/04/25-17:13:07 WARN  hasPageMap false

I'll let you see what else you need to do on frontend when setting and unsetting for everything to be consistent.

UPD: but since it requires reload anyway, maybe my solution is good as well (invalidate the cache + reload)

When unsetting (=0) probably. When setting (=1400), I don't know. I think it may be similar to what we need when switching to/from the Alternative ToC.

@hius07

hius07 commented Oct 4, 2025

Copy link
Copy Markdown
Member Author

I'm sure you don't forget, just in case: please also fix the page=1 issue.

@poire-z

poire-z commented Oct 4, 2025

Copy link
Copy Markdown
Contributor

Forgot to mention that above: if unsetting synthetic pages, if the book has no documentprovided, you may not need to reload it, just to clear/reset stuff in the pagemap module.

@poire-z

poire-z commented Oct 13, 2025

Copy link
Copy Markdown
Contributor

^ I'd like to be told the "source" (I call if "source", but there may be another better name), so I never have to go see that menu.

This book has stable page numbers provided by the publisher, that refer to:\n\n
<source>\n\n
Do you want to use them?

Source can be a url, or urn:isbn:9783218435, or something else, possibly multilines (?). It can be an empty string. So our _("N/A") when nil or empty.

@poire-z

poire-z commented Oct 13, 2025

Copy link
Copy Markdown
Contributor

In my has stable page numbers provided by the publisher, that refer to:, I was trying to keep a last trace of the word that you have savagely anihilated : "refer(ence)"

@hius07

hius07 commented Oct 13, 2025

Copy link
Copy Markdown
Member Author

We have "Page numbers source" in the menu item.
Is it clear that "refer to" means "the source"?

@poire-z

poire-z commented Oct 13, 2025

Copy link
Copy Markdown
Contributor

Is it clear that "refer to" means "the source"?

I think it is, but well.
We also used "reference source info" because "Reference" was the may keyword, and we needed another qualifier here to give it more identity.
image

May be now "Publisher reference" can just work, now that we don't use "reference" anywhere ?
Wait for @Frenzie @Commodore64user opinions before following this suggestion :)

@hius07

hius07 commented Oct 13, 2025

Copy link
Copy Markdown
Member Author

No "reference" in the spec
https://www.w3.org/publishing/a11y/page-source-id/

@hius07

hius07 commented Oct 13, 2025

Copy link
Copy Markdown
Member Author

By the way, they use "static page break".

Comment thread frontend/apps/reader/modules/readerpagemap.lua Outdated
@Commodore64user

Copy link
Copy Markdown
Member

I only just realised that "book hardcopy" doesn't make much sense, all books are physical copies of something, maybe it was meant to be hardback/paperback? What would that become exactly?

@hius07

hius07 commented Oct 13, 2025

Copy link
Copy Markdown
Member Author

Maybe we should change "stable" to "static"?

Comment thread frontend/apps/reader/modules/readerpagemap.lua Outdated
@poire-z

poire-z commented Oct 13, 2025

Copy link
Copy Markdown
Contributor

Maybe we should change "stable" to "static"?

I prefer "stable", it states exactly why these would be useful. "static" doesn't say that as clearly.

@Commodore64user

Commodore64user commented Oct 13, 2025

Copy link
Copy Markdown
Member

Maybe we should change "stable" to "static"?

I prefer "stable", it states exactly why these would be useful. "static" doesn't say that as clearly.

static means it doesn't change, "stable" although it also has a similar meaning, hints more at equilibrium or things being balanced.

I actually think static fits the bill better.

@poire-z

poire-z commented Oct 13, 2025

Copy link
Copy Markdown
Contributor

Not opposed to "static" if you all feel it's better.

@hius07

hius07 commented Oct 14, 2025

Copy link
Copy Markdown
Member Author

@Frenzie 's final decision: "stable page numbers" or "static page numbers"?

@Frenzie

Frenzie commented Oct 14, 2025

Copy link
Copy Markdown
Member

PDF is commonly known to have stable page numbers. Static page numbers are a problem in Microsoft Word where it doesn't update a page number when it should. ;-)

https://www.historians.org/perspectives-article/do-digital-docs-need-page-numbers-june-2011/

The manual encourages publishers, therefore, to “incorporate stable page numbers … or to otherwise number the elements in their web-based publications (e.g., through paragraph or section numbers) whenever possible.”

https://connect.ebsco.com/s/article/Citing-EBSCO-eBooks?language=en_US

EBSCO eBooks are available in both PDF and EPUB formats. PDF books have page numbers that typically correlate with the print version of the title, and those page numbers may be used for citation purposes. EPUB eBooks are formatted with reflowable text, which means the text resizes to fit the viewing window, and therefore they do not have stable pagination.

https://www.macmillanlearning.com/studentresources/highschool/english/easywriterhs4e/research_resources/mla_style_for_in_text_citations.htm

Many works found online or in electronic databases lack stable page numbers; you will have to omit the page number from the parenthetical citation in such cases. However, if you are citing a work with stable pagination, such as an article as a PDF file, include the page number in parentheses.

Etc.

@hius07

hius07 commented Oct 14, 2025

Copy link
Copy Markdown
Member Author

Okay, it's ready.

@offset-torque

Copy link
Copy Markdown

Also stable is a more familiar word because of its usage in diverse topics like stable income, stable personality, stable desk etc. It is generally used like this even in distant languages. So it might have a little advantage here.

@Frenzie Frenzie added this to the 2025.10 milestone Oct 14, 2025
@Frenzie Frenzie merged commit d5a54f6 into koreader:master Oct 14, 2025
4 checks passed
@Commodore64user

Copy link
Copy Markdown
Member

Congrats we are now Living in the plastic age.

@hius07 hius07 deleted the synthetic-pagemap branch October 15, 2025 03:11
@NQK

NQK commented Oct 16, 2025

Copy link
Copy Markdown

Oh, thank you.

This page counting mechanism is much better than I expected. Even the (x)html file includes only a handful of texts (like Part 1) or only an image with no text, is counted as 1. It makes sense.

@poire-z

poire-z commented Oct 16, 2025

Copy link
Copy Markdown
Contributor

This page counting mechanism is much better than I expected. Even the (x)html file includes only a handful of texts (like Part 1) or only an image with no text, is counted as 1. It makes sense.

Thanks for the feedback. Illustrated at #14426 (comment) .

@poire-z

poire-z commented Oct 16, 2025

Copy link
Copy Markdown
Contributor

Just updated, and I was a bit confused, as I didn't get the popup on a new book with ref pages.
Looking at the menu, [x] Show page labels in margins was checked - but in Default setting for new books > [ ] Show page labels in margins it wasn't.
There is a isTrue() in that menu item checked_func - but there is a G_reader_settings:nilOrTrue("pagemap_show_page_labels") in ReaderPageMap:onReadSettings(). So the mismatch I think.
(I didn't have any pagemap_show_page_labels in my settings.reader.lua - so I probably never use that menu item to toggle that.)
Might need to check all the things you didn't touch that they are consistent with what you touched.

(So, Show page labels in margin was defaulted to true - given the amount of people getting them and not liking them - but @Frenzie does, if we go at (p) in Book information, may be we could default to not enabled ?)

@Frenzie

Frenzie commented Oct 16, 2025

Copy link
Copy Markdown
Member

(So, Show page labels in margin was defaulted to true - given the amount of people getting them and not liking them - but @Frenzie does, if we go at (p) in Book information, may be we could default to not enabled ?)

Are they? It all looks very disabled to me. (Same on my Kobo, where I almost certainly didn't touch these settings.)
image

Edit:

pagemap_show_page_labels

I don't have that either.

0xstillb pushed a commit to 0xstillb/koreader-thai that referenced this pull request May 9, 2026
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.

FR: Logical Page count for crengine formats.

9 participants