Commit Graph

78 Commits

Author SHA1 Message Date
CaptainFrito
40eeead52c feat: UI themes, Lyra 2026-02-02 08:00:16 +07:00
Arthur Tazhitdinov
f4df513bf3
feat(ui): change popup logic (#442)
## Summary

* refactors Indexing popups into ScreenComponents (they had different
implementations in different files)
* removes Indexing popup for small chapters
* only show Indexing popup (without progress bar) for large chapters
(using same minimum file size condition as for progress bar before)

## Additional Context

* Having to show even single popup message and redraw the screen slows
down the flow significantly
* Testing results:
    * Opening large chapter with progress bar - 11 seconds
* Same chapter without progress bar, only single Indexing popup - 5
seconds

---

### AI Usage

Did you use AI tools to help write this code? _**< PARTIALLY>**_
2026-02-01 18:41:24 +11:00
Dave Allie
712c566664
fix: Correctly render italics on image alt placeholders (#569)
## Summary

* Correctly render italics on image alt placeholders
  * Parser incorrectly handled depth of self-closing tags
  * Self-closing tags immediately call start and end tag

## Additional Context

* Previously, it would incorrectly make the whole chapter bold/italics,
or not italicised the image alt

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? No
2026-01-28 03:33:36 +11:00
Daniel Chelling
83315b6179
perf: optimize large EPUB indexing from O(n^2) to O(n) (#458)
## Summary

Optimizes EPUB metadata indexing for large books (2000+ chapters) from
~30 minutes to ~50 seconds by replacing O(n²) algorithms with O(n log n)
hash-indexed lookups.

Fixes #134

## Problem

Three phases had O(n²) complexity due to nested loops:

| Phase | Operation | Before (2768 chapters) |
|-------|-----------|------------------------|
| OPF Pass | For each spine ref, scan all manifest items | ~25 min |
| TOC Pass | For each TOC entry, scan all spine items | ~5 min |
| buildBookBin | For each spine item, scan ZIP central directory | ~8.4
min |

Total: **~30+ minutes** for first-time indexing of large EPUBs.

## Solution

Replace linear scans with sorted hash indexes + binary search:

- **OPF Pass**: Build `{hash(id), len, offset}` index from manifest,
binary search for each spine ref
- **TOC Pass**: Build `{hash(href), len, spineIndex}` index from spine,
binary search for each TOC entry
- **buildBookBin**: New `ZipFile::fillUncompressedSizes()` API - single
ZIP central directory scan with batch hash matching

All indexes use FNV-1a hashing with length as secondary key to minimize
collisions. Indexes are freed immediately after each phase.

## Results

**Shadow Slave EPUB (2768 chapters):**

| Phase | Before | After | Speedup |
|-------|--------|-------|---------|
| OPF pass | ~25 min | 10.8 sec | ~140x |
| TOC pass | ~5 min | 4.7 sec | ~60x |
| buildBookBin | 506 sec | 34.6 sec | ~15x |
| **Total** | **~30+ min** | **~50 sec** | **~36x** |

**Normal EPUB (87 chapters):** 1.7 sec - no regression.

## Memory

Peak temporary memory during indexing:
- OPF index: ~33KB (2770 items × 12 bytes)
- TOC index: ~33KB (2768 items × 12 bytes)
- ZIP batch: ~44KB (targets + sizes arrays)

All indexes cleared immediately after each phase. No OOM risk on
ESP32-C3.

## Note on Threshold

All optimizations are gated by `LARGE_SPINE_THRESHOLD = 400` to preserve
existing behavior for small books. However, the algorithms work
correctly for any book size and are faster even for small books:

| Book Size | Old O(n²) | New O(n log n) | Improvement |
|-----------|-----------|----------------|-------------|
| 10 ch | 100 ops | 50 ops | 2x |
| 100 ch | 10K ops | 800 ops | 12x |
| 400 ch | 160K ops | 4K ops | 40x |

If preferred, the threshold could be removed to use the optimized path
universally.

## Testing

- [x] Shadow Slave (2768 chapters): 50s first-time indexing, loads and
navigates correctly
- [x] Normal book (87 chapters): 1.7s indexing, no regression
- [x] Build passes
- [x] clang-format passes

## Files Changed

- `lib/Epub/Epub/parsers/ContentOpfParser.h/.cpp` - OPF manifest index
- `lib/Epub/Epub/BookMetadataCache.h/.cpp` - TOC index + batch size
lookup
- `lib/ZipFile/ZipFile.h/.cpp` - New `fillUncompressedSizes()` API
- `lib/Epub/Epub.cpp` - Timing logs

<details>
<summary><b>Algorithm Details</b> (click to expand)</summary>

### Phase 1: OPF Pass - Manifest to Spine Lookup

**Problem**: Each `<itemref idref="ch001">` in spine must find matching
`<item id="ch001" href="...">` in manifest.

```
OLD: For each of 2768 spine refs, scan all 2770 manifest items
     = 7.6M string comparisons

NEW: While parsing manifest, build index:
     { hash("ch001"), len=5, file_offset=120 }
     
     Sort index, then binary search for each spine ref:
     2768 × log₂(2770) ≈ 2768 × 11 = 30K comparisons
```

### Phase 2: TOC Pass - TOC Entry to Spine Index Lookup

**Problem**: Each TOC entry with `href="chapter0001.xhtml"` must find
its spine index.

```
OLD: For each of 2768 TOC entries, scan all 2768 spine entries
     = 7.6M string comparisons

NEW: At beginTocPass(), read spine once and build index:
     { hash("OEBPS/chapter0001.xhtml"), len=25, spineIndex=0 }
     
     Sort index, binary search for each TOC entry:
     2768 × log₂(2768) ≈ 30K comparisons
     
     Clear index at endTocPass() to free memory.
```

### Phase 3: buildBookBin - ZIP Size Lookup

**Problem**: Need uncompressed file size for each spine item (for
reading progress). Sizes are in ZIP central directory.

```
OLD: For each of 2768 spine items, scan ZIP central directory (2773 entries)
     = 7.6M filename reads + string comparisons
     Time: 506 seconds

NEW: 
  Step 1: Build targets from spine
          { hash("OEBPS/chapter0001.xhtml"), len=25, index=0 }
          Sort by (hash, len)
  
  Step 2: Single pass through ZIP central directory
          For each entry:
            - Compute hash ON THE FLY (no string allocation)
            - Binary search targets
            - If match: sizes[target.index] = uncompressedSize
  
  Step 3: Use sizes array directly (O(1) per spine item)
  
  Total: 2773 entries × log₂(2768) ≈ 33K comparisons
  Time: 35 seconds
```

### Why Hash + Length?

Using 64-bit FNV-1a hash + string length as a composite key:
- Collision probability: ~1 in 2⁶⁴ × typical_path_lengths
- No string storage needed in index (just 12-16 bytes per entry)
- Integer comparisons are faster than string comparisons
- Verification on match handles the rare collision case

</details>

---

_AI-assisted development. All changes tested on hardware._
2026-01-28 01:29:15 +11:00
Lalo
8e0d2bece2
feat: Add Spanish hyphenation support (#558)
## Summary

* **What is the goal of this PR?** Add Spanish language hyphenation
support to improve text rendering for Spanish books.
* **What changes are included?**
- Added Spanish hyphenation trie (`hyph-es.trie.h`) generated from
Typst's hypher patterns
- Registered `spanishHyphenator` in `LanguageRegistry.cpp` for language
tag `es`
  - Added Spanish to the hyphenation evaluation test suite
  - Added Spanish test data file with 5000 test cases

## Additional Context

* **Test Results:** Spanish hyphenation achieves 99.02% F1 Score (97.72%
perfect matches out of 5000 test cases)
* **Compatibility:** Works automatically for EPUBs with
`<dc:language>es</dc:language>` (or es-ES, es-MX, etc.)
<img width="115" height="189" alt="imagen"
src="https://github.com/user-attachments/assets/9b92e7fc-b98d-48af-8d53-dfdc2e68abee"
/>


| Metric | Value |
|--------|-------|
| Perfect matches | 97.72% |
| Overall Precision | 99.33% |
| Overall Recall | 99.42% |
| Overall F1 Score | 99.38% |

---

### AI Usage

Did you use AI tools to help write this code? _**PARTIALLY**_

AI assisted with:
- Guiding and compile
- Preparing the PR description
2026-01-28 01:17:48 +11:00
Jonas Diemer
aca6dceaa8
fix: Make sure img alt text is treated as separate text block (#497)
## Summary

Should address issues discussed in #168 and potentially fix #478.

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**PARTIALLY**_
2026-01-27 22:12:40 +11:00
Vincent Politzer
bf6cf83577
fix: line break (#525)
## Summary

* Fixes #519 
* Refactors repeated code into new function:
`ChapterHtmlSlimParser::flushPartWordBuffer()`
    
## Additional Context 
  
* The `<br/>` tag is self closing and _in-line_, so the existing logic
for closing block tags does not get applied to `<br/>` tags.
* This PR adds the _in-line_ logic to:
* Flush the word preceding the `<br/>` tag from `partWordBuffer` to
`currentTextBlock` before calling `startNewTextBlock`
* **New function**: `ChapterHtmlSlimParser::flushPartWordBuffer()`
* **Purpose**: Consolidates the logic for flushing `partWordBuffer` to
`currentTextBlock`
* **Impact**: Simplifies `ChapterHtmlSlimParser::characterData(…)`,
`ChapterHtmlSlimParser::startElement(…)`, and
`ChapterHtmlSlimParser::endElement(…)` by integrating reused code into
single function

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**NO**_
2026-01-27 22:07:02 +11:00
Jonas Diemer
9224bc3f8c
fix: #348 fit cover artifacts 2 (#465)
Supersedes #358 and includes the bugfix from #351
2026-01-27 20:21:15 +11:00
Luke Stein
7a53342f9d
fix: Allow line break after ellipsis and underscore (#425)
## Summary

* Add additional punctuation marks to the list of characters that can be
immediately followed by a line break even where there is no explicit
space

## Additional Context

* Huge appreciation to @osteotek for his amazing work on hyphenation.
Reading on the device is so much better now.
* I am getting bad line breaks when ellipses (…) are between words and
book file does not explicitly include some kind of breaking space.
* Per
[discussion](https://github.com/crosspoint-reader/crosspoint-reader/pull/305#issuecomment-3765411406),
several new characters are added in this PR to the `isExplicitHyphen`
list to allow line breaks immediately after them:

Character | Unicode | Usage | Why include it?
-- | -- | -- | --
Solidus (Slash) | U+002F | / | Essential for breaking URLs and "and/or"
constructs.
Backslash | U+005C | \ | Critical for technical text, file paths, and
coding documentation.
Underscore | U+005F | _ | Prevents "runaway" line lengths in usernames
or code snippets.
Middle Dot | U+00B7 | · | Acts as a semantic separator in dictionaries
or stylistic lists.
Ellipsis | U+2026 | … | Prevents justification failure when dialogue
lacks following spaces.
Midline Horizontal Ellipsis | U+22EF | ⋯ | Useful for mathematical
sequences and technical notation.


### Example:

This shows an example of what line breaking looks like *with* this PR.
Note the line break after "matter…" (which would not previously have
been allowed). It's particularly important here because the book
includes non-breaking spaces in "Mr. Aldrich" and "Mr. Rockefeller."


![IMG_2917](https://github.com/user-attachments/assets/8fa610a9-91dd-407f-8526-0019a8a7195f)

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? **PARTIALLY**
2026-01-27 20:18:09 +11:00
Arthur Tazhitdinov
8824c87490
feat: dict based Hyphenation (#305)
## Summary

* Adds (optional) Hyphenation for English, French, German, Russian
languages

## Additional Context

* Included hyphenation dictionaries add approximately 280kb to the flash
usage (German alone takes 200kb)
* Trie encoded dictionaries are adopted from hypher project
(https://github.com/typst/hypher)
* Soft hyphens (and other explicit hyphens) take precedence over
dict-based hyphenation. Overall, the hyphenation rules are quite
aggressive, as I believe it makes more sense on our smaller screen.

---------

Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-19 12:56:26 +00:00
Justin Mitchell
f69cddf2cc
Adds KOReader Sync support (#232)
Some checks are pending
CI / build (push) Waiting to run
## Summary

- Adds KOReader progress sync integration, allowing CrossPoint to sync
reading positions with other
KOReader-compatible devices
- Stores credentials securely with XOR obfuscation
- Uses KOReader's partial MD5 document hashing for cross-device book
matching
  - Syncs position via percentage with estimated XPath for compatibility

# Features
- Settings: KOReader Username, Password, and Authenticate options
- Sync from chapters menu: "Sync Progress" option appears when
credentials are configured
- Bidirectional sync: Can apply remote progress or upload local progress

---------

Co-authored-by: Dave Allie <dave@daveallie.com>
2026-01-19 11:55:35 +00:00
Jonas Diemer
c1c94c0112
Feature: Show img alt text (#168)
Let's start small by showing the ALT text of IMG. This is rudimentary,
but avoids those otherwise completely blank chapters.

I feel we will need this even when we can render images if that
rendering takes >1s - I would then prefer rendering optional and showing
the ALT text first.
2026-01-15 23:21:46 +11:00
efenner
d45f355e87
feat: Add EPUB table omitted placeholder (#372)
## Summary

* **What is the goal of this PR?**: Fix the bug I reported in
https://github.com/daveallie/crosspoint-reader/issues/292
* **What changes are included?**: Instead of silently dropping table
content in EPUBs., replace with an italicized '[Table omitted]' message
where tables appear.

## Additional Context

* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
  specific areas to focus on).

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**PARTIALLY **_

---------

Co-authored-by: Evan Fenner <evan@evanfenner.com>
Co-authored-by: Warp <agent@warp.dev>
2026-01-15 23:14:59 +11:00
Maeve Andrews
e517945aaa
Add a bullet to the beginning of any <li> (#368)
Currently there is no visual indication whatsoever if something is in a
list. An `<li>` is essentially just another paragraph.

As a partial remedy for this, add a bullet character to the beginning of
`<li>` text blocks so that the user can see that they're list items.
This is incomplete in that an `<ol>` should also have a counter so that
its list items can get numbers instead of bullets (right now I don't
think we track if we're in a `<ul>` or an `<ol>` at all), but it's
strictly better than the current situation.

Co-authored-by: Maeve Andrews <maeve@git.mail.maeveandrews.com>
2026-01-14 23:23:03 +11:00
Maeve Andrews
489220832f
Only indent paragraphs for justify/left-align (#367)
Currently, when Extra Paragraph Spacing is off, an em-space is added to
the beginning of each ParsedText even for blocks like headers that are
centered. This whitespace makes the centering slightly off. Change the
calculation here to only add the em-space for left/justified text.

Co-authored-by: Maeve Andrews <maeve@git.mail.maeveandrews.com>
2026-01-14 23:21:48 +11:00
Jonas Diemer
1c027ce2cd
Skip BOM character (sometimes used in front of em-dashes) (#340)
## Summary

Skip BOM character (sometimes used in front of em-dashes) - they are not
part of the glyph set and would render `?` otherwise.

---

### AI Usage

Did you use AI tools to help write this code? _**YES**_
2026-01-14 22:38:30 +11:00
Eunchurn Park
fecd1849b9
Add cover image display in *Continue Reading* card with framebuffer caching (#200)
## Summary

* **What is the goal of this PR?** (e.g., Fixes a bug in the user
authentication module,

Display the book cover image in the **"Continue Reading"** card on the
home screen, with fast navigation using framebuffer caching.

* **What changes are included?**

- Display book cover image in the "Continue Reading" card on home screen
- Load cover from cached BMP (same as sleep screen cover)
- Add framebuffer store/restore functions (`copyStoredBwBuffer`,
`freeStoredBwBuffer`) for fast navigation after initial render
- Fix `drawBitmap` scaling bug: apply scale to offset only, not to base
coordinates
- Add white text boxes behind title/author/continue reading label for
readability on cover
- Support both EPUB and XTC file cover images
- Increase HomeActivity task stack size from 2048 to 4096 for cover
image rendering

## Additional Context

* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks, specific areas to
focus on).

- Performance: First render loads cover from SD card (~800ms),
subsequent navigation uses cached framebuffer (~instant)
- Memory: Framebuffer cache uses ~48KB (6 chunks × 8KB) while on home
screen, freed on exit
- Fallback: If cover image is not available, falls back to standard
text-only display
- The `drawBitmap` fix corrects a bug where screenY = (y + offset) scale
was incorrectly scaling the base coordinates. Now correctly uses screenY
= y + (offset scale)
2026-01-14 21:24:02 +11:00
Dave Allie
8f3df7e10e
fix: Handle EPUB 3 TOC to spine mapping when nav file in subdirectory (#332)
## Summary

- Nav file in EPUB 3 file is a HTML file with relative hrefs
- If this file exists anywhere but in the same location as the
content.opf file, navigating in the book will fail
- Bump the book cache version to rebuild potentially broken books

## Additional Context

- Fixes https://github.com/daveallie/crosspoint-reader/issues/264

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code?

- [ ] Yes
- [ ] Partially
- [x] No
2026-01-13 00:57:34 +11:00
Jonas Diemer
a9242fe61f
Generate different .bmp for cropped covers so settings have effect. (#330)
Addresses
https://github.com/daveallie/crosspoint-reader/pull/225#issuecomment-3735150337
2026-01-12 20:55:47 +11:00
Dave Allie
2b12a65011
Remove HTML entity parsing (#274)
## Summary

* Remove HTML entity parsing
  * This has been completely useless since the introduction of expat
* expat tries to parse all entities in the document, but only knows of
HTML ones
* Parsing will never end with HTML entities in the text, so the
additional step to parse them that we had went completely unused
* We should figure out the best way to parse that content in the future,
but for now remove that module as it generates a lot of heap allocations
with its map and strings
2026-01-07 23:08:43 +11:00
Pavel Liashkov
0332e1103a
Add EPUB 3 nav.xhtml TOC support (#197)
## Summary

* **What is the goal of this PR?** Add EPUB 3 support by implementing
native navigation document (nav.xhtml) parsing with NCX fallback,
addressing issue Fixes: #143.

  * **What changes are included?**
- New `TocNavParser` for parsing EPUB 3 HTML5 navigation documents
(`<nav epub:type="toc">`)
- Detection of nav documents via `properties="nav"` attribute in OPF
manifest
- Fallback logic: try EPUB 3 nav first, fall back to NCX (EPUB 2) if
unavailable
- Graceful degradation: books without any TOC now load with a warning
instead of failing

  ## Additional Context

* The implementation follows the existing streaming XML parser pattern
using Expat to minimize RAM usage on the ESP32-C3
* EPUB 3 books that include both nav.xhtml and toc.ncx will prefer the
nav document (per EPUB 3 spec recommendation)
* No breaking changes - existing EPUB 2 books continue to work as before
* Tested on examples from
https://idpf.github.io/epub3-samples/30/samples.html
2026-01-03 19:10:35 +11:00
Maeve Andrews
5e9626eb2a
Add paragraph alignment setting (justify/left/center/right) (#191)
## Summary

* **What is the goal of this PR?** 

Add a new user setting for paragraph alignment, instead of hard-coding
full justification.

* **What changes are included?**

One new line in the settings screen, with 4 options
(justify/left/center/right). Default is justified since that's what it
was already. I personally only wanted to disable justification and use
"left", but I included the other options for completeness since they
were already supported.

## Additional Context

Tested on my X4 and looks as expected for each alignment.

Co-authored-by: Maeve Andrews <maeve@git.mail.maeveandrews.com>
2026-01-02 18:21:48 +11:00
Jonas Diemer
39080c0e51
Skip soft hyphens. (#195)
For now, let's skip the soft hyphens (later, we can treat them in the
layouter). See
https://github.com/daveallie/crosspoint-reader/discussions/17#discussioncomment-15378475
2026-01-02 17:54:46 +11:00
Dave Allie
6e9ba1006a
Use sane smaller data types for data in section.bin (#188)
Some checks are pending
CI / build (push) Waiting to run
## Summary

* Update EpdFontFamily::Style to be u8 instead of u32 (saving 3 bytes
per word)
* Update layout width/height to be u16 from int
* Update page element count to be u16 from u32
* Update text block element count to be u16 from u32
* Bumped section bin version to version 8
2025-12-31 13:11:36 +11:00
Dave Allie
52a0b5bbe9
Small cleanups from https://github.com/juicecultus/crosspoint-reader-x4 2025-12-30 23:19:08 +11:00
Dave Allie
3abcd0d05d
Redesign home screen (#166)
## Summary

* Redesigned home screen with big option to continue reading and
slightly nicer options to navigate to core sections
* Attempt to use the cached EPUB details (title, author) if they exist,
otherwise fall back to file name
* Adjusted button hints on home screen, removed Back option and changed
left/right to up/down

## Additional Context

* Core of this work comes from @ChandhokTannay in
1d36a86ef1
2025-12-30 23:18:10 +11:00
Jonas Diemer
03f0ce04cc
Feature: go to text/start reference in epub guide section at first start (#156)
This parses the guide section in the content.opf for text/start
references and jumps to this on first open of the book.

Currently, this behavior will be repeated in case the reader manually
jumps to Chapter 0 and then re-opens the book. IMO, this is an
acceptable edge case (for which I couldn't see a good fix other than to
drag a "first open" boolean around).

---------

Co-authored-by: Sam Davis <sam@sjd.co>
Co-authored-by: Dave Allie <dave@daveallie.com>
2025-12-30 23:02:46 +11:00
Dave Allie
be1b5bad21
Parse the author name from content.opf file (#165)
## Summary

* Parse the author name from content.opf file
  * Listed in the dc:creator tag within the metadata section
2025-12-30 22:15:44 +11:00
Dave Allie
9f31f80c80
Show previous title for unnamed spines (#158)
## Summary

* Show previous title for unnamed spines
* The spec is a little unclear, but there are plenty of cases where
chapters are split up in parts and should show the previous chapter's
title
* List TOC items instead of spine items in chapter select
* Bump `BOOK_CACHE_VERSION` to `2` to force regeneration of spine item's
TOC indexes
2025-12-30 18:52:42 +11:00
Dave Allie
fb5fc32c5d
Add exFAT support (#150)
## Summary

* Swap to updated SDCardManager which uses SdFat
* Add exFAT support
  * Swap to using FsFile everywhere
* Use newly exposed `SdMan` macro to get to static instance of
SDCardManager
* Move a bunch of FsHelpers up to SDCardManager
2025-12-30 16:09:30 +11:00
Dave Allie
071ccb9d1b
Custom zip parsing (#140)
## Summary

* Use custom zip central directory parsing to lower memory usage when
loading zipped epub content
2025-12-29 21:17:29 +11:00
Dave Allie
2437943c94
Remove usused module 2025-12-29 21:07:26 +11:00
Dave Allie
534504cf7a
Consolidate chapter page data into single file (#144)
## Summary

* Consolidate chapter page data into single file
* Header structure of the file stays the same, following the page count,
we now put a LUT offset
   * The page data is all then appended to this file
* Finally the LUT is appended to the end of the file, and the page count
is updated
* This will also significantly improve the duration of cache cleanup
which takes a while to scan the directory and cleanup content
* Remove page file version as it's all tied up into the section file now
* Bumped section file version to 7
* Moved section content into sub directory
* Updated docs

## Additional Context

* Benchmarks:
  * Generating 74 pages of content from a chapter in Jade Legacy took:
    * master: 6,229ms
    * this PR: 1,305ms
    * Speedup of 79%
  * Generating 207 pages of content from Livesuit book:
    * With progress bar UI updates:
      * master: 24,250ms
      * this PR: 8,063ms
      * Speedup of 67%
    * Without progress bar UI updates:
      * master: 13,055ms
      * this PR: 3,600ms
      * Speedup of 72%
2025-12-29 13:19:54 +11:00
Tannay
dd280bdc97
Rotation Support (#77)
•  What is the goal of this PR?  
Implement a horizontal EPUB reading mode so books can be read in
landscape orientation (both 90° and 270°), while keeping the rest of the
UI in portrait.

•  What changes are included?
◦  Rendering / Display
▪ Added an orientation model to GfxRenderer (Portrait, LandscapeNormal,
LandscapeFlipped) and made:
▪ drawPixel, drawImage, displayWindow map logical coordinates
differently depending on orientation.
▪ getScreenWidth() / getScreenHeight() return orientation‑aware logical
dimensions (480×800 in portrait, 800×480 in landscape).
◦  Settings / Configuration
▪  Extended CrossPointSettings with:
▪  landscapeReading (toggle for portrait vs. landscape EPUB reading).
▪ landscapeFlipped (toggle to flip landscape 180° so both horizontal
holding directions are supported).
▪ Updated settings serialization/deserialization to persist these fields
while remaining backward‑compatible with existing settings files.
▪  Updated SettingsActivity to expose two new toggles:
▪  “Landscape Reading”
▪  “Flip Landscape (swap top/bottom)”
◦  EPUB Reader
▪  In EpubReaderActivity:
▪ On onEnter, set GfxRenderer orientation based on the new settings
(Portrait, LandscapeNormal, or LandscapeFlipped).
▪ On onExit, reset orientation back to Portrait so Home, WiFi, Settings,
etc. continue to render as before.
▪ Adjusted renderStatusBar to position the status bar and battery
indicator relative to GfxRenderer::getScreenHeight() instead of
hard‑coded Y coordinates, so it stays correctly at the bottom in both
portrait and landscape.
◦  EPUB Caching / Layout
▪ Extended Section cache metadata (section.bin) to include the logical
screenWidth and screenHeight used when pages were generated; bumped
SECTION_FILE_VERSION.
▪  Updated loadCacheMetadata to compare:
▪ font/margins/line compression/extraParagraphSpacing and screen
dimensions; mismatches now invalidate and clear the cache.
▪ Updated persistPageDataToSD and all call sites in EpubReaderActivity
to pass the current GfxRenderer::getScreenWidth() / getScreenHeight() so
portrait and landscape caches are kept separate and correctly sized.



Additional Context

•  Cache behavior / migration
◦ Existing section.bin files (old SECTION_FILE_VERSION) will be detected
as incompatible and their caches cleared and rebuilt once per chapter
when first opened after this change.
◦ Within a given orientation, caches will be reused as before. Switching
orientation (portrait ↔ landscape) will cause a one‑time re‑index of
each chapter in the new orientation.
•  Scope and risks
◦ Orientation changes are scoped to the EPUB reader; the Home screen,
Settings, WiFi selection, sleep screens, and web server UI continue to
assume portrait orientation.
◦ The renderer’s orientation is a static/global setting; if future code
uses GfxRenderer outside the reader while a reader instance is active,
it should be aware that orientation is no longer implicitly fixed.
◦ All drawing primitives now go through orientation‑aware coordinate
transforms; any code that previously relied on edge‑case behavior or
out‑of‑bounds writes might surface as logged “Outside range” warnings
instead.
•  Testing suggestions / areas to focus on
◦  Verify in hardware:
▪ Portrait mode still renders correctly (boot, home, settings, WiFi,
reader).
▪  Landscape reading in both directions:
▪  Landscape Reading = ON, Flip Landscape = OFF.
▪  Landscape Reading = ON, Flip Landscape = ON.
▪ Status bar (page X/Y, % progress, battery icon) is fully visible and
aligned at the bottom in all three combinations.
◦  Open the same book:
▪  In portrait first, then switch to landscape and reopen it.
▪  Confirm that:
▪ Old portrait caches are rebuilt once for landscape (you should see the
“Indexing…” page).
▪ Progress save/restore still works (resume opens to the correct page in
the current orientation).
◦ Ensure grayscale rendering (the secondary pass in
EpubReaderActivity::renderContents) still looks correct in both
orientations.

---------

Co-authored-by: Dave Allie <dave@daveallie.com>
2025-12-28 21:33:20 +11:00
Eunchurn Park
eabd149371
Add retry logic and progress bar for chapter indexing (#128)
## Summary

* **What is the goal of this PR?**

Improve reliability and user experience during chapter indexing by
adding retry logic for SD card operations and a visual progress bar.

* **What changes are included?**

- **Retry logic**: Add 3 retry attempts with 50ms delay for ZIP to SD
card streaming to handle timing issues after display refresh
- **Progress bar**: Display a visual progress bar (0-100%) during
chapter indexing based on file read progress, updating every 10% to
balance responsiveness with e-ink display limitations

## Additional Context

* **Problem observed**: When navigating quickly through books with many
chapters (before chapter titles finish rendering), the "Indexing..."
screen would appear frozen. Checking the serial log revealed the
operation had silently failed, but the UI showed no indication of this.
Users would likely assume the device had crashed. Pressing the next
button again would resume operation, but this behavior was confusing and
unexpected.

* **Solution**:
- Retry logic handles transient SD card timing failures automatically,
so users don't need to manually retry
- Progress bar provides visual feedback so users know indexing is
actively working (not frozen)

* **Why timing issues occur**: After display refresh operations, there
can be timing conflicts when immediately starting SD card write
operations. This is more likely to happen when rapidly navigating
through chapters.

* **Progress bar design**: Updates every 10% to avoid excessive e-ink
refreshes while still providing meaningful feedback during long indexing
operations (especially for large chapters with CJK characters).

* **Performance**: Minimal overhead - progress calculation is simple
byte counting, and display updates use `FAST_REFRESH` mode.
2025-12-28 15:59:44 +11:00
Eunchurn Park
286b47f489
fix(parser): remove MAX_LINES limit that truncates long chapters (#132)
## Summary

* **What is the goal of this PR?** Fixes a bug where text disappears
after approximately 25 pages in long chapters during EPUB indexing.

* **What changes are included?**
- Removed the `MAX_LINES = 1000` hard limit in
`ParsedText::computeLineBreaks()`
- Added safer infinite loop prevention by checking if `nextBreakIndex <=
currentWordIndex` and forcing advancement by one word when stuck

## Additional Context

* **Root cause:** The `MAX_LINES = 1000` limit was introduced to prevent
infinite loops, but it truncates content in long chapters. For example,
a 93KB chapter that generates ~242 pages (~9,680 lines) gets cut off at
~1000 lines, causing blank pages after page 25-27.

* **Solution approach:** Instead of a hard line limit, I now detect when
the line break algorithm gets stuck (when `nextBreakIndex` doesn't
advance) and force progress by moving one word at a time. This preserves
the infinite loop protection while allowing all content to be rendered.

* **Testing:** Verified with a Korean EPUB containing a 93KB chapter -
all 242 pages now render correctly without text disappearing.
2025-12-28 10:35:45 +11:00
Dave Allie
b6bc1f7ed3
New book.bin spine and table of contents cache (#104)
## Summary

* Use single unified cache file for book spine, table of contents, and
core metadata (title, author, cover image)
* Use new temp item store file in OPF parsing to store items to be
rescaned when parsing spine
  * This avoids us holding these items in memory
* Use new toc.bin.tmp and spine.bin.tmp to build out partial toc / spine
data as part of parsing content.opf and the NCX file
  * These files are re-read multiple times to ultimately build book.bin

## Additional Context

* Spec for file format included below as an image
* This should help with:
  * #10 
  * #60 
  * #99
2025-12-24 22:36:13 +11:00
Dave Allie
2771579007
Add support for blockquote, strong, and em tags (#121)
## Summary

* Add support for blockquote, strong, and em tags
2025-12-24 22:33:17 +11:00
Dave Allie
1107590b56
Standardize File handling with FsHelpers (#110)
Some checks failed
CI / build (push) Has been cancelled
## Summary

* Standardize File handling with FsHelpers
* Better central place to manage to logic of if files exist/open for
reading/writing
2025-12-23 14:14:10 +11:00
Dave Allie
f4491875ab
Thoroughly deinitialise expat parsers before freeing them (#103)
## Summary

* Thoroughly deinitialise expat parsers before freeing them
* Spotted a few crashes when de-initing expat parsers
2025-12-22 17:16:39 +11:00
Dave Allie
424104f8ff
Fix incorrect justification of last line in paragraph (#90)
## Summary

* Fix incorrect justification of last line in paragraph
* `words` is changing size due to the slice, so `isLastLine` would
rarely be right, either removing justification mid-paragraph, or
including it in the last line.

## Additional Context

* Introduced in #73
2025-12-21 19:01:00 +11:00
Dave Allie
955c78de64
Book cover sleep screen (#89)
## Summary

* Fix issue with 2-bit bmp rendering
* Add support generate book cover BMP from JPG and use as sleep screen

## Additional Context

* It does not support other image formats beyond JPG at this point
* Something is cooked with my JpegToBmpConverter logic, it generates
weird interlaced looking images for some JPGs

| Book 1 | Book 2|
| --- | --- |
|
![IMG_5653](https://github.com/user-attachments/assets/49bbaeaa-b171-44c7-a68d-14cbe42aef03)
|
![IMG_5652](https://github.com/user-attachments/assets/7db88d70-e09a-49b0-a9a0-4cc729b4ca0c)
|
2025-12-21 18:42:06 +11:00
Dave Allie
f264efdb12
Extract EPUB TOC into temp file before parsing (#85)
## Summary

* Extract EPUB TOC into temp file before parsing
* Streaming ZIP -> XML parser uses up a lot of memory as we're
allocating inflation buffers while also holding a few copies of the
buffer in different forms
* Instead, but streaming the inflated file down to the SD card (like we
do for HTML parsing, we can lower memory usage)

## Additional Context

* This should help with
https://github.com/daveallie/crosspoint-reader/issues/60 and
https://github.com/daveallie/crosspoint-reader/issues/10. It won't
remove those class of issues completely, but will allow for many more
books to be opened.
2025-12-21 17:08:34 +11:00
Dave Allie
0d32d21d75
Small code cleanup (#83)
Some checks are pending
CI / build (push) Waiting to run
## Summary

* Fix cppcheck low violations
* Remove teardown method on parsers, use destructor
* Code cleanup
2025-12-21 15:43:53 +11:00
Jonas Diemer
926c786705
Keep ZipFile open to speed up getting file stats. (#76)
Still a bit raw, but gets the time required to determine the size of
each chapter (for reading progress) down from ~25ms to 0-1ms.

This is done by keeping the zipArchive open (so simple ;)).

Probably we don't need to cache the spine sizes anymore then...

---------

Co-authored-by: Dave Allie <dave@daveallie.com>
2025-12-21 14:38:51 +11:00
Dave Allie
299623927e
Build out lines when parsing html and holding >750 words in buffer (#73)
## Summary

* Build out lines for pages when holding over 750 buffered words
* Should fix issues with parsing long blocks of text causing memory
crashes
2025-12-21 13:43:19 +11:00
IFAKA
73d1839ddd
fix: add bounds checks to Epub getter functions (#82)
## Problem
Three Epub getter functions can throw exceptions:
- `getCumulativeSpineItemSize()`: No bounds check before
`.at(spineIndex)`
- `getSpineItem()`: If spine is empty and index invalid, `.at(0)` throws
- `getTocItem()`: If toc is empty and index invalid, `.at(0)` throws

## Fix
- Add bounds check to `getCumulativeSpineItemSize()`, return 0 on error
- Add empty container checks to `getSpineItem()` and `getTocItem()`
- Use static fallback objects for safe reference returns on empty
containers

Changed `lib/Epub/Epub.cpp`.

## Test
- Defensive additions - follows existing bounds check patterns
- No logic changes for valid inputs
- Manual device testing appreciated
2025-12-21 13:36:30 +11:00
IFAKA
adfeee063f
Handle empty spine in getBookSize() and calculateProgress() (#67)
## Problem
- `getBookSize()` calls `getCumulativeSpineItemSize(getSpineItemsCount()
- 1)` which passes -1 when spine is empty
- `calculateProgress()` then divides by zero when book size is 0

## Fix
- Return 0 from `getBookSize()` if spine is empty
- Return 0 from `calculateProgress()` if book size is 0

## Testing
- Builds successfully with `pio run`
- Affects: `lib/Epub/Epub.cpp`
2025-12-19 23:28:36 +11:00
IFAKA
3e28724b62
Add bounds checking for TOC/spine array access (#64)
## Problem
`getSpineIndexForTocIndex()` and `getTocIndexForSpineIndex()` access
`toc[tocIndex]` and `spine[spineIndex]` without validating indices are
within bounds. Malformed EPUBs or edge cases could trigger out-of-bounds
access.

## Fix
Added bounds validation at the start of both functions before accessing
the arrays.

## Testing
- Builds successfully with `pio run`
- Affects: `lib/Epub/Epub.cpp`
2025-12-19 23:23:23 +11:00
Jonas Diemer
d86b3fe134
Bugfix/word spacing indented (#59)
Some checks are pending
CI / build (push) Waiting to run
Simplified the indentation to fix having too large gaps between words
(original calculation was inaccurate).
2025-12-19 08:45:20 +11:00