Skip to content

feat: Footnotes support (based on PR #43)#553

Closed
Uri-Tauber wants to merge 35 commits intocrosspoint-reader:masterfrom
Uri-Tauber:pr/43
Closed

feat: Footnotes support (based on PR #43)#553
Uri-Tauber wants to merge 35 commits intocrosspoint-reader:masterfrom
Uri-Tauber:pr/43

Conversation

@Uri-Tauber
Copy link
Contributor

Summary

  • What is the goal of this PR? This PR integrates comprehensive support for footnotes and paragraph notes into the Epub reader.
    It based on @jlaunay work in Feature/footnotes #43 with the latest changes from the main branch.

  • What changes are included?

  • Footnote Detection & Collection: Two-pass parsing system that first collects inline footnotes and paragraph notes, then processes the main content
  • Footnote Navigation: Navigate to footnote content and return to your original reading position
  • Footnote UI: New menu system with dedicated footnote viewer screen showing all notes for the current page

Additional Context

Tested and debuged om my Xteink X4.
There is a place for optimization, since the RAM usage is bigger in 20kb for books with a lot of footnotes. Need to do further testing and comparing on books without footnotes.

Master Sync: Merged the latest architectural improvements from the master branch, including the updated activity lifecycle and input management.
This PR now can be merged without any conflicts.


AI Usage

Did you use AI tools to help write this code? < YES > the actual conflicts resolving was done by Anti-Gravity (Gemini 3 pro high), However, I debugged and tested the changes myself.

@Uri-Tauber Uri-Tauber force-pushed the pr/43 branch 2 times, most recently from f2398f7 to 9441104 Compare January 27, 2026 21:14
@Uri-Tauber
Copy link
Contributor Author

Uri-Tauber commented Jan 27, 2026

I Improved the UX a bit: Chapters and Footnotes now use tabs instead of a navigation menu.
While additional testing is needed, the current implementation appears solid and ready for review.

demonstrate2

Though I believe it's working correctly, I would appreciate confirmation from others to ensure I didn't missed anything.

@osteotek
Copy link
Member

How does the performance of a two-pass system compare to what we have now?

@Uri-Tauber
Copy link
Contributor Author

Uri-Tauber commented Jan 27, 2026

Tests I made with my fork.
Memory_usage_with costum_footnotes

Test I made with the master branch (v 0.16)
Memory_usage_with master_branch

RAM usage increased slightly (under 20KB) during reading, with the highest impact seen in books containing many footnotes.

@Uri-Tauber
Copy link
Contributor Author

@osteotek I hope this answers your question, however, if I misunderstood, please clarify.

@osteotek
Copy link
Member

osteotek commented Jan 29, 2026

@Uri-Tauber Thanks for the effort, I am very much interested in having footnotes too. I've been testing this PR, found a few issues:

  • Footnotes that look like <a href="javascript:void(0)" class="a" data-3z2xdcpcsjrsrrngbq6c9f="{&quot;name&quot;: &quot;OPS/ch2.xhtml&quot;, &quot;frag&quot;: &quot;id46&quot;}">[10]</a> are not supported. Most of my books have footnotes in this particular format for some reason. Well, almost no other e-reader apps support them as well.

  • Footnotes from one page show up in menu on previous page. Uploaded video

  • I’ve noticed that certain parts of the text are disappearing with this PR. It seems like there might be a parsing issue. I’ve uploaded screenshots (sorry for the foreign language). This doesn't happen with master branch.

  • Footnotes on other page (notice footnote [1]) - https://github.com/user-attachments/assets/37a51c07-7afb-45dc-abbd-ddf68ce24596

  • Examples of missing text -
    PXL_20260129_091818760 jpg
    PXL_20260129_091848235 jpg

@Uri-Tauber
Copy link
Contributor Author

Uri-Tauber commented Jan 29, 2026

@osteotek Thanks for the feedback!

Regarding the first issue: I’m thinking of simplifying the logic for identifying footnotes so that any <a> tag that doesn’t link outside the EPUB is treated as a footnote.

As for the other bugs, I’ll dig into the parsing logic — there’s a good chance I accidentally removed something. It would be really helpful if you could check which tags are present on the affected text blocks that don’t appear on the correctly displayed text (or vice versa). If you’re able to share an EPUB file with these issues (preferably in English), that would help a lot as well.

@osteotek
Copy link
Member

osteotek commented Jan 29, 2026

@Uri-Tauber in book missing text begin right after <sup> tag and stops when it sees the <a> tag. I'll try to find book with demonstration

@Uri-Tauber
Copy link
Contributor Author

I think I’ve addressed all the issues @osteotek raised.
The Copilot review will be handled shortly.

@osteotek
Copy link
Member

Will have to find epub with inline footnotes (or generate it) for proper testing

@Uri-Tauber
Copy link
Contributor Author

Will have to find epub with inline footnotes (or generate it) for proper testing

Maybe @jlaunay can help here.

@jlaunay
Copy link

jlaunay commented Feb 10, 2026

Will have to find epub with inline footnotes (or generate it) for proper testing

Maybe @jlaunay can help here.

I haven't followed this topic for a while, but I had this test file which might be useful?

Latin, Styles Footnotes Test - ParserTest.epub.zip
Remove .zip extension (had to rename to allow upload here).

@Uri-Tauber
Copy link
Contributor Author

Uri-Tauber commented Feb 11, 2026

Implemented the majority of Copilot’s recommendations.
Tested the EPUB shared by @jlaunay: footnotes 1 and 3 render correctly, while footnotes 2 and 4 have some problem — I'll fix it soon.

@Uri-Tauber
Copy link
Contributor Author

Fixed Everything. Ready for review.

}

// Simple HTML entity replacement for noteref text
std::string replaceHtmlEntities(const char* text) {
Copy link
Member

@osteotek osteotek Feb 13, 2026

Choose a reason for hiding this comment

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

This function can probably be combined with lookupHtmlEntity function that is going to be introduced by #757 (depending on what's going to be merged first)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will look into this.

Copy link
Contributor Author

@Uri-Tauber Uri-Tauber Feb 14, 2026

Choose a reason for hiding this comment

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

I reviewed lookupHtmlEntity. While it’s more comprehensive than my method, it’s also quite CPU‑intensive.
replaceHtmlEntities runs once per each word in the text (Therefore, I tried to optimize it as much as possible). On the ESP32‑C3, using lookupHtmlEntity instead will means a linear scan of over 170 symbols, which adds noticeable rendering time and is an overkill for the device.

What do you think?

Copy link
Member

Choose a reason for hiding this comment

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

lookupHtmlEntity already has replacements for &lt;, &gt; , &amp; &quot; and it already runs on these entities, so not sure how useful to run replaceHtmlEntities function over every word again

@osteotek
Copy link
Member

@Uri-Tauber I've been testing this, and in the provided footnotes sample file jumping to links 1 and 3 (same file cases) doesn't jump to their location. Is this because we don't support jumping to anchors yet?

@Uri-Tauber
Copy link
Contributor Author

@Uri-Tauber I've been testing this, and in the provided footnotes sample file jumping to links 1 and 3 (same file cases) doesn't jump to their location. Is this because we don't support jumping to anchors yet?

It is. I already have a vague idea how to implement anchors, but let's finish with this PR first...

@Uri-Tauber
Copy link
Contributor Author

Uri-Tauber commented Feb 17, 2026

@osteotek It seems this PR might be stalled, possibly because it’s too large and difficult to review.

Would it make sense to split it up and start with a smaller PR that adds “slim” footnotes support — without Virtual Spine Items and <aside> notes, just basic <a> footnotes?

I think that would significantly reduce the amount of new code and make the review process easier.

What do you think?

@osteotek
Copy link
Member

Would it make sense to split it up and start with a smaller PR that adds “slim” footnotes support — without Virtual Spine Items and <aside> notes, just basic <a> footnotes?

Yes, let’s try that approach. To be honest, I have some reservations about this virtual spine-building and writing custom XML. Seems like there is a simpler way

@Uri-Tauber
Copy link
Contributor Author

Yes, let’s try that approach. To be honest, I have some reservations about this virtual spine-building and writing custom XML. Seems like there is a simpler way

Sure. I started this PR by rebasing #43 onto the latest master, but I agree that some of the logic may be over‑complicated for Crosspoint. I’ll close this PR as soon as I create a new one.

@Uri-Tauber Uri-Tauber closed this Feb 18, 2026
@Uri-Tauber Uri-Tauber deleted the pr/43 branch February 18, 2026 16:12
@Uri-Tauber
Copy link
Contributor Author

@osteotek take a look at #988.

znelson pushed a commit that referenced this pull request Feb 26, 2026
## Summary
**What is the goal of this PR?** Implement support for footnotes in epub
files.
It is based on #553, but simplified — removed the parts which
complicated the code and burden the CPU/RAM. This version supports basic
footnotes and lets the user jump from location to location inside the
epub.

**What changes are included?**
- `FootnoteEntry` struct — A small POD struct (number[24], href[64])
shared between parser, page storage, and UI.
- Parser: `<a href>` detection (`ChapterHtmlSlimParser`) — During a
single parsing pass, internal epub links are detected and collected as
footnotes. The link text is underlined to hint navigability.
Bracket/whitespace normalization is applied to the display label (e.g.
[1] → 1).
- Footnote-to-page assignment (`ChapterHtmlSlimParser`, `Page`) —
Footnotes are attached to the exact page where their anchor word
appears, tracked via a cumulative word counter during layout, surviving
paragraph splits and the 750-word mid-paragraph safety flush.
- Page serialization (`Page`, `Section`) — Footnotes are
serialized/deserialized per page (max 16 per page). Section cache
version bumped to 14 to force a clean rebuild.
- Href → spine resolution (`Epub`) — `resolveHrefToSpineIndex()` maps an
href (e.g. `chapter2.xhtml#note1`) to its spine index by filename
matching.
- Footnotes menu + activity (`EpubReaderMenuActivity`,
`EpubReaderFootnotesActivity`) — A new "Footnotes" entry in the reader
menu lists all footnote links found on the current page. The user
scrolls and selects to navigate.
- Navigate & restore (`EpubReaderActivity`) — `navigateToHref()` saves
the current spine index and page number, then jumps to the target. The
Back button restores the saved position when the user is done reading
the footnote.

  **Additional Context**

**What was removed vs #553:** virtual spine items
(`addVirtualSpineItem`, `isVirtualSpineItem`), two-pass parsing,
`<aside>` content extraction to temp HTML files, `<p class="note">`
paragraph note extraction, `replaceHtmlEntities` (master already has
`lookupHtmlEntity`), `footnotePages` / `buildFilteredChapterList`,
`noterefCallback` / `Noteref` struct, and the stack size increase from 8
KB to 24 KB (not needed without two-pass parsing and virtual file I/O on
the render task).
 
**Performance:** Single-pass parsing. No new heap allocations in the hot
path — footnote text is collected into fixed stack buffers (char[24],
char[64]). Active runtime memory is ~2.8 KB worst-case (one page × 16
footnotes × 88 bytes, mirrored in `currentPageFootnotes`). Flash usage
is unchanged at 97.4%; RAM stays at 31%.
   
**Known limitations:** When clicking a footnote, it jumps to the start
of the HTML file instead of the specific anchor. This could be
problematic for books that don't have separate files for each footnote.
(no element-id-to-page mapping yet - will be another PR soon).

---

### AI Usage

Did you use AI tools to help write this code? _**< PARTIALLY>**_
Claude Opus 4.6 was used to do most of the migration, I checked manually
its work, and fixed some stuff, but I haven't review all the changes
yet, so feedback is welcomed.

---------

Co-authored-by: Arthur Tazhitdinov <[email protected]>
el pushed a commit to el/crosspoint-reader that referenced this pull request Feb 26, 2026
**What is the goal of this PR?** Implement support for footnotes in epub
files.
It is based on crosspoint-reader#553, but simplified — removed the parts which
complicated the code and burden the CPU/RAM. This version supports basic
footnotes and lets the user jump from location to location inside the
epub.

**What changes are included?**
- `FootnoteEntry` struct — A small POD struct (number[24], href[64])
shared between parser, page storage, and UI.
- Parser: `<a href>` detection (`ChapterHtmlSlimParser`) — During a
single parsing pass, internal epub links are detected and collected as
footnotes. The link text is underlined to hint navigability.
Bracket/whitespace normalization is applied to the display label (e.g.
[1] → 1).
- Footnote-to-page assignment (`ChapterHtmlSlimParser`, `Page`) —
Footnotes are attached to the exact page where their anchor word
appears, tracked via a cumulative word counter during layout, surviving
paragraph splits and the 750-word mid-paragraph safety flush.
- Page serialization (`Page`, `Section`) — Footnotes are
serialized/deserialized per page (max 16 per page). Section cache
version bumped to 14 to force a clean rebuild.
- Href → spine resolution (`Epub`) — `resolveHrefToSpineIndex()` maps an
href (e.g. `chapter2.xhtml#note1`) to its spine index by filename
matching.
- Footnotes menu + activity (`EpubReaderMenuActivity`,
`EpubReaderFootnotesActivity`) — A new "Footnotes" entry in the reader
menu lists all footnote links found on the current page. The user
scrolls and selects to navigate.
- Navigate & restore (`EpubReaderActivity`) — `navigateToHref()` saves
the current spine index and page number, then jumps to the target. The
Back button restores the saved position when the user is done reading
the footnote.

  **Additional Context**

**What was removed vs crosspoint-reader#553:** virtual spine items
(`addVirtualSpineItem`, `isVirtualSpineItem`), two-pass parsing,
`<aside>` content extraction to temp HTML files, `<p class="note">`
paragraph note extraction, `replaceHtmlEntities` (master already has
`lookupHtmlEntity`), `footnotePages` / `buildFilteredChapterList`,
`noterefCallback` / `Noteref` struct, and the stack size increase from 8
KB to 24 KB (not needed without two-pass parsing and virtual file I/O on
the render task).

**Performance:** Single-pass parsing. No new heap allocations in the hot
path — footnote text is collected into fixed stack buffers (char[24],
char[64]). Active runtime memory is ~2.8 KB worst-case (one page × 16
footnotes × 88 bytes, mirrored in `currentPageFootnotes`). Flash usage
is unchanged at 97.4%; RAM stays at 31%.

**Known limitations:** When clicking a footnote, it jumps to the start
of the HTML file instead of the specific anchor. This could be
problematic for books that don't have separate files for each footnote.
(no element-id-to-page mapping yet - will be another PR soon).

---

Did you use AI tools to help write this code? _**< PARTIALLY>**_
Claude Opus 4.6 was used to do most of the migration, I checked manually
its work, and fixed some stuff, but I haven't review all the changes
yet, so feedback is welcomed.

---------

Co-authored-by: Arthur Tazhitdinov <[email protected]>
el pushed a commit to el/crosspoint-reader that referenced this pull request Feb 26, 2026
**What is the goal of this PR?** Implement support for footnotes in epub
files.
It is based on crosspoint-reader#553, but simplified — removed the parts which
complicated the code and burden the CPU/RAM. This version supports basic
footnotes and lets the user jump from location to location inside the
epub.

**What changes are included?**
- `FootnoteEntry` struct — A small POD struct (number[24], href[64])
shared between parser, page storage, and UI.
- Parser: `<a href>` detection (`ChapterHtmlSlimParser`) — During a
single parsing pass, internal epub links are detected and collected as
footnotes. The link text is underlined to hint navigability.
Bracket/whitespace normalization is applied to the display label (e.g.
[1] → 1).
- Footnote-to-page assignment (`ChapterHtmlSlimParser`, `Page`) —
Footnotes are attached to the exact page where their anchor word
appears, tracked via a cumulative word counter during layout, surviving
paragraph splits and the 750-word mid-paragraph safety flush.
- Page serialization (`Page`, `Section`) — Footnotes are
serialized/deserialized per page (max 16 per page). Section cache
version bumped to 14 to force a clean rebuild.
- Href → spine resolution (`Epub`) — `resolveHrefToSpineIndex()` maps an
href (e.g. `chapter2.xhtml#note1`) to its spine index by filename
matching.
- Footnotes menu + activity (`EpubReaderMenuActivity`,
`EpubReaderFootnotesActivity`) — A new "Footnotes" entry in the reader
menu lists all footnote links found on the current page. The user
scrolls and selects to navigate.
- Navigate & restore (`EpubReaderActivity`) — `navigateToHref()` saves
the current spine index and page number, then jumps to the target. The
Back button restores the saved position when the user is done reading
the footnote.

  **Additional Context**

**What was removed vs crosspoint-reader#553:** virtual spine items
(`addVirtualSpineItem`, `isVirtualSpineItem`), two-pass parsing,
`<aside>` content extraction to temp HTML files, `<p class="note">`
paragraph note extraction, `replaceHtmlEntities` (master already has
`lookupHtmlEntity`), `footnotePages` / `buildFilteredChapterList`,
`noterefCallback` / `Noteref` struct, and the stack size increase from 8
KB to 24 KB (not needed without two-pass parsing and virtual file I/O on
the render task).

**Performance:** Single-pass parsing. No new heap allocations in the hot
path — footnote text is collected into fixed stack buffers (char[24],
char[64]). Active runtime memory is ~2.8 KB worst-case (one page × 16
footnotes × 88 bytes, mirrored in `currentPageFootnotes`). Flash usage
is unchanged at 97.4%; RAM stays at 31%.

**Known limitations:** When clicking a footnote, it jumps to the start
of the HTML file instead of the specific anchor. This could be
problematic for books that don't have separate files for each footnote.
(no element-id-to-page mapping yet - will be another PR soon).

---

Did you use AI tools to help write this code? _**< PARTIALLY>**_
Claude Opus 4.6 was used to do most of the migration, I checked manually
its work, and fixed some stuff, but I haven't review all the changes
yet, so feedback is welcomed.

---------

Co-authored-by: Arthur Tazhitdinov <[email protected]>
iandchasse pushed a commit to iandchasse/crosspoint-reader-minRead that referenced this pull request Feb 27, 2026
**What is the goal of this PR?** Implement support for footnotes in epub
files.
It is based on crosspoint-reader#553, but simplified — removed the parts which
complicated the code and burden the CPU/RAM. This version supports basic
footnotes and lets the user jump from location to location inside the
epub.

**What changes are included?**
- `FootnoteEntry` struct — A small POD struct (number[24], href[64])
shared between parser, page storage, and UI.
- Parser: `<a href>` detection (`ChapterHtmlSlimParser`) — During a
single parsing pass, internal epub links are detected and collected as
footnotes. The link text is underlined to hint navigability.
Bracket/whitespace normalization is applied to the display label (e.g.
[1] → 1).
- Footnote-to-page assignment (`ChapterHtmlSlimParser`, `Page`) —
Footnotes are attached to the exact page where their anchor word
appears, tracked via a cumulative word counter during layout, surviving
paragraph splits and the 750-word mid-paragraph safety flush.
- Page serialization (`Page`, `Section`) — Footnotes are
serialized/deserialized per page (max 16 per page). Section cache
version bumped to 14 to force a clean rebuild.
- Href → spine resolution (`Epub`) — `resolveHrefToSpineIndex()` maps an
href (e.g. `chapter2.xhtml#note1`) to its spine index by filename
matching.
- Footnotes menu + activity (`EpubReaderMenuActivity`,
`EpubReaderFootnotesActivity`) — A new "Footnotes" entry in the reader
menu lists all footnote links found on the current page. The user
scrolls and selects to navigate.
- Navigate & restore (`EpubReaderActivity`) — `navigateToHref()` saves
the current spine index and page number, then jumps to the target. The
Back button restores the saved position when the user is done reading
the footnote.

  **Additional Context**

**What was removed vs crosspoint-reader#553:** virtual spine items
(`addVirtualSpineItem`, `isVirtualSpineItem`), two-pass parsing,
`<aside>` content extraction to temp HTML files, `<p class="note">`
paragraph note extraction, `replaceHtmlEntities` (master already has
`lookupHtmlEntity`), `footnotePages` / `buildFilteredChapterList`,
`noterefCallback` / `Noteref` struct, and the stack size increase from 8
KB to 24 KB (not needed without two-pass parsing and virtual file I/O on
the render task).

**Performance:** Single-pass parsing. No new heap allocations in the hot
path — footnote text is collected into fixed stack buffers (char[24],
char[64]). Active runtime memory is ~2.8 KB worst-case (one page × 16
footnotes × 88 bytes, mirrored in `currentPageFootnotes`). Flash usage
is unchanged at 97.4%; RAM stays at 31%.

**Known limitations:** When clicking a footnote, it jumps to the start
of the HTML file instead of the specific anchor. This could be
problematic for books that don't have separate files for each footnote.
(no element-id-to-page mapping yet - will be another PR soon).

---

Did you use AI tools to help write this code? _**< PARTIALLY>**_
Claude Opus 4.6 was used to do most of the migration, I checked manually
its work, and fixed some stuff, but I haven't review all the changes
yet, so feedback is welcomed.

---------

Co-authored-by: Arthur Tazhitdinov <[email protected]>
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.

5 participants