Compare commits

...
Author SHA1 Message Date
Uri Tauber d9a207f9a9 refactor: unify control settings models 2026-08-09 12:24:05 +03:00
Uri TauberandJulia Nguyen 77b58e431f feat: group Settings -> Controls into submenus
The Controls category had grown to a flat list of a dozen unrelated
entries. Split it into Power Button, Front Buttons and Side Buttons
groups, reached through a new SUBMENU row type that swaps the visible
list in place rather than pushing another activity.

- Front Buttons only exists on devices that have them. On touch devices
  the entries that still apply (long-press behaviour, which also covers
  the touch page-turn zones) stay at the top level.
- Tilt Page Turn and Tilt Direction sit at the top level, and only on
  IMU-capable boards.
- Quick-return from footnotes now appears whenever any shortcut targets
  footnotes, not only the short power press.

Back leaves a group before it leaves Settings, and the header names the
open group. Choosing which list is on screen was duplicated between the
rebuild and the input handler; both now call one method.

Ported from CrossInk.

Co-Authored-By: Julia Nguyen <julia@uxj.io>
2026-08-07 19:09:51 +03:00
Uri TauberandJulia Nguyen 167663cb6e feat: run configurable Controls shortcuts in the reader
Adds a single ShortcutAction space that both persisted shortcut enums
translate into, so each dispatcher switches over one type instead of
repeating a near-identical switch per binding. Global actions (sleep,
refresh, screenshot, file browser, file transfer, tilt toggle) run from
one executor; reader-only ones (footnotes, bookmark, sync, look up
word) are handled by the reader and fall through to it.

Wires it up:

- Long power hold runs longPwrBtn, latched so a held button fires once
  and the release that ends the hold does not also run the short-press
  action. Sleep is the default, matching what a hold has always done.
- Short power release runs shortPwrBtn, no longer only Refresh.
- Long Back runs longPressBackAction; the default keeps the existing
  file-browser navigation untouched.
- Long Confirm runs longPressMenuAction over the whole action set. An
  action that declines (sync with no credentials, a page with no
  footnotes) leaves the release alone, so Confirm still opens the menu.
- detectPageTurn reports whether a turn came from the side buttons, so
  the side and front pairs honour their own long-press settings and
  their own press-vs-release timing.

Font-size change is deliberately absent from the long-press options:
it needs per-reader re-layout plumbing that does not exist yet, and the
rule for this port is to offer no action we cannot execute cleanly.

Ported from CrossInk.

Co-Authored-By: Julia Nguyen <julia@uxj.io>
2026-08-07 19:04:19 +03:00
Uri TauberandJulia Nguyen 2aa0f7be17 feat: apply Controls button mapping at input layer
Routes the new Controls settings through MappedInputManager:

- Next/Next side layout, via a table of side-button bindings that also
  replaces the two parallel switch statements over sideButtonLayout.
- sideButtonOrientationAware, swapping the side pair (and reader menu
  Up/Down along with it) whenever the screen renders non-portrait.
- The ALL_BUTTONS front orientation mode, which mirrors Back/Confirm
  across the front row when the screen renders inverted. The navigation
  pair is deliberately left alone: isNavDirectionSwapped() already flips
  it, so mirroring both would cancel out.
- readerFrontButtons*, a reader-only front mapping, gated on a
  readerMode flag main.cpp keeps in sync with the activity stack. The
  renderer cannot supply this — a portrait reader renders exactly like
  portrait UI.

Button hints now resolve through the same mapping as the button reads,
so a reader override or an inverted mirror moves the labels with the
buttons instead of leaving them behind.

ButtonRemapActivity takes a target so it can edit either mapping.

Ported from CrossInk.

Co-Authored-By: Julia Nguyen <julia@uxj.io>
2026-08-07 18:55:39 +03:00
Uri TauberandJulia Nguyen a1c0bdfa05 feat: add Controls settings schema and migrations
Adds the persisted fields the CrossInk Controls screen is built on, and
migrates the existing CrossPoint settings onto them so no stored value
changes meaning:

- longPwrBtn, sharing the power-button action enum with shortPwrBtn.
  Values 0..4 keep their CrossPoint meanings — notably 4 stays
  Footnotes, where CrossInk's numbering diverges — and the new actions
  are appended, with the menu order restored via enumRawValues.
- frontButtonOrientationAware, a three-state setting migrated from the
  frontButtonFollowOrientation toggle (on -> nav buttons only).
- sideButtonOrientationAware and sideButtonLongPress. The side pair used
  to share longPressButtonBehavior with the front pair, so the old value
  is carried across on load.
- readerFrontButtons*, a reader-only front-button remap, inert until
  readerFrontButtonsEnabled is turned on.
- longPressMenuAction and longPressBackAction on a shared quick-action
  enum, migrated from longPressMenuFunction.
- tiltPageTurnDirection, splitting the axis out of the combined
  tiltPageTurn enum. Legacy "normal" is the non-inverted left/right
  gesture and "inverted" its negation, so the HAL reproduces both.

Only actions the reader can already execute are offered; the enums carry
no option that has no implementation behind it.

Ported from CrossInk.

Co-Authored-By: Julia Nguyen <julia@uxj.io>
2026-08-07 18:50:11 +03:00
Uri TauberandJulia Nguyen 4e8aea95a0 feat: support raw enum values in settings schema
An ENUM setting's option order and its persisted values were the same
number, so reordering or gapping an option list silently reinterpreted
existing settings.json files. Add an optional parallel enumRawValues
array and route every reader/writer of an ENUM — device settings UI,
web settings API, and load-time validation — through three shared
helpers that translate between display index and raw value.

Behaviour is unchanged for every setting that does not populate
enumRawValues: the helpers fall through to the identity mapping.

Ported from CrossInk.

