Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
d44fe4a
fix: suppress back release from reader settings screens
uxjulia Jun 14, 2026
90cad03
feat: index large file browser folders on SD
uxjulia Jun 14, 2026
55a5691
fix(i18n): localize home empty-state strings (#2342)
krunkosaurus Jun 14, 2026
bdcc6e5
fix: avoid oversized web multi-delete requests (fixes #269)
uxjulia Jun 14, 2026
552da01
fix: improve handling of invisible codepoints
uxjulia Jun 14, 2026
d13252a
feat: add first pass for clipping support
uxjulia Jun 14, 2026
e39d6c4
fix: anchor clipping highlights to text after relayout
uxjulia Jun 14, 2026
6d77347
fix: clean clipping preview text
uxjulia Jun 14, 2026
7910ea3
ci: update release workflow for catalog publishing
uxjulia Jun 15, 2026
02bab00
fix: swap reader menu navigation direction in CCW/inverted (#2321) (#…
TheCyberRonin Jun 15, 2026
16eb66d
fix: add missing HTML 4.01 named entities (#2352)
rafaelmsse Jun 15, 2026
90039d7
fix(koreader): resolve element XPath progress against visible body te…
uxjulia Jun 16, 2026
2266060
fix: Ukrainian translation (#2354)
KymAndriy Jun 16, 2026
7cd16d3
fix: lower memory guard to what jpeg decoder actually needs
uxjulia Jun 16, 2026
f8515e4
fix: preserve EPUB position across font-size reindex
uxjulia Jun 16, 2026
1d702bd
fix: add epub compatibility mode that falls back to upstream's parsin…
uxjulia Jun 16, 2026
d950a1a
fix: font resizing causing large jumps
uxjulia Jun 16, 2026
c0bebf4
fix: index large folders on sd card
uxjulia Jun 16, 2026
660b28c
Add grayscale preconditioning for e-ink displays
itsthisjustin Jun 14, 2026
a2eefce
Add displayGrayscaleBase method for differential refresh
itsthisjustin Jun 14, 2026
ee9daed
update sdk
uxjulia Jun 14, 2026
b4d0ee1
feat: add clipping/highlighting support
uxjulia Jun 16, 2026
0275473
fix: regenerate fonts to handle strange codepoints
uxjulia Jun 16, 2026
96e3c70
fix: avoid overlapping Calibre transfer status
uxjulia Jun 16, 2026
4cdb5ab
fix: improve rendering of invisible codepoints
uxjulia Jun 16, 2026
43044ca
feat: save EPUB reader settings per book
uxjulia Jun 16, 2026
8cb19cc
fix: prewarm SD fonts for clipping selection
uxjulia Jun 16, 2026
60cc4da
feat: new in-reader menu
uxjulia Jun 16, 2026
365b6c3
fix: improve reader menu tab focus state
uxjulia Jun 17, 2026
055209c
fix: exit reader menu from focused tab row
uxjulia Jun 17, 2026
8005b74
fix: align clipping highlights with SD font advances
uxjulia Jun 17, 2026
ac17aa7
fix: clipping with sd card fonts rendering missing
uxjulia Jun 17, 2026
f66220a
fix: enable Shift key for URL keyboard input (#2178) (#2357)
nnnkit Jun 17, 2026
54d5a78
feat: add drag-and-drop to upload modal (#2290)
krunkosaurus Jun 17, 2026
d4069ae
feat: long-press Confirm launches KOReader sync from EPUB (#1808)
darkbublu Jun 17, 2026
22f3575
feat: Support for Korean line breaks and glyph spacing (#2288)
itsthisjustin Jun 17, 2026
f58ae93
fix: stacked calibre transfer progress bars
uxjulia Jun 17, 2026
522cc2b
fix: protect incompatible reading stats files
uxjulia Jun 17, 2026
0f20dbf
chore: update docs for clippings/highlights
uxjulia Jun 17, 2026
15c0063
fix(i18n): localize home empty-state strings (#2342)
krunkosaurus Jun 14, 2026
b2e55ce
fix: add missing HTML 4.01 named entities (#2352)
rafaelmsse Jun 15, 2026
14d6437
fix: enable Shift key for URL keyboard input (#2178) (#2357)
nnnkit Jun 17, 2026
375a7ea
feat: add drag-and-drop to upload modal (#2290)
krunkosaurus Jun 17, 2026
5be93a7
feat: Support for Korean line breaks and glyph spacing (#2288)
itsthisjustin Jun 17, 2026
cb8448a
fix: persist reader time-left estimates for home stats
uxjulia Jun 16, 2026
d63ec6e
fix: prepare SD font metrics for clipping selection
uxjulia Jun 17, 2026
9b82996
fix: add stats.bin migration flow
uxjulia Jun 17, 2026
f19f763
fix: clippings not highlighting correctly with SD card fonts
uxjulia Jun 17, 2026
c4e8d19
fix: remove unlabeled clipping page number
uxjulia Jun 17, 2026
cfe3b60
fix: keep txt reader open at file boundary
uxjulia Jun 17, 2026
4f0ebb3
feat: apply inverted filter to generated sleep screens
uxjulia Jun 17, 2026
eeeba56
fix: move roundedraff clock down on home screen
uxjulia Jun 17, 2026
b9cf3fe
chore: sync upstream Crossspoint
uxjulia Jun 17, 2026
125fa18
fix: cppchecks
uxjulia Jun 17, 2026
3e68387
fix: export clippings with selected page number
uxjulia Jun 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ jobs:
steps:
- uses: actions/checkout@v6
with:
token: ${{ secrets.RELEASE_PAT }}
submodules: recursive
fetch-depth: 0

Expand Down
19 changes: 17 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
# Changelog

## [Unreleased]
## [1.3.4-RC] - 2026-06-17

### Added
- File Browser now indexes large SD-card folders so directories with many books can be browsed without loading every filename into memory at once.
- EPUB text clipping with saved highlights, clipping lists, and Kindle-style `/My Clippings.txt` export.
- Per-book EPUB reader settings for font, layout, styling, and reading aids when changed from inside the reader.

### Changed
- The EPUB reader menu has been updated to split out the growing menu of items into 3 separate screens for faster navigation.
- The Epub reader menu now labels per-book reader settings as `Book Options` and avoids showing duplicate `Orientation` controls.
- The `Inverted` sleep cover filter now flips Minimal and Reading Stats sleep screens to black text on a white background.

### Fixed
- Calibre Wireless transfer status no longer stacks the last received-file message on top of the upload percentage.
- EPUB chapters that run out of memory during full CrossInk layout now retry with a lighter compatibility renderer before showing a low-memory error.
- EPUB reader font-size changes now restore the current chapter position by content instead of jumping far backward after re-indexing.
- Reading Stats now use the reader's last live book time-left estimate instead of showing a separate fallback estimate.
- Per-book reading stats now migrate compatible legacy `stats.bin` files into the `stats_v5.bin` flow instead of resetting when only the old filename exists.
- Per-book reading stats now use versioned filenames so firmware builds with different schemas do not overwrite each other's stats.
- Lyra Carousel Home menu rendering now avoids extra label allocations that could crash tiny builds under low memory.
- TXT readers now stay open when pressing a page-turn button at the end of the file.
- Long-press reader shortcuts that open another screen no longer close or confirm it again when releasing the shortcut button.
- RoundedRaff's header battery icon and percentage now sit lower to avoid clipping at the top edge.
- Lyra Carousel now redraws the Home header when restoring cached carousel frames so battery percentage and clock values stay current while navigating between books.
- Lyra Carousel now keeps the Home header current when rendering the menu or restoring cached carousel frames, preventing stale battery and clock values while navigating between books.
- Web file manager multi-delete now handles larger selections without failing after a small batch.

## [v1.3.3] - 2026-06-13

Expand Down
17 changes: 15 additions & 2 deletions docs/data-cache.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ The main data directory is `.crosspoint` on the SD card. It stores render caches
├── opds.json # Saved OPDS servers
├── koreader.json # KOReader sync credentials
├── bookmarks/ # Bookmark files, one per book
├── clippings/ # EPUB clipping/highlight files, one per book
├── home_carousel_cache.bin # Lyra Carousel home-screen snapshot cache
├── sleep_frame.bin # Temporary sleep overlay framebuffer, when used
├── epub_12471232/ # Each EPUB is cached to epub_<hash>
│ ├── progress.bin # Reading position (chapter, page, etc.)
│ ├── stats.bin # Per-book reading stats
│ ├── reader_settings.bin # Per-book reader settings
│ ├── stats.bin # Legacy per-book reading stats
│ ├── stats_v5.bin # Version 5 per-book reading stats
│ ├── reader_settings.bin # Per-book reader settings and auto-page-turn interval
│ ├── cover.bmp # Book cover image, once generated
│ ├── thumb_*.bmp # Home/recent-books thumbnail images
│ ├── book.bin # Book metadata, spine, table of contents, etc.
Expand All @@ -57,6 +59,17 @@ To clear EPUB/XTC render caches from the device UI without deleting settings or

Cache folders are path-based. Moving a book file can create a new cache directory, so the moved copy may start with fresh reading progress unless the firmware migrates the cache for that move. CrossInk migrates cache and bookmark data for the built-in move-to-Read flow and related file-browser move actions.

EPUB reader font, page layout, styling, and reading-aid settings normally come from the global Reader settings. If those settings are changed from inside an EPUB, CrossInk stores a per-book override in that book's `reader_settings.bin`; books without that override continue to follow the global defaults.

EPUB clippings and highlights live outside the EPUB render-cache folder in
`/.crosspoint/clippings/`. Each book gets a binary clipping file named from the
book type and the CRC32 of the book path. The same clipping record powers the
in-reader highlight, the clipping list, and jump-back behavior. CrossInk also
appends a Kindle-style text export to `/My Clippings.txt` on the SD-card root;
that export is human-readable and append-only, so deleting a clipping in the UI
removes the in-app saved clipping but does not rewrite old text already exported
to `/My Clippings.txt`.

Cache data is cleared by supported CrossInk delete/move flows. If you remove or rename books outside CrossInk by editing the SD card directly, old cache folders may remain until you clear reading cache.

All-time reading stats can also be backed up outside `.crosspoint` in:
Expand Down
132 changes: 131 additions & 1 deletion docs/file-formats.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

These formats describe the SD-card cache files under `/.crosspoint/epub_<hash>/`.
All POD fields are written in the ESP32 little-endian representation used by
`Serialization.h`; strings are length-prefixed UTF-8.
`Serialization.h`; strings are length-prefixed UTF-8 unless a format notes a
fixed-size char buffer.

## `book.bin`

Expand Down Expand Up @@ -88,6 +89,135 @@ if (parsedSize != fileSize) {
}
```

## `reader_settings.bin`

### Version 2

Each EPUB cache directory may contain `reader_settings.bin`. Missing files mean
the book uses global Reader settings and the default auto-page-turn interval.

Version 1 stored only:

- `u8 version`
- `u16 autoPageTurnSeconds`

Version 2 stores flags before the full reader-settings snapshot. This lets the
file preserve an auto-page-turn interval without forcing custom font/layout
settings for the book.

```c++
struct ReaderSettingsBin {
u8 version; // 2
u8 flags; // bit 0 = custom reader settings, bit 1 = custom auto-page-turn interval
u16 autoPageTurnSeconds;

u8 fontFamily;
u8 fontSize;
u8 lineHeightPercent;
u8 orientation;
u8 screenMargin;
u8 publisherPageNumbers;
u8 paragraphAlignment;
u8 embeddedStyle;
u8 hyphenationEnabled;
u8 textAntiAliasing;
u8 readerDarkMode;
u8 imageRendering;
u8 extraParagraphSpacing;
u8 forceParagraphIndents;
u8 bionicReadingEnabled;
u8 guideReadingEnabled;
char sdFontFamilyName[64];
};
```

## `/.crosspoint/clippings/<bookType>_<crc32(path)>.bin`

### Version 2

Clipping files store the per-book EPUB clipping list used by the reader. A
saved clipping is also what CrossInk renders as an in-reader highlight; there is
no separate highlight file. The file lives in `/.crosspoint/clippings/` instead
of the EPUB render-cache directory so clearing/rebuilding layout cache does not
delete user clippings.

The current implementation only writes EPUB clipping files, so `bookType` is
`epub`. The numeric suffix is `uzlib_crc32()` of the book's SD-card path, for
example:

```text
/.crosspoint/clippings/epub_1234567890.bin
```

Binary layout:

- `[0]` version (`2`)
- `[1-2]` clipping count (`uint16_t` LE, maximum `64`)
- book title (`String`)
- book author (`String`)
- book path (`String`)
- repeated clipping records:
- `spineIndex` (`uint16_t` LE)
- `startPage` (`uint16_t` LE)
- `endPage` (`uint16_t` LE)
- `pageCount` (`uint16_t` LE, at least `1`)
- `startWordIndex` (`uint16_t` LE)
- `endWordIndex` (`uint16_t` LE)
- `wordCount` (`uint16_t` LE)
- `paragraphIndex` (`uint16_t` LE, `UINT16_MAX` when unavailable)
- `timestamp` (`uint32_t` LE, seconds since firmware boot when saved)
- `chapterTitle` (`char[48]`, null-terminated/truncated)
- selected text (`String`, truncated to `512` bytes for the in-app store)

CrossInk uses the stored spine/page/paragraph fields as anchors, then searches
near that location for the stored clipping text after relayout. This is similar
to keeping both a DOM position and a text quote in a web app: the numeric
position gives a fast starting point, while the text makes jumps and highlights
survive font, layout, or page-count changes when possible.

Creating a clipping also appends a Kindle-style export entry to
`/My Clippings.txt` on the SD-card root. That text export can keep up to `2000`
bytes of the selected text and is append-only. Removing a clipping from the
reader deletes or rewrites only the binary clipping file; it does not remove
previous entries from `/My Clippings.txt`.

When CrossInk moves an EPUB through its built-in move-to-Read flow, it rewrites
the clipping file under the new path-derived name and removes the old one. If a
book is renamed or moved outside CrossInk, the path hash changes, so the old
clipping file may no longer be associated with the book until the file is moved
back or the clipping store is migrated.

## `stats_v5.bin`

### Version 5

`stats_v5.bin` stores per-book reading statistics for stats schema version 5.
Versioned filenames let firmware branches with different stats schemas keep
their own per-book stats files without overwriting each other. Version 5 extends
version 4 with a cached live reader book time-left estimate so Home and Reading
Stats can show the same estimate the reader last computed.

When `stats_v5.bin` is missing, CrossInk can read the previous versioned stats
filename (`stats_v4.bin` for version 5, `stats_v5.bin` after a future version 6
bump) before falling back to legacy `stats.bin` files with compatible stats
payloads. Future changes are always saved to the current versioned filename.

Binary layout:

- `[0]` version (`5`)
- `[1-2]` `sessionCount` (`uint16_t` LE)
- `[3-6]` `totalReadingSeconds` (`uint32_t` LE)
- `[7-10]` `totalPagesTurned` (`uint32_t` LE)
- `[11]` `isCompleted` (`uint8_t`)
- `[12-13]` `avgSecondsPerForwardPage` (`uint16_t` LE)
- `[14-15]` `paceSampleCount` (`uint16_t` LE)
- `[16]` flags (`bit0=startDateManual`, `bit1=finishedDateManual`)
- `[17-20]` `startDate` (`year uint16_t` LE, `month uint8_t`, `day uint8_t`)
- `[21-24]` `finishedDate` (`year uint16_t` LE, `month uint8_t`, `day uint8_t`)
- `[25-40]` `timeOfDaySeconds[4]` (`uint32_t` LE each)
- `[41-68]` `dayOfWeekSeconds[7]` (`uint32_t` LE each)
- `[69-72]` `estimatedTimeLeftSeconds` (`uint32_t` LE, `0` means unavailable)

## `section.bin`

### Version 40
Expand Down
23 changes: 21 additions & 2 deletions docs/reader-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Open the reader menu and select **Reader Options** to adjust settings such as:
- Margins
- Alignment
- Image rendering
- Guide Dots
- Bionic Reading / Guide Dots
- Dark Reader Mode

Changes take effect immediately.
Expand Down Expand Up @@ -99,6 +99,25 @@ You can:
- Jump back to saved locations
- Delete individual bookmarks

## Clippings And Highlights

CrossInk supports EPUB text clippings from the reader. Use **Create Clipping**
from the reader menu, select text on the current page, and save it.

A saved clipping is used in three ways:

- It appears as a highlight in the reader
- It appears in the in-app clipping list for that book
- It is appended to `/My Clippings.txt` on the SD card in a Kindle-style text format

The in-app clipping list is stored separately from the text export. Deleting a
clipping from CrossInk removes the saved clipping and highlight from the device
UI, but it does not rewrite old entries that were already appended to
`/My Clippings.txt`.

For storage paths and binary format details, see [Data Cache](./data-cache.md)
and [File Formats](./file-formats.md).

## Reading Stats

CrossInk tracks per-book reading stats automatically and aggregates them into global stats.
Expand Down Expand Up @@ -142,4 +161,4 @@ Examples include:
- Quick access to Controls from the in-reader menu
- Side-button shortcuts for changing font size or font family

For the full controls reference, see [Controls](./controls.md).
For the full controls reference, see [Controls](./controls.md).
4 changes: 3 additions & 1 deletion docs/reading-stats-sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ To reset only this reader's local all-time totals, delete both:
/.crosspoint/global_stats.bin.bak
```

Do not delete `/.crosspoint/epub_<hash>/stats.bin` unless you also want to remove the per-book stats for that book.
Do not delete `/.crosspoint/epub_<hash>/stats.bin` or versioned files such as
`/.crosspoint/epub_<hash>/stats_v5.bin` unless you also want to remove the
per-book stats for that book.

If a sync was interrupted, a temporary `*.part` file may be left in `synced_stats/`. CrossInk ignores invalid stats files while aggregating, so it is safe to delete leftover `.part` files.

Expand Down
3 changes: 3 additions & 0 deletions lib/EpdFont/EpdFont.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,9 @@ static uint8_t lookupKernClass(const EpdKernClassEntry* entries, const uint16_t
}

int8_t EpdFont::getKerning(const uint32_t leftCp, const uint32_t rightCp) const {
if (utf8IsCjkBreakable(leftCp) || utf8IsCjkBreakable(rightCp)) {
return 0;
}
if (!data->kernMatrix) {
return 0;
}
Expand Down
Loading
Loading