## 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.
## 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.
## 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
## 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
## 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.
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>
## Summary
* Build out lines for pages when holding over 750 buffered words
* Should fix issues with parsing long blocks of text causing memory
crashes
## 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
## 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`
## 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`
## Summary
* This PR drastically reshapes the structure of the codebase, moving
from the concept of "Screens" to "Activities", restructing the files and
setting up the concept of subactivities.
* This should help with keep the main file clean and containing all
functional logic in the relevant activity.
* CrossPointState is now also a global singleton which should help with
accessing it from within activities.
## Additional Context
* This is probably going to be a bit disruptive for people with open
PRs, sorry 😞
## Summary
* Rely on media-type="application/x-dtbncx+xml" to find TOC instead of
hardcoded values
## Additional Context
* Most of my epubs don't have id==ncx for toc file location. I think
this media-type is EPUB standard
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* add horizontal indent in first line of paragraph in case Extra Paragraph Spacing is OFF
* Treat tabs as whitespace (so they are properly stripped)
* Changed size of indent to 1 em.
* Fixed calculation of space when indenting (avoiding squeezed text).
* Source code formatting
* white sleep screen
* quicker pwr button
* no extra spacing between paragraphs
* Added settings class with de/serialization and whiteSleepScreen setting to control inverting the sleep screen
* Added Settings screen for real, made settings a global singleton
* Added setting for extra paragraph spacing.
* fixed typo
* Rework after feedback
* Fixed type from bool to uint8
* First pass at moving to SDK EInkDisplay library
* Add 2-bit grayscale text and anti-aliased rendering of text
* Render status bar for empty chapters
* Refresh screen every 15 pages to avoid ghosting
* Simplify boot and sleep screens
* Give FileSelectionScreen task more stack memory
* Move text around slightly on Boot and Sleep screens
* Re-use existing buffer and write to whole screen for 'partial update'
* Add expat and swap out ERB HTML parser
* Increase EpubHtmlParserSlim file buffer to 1024 bytes
* Cleanup TextBlock functions
* Do not break words when leaving spans