Co-Authored-By: Julia Nguyen <julia@uxj.io>
2026-08-07 18:39:57 +03:00
Uri Tauber e00f5958df feat: guard against cross-chip firmware installs (#2880) 2026-08-07 09:42:29 +03:00
lranslot 255bab311a fix: read platformio.ini as UTF-8 in git_branch.py (#2897) 2026-08-07 02:05:45 +03:00
Justin Mitchell c507e54478 fix: thread-safety issues in credential store (#2834) 2026-08-07 01:16:22 +03:00
Uri Tauber 5eec70de36 add IPA intervals to SD fonts 2026-08-07 01:08:29 +03:00
+19 28f3260c51 chore: update translations (#2740)
Co-authored-by: Leopoldo Pla Sempere <leopoldo.pla@treelogic.com>
Co-authored-by: muhas <muhas@muhas.ru>
Co-authored-by: Matteo Scopel <matteo@scopel.email>
Co-authored-by: CookieCaptainD <65847023+CookieCaptainD@users.noreply.github.com>
Co-authored-by: Poc031205 <165477165+Poc031205@users.noreply.github.com>
Co-authored-by: Rodrigo Matsuura <matsuura@outlook.com.br>
Co-authored-by: Spigaw <73850535+Spigaw@users.noreply.github.com>
Co-authored-by: Tom-Inge Larsen <tom.inge@gmail.com>
Co-authored-by: Baris Albayrak <80099286+barbarhan@users.noreply.github.com>
Co-authored-by: Barış Albayrak <barisa@pop-os.lan>
Co-authored-by: Hendra Kusumah <h.kusumah87@gmail.com>
Co-authored-by: thehijacker <thehijacker@gmail.com>
Co-authored-by: Andrej Kralj <andrej.kralj@gmail.com>
Co-authored-by: Bastian <391732+dasrecht@users.noreply.github.com>
Co-authored-by: th0m4sek <53303628+th0m4sek@users.noreply.github.com>
Co-authored-by: Dexif <spev@gurtam.team>
Co-authored-by: Mirus <mirusim@gmail.com>
Co-authored-by: Mirza Arnaut <mirza.arnaut45@gmail.com>
Co-authored-by: Husam Younis <youhusam@gmail.com>
Co-authored-by: Ing. Jan Kaláb <jan+github@kalabovi.org>
Co-authored-by: Tadas <tadasss@gmail.com>
Co-authored-by: winst0niuss <chumachenko.vadym@gmail.com>
Co-authored-by: fsocietyipa <fsociety.ipa@gmail.com>
Co-authored-by: Okzhetpes <okzhetpes.valikhanov@gmail.com>
Co-authored-by: KymAndriy <32357131+KymAndriy@users.noreply.github.com>
Co-authored-by: KymAndriy <test@notamail.ua>
Co-authored-by: Martin <35097765+neresus@users.noreply.github.com>
Co-authored-by: Hoang Manh Linh <jetaudio.media@gmail.com>
2026-08-06 23:39:05 +03:00
Phạm Bình An 046827fc31 fix(epub): ruby overflows right border when justifying text, unwanted short lines in Chinese and Japanese (#2781) 2026-08-06 23:26:09 +03:00
Uri Tauber fecb9eb3a4 fix: trim curly quotes and dashes in dictionary lookup (#2877) 2026-08-05 15:37:41 -04:00
Jesse Vincent 53232eefab fix: recognize watchdog-triggered resets as panics (#2830) 2026-08-05 22:02:41 +03:00
winst0niuss cc96e3c538 fix: size translated-label buffers for UTF-8, not ASCII (#2850) 2026-08-05 21:51:37 +03:00
Justin Mitchell 4e61903578 fix: add support for new x3 display, x3 battery drain fix, and crypto support (#2864) 2026-08-04 12:08:14 -04:00
Jesse Vincent ba1b849f3d fix: load Settings page's Wi-Fi/OPDS/settings data sequentially (#2831)
## Summary

* **What is the goal of this PR?** Fix a reproducible crash: opening the
web UI's Settings page reliably crashes the device (silent reboot to
Home) under real-world conditions.
* **What changes are included?** `SettingsPage.html` fired three
unawaited `fetch()` calls on page load (`/api/settings`, `/api/wifi`,
`/api/opds`), opening simultaneous connections to the device's web
server. That server (Arduino's `WebServer`) handles one client at a
time; a genuinely concurrent second/third connection can stall a
response mid-stream for multiple seconds — long enough to trip the
firmware's watchdogs (300ms interrupt WDT / 5s task WDT, both
panic-on-timeout in this project's config). This PR makes the three
initial loads run sequentially via a small async IIFE instead of firing
them concurrently. Same end state for the page, no visible behavior
change.

## Scope Check

CrossPoint is intentionally narrow. See
[SCOPE.md](../blob/master/SCOPE.md) and
[ROADMAP.md](../blob/master/ROADMAP.md).

- [x] I have read SCOPE.md and ROADMAP.md.
- [x] This PR is **not** a new built-in theme.
- [x] This PR is **not** a new external network connector.
- [x] This PR is **not** an interactive app, writing tool,
RSS/news/browser, media playback, or PDF feature.
- [x] This is a bug fix for a reproducible crash, not new functionality.
- [x] Does not touch `freeink-sdk/`, `lib/hal/`, the bootloader, OTA, or
recovery code.

## Testing performed

Verified live on an X4 (driven via the `CMD:WIFI` serial debug command
from the companion PR, since I didn't have physical access to the device
for part of this investigation):

- Root-caused via a temporary per-entry heap/timing log in
`handleGetSettings()` (not included in this diff): under concurrent
load, individual `sendContent()` calls stalled 1-3+ seconds between
simple, static settings entries that should serialize in well under a
millisecond each.
- Reproduced the crash directly: firing `/api/settings`, `/api/wifi`,
and `/api/opds` concurrently (mirroring the page's old JS behavior)
crashed the device in 2/2 attempts. The companion PR (watchdog reset
detection) let the second crash actually get logged: reset reason
`ESP_RST_TASK_WDT`, panic confirmed.
- After the fix: same three endpoints, hit sequentially (matching the
new JS), succeeded cleanly across dozens of repeats (single-shot and a
60-request hammer loop) with no crashes and stable free-heap.
- Confirmed the live device served the updated, gzip-compressed
generated header after a normal `pio run` (source HTML →
`SettingsPageHtml.generated.h` via the existing `scripts/build_html.py`
build step).

## Additional Context

Memory/flash impact: none — same JS logic, just sequenced instead of
concurrent. No new allocations, no change to the generated header's size
beyond the few added bytes of source text.

Risk: low. This only changes the *order* three existing fetches happen
in on page load; no new endpoints, no changed request/response shapes.

---

### AI Usage

YES — diagnosed and fixed by Claude (Sonnet 5), including live
reproduction and verification against real hardware.
2026-08-03 16:46:24 -04:00
Uri Tauber 9c48609f8b Make bookmarks survive re-pagination
Ties bookmarks to the actual text on the page rather than to a page number. Page numbers shift whenever the book gets re-laid-out — changing font size, margins, line spacing, or rotating the screen — which could previously land you on the wrong spot.

This is a followup of #2805, making bookmarks use the same mechanism. Several minor improvments ride along.
2026-08-02 00:06:12 +03:00
Thiago Kenji Okada 0f747b82a1 fix: make EPUB sync positions content-based (#2805) 2026-08-01 23:37:07 +03:00
Bob Vincent 1a7f5a9e38 perf: share one static bidi scratch buffer to save ~1.5 KB RAM (#2554) 2026-07-31 19:07:39 +03:00
Uri Tauber 5933da8f1d perf: check dictionary sidecar freshness once per open
needsIndex() opens .idx and .qidx and re-reads the sidecar header on every
call, and DictionaryWordSelectActivity called it once per tapped word rather
than once per dictionary open. A lookup therefore cost 4 SD opens and 2
std::string path temporaries, not the 2 and 0 the previous change set out to
achieve.

Ask needsIndex() once, inside the same guarded block that opens the
dictionary, and feed the build result back so a successful build stops
re-triggering while a failed one still retries on the next word.

Reimplement needsIndex() on openSession() so the "is the sidecar usable?"
rule lives in one place rather than two that have to be kept in step. This
is behaviour-preserving for everything buildIndex() can produce: it always
writes at least one sample (entry 0) and removes the file on failure, so
sampleCount == 0 means absent, stale or corrupt, all of which rebuild. The
one divergence is an improvement -- a corrupt sidecar with a valid magic
previously reported fresh and then full-scanned every lookup forever, and
now rebuilds once.

Also make the open()-time length bound a compile-time constant instead of
two runtime strlen() calls, and correct the buildPath() comment, which
claimed a lookup allocates nothing at all -- cleanWord(), stemVariants() and
the matched headword all still do.
2026-07-31 15:46:42 +03:00
William Floyd bf253b15af perf: open dictionary index files once per lookup (#2733) 2026-07-31 15:27:57 +03:00
William Floyd 47466c775d fix: dictionary lookup OOM on .dict.dz definitions (#2791) 2026-07-31 14:16:55 +03:00
Justin Mitchell d0b70b3f9f fix: Replace vector with deque for text token storage (#2814) 2026-07-31 09:42:27 +03:00
Eduard 74065f10a1 fix: avoid duplicate OTA update actions (#2807) 2026-07-31 01:46:29 +03:00
Uri Tauber b215ed86e5 make OptionPopup orientation-aware 2026-07-31 01:30:51 +03:00
Thiago Kenji Okada 5c26fa7c1d fix: orient dictionary navigation buttons (#2749) 2026-07-31 01:18:16 +03:00
Justin Mitchell b19503bfad fix: Properly power down SD power rails on x3 (#2808)
Fixes a regression in the sticky device support that lumped GPIO13 into
a device check that never powered down the SD rails on this device.
2026-07-30 18:13:47 -04:00
Justin Mitchell 13a077dfb3 fix: Save text settings immediately on each change (#2806) 2026-07-30 22:13:19 +03:00
Bastian 3eeb6acd87 fix: order languages by native name instead of language code (#2803) 2026-07-30 14:59:38 +00:00
Justin Mitchell 2ceeeccd5a fix: Restrict CrossPoint position extension to official server (#2790) 2026-07-29 18:22:38 +03:00
Uri Tauber 67e608e828 feat: Access text settings from epub reader (#2788) 2026-07-29 13:03:55 +00:00
Thiago Kenji Okada dadc8ec2eb fix: clean image page after sync return (#2747) 2026-07-29 14:36:20 +03:00
Uri Tauber 9163149adc fix: restore spaces between CJK words (#2768) 2026-07-29 08:29:30 +03:00
Leopoldo Pla Sempere 187feda47d fix: derive file-transfer WebSocket port from HTTP (#2729)
## Summary

* **What is the goal of this PR?**
* Preserve the firmware's adjacent HTTP/WebSocket port contract when the
web
    UI is served from a mapped host port in the desktop simulator.
* **What changes are included?**
  * Use the current page's HTTP port, defaulting to 80 on-device.
* Connect WebSocket uploads to the next port instead of always using
host
    port 81.

## Scope Check

- [x] I have read SCOPE.md and ROADMAP.md.
- [x] This PR is **not** a new built-in theme.
- [x] This PR is **not** a new external network connector.
- [x] This PR is **not** an interactive app, writing tool, browser,
media, or
      PDF feature.
- [x] The current file-transfer page does not handle simulator port
mapping and
      no pending PR fixes it.
- [x] This PR does not touch `freeink-sdk/`, `lib/hal/`, bootloader,
OTA, or
      recovery code.

## Additional Context

On hardware the page is served from port 80 and WebSocket remains port
81, so
device behavior is unchanged. In the simulator, a page served from 8080
now
uses 8081; a configured 18080 listener uses 18081.

Validation:

- Built the real CrossPoint file-transfer server in the simulator.
- Loaded the page through `http://127.0.0.1:18080/`.
- Verified a WebSocket upgrade on port 18081 returns HTTP 101.
- Exercised WebSocket upload progress and the HTTP fallback path.
- `git diff --check`.

Memory impact: no firmware heap allocation is added. The change is
JavaScript
inside the existing compressed web asset.

---

### AI Usage

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

AI tools were used to reproduce the browser/simulator port mismatch and
verify
the HTTP and WebSocket listeners together.


## Emulator disclosure

The mapped-port HTTP/WebSocket behavior was reproduced and validated
with the desktop emulator on Intel macOS. The on-device port-80/81
behavior was established from the unchanged firmware contract; no
physical device was used for runtime validation.
2026-07-28 18:17:08 -04:00
William Floyd fe37bf80ab perf: skip ruby scratch allocations on ruby-less lines (#2735) 2026-07-29 00:33:55 +03:00
Justin Mitchell 5713432004 fix: Gyro not powering down on x3 and newer x4 battery latch issues (#2774)
Removes X3ProbeResult struct and associated I2C device detection
functions (probeBQ27220Signature, probeDS3231Signature,
probeQMI8658Signature) along with helper function readI2CReg8 in favor
of an SDK driven device detect API
2026-07-28 16:34:59 -04:00
Leopoldo Pla Sempere 5965dc67a8 chore: remove redundant wolfSSL TLS defines (#2727)
## Summary

* **What is the goal of this PR?**
* Remove the macro-redefinition warning storm from the normal ESP32-C3
build
    without changing enabled TLS features.
* **What changes are included?**
  * Stop repeating four TLS feature macros already enabled by
    Arduino-wolfSSL's `user_settings.h` when `WOLFSSL_TLS13` is set.
  * Document which configuration layer owns those features.

## Scope Check

- [x] I have read SCOPE.md and ROADMAP.md.
- [x] This PR is **not** a new built-in theme.
- [x] This PR is **not** a new external network connector.
- [x] This PR is **not** an interactive app, writing tool, browser,
media, or
      PDF feature.
- [x] No issue or pending PR removes these redundant definitions.
- [x] This PR does not touch `freeink-sdk/`, `lib/hal/`, bootloader,
OTA, or
      recovery code.

## Additional Context

`HAVE_TLS_EXTENSIONS`, `HAVE_SUPPORTED_CURVES`, `HAVE_HKDF`, and
`WC_RSA_PSS` remain enabled by the installed Arduino-wolfSSL
configuration.
This removes duplicate command-line definitions; it does not disable
those
features.

Validation:

- Full ESP32-C3 `default` rebuild passed.
- Reported size: 52,028 bytes RAM and 5,505,149 bytes flash.
- The wolfSSL macro-redefinition warnings are gone.
- The only remaining warning in that build was an unrelated deprecated
  `NetworkClient::flush()` call inside the vendored WebSockets library.
- `git diff --check`.

Memory/flash impact: no intentional change. The effective preprocessor
feature
set is unchanged.

---

### AI Usage

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

AI tools were used to trace the duplicate definitions to the library
configuration and compare the full build output before and after
cleanup.


## Emulator disclosure

The ESP32-C3 firmware was compiled locally and the warning output was
checked on Intel macOS. Runtime smoke testing used the desktop emulator;
this PR was not flashed to a physical X3 or X4.
2026-07-28 15:53:20 -04:00
Uri Tauber 52ff942ab5 perf: drop all-empty ruby vectors from TextBlock (#2772) 2026-07-28 14:49:22 -04:00
Tsz Ho Sze b508f75e79 fix: treat <br> in CJK flowing text as a paragraph separator (#2710) 2026-07-28 21:34:55 +03:00
Paul Portocarrero f39d35d748 docs: Add instructions to fix a bricked Xteink (#2622) (#2682) 2026-07-28 20:24:07 +03:00
Uri Tauber 99c0e504bd fix: TextSettingsActivity default to selected font (#2739) 2026-07-27 19:10:24 +03:00
Leopoldo Pla Sempere eea75d561b fix: bind settings labels to enum values (#2644) 2026-07-27 08:02:20 +03:00
William Floyd 087c3da462 fix: name dictionary lookup failures (low-memory vs decompress vs read) instead of 'Not found' (#2706) 2026-07-27 07:58:32 +03:00
Emily Marriott ef3d980cb3 feat: Add packages to nix dev shell required for clang-format-fix and… (#2695) 2026-07-27 01:12:28 +03:00
Bastian c731d5187c fix: fixing german translation inconsistencies (#2702) 2026-07-27 00:52:30 +03:00
Leopoldo Pla Sempere c9164ac54a fix: translate BMP viewer error messages (#2730) 2026-07-26 23:31:00 +03:00
Ing. Jan Kaláb a02cffe2de chore: Update czech.yaml (#2726) 2026-07-26 23:29:39 +03:00
Leopoldo Pla Sempere b25e57dac2 test: repair malformed section-break EPUB fixture (#2728) 2026-07-26 23:28:22 +03:00
Uri Tauber 9cff12b533 docs: Add StarDict sources list 2026-07-26 17:55:11 +03:00
Thiago Kenji Okada 9ec7767111 fix: preserve custom font ligatures (#2673) 2026-07-26 08:23:25 +03:00
Uri Tauber 9190048466 feat: point-size font selection (#2720) 2026-07-25 22:02:49 +00:00
Justin Mitchell 1cf265aa21 fix: Lower TLS minimum free memory threshold to 35KB for kosync (#2719) 2026-07-26 00:48:57 +03:00
Julia dde7e03776 fix: ignore ambiguous EPUB guide text so books open at the right location (#2716) 2026-07-26 00:48:29 +03:00
Julia Nguyen 78c1c8e182 Merge branch 'release/1.5.0' into develop 2026-07-25 17:14:19 -04:00
Matteo Scopel 0701683971 chore: small Italian translation fixes (#2703) 2026-07-25 22:05:42 +03:00
CookieCaptainD 36ac29e284 fix(i18n): add missing strings in PT-PT translation (#2701) 2026-07-25 21:38:55 +03:00
Phạm Bình An d0359edfcc feat(epub): native <ruby> support for Chinese, Japanese (#2665) 2026-07-24 23:31:59 -04:00
Justin Mitchell 1d98ce1ea7 feat: Add UC8279 panel controller detection for X3 devices (#2707) 2026-07-24 21:07:18 -04:00
Totofaki ad5a7a2e2a fix: avoid X3 Quick Resume flashes (#2698) 2026-07-24 12:52:46 -04:00
Phạm Bình An 962d5acc9c fix(epub): split text blocks on closed tags to prevent style leaks (#2679) 2026-07-24 09:59:34 +03:00
Thiago Kenji Okada d7c98adcc8 fix: restore antialiasing after manual refresh (#2683) 2026-07-24 00:33:18 +03:00
Uri Tauber 90785b71ff chore: Bump version to 1.5.0 2026-07-23 22:23:13 +03:00
Uri Tauber 66abde5fc5 fix: STR_RESTARTING_HINT text overflow bug (#2692)
## Summary
fix #2021
2026-07-23 15:18:02 -04:00
Uri Tauber b85580aa2c fix: Warp around in Percent Selection (#2677)
## Summary

Address request
https://github.com/crosspoint-reader/crosspoint-reader/discussions/2659

---

### 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-07-23 15:17:02 -04:00
Justin MitchellandUri Tauber f0a50557be feat: deferred refresh, memory work port, and first-open speedups (#2611)
Co-authored-by: Uri Tauber <uritaube@gmail.com>
2026-07-23 22:12:56 +03:00
Leopoldo Pla Sempere c6f6f049a3 chore: update Spanish, Catalan, and Valencian translations (#2651) 2026-07-23 21:36:17 +03:00
Tsz Ho Sze 10db60c4fd feat: cjk UI font fallback (#2521) 2026-07-23 21:26:54 +03:00
Matteo Scopel a39fb39a07 chore: update Italian translation (#2691) 2026-07-23 21:25:01 +03:00
Justin Mitchell bb732479e2 fix: Move sunlight fading fix setting to different group (#2689)
Relocate STR_SUNLIGHT_FADING_FIX from the touch reader controls group to
the front button orientation group in the settings filter logic.
Sunlight fix was accidentally being hidden on non-touch where it should
have been hidden on touch instead. Closes #2030
2026-07-23 12:33:09 -04:00
Paul DelestracandUri Tauber 4e41bf68f7 feat: unified Text Settings screen with live preview (#2605)
Co-authored-by: Uri Tauber <uritaube@gmail.com>
2026-07-22 23:23:51 +03:00
Hendra KusumahandUri Tauber aacdd7e9cf add Bahasa Indonesia Translation (#2666)
Co-authored-by: Uri Tauber <uritaube@gmail.com>
2026-07-22 12:21:17 +03:00
Uri Tauber 63eda54ee9 chore: Migrate Settings/State onto PersistableStore; (#2647) 2026-07-21 17:56:21 -04:00
Phạm Bình An f1206f4d81 chore: update Vietnamese translations (#2667) 2026-07-22 00:32:49 +03:00
Dylan Byars 973a902332 fix: duplicate User-Agent header on wolfSSL requests breaks strict servers (aiohttp 400) (#2661) 2026-07-21 01:48:26 -04:00
Justin Mitchell cb9b971c26 Refactor SCOPE.md for improved formatting
Updated SCOPE.md to remove unnecessary line breaks and improve readability.
2026-07-21 01:20:26 -04:00
Justin MitchellandJulia Nguyen f42fab1c66 feat: Add touch coordinate mapping and RTOS task yielding (#2481)
Co-authored-by: Julia Nguyen <julia@uxj.io>
2026-07-20 16:31:07 -04:00
Justin Mitchell c9188a7347 chore: Clarify project scope and development priorities (#2149)
Expand scope to support multiple e-ink devices beyond X4. Add guiding
principle to fill gaps in stock firmware rather than duplicate
functionality. Establish current focus on memory/flash optimization and
code cleanup. Temporarily freeze new themes and network connectors to
consolidate codebase before multi-device expansion.
2026-07-20 16:02:02 -04:00
Leopoldo Pla Sempere e03aa16330 fix: select strongest AP for matching WiFi SSID (#2655) 2026-07-20 18:17:16 +03:00
Phạm Bình An 3319aa1720 fix(epub): preserve word continuation when splitting CJK text on MAX_WORD_SIZE (#2652) 2026-07-20 15:02:08 +03:00
CookieCaptainD 3dfbc0383f fix(i18n): add missing strings in PT-PT translation (#2632) 2026-07-20 14:12:06 +03:00
Thiago Kenji Okada 64364bec5d feat: add Nix development shell (#2645) 2026-07-20 00:57:37 +03:00
Stefan Blixten Karlsson 556b8aee5b fix: swedish translation (#2649) 2026-07-19 22:12:10 +03:00
b1d1d757ba feat: Slim dictionary (#2583)
Co-authored-by: Ryan Hitchman <hitchmanr@gmail.com>
Co-authored-by: Kurtis Grant <kurtis.b.grant@gmail.com>
Co-authored-by: kemonine <kemonine@kemonine.info>
Co-authored-by: DustinHu <hu.dustin@gmail.com>
Co-authored-by: Justin Mitchell <justin@jmitch.com>
2026-07-19 17:57:16 +03:00
Uri Tauber 5ba1d5747f fix: EndOfBookOptions fails to compile
ActivityManager.h forward-declares Activity but holds
std::unique_ptr<Activity> members. Instantiating that unique_ptr's
destructor requires the complete type, so any TU including
ActivityManager.h without Activity.h fails to compile.

Include Activity.h directly. Adding it to ActivityManager.h instead does
not work: Activity.h depends on HomeMenuItem, which ActivityManager.h
defines.
2026-07-19 16:45:42 +03:00
Thomas Symalla 9fe4dc5e38 feat: Add option to switch behavior for "back to browser / home" in Reader activity (#2366)
## Summary

It would be nice to switch back to the file list from an Reader activity
via a short back button press. This change adds an Reader option to
switch the default behavior, so a short back button press in the Reader
activity can now go back to the file list, and a long press on back goes
back to the home view. This does a fair bit of refactoring, introducing
a new constant for the ms limit.

* **What changes are included?**

- Changes to the translation
- Additional global Reader option 
- Refactoring of the back button behavior in the Reader activity
2026-07-19 08:29:13 +03:00
Justin Mitchell b1d037569b feat: Add kosync user registration and switch to crosspoint-sync server (#2587)
Implements createUser() endpoint to allow account creation via the
KOSync protocol. Changes default sync server from sync.koreader.rocks to
sync.crosspointreader.com with migration logic to preserve existing
users' server settings. Extends sync protocol to include position data
(spine index, page numbers, xpath) that crosspoint-sync supports while
remaining compatible with standard kosync servers.
2026-07-18 14:50:51 -04:00
Julia 9737cb335c fix: correct the settings enums for "blank" and "cover + custom" sleep screens (#2635) 2026-07-17 12:35:51 -04:00
Phạm Bình An fdffc2e5d9 fix: reduce CSS parse-time OOM risk in chapter layout (#2606) 2026-07-16 07:21:08 +03:00
a2db43d235 feat: enable CORS headers in the HTTP API (#2594)
Closes #2558.

Enables the Arduino WebServer's built-in CORS support
(`enableCORS(true)`), which adds
`Access-Control-Allow-Origin/Methods/Headers: *` to every response, and
answers preflight `OPTIONS` requests with `204` in `handleNotFound()` —
routes are registered per-method, so OPTIONS always lands there. The
AP-mode captive-portal redirect is untouched (the OPTIONS check runs
before it, and browsers don't send preflights for captive-portal
probes).

This lets web-based clients and PWAs served from other origins call the
JSON API (`/api/status`, `/api/files`, `/api/settings`, ...) directly
from the browser.

Overhead is three static response headers; no behavior change for the
built-in web UI.

Note: not yet tested on hardware.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: metoli <metoli@metoli-Mac-mini.local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-15 18:35:10 -04:00
rxmmahandkira 63093e606c feat: Back on home menu opens the most recent book (#2619)
Co-authored-by: kira <rammah@tuta.io>
2026-07-15 23:50:22 +03:00
a4ac3b2788 feat: add smart KOReader progress sync (#2192)
## Summary

Make KOReader progress sync a one-click flow for common cases while
keeping the existing manual mode available.

### Changes

- Add a **Sync Behavior** setting:
  - **Smart sync** for new configurations
  - **Ask every time** for manual control
- Preserve **Ask every time** when migrating an existing credential file
that predates this setting.
- In Smart mode:
  - upload local progress when no remote record exists;
  - show a short confirmation when already synced;
  - upload when local progress is further ahead;
  - apply remote progress when remote progress is further ahead.
- Probe both KOReader document-matching hashes before making the Smart
decision, while keeping uploads on the user's configured matching
method.
- Auto-return after successful Smart terminal states, with Back/Confirm
still available.
- Persist the setting alongside the current credential, matching-method,
and send-metadata fields.
- Document both behaviors in the user guide.

## Additional context

This pairs well with #2189 (smart Wi-Fi auto-connect), but does not
depend on it. It does not add background Wi-Fi or passive network
detection; sync is still triggered by the user from the reader menu.

Smart sync uses the furthest progress because CrossPoint does not
currently persist local progress timestamps. Users who prefer explicit
conflict resolution can select **Ask every time**.

## Verification

- `git diff --check`
- `platformio check --fail-on-defect low --fail-on-defect medium
--fail-on-defect high` — no defects
- `platformio run -e default` — firmware build successful
- The original implementation was manually smoke-tested on an X4 device.

---

### AI Usage

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

AI tools were used to inspect the codebase, draft and review the
implementation, resolve the rebase against current `develop`, and
prepare the PR text. The resulting firmware was built locally.

---------

Co-authored-by: Alexander Hoffer <git@alexanderhoffer.com>
Co-authored-by: Alexander Hoffer <contact@alexanderhoffer.com>
2026-07-15 14:59:15 -04:00
Víctor Fernández 35a45f9c1e feat: add options to remember web upload settings & rename ebooks to {title} - {author} (#2534) 2026-07-15 14:43:42 -04:00
William Floyd f3cad2f9e1 fix(kosync): reload Epub before reading upload metadata (#2608) 2026-07-15 11:46:10 -04:00
Oscar Nogueira Neto 95d8cb712b feat: configurable OPDS download folder and filename format (#2571) 2026-07-15 10:53:27 -04:00
Mirza Arnaut b9867b0d09 feat(i18n): add Bosnian translation (#2616) 2026-07-15 10:49:42 -04:00
Uri Tauber 3dac4446b7 fix: remove duplicate sleep logic (#2492) 2026-07-15 10:48:08 -04:00
Uri Tauber f180069643 fix: ignore open-x4-sdk and fs_ (#2609) 2026-07-14 21:34:28 +03:00
Husam Younis a0ec5812b1 feat: Arabic translation YAML - PR 3/3 (#2599) 2026-07-14 20:33:42 +03:00
Phạm Bình An e84840b473 fix(css-parser): don't save unusable rules to RAM (#2604) 2026-07-14 17:00:13 +03:00
Tom-Inge Larsen 94ae750e89 feat(i18n): add Norwegian Bokmål translation (#2113) 2026-07-14 16:54:43 +03:00
metoli86andmetoli 8c5cd67f38 feat: complete Turkish translation (390/390) (#2592)
Co-authored-by: metoli <metoli@metoli-Mac-mini.local>
2026-07-14 16:50:03 +03:00
Justin Mitchell c5787d1cc1 fix: Use HALF_REFRESH for sleep and boot instead of FULL (#2588)
Changes all sleep screen display refreshes and boot screens from
FULL_REFRESH to HALF_REFRESH to match OEM firmware behavior. The stock
firmware uses a single-pass 0xD7 waveform for sleep screens, not the
multi-flash GC waveform (0xF7) that FULL_REFRESH triggers. For grayscale
mode, HALF_REFRESH is required because the gray nudge LUT is calibrated
against the pixel charge state left by the single-pass waveform; using
FULL_REFRESH causes blotchy noise in gray areas. Also adds X3 wb_gc
passive byte (the one-byte 0x54 to 0x00, matching stock's passive WB
table in the gc nudge bank): Every X3 grayscale render, reader AA,
covers, and the sleep-cover nudge, gets it.
2026-07-13 17:06:09 -04:00
Husam Younis 2a19eb9e0d feat: Arabic/Farsi/Urdu glyphs in built-in UI fonts - PR 2/3 (#2596) 2026-07-13 22:35:47 +03:00
Uri Tauber fae3423dec feat: add portuguese-PT.yaml (#2597) 2026-07-13 22:27:23 +03:00
Husam YounisandUri Tauber 932a472835 feat: Arabic/Farsi/Urdu bidi reordering and contextual shaping — PR 1/3 (#2541)
Co-authored-by: Uri Tauber <uritaube@gmail.com>
2026-07-13 16:02:21 +03:00
Jan Steinke 552b2683e6 fix: keep list item bullet inline with nested paragraph text (#2589) 2026-07-13 12:06:43 +03:00
Mr.Catfood bfa1d706b1 feat: Hidden wifi ssid support (#2360) 2026-07-13 02:33:48 -04:00
Jacob Latonis 73ab9ab20d feat: implement captive portal redirects for auto-loading the management page on hotspot mode (#2550) 2026-07-13 00:10:23 -04:00
Nick Perez 3a97f3e63e feat: Send optional document metadata with KOSync progress uploads (#1820) 2026-07-12 23:50:38 -04:00
Matthías Páll Gissurarson 39ea4b045f feat: render placeholders while waiting for images to render (#1003) 2026-07-12 21:40:07 +03:00
Justin Mitchell 444d87de82 fix: Add framebuffer release/realloc and improved lazy indexing (#2563) 2026-07-12 13:16:48 -04:00
Leopoldo Pla Sempere 859f6cb0d5 chore: update Spanish, Catalan, and Valencian Wi-Fi strings (#2578) 2026-07-12 12:13:06 +03:00
hooligan333andErica Jensen d8139e9605 perf: release CSS rule map after warm open (#2439)
Co-authored-by: Erica Jensen <erica@mailershaven.com>
2026-07-11 22:59:31 -04:00
hooligan333andErica Jensen b596f21e61 perf: stream NCX/NAV TOC into parser, drop temp-file round-trip (#2440)
Co-authored-by: Erica Jensen <erica@mailershaven.com>
2026-07-11 22:57:48 -04:00
hooligan333andErica Jensen 5fbc657aeb perf: always binary-search idref lookups in content.opf (#2433)
Co-authored-by: Erica Jensen <erica@mailershaven.com>
2026-07-11 22:56:28 -04:00
Jason Huebel bb06219c9c docs: add script to generate EPUB from USER_GUIDE.md (#2152) 2026-07-11 22:54:32 -04:00
Alexander Hoff ❍andAlexander Hoffer 5202bbf911 feat: auto-connect saved Wi-Fi networks (#2189)
Co-authored-by: Alexander Hoffer <git@alexanderhoffer.com>
2026-07-11 22:47:26 -04:00
hooligan333andErica Jensen e0253a8664 perf: reserve CSS rule map before loading from cache (#2435)
Co-authored-by: Erica Jensen <erica@mailershaven.com>
2026-07-11 22:24:43 -04:00
hooligan333andErica Jensen e142e88ce7 perf: drop per-image delay(50) on chapter build, retry getDimensions (#2434)
Co-authored-by: Erica Jensen <erica@mailershaven.com>
2026-07-11 22:23:52 -04:00
Timo 6add0b91bc feat: Add Finnish hyphenation (#2084) 2026-07-11 22:16:42 -04:00
Leopoldo Pla Sempere 287457f7dd fix: handle low-bit-depth, upscaled, and SVG EPUB images (#2503) 2026-07-11 21:45:53 -04:00
Justin Mitchell 3e627112f6 fix: oom exceptions for OPDS, KOSync, and OTA via wolfssl (#2475) 2026-07-12 01:53:26 +03:00
Stefan Blixten Karlsson 1f5669a08a fix: Swedish translation (#2577) 2026-07-11 23:20:20 +03:00
Ing. Jan Kaláb 0dc92c142c fix: update czech.yaml (#2574) 2026-07-11 21:09:09 +03:00
Leopoldo Pla Sempere 4b34a576eb chore: update Spanish, Catalan, and Valencian translations (#2566) 2026-07-10 17:45:40 +03:00
4fb843ef92 perf: skip redundant progress writes when position is unchanged (#2436)
Co-authored-by: Erica Jensen <erica@mailershaven.com>
Co-authored-by: Uri Tauber <uritaube@gmail.com>
2026-07-10 10:37:43 +03:00
Matteo Scopel a43061305a chore: update the Italian translation (#2559) 2026-07-08 22:22:47 +03:00
Uri Tauber 7ec07b4d9d fix: show "Failed to index" when failing to parse epub (#2556) 2026-07-08 21:22:35 +03:00
Uri Tauber c2c1badc7e chore: Refactor stores to use PersistableStore CRTP template (#2464) 2026-07-08 12:44:52 +03:00
Nick 5776911af0 fix: FontDecompressor OOM aborts on the render path (-fno-exceptions makes vector resize fatal) (#2526) 2026-07-07 09:06:18 +03:00
Pietro Campagnano 3c5c5e8aa7 feat: preview image files inline in web file browser (#2429) 2026-07-06 16:45:22 -04:00
Justin Mitchell 6f5c5a0900 fix: Flatten TextBlock word storage into single allocation (#2547) 2026-07-06 23:41:00 +03:00
Uri Tauber f2b00f6b93 chore: Replace product link with affiliate tracking link (#2401) 2026-07-06 23:13:27 +03:00
Uri TauberandBrooks Ilg 44ff313740 fix: render <br> between paragraphs as a visible section break (#2548)
Co-authored-by: Brooks Ilg <brooksmilg@gmail.com>
2026-07-06 20:10:31 +03:00
Jacob Latonis ca1b833126 fix: follow spec for zxing qr code generation (#2540) 2026-07-06 00:33:34 +03:00
Tom-Inge Larsen 3e1dd31e53 feat(reader): End of Book next-book suggestions (#2499) (#2532) 2026-07-04 23:12:59 +03:00
685d4e88f9 feat: Lazy incremental EPUB section indexing (#2452)
Co-authored-by: Uri Tauber <uritaube@gmail.com>
Co-authored-by: Julia Nguyen <julia@uxj.io>
2026-07-04 21:21:24 +03:00
Jakub Sypiański 79b4d63ee2 fix: settings persist on font clear, reserve() before push_back, minor (#2519) 2026-07-03 18:58:24 +03:00
Leopoldo Pla SempereandJiangoJ 57447a56a6 feat(epub): improve text-decoration support (#2397)
Co-authored-by: JiangoJ <jiangj2620@gmail.com>
2026-07-02 12:35:24 +03:00
Rahat Ahmed 5c86bfe1fd feat(network): add device serial number to web UI (#2506) 2026-07-02 00:53:55 +03:00
Justin Mitchell 4cdefb137c Update README with firmware flashing warnings and crosspet status
Removed warning about flashing Papyrix on USB-locked devices and updated crosspet status to unmaintained.
2026-07-01 14:22:27 -04:00
Justin Mitchell e7f3406704 fix: Add socket module import to build-sd-fonts script (#2504) (#2505) 2026-07-01 12:10:15 -04:00
Bastian 6f08a88b12 fix: changing translation in czech to fix overflowing navigation (#2502) 2026-07-01 11:58:24 -04:00
Tom-Inge LarsenandClaude Opus 4.8 e4d3981469 fix(reader): correct slider side-button direction and legend on X3 (#2402) (#2428)
## Summary

* **What is the goal of this PR?** Fixes #2402. On the X3, the "Time to
Sleep"
picker's 5-minute side buttons were inverted (left increased, right
decreased)
  and the on-screen legend didn't match the physical buttons.
* **What changes are included?**
  * Flip the large-step (±5 min) direction on X3 so the left side button
decreases and the right increases, matching the layout. X4 is unchanged.
* Add a device-specific step-hint string
(`STR_SLEEP_TIMER_STEP_HINT_X3`)
selected via `gpio.deviceIsX3()`. X3 shows `+/-: 1 min Side buttons: 5
min`;
    X4 keeps the original `Left/Right: 1 min  Up/Down: 5 min`.
* Same fix applied to both slider pickers that read the raw up/down side
    buttons: the Time-to-Sleep picker and the "Go to %" picker
(`EpubReaderPercentSelectionActivity`), each with its own X3 hint string
    (`STR_SLEEP_TIMER_STEP_HINT_X3`, `STR_PERCENT_STEP_HINT_X3`).

## Additional Context

* **Root cause:** the X3's side buttons sit one on each edge of the
screen
(power on top), whereas the X4 has a vertical up/down rocker on the
right
edge. So `BTN_UP` is physically the *left* button on X3 but the *top*
button
  on X4. The picker mapped up→+5 / down→−5 unconditionally, which reads
naturally on the X4 rocker but inverted on the X3's left/right buttons.
The
  static legend ("Up/Down: 5 min") had the same X3-only mismatch.
* The codebase already detects the device at runtime and handles this
layout
difference elsewhere (e.g. `LyraTheme::drawSideButtonHints` draws "Up on
left,
Down on right" for X3), so this reuses the same `gpio.deviceIsX3()`
signal.
* Swept the codebase for other side-button sliders:
`ClockOffsetActivity` already
uses the `Next/Previous` (`ButtonNavigator`) abstraction, which resolves
to
left=decrement / right=increment on X3, so it needs no change. List/page
navigation (Wi-Fi, KOReader sync, BMP viewer) and the keyboard cursor
are also
unaffected. The two slider pickers above were the only ones using raw
up/down.
* **X4 is untouched** — same direction, same legend wording as before.
* **Tested on X3 hardware:** left side button now decreases, right
increases, and
  the legend matches. X4 not yet tested on device (no unit on hand); its
  behaviour and strings are unchanged from master.
* **Translations:** the new `STR_SLEEP_TIMER_STEP_HINT_X3` and
`STR_PERCENT_STEP_HINT_X3` were added to all 26 languages, but the
non-English
  ones are AI-generated and would benefit from a native-speaker pass —
particularly Hebrew (RTL ordering with a leading `+/-`), Kazakh, and
Vietnamese.

---

### AI Usage

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 11:06:31 -04:00
Bastian c9d27826f9 docs: update TOC in userguide (#2479) 2026-06-30 08:48:21 +03:00
Bastian 13bd00bf9d fix: sort sleep screen menu options to be more logically consistent (#2480) 2026-06-30 08:47:25 +03:00
Uri Tauberandpablohc fef39da23c feat: Selection Popup (#2358)
Co-authored-by: pablohc <pablonoviello@outlook.com>
2026-06-29 16:59:25 -04:00
Uri Tauber fd43ca2fe1 chore: Initial multi-core compatibility (#2294) 2026-06-29 21:57:21 +03:00
Pietro Campagnano 28255061fb fix(icons): align home menu icons with their labels (#2470) 2026-06-29 13:59:54 -04:00
Julia Nguyen 43ff1b6644 Merge remote-tracking branch 'origin/master' into develop 2026-06-29 12:29:58 -04:00
Егор Мартынов 2754a5ff01 feat: add Vollkorn font (#2473) 2026-06-29 15:54:01 +03:00
Justin Mitchell 6edeed73c5 fix: x4 sleep/boot ghosting (#2471) 2026-06-29 12:04:33 +03:00
Justin Mitchell 8ac302018c fix: x4 ghosting by bumping sdk (#2469) 2026-06-28 22:38:35 +03:00
Rodrigo Matsuura a11517a62d fix: update Portuguese (Brasil) translations (#2458) 2026-06-28 15:07:52 +03:00
Bastian cbaa498ccc docs: adding quick resume option and quick resume on timeout to userguide (#2425) 2026-06-27 22:40:21 +03:00
Justin Mitchell ebebc6f202 chore: migrate from open-x4-sdk to freeink-sdk (#2449)
This PR moves us from the xteink openx4 SDK to the freeink sdk from
https://freeink.org. Out of the box there are NO changes needed in the
firmware to support this swap, it all magically works as is. However as
we support more than just the x3/x4 devices, this sdk allows us to pass
env vars into the build commands to include support for other devices.
As support for new hardware such as touch screens and bluetooth are
added the xteink builds decide at compile time if the libraries are used
or not. For example right now the freeinkui and icons libraries are in
the platform.io file but as they are not used anywhere, they won't be
included in the final build. Once the touch branch and sd themes branch
are merged in this sdk is required for them to function correctly. All
the docs for freeink are available at freeink.org/docs. x4/x3 is a
single binary build unlike other devices that will build unique binaries
for each device. Eventually we will want to remove a lot of the manual
isx3 type stuff from our firmware and go through the boardsupport api
the sdk provides as it will generalize everything into one common system
that any device can support. The upcoming touch branch does a lot of
this for us but this initial PR is JUST to get the sdk swapped over
without any code changes to show seamless integration without any
regressions.
2026-06-27 21:22:58 +03:00
Julia 970b2c6ca1 chore: release 1.4.1 (#2447)
## Improvements

* Moved the File Manager breadcrumb into the Contents card header for a
cleaner, more consistent interface.
* Updated the Wireless Transfer section of the User Guide with clearer
instructions.
* Battery status bar indicator no longer changes position when adding or
removing bookmarks.

## Performance

* Optimized path normalization for faster file handling.
* Significantly improved bookmark rendering by removing unnecessary
XPath lookups.
* Optimized dithered rectangle drawing (fillRectDither) using a
byte-aligned rendering implementation, improving display performance on
supported devices.

## Bug Fixes

* Fixed an issue where the Inverted Orientation label was incorrectly
combined with the Color Filter label for Geman localization.
* Fixed excessive ghosting on the X3 cover screen during sleep
2026-06-26 17:40:04 -04:00
Julia Nguyen b6ce599b20 fix: address release review feedback 2026-06-26 16:22:20 -04:00
Juliaandcoderabbitai[bot] f54eab2725 fix: typo in STR_ORIENTATION_INVERTED Spanish translation
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-06-26 16:13:19 -04:00
Justin Mitchell 0d4c9ab91b Bump version to 1.4.1 2026-06-26 15:48:19 -04:00
Julia 0daa9db243 fix: keep status bar indicators stable when toggling bookmarks (#2444) 2026-06-26 19:26:33 +03:00
Uri TauberandRyan Mercado a2f2eea79e perf: Optimize fillRectDither with Byte-Aligned fillRectImpl (#2270)
## Summary

* **What is the goal of this PR?**
Replace the pixel-by-pixel `fillRectDither` implementation with a new
byte-aligned `fillRectImpl` that eliminates per-pixel
`rotateCoordinates` calls and Read-Modify-Write bitwise loops, yielding
a significant rendering speedup on ESP32 E-ink framebuffers.

Ported from @rhythmerc's crosspoint-reader fork commit 27ea625.

* **What changes are included?**

- **`GfxRenderer.cpp` — `fillRectDither` refactor:** The existing
`if/else if` chain is replaced with a `switch` statement that delegates
each `Color` case to the new `fillRectImpl<Color>()` template,
eliminating runtime branching.

- **`GfxRenderer.cpp` — new `fillRectImpl<C>()` template:** Core of the
optimization. Key behaviors:
    - Clips the rectangle in logical space upfront.
- Rotates only **2 opposing corner points** (top-left and bottom-right)
into physical framebuffer space instead of rotating every pixel
individually.
- Derives physical-space `byteStart`/`byteEnd` and precomputes
`headMask` / `tailMask` for MSB-first partial-byte boundaries,
performing RMW only on the edge bytes.
- **Solid fills (`Black` / `White`):** Uses `memset` for all interior
full-byte runs per row — no per-pixel writes.
- **Dithered fills (`LightGray` / `DarkGray`):** Precomputes both parity
variants of `blackMask` (even/odd `py`) **outside** the row loop,
eliminating the previously re-evaluated 8-bit construction loop on every
physical row. Interior full bytes are then written with a single
`memset(whiteMask)`.
- Uses `if constexpr` throughout to dispatch on `Color` at compile time,
generating zero runtime branches per template instantiation.

- **`GfxRenderer.h`:** Declares the new private `fillRectImpl<Color>()`
template method with an explanatory doc-comment.

- **Explicit template instantiations** added for all four active `Color`
variants (`Black`, `White`, `LightGray`, `DarkGray`).

## Additional Context

* **Performance:** The primary motivation is ESP32 E-ink framebuffer
performance. The old path called `rotateCoordinates` and did a full RMW
for every single pixel in the rectangle. The new path calls
`rotateCoordinates` exactly **twice** per fill regardless of rectangle
size, then operates at byte granularity — a complexity reduction from
O(W×H) coordinate transforms to O(1).
* **Dither correctness:** The `blackMask` precomputation relies on the
dither pattern having period 2 in both logical X and Y, which makes the
per-row byte pattern repeat with period 2 in `py`. Reviewers should
verify the `lxBase`/`lyBase` derivations for all four orientations
(`Portrait`, `PortraitInverted`, `LandscapeClockwise`,
`LandscapeCounterClockwise`) match the inverse of `rotateCoordinates`.
* **Edge case — single-byte rows:** When `byteStart == byteEnd`, the
head and tail masks are ANDed together into a single `rectMask` to avoid
double-masking the same byte. This path should be tested with narrow
rectangles (width < 8px).
* **No behavioral change for `Color::Clear`:** The `Clear` case exits
early via `if constexpr` and is a no-op, matching the original behavior.

---

### 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 >**_

---------

Co-authored-by: Ryan Mercado <rmercado@firstdollar.com>
2026-06-26 12:16:25 -04:00
Ankit 86a9b9c4a2 docs: update user guide wireless transfer section (#2369)
Co-authored-by: Uri Tauber <uritaube@gmail.com>
Closes #1407
2026-06-26 08:25:52 +03:00
Julia Nguyen ba44978ac4 Merge branch 'master' into develop 2026-06-25 17:51:56 -04:00
Pietro Campagnano 555f76da88 feat: move file manager breadcrumb into contents card header (#2430) 2026-06-26 00:45:43 +03:00
Justin Mitchell 0a57c0a5a7 Fix X3 display ghosting on cover screen transitions
Force display resync on X3 when HALF refresh is requested to clear prior content before rendering. Add grayscale preconditioning for X3's UC81xx controller to even out single-pixel dithering artifacts that appear as speckle with its turbo BW waveform.
2026-06-25 17:42:19 -04:00
Uri Tauber a09aef0889 fix: Optimize Bookmark Rendering by Removing XPath Lookup (#2417)
## Summary

fix #2414 

### Root Cause

`updateBookmarkFlag()` was introduced in commit 1db1442 and is executed
on every render (every page turn). The function calls:

`ProgressMapper::toSavedProgress()`
→ `ChapterXPathResolver::findXPathForProgress()`

This path decompresses the current EPUB section content twice:

1. To count visible characters.
2. To resolve the corresponding XPath.

For larger sections (e.g. ~133 KB decompressed content), this adds
approximately **1 second of I/O overhead per page turn**, with the cost
increasing as chapter size grows.

### Fix

`updateBookmarkFlag()` only needs to determine whether a bookmark falls
within the currently displayed page range.

The required information is already available during rendering:

* `currentPage`
* `section->pageCount`
* `currentSpineIndex`

Instead of converting the current location to a saved progress object
(and resolving an XPath), the implementation now computes the current
page's progress range directly and compares bookmark percentages against
that range.

This is effectively the same percentage-based matching logic already
used as a fallback in `bookmarkMatchesProgress()` when XPath matching is
unavailable.

---

### 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-06-25 09:20:24 -04:00
Bastian 8626d69f46 fix: small translation changes for german (#2420) 2026-06-25 09:29:05 +03:00
Uri Tauber fc89e57e69 perf: optimise normalisePath (#2162) 2026-06-25 09:01:50 +03:00
Julia 487613b082 fix: split inverted orientation label from color filter label (#2421)
## Summary

* **What is the goal of this PR?** 
* Fix the “Inverted” translation so reader orientation and color/filter
inversion can use separate labels.
* **What changes are included?**
* Adds `STR_ORIENTATION_INVERTED` for the inverted portrait orientation
option.
* Updates the reader orientation setting to use
`STR_ORIENTATION_INVERTED` instead of reusing `STR_INVERTED`.
* Leaves `STR_INVERTED` for the sleep cover filter and tilt page-turn
mode
  * Adds the new orientation string across all 26 locale YAML files.

## Additional Context

* The original issue was found by a user in German, where `STR_INVERTED`
was translated as `Hochformat 180°`, which made sense for orientation
but not for color filters.
* This is a UI-label-only change. It does not change persisted
orientation values or settings behavior.
* Reviewer note: non-English wording may still benefit from
native-speaker review, especially for the new orientation-specific
labels and to verify the interchangeable usage between inverted color
and inverted tilt page turn direction.
---

### 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 >**_
2026-06-25 00:47:06 -04:00
JuliaandHusam Younis 8d5b119644 fix: sync master into develop (#2423)
Sync develop branch with master

Co-authored-by: Husam Younis <youhusam@gmail.com>
2026-06-24 22:07:13 -04:00
Husam Younis 7271c00d35 feat: Allow statusbar clock to be on the left (#2359) 2026-06-25 00:23:23 +03:00
Uri Tauber 6e8dbd7f23 chore: Update version to 1.4.0 (#2283) 2026-06-24 18:30:32 +03:00
Uri Tauber c4b1d9644a fix: correct font size selection (#2410) 2026-06-24 17:01:10 +03:00
Justin Mitchell f6e59aab72 feat: Add displayGrayscaleBase method for differential refresh
(#2334)

Introduces a new display method that prepares the framebuffer as a base
frame for grayscale overlays. On X3 panels, this uses the OEM
differential base waveform (AA-pre-BW) without forcing a resync. Other
panels fall back to normal display with configurable refresh mode.


Dependent upon matching SDK commit to work
2026-06-24 07:53:19 -04:00
Uri Tauber 9ad2da0950 fix: add seven missing hebrew translations (#2409) 2026-06-24 09:34:40 +03:00
Uri TauberandJulia Nguyen 1db1442319 fix: several bookmarks UX improvments (#2372)
## Summary

This PR enhances the EPUB reader's bookmark system with two
complementary improvements: a per-page bookmark indicator icon and
toggle behavior on the existing long-press action.

---

### What Changed

**Bookmark Toggle (was: add-only)**

The long-press Confirm action now toggles bookmarks rather than always
adding. `addBookmark()` checks whether a bookmark with the same xpath
already exists in the in-memory cache:
- If found → removes it and shows "Bookmark removed."
- If not found → adds it and shows "Bookmark added."

A new `STR_BOOKMARK_REMOVED` translation string was added to support the
removal message.

**Bookmark Icon Indicator**

A `BookmarkIcon` is now drawn at the top-right corner of the page
whenever the current page has a bookmark. `updateBookmarkFlag()` is
called at render time to determine whether the current page is
bookmarked.

**In-Memory Bookmark Cache**

Bookmarks are now loaded into `cachedBookmarks` on `onEnter()` rather
than being re-read from disk on every toggle. All subsequent add/remove
operations work against this cache and flush to disk, avoiding redundant
file reads on each bookmark action.

**Faster bookmarks list**

Previously, calculating "page X/Y" for each entry required decompressing
the entire spine item. We now persist `si`/`pc`/`pp` (spine index, page
count, and page progress) in the bookmark JSON when saving, and restore
them when loading. This avoids the expensive `toCrossPoint()` loop in
`onEnter()`, significantly reducing the cost of initializing the
bookmarks list.

---

### Files Changed

- `EpubReaderActivity.cpp` — `addBookmark()` toggle logic,
`updateBookmarkFlag()` (new), icon rendering in `renderContents()`,
cache initialization in `onEnter()`
- `EpubReaderActivity.h` — new fields: `currentPageBookmarked`,
`bookmarkRemoved`, `cachedBookmarks`; new method declaration
`updateBookmarkFlag()`

---

### 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: Julia Nguyen <julia@uxj.io>
2026-06-23 14:55:01 -04:00
Leopoldo Pla Sempere 362dcb2a65 feat: Smooth progressive JPEG cover upscales in BMP conversion (#2214) 2026-06-22 13:55:12 -04:00
Julia d1abcc00a2 feat: render grayscale epub images without text aa (#2393) 2026-06-22 17:15:27 +03:00
Zeph 0ce874f8d4 feat: footnote returns to original position on deep sleep to avoid losing reading position (#2394) 2026-06-22 15:28:42 +03:00
kygia 282514f755 fix(epub): flush displaced anchor before overwrite (#2336) (#2382)
## Summary

* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)

Fixes footnote links landing in start section instead of the specific
foot note target.

* **What changes are included?**

ChapterHtmlSlimParser.cpp: call flushPendingAnchor() before overwriting
pendingAnchorId. First id got lost due to consecutive non-block elements
carry ids, the first id was lost and the reader had no page to jump to,
so it defaulted to page 0.

## Additional Context

Tested with the epubs attached to to #2336. Need to clear .crosspoint/
cache after flashing so the anchor map gets rebuilt with the fix.

* 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 assisted with writing documentation.
2026-06-21 16:43:39 -04:00
SurprisedDuck d1e4650e19 fix: don't justify-stretch a leading no-break space (#2185) (#2298) 2026-06-21 16:12:14 +03:00
Justin Mitchell 370f87ea01 feat: add Spacing Modifier Letters range to font presets (#2194) 2026-06-21 09:14:42 +03:00
Leopoldo Pla Sempere 8b9a73a735 chore: update Spanish, Catalan, and Valencian translations (#2384) 2026-06-21 00:26:30 +03:00
Julia 29c69b3d8f fix: automatically connect to wifi for clock sync (#2379)
## Summary

* **What is the goal of this PR?** 
* Make manual clock sync work even when the device is not already
connected to Wi-Fi, so the user can start the sync flow directly from
settings instead of being blocked by connection state.
* **What changes are included?**
* `ClockSyncActivity` now launches the normal Wi-Fi selection flow
before syncing when the device is offline.
* After Wi-Fi selection succeeds, clock sync resumes automatically and
performs the existing forced NTP sync.
* If clock sync had to bring Wi-Fi up, the activity disconnects and uses
the existing silent restart cleanup path to avoid leaving the device in
a fragmented post-Wi-Fi heap state.
* The completion UI now only advertises Back, matching the updated input
handling.

## Additional Context

* Existing behavior is unchanged when Wi-Fi is already connected: the
activity syncs immediately.
* Cancelling Wi-Fi selection exits the clock sync flow.

---

### 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 >**_
2026-06-20 22:09:28 +03:00
Matteo Scopel 4b5a84dd81 chore: update the Italian translation (#2376) 2026-06-20 22:08:35 +03:00
muhasandmuhas b9874c8114 fix(lang): [russian] new string (#2374)
Co-authored-by: muhas <mail@muhas.name>
2026-06-19 13:15:15 +03:00
Paul Delestrac b1131795d8 feat: add live font preview pane to font selection screen (#2349)
## Summary
* Adds a live font preview pane to the font selection screen so users
can see how a font looks before committing to it.
* Changes
* A preview pane occupying the top 30% of the font selection screen,
rendering sample pangram text in the previewed font
* A two-step confirm flow: first press (enter button) previews the font,
second press selects it
* Back restores the original font settings, so browsing has no side
effects
* Layout dimensions cached in `onEnter()` to avoid redundant
recalculation between `loop()` and `render()`

## Additional Context
* Preview sample text is hardcoded English; didn't want to use AI for
translation as I would not be able to verify the output in most
languages...
* The preview pane reduces visible list height; this is compensated by
passing the reserved height into `getNumberOfItemsPerPage`

---
### AI Usage
Did you use AI tools to help write this code? **PARTIALLY**
AI use for assisting in coding and in writing the PR description.
2026-06-18 18:16:28 -04:00
Pedro CardosoandClaude Opus 4.8 5e990b3991 fix: prevent progress.bin corruption from interrupted writes (#2275) (#2305)
## Summary

* **What is the goal of this PR?**
Fixes #2275. A book could get stuck reopening on an old page, with
progress no
longer saving and neither "Delete Book Cache" nor "Clear Reading Cache"
able to
fix it. Root cause: `progress.bin` was written truncate-in-place, so an
interrupted write (power loss, or a crash mid-SPI during sleep) left it
with a
broken FAT cluster chain that the firmware could neither rewrite nor
delete —
  recovery required `fsck`/manual deletion on a host PC.

  Confirmed in the SDK: `SDCardManager::openFileForWrite` opens with
`O_RDWR | O_CREAT | O_TRUNC`, so the canonical file is zeroed before the
few
progress bytes are rewritten — exactly the window that corrupts the FAT
chain.

* **What changes are included?**
  * New shared helper `ProgressFile::writeAtomic()`
    (`src/activities/reader/ProgressFile.h`): writes progress to
    `progress.bin.tmp`, flushes and closes it, then `remove`s the old
`progress.bin` and `rename`s the temp into place. An interrupted write
now
only ever damages the throwaway temp; the canonical file is never torn.
  * All three readers route their progress saves through the helper:
EPUB (`EpubReaderUtils.h`), `TxtReaderActivity`, `XtcReaderActivity` —
they
    all shared the identical vulnerable pattern.
* Minor: `EpubReaderUtils::saveProgress` now takes `const Epub&` (clears
a
    cppcheck `constParameterReference` finding).

## Additional Context

* **Crash-safe, not metadata-atomic.** On FAT the replace is `remove` +
`rename`
(two directory ops; SdFat's `rename` won't overwrite, hence
remove-first). A
crash between them leaves *neither* file, which reads as "no saved
progress" on
next launch — a harmless reset to an old page, never a
corrupt/unclearable file.
  The guarantee is that `progress.bin` is never half-written.
* **Prevents, does not repair.** This stops new corruption on healthy
cards. It
cannot fix an already-corrupted `progress.bin` (removing it may itself
fail at
the FAT level) — those still need `fsck`/manual deletion, as in the
issue's
  workaround.
* **Known follow-up (out of scope here):** a crash *while writing the
temp* can
leave an orphan `progress.bin.tmp`. It's harmless and self-healing (the
next
save overwrites it, and it never blocks reading progress), but a
boot-time
  orphan-`.tmp` cleanup would be a tidy follow-up.
* **Focus areas for review:** the close-before-rename ordering in
  `ProgressFile.h` and the remove-before-rename rationale.

## Verification

* `./bin/clang-format-fix` — clean
* `pio check --fail-on-defect low --fail-on-defect medium
--fail-on-defect high` — no defects
* `pio run` — SUCCESS (RAM 30.9%, Flash 78.8%; footprint essentially
unchanged)
* Tested on a **Xteink X4** device: open book, turn pages, sleep/exit,
reopen —
  progress now restores to the navigated page across all three readers
  (EPUB / TXT / XTC).

---

### AI Usage

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 18:01:56 -04:00
Uri Tauber ff1951c715 fix: correct behaviour for prev/next side buttons (#2373)
## Summary

* **What is the goal of this PR?** fix #2365
2026-06-18 13:13:35 -04:00
Ankit 121c0690b0 fix: use STR_SELECT instead of STR_OPEN in bookmark button hint (#2371) 2026-06-18 19:58:47 +03:00
16b0853654 fix(epub): NFC-normalize EPUB text so NFD diacritics render correctly (#2277)
Co-authored-by: Uri Tauber <uritaube@gmail.com>
Co-authored-by: Julia <julia@uxj.io>
2026-06-18 13:00:01 +03:00
Uri Tauber 7d639cf880 fix: submodule pointer (#2368) 2026-06-17 22:35:12 +03:00
Justin MitchellandUri Tauber 22f3575064 feat: Support for Korean line breaks and glyph spacing (#2288)
Co-authored-by: Uri Tauber <uritaube@gmail.com>
2026-06-17 17:27:03 +03:00
darkbubluandClaude Opus 4.7 d4069aeae5 feat: long-press Confirm launches KOReader sync from EPUB (#1808)
## Summary
- Hold the menu (Confirm) button for ≥1s while reading an EPUB to launch
the existing `KOReaderSyncActivity` directly — replaces the three-step
path (open reader menu → scroll to Sync → confirm) with a single
gesture.
- Reuses `ReaderUtils::GO_HOME_MS` (same 1s threshold used by long-press
Back) and the existing `KOREADER_STORE.hasCredentials()` guard.
- Adds a Controls picker **"Long-press Menu"** (`longPressMenuFunction`,
default **Bookmark**) that **cycles through the available functions**
bound to the long-press gesture: `KOSync → Disabled → Bookmark`. The
field name and `LONG_PRESS_MENU_FUNCTION` enum are intentionally general
so future actions (dictionary lookup, table of contents, etc.) can be
appended without another schema migration. The setting is **not** a
binary toggle.
- Existing menu Sync entry still works — both call sites share one
extracted helper (`launchKOReaderSync`); no logic duplication.
- Short-press Confirm release is gated on duration so the reader menu
does not also open after a long press that *acts*, mirroring the
existing long-press Back pattern.
- **No-credentials fall-through:** `launchKOReaderSync()` now returns
whether it acted. When the function is set to KOSync but no KOReader
credentials are stored, the long-press is a no-op that **falls through
to open the reader menu** — so the menu stays reachable instead of the
hold silently swallowing the gesture. The release is only suppressed
when sync actually launched or surfaced a save error.

## Test plan
- [x] `pio run` succeeds clean for the `default` ESP32-C3 environment.
- [x] On-device, value **KOSync**, valid KOReader credentials:
long-press Confirm ≥1s → sync screen launches; get + update progress
return HTTP 200; release does **not** also open the reader menu; returns
to the same page.
- [x] On-device, value **KOSync**, **no** credentials: long-press
Confirm falls through and **opens the reader menu** (regression fix);
short-press also opens the menu.
- [x] On-device: menu → Sync still launches the same screen (shared
helper) and syncs (200/200).
- [x] On-device, value **Bookmark**: long-press drops a bookmark and
does **not** also open the menu.
- [x] On-device, value **Disabled**: long-press Confirm opens the menu
on release; no sync, no bookmark.
- [x] On-device: long-press Back still goes to the file browser
(unchanged path).
- [x] Heap: epub is released before the TLS handshake (frees ~16 KB);
min free heap stayed ~84 KB during sync, well above the safe floor. No
panics/OOM across the session.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-17 17:10:26 +03:00
Mauvis LedfordandJulia 54d5a788a5 feat: add drag-and-drop to upload modal (#2290)
Co-authored-by: Julia <julia@uxj.io>
2026-06-17 16:53:31 +03:00
Ankit f66220a245 fix: enable Shift key for URL keyboard input (#2178) (#2357) 2026-06-17 16:32:36 +03:00
KymAndriyandKymAndriy 2266060e84 fix: Ukrainian translation (#2354)
Co-authored-by: KymAndriy <test@notamail.ua>
2026-06-16 14:02:23 +03:00
Julia 90039d7f4d fix(koreader): resolve element XPath progress against visible body text (#2308) 2026-06-15 22:03:32 -04:00
rafaelmsseandRafael Santos 16eb66d7cf fix: add missing HTML 4.01 named entities (#2352)
Co-authored-by: Rafael Santos <rmsantos@applaudostudios.com>
2026-06-15 22:25:37 +03:00
RoninandUri Tauber 02bab00be6 fix: swap reader menu navigation direction in CCW/inverted (#2321) (#2341)
Co-authored-by: Uri Tauber <uritaube@gmail.com>
2026-06-15 22:19:34 +03:00
Mauvis Ledford 55a56914d7 fix(i18n): localize home empty-state strings (#2342) 2026-06-14 21:40:05 +03:00
Uri Tauber 8634cb8ad3 fix: Restore first-line paragraph indentation (#2320) 2026-06-12 11:55:00 -04:00
Julia 21caebd6e4 fix: use Noto Sans as punctuation fallback when generating downloadable SD fonts (#2331) 2026-06-12 11:54:34 -04:00
Matteo Scopel 6f05e401a1 feat: add the Libre Baskerville font family (#2088)
## Summary

This PR adds the [Libre Baskerville
](https://github.com/impallari/Libre-Baskerville) font family to the
fonts available for download in CrossPoint.


## Additional Context

Baskerville is a classic font family, widely available in some form or
the other in most ereaders and widely used in publishing and in the
academy.

Libre Baskerville is a libre (SIL Open Font License v1) implementation
of it, which makes it possible for CrossPoint to ship it without issues.


![Vita-e-destino_ch30_p1_13pct_99220.bmp](https://github.com/user-attachments/files/28070672/Vita-e-destino_ch30_p1_13pct_99220.bmp)


---

### AI Usage

Did you use AI tools to help write this code? _**NO**_
2026-06-11 19:10:41 +03:00
Zach Nelson f2e3d117dc fix: Hanging indent causes overlapping words (#2324) 2026-06-11 18:23:49 +03:00
Zach Nelson 9202522a39 refactor: Drop redundant self-class prefix on applyDirectionToEntry (#2325)
## Summary

Non-functional cleanup: `ChapterHtmlSlimParser::` prefix is redundant
when calling `applyDirectionToEntry` within `ChapterHtmlSlimParser`
methods.

---

### AI Usage

Did you use AI tools to help write this code? _**NO**_
2026-06-11 10:38:00 -04:00
Uri Tauber 301f1d1a38 fix: Compile Error: Duplicate _order values found (#2323) 2026-06-11 15:18:53 +03:00
Hoang Manh LinhandClaude Opus 4.8 1f83087aa5 feat(fonts): add Vietnamese glyph coverage to the built-in UI font (#2280)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 15:11:50 +03:00
Hoang Manh LinhandClaude Opus 4.8 68138fffdb feat(i18n): add Vietnamese translation (#2279)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 15:10:45 +03:00
MartinandUri Tauber eb98735115 feat: Slovak translation add (#2251)
Co-authored-by: Uri Tauber <uritaube@gmail.com>
2026-06-10 18:49:39 +03:00
Uri Tauber b94b58756f fix: german traslation for STR_INVERTED (#2315) 2026-06-10 16:33:54 +03:00
muhas ca6ebc56fb fix(lang): russian.yaml new string (#2309) 2026-06-10 08:10:26 +03:00
Uri Tauber 2ea042a68a fix: skip <span> anchors (#2303)
## Summary

* **What is the goal of this PR?** Prevent heap memory exhaustion caused
by thousands of machine-generated anchor IDs injected by epub converters
like Kobo KePub.
* Fix #2292.

* **What changes are included?**
* **Element-type filter (Layer 1):** Introduced the
`isNonNavigableInlineElement()` function in `ChapterHtmlSlimParser.cpp`
to automatically skip recording IDs on `<span>` elements, as they are
purely inline wrappers used for tracking and lack navigable meaning.
* **Hard cap (Layer 2):** Added the `MAX_ANCHORS_PER_CHAPTER = 1024`
constant to act as a fallback safety net against unbounded heap growth
from unknown future ID-injection patterns on non-span elements.
* **TOC Safety net:** Ensured that IDs matching known Table of Contents
(TOC) entries explicitly bypass both the `<span>` filter and the
1024-anchor cap, guaranteeing that chapter page-break and core
navigation logic are never compromised.


---

### 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-06-09 08:58:21 -04:00
Lorenzo Santini 9c12b90737 feat: add power button short press for quick access to footnotes (#1658) 2026-06-08 14:45:41 -04:00
Leopoldo Pla Sempere 7f01eab62e docs: refresh cache formats and web server workflows (#2233)
## Summary

* **What is the goal of this PR?** Update project documentation to match
the current master implementation for cache formats, i18n, file
transfer/web server workflows, SD-card fonts, and root user-facing docs.
* **What changes are included?** Refreshes `book.bin`/`section.bin` docs
for v6/v25, updates File Transfer/Calibre/WebDAV/API docs, documents 24
UI languages and JSON language persistence, updates root
README/USER_GUIDE cache and network details, and syncs the tracked
CLAUDE skill doc cache-version notes.

## Additional Context

* Docs-only change. Verified with `git diff --check origin/master..HEAD`
and stale-reference greps for old cache versions, removed i18n APIs, old
WiFi screen wording, and raw `Serial.printf` examples. No firmware build
was run.

---

### 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-06-07 15:47:44 -04:00
Zach Nelson 3a1e9f3023 refactor: Dedupe wifi scan in-place without std::map (#2262)
## Summary

`std::map` heap-allocates each node. Avoid using it for wifi SSID
dedupe, and instead just dedupe in-place with the existing `networks`
vector. Removed dead `ipAddress` member in `WifiNetworkInfo` struct.

---

### AI Usage

Did you use AI tools to help write this code? _**PARTIALLY**_
2026-06-07 08:27:20 -04:00
SurprisedDuck fad1a801a4 fix: scale SUP/SUB underline to match 50%-scaled glyphs (#2255) 2026-06-06 18:55:47 -04:00
Julia 67936cb3af fix(epub): decode footnote href path before spine lookup (#2271) 2026-06-05 18:38:13 -04:00
Uri Tauber fd5b8078c6 chore: add t5s3 fork (#2268) 2026-06-05 18:50:22 +03:00
Julia bd101b2af8 fix(epub): decode percent-encoded internal asset paths so assets render correctly (#2249)
## Summary

* **What is the goal of this PR?** 
* Fix EPUBs where internal file references are written in URL-style
escaped form, like spaces appearing as `%20`, so the reader can find the
right files instead of treating those references as missing.
* **What changes are included?**
* Added a small shared helper that converts those escaped EPUB-internal
paths back into their normal filenames before we try to look them up.
* Applied that cleanup step across the EPUB parsing flow wherever we
resolve internal references, including cover images, manifest items, TOC
links, spine entries, and inline HTML images.
* Kept the change narrowly focused on EPUB-internal asset resolution
rather than changing broader URL or networking behavior.

## Additional Context

* The user-facing bug here is that some books package their internal
filenames in an escaped form, so a file like `Chapter 1.xhtml` may be
referenced more like `Chapter%201.xhtml`. The reader was treating that
escaped text as the literal filename, which means otherwise-valid books
could lose images, covers, or chapter targets because the lookup no
longer matched the real file inside the EPUB.
* Risk is intentionally low. The helper only rewrites valid `%XX` escape
sequences and leaves malformed input alone, so it should improve
compatibility with escaped filenames without broadening the parser’s
behavior in unrelated cases.

## Local Testing Performed
* This was tested on my device with the user-provided optimized epub
that was not rendering images within the text prior to this fix (cover
image and chapter headers were rendering fine):

[orv_main_baseline.epub.zip](https://github.com/user-attachments/files/28529545/orv_main_baseline.epub.zip)
* This was also tested by the user with a local build and the affected
epub and confirmed to be working

## Steps for Testing
* Try to open the affected epub (linked above) or any epub that has
similar percent-encoding on a build prior to this fix.
* Apply this fix, clear book cache, and re-open the affected book.
* Images should render properly.
---

### 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 >**_
2026-06-05 10:31:20 -04:00
Uri Tauber 19d51ec08c fix: german translation for STR_INVERTED (#2269) 2026-06-05 12:22:29 +03:00
Uri Tauber ea231ffd18 Revert "Update german.yaml" (#2267) 2026-06-05 11:22:36 +03:00
Uri Tauber a2d4c6a139 Update german.yaml
Fix #2266
2026-06-05 11:17:22 +03:00
Zach Nelson b5b1f650e2 perf: Minimize string allocations in CSS parsing (#2263)
## Summary

Use `std::string_view` and case-insensitive comparisons to avoid string
allocations during CSS parsing.

**Hot path:** `resolveStyle` (called per HTML start tag during chapter
rendering) now does zero heap allocations. Previously it allocated a
normalized tag string, a vector of class strings, and a composite key
per class. For a chapter with ~2000 tags × 2 classes each, that's ~12
000 small short-lived allocations eliminated per page render — primarily
a heap-fragmentation win on the ESP32-C3's ~380KB RAM.

**Cold path:** CSS load no longer allocates per-rule selector vectors or
per-token strings; `splitOnChar`/`splitWhitespace` are gone, replaced
with callback-based tokenization (`forEachDelimitedToken`).

**Behavioral notes:**
- The selector `unordered_map` now uses an ASCII-case-insensitive
hash/equal. Selectors are stored with their original case rather than
pre-lowercased; the observable lookup result is unchanged.
- `stripTrailingImportant` is now case-insensitive (per CSS spec;
previously matched only lowercase `!important`).

**Cache compatibility:** `CSS_CACHE_VERSION` unchanged. Old caches
(lowercase selectors) load correctly under the new lookup; new caches
will contain verbatim-case selectors — both forms work.

---

### AI Usage

Did you use AI tools to help write this code? _**PARTIALLY**_
2026-06-04 18:05:32 -04:00
Eloren1 f04b8aa9a9 fix: apply progress bar offset from top instead of bottom edge (#2250) 2026-06-04 17:54:57 +03:00
Justin Mitchell d9bcef7a58 fix: Replace full-image cache buffer with streaming band buffer to reduce memory usage (#2230) 2026-06-04 10:46:22 -04:00
Uri TauberandJulia a60f31cdd4 fix: Crash on invalid font filename (#2253)
Co-authored-by: Julia <julia@uxj.io>
2026-06-04 10:34:34 -04:00
Uri TauberandJulia f055fdd774 fix: long-press back should move to the start of chapter (#2243)
Co-authored-by: Julia <julia@uxj.io>
2026-06-04 16:35:38 +03:00
Julia bb078bae09 fix: KOReader sync drift when syncing at chapter start (#2245) 2026-06-04 15:23:52 +03:00
Matteo Scopel 2d65808302 chore: small Italian fixes (#2252) 2026-06-03 14:43:43 +03:00
Matheus Martins 6e75e5a6be docs: Fix section numbering and table of contents in USER_GUIDE.md (#2244) 2026-06-02 22:12:00 +03:00
Uri Tauber db94a86fba fix: Skip Underline Calculations During Font Cache Scan Pass (#2237)
## Summary

This PR optimizes the text rendering process by skipping underline style
calculations and measurements during the initial font cache scanning
phase. This prevents excessive and unnecessary SD card reads on pages
with heavy use of underlines (e.g., Table of Contents pages).

### **The Problem**

During the first rendering pass (the font cache scan pass used to
collect text for prewarming), `GfxRenderer::drawText()` early-returns
after recording text as expected. However, `TextBlock::render()`
continued past this point to execute the underline decoration logic.

Because underline calculation calls `getTextWidth()` and
`getTextAdvanceX()`, it triggered immediate glyph lookups via
`EpdFont::getGlyph()`. Since the SD card font had not been prewarmed yet
at this stage, the lookups fell back to the `glyphMissHandler`,
resulting in hundreds of individual, slow SD card reads into a limited
8-slot ring buffer.

### **The Fix**

1. **Exposed Scan State:** Added `GfxRenderer::isFontCacheScanning()` to
safely check if the font cache manager is currently in
text-collection/scan mode.
2. **Bypassed Underline Logic:** Modified `TextBlock::render()` to check
this state and skip underline measurement and drawing entirely while
scanning is active.

> [!NOTE]
> The text itself is still properly captured for prewarming via
`drawText()`. The underlines will be safely and efficiently calculated
and drawn during the actual render pass after the fonts have been
completely prewarmed.

---

### 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 >**_
2026-06-02 09:47:24 -04:00
Uri Tauber b12839d1d4 chore: Move opendyslexic from flash to SD (#2231) 2026-06-01 13:49:57 -04:00
Justin Mitchell 50e4d550fb Fix ghosting on pages following images in grayscale (#2226) 2026-05-31 23:33:26 -04:00
Julia 03f73fadc7 fix: avoid zip-wide css scan for large epubs (#2213)
## Summary

* **What is the goal of this PR?** 
* This PR ports over Crossink's handling of large EPUBs that helps
prevent crashes during open after the book metadata cache is built.

* **What changes are included?**
* Removes the post-indexing ZIP-wide CSS discovery pass that built an
in-memory map of every ZIP entry.
* Reuses `content.opf` parsing to collect declared CSS files without
writing spine entries again.
* Temporarily releases the loaded book metadata cache while rebuilding
CSS for cached books.
* Parses CSS before reloading `book.bin` after a fresh cache build,
leaving more heap available during CSS rule parsing.

## Additional Context

* User reported their EPUB opening fine on Crossink but would crash on
Crosspoint. Verified this claim on my own devices.
* The crash this addresses happened after `book.bin` was successfully
built, when CSS discovery allocated a large `unordered_map` for ~3k EPUB
ZIP entries.
* Tradeoff: CSS files not declared in `content.opf` are no longer
discovered by scanning the full ZIP. This avoids the high-risk memory
allocation but improperly formatted EPUBs (ones that don't declare their
CSS styles in `content.opf` will render without styling and fallback to
inline styles.
* User provided epub that was crashing prior to this change:
https://www.mediafire.com/file/g57ea4mj13iunvh/Quang+%C3%82m+Chi+Ngo%E1%BA%A1i+-+Nh%C4%A9+C%C4%83n.epub/file

---

### 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 >**_
2026-05-31 18:31:06 -04:00
Wilhelm Schuster 0d64980d25 fix: update German translation (#2224) 2026-06-01 00:51:52 +03:00
Nathanael Maher 4fe80ba7a5 fix: bookmark percentage always 0% and page number starts at 0 (#2188)
## Summary

* Fix the issue described in #2176 where bookmark percentages are always
0%
* Also fixed the page number rendering to +1 because they are 0 index
based, but should be rendered starting from 1

---

### AI Usage

Did you use AI tools to help write this code? _**< NO >**_
2026-05-31 17:37:21 -04:00
Wilhelm Schuster b229428a17 chore: remove unused sleep timer interval strings (#2223) 2026-06-01 00:26:17 +03:00
Matteo Scopel 36d0e52e4b chore: new Italian fixes (#2218) 2026-05-31 17:39:15 +03:00
Uri Tauber 94c0ba466c feat: Hebrew localization (#2068) 2026-05-31 16:57:51 +03:00
muhas da2e4eabb0 fix(lang): Russian translate for new strings (#2217) 2026-05-31 16:05:47 +03:00
Matteo Scopel 3392b3e35a chore: update the Italian translation (#2207) 2026-05-30 23:42:19 +03:00
Matheus Martins 52e7446757 fix(docs): Section numbering in USER_GUIDE.md (#2195) 2026-05-30 23:34:44 +03:00
Jeremy Klein d83605e003 docs: add Claude Code contributor skills for firmware code quality (#2212) 2026-05-30 23:30:18 +03:00
WuTofuandJulia ebf0413fe1 feat: add custom sleep timer picker (#2206)
## Summary

* **What is the goal of this PR?**
Mainly fixes #2196, but also allows users to set a sleep time of 1~30
min or to never sleep.

* **What changes are included?**

1. Revert changes from #1948 and #2137 (Sorry @Uri-Tauber)
2. Cherrypick changes from
https://github.com/uxjulia/CrossInk/commit/0cb91c3dd1c63f0559d4392fadc4a8340af5bdde
and
https://github.com/uxjulia/CrossInk/commit/4e6bd634b6ede51ff1a7107ba3e8efcd152bc6ee
for a timeout interval picker UI, the custom sleep time feature, and
migrate the existing setting in v1.3.0, `sleepTimeout`, to
`sleepTimeoutMinutes` (Thanks! @uxjulia)
3. Add a "Never" at the far right of the timeout interval picker

---

### 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: Julia <julia@uxj.io>
2026-05-30 08:28:07 -04:00
Julia 66d9e4403a fix: redesign X3's UTC offset picker so it is easier to use (#2205)
## Summary

* **What is the goal of this PR?**
Improve the X3 clock UTC offset picker so it is clearer which part of
the offset will change when the user presses up/down.

* **What changes are included?**
Replaces the single underlined `UTC +/- H:MM` string with separate
editable fields for the sign, hour, and minutes.
The editable fields are spaced out, outlined, and the currently selected
field is highlighted with a light gray background.

## Additional Context

* This is a render-only UI change in `ClockOffsetActivity`; it does not
change how UTC offsets are stored, clamped, saved, or applied.
* The selected field still cycles with Confirm, and up/down still
adjusts the active sign/hour/minute value.

Screenshot of **current design** showing the sign (+/-) highlighted:
<img width="264" height="396" alt="old utc offset"
src="https://github.com/user-attachments/assets/c88d69ea-089c-47da-b5b3-ed0ebda5b112"
/>


Screenshot of **new design** showing the sign (+/-) highlighted:
<img width="264" height="396" alt="utc offset fix"
src="https://github.com/user-attachments/assets/f51da8b3-9aab-4f48-a9d1-21c828c01499"
/>

---

### 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 >**_
2026-05-29 19:37:38 -04:00
Leopoldo Pla Sempere 174a24ba42 fix: complete Spanish, Catalan and Valencian strings (#2203) 2026-05-29 16:09:14 +03:00
Stefan Blixten Karlsson 9897025ded fix: swedish translation for strings added by #1337 and #2105 (#2197) 2026-05-29 11:13:57 +03:00
Uri TauberandZach Nelson f5bc554ae7 feat: add RTL support in epub and txt readers (#1700)
Co-authored-by: Zach Nelson <zach@zdnelson.com>
2026-05-29 03:25:17 -04:00
Angad Kandhari cc2079a578 docs: sync book.bin/section.bin version refs to source (#2169) 2026-05-29 03:07:57 -04:00
Djef0o 75a35f0578 chore: Update font size translation in French YAML (#2187) 2026-05-29 00:39:52 +03:00
Jeremy Klein f872f1f549 fix(docs): Remove fork with upstream attribution issues. (#2177)
Due to attribution issues with jpirnay's crosspoint fork, I am removing
it from our blessed list of forks.

I will not tolerate stolen code.
2026-05-27 22:21:10 -04:00
Justin Mitchell 02573dc592 chore: bump open-x4-sdk to 344c479 (restore X3 4-level grayscale LUT) (#2174) 2026-05-27 18:56:59 -04:00
muhasandmuhas dfefbfe9b5 feat(lang): Russian translate for new string (#2173)
Co-authored-by: muhas <mail@muhas.name>
2026-05-28 00:22:51 +03:00
36a3a0cc3a feat: epub bookmarks (#1337)
Co-authored-by: vedi0boy <nate@origin8publishing.com>
Co-authored-by: Uri Tauber <uritaube@gmail.com>
2026-05-27 15:52:01 -04:00
Uri Tauber bbb3e06eb1 fix: BOOK_CACHE_VERSION jump (#2161) 2026-05-26 22:13:09 +03:00
Vadim KaushanandClaude Sonnet 4.6 213972badc fix: navigate to TOC anchor when selecting sub-chapters (#1981)
Chapter selection previously only used the spine index, causing
navigation to always land on page 0 of the spine item. Sub-chapters that
share a spine file but differ by anchor (e.g. `chapter.xhtml#sec2`) were
silently ignored. Now the TOC anchor is passed through `ChapterResult`
and applied via the existing `pendingAnchor` mechanism.

## Summary

* This PR implements navigation to sub-chapters which didn't work
correctly previously. If a sub-chapter of the current top-level chapter
was selected, nothing happened. If a sub-chapter of another top-level
chapter was selected, reader switched to the beginning of the top-level
chapter.
* In addition, chapters now always start from a new page. This fixes
anchor to page calculation for the cases when the actual chapter content
doesn't fit on the page where the corresponding ToC anchor was found.

## Additional Context

* I might misuse `pendingAnchor` here which was previously used for
footnote navigation, please double check. I'm open to suggestions for
improvements.
* Note that the chapter selected by default when
`EpubReaderChapterSelectionActivity` opens is still wrong. I'm going to
fix this separately. This PR addresses only navigation to the selected
chapter.
* I tested this PR on my X4 and verified that navigation to a different
sub-chapter works correctly, both inside and outside the current spine.
* Some of the changes were borrowed from
https://github.com/crosspoint-reader/crosspoint-reader/pull/1455
---

### 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: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 12:14:32 -05:00
c5e861d71c feat: <sup> and <sub> support (#2131)
## Summary

* **What is the goal of this PR?** Support for `<sup>` and `<sub>` tags.

## Additional Context

This isn't my work, but @jpirnay 's (missing you here, man!). I migrated
his work from
https://github.com/jpirnay/crosspoint-reader/commit/bcd8c32cf26447ccc792cfedd2fdbfce4fee5210
with some micro-optimizations.

Screenshots: 

[Subscript-and-Superscript-Tests_ch2_p1_10pct_55632.bmp](https://github.com/user-attachments/files/28196936/Subscript-and-Superscript-Tests_ch2_p1_10pct_55632.bmp)

[Subscript-and-Superscript-Tests_ch3_p1_21pct_77473.bmp](https://github.com/user-attachments/files/28196937/Subscript-and-Superscript-Tests_ch3_p1_21pct_77473.bmp)


---

### 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 >**_

---------

Co-authored-by: jpirnay <jens@pirnay.com>
Co-authored-by: Julia <julia@uxj.io>
2026-05-26 12:38:40 -04:00
Zach Nelson 34e923d722 chore: Pin PNGdec lib_dep to 1.1.6 (#2154) 2026-05-26 01:06:24 -04:00
Jacob Latonis f4e7eaa198 feat: implement CSS enumeration through OPF directory when missing from manifest (#2148) 2026-05-25 21:23:19 -04:00
Zach Nelson 94d4b0c7bb ci: Cache PlatformIO packages between runs (#2142) 2026-05-25 17:03:10 -07:00
Zach Nelson 6dbfc29c7e test: Migrate unit tests to gtest, integrate with CI (#2144) 2026-05-25 15:51:53 -07:00
Jeremy Klein 4ee406897b feat: tiled grayscale rendering to drop the storeBwBuffer peak (#2106)
Tiled grayscale rendering to drop the storeBwBuffer peak largest
contiguous free block (the value that actually drives OOM on the C3)
from ~114 KB to ~82-90 KB.

This renders each grayscale plane band-by-band into a small (~8 KB)
scratch and
streams each band straight to controller RAM (community-sdk
writeGrayscalePlaneStrip), leaving the BW framebuffer intact. No save,
no
restore; controller RAM is re-synced for the next differential turn
directly
from the live framebuffer.

Three writers honor the active band target so per-band re-rendering
stays cheap
and correct:

- drawPixel (text) redirects writes to the band scratch and clips to it.
- renderCharImpl skips glyphs whose physical y-extent is outside the
band before
the bitmap decode (glyphIntersectsStrip), so the per-band re-render
doesn't
  pay N x glyph decode.
- DirectPixelWriter (images) writes the band scratch via getWriteTarget
instead
  of the framebuffer. Without this, image pixels wrote the live BW frame
directly and cleanup re-synced that corruption, leaving thin outlines
after
  navigating away from an image.

Controller specifics live in the SDK (X4 setRamArea windowing, X3 PTL);
the
reader checks supportsStripGrayscale() and is otherwise
controller-agnostic.

Measured on hardware (X4 and X3, text and images, visually correct):

- Grayscale scratch ~8 KB vs ~50 KB save; largest contiguous free block
held at
  full size during grayscale instead of dropping ~25-32 KB.
- X4 text page about +25 ms/page; X3 page time is dominated by its
intrinsic
  grayscale refresh, not tiling.

Depends on community-sdk #13 (the writeGrayscalePlaneStrip API). The
submodule
bump here points at that branch, so until #13 merges the submodule won't
resolve
from upstream and CI will fail there; keeping this a draft until then.
Will
rebase onto master and re-point the submodule to the merged SDK commit
once #13
lands.

Did you use AI tools to help write this code? partial
2026-05-25 18:03:01 -04:00
Joseph DiGiovanni 67973166e3 feat: Allow disabling side buttons in reader (#2105) 2026-05-25 18:00:27 -04:00
Jason HuebelandUri Tauber 50bc9325a4 docs: update USER_GUIDE.md for v1.1.0–v1.3.0 features (#2134)
Co-authored-by: Uri Tauber <uritaube@gmail.com>
2026-05-26 00:00:00 +03:00
WuTofu d736e52bdd fix: clear cache when deleting folders in FileBrowserActivity (#1892) 2026-05-25 16:47:35 -04:00
OrbisAI Security 91d17d9f48 perf: right-size (#2093) 2026-05-25 16:46:49 -04:00
Leopoldo Pla Sempere d5c28fa1f1 fix: refresh Catalan, add Valencian locale, and complete Spanish clock strings (#2063) 2026-05-25 16:41:48 -04:00
Leopoldo Pla Sempere f69650fb86 fix: validate OPF cover items as images (#2062) 2026-05-25 16:40:41 -04:00
Boris Faure e3298c6e43 docs: update presets on sd-card-fonts (#2110) 2026-05-25 16:31:38 -04:00
Chun Ming Lee 56ba9e4337 feat: add image auto-cropping in web epub optimizer (#2139) 2026-05-25 16:31:05 -04:00
Stefan Blixten Karlsson b56d3d853d fix: update Swedish translations and remove unused strings (#2090) 2026-05-25 16:30:22 -04:00
Zach Nelson e9120888fa refactor: Drop FsFile alias, use HalFile in downstream code (#2141) 2026-05-25 16:26:54 -04:00
Eloren1 d7797baff2 fix: Fill the gap under the screen for the progress bar (#2138) 2026-05-25 15:10:45 -04:00
Zach Nelson b53ac3d52c chore: Minor cleanup flagged by newer gcc (#2140)
## Summary

Minor cleanup flagged by newer gcc:
- Removed unused variables
- Removed unimplemented function declaration
- `static` -> `inline` to avoid per-TU duplication

---

### AI Usage

Did you use AI tools to help write this code? _**NO**_
2026-05-25 11:39:28 -05:00
Uri Tauber 38210be820 fix: SLEEP_TIMEOUT enum mismatch (#2137)
## Summary

* **What is the goal of this PR?** fixes #2132.

---

### 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-05-25 09:51:52 -05:00
Jeremy Klein 88b10c82a3 fix: serialize SdFat FsFile close through HalStorage mutex (#2135)
SdFat's SdSpiCard tracks SPI bus state with an unsynchronized
m_spiActive bool. When two tasks call into SdFat concurrently they can
confuse that state machine, ending with one task calling
SPIClass::endTransaction() against a paramLock the other task holds.
That trips FreeRTOS's xTaskPriorityDisinherit assert (tasks.c:5156,
pxTCB == pxCurrentTCBs[0]) and panics the system.

HalStorage already serialized every explicit method call via
storageMutex, but HalFile's destructor was `= default`, which let the
underlying SdFat FsFile destructor run close() outside any lock
(DESTRUCTOR_CLOSES_FILE=1). Any task that destructed a HalFile while
another task was mid-SD-op would race the unsynchronized state.

Move the locking discipline into HalFile::Impl::~Impl: an explicit
close() under StorageLock, then the FsFile member destructor's redundant
close() is a no-op. HalFile's special members can stay = default.

Switch storageMutex to xSemaphoreCreateRecursiveMutex so openFileForRead
and openFileForWrite can hold the lock while assigning to a HalFile&
out-param whose prior Impl needs locked teardown. Priority inheritance
still applies to recursive mutexes.

Also documented the no-bypass rule in CLAUDE.md: never call SdFat /
SdSpiCard / FsBaseFile / SDCardManager directly, never define
HAL_STORAGE_IMPL outside HalStorage.cpp.

Addresses my admittedly synthetic repro for #2047

Did you use AI tools to help write this code? partial
2026-05-24 21:48:33 -04:00
Jeremy Klein 19954aa1b5 refactor: route OTA version check through HttpDownloader (#2076)
With both OtaUpdater and HttpDownloader on esp_http_client
(https://github.com/crosspoint-reader/crosspoint-reader/pull/2074,
https://github.com/crosspoint-reader/crosspoint-reader/pull/2075),
checkForUpdate no longer needs its own client and event handler to fetch
the release JSON. It streams the response straight into
ReleaseJsonParser through a new HttpDownloader::fetchUrl(url,
DataCallback) overload, dropping the duplicate esp_http_client setup,
the HTTP_EVENT_ON_DATA handler, and the totalBytesReceived file global.

DataCallback hands body chunks to a callback without buffering. The
naive alternative, collecting the ~32KB JSON into a std::string, aborts
under -fno-exceptions: the growing allocation collides with the TLS
session's heap mid-fetch and operator new calls abort().

The OTA install path stays on esp_https_ota (flash-write streaming),
which has no HttpDownloader equivalent.

HttpDownloader.h must precede the lwip (esp_http_client) headers in
OtaUpdater.cpp, or Arduino/SdFat macros collide with lwip.


Did you use AI tools to help write this code? partial
2026-05-24 21:44:30 -04:00
Jeremy Klein 2823a4a2cd refactor: move HttpDownloader onto esp_http_client (#2075)
Based on the learnings from
https://github.com/crosspoint-reader/crosspoint-reader/pull/2074 , I
wanted to bring the same buffer savings to the rest of our HTTP Client
stack. That being said, HttpDownloader (fonts/OPDS) used the Arduino
HTTPClient.

HttpDownloader was the last consumer of the Arduino HTTPClient +
NetworkClientSecure stack. OtaUpdater already runs on esp_http_client,
so this drops the parallel HTTP/TLS implementation. It also fixes a
class of OPDS/font download failures: HTTPClient's setTimeout is uint16
and truncates, and its short per-read deadline killed slow or chunked
responses (the -11 / incomplete-data errors).

What changed:

- Rewrote fetchUrl/downloadToFile around esp_http_client with a
streaming open() -> fetch_headers() -> read() loop, manual redirect
following, and is_complete_data_received() as the completeness gate.
Body bytes go straight to the sink (OPDS parser stream, std::string, or
file), so nothing buffers the payload.

- HTTPS is now verified against the CA bundle instead of
NetworkClientSecure::setInsecure(). esp-tls is built with
CONFIG_ESP_TLS_INSECURE off, so an unverified handshake can't be set up
anyway; the model is public servers over verified https and local
servers over plain http (transport is chosen from the URL scheme).

** Self-signed https servers are no longer supported, by design. **

- timeout_ms is 60s; esp_http_client's timeout is uint32, so unlike
HTTPClient it doesn't silently truncate.

- HTTP buffers are 4096 (rx) / 1024 (tx). 4096 holds real OPDS server
headers; the GitHub release CDN sends more and logs a non-fatal
truncation warning, but the headers we read (Location, Content-Length)
come first and survive.

- Removed the now-unused UrlUtils::isHttpsUrl and a stale HTTPClient
comment in FontDownloadActivity.

Validated on device: OPDS browse and a 3.4 MB book download over
verified https, GitHub font downloads (crc-checked), redirect handling
matching curl, and slow/erroring servers surfaced correctly.


Did you use AI tools to help write this code? partial
2026-05-24 00:03:56 -04:00
Danila Yudin 929f290042 fix: close leaked resource handles (#2040) 2026-05-23 21:26:13 +03:00
Leopoldo Pla Sempere 0f021ea5f1 fix: normalize Wi-Fi spelling across remaining locales (#2094) 2026-05-23 21:01:12 +03:00
Justin Mitchell 99ab8b2772 fix: improves Edge case font/glyph handling (#2100) 2026-05-23 20:40:19 +03:00
Julia 7accc607af feat: ports hr tag rendering from crossink (#2117) 2026-05-23 09:47:16 -04:00
Julia f39ba7037f fix(settings): preserve quick resume timeout preference (#2101)
## Summary

### **What is the goal of this PR?**
This fixes an unintended settings side effect when cycling the `Sleep
Screen` option through `Quick Resume`.

Previously, selecting `Sleep Screen = Quick Resume` globally forced
`Quick Resume on Timeout = ON` and left it enabled even after the user
toggled `Sleep Screen` to another option within the same settings
session. Now the auto-enable behavior is scoped to the Settings screen
session:

- If `Quick Resume on Timeout` was already `ON` when entering Settings,
it stays `ON`.
- If it was `OFF`, selecting `Sleep Screen = Quick Resume` temporarily
turns it `ON`.
- If the user then switches away from `Quick Resume`, it turns back
`OFF`.

### **What changes are included?**

- Removes the global logic that permanently forced `Quick Resume on
Timeout` to `ON` whenever `Sleep Screen` was set to `Quick Resume`, even
if it was just due to toggling through the options.
- Adds Settings-screen session tracking so `Quick Resume on Timeout` is
only auto-enabled while the user has `Sleep Screen = Quick Resume`.
- Restores `Quick Resume on Timeout` back to `OFF` when the user
switches away, but only if it was `OFF` when they entered Settings.
- Preserves existing `ON` timeout preferences.
- Same behavior applies to the web settings

## Additional Context

- Tested this on device and via the settings UI
---

### 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  >**_
2026-05-21 21:06:51 -04:00
WuTofu 2dd491b62e refactor: unify book cache clearing for epub, txt, and xtc files (#1875) 2026-05-21 14:44:10 +03:00
Matteo Scopel dc404b41c7 chore: fix the Italian translation (#2095) 2026-05-21 13:08:50 +03:00
Jeremy Klein 4ffc2a7e7e fix: sleep from a WiFi activity instead of silent-rebooting (#2092)
Unify the two splash-skip signals (RTC silent-reboot flag, SD
seamless-sleep flag) into one BootResume enum driving a single switch.
Storage unchanged; behavior-preserving apart from the fix.

Holding power to sleep from a WiFi activity (Font Download, OPDS, web
server, Calibre, KOReader sync) rebooted to home instead of sleeping.
goToSleep() runs the outgoing activity's onExit(), and those activities
call silentRestart() to clear heap fragmentation, so the heap-defrag
reboot fired before deep sleep could start.

enterDeepSleep() now latches deepSleepInProgress before goToSleep();
silentRestart()/silentRestartToReader() no-op while it's set. Deep sleep
is a full chip reset on wake, so it already clears the fragmentation the
reboot existed for.



Did you use AI tools to help write this code? partial
2026-05-21 00:03:46 -04:00
Julia c44555007b feat(settings): modify "Page as Sleep Screen" to "Quick Resume" options (#2089)
## Summary

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

Adds a clearer Quick Resume sleep-screen flow. The previous “Page as
Sleep Screen” behavior is now exposed as a dedicated `Sleep Screen >
Quick Resume `option, with the timeout-only behavior controlled by a
renamed `Quick Resume on Timeout `setting.

**What changes are included?**

- Adds `Quick Resume` as a new `Sleep Screen` option.
- Renames the old `Page as Sleep Screen` setting to `Quick Resume on
Timeout`.
- Changes that setting’s choices from `Never / After Timeout / Always`
to `OFF / ON`.
- Makes `Quick Resume on Timeout = ON` equivalent to the old `After
Timeout` behavior.
- Makes `Sleep Screen > Quick Resume` equivalent to the old `Always`
behavior.
- Automatically forces `Quick Resume on Timeout` to `ON` when `Sleep
Screen` is set to `Quick Resume`.
- Renames internal setting references from `seamlessSleepScreen` to
`quickResumeSleepScreen`.
- Updates translations for the renamed setting label.

**Additional Context**

- This is mostly a settings/labeling restructure around existing
behavior, not a new rendering path.
- The runtime quick-resume behavior still uses the existing saved
framebuffer / last-screen sleep flow.
- Review focus areas:
  - Sleep entry behavior from manual sleep vs timeout sleep.
- The automatic dependency where selecting `Sleep Screen > Quick Resume`
sets `Quick Resume on Timeout` to `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? _**< YES >**_

---
**New `Quick Resume` option for `Sleep Screen` will automatically set
`Quick Resume on Timeout` to `ON`**:
<img width="480" height="800" alt="quick resume"
src="https://github.com/user-attachments/assets/94c553fd-a122-47a8-add9-f29694f55566"
/>

**Example where a different sleep screen setting like `Cover` can be
used in combination with the `Quick Resume on Timeout` setting**:
<img width="480" height="800" alt="cover + quick resume"
src="https://github.com/user-attachments/assets/dd18ce18-230b-4b78-808b-ac85f5e7d5d8"
/>
2026-05-20 22:43:48 -04:00
Vadim Kaushan d9aa5b4de1 fix: take orientation into account for border generation in ScreenshotUtil (#1977)
## Summary

Previously `ScreenshotUtil` used physical display size to draw a border
around the screen contents. Because of this, in landscape orientation
the border was shown as a broken square. This PR changes border drawing
to use logical screen size instead of a physical display size to take
orientation into account.

## Additional Context

* Tested on X4 in all 4 reading orientations. Behavior is now correct,
however it doesn't look perfect on my X4: the border is much closer to
the physical top side of the display than to the other sides. This might
be related to assembly variation during manufacturing, but it might as
well be related to the way a eink controller is connected to the display
(controller supports bigger display sizes, so an offset may be present).

---

### 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-05-20 15:48:06 -04:00
jpirnayandArthur Tazhitdinov a7aa4c55d8 fix: Prefer epub format over derived formats when downloading from opds server (#1480)
## Summary

* **What is the goal of this PR?** Prefer epub format over kepub or
other formats offered from an OPDS server
* **What changes are included?**

## Additional Context

Should address #1419 

---

### 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 >**_

---------

Co-authored-by: Arthur Tazhitdinov <lisnake@gmail.com>
2026-05-20 11:46:11 -05:00
Jeremy Klein 3800179595 fix: wire through silent restart clear resume state with sdk (#2033)
## Summary

After a silent reboot, there was a small window where the esp32 would
listen for button presses but the full refresh would hold the event
loop. This gave a UX experience where the silent reboot had completed to
the home screen, a user taps select (at any time during the process),
and they find themselves unexpectedly in a book.

## Additional Context

This must land after
https://github.com/crosspoint-reader/community-sdk/pull/11 and will need
the submodule SHA changes included in. 

---

### 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-05-20 08:58:43 -04:00
Jeremy Klein 0b9d1a7d23 fix: keep wifi OTA off the heap floor (#2074)
OTA install streams the full 5.8MB image over a multi-minute TLS session
while wifi/LWIP already holds the big internal arena. Measured on
device, the arena bottomed out at ~7.7KB free with the largest
contiguous block down to ~2.4KB; for 80% of the download there wasn't
even a contiguous 8KB block. It finishes on a clean heap but tips into
OOM for anyone carrying more pre-OTA fragmentation.

Two avoidable drains, both in OtaUpdater:

- The esp_http_client RX/TX buffers were 8192/8192 on both the version
check and the install. RX only has to hold response headers (bodies
stream through the parser / OTA writer) and TX only carries our GET, so
trim both to 4096/1024. 4096 still fits the github->CDN redirect
headers; the 512 IDF default truncates them, which is why they got
oversized in the first place.

- installUpdate fired the progress callback every ~100ms perform
iteration, waking the render task on every tick. Its framebuffer work
fights the TLS session for the same arena, and epd can't really repaint
faster than a percent anyway. Throttle it to whole-percent changes.

On device, combined: floor 7.7KB -> 19KB, worstcase contiguous block
2.4KB -> 21KB, zero sub 8KB iterations across the whole download.

KOReaderSyncClient already uses small buffers; HttpDownloader is on the
Arduino HTTPClient stack with no equivalent knob, so neither changed.



Did you use AI tools to help write this code? partial, heap-exploration
assisted by Claude.
2026-05-20 08:46:49 -04:00
Justin Mitchell 252a64fa97 chore: Add funding badge for contributors in README (#2072) 2026-05-19 22:42:45 -04:00
Jeremy Klein 75764bc6eb fix: 0 -> 1, even more deep-sleep fix (#2073)
setTimeout(0) on the serial could trigger a subtle but obnoxious
underflow.

Eat a milli, save a reset button.


Did you use AI tools to help write this code? no
2026-05-19 22:40:44 -04:00
Dave Allie 0ddefdc0c9 chore: Remove FUNDING.yml (#2071)
## Summary

* I am no longer maintaining or running the project, so avoiding
collecting money for nothing
* This will likely be updated by Justin in the near future with
different details
* See
https://github.com/crosspoint-reader/crosspoint-reader/discussions/2070
2026-05-20 11:33:32 +10:00
Eloren1andClaude Sonnet 4.6 41e6e15229 feat: Seamless sleep/wake screens for displaying book pages during deep sleep (#2064)
<img width="605" height="454" alt="image"
src="https://github.com/user-attachments/assets/bfd84afe-3b58-436e-9a5d-539af3ec3d4e"
/>

Actualized "Last" sleep screen setting from previous PRs, rebranded as a
~~`Seamless Sleep`~~ `Page as Sleep Screen` option with more
improvements.


https://github.com/user-attachments/assets/59029ba6-007e-4841-abfa-f680d7e98b79

---

New option: `Page as Sleep Screen` - `Never (default)`, `After Timeout`,
`Always`

When enabled, it seamlessly sleeps on timeout or power off, making a
fast refresh for the moon icon. When waking up, we still show the last
page, instead of the boot screen, making it fully seamless.

I tried different icons such as "refresh arrow" and others, but they
looked not as nice as 3 simple dots.

With this mode, the device turns off 4 seconds faster. And has 6 seconds
less delay when turning back on. Much more responsive.

Previously, even a 10-minute timeout sometimes wasn't enough, and I'd
worry about seeing the book cover. It's now easier to use a shorter
sleep timeout: if I get distracted during a reading session but don't
want to stop, the new screen is much more inviting to come back to.

---

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

---

Test v1.3.0 firmware.bin file
[download](https://github.com/user-attachments/files/28015193/firmware.zip)

Based on PRs #410 and #495

Closes #400, #1649

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-19 18:45:28 -04:00
Danila Yudin 082b5f295b docs: update delete endpoint reference (#1940)
## Summary

Update the `/delete` endpoint reference to match the current web server
handler.

## Details

The endpoint docs still described a `type` form field and the old
`Deleted successfully` response. The handler now accepts either `path`
for a single item or `paths` as a JSON array for multi-delete, infers
file versus folder from the SD card entry, and returns `All items
deleted successfully` when all deletes complete.

This updates the curl examples, parameter table, success response, and
error response list to match the implemented behavior.

## Validation

- Compared the documented parameters and response strings with
`CrossPointWebServer::handleDelete()`
- `git diff --check`
2026-05-19 17:39:04 -05:00
Jackson beb7876ccf feat: Make page turn naturally follow orientation (#2023) 2026-05-19 23:13:27 +03:00
Uri Tauber b69111bea5 refactor: Consolidate theme rendering into ThemeMetrics (#1868) 2026-05-19 22:50:26 +03:00
Jeremy Klein 86a9510957 fix: stabilize deep sleep wake on USB power (#2060)
When the device went into deep sleep while plugged into USB, a
power-button press would occasionally not wake it. The display held its
last frame, the chip stayed in deep sleep, and recovery required an
unplug + reset + hold-power cycle. On battery the symptom never surfaced
because the power button physically re-energises the chip.

Two peripherals were holding power domains alive across the deep sleep
boundary and interfering with the configured GPIO wake on the power
button:

1. HWCDC. Once Serial is initialized, the USB Serial/JTAG peripheral
keeps its power domain configured even with TX timeout at zero and no
host draining. Tear it down with Serial.end() in
HalPowerManager::startDeepSleep, gated by ENABLE_SERIAL_LOG to match the
Serial.begin site. This hit me if I was charging off my computer.

2. WiFi. enterDeepSleep had no WiFi teardown, so sleeping from any
network-using activity left the modem domain alive. Call
WiFi.disconnect(true) + WiFi.mode(WIFI_OFF) when WiFi is active. Wake
from deep sleep is effectively a chip reset, so no WiFi state needs to
survive. While this doesn't cause higher power drain, it apparently was
causing issues where I'd occasionally have the chip hang on sleep
transition from a wifi activity.

Confirmed on device.


Did you use AI tools to help write this code? partial
2026-05-19 12:04:38 -04:00
Jeremy Klein acc1ed4358 fix: guard DC writes in JPEGDEC MCU_SKIP path (#2058)
EIGHT_BIT_GRAYSCALE decode of a 3-component progressive JPEG calls
JPEGDecodeMCU_P with MCU_SKIP for Cb and Cr after every Y MCU. The
existing safe-pMCU patch redirects the wild pointer to &sMCUs[0] but
leaves the DC store unguarded, so each chroma skip overwrites the
just-decoded Y DC with the chroma DC predictor. Output reads sMCUs[0],
gets the trailing Cr DC (~0), and renders an all-black image.

Add `if (iMCU >= 0)` guards to the two pMCU[0] writes (main DC store and
successive-approximation update). The pointer redirect stays as the AC
wild-pointer defense; the new guards stop the silent corruption at
sMCUs[0]. The two fixes are independent and both required.

fixes the progressive 8bit grayscale jpeg regression in 1.3.0

Did you use AI tools to help write this code? partial
2026-05-19 09:52:29 -04:00
Uri Tauber 08461c08e7 fix: small QoL: return to the last selected menu location (#1629)
## Summary

* **What is the goal of this PR?** Small UX improvement to the Home
screen by preserving the last selected cursor position when returning to
it.

It supersedes #985 and #1103, which are both significantly outdated and
hundreds of commits behind master.

---

### AI Usage

Did you use AI tools to help write this code? _**< YES >**_
2026-05-19 08:41:39 -05:00
Justin Mitchell dac7fef49d fix: Update documentation with new features and links (#1991) 2026-05-19 07:32:30 +03:00
Jeremy Klein a14c8e762d perf: shrink HomeActivity cover cache from 48KB framebuffer to 16KB region (#2035)
On-device repro showed the cover snapshot pinning ~52KB of contiguous
heap (cloning the full 48KB framebuffer with malloc overhead). MaxAlloc
on Home was 61KB; nothing was leaving headroom for HTTPS, which needs
30-50KB contiguous for the mbedTLS handshake.

Add region-aware framebuffer helpers to GfxRenderer that translate a
logical rect through rotateCoordinates and copy only the byte range that
contains the rotated rect. HomeActivity records the tile rect it passes
to drawRecentBookCover and caches only that subregion.

Measured on device (X3, Portrait):

  Idle on Home    | Free 102K -> 139K  | MaxAlloc 61K -> 115K
  Mid-EPUB-read   | Free  81K -> 134K  | MaxAlloc 70K -> 115K
  Cover cache     |        ~52K -> ~16K (per allocation)

Works in all four orientations because the bounds helper samples the
four logical corners through the existing rotation, so the cached byte
range always covers the pixels the theme could have drawn into.

Savings will vary with theme, but should be significant across all.
2026-05-18 22:41:02 -04:00
Jeremy Klein a525606d7f fix: USB serial logs now flow on cold+warm boot without jiggle (#2034)
The "logs only flow if you unplug and replug the USB cable at the right
moment" symptom traced to two interacting problems with the ESP32-C3 USB
Serial/JTAG controller (HWCDC):

1. Serial.begin was gated on gpio.isUsbConnected(). That check sampled
USB state at one specific microsecond during boot. If USB enumeration on
the host hadn't completed by that moment (common after a reset that
auto- reconnects a moment later), Serial was never initialized and
stayed dead until the next boot where the timing happened to win.

2. HWCDC writes block for up to the configured TX timeout (default 250
ms) when the host has the port open but isn't actively draining — a
state the macOS USB CDC stack enters intermittently after reconnect. The
firmware then appears to hang on logging until a USB unplug+replug
cycles the peripheral and flushes the TX FIFO.

Fix: move the Serial init to the very top of setup() with a 250 ms stall
before Serial.begin (lets the USB peripheral power-on and host
enumeration complete on cold boot), and call logSerial.setTxTimeoutMs(0)
so writes drop bytes harmlessly when the host is slow instead of
stalling the firmware. Both warm reboot and cold power-on now produce
logs immediately.

Did you use AI tools to help write this code? partial
2026-05-18 22:26:25 -04:00
KemoNine df53faab91 feat: allow removing book from recent list (#2045)
## Summary

Add ability to long press 'confirm' on a book in the recent books list
to be prompted to remove it from the list.

---

### 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, Claude*

---------
2026-05-18 22:16:33 -04:00
Justin Mitchell f2adefe729 chore: Update open-x4-sdk submodule for faster page turns on x3 (#2055) 2026-05-18 21:41:40 -04:00
KemoNine 6a98c2d865 feat: add setting that allows removing books from recent list when read (#2043) 2026-05-18 21:13:41 -04:00
Justinian cfd3a381ed feat: X3 clock display with DS3231 RTC and NTP sync (#1612) 2026-05-18 21:06:56 -04:00
Jeremy Klein 151bf1dae4 fix: silent-restart on exit from KOReader auth and OTA update (#2036)
PR #1908 silent-restarts on exit from any wifi-using activity to defuse
LWIP/mbedTLS heap fragmentation, but two of the wifi-using paths slipped
through that audit:

  KOReaderAuthActivity (Settings -> KOReader sync -> Authenticate)
  OtaUpdateActivity    (Settings -> Check for update, back-out paths)

Both used WiFi.disconnect + WiFi.mode(WIFI_OFF) on exit and returned
control to Settings, leaving ~50KB of contiguous heap stranded for the
rest of the session.

Mirror the FontDownloadActivity pattern: if WiFi was activated,
disconnect and silentRestart. OTA's success path is unchanged:
SHUTTING_DOWN already calls plain ESP.restart() so the new firmware
boots normally; only the cancel/fail/no-update back-out paths now go
through silentRestart().


Did you use AI tools to help write this code? partial
2026-05-18 21:04:11 -04:00
KemoNine 06d28d6ffa feat: port crossink 'read book move' feature to crosspoint (#2032) 2026-05-18 12:39:50 -04:00
CaptainFrito 8a11f44571 feat: Themed reader menus (#1072) 2026-05-18 17:35:27 +03:00
WuTofu 061c7688a4 chore: add 3-minute sleep option (#1948) 2026-05-18 00:17:24 +03:00
muhasandmuhas 510ee10153 feat: update Russian translation (#2017)
Co-authored-by: muhas <mail@muhas.name>
2026-05-17 15:17:56 +03:00
zgredexandJustin Mitchell c6d116024c fix: harden EPUB optimiser UI gating, size reporting, and picker teardown (#1947)
Co-authored-by: Justin Mitchell <justin@jmitch.com>
2026-05-17 12:12:56 +03:00
Matteo Scopel 85e08f9a93 feat: add the Domitian font family (#2016) 2026-05-17 12:08:16 +03:00
Justin Mitchell 0af0ad5a17 fix: bump open-x4-sdk to clear grayscale state after AA cleanup (#2022)
Pulls in community-sdk PR #9, which clears inGrayscaleMode inside
cleanupGrayscaleBuffers() after the restored BW frame is written back
into RED RAM. Without this, the next BW page turn would still see the
flag set and trigger a redundant grayscaleRevert() refresh, producing
visible ghosting on the X4 with text anti-aliasing enabled.

Regression introduced by SDK commit 0a8ada2 (factory LUT grayscale
support), which removed a redundant inGrayscaleMode guard in
grayscaleRevert() and so caused the cleanup to actually run for the
first time.

Bypassing rules to avoid this going stale and all nightly builds being broken for x4 users
2026-05-17 03:45:11 -04:00
KemoNine 93e81daf41 fix: prune books missing form sd card in recent books list (#1959) 2026-05-16 22:03:49 +03:00
mvidelatraduc a3e51f9b1e chore: Update spanish.yaml (#2011) 2026-05-16 21:55:27 +03:00
Blue 90d4c885e1 fix: update URL-encoded image during EPUB optimization (#1985)
## Summary

* **What is the goal of this PR?**  
Fix EPUB optimization when XHTML image references are URL-encoded.

* **What changes are included?**  
The optimizer already converts image files to `.jpg`, but XHTML files
could still reference the original URL-encoded image path, for example:

```html
<img src="images/wensday%201%20full%202.png">
````

The optimized EPUB then contained the converted file:

```text
images/wensday 1 full 2.jpg
```

but the XHTML still pointed to the old `.png`, so CrossPoint failed to
extract/render the image.

The issue was that the previous replacement logic matched only the plain
filename form, such as:

```text
wensday 1 full 2.png
```

but not the URL-encoded form:

```text
wensday%201%20full%202.png
```

This PR updates XHTML image `src` attributes through the existing
DOMParser pass by decoding and resolving the image path before matching
it against renamed images.

After this fix, the optimized EPUB correctly rewrites the XHTML image
reference to the generated `.jpg`, and the image renders correctly.

---

### 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**_

---

Please let me know if you have questions,
Thank you!
2026-05-15 21:30:49 -05:00
Kira ee947b06d1 fix: prevent card overflow on screens (#1943)
## Summary

prevent card overflow

## Additional Context

<img width="1920" height="1080" alt="bug"
src="https://github.com/user-attachments/assets/df84e233-908e-4ce1-8289-d0e9b579bc13"
/>
<img width="1920" height="1080" alt="Bug"
src="https://github.com/user-attachments/assets/cfd20f51-6421-4271-9a62-9c1987cc0dd0"
/>
<img width="1920" height="1080" alt="fix"
src="https://github.com/user-attachments/assets/4fbe83fe-5376-4393-bd45-a825f24e19e3"
/>
<img width="1920" height="1080" alt="fix2"
src="https://github.com/user-attachments/assets/4397848e-d8fa-4c51-ab4d-c32b2fcf33d1"
/>


---

### AI Usage

Did you use AI tools to help write this code? _**NO**_
2026-05-15 21:29:38 -05:00
marcinoktawian 28b907321e fix: use power button held time for shutdown logic (#1890)
## Summary

* **What is the goal of this PR?**
Fix incorrect power button long-press detection during shutdown/wake
verification by introducing dedicated power button timing logic.
* **What changes are included?**
* Added getPowerButtonHeldTime() to HalGPIO as a wrapper over input
manager logic
* Replaced generic getHeldTime() usage with power-button-specific timing
in verifyPowerButtonWakeup()
* Ensures shutdown/wake decision is based only on actual power button
hold duration, not any-button timing
  * Minor header update for new API exposure in HalGPIO.h
## Additional Context

This fixes a bug where holding another button while briefly pressing the
power button could incorrectly trigger shutdown behavior due to shared
timing state (getHeldTime()).

The change isolates power button timing to prevent cross-button
interference and makes shutdown logic reliable during multi-button
interactions.

No behavioral changes are expected outside of power-button handling
logic.

**Dependencies**
- SDK PR: https://github.com/crosspoint-reader/community-sdk/pull/3

This PR requires the `community-sdk` submodule to be updated after the
SDK change is merged.

- Fixes: #1881

---

### AI Usage
Did you use AI tools to help write this code? _**PARTIALLY**_
2026-05-15 21:28:52 -05:00
Stefan Blixten Karlsson 77afea4d95 feat: Add swedish hyphenation (#1637)
## Summary

* Add swedish hyphenation using scripts/update_hypenation.sh
* Add hyphenation test data using the Swedish translation of Andy Weir's
Project Hail Mary

---

### 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-05-15 21:27:52 -05:00
WuTofu 2f342508bc fix: several QoL updates for SD font's UI (#1965)
## Summary

* **What is the goal of this PR?**  
Improve the UI based on feedback from someone on discord

> Downloading ALL fonts feature.
> 1.1 Disable sleep when downloading, in my case went directly to sleep
just right after downloading.
> 1.2 It would be great to have and overall progress indicator as we
only have the indication of each font family
> 1.3 Any cancel or pause function might come in handy in case battery
is running out and then resume or retry with pending fonts

* **What changes are included?**  
- Now the UI can show overall progress across every file being
downloaded in the batch, not just progress inside the current family.
- Extended `HttpDownloader::downloadToFile()` to accept a cancel flag
and abort the download.
- Rendered a cancel button in the font download UI while a download is
in progress.
- `preventAutoSleep()` in `FontDownloadActivity.h` now returns true for
`state_ == COMPLETE` and `state_ == ERROR` in addition to
`LOADING_MANIFEST` and `DOWNLOADING`

## Additional Context

Not very satisfied with how `HttpDownloader.cpp` is right now, might try
to refactor it after v1.3.0

---

### 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-05-15 21:25:48 -05:00
luca 7bb1f7ed76 fix: update Italian translation (#1970)
## Summary
* **What is the goal of this PR?** Update the Italian translation.
* **What changes are included?** Took the latest `english.yaml` as
reference and updated `italian.yaml` accordingly, translating new
strings and revising existing ones where needed. Specific changes can be
inspected from the diff.

## Additional Context
* Nothing special to flag — happy to adjust any wording the reviewer
disagrees with.

---
### 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**_ — Claude
provided a first-pass draft; I revised and rewrote a substantial portion
by hand.
2026-05-15 20:52:42 -05:00
Jeremy Klein 7acc31bc34 fix: silent-reboot on wifi activity exit to clear heap fragmentation (#1908)
WiFi/LWIP/netif teardown scatters long-lived allocations across the
heap, leaving ~50KB of contiguous space unrecoverable without a reboot.

Reboot the SoC on exit from any wifi-using activity to guarantee a clean
heap. An RTC_NOINIT flag survives the reboot and tells setup() to skip
the boot splash and route the user back where they came from:
  - File transfer / Calibre / OPDS / Font download -> home
  - KOReader sync -> currently-open EPUB

Activities check WiFi.getMode() before rebooting, so backing out of the
network mode menu without joining doesn't trigger a cycle. KOSync also
esp_wifi_stop()s after the sync result so the radio is off while the
user reads it; full teardown happens at the reboot.


## Additional Context

The silent reboot skips the booting splash screen - it visibly looks
like a screen refresh. This does cause a disconnection/reconnection blip
for developers actively pulling logs over serial, but `pio device
monitor` and the like successfully reconnect and feed in the early boot
serial.
as an example: 
```
[256676] [DBG] [ACT] Exiting activity: KOReaderSync
[256706] [DBG] [MAIN] Silent restart (target=reader)

ESP-ROM:esp32c3-api1-20210207
Build:Feb  7 2021
rst:0xc (RTC_SW_CPU_RST),boot:0xf (SPI_FAST_FLASH_BOOT)
Saved PC:0x403872bc
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fcd72a0,len:0x990
load:0x403cbf10,len:0xac8
load:0x403ce710,len:0x4d28
entry 0x403cbf10
[22] [INF] [MAIN] Hardware detect: X4
[29] [SD] SD card detected
[43] [DBG] [CPS] Settings loaded from file
[58] [DBG] [KRS] Loaded KOReader credentials for user: jeremydk
[69] [DBG] [OPS] Loaded 1 OPDS servers from file
[69] [DBG] [UI] Using Lyra theme
[70] [DBG] [MAIN] Starting CrossPoint version 1.2.0-dev-detached-bde75787

...

[203] [DBG] [ACT] Entering activity: Reader
[211] [DBG] [EBP] Loading ePub: /Halting State - Charles Stross.epub
[221] [DBG] [BMC] Loaded cache data: 51 spine, 41 TOC entries
[246] [DBG] [CSS] Loaded 41 rules from cache
[247] [DBG] [EBP] Loaded ePub: /Halting State - Charles Stross.epub
```
---
### 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-05-15 19:27:54 -05:00
938ca3fac1 chore: Update version to 1.3.0 (#1827)
## Summary

This release adds SD card fonts — the most-requested feature since
launch — brings the X3 to first-class status, redesigns the on-screen
keyboard, overhauls OPDS, and ships SD-card firmware updates. 144
changes from 53 contributors, 32 of whom are new to the project.

**🔠 SD Card Fonts**
Custom fonts are here. A complete font subsystem lets you install and
use fonts beyond the three built-in families. A new `.cpfont` binary
format packs multiple styles (regular, bold, italic, bold-italic) into a
single file per size, with on-demand glyph loading from the SD card. A
two-pass prewarm renderer bulk-reads glyphs per page, achieving
near-flash performance for Latin text and viable CJK rendering. Fonts
can be downloaded over WiFi directly from the device, uploaded via the
web interface, or copied manually to the SD card. The build pipeline
ships a 17-family font library (serif, sans, mono, accessibility) with
CI distribution via a dedicated crosspoint-fonts repository. As a bonus,
CJK characters no longer get spurious hyphens at line breaks, and an
advance-table cache eliminates 30+ second stalls during CJK section
indexing.

**📱 X3 Comes of Age**
The X3 graduates from initial bring-up to a proper target. Grayscale
antialiasing is sharper, EPUB images render correctly, OTA updates work,
and sleep screen dimensions are dialed in. The headline addition:
gyroscope-based tilt page turning via the QMI8658 IMU — tilt the device
to turn pages hands-free. SD-card firmware update support and X3
bootloader compatibility mean users can update without a USB connection.

**⌨️ Redesigned On-Screen Keyboard**
The keyboard has been completely redesigned with improved layout, better
key feedback, and a fix for the space key barely moving the cursor. Text
entry across WiFi setup, OPDS search, and KOSync login is noticeably
smoother.

**👁️ Focus Reading**
A new reading mode bolds the initial characters of each word (similar to
Bionic Reading) to create artificial fixation points, helping improve
reading speed and focus. The bolding ratio is 45%, with a minimum of 1
character and a maximum of 9, applied dynamically during indexing.

**📚 OPDS Overhaul**
OPDS gains in-catalog search with next/prev page navigation, support for
multiple servers, correct handling of relative paths and query
parameters (fixing CopyParty compatibility), and KOReader-compatible
download filenames.

**🔤 Text Rendering Refinements**
Combining marks (diacritics) now use font metrics for positioning
instead of heuristics, proportional numeral spacing is supported, and
differential rounding eliminates uneven inter-glyph gaps. Hyphenation
now recognizes ISO 639-2 language codes, nested block-level CSS styles
are tracked correctly, and horizontal CSS insets are capped at 2em to
prevent runaway margins. Bookerly has been replaced with Noto Serif for
licensing reasons.

**🎨 New Theme: RoundedRaff**
A new rounded theme joins the theme picker, with fixes for sleep cover
crop grid artifacts.

**🔋 Battery & Power**
Battery percentage smoothing on the X4 eliminates jittery readings. A
short press on the power button can be set to trigger a manual screen
refresh — handy for clearing ghosting.

**📶 WiFi & Networking**
WiFi connections now self-heal from transient drops without manual
intervention, and a dBm signal strength indicator appears during web
server sessions. WiFi networks can be edited directly from the web UI.

**🔄 KOSync**
Reading position sync is significantly more accurate. The old
character-offset approach frequently landed on the wrong paragraph after
syncing between devices — the new xpath-based mapping syncs at the
paragraph level, matching KOReader's own behavior. A separate fix
switches the HTTP layer to `esp_http_client`, and the reader now
releases ~65KB of EPUB heap before the TLS handshake — together these
eliminate the out-of-memory crashes that plagued KOSync on large books.

**🛡️ Stability**
Two memory leaks patched, a wild pointer crash in JPEGDEC MCU_SKIP
handling fixed, boot loops with large XTC files eliminated, legacy XTC
headers supported, the OTA updater now streams GitHub release JSON
instead of buffering it in RAM, and a JPEG downscaler y-axis scale
factor bug is corrected.

**🌐 Languages**
Slovenian is new. Russian, Ukrainian, Swedish, Italian, and Spanish
translations received significant updates.

---

Also in this release: **SD-card firmware updates without USB**, **file
extensions in the file browser**, **full path bar navigation**,
**end-of-book navigation improvements**, **XTC status bar**, **smarter
"Cover + Custom" sleep screens**, **set sleep cover from the BMP
viewer**, **orientation-aware popups**, **page turn buttons that follow
orientation**, **long-press delete for directories**, **context-aware
screenshot filenames with book title**, **crash reason displayed on
boot**, **empty line rendering in the TXT reader**, **wallpaper recency
buffer to prevent clustering**, **font family deletion from the
device**, **next/prev labels in the BMP viewer**, **non-breaking space
justification fix**, **README guidance for USB-locked third-party Xteink
units**, and a long tail of web UI polish, i18n memory optimizations,
and code quality improvements.

## What's Changed

### Features

* feat: add SD card font support with on-device download and web
management by @adriancaruana, @znelson, @itsthisjustin, @jpirnay, and
@mcrosson
* feat: Initial support for the x3 by @itsthisjustin in
https://github.com/crosspoint-reader/crosspoint-reader/pull/875
* feat: X3 grayscale antialiasing improvements by @juicecultus in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1607
* feat: X3 gyroscope-based tilt page turning via QMI8658 IMU by
@juicecultus in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1636
* feat(update): SD-card firmware update + X3 bootloader compatibility by
@eunchurn in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1786
* feat: self-heal from transient WiFi loss, add dBm indicator during
WebServerActivity by @jeremydk in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1780
* feat: edit wifi networks in webui by @osteotek in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1743
* feat: add OPDS search support & next/prev page navigation by @rxmmah
in https://github.com/crosspoint-reader/crosspoint-reader/pull/1462
* feat: Support for multiple OPDS servers by @osteotek in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1209
* feat: Adjust Navigation at End of Book by @nscheung in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1425
* feat: Display file extensions in File Browser by @CaptainFrito in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1019
* feat: show full path bar in file browser by @zgredex in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1411
* feat: enable manual screen refresh on power button short press by
@bdeshi in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1626
* feat: Rework "Cover + Custom" sleep screens to show covers only when
currently reading by @iandchasse in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1256
* feat: Set sleep cover from BMP viewer by @el in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1104
* feat: show crash reason on boot by @ngxson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1453
* feat: Support for proportional numeral spacing by @znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1414
* feat: add orientation-aware popups for reader activities by @mrtnvgr
in https://github.com/crosspoint-reader/crosspoint-reader/pull/1428
* feat: smooth battery percentage for x4 by @jonvex in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1635
* feat: context-aware screenshot filenames with book title by
@jonstieglitz in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1589
* feat(theme): add roundedraff theme and fix sleep cover crop grid
artifacts by @bunsoootchi in
https://github.com/crosspoint-reader/crosspoint-reader/pull/918
* feat: Page turn button orientation change by @mchuck in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1069
* feat: Status bar for XTC files by @leecming82 in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1849
* feat: enhance long press action to delete both files and directories
by @WuTofu in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1803
* feat: Added Slovenian translation by @thehijacker in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1551
* feat: focus reading by @vjapolitzer in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1670
* feat: add next / prev labels to bmp viewer by @Telemaniaka in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1852
* feat: add font family deletion functionality by @WuTofu in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1919
* feat: separate into "Download All" and "Update All" in font manager by
@WuTofu in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1955
* feat: verify CRC32 checksum for font files by @WuTofu in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1904
* feat: increase default weight of Bitter font for improved rendering by
@uxjulia in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1922
* feat: allow unnamed intervals by @steka in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1903

### Fixes

* fix: epub images not rendering correctly on x3 by @itsthisjustin in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1572
* fix: OTA update on x3 and progress bar on x4 and x3 by @itsthisjustin
in https://github.com/crosspoint-reader/crosspoint-reader/pull/1805
* fix: boot looping when opening large XTC files by @itsthisjustin in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1648
* fix: Wild pointer crash in JPEGDEC MCU_SKIP handling by @itsthisjustin
in https://github.com/crosspoint-reader/crosspoint-reader/pull/1627
* fix: two small memory leaks by @Uri-Tauber in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1628
* fix: use esp_http_client for KOSync to prevent TLS OOM on ESP32-C3 by
@trilwu in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1381
* fix: Read GH release JSON as stream in OTA updater by @znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1810
* fix: support legacy XTC file headers where pageTableOffset=48 by
@uxjulia in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1816
* fix: Use font metrics for combining mark positioning by @znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1310
* fix: Use differential rounding for consistent inter-glyph spacing by
@znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1413
* fix: Support hyphenation for EPUBs using ISO 639-2 language codes by
@znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1461
* fix: Track block style stack for nested styles by @daveallie in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1582
* fix: cap per-side horizontal CSS inset at 2em by @rhoopr in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1694
* fix: increase loadable epub size by @CSCMe in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1638
* fix: Switch to xpath map for paragraph level syncing in KOSync by
@itsthisjustin in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1686
* fix: free Epub RAM and simplify KOSync navigation via ActivityManager
by @wylanswets in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1860
* fix: improve KOSync bidirectional position matching accuracy by
@wylanswets in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1897
* fix: Fix failing very first wifi connection attempt by @jpirnay in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1521
* fix: avoid skipping chapter after screenshot by @Mraulio in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1625
* fix: back navigation from BMPViewer by @Telemaniaka in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1597
* fix: Fix ghosting on exit of BMPViewer by @jpirnay in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1432
* fix: make footnotes consider orientation for gutters by @Telemaniaka
in https://github.com/crosspoint-reader/crosspoint-reader/pull/1665
* fix: footnote link text by @steka in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1666
* fix: Erroneous navigation with long filenames in footnote links by
@CSCMe in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1723
* fix: prevent wallpaper clustering with 16-entry recency buffer by
@zgredex in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1606
* fix: webserver /delete API backward compatibility by @DianaNites in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1475
* fix: relative opds paths and query param with copyparty by @philips in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1535
* fix: use same file name as KOReader for OPDS downloads by @spfenwick
in https://github.com/crosspoint-reader/crosspoint-reader/pull/1286
* fix: pressing space barely moves input cursor (#1729) by @pablohc in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1733
* fix: keyboard feedback #1644 by @pablohc in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1697
* fix: pluralize folder/file counts correctly in file list summary by
@fain182 in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1701
* fix: rendering bug of scrollbar in RoundedRaff theme by @Uri-Tauber in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1814
* fix: two roundedraff bugs by @Uri-Tauber in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1851
* fix: overlap in download font list layout by @pablohc in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1900
* fix: remove duplicate 'Download Fonts' menu entry and improve
navigation by @zgredex in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1893
* fix: Add common ligatures to SD font conversion ranges by @znelson
* fix: capture instantiateVariableFont return value by @jpirnay in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1911
* fix: Roundraff theme home menu offset with no recent books by @znelson
in https://github.com/crosspoint-reader/crosspoint-reader/pull/1845
* fix: Missing navigation button labels in Roundedraff theme by
@Uri-Tauber in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1905
* fix: gracefully resolve fonts missing variants by @Uri-Tauber in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1921
* fix: distribute justifyExtra to non-breaking space tokens by
@prawnwhoyawns in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1783
* fix: remove percent rendering from activities by @mcrosson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1901
* fix: Restore performance in fontconvert_sdcard.py by @znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1924
* fix: Prepare SD card font caches from txt reader by @znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1973
* fix: make script help paths lightweight by @sabraman in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1937
* fix: Replaced Bookerly with Noto Serif for licensing reasons by
@znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1736
* fix: incorrect y-axis scale factor in jpeg nearest-neighbor downscaler
by @WuTofu in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1807
* fix: display empty lines in txt reader by @Uri-Tauber in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1841
* fix: short-press power action triggered after screenshot combo release
by @pablohc in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1853
* fix: correct Russian auto-turn translations by @a-ignatev in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1566
* fix: Update Ukrainian translations for footnotes (issue 1409) by
@mirus-ua in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1585
* fix: missing swedish translations by @steka in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1667
* fix: Add swedish keyboard translations by @steka in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1726
* fix: swedish translations by @steka in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1762
* fix: swedish translation by @steka in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1829
* fix: swedish translation by @steka in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1888
* fix: Polish translation by @th0m4sek in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1909
* fix: Ukrainian-translation by @KymAndriy in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1946
* fix: Ukrainian translation by @KymAndriy in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1939
* fix: python requirements files by @steka in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1768
* fix: missing requirement by @steka in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1896
* fix: Use LOG_ macros in loc functions by @znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1794

### Internal

* refactor: redesign on-screen keyboard by @pablohc in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1644
* refactor: replace picojpeg with JPEGDEC for cover art conversion by
@jpirnay in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1517
* refactor: Refactor drawArc / fillArc for faster execution by @jpirnay
in https://github.com/crosspoint-reader/crosspoint-reader/pull/1540
* perf: replace i18n pointer tables with offset tables, strip unused
strings by @jpirnay in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1408
* refactor: Store only unique localization strings in offset buffers by
@znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1802
* refactor: Move language setting into JSON settings by @znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1796
* refactor: Use C++20 'requires' in ActivityResult constructor by
@znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1420
* refactor: Use default member initializers for JpegContext and
PngContext by @znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1435
* refactor: logPrintf and predefined log level strings by @CSCMe in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1546
* refactor: RAII scoped open/close for ZipFile by @znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1433
* refactor: Deduplicated BMP header writing in Xtc by @znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1439
* refactor: Added shared XML parser teardown helper by @znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1438
* refactor: Removed redundant FsFile close() calls by @znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1434
* refactor: Deduplicate battery drawing code and fix Lyra charging
indicator by @znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1437
* refactor: Deduplicate Roundraff battery drawing by @znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1847
* refactor: Simplify sort in GfxRenderer::fillPolygon by @znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1817
* refactor: Avoid vector for page turn rates list by @znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1818
* refactor: Use std::size instead of sizeof/sizeof by @znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1819
* refactor: Use fixed-size integers for BookMetadataCache data by
@znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1844
* refactor: Simplify isReaderActivity bookkeeping by @znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1838
* refactor: Simplify XtcReaderActivity with detectPageTurn by @znelson
in https://github.com/crosspoint-reader/crosspoint-reader/pull/1837
* refactor: change ukrainian translation to adaptation and add missing
lines by @KymAndriy in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1828
* chore: drop JPEGDEC patch in favour of upstream fix by @martinbrook in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1465
* chore: clang-format.fix.ps1 script: Add .venv to list of path
exclusions by @jpirnay in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1515
* chore: Updating sleep screen dimensions for X3 by @jensechu in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1688
* chore: Clarify X3 RTC in SCOPE.md by @znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1687
* chore: Improved Italian translations by @znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1685
* chore: change ukrainian translation to adaptation by @KymAndriy in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1684
* chore: Update spanish.yaml by @mvidelatraduc in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1717
* chore: One Italian translation tweak by @znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1718
* chore: git pre-commit hook for format fix by @osteotek in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1730
* chore: Update SDK to fork in CrossPoint org by @znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1836
* chore: Added RAM to firmware_size_history.py script by @znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1830
* chore: Updated docs to reflect DESTRUCTOR_CLOSES_FILE=1 by @znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1878
* feat: cap compressed group size at 64 KB by @jpirnay in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1913
* fix: build-script bug fixes for fontconvert{,_sdcard}.py by @jpirnay
in https://github.com/crosspoint-reader/crosspoint-reader/pull/1910
* feat: include short SHA in CROSSPOINT_VERSION by @osteotek in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1728
* feat: show long branch names by @steka in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1727
* feat: enable pio build cache by @Uri-Tauber in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1769
* style: put page name first in browser titles by @fain182 in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1703
* style: unify page headers across web UI by @fain182 in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1702
* style: move file type badges into Type column by @fain182 in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1793
* style: align action buttons vertically with page title by @fain182 in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1795
* docs: Update README with firmware flashing instructions by @ryneches
in https://github.com/crosspoint-reader/crosspoint-reader/pull/1654
* docs: fix typos by @kianmeng in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1705
* docs: update README.md to reflect the current state of crosspoint by
@Uri-Tauber in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1812
* docs: Add documentation for USB-locked Xteink devices by
@itsthisjustin in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1990
* docs: expand first use of OPDS acronym and provide a wikipedia link by
@sizezero in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1824
* docs: fix KOReader sync guide link by @sabraman in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1930
* docs: fix hyphenation updater script name by @sabraman in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1931
* fix: sd font download urls in docs by @mcrosson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1945
* fix: sd font folder paths in documentation by @mcrosson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1944
* chore: Add verbose mode to build-sd-fonts.py by @znelson in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1923

## New Contributors
* @a-ignatev made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1566
* @CSCMe made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1546
* @thehijacker made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1551
* @Telemaniaka made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1597
* @Mraulio made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1625
* @rxmmah made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1462
* @bdeshi made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1626
* @DianaNites made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1475
* @ryneches made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1654
* @zgredex made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1411
* @jonvex made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1635
* @KymAndriy made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1684
* @jensechu made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1688
* @kianmeng made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1705
* @philips made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1535
* @fain182 made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1701
* @mvidelatraduc made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1717
* @bunsoootchi made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/918
* @rhoopr made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1694
* @spfenwick made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1286
* @trilwu made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1381
* @jonstieglitz made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1589
* @uxjulia made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1816
* @mchuck made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1069
* @sizezero made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1824
* @leecming82 made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1849
* @jeremydk made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1780
* @WuTofu made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1803
* @wylanswets made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1860
* @sabraman made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1930
* @prawnwhoyawns made their first contribution in
https://github.com/crosspoint-reader/crosspoint-reader/pull/1783
* @mcrosson made their first contribution as co-author on SD card font
support

**Full Changelog**:
https://github.com/crosspoint-reader/crosspoint-reader/compare/1.2.0...release/1.3.0

---------

Co-authored-by: Justin Mitchell <justin@jmitch.com>
Co-authored-by: Chun Ming Lee <95391408+leecming82@users.noreply.github.com>
Co-authored-by: Uri Tauber <uritaube@gmail.com>
2026-05-15 15:51:43 -05:00
Uri TauberandZach Nelson 0fbe6ff469 fix: update README.md to reflect the current state of crosspoint (#1812)
## Summary

As noted in
[#1680](https://github.com/crosspoint-reader/crosspoint-reader/discussions/1680#discussioncomment-16661106),
the README hasn't been updated in a while and has fallen behind the
actual firmware. This PR brings it up to date.

Beyond the feature list, I added a section acknowledging community forks
worth knowing about. I also took some deliberate editorial choices
around how CrossPoint is framed — I think it has the potential to be
more than just "an alternative Xteink firmware", and the wording
reflects that.

A note on process: I wrote the bulk of the text myself, but used AI
tools to scan the codebase and catch features I might have missed, and
to clean up my English (I'm fluent but not a native speaker). If any
line reads as unnatural or AI-sounding, please flag it — I'd rather fix
it than leave it.

---

One thing outside the scope of this PR: I think the cover photo could
use a refresh, ideally replaced with a small gallery showing different
CrossPoint screens. If you have a professional camera and an Xteink
device and want to help with that, let me know.

---------

Co-authored-by: Zach Nelson <zach@zdnelson.com>
2026-05-15 15:38:22 -05:00
Justin Mitchell 43b20bd8be fix: Add documentation for USB-locked Xteink devices (#1990)
Document the Xteink Unlocker tool requirement for third-party purchased
xteink units that ship with USB flashing locked. Include warnings about
bricking risks when flashing unsupported firmwares (e.g. Papyrix) on
locked devices, as they may permanently lock the device with no recovery
path.
2026-05-15 10:44:47 -05:00
Zach NelsonandJustin Mitchell b186529120 fix: Prepare SD card font caches from txt reader (#1973)
## Summary

SD card font fixes:
- `TxtReaderActivity` needs to call `renderer.ensureSdCardFontReady` to
build the advance lookup table to support rendering with SD card fonts.
This revealed that `TxtReaderActivity` was inconsistently performing
layout with `getTextWidth`, when the renderer actually uses
`getTextAdvanceX`, which can lead to minor inconsistencies in alignment.
- Avoid allocating one big `allText` string in
`ParsedText::layoutAndExtractLines`. Instead, pass the vector of word
strings directly to `SdCardFont::buildAdvanceTable`, where the algorithm
just needs to iterate codepoints anyway.

---

### 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: Justin Mitchell <justin@jmitch.com>
2026-05-15 09:50:41 -05:00
IjonFryderykandClaude Opus 4.6 5fa5a71ba2 feat: Add Polish hyphenation support (#1590)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-14 16:32:59 +03:00
Zach Nelson aa43bd3569 chore: Removed unused icon header files (#1975)
## Summary

Deleted two unused header files containing binary icon data.

---

### 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-05-14 07:34:35 -05:00
Zach Nelson 8377ac9e31 refactor: Added utils for non-throwing memory allocation and scoped cleanup (#1832)
## Summary

Pared down version of #1418.

Following up on b5df6cb2b5. Added
lib/Memory/Memory.h with:
- `makeUniqueNoThrow<T>` a `nothrow` wrapper for `std::make_unique` that
return `nullptr` on OOM instead of calling `abort()` (the behavior of
bare `new` with `-fno-exceptions`)
- `ScopedCleanup` a helper to call a cleanup lambda on scope exit.

These utilities help to write code that handles OOM scenarios
gracefully, and consistently cleans up resources on scope exit.

JpegToBmpConverter.cpp has been converted to use these utilities. Other
files can be converted later.

This will simplify some of the SD card font resource management in
#1327.

---

### 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-05-13 21:19:47 -05:00
Fabio Barbonanddrbourbon 71518b3336 fix: KOSync authentication with Calibre-Web-Automated (#1951)
## Summary

* **What is the goal of this PR?** Fix regression of
Calibre-Web-Automated authentication
* **What changes are included?** Trivial fix using ESP HTTP Client
primitives

## Additional Context

Calibre-Web-Automated KOSync stopped working after refactoring HTTP
client to use `ESP HTTP Client`, resulting in a _Server error (try again
later)_ error.

---

### AI Usage

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

---------

Co-authored-by: drbourbon <fabio@MacBook-Air-di-Fabio.local>
2026-05-13 08:32:18 -05:00
Zach Nelson 30b14f2ecf refactor: Removed SdCardFontGlobals.h (#1962)
## Summary

First of several changes to decouple and clean up SD card fonts
integration. This change eliminates SdCardFontGlobals.h:
- Simply declare the `extern SdCardFontSystem sdFontSystem` in
SdCardFontSystem.h.
- `ActivityManager::goToReader` should not care about loading SD card
fonts. Instead do the same work in `ReaderActivity::onEnter`, after the
previous activity has exited and after ReaderActivity has validated the
file path.

---

### 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-05-13 08:26:56 -05:00
Zach Nelson 6f7f4c592a refactor: Eliminated relative path includes (#1961)
## Summary

Relative includes can hide inappropriate dependency relationships. In
this case, I found that lib/KOReaderSync/KOReaderCredentialStore.cpp was
dependent on src/JsonSettingsIO.h -- a lib -> app dependency, the
opposite direction dependencies should flow in this project.

This change replaces all relative includes with root-relative includes,
and corrects the KOReaderCredentialStore dependency by moving its JSON
settings serialization local to the KOReaderSync library.

---

### 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-05-13 08:26:30 -05:00
zgredex bc57e5d64b fix: jump page on hold in font family and language selection (#1925)
## Summary
- Holding the navigation button in **Settings → Reader → Font family**
now advances the selection by a full visible page instead of one item at
a time.
- Same fix applied to **Settings → Language**, which had the same
one-item-only behavior.
- Mirrors the pattern already used in font download (ceb3fed) and
chapter selection screens.

## Test plan
- [ ] Settings → Reader → Font family: tap moves by one item; hold jumps
a page (wraps at ends)
- [ ] Settings → Language: tap moves by one item; hold jumps a page
(wraps at ends)
2026-05-12 17:37:56 -04:00
Chun Ming LeeandUri Tauber db3bb850b2 fix: handle fallbacks for advance table and prewarm (#1929)
## Summary
- Fixes #1928 by having the prewarm and advance table functions resolve
fallback styles
---

### AI Usage
Did you use AI tools to help write this code?  YES - Codex

---------

Co-authored-by: Uri Tauber <uritaube@gmail.com>
2026-05-12 22:11:37 +03:00
Uri Tauber 30a209de8b fix: Characters from unsupported characterset are overlapping (#1958)
## Summary

* **What is the goal of this PR?** fix #1956.

## Additional Context



[NixOS-and-Flakes-Book_ch3_p7_4pct_130044.bmp](https://github.com/user-attachments/files/27648392/NixOS-and-Flakes-Book_ch3_p7_4pct_130044.bmp)

---

### 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 >**_
2026-05-12 14:00:17 -05:00
476 changed files with 71393 additions and 64068 deletions

No files matched your search

+33
View File
@@ -0,0 +1,33 @@
# CrossPoint Reader: Claude Code skills
Project skills for Claude Code. Claude auto-discovers them and loads one when the
task matches its `description`; you do not invoke them by hand. They encode how
this project wants C/C++ written: the judgment calls and self-review gates that
keep the firmware small, stable, and reviewable.
These are written for capable agents, not beginners. They are principle- and
decision-focused on purpose. They deliberately avoid line-number citations,
which drift; they anchor on durable names (APIs, types, macros, files).
This is separate from `.skills/SKILL.md`, the GitHub coding-agent guide that
mirrors CLAUDE.md. CLAUDE.md stays the always-loaded rule set; these skills are
the applied decision procedures that load on demand and add the judgment layer
CLAUDE.md does not carry.
| Skill | Loads when you are... |
|---|---|
| `heap-discipline` | allocating memory: new/malloc/vector/string, buffers, caches |
| `control-flow-clarity` | writing branching logic, state flags, modes, if/else ladders |
| `hal-and-abstractions` | touching storage, input, display, settings, i18n, rendering |
| `scope-discipline` | adding a feature, activity, lib, setting, or dependency |
| `refactor-for-review` | refactoring, cleaning up, or preparing a change for PR |
Each skill ends with a self-review checklist Claude runs against its own diff
before handing it back. Reviewing a PR? Those checklists double as a fast rubric.
## Maintaining these
Edit the `SKILL.md` under each directory. Keep them tight. Do not restate
CLAUDE.md; add the judgment CLAUDE.md cannot afford to carry. Trigger quality
lives in the `description` field: it must name the situations that should pull
the skill in, in the words a contributor's task would use.
@@ -0,0 +1,56 @@
---
name: control-flow-clarity
description: Branching and state-modeling clarity in C/C++. Use when writing or refactoring logic that branches on discrete values: if/else-if ladders, status flags, mode or state ints, or anything that should be an enum plus an exhaustive switch. Covers enum class over magic ints, exhaustive switch over nested if, early-return guard clauses, table dispatch, and when each is the right call.
---
# Control-Flow Clarity
The goal: a reviewer verifies correctness by reading, not by tracing. Branching
that mirrors the problem's shape is self-evident; branching that encodes it in
ad-hoc ints and nesting forces the reader to reconstruct intent.
## Core moves
- **Model a closed set of states/modes as an `enum class`, not ints or bools.**
A variable kept honest by a comment ("0 = hidden, 1 = showing, 2 = confirm")
is a latent bug. Make it an enum and the comment becomes the type.
- **Dispatch on an enum with an exhaustive `switch`, no `default`.** This
codebase relies on it: omitting `default` lets the compiler flag the
unhandled case when someone adds an enum value. A `default:` that swallows the
unknown case throws that safety away. Add `default` only when "every other
value does nothing" is a deliberate, documented decision.
- **Replace nested `if/else-if` ladders that branch on one discriminant with a
`switch`.** If each branch only maps input to a value, prefer a lookup table
(`static constexpr` array) over both.
- **Prefer early-return guard clauses over nested success bodies.** Handle the
error/empty/skip cases first and return; keep the main path at the left
margin.
## When NOT to switch
- Branches test unrelated conditions, not one discriminant: a guarded `if`
sequence is honest; a switch would be forced.
- Two outcomes on a genuine boolean: keep the `if`.
- The discriminant is an open or unbounded set (arbitrary ints, strings): table
or map, not a switch.
## Enum hygiene
- `enum class` by default for type safety. Plain `enum` only when values must
implicitly convert (e.g. a value that doubles as a UI dropdown index), and
then give it a trailing `_COUNT` sentinel for safe bounds/iteration, matching
the existing settings enums.
- Name the discriminant after what it selects, not its storage:
`Orientation orientation`, not `uint8_t mode`.
- No magic numeric codes for states. If you write a comment mapping numbers to
meanings, you owe an enum.
## Self-review
- [ ] No int/bool standing in for a closed set of modes; it is an `enum class`.
- [ ] Enum dispatch is an exhaustive `switch` with no catch-all `default` (or
the `default` is a documented deliberate choice).
- [ ] No nested if/else-if ladder on a single discriminant that should be a
switch or a table.
- [ ] Error/skip cases are early-return guards; the happy path is not buried.
- [ ] No magic numbers where a named enum or `constexpr` would state the intent.
@@ -0,0 +1,59 @@
---
name: hal-and-abstractions
description: Layering and abstraction discipline for the firmware. Use when touching storage, input, display, settings, i18n, or rendering, or any code that could reach into the SDK. Covers routing through the HAL (HalStorage / HalGPIO / HalDisplay) instead of raw SDK classes, MappedInputManager logical buttons instead of raw GPIO indices, UITheme/GUI for all rendering, the singleton macros, tr() for user-facing text, and where a new abstraction boundary belongs.
---
# HAL and Abstractions
CLAUDE.md lists the HAL classes and the SdFat-concurrency reason they exist.
This is when and how to route through them, and where to draw a new boundary.
## Route through the layer, always
- **SD card I/O:** `Storage` (HalStorage) and `HalFile`. Never `SdFat`,
`FsFile`, `SdSpiCard`, `FsBaseFile`, or `SDCardManager` directly. The HAL
serializes every SD access through one mutex; bypassing it races the SPI state
machine and panics FreeRTOS (CLAUDE.md has the failure mode). This is a
correctness boundary, not a style preference.
- **Display:** `HalDisplay` over `EInkDisplay`. **Input:** `HalGPIO` over
`InputManager`.
- **Rendering:** everything through the `GUI` macro (UITheme) and the renderer's
oriented metrics. No hardcoded fonts, colors, coordinates, or 800/480
literals; ask the renderer for width/height and use the oriented viewable
area.
- **Input in activities:** `MappedInputManager::Button` logical enums
(`Button::Confirm`, `Button::PageForward`, ...). Never raw `HalGPIO::BTN_*`
indices outside `ButtonRemapActivity`. Logical buttons survive user remapping
and orientation; raw indices do not.
- **Shared state:** the singleton macros (`SETTINGS`, `APP_STATE`, `GUI`,
`Storage`, `I18N`), not threaded pointers.
## User-facing text
Every string a user reads goes through `tr(STR_*)`. Add the key to the English
YAML, regenerate with `scripts/gen_i18n.py`, then use the `StrId`. Log lines
(`LOG_*`) stay hardcoded.
## Drawing a new boundary
When you need an SDK capability the HAL does not expose yet, **add the method to
the HAL; do not reach around it.** The new method inherits the mutex, logging,
and error contract the rest of the HAL carries. A one-off direct SDK call in an
activity is exactly the layering violation the mutex discipline cannot tolerate.
Keep abstractions thin. A wrapper that only renames an SDK call without adding
the mutex, logging, or an error contract is dead weight. Add a layer only when
it carries one of those contracts or hides a real implementation choice.
## Self-review
- [ ] No direct SdFat / FsFile / SDCardManager / EInkDisplay / InputManager use
outside `lib/hal`.
- [ ] File access uses `HalFile`; no `.close()` on a local handle
(DESTRUCTOR_CLOSES_FILE); members closed in `onExit`.
- [ ] Input uses `MappedInputManager::Button`, not raw `BTN_*` indices.
- [ ] Rendering goes through GUI/UITheme and oriented metrics; no 800/480 or
hardcoded fonts/coords.
- [ ] User-facing strings use `tr(STR_*)`; new keys added to YAML and
regenerated.
- [ ] Any new SDK capability is exposed as a HAL method, not called inline.
+65
View File
@@ -0,0 +1,65 @@
---
name: heap-discipline
description: Memory allocation discipline for the ESP32-C3 (~380KB RAM, no PSRAM, single 48KB framebuffer). Use whenever writing or reviewing code that allocates: new / malloc / std::vector / std::string, buffers, caches, or anything held across a loop or an activity lifecycle. Covers makeUniqueNoThrow vs raw new/malloc, fragmentation avoidance, reserve-before-push_back, alloc-once-reuse, stack vs heap sizing, and the chunked grayscale buffer pattern.
---
# Heap Discipline (ESP32-C3)
CLAUDE.md states the allocation rules. This is the procedure you run while
writing the code and the gate you run before handing it back.
The constraint that makes every call matter: ~380KB RAM, no PSRAM, one 48KB
framebuffer. **Fragmentation, not total usage, is what kills this device.**
Free-heap can read fine while the largest free block is too small for the next
allocation. Optimize for not leaving holes, not just for using fewer bytes.
## Allocation decision procedure
Ask in order; stop at the first yes.
1. **Stack?** Local, bounded, under ~256 bytes total: plain array/struct. No
heap, no fragmentation. Keep frames lean; the task stack is small.
2. **Compile-time constant?** `static constexpr` lives in flash, costs zero DRAM.
3. **Allocated once and reused for an activity's lifetime?** Allocate in
`onEnter`, hold in a member, release in `onExit`. Never per-frame, never
per-iteration.
4. **Dynamic and fallible?** `makeUniqueNoThrow<T>(...)` /
`makeUniqueNoThrow<T[]>(n)` from `lib/Memory/Memory.h`. Null-check, `LOG_ERR`
with the size, return false. It frees on every exit path.
5. **A C/SDK API takes ownership and frees it itself?** Only then raw
`new (std::nothrow)` / `malloc`, with a comment naming who frees it.
Bare `new` / `new[]` is never correct here: under `-fno-exceptions` it calls
`abort()` on OOM instead of returning null.
## Fragmentation rules
- `std::vector`: `reserve(n)` before any `push_back` loop. Each growth is
alloc-copy-free (three heap ops) and leaves a hole. Unknown n: estimate high.
- No repeated `new`/`delete` or growing containers inside a loop or render path.
Hoist the allocation out of the loop.
- Large contiguous blocks fragment worst. Full-screen-class buffers use the
chunked `storeBwBuffer` / `restoreBwBuffer` path in `GfxRenderer` so they
never demand one contiguous 48KB block. Reuse that path. Do not malloc a
second full-screen buffer.
- `std::string` / Arduino `String`: acceptable on cold paths (file I/O, one-shot
setup). Banned on hot/render paths. Build text with a stack `char[]` +
`snprintf`; if a `std::string` is unavoidable, `reserve` it first.
## Justify every allocation
Per CLAUDE.md's evidence rule: when you add a heap allocation, state in one line
why stack/static/reuse was rejected and the worst-case size. If you cannot name
the size, you cannot budget it, and you should not allocate it.
## Self-review before handoff
- [ ] No bare `new`/`new[]`. Every fallible alloc is `makeUniqueNoThrow`, or a
raw alloc with an explicit owner comment.
- [ ] Every allocation is null-checked with `LOG_ERR` before the error return.
- [ ] No allocation inside a loop or render path that could be hoisted.
- [ ] Every `push_back` loop has a preceding `reserve`.
- [ ] Anything allocated in `onEnter` is released in `onExit`; member `HalFile`
closed there too.
- [ ] No second full-screen buffer; grayscale uses store/restoreBwBuffer.
- [ ] Each new allocation carries a one-line size + why-not-stack/static note.
@@ -0,0 +1,59 @@
---
name: refactor-for-review
description: Producing small, single-concern, reviewable changes. Use when refactoring, cleaning up, restructuring, decomposing, or preparing a change for PR, especially in this multi-contributor AI-assisted codebase that is prone to sprawl diffs. Covers one-concern-per-commit, extracting helpers without widening scope, not bundling unrelated edits, decomposing oversized activities, comment hygiene, and a pre-handoff self-review checklist.
---
# Refactor for Review
This is a multi-contributor, AI-assisted codebase, and the dominant failure mode
is the sprawl diff: a one-line intent that touches thirty files. The goal is a
change a reviewer can verify in one sitting. Cleaner structure that makes the
next change easier is the win, not lines added.
## One concern per change
- A commit/PR does one thing. A bug fix is not also a rename is not also a
reformat. If you spot an unrelated improvement mid-change, leave it or capture
it separately; do not fold it in.
- When the working tree has bundled two changes, separate them with the
copy-affected-files-aside, reset, re-apply one concern, restore the rest
pattern, not by committing the tangle.
- Refactor and behavior change do not ride together. A pure refactor must not
alter behavior; a behavior change should not drag a refactor along. If both
are needed: two commits, refactor first.
## Keep the diff narrow
- Extract a helper to remove real duplication or to name a concept, not to chase
abstraction. Three-plus copies, or a block that needs a name to be understood:
extract. Two similar lines: leave them.
- No "while I'm here" scope creep. A signature or type change that ripples to
many call sites is its own PR: map every caller first, update them in one
topological pass, and land it separately, not as a rider on a feature.
- Match the surrounding code: comment density, naming, idiom. The diff should
read like the file, not like a different author.
## Decompose oversized units
An activity or function that has outgrown one screen of responsibility (multiple
unrelated state machines, or a file far larger than its siblings) is a
decomposition candidate. Extract a cohesive sub-responsibility into its own
unit, as a standalone behavior-preserving refactor, verified on its own, never
mixed into a feature change.
## Comments earn their place
Comments explain why: an invariant, a defense, a past incident, a non-obvious
constraint. Never what the next line already says. Delete narration, phase-marker
comments ("now we loop over..."), and restated function names. If a comment and
the code it sits on say the same thing, the comment is the thing to cut.
## Self-review before handoff
- [ ] The change does exactly one thing; nothing unrelated rode along.
- [ ] Refactor and behavior change are not mixed in one commit.
- [ ] No "while I'm here" creep; rename/signature ripples are split out.
- [ ] Extractions remove real duplication or name a real concept, not
speculative abstraction.
- [ ] New comments say why, not what; no narration or phase markers.
- [ ] A reviewer can understand the diff without running it.
+54
View File
@@ -0,0 +1,54 @@
---
name: scope-discipline
description: Feature-scope discipline for a dedicated e-reader (not a Swiss Army knife). Use when adding a feature, a new activity, a new lib, a setting, or a dependency, or when a request would grow the firmware's surface. Covers the SCOPE.md test, the RAM-cost vs reading-benefit gate, preferring no-code or existing-mechanism solutions, awareness of the existing activity surface, and how to push back on out-of-scope asks.
---
# Scope Discipline
The mission: do one thing exceptionally well, focused reading on constrained
hardware. `SCOPE.md` is the source of truth for what is in and out. Read it
before adding surface. This is the gate to run before writing a new feature.
## The gate
Before adding a feature, activity, lib, setting, or dependency, answer in order:
1. **Is it in `SCOPE.md`?** Explicitly out: interactive apps (notepad,
calculator, games), active connectivity (RSS, news, browser), media/audio
playback. If it is out, say so and stop.
2. **Does it materially improve focused reading?** If the benefit is
"nice to have" or serves a different use case, it is out. This is not a PDA.
3. **What does it cost in RAM and in the largest-free-block budget?** A feature
that adds steady-state RAM or a large transient allocation needs a reading
benefit that clearly outweighs it. Quantify with `firmware_size_history.py`
and `script_profile_mem.sh` rather than guessing.
4. **Can it be done with no new code?** Prefer an existing activity, an existing
setting, or a doc over a new code path. The cheapest feature is the one
already built.
If a request fails the gate, push back with the specific reason and the
`SCOPE.md` basis, and offer the in-scope alternative. Make the call and say why;
do not just hand over a menu.
## Surface awareness
The firmware already carries dozens of activities. Each new one is permanent
RAM, permanent maintenance, and another thing every future refactor must not
break. Default to extending an existing activity or setting before adding a new
screen. New top-level surface needs a real justification, not "it would be
convenient."
## Settings are not free
A new setting is a field to persist, migrate, validate, translate, and render,
plus combinatorial test burden. Add one only when users genuinely need the
choice; otherwise pick a sensible fixed default.
## Self-review
- [ ] Checked against `SCOPE.md`; not on the out-of-scope list.
- [ ] Stated the concrete reading benefit, not a generic "useful."
- [ ] Named the RAM/size cost (measured, not guessed) and why the benefit wins.
- [ ] Checked whether an existing activity/setting/doc already covers it.
- [ ] New setting (if any) is justified by a real user need, not added
"just in case."
+1 -2
View File
@@ -1,2 +1 @@
github: [daveallie]
ko_fi: daveallie
custom: ["https://app.royalty.dev/crosspoint-reader/crosspoint-reader"]
+13
View File
@@ -0,0 +1,13 @@
blank_issues_enabled: false
contact_links:
- name: Scope or Roadmap Question (start here if unsure)
url: https://github.com/crosspoint-reader/crosspoint-reader/discussions
about: |
Not sure if your idea fits CrossPoint's scope? Start a Discussion before filing an issue.
See SCOPE.md and ROADMAP.md for what is in, out, and currently paused.
- name: Read the Scope document
url: https://github.com/crosspoint-reader/crosspoint-reader/blob/master/SCOPE.md
about: The authoritative list of what CrossPoint will and will not accept.
- name: Read the Roadmap
url: https://github.com/crosspoint-reader/crosspoint-reader/blob/master/ROADMAP.md
about: Current phase, what is being closed out, and what comes next.
@@ -0,0 +1,80 @@
name: Feature Request
description: Propose a new feature, enhancement, or change to CrossPoint
title: "Short, descriptive title of the request"
labels: ["enhancement", "needs-scope-review"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to propose a change to CrossPoint!
**Before you continue, please read [SCOPE.md](../blob/master/SCOPE.md) and [ROADMAP.md](../blob/master/ROADMAP.md).**
CrossPoint is intentionally narrow. Most rejected proposals are rejected for scope reasons that are already
documented. The checklist below exists to save both of us time.
If you are not sure whether your idea fits, open a [Discussion](../../discussions) first instead of filing
this issue.
- type: checkboxes
id: scope-check
attributes:
label: Scope Self-Check (required)
description: Please confirm each of the following. If any are unchecked, your issue will likely be closed.
options:
- label: I have read SCOPE.md and ROADMAP.md.
required: true
- label: This is **not** a new theme or theming change (themes are temporarily closed pending the move to SD-loaded themes).
required: true
- label: This is **not** a new external network connector (sync engine, cloud storage, remote file access, OPDS extensions beyond what exists, or any new "talk to a server" feature).
required: true
- label: This is **not** an interactive app (game, calculator, notepad), writing/authoring tool, RSS/news/browser feature, media playback feature, or PDF rendering.
required: true
- label: The stock firmware does **not** already handle this well.
required: true
- label: No other popular CrossPoint fork already handles this well (or, if one does, I explain below why CrossPoint still needs it).
required: true
- type: textarea
id: problem
attributes:
label: Problem this solves
description: What user-facing problem or reading-experience gap does this address? Be concrete.
placeholder: e.g., "When reading in landscape, paragraph breaks are inconsistent because..."
validations:
required: true
- type: textarea
id: stock-gap
attributes:
label: Why the stock firmware (and other forks) do not already solve this
description: Explain which existing solutions you checked and why they fall short. This is the core scope filter.
validations:
required: true
- type: textarea
id: proposal
attributes:
label: Proposed change
description: A short description of what you would build or change. Focus on user impact; implementation details can come later.
validations:
required: true
- type: textarea
id: tradeoffs
attributes:
label: Memory / flash / complexity cost
description: |
CrossPoint runs on 380KB of RAM. Roughly how much DRAM, flash, or code complexity does this add?
"Don't know" is a valid answer, but please attempt an estimate.
placeholder: e.g., "Adds ~2KB flash for the new font tables, no DRAM impact at runtime."
validations:
required: false
- type: textarea
id: additional-context
attributes:
label: Additional context
description: Anything else relevant (links, screenshots, related discussions).
validations:
required: false
+20 -2
View File
@@ -3,16 +3,34 @@
* **What is the goal of this PR?** (e.g., Implements the new feature for file uploading.)
* **What changes are included?**
## Scope Check
CrossPoint is intentionally narrow. See [SCOPE.md](../blob/master/SCOPE.md) and [ROADMAP.md](../blob/master/ROADMAP.md).
Please confirm:
- [ ] I have read SCOPE.md and ROADMAP.md.
- [ ] This PR is **not** a new built-in theme (themes are temporarily closed pending the move to SD-loaded themes).
- [ ] This PR is **not** a new external network connector (sync engine, cloud storage, remote file access, etc.).
- [ ] This PR is **not** an interactive app, writing tool, RSS/news/browser, media playback, or PDF feature.
- [ ] The stock firmware does not already handle this well, **and** no other popular CrossPoint fork already does
(or, if one does, I explain why CrossPoint still needs it below).
- [ ] If this PR touches `freeink-sdk/`, `lib/hal/`, the bootloader, OTA, or recovery code, I have coordinated with
the relevant maintainer.
**If this PR was opened against the previous (broader) scope and was already in flight under Phase 0, link the
relevant Discussion or issue so reviewers can see the history.**
## Additional Context
* Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks,
* Add any other information that might be helpful for the reviewer (e.g., performance implications, potential risks,
specific areas to focus on).
* Memory / flash impact, if known.
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it
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 | NO >**_
+47 -1
View File
@@ -53,6 +53,13 @@ jobs:
- name: Install PlatformIO Core
run: uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
- name: Cache PlatformIO packages
uses: actions/cache@v4
with:
path: ~/.platformio
key: pio-${{ runner.os }}-${{ hashFiles('platformio.ini') }}
restore-keys: pio-${{ runner.os }}-
- name: Run cppcheck
run: pio check --fail-on-defect low --fail-on-defect medium --fail-on-defect high
@@ -76,10 +83,21 @@ jobs:
- name: Install PlatformIO Core
run: uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
- name: Cache PlatformIO packages
uses: actions/cache@v4
with:
path: ~/.platformio
key: pio-${{ runner.os }}-${{ hashFiles('platformio.ini') }}
restore-keys: pio-${{ runner.os }}-
- name: Build CrossPoint
# Both envs in ONE pio invocation: a second `pio run` can wipe the whole
# .pio/build tree (project-checksum mismatch after the S3 toolchain
# installs into the restored cache), deleting the default firmware.bin
# before the artifact upload.
run: |
set -euo pipefail
pio run | tee pio.log
pio run -e default -e sticky | tee pio.log
- name: Extract firmware stats
@@ -102,6 +120,33 @@ jobs:
path: .pio/build/default/firmware.bin
if-no-files-found: error
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Install build tools
run: |
sudo apt-get update
sudo apt-get install -y cmake ninja-build
- name: Cache googletest source
uses: actions/cache@v4
with:
path: build/test/_deps/googletest-src
key: ${{ runner.os }}-googletest-${{ hashFiles('test/CMakeLists.txt') }}
- name: Configure
run: cmake -S test -B build/test -G Ninja -DCMAKE_BUILD_TYPE=Release
- name: Build
run: cmake --build build/test
- name: Run tests
run: ctest --test-dir build/test --output-on-failure -j
# This job is used as the PR required actions check, allows for changes to other steps in the future without breaking
# PR requirements.
test-status:
@@ -110,6 +155,7 @@ jobs:
- build
- clang-format
- cppcheck
- unit-tests
if: always()
runs-on: ubuntu-latest
steps:
+7
View File
@@ -25,6 +25,13 @@ jobs:
- name: Install PlatformIO Core
run: uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
- name: Cache PlatformIO packages
uses: actions/cache@v4
with:
path: ~/.platformio
key: pio-${{ runner.os }}-${{ hashFiles('platformio.ini') }}
restore-keys: pio-${{ runner.os }}-
- name: Build CrossPoint
run: pio run -e gh_release
+7
View File
@@ -25,6 +25,13 @@ jobs:
- name: Install PlatformIO Core
run: uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip
- name: Cache PlatformIO packages
uses: actions/cache@v4
with:
path: ~/.platformio
key: pio-${{ runner.os }}-${{ hashFiles('platformio.ini') }}
restore-keys: pio-${{ runner.os }}-
- name: Extract env
run: |
echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
+12
View File
@@ -2,6 +2,8 @@
.idea
.DS_Store
.vscode
open-x4-sdk
fs_
lib/EpdFont/fontsrc
lib/I18n/I18nKeys.h
lib/I18n/I18nStrings.h
@@ -19,3 +21,13 @@ build
lib/EpdFont/scripts/downloaded_fonts/
lib/EpdFont/scripts/instanced_fonts/
lib/EpdFont/scripts/output/
# Claude Code: share project skills with the team, keep local agent state
# (worktrees, scheduled-task locks, settings.local, scout CLEANUP.md) out.
.claude/*
!.claude/skills/
/managed_components
/.dummy
/CMakeLists.txt
/dependencies.lock
/sdkconfig.*
sdkconfig.sticky
+4 -3
View File
@@ -1,3 +1,4 @@
[submodule "open-x4-sdk"]
path = open-x4-sdk
url = https://github.com/crosspoint-reader/community-sdk.git
[submodule "freeink-sdk"]
path = freeink-sdk
url = https://github.com/Free-Ink/freeink-sdk.git
branch = main
+72 -32
View File
@@ -7,7 +7,7 @@ Mission: Provide a lightweight, high-performance reading experience focused on E
* Role: Senior Embedded Systems Engineer (ESP-IDF/Arduino-ESP32 specialized).
* Primary Constraint: 380KB RAM is the hard ceiling. Stability is non-negotiable.
* Evidence-Based Reasoning: Before proposing a change, you MUST cite the specific file path and line numbers that justify the modification.
* Anti-Hallucination: Do not assume the existence of libraries or ESP-IDF functions. If you are unsure of an API's availability for the ESP32-C3 RISC-V target, check the open-x4-sdk or official docs first.
* Anti-Hallucination: Do not assume the existence of libraries or ESP-IDF functions. If you are unsure of an API's availability for the ESP32-C3 RISC-V target, check the freeink-sdk source or the FreeInk SDK docs (https://freeink.org/llms.txt for an LLM-readable index) first.
* No Unfounded Claims: Do not claim performance gains or memory savings without explaining the technical mechanism (e.g., DRAM vs IRAM usage).
* Resource Justification: You must justify any new heap allocation (new, malloc, std::vector) or explain why a stack/static alternative was rejected.
* Verification: After suggesting a fix, instruct the user on how to verify it (e.g., monitoring heap via Serial or checking a specific cache file).
@@ -58,6 +58,7 @@ find src -name "*.cpp" -o -name "*.h" | xargs clang-format -i
6. `constexpr` First: Compile-time constants and lookup tables must be `constexpr`, not just `static const`. This moves computation to compile time, enables dead-branch elimination, and guarantees flash placement. Use `static constexpr` for class-level constants.
7. `std::vector` Pre-allocation: Always call `.reserve(N)` before any `push_back()` loop. Each growth event allocates a new block (2×), copies all elements, then frees the old one — three heap operations that fragment DRAM. When the final size is unknown, estimate conservatively.
8. SPIFFS Write Throttling: Never write a settings file on every user interaction. Guard all writes with a value-change check (`if (newVal == _current) return;`). Progress saves during reading must be debounced — write on activity exit or every N page turns, not on every turn. SPIFFS sectors have a finite erase cycle limit.
9. `new` is not nothrow on ESP32: With `-fno-exceptions`, bare `new` that fails calls `abort()` — it does NOT return `nullptr`. Always use `new (std::nothrow)` and null-check the result, or use `makeUniqueNoThrow<T>()` from `lib/Memory/Memory.h`. Never write bare `new` for any fallible allocation.
---
@@ -126,7 +127,7 @@ These flags in `platformio.ini` fundamentally affect firmware behavior:
* lib/hal/: Hardware Abstraction Layer (HalDisplay, HalGPIO, HalStorage)
* lib/I18n/: Internationalization (translations in `translations/*.yaml`, generated string tables)
* src/activities/: UI logic using the Activity Lifecycle (onEnter, loop, onExit)
* open-x4-sdk/: Low-level SDK (EInkDisplay, InputManager, BatteryMonitor, SDCardManager)
* freeink-sdk/: Low-level SDK (EInkDisplay, InputManager, BatteryMonitor, SDCardManager)
* .crosspoint/: SD-based binary cache for EPUB metadata and pre-rendered layout sections
### Hardware Abstraction Layer (HAL)
@@ -151,14 +152,19 @@ These flags in `platformio.ini` fundamentally affect firmware behavior:
#include <HalStorage.h>
// Use Storage singleton (defined via macro)
FsFile file;
HalFile file;
if (Storage.openFileForRead("MODULE", "/path/to/file.bin", file)) {
// Read from file
// No file.close() needed — DESTRUCTOR_CLOSES_FILE=1 handles it at scope exit
}
```
**Usage**: See example above. Uses `FsFile` (SdFat), NOT Arduino `File`. Do NOT add `file.close()` for local variables (see DESTRUCTOR_CLOSES_FILE above).
**Usage**: Use `HalFile` (the mutex-wrapping handle), NOT raw SdFat `FsFile` or Arduino `File`. Do NOT add `file.close()` for local variables (see DESTRUCTOR_CLOSES_FILE above).
**SdFat is not thread-safe; all SD access MUST go through HalStorage**:
- SdFat's `SdSpiCard` tracks SPI bus state with an unsynchronized `m_spiActive` bool. Two tasks calling SdFat concurrently can confuse that state machine and end with one task calling `SPIClass::endTransaction()` against a paramLock the *other* task is holding. That trips FreeRTOS's `xTaskPriorityDisinherit` assert (`tasks.c:5156, pxTCB == pxCurrentTCBs[0]`) and panics the system. See SdFat issue #518.
- `HalStorage` serializes everything via `storageMutex`. Downstream code uses `HalFile` (declared in `<HalStorage.h>`); every method call (read, write, seek, close) takes the mutex. `HalFile`'s destructor also takes the mutex before letting the underlying SdFat `FsFile` close.
- **Never** call into `SdFat` / `SdSpiCard` / `FsBaseFile` / `SDCardManager` / raw `FsFile` directly — that bypasses the mutex.
---
@@ -266,43 +272,78 @@ When a template is necessary, limit instantiations: use explicit template instan
**Rules**: NO exceptions, NO abort(), ALWAYS log before error return
### Acceptable malloc/free Patterns
### Heap Buffer Allocation
**Source**: [src/activities/home/HomeActivity.cpp:166](../src/activities/home/HomeActivity.cpp), [lib/GfxRenderer/GfxRenderer.cpp:439-440](../lib/GfxRenderer/GfxRenderer.cpp)
**Prefer `makeUniqueNoThrow` over `malloc`.** Both are nothrow (return `nullptr` on OOM rather than calling `abort()`), but `malloc` requires a manual `free` on every return path — a common source of leaks. `makeUniqueNoThrow<uint8_t[]>(size)` from `lib/Memory/Memory.h` frees automatically when it goes out of scope.
Despite "prefer stack allocation," malloc is acceptable for:
1. **Large temporary buffers** (> 256 bytes, won't fit on stack)
2. **One-time allocations** during activity initialization
3. **Bitmap rendering buffers** (variable size, used briefly)
**Pattern**:
**Preferred pattern**:
```cpp
// Allocate
auto* buffer = static_cast<uint8_t*>(malloc(bufferSize));
#include <Memory.h>
auto buffer = makeUniqueNoThrow<uint8_t[]>(bufferSize);
if (!buffer) {
LOG_ERR("MODULE", "malloc failed: %d bytes", bufferSize);
return false; // Handle allocation failure
LOG_ERR("MODULE", "OOM: %d bytes", bufferSize);
return false;
}
// Use buffer
processData(buffer, bufferSize);
processData(buffer.get(), bufferSize);
// freed automatically — no manual free needed, no leak on early return
```
// Free immediately after use
free(buffer);
buffer = nullptr;
**`malloc` or `new (std::nothrow)` are still acceptable** when the buffer must be passed to a C API that takes ownership and frees it itself (e.g., certain SDK callbacks). In that case follow the manual pattern:
```cpp
auto* buffer = static_cast<uint8_t*>(malloc(bufferSize)); // or new (std::nothrow) uint8_t[bufferSize]
if (!buffer) {
LOG_ERR("MODULE", "OOM: %d bytes", bufferSize);
return false;
}
sdkApiThatTakesOwnership(buffer, bufferSize); // SDK calls free() / delete[]
```
**Rules**:
- **ALWAYS check for nullptr** after malloc
- **Free immediately** after use (don't hold across multiple operations)
- **Set to nullptr** after free (avoid use-after-free)
- **Document size**: Comment why stack allocation was rejected
- **Prefer `makeUniqueNoThrow`** — automatic cleanup eliminates leak risk on error paths
- **ALWAYS check for nullptr** after any allocation and `LOG_ERR` before returning false
- **Raw allocation only** when a C API takes ownership; document why in a comment
**Examples in codebase**:
- Memory utilities: [Memory.h](../lib/Memory/Memory.h) (`makeUniqueNoThrow`)
- Cover image buffers: [HomeActivity.cpp:166](../src/activities/home/HomeActivity.cpp)
- Text chunk buffers: [TxtReaderActivity.cpp:259](../src/activities/reader/TxtReaderActivity.cpp)
- Bitmap rendering: [GfxRenderer.cpp:439-440](../lib/GfxRenderer/GfxRenderer.cpp)
- OTA update buffer: [OtaUpdater.cpp:40](../src/network/OtaUpdater.cpp)
### Heap Allocation with `new`: Always Use `makeUniqueNoThrow`
**CRITICAL**: With `-fno-exceptions`, bare `new` on OOM calls `abort()` — it does NOT return `nullptr`. Always use `makeUniqueNoThrow` from `lib/Memory/Memory.h`, which wraps `new (std::nothrow)` and returns a `std::unique_ptr` that is null on OOM and automatically frees on scope exit.
**Preferred pattern**:
```cpp
#include <Memory.h>
auto obj = makeUniqueNoThrow<MyClass>(args);
if (!obj) { LOG_ERR("MOD", "OOM: MyClass"); return false; }
auto buf = makeUniqueNoThrow<uint8_t[]>(size);
if (!buf) { LOG_ERR("MOD", "OOM: %d bytes", size); return false; }
// Pass to C APIs via .get(); unique_ptr frees automatically on return
someApi(buf.get(), size);
```
**`new (std::nothrow)` directly is acceptable** when the object must be passed to a C API that takes ownership and calls `delete` itself:
```cpp
auto* obj = new (std::nothrow) MyClass(args);
if (!obj) { LOG_ERR("MOD", "OOM: MyClass"); return false; }
sdkApiThatTakesOwnership(obj); // SDK calls delete
```
**Rules**:
- **Prefer `makeUniqueNoThrow`** — automatic cleanup eliminates leak risk on error paths
- **NEVER use bare `new`** — always `makeUniqueNoThrow` or `new (std::nothrow)`
- **ALWAYS `LOG_ERR` before returning false** on OOM
- **Use `.get()`** to pass the raw pointer to C-style APIs; ownership stays with the `unique_ptr`
- **`new (std::nothrow)` directly only** when a C API takes ownership; document why in a comment
**Examples in codebase**:
- Memory utilities: [Memory.h](../lib/Memory/Memory.h) (`makeUniqueNoThrow`)
---
@@ -416,7 +457,6 @@ void onExit() { /* free: vTaskDelete, free buffer, close member FsFiles */ Act
**All fonts are loaded as global static objects** at firmware startup:
- Noto Serif: 12, 14, 16, 18pt (4 styles each: regular, bold, italic, bold-italic)
- Noto Sans: 12, 14, 16, 18pt (4 styles each)
- OpenDyslexic: 8, 10, 12, 14pt (4 styles each)
- Ubuntu UI fonts: 10, 12pt (2 styles)
**Total**: ~80+ global `EpdFont` and `EpdFontFamily` objects
@@ -856,8 +896,8 @@ rm -rf /path/to/sd/.crosspoint/epub_<hash>/sections/
**Source**: `lib/Epub/Epub/Section.cpp`, `lib/Epub/Epub/BookMetadataCache.cpp`
**Current Versions** (as of docs/file-formats.md):
- `book.bin`: **Version 5** (metadata structure)
- `section.bin`: **Version 12** (layout structure)
- `book.bin`: **Version 7** (metadata structure)
- `section.bin`: **Version 25** (layout structure)
**Version Increment Rules**:
1. **ALWAYS increment version** BEFORE changing binary structure
@@ -867,7 +907,7 @@ rm -rf /path/to/sd/.crosspoint/epub_<hash>/sections/
**Example** (incrementing section format version):
```cpp
// lib/Epub/Epub/Section.cpp
static constexpr uint8_t SECTION_FILE_VERSION = 13; // Was 12, now 13
static constexpr uint8_t SECTION_FILE_VERSION = 26; // Was 25, now 26
// Add new field to structure
struct PageLine {
@@ -878,4 +918,4 @@ struct PageLine {
---
Philosophy: We are building a dedicated e-reader, not a Swiss Army knife. If a feature adds RAM pressure without significantly improving the reading experience, it is Out of Scope.
Philosophy: We are building a dedicated e-reader, not a Swiss Army knife. If a feature adds RAM pressure without significantly improving the reading experience, it is Out of Scope.
+216 -126
View File
@@ -1,122 +1,196 @@
# CrossPoint Reader
Firmware for the **Xteink X4** e-paper display reader (unaffiliated with Xteink).
Built using **PlatformIO** and targeting the **ESP32-C3** microcontroller.
[![Fund contributors](https://img.shields.io/badge/%F0%9F%91%91_Fund_contributors-royalty.dev-BB953A?style=for-the-badge&labelColor=1a1a1a)](https://app.royalty.dev/crosspoint-reader/crosspoint-reader)
CrossPoint Reader is a purpose-built firmware designed to be a drop-in, fully open-source replacement for the official
Xteink firmware. It aims to match or improve upon the standard EPUB reading experience.
CrossPoint is open-source e-reader firmware - community-built, fully hackable, free forever. It's maintained by a growing community of developers and readers who believe your device should do what you want - not what a manufacturer decided for you.
![](./docs/images/cover.jpg)
**Now running on:** ESP32C3-based Xteink [X4](https://www.xteink.com/products/xteink-x4) and [X3](https://www.xteink.com/products/xteink-x3).
## Motivation
![CrossPoint Reader running on Xteink device](./docs/images/cover.jpg)
E-paper devices are fantastic for reading, but most commercially available readers are closed systems with limited
customisation. The **Xteink X4** is an affordable, e-paper device, however the official firmware remains closed.
CrossPoint exists partly as a fun side-project and partly to open up the ecosystem and truly unlock the device's
potential.
> If you're planning to buy an Xteink device, consider purchasing an **X3/X4 Developer Edition** through https://crosspointreader.com. CrossPoint receives a small share of each sale, helping fund development costs.
CrossPoint Reader aims to:
* Provide a **fully open-source alternative** to the official firmware.
* Offer a **document reader** capable of handling EPUB content on constrained hardware.
* Support **customisable font, layout, and display** options.
* Run purely on the **Xteink X4 hardware**.
## What can CrossPoint do?
This project is **not affiliated with Xteink**; it's built as a community project.
- **Reader engine**: EPUB 2/3 rendering with embedded-style option, image handling, hyphenation, kerning, chapter navigation, footnotes, bookmarks, dictionary lookups ([StarDict](docs/dictionary.md)), go-to-percent, auto page turn, orientation control, focus reading, KOReader progress sync and more.
## Features & Usage
- **Various formats**: native handling for `.epub`, `.xtc/.xtch`, `.txt`, and `.bmp`.
- [x] EPUB parsing and rendering (EPUB 2 and EPUB 3)
- [x] Image support within EPUB
- [x] Saved reading position
- [x] File explorer with file picker
- [x] Basic EPUB picker from root directory
- [x] Support nested folders
- [ ] EPUB picker with cover art
- [x] Custom sleep screen
- [x] Cover sleep screen
- [x] Wifi book upload
- [x] Wifi OTA updates
- [x] KOReader Sync integration for cross-device reading progress
- [x] Configurable font, layout, and display options
- [ ] User provided fonts
- [ ] Full UTF support
- [x] Screen rotation
- **Screenshots.**
Multi-language support: Read EPUBs in various languages, including English, Spanish, French, German, Italian, Portuguese, Russian, Ukrainian, Polish, Swedish, Norwegian, [and more](./USER_GUIDE.md#supported-languages).
- **Custom fonts**: install your favorite fonts on the SD card.
See [the user guide](./USER_GUIDE.md) for instructions on operating CrossPoint, including the
[KOReader Sync quick setup](./USER_GUIDE.md#367-koreader-sync-quick-setup).
- **Tilt page turn (X3 only)**.
For more details about the scope of the project, see the [SCOPE.md](SCOPE.md) document.
- **Library workflow**: folder browser, hidden-file toggle, long-press delete, recent books, SD-cache management.
## Installing
- **Wireless workflows**:
- File transfer web UI
- EPUB Optimizer
- Web settings UI/API (edit many device settings from browser)
- WebSocket fast uploads
- WebDAV handler
- AP mode (hotspot) and STA mode (join existing Wi-Fi), both with QR helpers
- Calibre wireless connect flow
- OPDS browser with saved servers (up to 8), search, pagination, and direct download
- OTA update checks and installs from GitHub releases
### Web (latest firmware)
- **Customization**: multiple themes (Classic, Lyra, Lyra Extended, RoundedRaff), sleep screen modes, front/side button remapping, status bar controls, power-button behavior, refresh cadence, and more.
1. Connect your Xteink X4 to your computer via USB-C and wake/unlock the device
2. Go to https://xteink.dve.al/ and click "Flash CrossPoint firmware"
- **Localization**: 24 UI languages and counting. RTL support.
To revert back to the official firmware, you can flash the latest official firmware from https://xteink.dve.al/, or swap
back to the other partition using the "Swap boot partition" button here https://xteink.dve.al/debug.
### Coming soon:
### Web (specific firmware version)
- More themes.
1. Connect your Xteink X4 to your computer via USB-C
2. Download the `firmware.bin` file from the release of your choice via the [releases page](https://github.com/crosspoint-reader/crosspoint-reader/releases)
3. Go to https://xteink.dve.al/ and flash the firmware file using the "OTA fast flash controls" section
- Much more! stay tuned.
To revert back to the official firmware, you can flash the latest official firmware from https://xteink.dve.al/, or swap
back to the other partition using the "Swap boot partition" button here https://xteink.dve.al/debug.
---
### Command line (specific firmware version)
## USB-locked devices (Xteink Unlocker)
Some Xteink units purchased from third-party stores (e.g. AliExpress) ship with USB flashing locked from the factory.
If your device is locked, you will need to use the **Xteink Unlocker** tool available at
https://crosspointreader.com/#unlock-tool before you can flash CrossPoint.
**You do not need this tool if you bought your device directly from xteink.com.** Those units are not locked.
**Not sure if your device is locked?** Power it on, connect the USB-C cable, and try flashing via the web flasher first (see
[Install firmware](#install-firmware) below). If the browser's serial device picker does not show your device, try a different
USB port or browser before assuming the device is locked. Only reach for the unlocker if the device still doesn't appear.
> ### ⚠️ WARNING: READ THIS BEFORE USING THE UNLOCKER ⚠️
>
> **The only officially supported firmwares in the unlock tool are CrossPoint and CrossInk.**
>
> Flashing any other firmware on a USB-locked device may **permanently brick the device** or leave it **permanently
> stuck on that firmware with no recovery path**. Once USB flashing is re-locked, your only way back is via OTA, and if
> the firmware you flashed doesn't support OTA, **there is no way out**.
## Install firmware
### Web installer (recommended)
1. Connect your device to your computer via USB-C and wake/unlock the device
2. Go to https://crosspointreader.com/#flash-tools, select device (X3 or X4), and choose an official CrossPoint release.
### Web installer (specific version)
1. Connect your device to your computer via USB-C and wake/unlock the device
2. Download a `firmware.bin` from [Releases](https://github.com/crosspoint-reader/crosspoint-reader/releases), local build, or continuous integration artifact.
3. Go to https://crosspointreader.com/#flash-tools, select device (X3 or X4), click "Custom .bin" and upload a `firmware.bin`.
### Revert to Official Firmware
To revert to the official firmware, you can also flash the latest official firmware using https://crosspointreader.com/#flash-tools.
### Command line
1. Install [`esptool`](https://github.com/espressif/esptool):
1. Install [`esptool`](https://github.com/espressif/esptool) :
```bash
pip install esptool
```
2. Download the `firmware.bin` file from the release of your choice via the [releases page](https://github.com/crosspoint-reader/crosspoint-reader/releases)
3. Connect your Xteink X4 to your computer via USB-C.
4. Note the device location. On Linux, run `dmesg` after connecting. On MacOS, run :
2. Download `firmware.bin` from the [releases page](https://github.com/crosspoint-reader/crosspoint-reader/releases).
3. Connect your device via USB-C.
4. Find the device port. On Linux, run `dmesg` after connecting. On macOS:
```bash
log stream --predicate 'subsystem == "com.apple.iokit"' --info
```
5. Flash the firmware :
5. Flash:
```bash
esptool.py --chip esp32c3 --port /dev/ttyACM0 --baud 921600 write_flash 0x10000 /path/to/firmware.bin
```
Change `/dev/ttyACM0` to the device for your system.
Adjust `/dev/ttyACM0` to match your system.
### Manual
See [Development](#development) below.
See [Development quick start](#development-quick-start) below.
## Development
---
## Custom SD-card fonts
Convert your own TTF/OTF files into `.cpfont` files that load from the SD card. No firmware reflash is needed.
1. Go to https://crosspointreader.com/fonts and open the "SD-card font builder" form.
2. Upload up to four styles (regular, bold, italic, bold-italic), set the family name, point sizes, and Unicode range.
3. Download the generated `.cpfont` files.
4. Copy them to your SD card under `/fonts/YourFont/` (or `/.fonts/YourFont/` to hide the folder).
5. Select the font on the device from the font settings.
Conversion runs the firmware repo's `lib/EpdFont/scripts/fontconvert_sdcard.py` script unmodified, so output matches a local host build.
---
## Documentation
- [User Guide](./USER_GUIDE.md)
- [Web server usage](./docs/webserver.md)
- [Web server endpoints](./docs/webserver-endpoints.md)
- [Project scope](./SCOPE.md)
- [Contributing docs](./docs/contributing/README.md)
- [Touch and UI development](./docs/contributing/touch-and-ui.md) - FreeInkUI components for new screens, the touch bridge for existing ones, and build envs for the non-Xteink touch devices
---
## Development quick start
### Prerequisites
* **PlatformIO Core** (`pio`) or **VS Code + PlatformIO IDE**
* Python 3.8+
* USB-C cable for flashing the ESP32-C3
* Xteink X4
- [pioarduino](https://github.com/pioarduino/pioarduino) or VS Code + pioarduino plugin
- Python 3.8+
- `clang-format` 21
- USB-C cable supporting data transfer
### Checking out the code
### Setup
CrossPoint uses PlatformIO for building and flashing the firmware. To get started, clone the repository:
```
```bash
git clone --recursive https://github.com/crosspoint-reader/crosspoint-reader
cd crosspoint-reader
# Or, if you've already cloned without --recursive:
# if cloned without --recursive:
git submodule update --init --recursive
```
### Flashing your device
### Nix/NixOS
Connect your Xteink X4 to your computer via USB-C and run the following command.
Nix/NixOS users can enter the development shell with either `nix develop` (flakes) or `nix-shell`:
```sh
```bash
nix develop -f nix
# or
nix-shell nix
```
To flash a connected ESP32-C3 device, enable PlatformIO's udev rules in your NixOS configuration:
```nix
services.udev.packages = with pkgs; [ platformio-core.udev ];
```
After rebuilding the system configuration, reconnect the device or reload udev rules.
### Build / flash / monitor
```bash
pio run --target upload
```
### Contributor pre-PR checks
```bash
./bin/clang-format-fix
pio check -e default
pio run -e default
```
### Debugging
After flashing the new features, it’s recommended to capture detailed logs from the serial port.
@@ -126,7 +200,9 @@ First, make sure all required Python packages are installed:
```python
python3 -m pip install pyserial colorama matplotlib
```
after that run the script:
After that run the script:
```sh
# For Linux
# This was tested on Debian and should work on most Linux systems.
@@ -135,63 +211,77 @@ python3 scripts/debugging_monitor.py
# For macOS
python3 scripts/debugging_monitor.py /dev/cu.usbmodem2101
```
Minor adjustments may be required for Windows.
## Internals
CrossPoint Reader is pretty aggressive about caching data down to the SD card to minimise RAM usage. The ESP32-C3 only
has ~380KB of usable RAM, so we have to be careful. A lot of the decisions made in the design of the firmware were based
on this constraint.
### Data caching
The first time chapters of a book are loaded, they are cached to the SD card. Subsequent loads are served from the
cache. This cache directory exists at `.crosspoint` on the SD card. The structure is as follows:
```
.crosspoint/
├── epub_12471232/ # Each EPUB is cached to a subdirectory named `epub_<hash>`
│ ├── progress.bin # Stores reading progress (chapter, page, etc.)
│ ├── cover.bmp # Book cover image (once generated)
│ ├── book.bin # Book metadata (title, author, spine, table of contents, etc.)
│ └── sections/ # All chapter data is stored in the sections subdirectory
│ ├── 0.bin # Chapter data (screen count, all text layout info, etc.)
│ ├── 1.bin # files are named by their index in the spine
│ └── ...
│
└── epub_189013891/
```
Deleting the `.crosspoint` directory will clear the entire cache.
Due the way it's currently implemented, the cache is not automatically cleared when a book is deleted and moving a book
file will use a new cache directory, resetting the reading progress.
For more details on the internal file structures, see the [file formats document](./docs/file-formats.md).
## Contributing
Contributions are very welcome!
If you are new to the codebase, start with the [contributing docs](./docs/contributing/README.md).
If you're looking for a way to help out, take a look at the [ideas discussion board](https://github.com/crosspoint-reader/crosspoint-reader/discussions/categories/ideas).
If there's something there you'd like to work on, leave a comment so that we can avoid duplicated effort.
Everyone here is a volunteer, so please be respectful and patient. For more details on our governance and community
principles, please see [GOVERNANCE.md](GOVERNANCE.md).
### To submit a contribution:
1. Fork the repo
2. Create a branch (`feature/dithering-improvement`)
3. Make changes
4. Submit a PR
---
CrossPoint Reader is **not affiliated with Xteink or any manufacturer of the X4 hardware**.
## Internals
Huge shoutout to [**diy-esp32-epub-reader** by atomic14](https://github.com/atomic14/diy-esp32-epub-reader), which was a project I took a lot of inspiration from as I
was making CrossPoint.
CrossPoint Reader is pretty aggressive about caching data down to the SD card to minimise RAM usage. The ESP32-C3 only has ~380KB of usable RAM, so we have to be careful. A lot of the decisions made in the design of the firmware were based on this constraint.
### Data caching
The first time chapters of a book are loaded, they are cached to the SD card. Subsequent loads are served from the
cache. This cache directory exists at `.crosspoint` on the SD card. The structure is as follows:
```text
.crosspoint/
├── epub_<hash>/ # one directory per book, named by content hash
│ ├── progress.bin # reading position (chapter, page, etc.)
│ ├── cover.bmp # generated cover image
│ ├── book.bin # metadata: title, author, spine, TOC
│ ├── css_rules.cache # parsed CSS rule cache
│ ├── img_* # rendered image cache files
│ └── sections/ # per-chapter layout cache
│ ├── 0.bin
│ ├── 1.bin
│ └── ...
├── settings.json # device settings
├── state.json # resume/runtime state
└── recent.json # recent books list
```
Removing `/.crosspoint` clears all cached metadata and forces a full regeneration on next open. Book deletes, overwrites, and moves done through the firmware or web UI clear or re-key matching caches; manual SD-card edits may leave stale cache directories behind.
For more details on the internal file structures, see the [file formats document](./docs/file-formats.md).
---
## Contributing
Contributions are welcome. If you're new to the codebase, start with the [contributing docs](./docs/contributing/README.md). For things to work on, check the [ideas discussion board](https://github.com/crosspoint-reader/crosspoint-reader/discussions/categories/ideas) — leave a comment before starting so we don't duplicate effort.
Everyone here is a volunteer, so please be respectful and patient. For governance and community expectations, see [GOVERNANCE.md](./GOVERNANCE.md).
---
## Community forks
One of the best things about open source is that anyone can take the code in a different direction. If you need something outside CrossPoint's [scope](./SCOPE.md), check out the community forks:
- [CrossInk](https://github.com/uxjulia/CrossInk) — Typography and reading tracking: Bionic Reading (bolds word stems to create fixation points), guide dots between words, improved paragraph indents, and replaces the default fonts with ChareInk/Lexend/Bitter.
- [papyrix-reader](https://github.com/bigbag/papyrix-reader) — Adds FB2 and MD format support. Actively maintained with Arabic script support. Custom themes via SD card.
- ~~[crosspet](https://github.com/trilwu/crosspet) — A Vietnamese fork that adds a Tamagotchi-style virtual chicken that grows based on your reading milestones (pages read, streaks, care). Also: Flashcards, Weather, Pomodoro timer, and mini-games.~~ (Unmaintained)
- [crosspoint-reader-cjk](https://github.com/aBER0724/crosspoint-reader-cjk) — Purpose-built for Chinese, Japanese, and Korean reading.
- [inx](https://github.com/obijuankenobiii/inx) — Completely reimagines the user interface with tabbed navigation.
- ~~[PlusPoint](https://github.com/ngxson/pluspoint-reader) — custom JS apps support.~~ (Unmaintained)
- [crosspoint-reader-papers3](https://github.com/juicecultus/crosspoint-reader-papers3) — Crosspoint port for M5Stack Paper S3.
- [t5s3-reader](https://github.com/ShallowGreen123/t5s3-reader) — Crosspoint port for LilyGo T5 ePaper S3 / T5S3 4.7-inch e-paper device.
**Note:** Many of these features will make their way into CrossPoint over time. We maintain a slower pace to ensure rock-solid stability and squash bugs before they reach your device.
Want to build your own device? Be sure to check out the [de-link](https://github.com/iandchasse/de-link) project.
---
CrossPoint Reader is **not affiliated with Xteink or any device manufacturer**.
Huge shoutout to [diy-esp32-epub-reader](https://github.com/atomic14/diy-esp32-epub-reader), which inspired this project.
+88
View File
@@ -0,0 +1,88 @@
# CrossPoint Reader Roadmap
This roadmap describes how CrossPoint is moving through the tighter scope defined in [SCOPE.md](SCOPE.md). It is
intentionally phased: Phase 0 closed out the commitments already in flight before locking down to the stricter
"fill gaps the stock firmware leaves" delineator.
Phases are sequential. We do not start the next phase until the prior one is wrapped or explicitly carried over.
---
## Phase 0 - Close Out Legacy Scope Items — **COMPLETE**
**Goal:** Land the work that was already in motion under the prior, broader scope so contributors are not left
hanging, and so we enter the stricter phases with a clean slate.
**Landed in Phase 0:**
* **RTL support PRs.** The in-flight right-to-left work was reviewed, iterated, and merged.
* **Dictionary PR.** The offline dictionary lookup work was reviewed and merged.
* **Bookmarks** feature. First-class navigation markers in EPUBs.
* ~~**Transparent sleep screens.**~~ Shelved; not picked back up under the stricter phases.
Phase 0 is closed. The tighter scope in [SCOPE.md](SCOPE.md) is now fully enforced. "But it was on the old roadmap"
is not a valid argument for accepting a PR.
---
## Phase 1 - Consolidation, Footprint, and Multi-Device Support — **IN PROGRESS**
**Goal:** Reduce memory and flash usage, clean up the codebase, and land the SDK / HAL generalization work so
CrossPoint runs cleanly on ESP32-based e-reader hardware beyond Xteink (X3 / X4), including ESP32-S3 class devices.
**Focus areas:**
* DRAM and heap fragmentation reduction across the reader core.
* Flash footprint reduction (dead code, redundant strings, oversized tables).
* Refactors that tighten the HAL / SDK boundary.
* Pluggable per-device SDK layers (display, input, storage, battery) and per-device build configuration without
forking the reader core.
* Documentation for adding a new ESP32 e-reader target.
* E-ink driver refinement (ghosting, partial update behavior).
**Closed during this phase:** new themes built into firmware, new external network connectors (sync engines, cloud
storage, remote file access).
---
## Phase 2 - Languages, Fonts, and Themes
**Goal:** With the codebase smaller and portable, make reading great in every language: multi-language support,
better font support with custom fonts, UI translations, and themes loaded from the SD card instead of consuming
flash.
**Focus areas:**
* Multi-language reading support (underserved languages, complex script support where realistic on ESP32 hardware).
* Better font support and custom fonts.
* UI languages and localization.
* Moving themes off-firmware to SD-loaded assets (see SCOPE.md Section 6).
* **Moving hyphenation files off-firmware.** Hyphenation rules vary per language and the files are large (German
alone is ~200KB). Today these eat flash budget that should be available for the reader core. The plan is to build
a downloader analogous to the existing font downloader and store the dictionaries on SD / SPIFFS, loading on
demand. This unlocks better hyphenation for long-word languages (German, Finnish, Norwegian, etc.) without paying
the flash cost up front.
This phase depends on Phase 1 cleanup landing first; otherwise we generalize a moving target.
---
## Out of Roadmap
The following are explicitly *not* on the roadmap. They may live in other CrossPoint forks; they will not be picked
up here:
* Interactive apps (games, calculators, notepads).
* Writing / authoring tools.
* Active connectivity features (RSS, news, browsers).
* PDF rendering as a first-class format.
See [SCOPE.md](SCOPE.md) for the full rationale.
---
## How This Roadmap Changes
* Phase boundaries are decided by maintainers, not by individual PRs.
* If a phase needs to be extended or an item carried over, that is documented here with a short note.
* Proposals for new phases or reordering should go through a Discussion first.
+80 -42
View File
@@ -1,62 +1,100 @@
# Project Vision & Scope: CrossPoint Reader
The goal of CrossPoint Reader is to create an efficient, open-source reading experience for the Xteink X4. We believe a
dedicated e-reader should do one thing exceptionally well: **facilitate focused reading.**
The goal of CrossPoint Reader is to create an efficient, open-source reading experience for ESP32-based e-reader devices. Xteink hardware (X3, X4) is where the project started and remains a primary target, but CrossPoint is explicitly broadening to support the wider ecosystem of small ESP32 e-ink readers. We believe a dedicated e-reader should do one thing exceptionally well: **facilitate focused reading.**
## 1. Core Mission
To provide a lightweight, high-performance firmware that maximizes the potential of the X4, prioritizing legibility and
usability over "swiss-army-knife" functionality.
To provide a lightweight, high-performance firmware that maximizes the potential of ESP32-based e-reader hardware, prioritizing legibility, performance, and usability over "swiss-army-knife" functionality.
## 2. Scope
CrossPoint is **not** a kitchen-sink firmware, and it is **not** Xteink-only. We want clean, maintainable code that the community can build on, and that runs across the range of ESP32 e-reader devices (ESP32-C3, ESP32-S3, and adjacent variants). Every accepted change should make that goal easier, not harder. Device-specific code should live behind the HAL / SDK boundary so the reader core stays portable.
## 2. Guiding Principle: Fill Gaps the Stock Firmware Leaves
CrossPoint exists to do the things the stock firmware does poorly or not at all. New work is evaluated against that delineator:
* **Does the stock firmware already do this well?** We should hit that bar or surpass it
* **Is another popular CrossPoint fork already solving this well?** If yes, we generally defer to that fork if it's not part of the core reading experience. e.g. stats
* **Does this directly improve the reading experience or the firmware's long-term maintainability?** If no, it is out of scope.
## 3. Current Focus
We are intentionally narrowing scope to consolidate the codebase as we open it up to more ESP32 e-reader devices.
During this period, the priorities are:
* **Memory footprint:** Reducing DRAM usage and heap fragmentation. The ESP32-C3 is the tightest target and sets the ceiling, but the gains benefit every ESP32 variant we run on.
* **Flash footprint:** Trimming binary size to leave room for additional device targets and features.
* **Code cleanup:** Refactoring, removing dead code, tightening abstractions, and improving readability.
* **Reading experience:** EPUB parsing and rendering, typography, hyphenation, line spacing, font handling, and legibility improvements.
### Temporarily Closed Areas
PRs in the following areas will be closed until this notice is lifted. Adding these now makes the cleanup and multi-device work materially harder:
* **New themes.** The existing theming surface is frozen.
* **New external network connectors.** This includes sync engines, cloud storage clients, OPDS extensions beyond what exists, remote file access, and any new "talk to a server" feature. We now have our own CrossPoint KOSync server which gives us a way to sync to 3rd party systems like Hardcover at an API level instead of bloating the firmware. If you're interested in helping here, the sync server is also open source.
If you are unsure whether your idea falls into one of these categories, open a Discussion first.
## 4. Scope
### In-Scope
*These are features that directly improve the primary purpose of the device.*
*Features that directly improve the core reading experience or the firmware's maintainability.*
* **User Experience:** E.g. User-friendly interfaces, and interactions, both inside the reader and navigating the
firmware. This includes things like button mapping, book loading, and book navigation like bookmarks.
* **Document Rendering:** E.g. Support for rendering documents (primarily EPUB) and improvements to the rendering
engine.
* **Format Optimization:** E.g. Efficiently parsing EPUB (CSS/Images) and other documents within the device's
capabilities.
* **Typography & Legibility:** E.g. Custom font support, hyphenation engines, and adjustable line spacing.
* **E-Ink Driver Refinement:** E.g. Reducing full-screen flashes (ghosting management) and improving general rendering.
* **Library Management:** E.g. Simple, intuitive ways to organize and navigate a collection of books.
* **Local Transfer:** E.g. Simple, "pull" based book loading via a basic web-server or public and widely-used standards.
* **Language Support:** E.g. Support for multiple languages both in the reader and in the interfaces.
* **Reference Tools:** E.g. Local dictionary lookup. Providing quick, offline definitions to enhance comprehension
without breaking focus.
* **Clock Display (device dependent):**
| Device | Scope |
| -- | -- |
| X3 | The X3 uses a dedicated DS3231 RTC, which maintains accurate time across sleep cycles and can be treated as a reliable wall clock. |
| X4 | The X4 relies on the ESP32-C3's internal RTC, which drifts significantly during deep sleep. NTP sync could correct this, with an appropriate user experience around connecting to the internet on wake or on demand. This causes some tension with the **Active Connectivity** section below, so please open a discussion about this UX if it's a feature you would find useful. |
* **EPUB Rendering & Optimization:** Improvements to the rendering engine, CSS/image handling, and parsing performance.
* **Typography & Legibility:** Custom font support, hyphenation, line and paragraph spacing, margins.
* **E-Ink Driver Refinement:** Reducing full-screen flashes (ghosting management) and improving general rendering.
* **Reading UX:** Bookmarks, progress tracking, button mapping, page navigation, and other in-reader interactions.
* **Library Management:** Simple, intuitive ways to organize and navigate a local book collection.
* **Memory, Flash, and Code Quality:** Refactors and cleanups that reduce resource use or improve maintainability, even without a user-visible feature.
### Out-of-Scope
*These items are rejected because they compromise the device's stability or mission.*
*Rejected because they compromise the device's stability, maintainability, or core mission.*
* **Interactive Apps:** No Notepads, Calculators, or Games. This is a reader, not a PDA.
* **Active Connectivity:** No RSS readers, News aggregators, or Web browsers. Background Wi-Fi tasks drain the battery
and complicate the single-core CPU's execution.
* **Media Playback:** No Audio players or Audio-books.
* **Complex Annotation:** No typed out notes. These features are better suited for devices with better input
capabilities and more powerful chips.
* **Interactive Apps:** No notepads, calculators, or games. These belong in other forks and are not part of CrossPoint's focus.
* **Writing / Authoring Tools:** No typed notes, journals, or editors. Input hardware and RAM are wrong for this, and other forks already explore this space.
* **Active Connectivity:** No RSS readers, news aggregators, or web browsers. Background Wi-Fi drains the battery and complicates the single-core CPU.
* **PDF Rendering:** PDFs are fixed-layout documents, so rendering them requires displaying pages as images rather than reflowable text, resulting in constant panning and zooming that makes for a poor reading experience on e-ink. Out of scope on the current hardware class.
### In-scope — Technically Unsupported
## 5. Calls to Action
*These features align with CrossPoint's goals but are impractical on the current hardware or produce poor UX.*
These are the areas where contributor help is most valuable right now. If you want to take one of these on, open a Discussion or issue first so we can coordinate.
* **PDF Rendering:** PDFs are fixed-layout documents, so rendering them requires displaying pages as images rather than reflowable text — resulting in constant panning and zooming that makes for a poor reading experience on e-ink.
### Theme System: Move Themes Off-Firmware
## 3. Idea Evaluation
We want to abstract themes out of the firmware entirely so they no longer consume flash, and instead load from the SD card. This directly supports the current focus on flash footprint and code cleanup.
While I appreciate the desire to add new and exciting features to CrossPoint Reader, CrossPoint Reader is designed to be
a lightweight, reliable, and performant e-reader. Things which distract or compromise the device's core mission will not
be accepted. As a guiding question, consider if your idea improve the "core reading experience" for the average user,
and, critically, not distract from that reading experience.
* **Status:** [@itsthisjustin](https://github.com/itsthisjustin) plans to take this on eventually but is very open to someone else claiming it sooner.
* **Why it matters:** Every built-in theme costs flash that we would rather spend on rendering, fonts, or future device support. SD-loaded themes also let users customize without rebuilding firmware. It also leads to SD font loading for better language support in the UI.
* **How to claim:** Comment on the relevant Discussion (or open one) before starting.
> **Note to Contributors:** If you are unsure if your idea fits the scope, please open a **Discussion** before you start
> coding!
### Identifying Other Stock-Firmware Gaps
We want help cataloguing things the stock firmware (and other popular CrossPoint forks) handle poorly or not at all, so future work has a clear target list. Particularly interested in:
* **RTL (right-to-left) text support:** Arabic, Hebrew, Persian, and similar scripts.
* **Languages with poor stock and fork coverage:** Especially those that need shaping, complex layout, or non-Latin font work that nobody is handling well today.
* **Other gaps:** Rendering edge cases, accessibility issues, input quirks, anything stock does badly and existing forks have not fixed.
If you can read or use the device in one of these languages, your feedback (even without code) is genuinely useful. Open a Discussion with concrete examples (screenshots, sample EPUBs, expected vs actual behavior) and we will prioritize from there.
## 6. Funding and Contributor Sustainability
CrossPoint uses [Royalty.dev](https://royalty.dev) (yes, a product built by [@itsthisjustin](https://github.com/itsthisjustin)) to fund contributors. There has been some tension in the community around this, so the intent is being clarified here directly.
**Why we do this:**
* To maintain long-term interest from contributors and maintainers, in direct response to substantial community requests for a way to give back.
* To motivate contributors to invest in the *core* project rather than spinning up competing forks.
* To help pay for new ESP32 devices so we can port CrossPoint to additional hardware.
* To give the project a credible long-term path to sustainability.
**How it works:**
* Funds are distributed automatically to contributors based on impact to the codebase and tenure on the project.
* Over **$600** was raised in the first few hours after opening up funding, which is a signal the demand is real.
* The exact scoring methodology is published at <https://app.royalty.dev/transparency>.
**This is not fixed in stone.** The weighting, eligibility, and distribution rules can be tweaked as we learn what works for this project. If you have concerns or suggestions about how funds are allocated, open a Discussion. The goal is a system that fairly recognizes the people doing the work, not a perfect one on day one.
+327 -76
View File
File diff suppressed because it is too large. Load diff
Loaded 20 of 476 files, more files were not shown because too many files have changed in this diff. Show more