## Summary
Adds Xteink X3 hardware support to CrossPoint Reader. The X3 uses the
same SSD1677 e-ink controller as the X4 but with a different panel
(792x528 vs 800x480), different button layout, and an I2C fuel gauge
(BQ27220) instead of ADC-based battery reading.
All X3-specific behavior is gated by runtime device detection — X4
behavior is unchanged.
Depends on community-sdk X3 support: open-x4-epaper/community-sdk#19
(merged).
## Changes
### HAL Layer
**HalGPIO** (`lib/hal/HalGPIO.cpp/.h`)
- I2C-based device fingerprinting at boot: probes for BQ27220 fuel
gauge, DS3231 RTC, and QMI8658 IMU to distinguish X3 from X4
- Detection result cached in NVS for fast subsequent boots
- Exposes `deviceIsX3()` / `deviceIsX4()` helpers used throughout the
codebase
- X3 button mapping (7 GPIOs vs X4's layout)
- USB connection detection and wake classification for X3
**HalDisplay** (`lib/hal/HalDisplay.cpp/.h`)
- Calls `einkDisplay.setDisplayX3()` before init when X3 is detected
- Requests display resync after power button / flash wake events
- Runtime display dimension accessors (`getDisplayWidth()`,
`getDisplayHeight()`, `getBufferSize()`)
- Exposed as global `display` instance for use by image converters
**HalPowerManager** (`lib/hal/HalPowerManager.cpp/.h`)
- X3 battery reading via I2C fuel gauge (BQ27220 at 0x55, SOC register)
- X3 power button uses GPIO hold for deep sleep
### Display & Rendering
**GfxRenderer** (`lib/GfxRenderer/GfxRenderer.cpp/.h`)
- Buffer size and display dimensions are now runtime values (not
compile-time constants) to support both panel sizes
- X3 anti-aliasing tuning: only the darker grayscale level is applied to
avoid washed-out text on the X3 panel. X4 retains both levels via
`deviceIsX4()` gate
**Image Converters** (`lib/JpegToBmpConverter`, `lib/PngToBmpConverter`)
- Cover image prescale target uses runtime display dimensions from HAL
instead of hardcoded 800x480
### UI Themes
**BaseTheme / LyraTheme** (`src/components/themes/`)
- X3 button position mapping for the different physical layout
- Adjusted UI element positioning for 792x528 viewport
### Boot & Init
**main.cpp**
- X3 hardware detection logging
- Adjusted init sequence for X3 (no `HalSystem::begin()` dependency on
X3 path)
**HomeActivity**
- Uses runtime `renderer.getBufferSize()` instead of static
`GfxRenderer::getBufferSize()`
FYI I did not add support for the gyro page turner. That can be it's own
PR.
Full Hungarian localization for the firmware with all UI elements and
system messages translated.
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
* **What changes are included?**
Added hungarian.yaml language file
Translated all UI elements and system messages into Hungarian
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
I am a native Hungarian speaker. The initial translation was assisted by
AI, but I reviewed and corrected all translation errors to ensure a
natural and accurate Hungarian localization.
### 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**_
## Summary
* Add Lithuanian transilation
### 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: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
## Summary
Replace per-pixel getRenderMode() + rotateCoordinates() + bounds checks
with a DirectPixelWriter struct that pre-computes orientation and render
mode state once per row. Use bitwise ops instead of division/modulo for
cache pixel packing. Skip PNG cache allocation when buffer exceeds 48KB
(framebuffer size) since PNG decode is fast enough that caching provides
minimal benefit, and the large buffer competes with the 44KB PNG decoder
for heap.
## Additional Context
Measured improvements on ESP32-C3 @ 160MHz:
- JPEG decode: 5-7% faster (1:1 scale)
- PNG decode: 15-20% faster (1:1 scale)
- Cache renders: 3-6% faster across both formats
- Eliminates "Failed to allocate cache buffer" errors for large PNGs
---
### 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 >**_
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
Added new Dutch translations
* **What changes are included?**
New Dutch translations
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**PARTIALLY**_
Added missing strings for the French translation.
## Summary
Adding the missing French translated strings before the next PR.
No modification done to the preexisting strings, only added new ones.
## Additional Context
N/A
---
### 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.
No AI usage.
## Summary
* **What is the goal of this PR?** Updating German language file
* **What changes are included?**
## Additional Context
- None
---
### 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 >**_
## Summary
Polish localization for `STR_SHOW_HIDDEN_FILES`
---
### 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**_
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
Adds the missing Catalan translation for STR_SHOW_HIDDEN_FILES in
lib/I18n/translations/catalan.yaml.
* **What changes are included?**
Only modified catalan.yaml file
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? NO
## Summary
* **What is the goal of this PR?**
fix swedish translation
* **What changes are included?**
lib\I18n\translations\swedish.yaml
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**NO**_
Adds the missing Turkish translation for STR_SHOW_HIDDEN_FILES in
lib/I18n/translations/turkish.yaml.
- Added: STR_SHOW_HIDDEN_FILES: "Gizli Dosyaları Göster"
- Scope: Turkish only (single-key micro-fix)
This follows up the missing-translations callout in #1483.
Co-authored-by: Barış Albayrak <barisa@pop-os.lan>
## Summary
* Adding Spanish translation for string `STR_SHOW_HIDDEN_FILES`
---
### 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**_
## Summary
* **What is the goal of this PR?** It's difficult to distinguish
directory names from normal file entries, so they are displayed now as
"[dir]" instead of "dir" for classic theme
* **What changes are included?**
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**NO**_
## Summary
* **What is the goal of this PR?** Add a windows equivalent for the
linux clang-format-fix script
* **What changes are included?**
## Additional Context
```
.SYNOPSIS
Runs clang-format -i on project *.cpp and *.h files.
.DESCRIPTION
Formats all C/C++ source and header files in the repository, excluding
generated, vendored, and build directories (open-x4-sdk, builtinFonts,
hyphenation tries, uzlib, .pio, *.generated.h).
The clang-format binary path is resolved once and cached in
.local/clang-format-fix.local. On first run it checks a default path,
then PATH, then common install locations. Edit the .local file to
override manually.
.PARAMETER g
Format only git-modified files (git diff --name-only HEAD) instead of
the full tree.
.PARAMETER h
Show this help text.
```
---
### 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 >**_
## Summary
**What is the goal of this PR?**
When viewing a page with images and anti-aliasing enabled, the
`imagePageWithAA` path renders the page twice with fast refreshes (blank
image area, then restore). Both passes called `renderStatusBar()`, which
reads the battery percentage live. If the value changed between the two
renders (e.g. 88% -> 87%), the digits would overlap on screen.
Fix: Removed the redundant `renderStatusBar()` from the second BW
render. The status bar is already drawn and displayed in the first pass,
and only the image area needs restoration.
---
### 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**_
## Summary
- Add CSS `display: none` support to the EPUB rendering pipeline (fixes
#1431)
- Parse `display` property in stylesheets and inline styles, with full
cascade resolution (element, class, element.class, inline)
- Skip hidden elements and all their descendants in
`ChapterHtmlSlimParser`
- Separate display:none check for `<img>` tags (image code path is
independent of the general element handler)
- Flush pending text blocks before placing images to fix layout ordering
(text preceding an image now correctly renders above it)
- Bump CSS cache version to 4 to invalidate stale caches
- Add test EPUB (`test_display_none.epub`) covering class selectors,
element selectors, combined selectors, inline styles, nested hidden
content, hidden images, style priority/override, and realistic use cases
## Problem
Many e-ink readers have limited image decoder support natively.
EPUBs with images in other formats than **baseline JPEG** frequently
cause:
- **Broken images**: pages render as blank, corrupted noise, or never
load
- **Slow rendering**: unoptimized images cause severe delays on e-ink
hardware, up to 7 seconds per page turn, with cover images taking up to
59 seconds to render
- **Broken covers**: the book thumbnail never generates
Fixing this today requires external tools before uploading.
---
## What this PR does
Adds an **optional, on-demand EPUB optimizer** to the file upload flow.
When enabled,
it converts all images to baseline JPEG directly in the browser — no
server, no internet,
no external tools needed.
**Conversion is opt-in. The standard upload flow is unchanged.**
---
## Real-world impact
The optimizer was applied in batch to **61 EPUBs**:
- 60 standard EPUBs: 198 MB → 55 MB (**−72.2%**, 143 MB saved)
- Text-dominant books: 8–46% smaller (covers and inline images
converted)
- Image-heavy / illustrated books: 65–93% smaller
- 1 Large manga volume (594 MB): 594 MB → 72 MB (**−87.8%**, 522 MB
saved)
- EPUB structural integrity fully maintained — zero new validation
issues introduced across all 61 books
*Size and integrity analysis:
[epub-comparator](https://github.com/pablohc/epub-comparator)*
From that set, **17 books were selected** as a representative sample
covering different content
types: image-heavy novels, pure manga, light novels with broken images,
and text-dominant books.
Each was benchmarked on two devices running in parallel, one on `master`
and one
on `PR#1224` — measuring render time across ~30 pages per book on
average.
### Rendering bugs fixed
| Book | Problem (original) | After optimization |
|------|--------------------|--------------------|
| Fairy Tale — Stephen King | Cover took **59.7 s** to render | 2.1 s
(−96%) |
| Cycle of the Werewolf — Stephen King | Cover took **23.3 s** to render
| 1.7 s (−93%) |
| Tomie: Complete Deluxe Ed. — Junji Ito | Cover took **18.3 s** to
render | 2.0 s (−89%) |
| Joel Dicker — El tigre (Ed. Ilustrada) | Cover took **14.5 s** to
render | 1.4 s (−90%) |
| Jackson, Holly — Asesinato para principiantes | Cover failed
completely (blank) | 2.0 s ✓ |
| Sentenced to Be a Hero — Yen Press | Cover failed, **8 images failed
to load** | All fixed ✓ |
| Flynn, Gillian — Perdida | Cover failed completely (blank) | 1.6 s ✓ |
| Chandler, Raymond — Asesino en la lluvia | Cover failed completely
(blank) | 2.0 s ✓ |
### Page render times — image-heavy EPUBs (avg per page)
| Book | Pages | Avg original | Avg optimized | Improvement | File size
|
|------|-------|-------------|---------------|-------------|-----------|
| Fairy Tale — Stephen King | 30 | 3,028 ms | 1,066 ms | **−64.8%** |
32.4 MB → 9.1 MB (−72%) |
| Cycle of the Werewolf — Stephen King | 33 | 3,026 ms | 1,558 ms |
**−48.5%** | 35.1 MB → 2.9 MB (−92%) |
| Joel Dicker — El tigre (Ed. Ilustrada) | 16 | 1,846 ms | 1,051 ms |
**−43.1%** | 5.3 MB → 0.4 MB (−93%) |
| Tomie: Complete Deluxe Ed. — Junji Ito | 30 | 4,817 ms | 2,802 ms |
**−41.8%** | 593.8 MB → 72.2 MB (−87.8%) |
| Sentenced to Be a Hero — Yen Press | 30 | 1,719 ms | 1,388 ms |
**−19.2%** | 15.2 MB → 1.6 MB (−90%) |
### Text-heavy EPUBs — no regression
| Book | Pages | Avg original | Avg optimized | Delta |
|------|-------|-------------|---------------|-------|
| Christie — Asesinato en el Orient Express | 30 | 1,672 ms | 1,646 ms |
−1.6% |
| Flynn — Perdida | 30 | 1,327 ms | 1,291 ms | −2.7% |
| Dicker — La verdad sobre el caso Harry Quebert | 30 | 1,132 ms | 1,084
ms | −4.2% |
| Hammett — El halcón maltés | 30 | 1,009 ms | 966 ms | −4.3% |
| Chandler — Asesino en la lluvia | 30 | 989 ms | 1,007 ms | +1.8% |
*Differences within ±5% — consistent with device measurement noise.*
*Render time benchmark:
[epub-optimization-benchmark](https://github.com/pablohc/epub-optimization-benchmark)*
---
## How to use it
**Single file:**
1. Click **Upload** (top of the page) — a modal opens. Use **Choose
files** to select one EPUB from your device.
2. Check **Optimize**.
- *(Optional)* Expand **Advanced Mode** — adjust quality, rotation, or
overlap; set individual images to H-Split / V-Split / Rotate.
3. Click **Optimize & Upload**.
**Batch (2+ files):**
1. Click **Upload** (top of the page) — a modal opens. Use **Choose
files** to select multiple EPUBs from your device.
2. Check **Optimize**.
- *(Optional)* Expand **Advanced Mode** — adjust quality.
3. Click **Upload** — all files are converted and uploaded sequentially.
Upload a batch of files, without optimization:
<img width="810" height="671" alt="image"
src="https://github.com/user-attachments/assets/d892ae13-0b87-4ea4-b6b8-340d56efc763"
/>
Batch file upload, with standard optimization:
<img width="809" height="707" alt="image"
src="https://github.com/user-attachments/assets/d32dbc88-1208-4555-bfcf-330ab91d2174"
/>
Optimization Phase (1/2):
<img width="807" height="1055" alt="image"
src="https://github.com/user-attachments/assets/fd4cd5f9-e56e-4ca1-9777-6926b9baf2bb"
/>
Upload Phase (2/2):
<img width="805" height="1065" alt="image"
src="https://github.com/user-attachments/assets/483294f0-02f0-4569-ae11-c10b3581d747"
/>
Batch upload successfully confirmed:
<img width="812" height="1043" alt="image"
src="https://github.com/user-attachments/assets/80c135bf-05c3-4c80-8755-2a04c68235bc"
/>
---
## Options
**Always active when the converter is enabled:**
- Converts PNG, WebP, BMP, GIF → baseline JPEG
- Smart downscaling to 480×800 px max (preserves aspect ratio)
- True grayscale for e-ink (BT.709 luminance, always on)
- SVG cover fix + OPF/NCX compliance repairs
**Advanced Mode (opt-in) — single file:**
- JPEG quality presets: 30% / 45% / 60% / 75% / **85%** (default) / 95%
- Rotation direction for split images: CW (default) / CCW
- Min overlap when splitting: 5% (default) / 10% / 15%
- Auto-download conversion log toggle (detailed stats per image)
- Per-image picker: set Normal / H-Split / V-Split / Rotate per image
individually,
with "Apply to all" for bulk assignment
**Advanced Mode (opt-in) — batch (2+ files):**
- JPEG quality presets: 30% / 45% / 60% / 75% / **85%** (default) / 95%
- Auto-download conversion log toggle (aggregated stats for all files)
---
## ⚠️ Known limitations
**KoReader hash-based sync will break** for converted files. The file
content changes,
so the hash no longer matches the original. Filename-based sync is
unaffected.
If you rely on KoReader hash sync, use the Calibre plugin or the web
tool instead.
---
## Build size impact
| Metric | master (53beeee) | PR #1224 (a2ba5db) | Delta |
|---------------|------------------|--------------------|----------------|
| Flash used | 5,557 KB | 5,616 KB | +59 KB (+1.1%) |
| Flash free | 843 KB | 784 KB | −59 KB |
| Flash usage | 86.8% | 87.7% | +0.9 pp |
| RAM used | 95,156 B | 95,156 B | no change |
> Both builds compiled with `gh_release` environment in release mode
(ESP32-C3, 6,400 KB Flash).
> The +59 KB increase is entirely due to `jszip.min.js` embedded as a
> gzipped static asset served from Flash. RAM usage is identical,
> confirming no runtime overhead — the library runs in the browser,
> not on the ESP32. ~784 KB of Flash remain available.
---
## Alternatives considered
| Approach | Friction |
|----------|---------|
| **This PR** — integrated in upload flow | Zero: convert + upload in
one step, offline, any browser |
| Calibre plugin (in parallel development) | Requires a computer with
Calibre installed, same network |
| Web converters | Requires extra upload / download / transfer steps |
---
## Credits
Based on the converter algorithm developed by @zgredex.
Co-authored-by: @zgredex
---
### AI Usage
Did you use AI tools to help write this code? **PARTIALLY**
---------
Co-authored-by: zgredex <zgredex@users.noreply.github.com>
## Summary
Fixes#1263
I spent half of my day(-off) reverse engineering the stock english
firmware V3.1.1, it's more or less like solving a sudoku with some known
pieces (like debug strings, known static addresses, known compiled
function, etc) and then the task is to guess the rest.
Long story short, this is the sleep routine that they use:
<img width="674" height="604" alt="image"
src="https://github.com/user-attachments/assets/6d53ce44-7bae-40c7-b4fb-24f898dbcc05"
/>
From the code above:
- They pull down GPIO13 (value = 0xd) before sleep
- They verify that power button is released by doing a delay loop of
50ms, similar to what we're doing
- `esp_sleep_config_gpio_isolate` is called but I'm not 100% sure why
- Pull up power button, note that it's likely redundant because power
button should already pulled up by `InputManager`
- `param1` and `param2` means enabling front/side buttons for wake up,
but it doesn't used in the code in reality. But I think it's physically
impossible, see the explanation below
- `param3` means "wake up from power button"
- `esp_sleep_start` is used; there is a logic to handle if it fails to
sleep, then retry recursively (no idea why!)
My observation is that they use GPIO13 so that it will be on HIGH state
when the chip is powered on, without any user space code to keep it on
that state. And once going to deep sleep, it goes into FLOATING by
default. That may explain why it need to be in LOW state before going to
sleep. (Nice trick btw)
Looking again at the circuit diagram provided
[here](https://github.com/sunwoods/Xteink-X4/blob/main/readme-img/sch.jpg)
(note: it's not official):
<img width="705" height="384" alt="image"
src="https://github.com/user-attachments/assets/b98d59fd-47ca-4d3d-a24a-94bf999e957b"
/>
It kinda make sense as the GPIO13 and VBUS (USB VCC) have the same role,
they are part of a simple "battery protection" cirtuit
Now, we may wonder, how the device wake up when there is no battery at
all?
<img width="440" height="323" alt="image"
src="https://github.com/user-attachments/assets/2981c411-239b-49a7-b9f7-9a75b6c1b6d3"
/>
It seems like power button is not just a simple switch between GPIO3 and
ground, but it also linked the POWER_CTRL, which leads to nowhere on the
diagram, but I suppose it connects the battery back for a short amount
of time, just enough for the MCU to wake up, and GPIO13 goes HIGH again.
It may also explain why power button becomes non-responsive for ~1
second after power on, as it's being pulled up by the current from
battery (remind: high = not pressed, low = pressed)
To test the theory above, I simply **comment out** the
`esp_deep_sleep_enable_gpio_wakeup`:
- On battery, power button works as nothing happen
- On USB, it doesn't wake up, I need to press RST
---
Important things about my analysis:
1. I had to name every function on the code above **manually**, but I'm
99% confident about it. The only function that I'm not sure is
`esp_wifi_bt_power_domain_off` ; Edit: it was indeed mislabeled, see
https://github.com/crosspoint-reader/crosspoint-reader/pull/1298#discussion_r2879670852
2. Some logic inside the stock firmware looks very strange, there is
almost no mention to "arduino" in the hardware, suggesting that they may
just call esp-idf functions directly, bypassing the arduino abstraction.
---
### 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: Zach Nelson <zach@zdnelson.com>
## Summary
**What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
Fix prewarm perf when a page contains many styles.
The prewarm page buffer was a single slot, so each `prewarmCache` call
for a new font style freed the previous style's glyphs. On pages with
multiple styles (regular + bold + italic), only the last style was
prewarmed. The others fell through to the hot-group compaction path at
~2-3ms per glyph.
This was most visible on rich formatting (e.g. this [Czech prayer
book](https://stahuj.kancional.cz/e-kniha/kancional.epub) with bold
headings, italic liturgical text, and regular body), where page renders
took 3-5 seconds instead of ~700ms.
Fix: use up to 4 page buffer slots (one per font style) so all styles
stay prewarmed simultaneously.
Fixes#1450.
---
### 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: to diagnose and
brainstorm solutions.
## Summary
**What is the goal of this PR?**
This change deletes include/README, which is a PlatformIO boilerplate
placeholder file explaining what header files are. The include/
directory isn't used by this project (headers live in lib/ and src/), so
this is just cleanup.
### 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**_
## Summary
* A simple tweak to pre-index the next chapter silently during normal
reading.
* Triggers silent pre-indexing of the next chapter when the penultimate
page of a chapter is rendered to reduce visible interruptions.
* Keeps existing indexing with popup when a reader jumps directly into
an unindexed chapter.
## Additional Context
* Reader input is temporarily blocked during silent indexing to avoid
navigation/index state conflicts.
* The penultimate page is used because readers typically spend longer
there than on the final page.
* This change optimizes linear reading flow while preserving reliable
indexing for non-linear navigation.
## Possible Improvements
* Add a setting for First Page Indexing vs Penultimate Page Pre-indexing
* Display an indexing icon in the status bar instead of using a popup
that overlaps book text.
Tested on device:
https://www.dropbox.com/scl/fi/29g5kjqgsi5e4hgujv38u/Silent-Indexing.MOV?rlkey=yemi4mosmev5vicaa7gpe49qw&dl=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? _**YES**_
---------
Co-authored-by: Jake Kenneally <jakekenneally@gmail.com>
## Summary
**What is the goal of this PR?**
Update SKILL.md to stop instructing contributors to commit `I18nKeys.h`
and `I18nStrings.h`. All three generated i18n files have been gitignored
since ff577540 and are regenerated at build time.
---
### 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**_
## Summary
* **What is the goal of this PR?** All praise goes to @didacta for his
PR #537. Just picked up the reviewer comments to contain the changes as
suggested (there was no response for more than 6 weeks, so I wanted to
reanimate this feature).
Just one addition: should recognize usb cable plug ins / retractions and
update the icon immediately
* **What changes are included?**
## Additional Context
see #537
---
### 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 >**_
## Summary
**What is the goal of this PR?**
Until today, I sincerely thought we were building without exception
support. The codebase is not set up with any exception handling
infrastructure. The SKILL.md file specifies no exceptions.
I just learned that we actually were building with exceptions enabled,
with `-fexceptions` coming from
~/.platformio/packages/framework-arduinoespressif32-libs/esp32c3/pioarduino-build.py.
This change removes the `-fexceptions` flag and adds `-fno-exceptions`.
The result is **53,670 bytes in flash savings**.
---
### 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**_
## Summary
* **What is the goal of this PR?**
Removing no longer used i18n keys/string, to reduce (~28k) used flash
space.
To correct to swedish translations for `STR_FONT_SIZE` and
`STR_KOREADER_SYNC`.
* **What changes are included?**
`lib\I18n\translations\*`
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**NO**_
## Summary
* **What is the goal of this PR?** Add setting to display hidden files /
directories in filebrowser / web file browser
* **What changes are included?**
## Additional Context
-
---
### 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 >**_
## Summary
* **What is the goal of this PR?** Fix leak on decode error path in JPEG
converter
* **What changes are included?**
Unif resource cleanup
## Additional Context
---
### 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**_
Identification of the issue by AI
## Summary
* **What is the goal of this PR?** Implements Kazakh language
* **What changes are included?**
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? YES
---------
Co-authored-by: Okzhetpes <okhzetpes.20@gmail.com>
## Summary
* **What is the goal of this PR?** fix edge case for definition
## Additional Context
If loadFromFile() returns false (no state file exists — first boot, or
SD missing), lastSleepImage is never set and contains garbage.
[SleepActivity.cpp:83] then uses it in a while comparison to avoid
repeating the same image. The JSON path (doc["lastSleepImage"] |
(uint8_t)0) handles it, but only if the file exists.
---
### 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 **_
## Summary
* **What is the goal of this PR?** The "Toggle" label while on the "tab"
actions of settings screen was misleading. Will show "Select" now ,
while "Toggle" remains in place for all 'real' settings
* **What changes are included?**
## Additional Context
<img width="240" alt="1"
src="https://github.com/user-attachments/assets/dc198716-0aad-4c75-96fe-52595625e69d"
/>
<img width="240" alt="2"
src="https://github.com/user-attachments/assets/85ce5368-801c-489d-aa94-51f126c3ddc8"
/>
---
### 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 **_
## Summary
- Pressing Back while browsing settings within a category now jumps
focus to the category tab bar
- Pressing Back again from the tab bar exits settings to home
- Previously, Back always exited directly to home regardless of scroll
position
Closes#797
## Test plan
- [ ] Scroll deep into a settings category → press Back → tab bar is
focused
- [ ] Press Back again from tab bar → exits to home screen
- [ ] Use category switching (continuous hold) → still works as before
- [ ] Settings are saved on exit (not on tab-bar jump)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: bkb <bkb@arcnode.xyz>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary
* **What is the goal of this PR?** Fixing / extending the hyphenation
logic to deal with words containing an apostophe as raised in #1186
* **What changes are included?**
## Additional Context
---
### 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**_ (as the
user provided a thorough analysis that I followed)
## Purpose
This PR includes some preparatory changes that are needed for an
upcoming performant CJK font feature. The changes have no impact on
render time and heap allocation for latin text. **Despite this, I think
these changes stand on their own as a better font
compression/decompression implementation.**
## Summary
- Font decompressor rewrite: Replaced the 4-slot LRU group cache with a
two-tier system — a page buffer (glyphs prewarmed before rendering
begins) and a hot-group fallback (last decompressed group retained for
non-prewarmed
glyphs).
- Byte-aligned compressed bitmap format: Glyph bitmaps within compressed
groups are now stored row-padded rather than tightly packed before
DEFLATE compression, improving compression ratios by making identical
pixel rows produce
identical byte patterns. Glyphs are compacted back to packed format on
demand at render time. Reduces flash size by 155 KB.
- Page prewarm system: Added `Page::collectText` and
`Page::getDominantStyle` to extract per-style glyph requirements before
rendering, and `GfxRenderer::prewarmFontCache` to pre-decompress only
the groups needed for the dominant style
— eliminating mid-render decompression for the common case.
- UTF-8 robustness fixes: `utf8NextCodepoint` now validates continuation
bytes and returns a replacement glyph on malformed input;
`ChapterHtmlSlimParser` correctly preserves incomplete multi-byte
sequences across word-buffer flush
boundaries rather than splitting them.
---
### 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**_ Architecture and
design was done by me, refined a bit by Claude. Code mostly by Claude,
but not entirely.
## Summary
* **What is the goal of this PR?**
- Improve and add the latest missing Spanish translations
* **What changes are included?**
- Replaced `Der.` with `Dcha.`, as it's a more common abbreviation
- Replaced `Selec.` with `Selecc.` (same reason as in the previous case)
- Added missing strings `STR_IMAGES`, `STR_IMAGES_DISPLAY`,
`STR_IMAGES_PLACEHOLDER` and `STR_IMAGES_SUPPRESS`
- Rewording certain translations to make them clearer
---
### 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**_
## Summary
**What is the goal of this PR?**
### Problem
Inter-word gap widths were computed as two separately-snapped integers:
```cpp
gap = getSpaceWidth(fontId, style); // fp4::toPixel(spaceAdvance)
gap += getSpaceKernAdjust(fontId, leftCp, rightCp, style); // fp4::toPixel(kern1 + kern2)
```
Because `fp4::toPixel(a) + fp4::toPixel(b)` can differ from
`fp4::toPixel(a + b)` by +/-1 pixel when the fractional parts straddle a
rounding boundary, each inter-word space could be one pixel wider or
narrower than the correct value. This affected line-break width
decisions and word-position accumulation across the whole paragraph
layout pipeline.
### Fix
Replaces `getSpaceKernAdjust()` with `getSpaceAdvance(fontId, leftCp,
rightCp, style)`, which combines the space glyph advance and both
flanking kern values (`kern(leftCp, ' ')` + `kern(' ', rightCp)`) into a
single fixed-point sum before the snap:
```cpp
return fp4::toPixel(spaceAdvanceFP + kern(leftCp, ' ') + kern(' ', rightCp));
```
This is the same single-snap pattern already used by `getTextAdvanceX`
for word widths.
### Changes
- **`GfxRenderer`**: Replaces `getSpaceKernAdjust()` with
`getSpaceAdvance()`. `getSpaceWidth()` is retained for the
single-space-word case in `measureWordWidth` where no adjacent-word kern
context is available.
- **`ParsedText`**: All four call sites (`computeLineBreaks`,
`computeHyphenatedLineBreaks`, and both loops in `extractLine`) updated
to use `getSpaceAdvance()`. The now-redundant `spaceWidth`
pre-computation and parameter are removed from all three internal layout
functions.
---
### 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 to analyze for
correctness**_
## Summary
Fixes a crash (load access fault) when opening EPUB chapters whose first
text block exceeds 750 words.
## Changes
* **Crash fix (`addLineToPage`)**: Added a null guard for `currentPage`.
If `makePages()` hasn't run yet (which can happen when the first block
triggers the "text block too long" split path), the page is now created
on demand.
* **Layout fix (`characterData`)**: The early-split path previously used
`viewportWidth`, ignoring CSS margins and padding. It now computes
`effectiveWidth` using `totalHorizontalInset()`, consistent with
`makePages()`.
## Additional Context
* Crash signature: `MCAUSE=0x5` (load access fault), `A0=0x0` (`Page*`
null), `MTVAL=0x4 / 0x8` (offsets into `Page::elements`).
* Confirmed in two user reports reported in #1328
* Tested on PR #1357 (not on `master`).
---
### 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 >**_
## Summary
* **What is the goal of this PR?** On a cold boot (or after a crash that
corrupts RTC RAM), logHead contains garbage. Then addToLogRingBuffer
does: ``strncpy(logMessages[logHead], message, MAX_ENTRY_LEN - 1); ``
With garbage logHead, this computes a completely invalid address. The %
MAX_LOG_LINES guard on line 16 only runs after the bad store, which is
too late. The fix is to clamp logHead before use.
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**NO**_ (did use claude
for the magic hash value)
## Summary
* **What is the goal of this PR?** Avoid repeated full central-directory
scans by using early stop when all requested targets are already
matched.
* **What changes are included?**
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _** PARTIALLY **_
Identified by AI
## Summary
Extract shared reader utilities (`ReaderUtils.h`) to reduce duplication
across `EpubReaderActivity`, `TxtReaderActivity`, and (upcoming)
`MarkdownReaderActivity`.
Utilities extracted:
- `applyOrientation()` — orientation switch logic
- `detectPageTurn()` — page navigation input detection
- `renderAntiAliased()` — grayscale anti-aliasing pass
- `displayWithRefreshCycle()` — refresh mode cadence
- `GO_HOME_MS` — back button timing constant
## Impact
Flash: 32 bytes saved (6006441 → 6006409 bytes). Minimal immediate gain,
but meaningful once markdown reader and future reader types share these
functions.
Code quality: Eliminates ~100 lines of duplicated logic spread across
multiple files. All readers now follow the same patterns for
orientation, input handling, and rendering.
## Rationale
This refactor is preparation for markdown support, which requires
identical input and rendering logic. Instead of copy-pasting these
patterns a third time, all readers now share a single, tested
implementation. Future reader types can reuse `ReaderUtils` without
duplication.
---
## AI Usage
Did you use AI tools to help write this code? YES Claude extracted the
code, under my guidance. Tested on my device and seems to work fine.
## Summary
Follow-up
https://github.com/crosspoint-reader/crosspoint-reader/pull/1145
- Fix log not being record without USB connected
- Bump release log to INFO for more logging details
---
### 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: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
## Summary: Enable footnote anchor navigation in EPUB reader
This PR extracts the core anchor-to-page mapping mechanism from PR #1143
(TOC fragment navigation) to provide immediate footnote navigation
support. By merging this focused subset first, users get a complete
footnote experience now while simplifying the eventual review and merge
of the full #1143 PR.
---
## What this extracts from PR #1143
PR #1143 implements comprehensive TOC fragment navigation for EPUBs with
multi-chapter spine files. This PR takes only the anchor resolution
infrastructure:
- Anchor-to-page mapping in section cache: During page layout,
ChapterHtmlSlimParser records which page each HTML id attribute lands
on, serializing the map into the .bin cache file.
- Anchor resolution in `EpubReaderActivity`: When navigating to a
footnote link with a fragment (e.g., `chapter2.xhtml#note1`), the reader
resolves the anchor to a page number and jumps directly to it.
- Section file format change: Bumped to version 15, adds anchor map
offset in header.
---
## Simplified scope vs. PR #1143
To minimize conflicts and complexity, this PR differs from #1143 in key
ways:
* **Anchors tracked**
* **Origin:** Only TOC anchors (passed via `std::set`)
* **This branch:** All `id` attributes
* **Page breaks**
* **Origin**: Forces new page at TOC chapter boundaries
* **This branch:** None — natural flow
* **TOC integration**
* **Origin**: `tocBoundaries`, `getTocIndexForPage()`, chapter skip
* **This branch:** None — just footnote links
* **Bug fix**
* **This branch:** Fixed anchor page off-by-1/2 bug
The anchor recording bug (recording page number before `makePages()`
flushes previous block) was identified and fixed during this extraction.
The fix uses a deferred `pendingAnchorId` pattern that records the
anchor after page completion.
---
## Positioning for future merge
Changes are structured to minimize conflicts when #1143 eventually
merges:
- `ChapterHtmlSlimParser.cpp` `startElement()`: Both branches rewrite
the same if `(!idAttr.empty())` block. The merged version will combine
both approaches (TOC anchors get page breaks + immediate recording;
footnote anchors get deferred recording).
- `EpubReaderActivity.cpp` `render()`: The `pendingAnchor` resolution
block is positioned at the exact same insertion point where #1143 places
its `pendingTocIndex` block (line 596, right after `nextPageNumber`
assignment). During merge, both blocks will sit side-by-side.
---
## Why merge separately?
1. Immediate user value: Footnote navigation works now without waiting
for the full TOC overhaul
2. Easier review: ~100 lines vs. 500+ lines in #1143
3. Bug fix included: The page recording bug is fixed here and will carry
into #1143
4. Minimal conflicts: Structured for clean merge — both PRs touch the
same files but in complementary ways
---
### AI Usage
Did you use AI tools to help write this code? _**< YES >**_ Done by
Claude Opus 4.6
## Summary
* **What is the goal of this PR?** Potential stack buffer overflow from
untrusted ZIP entry name length
* **What changes are included?** If nameLen >= 256 , this writes past
the stack buffer. Risk: memory corruption/crash on malformed EPUB/ZIP.
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _** PARTIALLY **_ Issue
identified by AI
Description
This Pull Request introduces Turkish language support to CrossPoint
Reader firmware.
Key Changes:
- Translation File: Added lib/I18n/translations/turkish.yaml with 315
translated
string keys, covering all system UI elements.
- I18N Script Update: Modified scripts/gen_i18n.py to include the "TR"
abbreviation mapping for Turkish.
- System Integration: Regenerated I18N C++ files to include the new
Language::TR
enum and STRINGS_TR array.
- UI Availability: The language is now selectable in the Settings menu
and
correctly handles Turkish-specific characters (ç, ğ, ı, ö, ş, ü).
- Documentation: Updated docs/i18n.md to include Turkish in the list of
supported languages.
Testing:
- Verified the build locally with PlatformIO.
- Flashed the firmware to an Xteink X4 device and confirmed the Turkish
UI
renders correctly.
---
### AI Usage
Did you use AI tools to help write this code? Yes Gemini
---------
Co-authored-by: Baris Albayrak <baris@Bariss-MacBook-Pro.local>
Co-authored-by: Barış Albayrak <barisa@pop-os.lan>
## Summary
* **What is the goal of this PR?**
* Improve and add the latest missing Swedish translations.
* **What changes are included?**
* Added missing Swedish translations in
`lib\I18n\translations\swedish.yaml`
## Additional Context
* (none)
---
### 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**_
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
added Romanian ranslations from recent commits.
---
### 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 >**_
## Summary
**What is the goal of this PR?**
This change avoids the pattern of creating a `std::string` using
`.substr` in order to compare against a file extension literal.
```c++
std::string path;
if (path.length() >= 4 && path.substr(path.length() - 4) == ".ext")
```
The `checkFileExtension` utility has moved from StringUtils to
FsHelpers, to be available to code in lib/. The signature now accepts a
`std::string_view` instead of `std::string`, which makes the single
implementation reusable for Arduino `String`.
Added utility functions for commonly repeated extensions.
These changes **save about 2 KB of flash (5,999,427 to 5,997,343)**.
---
### 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**_
## Summary
**What is the goal of this PR?**
Quick follow up to #1291, adding Polish translations suggested by
@th0m4sek
---
### 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**_
## Summary
* **What is the goal of this PR?**
Update relative paths to correctly navigate from .skills/ directory to
project root by adding ../ prefix to file references.
* **What changes are included?**
.skills/SKILL.md
---
### 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 >**_
## Summary
* **What is the goal of this PR?**
Add missing Catalan strings.
* **What changes are included?**
Changes on catalan.yaml file only.
### 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
## Summary
**What is the goal of this PR?**
Avoid building cache path strings twice, once to check existence of the
file and a second time to delete the 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? _**NO**_
## Summary
* **What is the goal of this PR?** Extend missing / amend existing
German translations
* **What changes are included?** German.yaml
## Additional Context
---
---
### 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>
## Summary
**What is the goal of this PR?** Add a user setting to decide image
support: display, show placeholder instead, supress fully
Fixes#1289
---
### 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 >**_
## Summary
**Goal of this PR**
Add an **"In-scope — technically not supported"** section to `SCOPE.md`.
This clarifies hardware/UX limitations (e.g., clock support) and is
intended to reduce recurring feature requests on topics already
discussed.
Based on discussions in #287 and #626.
---
### 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 >**_
## Summary
* Enable `DESTRUCTOR_CLOSES_FILE` flag
* We're never intending to not close files, so if we accidentally leave
them open as they're destructured, this will help close them.
## Additional Context
* As spotted in
https://github.com/crosspoint-reader/crosspoint-reader/pull/869, there
are cases where we were accidentally not closing files
Looks to use about 5K of flash.
```
RAM: [=== ] 31.5% (used 103100 bytes from 327680 bytes)
Flash: [======= ] 68.9% (used 4513220 bytes from 6553600 bytes)
```
```
RAM: [=== ] 31.5% (used 103100 bytes from 327680 bytes)
Flash: [======= ] 68.9% (used 4518498 bytes from 6553600 bytes)
```
---
### 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
## Summary
* **What changes are included?**
New Ukrainian localization strings
## Additional Context
auto turn functionality
https://github.com/crosspoint-reader/crosspoint-reader/pull/1219
---
### 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 **_
## Summary
Ref discussion:
https://github.com/crosspoint-reader/crosspoint-reader/pull/1222#discussion_r2865402110
Important note that this is a bug-for-bug fix. In reality, this branch
`WiFi.status() == WL_CONNECTED` is pretty much a dead code because the
entry point of these 2 activities don't use wifi.
It is better to refactor the management of network though, but it's
better to be a dedicated PR.
---
### 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**
## Summary
* **What is the goal of this PR?**
- Improve and add the latest missing Spanish translations
* **What changes are included?**
- Add missing spaces and remove extra unneeded ones (spaces at the end
of certain strings and others, i.e. the one introduced in the string
`Smart Device`; actually, `SmartDevice` is the correct Calibre plugin
name)
- Normalise the use of caps in certain strings
- Adapting the translation to the one found in related third-party
software (i.e. Spanish translation for the word `plugin` in Calibre is
`complemento`)
- Shortening some translations to make them smaller and fit better in
screen
- Rewording ambiguous translations (i.e. `Volver a inicio` could mean to
go back to Home, but also to go back to the first page of the current
book, so I changed it for a more specific action, `Volver al menú
Inicio`)
## Additional Context
* **Missing spaces caused a lack of clarity**
- My main motivation for this PR was the following:
<details>
<summary>Screenshots:</summary>
In English:

In Spanish:

</details>
---
### 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: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
## Summary
* Custom sleep screen images now load from /.sleep directory
(preferred), falling back to /sleep for backwards compatibility. The
dot-prefix keeps the directory hidden from the file browser.
* Rewrote User Guide section 3.6 to document all six sleep screen modes,
cover settings, and the updated custom image setup.
## Additional Context
* The sleep directoy entry while browsing files was distracting.
---
### 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_
## Summary
* **What is the goal of this PR?** Fixing two independent CSS rendering
bugs combined to make hanging-indent list styles
(e.g. margin-left:3em; text-indent:-1em) render incorrectly:
* **What changes are included?**
1. Negative text-indent was silently ignored
Three guards in ParsedText.cpp (computeLineBreaks,
computeHyphenatedLineBreaks,
extractLine) conditioned firstLineIndent on blockStyle.textIndent > 0,
so any
negative value collapsed to zero. Additionally, wordXpos was uint16_t,
which
cannot represent negative offsets — a cast of e.g. −18 would wrap to
65518 and
render the word far off-screen.
2. extraParagraphSpacing suppressed hanging indents
Even after removing the > 0 guard, the existing !extraParagraphSpacing
condition
would still suppress all text-indent when that setting is on (its
default). Positive
text-indent is a decorative paragraph indent that the user can
reasonably replace with
vertical spacing — negative text-indent is structural (it positions the
list marker)
and must always apply.
3. em unit was calibrated against line height, not font size
emSize was computed as getLineHeight() * lineCompression (the full line
advance).
CSS em units are defined relative to the font-size, which corresponds to
the
ascender height — not the line height. Using line height makes every
em-based
margin/indent ~20–30% wider than a browser would render it, and is
especially
noticeable for CSS that uses font-size: small (which we do not
implement).
## Additional Context
Test case
```
.lsl1 { margin-left: 3em; text-indent: -1em; }
<div class="lsl1">• First list item that wraps across lines</div>
<div class="lsl1">• Short item</div>
```
Before: all lines of all items started at 3 em from the left edge
(indent ignored).
After: the bullet marker hangs at 2 em; continuation lines align at 3
em.
<img width="240" alt="before"
src="https://github.com/user-attachments/assets/9dcbf3e0-fcd9-4af8-b451-a90ba4d2fb75"
/>
<img width="240" alt="after"
src="https://github.com/user-attachments/assets/1ffdcf56-a180-4267-9590-c60d7ac44707"
/>
---
### 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**_
## Summary
* Renames MyLibrary component to FileBrowser, as it better reflects what
it is, in my opinion
## Additional Context
* Frees the Library name for possible future library component that can
cache metadata, provide other ways of browsing than filesystem
structure, etc
---
### AI Usage
Did you use AI tools to help write this code? _**< YES >**_
## Summary
**What is the goal of this PR?**
Hopefully fixes#1182.
_Note: I think letterforms got a "heavier" appearance after #1098, which
makes this more noticeable. The current version of this PR reverts the
change to add `--force-autohint` for Bookerly, which to me seems to
bring the font back to a more aesthetic and consistent weight._
#### Problem
Character spacing was uneven in certain words. The word "drew" in
Bookerly was the clearest example: a visible gap between `d` and `r`,
while `e` and `w` appeared tightly condensed. The root cause was
twofold:
1. **Integer-only glyph advances.** `advanceX` was stored as a `uint8_t`
of whole pixels, sourced from FreeType's hinted `advance.x` (which
grid-fits to integers). A glyph whose true advance is 15.56px was stored
as 16px -- an error of +0.44px per character that compounds across a
line.
2. **Floor-rounded kerning.** Kern adjustments were converted with
`math.floor()`, which systematically over-tightened negative kerns. A
kern of -0.3px became -1px -- a 0.7px over-correction that visibly
closed gaps.
Combined, these produced the classic symptom: some pairs too wide,
others too tight, with the imbalance varying per word.
#### Solution: fixed-point accumulation with 1/16-pixel resolution, for
sub-pixel precision during text layout
All font metrics now use a "fixed-point 4" format -- 4 fractional bits
giving 1/16-pixel (0.0625px) resolution. This is implemented with plain
integer arithmetic (shifts and adds), requiring no floating-point on the
ESP32.
**How it works:**
A value like 15.56px is stored as the integer `249`:
```
249 = 15 * 16 + 9 (where 9/16 = 0.5625, closest to 0.56)
```
Two storage widths share the same 4 fractional bits:
| Field | Type | Format | Range | Use |
|-------|------|--------|-------|-----|
| `advanceX` | `uint16_t` | 12.4 | 0 -- 4095.9375 px | Glyph advance
width |
| `kernMatrix` | `int8_t` | 4.4 | -8.0 -- +7.9375 px | Kerning
adjustment |
Because both have 4 fractional bits, they add directly into a single
`int32_t` accumulator during layout. The accumulator is only snapped to
the nearest whole pixel at the moment each glyph is rendered:
```cpp
int32_t xFP = fp4::fromPixel(startX); // pixel to 12.4: startX << 4
for each character:
xFP += kernFP; // add 4.4 kern (sign-extends into int32_t)
int xPx = fp4::toPixel(xFP); // snap to nearest pixel: (xFP + 8) >> 4
render glyph at xPx;
xFP += glyph->advanceX; // add 12.4 advance
```
Fractional remainders carry forward indefinitely. Rounding errors stay
below +/- 0.5px and never compound.
#### Concrete example: "drew" in Bookerly
**Before** (integer advances, floor-rounded kerning):
| Char | Advance | Kern | Cursor | Snap | Gap from prev |
|------|---------|------|--------|------|---------------|
| d | 16 px | -- | 33 | 33 | -- |
| r | 12 px | 0 | 49 | 49 | ~2px |
| e | 13 px | -1 | 60 | 60 | ~0px |
| w | 22 px | -1 | 72 | 72 | ~0px |
The d-to-r gap was visibly wider than the tightly packed `rew`.
**After** (12.4 advances, 4.4 kerning, fractional accumulation):
| Char | Advance (FP) | Kern (FP) | Accumulator | Snap | Ink start | Gap
from prev |
|------|-------------|-----------|-------------|------|-----------|---------------|
| d | 249 (15.56px) | -- | 528 | 33 | 34 | -- |
| r | 184 (11.50px) | 0 | 777 | 49 | 49 | 0px |
| e | 208 (13.00px) | -8 (-0.50px) | 953 | 60 | 61 | 1px |
| w | 356 (22.25px) | -4 (-0.25px) | 1157 | 72 | 72 | 0px |
Spacing is now `0, 1, 0` pixels -- nearly uniform. Verified on-device:
all 5 copies of "drew" in the test EPUB produce identical spacing,
confirming zero accumulator drift.
#### Changes
**Font conversion (`fontconvert.py`)**
- Use `linearHoriAdvance` (FreeType 16.16, unhinted) instead of
`advance.x` (26.6, grid-fitted to integers) for glyph advances
- Encode kern values as 4.4 fixed-point with `round()` instead of
`floor()`
- Add `fp4_from_ft16_16()` and `fp4_from_design_units()` helper
functions
- Add module-level documentation of fixed-point conventions
**Font data structures (`EpdFontData.h`)**
- `EpdGlyph::advanceX`: `uint8_t` to `uint16_t` (no memory cost due to
existing struct padding)
- Add `fp4` namespace with `constexpr` helpers: `fromPixel()`,
`toPixel()`, `toFloat()`
- Document fixed-point conventions
**Font API (`EpdFont.h/cpp`, `EpdFontFamily.h/cpp`)**
- `getKerning()` return type: `int8_t` to `int` (to avoid truncation of
the 4.4 value)
**Rendering (`GfxRenderer.cpp`)**
- `drawText()`: replace integer cursor with `int32_t` fixed-point
accumulator
- `drawTextRotated90CW()`: same accumulator treatment for vertical
layout
- `getTextAdvanceX()`, `getSpaceWidth()`, `getSpaceKernAdjust()`,
`getKerning()`: convert from fixed-point to pixel at API boundary
**Regenerated all built-in font headers** with new 12.4 advances and 4.4
kern values.
#### Memory impact
Zero additional RAM. The `advanceX` field grew from `uint8_t` to
`uint16_t`, but the `EpdGlyph` struct already had 1 byte of padding at
that position, so the struct size is unchanged. The fixed-point
accumulator is a single `int32_t` on the stack.
#### Test plan
- [ ] Verify "drew" spacing in Bookerly at small, medium, and large
sizes
- [ ] Verify uppercase kerning pairs: AVERY, WAVE, VALUE
- [ ] Verify ligature words: coffee, waffle, office
- [ ] Verify all built-in fonts render correctly at each size
- [ ] Verify rotated text (progress bar percentage) renders correctly
- [ ] Verify combining marks (accented characters) still position
correctly
- [ ] Spot-check a full-length book for any layout regressions
---
### 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 Opus 4.6
helped figure out a non-floating point approach for sub-pixel error
accumulation**_
## Summary
**What is the goal of this PR?**
- Adds `scripts/firmware_size_history.py`, a developer tool that builds
firmware at selected git commits and reports flash usage with deltas
between them.
- Supports two input modes: `--range START END` to walk every commit in
a range, or `--commits REF [REF ...]` to compare specific refs (which
can span branches).
- Defaults to a human-readable aligned table; pass `--csv` for
machine-readable output to stdout or `--csv FILE` to write to a 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, fully written by
AI**_
## Summary
**What is the goal of this PR?**
Rewrite of font routines to use std binary search algorithms instead of
custom repeated implementations: `lookupKernClass`,
`EpdFont::getLigature`, and `EpdFont::getGlyph`.
---
### 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**_
## Summary
**What is the goal of this PR?**
Small follow up to #909, removing an unused member variable and some
temporary debug logging.
---
### 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**_
## Summary
* **What is the goal of this PR?**
Increase accuracy of button hints and text description in the file
browser when viewing empty directory.
* **What changes are included?**
Adjusted button label hint rendering logic in file browser to hide the
"Open", "Up", and "Down" hints when the they are not available due to an
empty directory.
I also changed the NO_BOOKS_FOUND string to NO_FILES_FOUND and updated
translations. File browser shows more than just books so seeing "No
Books Found" really doesn't make sense.
## Additional Context
Very Simple change, here is what that looks like on my device.
<img width="1318" height="879" alt="Untitled (7)"
src="https://github.com/user-attachments/assets/6416c8c8-795d-41a5-9b9f-28d2c26666a0"
/>
---
### 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**_
## Summary
* **What is the goal of this PR?** Eliminate the 3-file / 4-location
overhead for adding a new setting. Previously, every new setting
required manually editing JsonSettingsIO.cpp in two places (save +
load), duplicating knowledge already present in SettingsList.h. After
this PR, JsonSettingsIO.cpp never needs to be touched again for standard
settings.
* **What changes are included?**
* `SettingInfo` (in `SettingsActivity.h`) gains one new field: `bool
obfuscated` (base64 save/load for passwords), with a fluent builder
method `.withObfuscated()`. The previously proposed
`defaultValue`/`withDefault()` approach was dropped in favour of reading
the struct field's own initializer value as the fallback (see below).
* `SettingsList.h` entries are annotated with `.withObfuscated()` on the
OPDS password entry. The list is now returned as a `static const`
singleton (`const std::vector<SettingInfo>&`), so it is constructed
exactly once. A missing `key`/`category` on the
`statusBarProgressBarThickness` entry was also fixed — it was previously
skipped by the generic save loop, so changes were silently lost on
restart.
* `JsonSettingsIO::saveSettings` and `loadSettings` replace their ~90
lines of manual per-field code with a single generic loop over
`getSettingsList()`. The loop uses `info.key`,
`info.valuePtr`/`info.stringOffset`+`info.stringMaxLen` (for char-array
string fields), `info.enumValues.size()` (for enum clamping), and
`info.obfuscated`.
* **Default values**: instead of a duplicated `defaultValue` field in
`SettingInfo`, `loadSettings` reads `s.*(info.valuePtr)` *before*
overwriting it. Because `CrossPointSettings` is default-constructed
before `loadSettings` is called, this captures each field's struct
initializer value as the JSON-absent fallback. The single source of
truth for defaults is `CrossPointSettings.h`.
* One post-loop special case remains explicitly: the four `frontButton*`
remap fields (managed by the RemapFrontButtons sub-activity, not in
SettingsList) and `validateFrontButtonMapping()`.
* One pre-loop migration guard handles legacy settings files that
predate the status bar refactor: if `statusBarChapterPageCount` is
absent from the JSON, `applyLegacyStatusBarSettings()` is called first
so the generic loop picks up the migrated values as defaults and applies
its normal clamping.
* OPDS password backward-compat migration (plain `opdsPassword` →
obfuscated `opdsPassword_obf`) is preserved inside the generic
obfuscated-string path.
## Additional Context
Say we want to add a new `bookmarkStyle` enum setting with options
`DOT`, `LINE`, `NONE` and a default of `DOT`:
1. `src/CrossPointSettings.h` — add enum and member:
```cpp
enum BOOKMARK_STYLE { BOOKMARK_DOT = 0, BOOKMARK_LINE = 1, BOOKMARK_NONE = 2 };
uint8_t bookmarkStyle = BOOKMARK_DOT;
```
2. `lib/I18n/translations/english.yaml` — add display strings:
```yaml
STR_BOOKMARK_STYLE: "Bookmark Style"
STR_BOOKMARK_DOT: "Dot"
STR_BOOKMARK_LINE: "Line"
```
(Other language files will fall back to English if not translated. Run
`gen_i18n.py` to regenerate `I18nKeys.h`.)
3. `src/SettingsList.h` — add one entry in the appropriate category:
```cpp
SettingInfo::Enum(StrId::STR_BOOKMARK_STYLE, &CrossPointSettings::bookmarkStyle,
{StrId::STR_BOOKMARK_DOT, StrId::STR_BOOKMARK_LINE, StrId::STR_NONE_OPT},
"bookmarkStyle", StrId::STR_CAT_READER),
```
That's it — no default annotation needed anywhere, because
`bookmarkStyle = BOOKMARK_DOT` in the struct already provides the
fallback. The setting will automatically persist to JSON on save, load
with clamping on boot, appear in the device settings UI under the Reader
category, and be exposed via the web API — all with no further changes.
---
### 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>**_
## Summary
Replaces the picojpeg library with bitbank2/JPEGDEC for JPEG decoding in
the EPUB image pipeline. JPEGDEC provides built-in coarse scaling (1/2,
1/4, 1/8), 8-bit grayscale output, and streaming block-based decoding
via callbacks.
Includes a pre-build patch script for two JPEGDEC changes affecting
progressive JPEG support and EIGHT_BIT_GRAYSCALE mode.
Closes#912
## Additional Context
# Example progressive jpeg
<img
src="https://github.com/user-attachments/assets/e63bb4f8-f862-4aa0-a01f-d1ef43a4b27a"
width="400" height="800" />
Good performance increase from JPEGDEC over picojpeg cc @bitbank2 thanks
## Baseline JPEG Decode Performance: picojpeg vs JPEGDEC (float in
callback) vs JPEGDEC (fixed-point in callback)
Tested with `test_jpeg_images.epub` on device (ESP32-C3), first decode
(no cache).
| Image | Source | Output | picojpeg | JPEGDEC float | JPEGDEC
fixed-point | vs picojpeg | vs float |
|-------|--------|--------|----------|---------------|---------------------|-------------|----------|
| jpeg_format.jpg | 350x250 | 350x250 | 313 ms | 256 ms | **104 ms** |
**3.0x** | **2.5x** |
| grayscale_test.jpg | 400x600 | 400x600 | 768 ms | 661 ms | **246 ms**
| **3.1x** | **2.7x** |
| gradient_test.jpg | 400x500 | 400x500 | 707 ms | 597 ms | **247 ms** |
**2.9x** | **2.4x** |
| centering_test.jpg | 350x400 | 350x400 | 502 ms | 412 ms | **169 ms**
| **3.0x** | **2.4x** |
| scaling_test.jpg | 1200x1500 | 464x580 | 5487 ms | 1114 ms | **668
ms** | **8.2x** | **1.7x** |
| wide_scaling_test.jpg | 1807x736 | 464x188 | 4237 ms | 642 ms | **497
ms** | **8.5x** | **1.3x** |
| cache_test_1.jpg | 400x300 | 400x300 | 422 ms | 348 ms | **141 ms** |
**3.0x** | **2.5x** |
| cache_test_2.jpg | 400x300 | 400x300 | 424 ms | 349 ms | **142 ms** |
**3.0x** | **2.5x** |
### Summary
- **1:1 scale (fixed-point vs float)**: ~2.5x faster — eliminating
software float on the FPU-less ESP32-C3 is the dominant win
- **1:1 scale (fixed-point vs picojpeg)**: ~3.0x faster overall
- **Downscaled images (vs picojpeg)**: 8-9x faster — JPEGDEC's coarse
scaling + fixed-point draw callback
- **Downscaled images (fixed-point vs float)**: 1.3-1.7x — less dramatic
since JPEG library decode dominates over the draw callback for fewer
output pixels
- The fixed-point optimization alone (vs float JPEGDEC) saved **~60% of
render time** on 1:1 images, confirming that software float emulation
was the primary bottleneck in the draw callback
- See thread for discussions on quality of progressive images,
https://github.com/crosspoint-reader/crosspoint-reader/pull/1136#issuecomment-3952952315
- and the conclusion
https://github.com/crosspoint-reader/crosspoint-reader/pull/1136#issuecomment-3959379386
- Proposal to improve quality added at
https://github.com/crosspoint-reader/crosspoint-reader/discussions/1179
---
### 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: Dave Allie <dave@daveallie.com>
## Summary
Properly implement `requestUpdateAndWait()` using freeRTOS direct task
notification.
FWIW, I think most of the current use cases of `requestUpdateAndWait()`
are redundant, better to be replaced by `requestUpdate(true)`. But just
keeping them in case we can find a proper use case for it in the future.
---
### 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**, it's trivial, so
I asked an AI to write the code
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
## Summary
* **What is the goal of this PR?** During my development I am frequently
jumping from branch to branch flashing test versions on my device. It
becomes sometimes quite difficult to figure out which version of the
software I am currently looking at.
* **What changes are included?**
- Dev builds now display the current git branch in the version string
shown on the Settings screen (e.g. 1.1.0-dev+feat-my-feature), making it
easier to identify which firmware is running on the device when
switching between branches frequently.
- Release, RC, and slim builds are unaffected — they continue to set
their version string statically in platformio.ini.
<img width="480" height="800" alt="after"
src="https://github.com/user-attachments/assets/d2ab3d69-ab6b-47a1-8eb7-1b40b1d3b106"
/>
## Additional Context
A new PlatformIO pre-build script (scripts/git_branch.py) runs
automatically before every dev build. It reads the base version from the
[crosspoint] section of platformio.ini, queries git rev-parse
--abbrev-ref HEAD for the current branch, and injects the combined
string as the CROSSPOINT_VERSION preprocessor define. In a detached HEAD
state it falls back to the short commit SHA. If git is unavailable it
warns and falls back to unknown.
The script can also be run directly with python scripts/git_branch.py
for validation without triggering a full build.
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**< YES | PARTIALLY | NO
>**_
## Summary
* **What is the goal of this PR?** Add missing strings and tweaks for
polish language
* **What changes are included?**
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**< YES | PARTIALLY | NO
>**_
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
Allow users to better manage their epub library by offloading unwanted
or finished books and other files. Resolves#893
* **What changes are included?**
Added Delete Book shortcut in the fil browser. Delete function
implements the new ConfirmationActivity to show file name and solicit
user interaction before either returning to the file browser on a press
of the back button, or proceeding to delete. Delete function then
deletes the file and returns user to the file browser menu at the
current directory. Video of it working on my machine attached here:
https://github.com/user-attachments/assets/329b0198-9e97-45ad-82aa-c39894351667
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
Certainly potential risks associated with file deletion. Please let me
know if there are any concerns that need to be better addressed. I think
this is a very good feature to have to go along with the new screenshots
so you don't get stuck with a bunch of extra files on your device. Also
I did add this to the user guide.
---
### 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: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Егор Мартынов <martynovegorOF@yandex.ru>
Co-authored-by: Arthur Tazhitdinov <lisnake@gmail.com>
Co-authored-by: Zach Nelson <zach@zdnelson.com>
## Summary
* Refactored `HttpDownloader::downloadToFile` to use `FileWriteStream`
and `HTTPClient::writeToStream`, removing manual chunked downloading
logic, which was error-prone.
* Fixes
https://github.com/crosspoint-reader/crosspoint-reader/issues/632
## Additional Context
* Tested downloading files from OPDS with a size up to 10 mb.
---
### 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 >**_
## Summary
Fix https://github.com/crosspoint-reader/crosspoint-reader/issues/1137
Introducing `HalFile`, a thin wrapper around `FsFile` that uses a global
mutex to protect file operations.
To test this PR, place the code below somewhere in the code base (I
placed it in `onGoToRecentBooks`)
```cpp
static auto testTask = [](void* param) {
for (int i = 0; i < 10; i++) {
String json = Storage.readFile("/.crosspoint/settings.json");
LOG_DBG("TEST_TASK", "Read settings.json, bytes read: %u", json.length());
}
vTaskDelete(nullptr);
};
xTaskCreate(testTask, "test0", 8192, nullptr, 1, nullptr);
xTaskCreate(testTask, "test1", 8192, nullptr, 1, nullptr);
xTaskCreate(testTask, "test2", 8192, nullptr, 1, nullptr);
xTaskCreate(testTask, "test3", 8192, nullptr, 1, nullptr);
delay(1000);
```
It will reliably lead to crash on `master`, but will function correctly
with this PR.
A macro renaming trick is used to avoid changing too many downstream
code files.
---
### 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**, only to
help with tedious copy-paste tasks
---------
Co-authored-by: Zach Nelson <zach@zdnelson.com>
## Summary
**What is the goal of this PR?**
Added overview and migration guide for ActivityManager changes in #1016.
Thanks for the suggestion, @drbourbon!
---
### 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, fully written by
Claude 4.6**_
## Summary
* **What is the goal of this PR?**
Fixed a small prefix translation (`STR_TO_PREFIX`)
* **What changes are included?**
Changed the translation from `naar ` to `met `
## Additional Context
* The English translation file doesn't make clear what the context of
`STR_TO_PREFIX` is, so the Dutch translation wasn't correct. This PR
fixes that.
---
### 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: Bas van der Ploeg <bas@MBP-M2-Max-3.localdomain>
## Summary
**What is the goal of this PR?**
Small tweaks to #1016:
- Only Activity and ActivityManager can access activityResultHandler and
activityResult
- `[[maybe_unused]]` in RenderLock constructor
- Only ActivityManager and RenderLock can access renderingMutex
- Missing renderUpdate after failed wifi selection
- Standardize on activities calling finish instead of
activityManager.popActivity
- Hold RenderLock while mutating state in EpubReaderActivity result
handlers
---
### 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**_
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
- Implements auto page turn feature for epub reader in the reader
submenu
* **What changes are included?**
- added auto page turn feature in epub reader in the submenu
- currently there are 5 settings, `OFF, 1, 3, 6, 12` pages per minute
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
- Replacement PR for #723
- when auto turn is enabled, space reserved for chapter title will be
used to indicate auto page turn being active
- Back and Confirm button is used to disable it
---
### 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 (mainly code
reviews)**_
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
Fixes a clarity issue regarding the translation string `STR_SET`. The
issue lies in the fact that the english word can have different
meanings.
The only time the string is used is in the language selectio screen,
where it has the meaning of _selected_. (As in _The language has been
**set** to French_).
Another meaning can be _configured_. (As in _The KOReader username has
been __set__). This is the meaning many of the translations have taken.
The reason that the string is right above `STR_NOT_SET` (which is meant
as _not configured_).
With this PR I propose to explicitly use the term "_Selected_". There
are two good reasons for this:
+ it removes the confusion and the misleading translations
+ it is consistent with the button label `Select`, communicating the
link between the two (the row will be marked `Selected` if you press the
buttpn `Select`. Much clearer than now)
* **What changes are included?**
Removed the unused strings and added translations for the new string
`STR_SELECTED` for the languages I know.
tagging the translators for feedback:
fr: @Spigaw @CaptainFrito
de: @DavidOrtmann
cs: @brbla
pt: @yagofarias
it: @andreaturchet @fargolinux
ru: @madebykir @mrtnvgr
es: @yeyeto2788 @Skrzakk @pablohc
sv: @dawiik
ca: @angeldenom
uj: @mirus-ua
be: @dexif
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
the Issue was introduced in #1020. Previously, if a language was
selected it was marked with `[ON]` (`STR_ON_MARKER`). I considered
reverting it back to that, but the solution I described above seemed
superior.
---
### 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: Егор Мартынов <martynovegorOF@yandex.ru>
Co-authored-by: Mirus <mirusim@gmail.com>
## Summary
* **What is the goal of this PR?**
Improve KOReader Sync documentation so new users can self-host and
configure sync quickly, with clear references in both README and
USER_GUIDE.
* **What changes are included?**
- Add a KOReader Sync feature mention in `README.md` and link to a
quick-setup section in the guide.
- Add `3.6.5 KOReader Sync Quick Setup` to `USER_GUIDE.md` with:
- Docker Compose-first setup instructions (plus Podman compose
alternative)
- healthcheck verification
- one-time user registration example using `curl`
- device-side setup steps (`Settings -> System -> KOReader Sync` and
`Authenticate`)
- in-reader usage via `Sync Progress` in the reader menu
- Update reading mode navigation wording from "Chapter Menu" to "Reader
Menu" so it reflects current UI behavior.
Closes#1032.
## Additional Context
* This is documentation-only and does not change firmware behavior.
* The quick-setup flow is intentionally short and focused on getting
sync working quickly for typical home-network setups.
---
### 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**_
## Summary
* **Added Dutch translation**
* **(Added dutch.yaml translation file)**
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**< PARTIALLY>**_
---------
Co-authored-by: Bas van der Ploeg <bas@MBP-M2-Max-3.localdomain>
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
* **What changes are included?**
Added translator name for Portuguese (Portugal)
---
### AI Usage
Did you use AI tools to help write this code? _**< NO >**_
## Summary
Ref comment:
https://github.com/crosspoint-reader/crosspoint-reader/pull/1010#pullrequestreview-3828854640
This PR introduces `ActivityManager`, which mirrors the same concept of
Activity in Android, where an activity represents a single screen of the
UI. The manager is responsible for launching activities, and ensuring
that only one activity is active at a time.
Main differences from Android's ActivityManager:
- No concept of Bundle or Intent extras
- No onPause/onResume, since we don't have a concept of background
activities
- onActivityResult is implemented via a callback instead of a separate
method, for simplicity
## Key changes
- Single `renderTask` shared across all activities
- No more sub-activity, we manage them using a stack; Results can be
passed via `startActivityForResult` and `setResult`
- Activity can call `finish()` to destroy themself, but the actual
deletion will be handled by `ActivityManager` to avoid `delete this`
pattern
As a bonus: the manager will automatically call `requestUpdate()` when
returning from another activity
## Example usage
**BEFORE**:
```cpp
// caller
enterNewActivity(new WifiSelectionActivity(renderer, mappedInput,
[this](const bool connected) { onWifiSelectionComplete(connected); }));
// subactivity
onComplete(true); // will eventually call exitActivity(), which deletes the caller instance (dangerous behavior)
```
**AFTER**: (mirrors the `startActivityForResult` and `setResult` from
android)
```cpp
// caller
startActivityForResult(new NetworkModeSelectionActivity(renderer, mappedInput),
[this](const ActivityResult& result) { onNetworkModeSelected(result.selectedNetworkMode); });
// subactivity
ActivityResult result;
result.isCancelled = false;
result.selectedNetworkMode = mode;
setResult(result);
finish(); // signals to ActivityManager to go back to last activity AFTER this function returns
```
TODO:
- [x] Reconsider if the `Intent` is really necessary or it should be
removed (note: it's inspired by
[Intent](https://developer.android.com/guide/components/intents-common)
from Android API) ==> I decided to keep this pattern fr clarity
- [x] Verify if behavior is still correct (i.e. back from sub-activity)
- [x] Refactor the `ActivityWithSubactivity` to just simple `Activity`
--> We are using a stack for keeping track of sub-activity now
- [x] Use single task for rendering --> avoid allocating 8KB stack per
activity
- [x] Implement the idea of [Activity
result](https://developer.android.com/training/basics/intents/result)
--> Allow sub-activity like Wifi to report back the status (connected,
failed, etc)
---
### 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**, some
repetitive migrations are done by Claude, but I'm the one how ultimately
approve it
---------
Co-authored-by: Zach Nelson <zach@zdnelson.com>
## Summary
* **What is the goal of this PR?**
Fix an infinite render loop bug in CalibreConnectActivity that caused
the e-ink display to refresh continuously every ~421ms after a file
transfer completed.
* **What changes are included?**
- Added lastProcessedCompleteAt member variable to track which server
completion timestamp has already been processed
- Modified the completion status update logic to only accept new values
from the server, preventing re-processing of old timestamps
- Added clarifying comments explaining the fix
## Problem Description
After receiving a file via Calibre Wireless, the activity displays
"Received: [filename]" for 6 seconds, then clears the message. However,
the web server's wsLastCompleteAt timestamp persists indefinitely and is
never cleared.
This created a race condition:
After 6 seconds, lastCompleteAt is set to 0 (timeout)
In the next loop iteration, status.lastCompleteAt (still has the old
timestamp) ≠ lastCompleteAt (0)
The code restores lastCompleteAt from the server value
Immediately, the 6-second timeout condition is met again
This creates an infinite cycle causing unnecessary e-ink refreshes
## Solution
The fix introduces lastProcessedCompleteAt to track which server
timestamp value has already been processed:
Only accept a new status.lastCompleteAt if it differs from
lastProcessedCompleteAt
Update lastProcessedCompleteAt when processing a new value
Do NOT reset lastProcessedCompleteAt when the 6-second timeout clears
lastCompleteAt
This prevents re-processing the same old server value after the timeout.
## Testing
Tested on device with multiple file transfer scenarios:
✅ File received message appears correctly after transfer
✅ Message clears after 6 seconds as expected
✅ No infinite render loop after timeout
✅ Multiple consecutive transfers work correctly
✅ Exiting and re-entering Calibre Wireless works as expected
## Performance Impact
Before: Infinite refreshes every ~421ms after timeout (high battery
drain, display wear)
After: 2-3 refreshes after timeout, then stops (normal behavior)
## Additional Context
This is a targeted fix that only affects the Calibre Wireless file
transfer screen. The root cause is the architectural difference between
the persistent web server state (wsLastCompleteAt) and the per-activity
display state (lastCompleteAt).
An alternative fix would be to clear wsLastCompleteAt in the web server
after some timeout, but that would affect all consumers of the web
server status. The chosen solution keeps the fix localized to
CalibreConnectActivity.
---
### AI Usage
Did you use AI tools to help write this code? _**< YES >**_
## Summary
* **What is the goal of this PR?** some tweaks to Polish translation
* **What changes are included?**
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**< YES | PARTIALLY | NO
>**_
---------
Co-authored-by: Zach Nelson <zach@zdnelson.com>
Co-authored-by: Arthur Tazhitdinov <lisnake@gmail.com>
## Summary
* As many of us have experienced, the use of AI coding agents (Claude,
Cursor, Copilot) in our daily workflows is no longer a hypothetical—it
is a reality. While these tools can significantly accelerate
development, they also pose a unique risk to a project like CrossPoint
Reader, where our hardware constraints (ESP32-C3 with ~380KB RAM) are
extremely tight.
* AI models often "hallucinate" APIs or suggest high-level C++ patterns
(like std::string or heavy heap usage) that are detrimental to our
memory-constrained environment.
* To address this, I am proposing the introduction of an AI Agent
Guidance File (.skills/SKILL.md recognized by many AI systems). This
file acts as a "Constitutional Document" for AI agents, forcing them to
adhere to our specific engineering rigors before they generate a single
line of code.
## Additional Context
---
### 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**_ Asked
several Ai systems about their needs, added my own 5 cents of nuisances
I faced
## Summary
**What is the goal of this PR?** Replace the linear scan of
`lookupHtmlEntity` with a simple binary search to improve lookup
performance.
**What changes are included?**
`lib/Epub/Epub/Entities/htmlEntities.cpp`:
- Sorted the `ENTITY_LOOKUP` array.
- Added a compile-time assertion to guarantee the array remains sorted.
- Rewrote `lookupHtmlEntity` to use a binary search.
## Additional Context
Benchmarked on my x64 laptop (probably will be different on RISC-V)
```
=== Benchmark (53 entities x 10000 iterations) ===
Version Total time Avg per lookup
----------------------------------------------
linear 236.97 ms total 447.11 ns/lookup
binary search 22.09 ms total 41.68 ns/lookup
=== Summary ===
Binary search is 10.73x faster than linear scan.
```
This is a simplified alternative to #1180, focused on keeping the
implementation clean, and maintainable.
### AI Usage
Did you use AI tools to help write this code? _**< NO >**_
---------
Co-authored-by: Zach Nelson <zach@zdnelson.com>
## Summary
* **What is the goal of this PR?**
Update translation after
https://github.com/crosspoint-reader/crosspoint-reader/pull/733
---
### 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**_
## Summary
Translation added russian.yaml
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**< YES | PARTIALLY | NO
>**_
NO
## Summary
**What is the goal of this PR?** Implement support for footnotes in epub
files.
It is based on #553, but simplified — removed the parts which
complicated the code and burden the CPU/RAM. This version supports basic
footnotes and lets the user jump from location to location inside the
epub.
**What changes are included?**
- `FootnoteEntry` struct — A small POD struct (number[24], href[64])
shared between parser, page storage, and UI.
- Parser: `<a href>` detection (`ChapterHtmlSlimParser`) — During a
single parsing pass, internal epub links are detected and collected as
footnotes. The link text is underlined to hint navigability.
Bracket/whitespace normalization is applied to the display label (e.g.
[1] → 1).
- Footnote-to-page assignment (`ChapterHtmlSlimParser`, `Page`) —
Footnotes are attached to the exact page where their anchor word
appears, tracked via a cumulative word counter during layout, surviving
paragraph splits and the 750-word mid-paragraph safety flush.
- Page serialization (`Page`, `Section`) — Footnotes are
serialized/deserialized per page (max 16 per page). Section cache
version bumped to 14 to force a clean rebuild.
- Href → spine resolution (`Epub`) — `resolveHrefToSpineIndex()` maps an
href (e.g. `chapter2.xhtml#note1`) to its spine index by filename
matching.
- Footnotes menu + activity (`EpubReaderMenuActivity`,
`EpubReaderFootnotesActivity`) — A new "Footnotes" entry in the reader
menu lists all footnote links found on the current page. The user
scrolls and selects to navigate.
- Navigate & restore (`EpubReaderActivity`) — `navigateToHref()` saves
the current spine index and page number, then jumps to the target. The
Back button restores the saved position when the user is done reading
the footnote.
**Additional Context**
**What was removed vs #553:** virtual spine items
(`addVirtualSpineItem`, `isVirtualSpineItem`), two-pass parsing,
`<aside>` content extraction to temp HTML files, `<p class="note">`
paragraph note extraction, `replaceHtmlEntities` (master already has
`lookupHtmlEntity`), `footnotePages` / `buildFilteredChapterList`,
`noterefCallback` / `Noteref` struct, and the stack size increase from 8
KB to 24 KB (not needed without two-pass parsing and virtual file I/O on
the render task).
**Performance:** Single-pass parsing. No new heap allocations in the hot
path — footnote text is collected into fixed stack buffers (char[24],
char[64]). Active runtime memory is ~2.8 KB worst-case (one page × 16
footnotes × 88 bytes, mirrored in `currentPageFootnotes`). Flash usage
is unchanged at 97.4%; RAM stays at 31%.
**Known limitations:** When clicking a footnote, it jumps to the start
of the HTML file instead of the specific anchor. This could be
problematic for books that don't have separate files for each footnote.
(no element-id-to-page mapping yet - will be another PR soon).
---
### AI Usage
Did you use AI tools to help write this code? _**< PARTIALLY>**_
Claude Opus 4.6 was used to do most of the migration, I checked manually
its work, and fixed some stuff, but I haven't review all the changes
yet, so feedback is welcomed.
---------
Co-authored-by: Arthur Tazhitdinov <lisnake@gmail.com>
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
The strings for `Show` and `Hide` were always showing in English,
regardless of which language was selected.
* **What changes are included?**
Replace the variables in the lambda by direct calls to the `tr` macro
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**<NO >**_
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
Romanian translations for newly added strings.
* **What changes are included?**
Only the translations
---
### 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 >**_
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
A Danish translation for the GUI
* **What changes are included?**
Everything from
[`i18n.md`](https://github.com/crosspoint-reader/crosspoint-reader/blob/master/docs/i18n.md)
---
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
No
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**< YES | PARTIALLY | NO
>**_
Started translating myself, transitioned to having Claude Code do the
bulk of the translation. Read every translation myself and doubled
checked with a dictionary if I agreed with the translation made.
---------
Co-authored-by: Arthur Tazhitdinov <lisnake@gmail.com>
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
Adds Finnish language support
* **What changes are included?**
Created new translation yaml file, ran the translation script to
generate the C++ code
---
### 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: Arthur Tazhitdinov <lisnake@gmail.com>
## Summary
* **What is the goal of this PR?** Implements Polish language
* **What changes are included?**
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**< YES | PARTIALLY | NO
>**_
---------
Co-authored-by: Zach Nelson <zach@zdnelson.com>
Co-authored-by: Arthur Tazhitdinov <lisnake@gmail.com>
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
Currently we are displaying the languages in the order they were added
(as in the `Language` enum). However, as new languages are coming in,
this will quickly be confusing to the users.
But we can't just change the ordering of the enum if we want to respect
bakwards compatibility.
So my proposal is to add a mapping of the alphabetical order of the
languages. I've made it so that it's generated by the `gen_i18n.py`
script, which will be used when a new language is added.
* **What changes are included?**
Added the array from the python script and changed
`LanguageSelectActivity` to use the indices from there. Also commited
the generated `I18nKeys.h`
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
I was wondering if there is a better way to sort it. Currently, it's by
unicode value and Czech and Russian are last, which I don't know it it's
the most intuitive.
The current order is:
`Català, Deutsch, English, Español, Français, Português (Brasil),
Română, Svenska, Čeština, Русский`
---
### 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 >**_
## Summary
* **What is the goal of this PR?** KOReader sync on a German-language
book would fail with an out-of-memory error when trying to open the
destination chapter after applying remote progress. The root cause was a
chain of two independent bugs that combined to exhaust the contiguous
heap needed by the EPUB inflate pipeline.
* **What changes are included?**
## Fix 1 — Hyphenation heap defragmentation (LiangHyphenation.cpp)
### What was happening
AugmentedWord, the internal struct used during Liang pattern matching,
held three std::vector<> members (bytes, charByteOffsets,
byteToCharIndex) plus a separate scores vector — a total of 4 heap
allocations per word during page layout. For a German-language section
with hundreds of words, thousands of small malloc/free cycles fragmented
the heap. Total free memory was adequate (~108 KB) but the largest
contiguous block shrank well below the 32 KB needed for the INFLATE ring
buffer used during EPUB decompression. The failure was invisible with
hyphenation disabled, where MaxAlloc stayed at ~77 KB; enabling German
hyphenation silently destroyed the contiguity the allocator needed.
### What changed
The three std::vector<> members of AugmentedWord and the scores vector
are replaced with fixed-size C arrays on the render-task stack:
```
uint8_t bytes[160] // was std::vector<uint8_t>
size_t charByteOffsets[70] // was std::vector<size_t>
int32_t byteToCharIndex[160] // was std::vector<int32_t>
uint8_t scores[70] // was std::vector<uint8_t> (local in liangBreakIndexes)
```
Sizing is based on the longest known German word (~63 codepoints × 2
UTF-8 bytes + 2 sentinel dots = 128 bytes); MAX_WORD_BYTES=160 and
MAX_WORD_CHARS=70 give comfortable headroom. The same analysis holds for
all seven supported languages (en, fr, de, es, it, ru, uk) — every
accepted letter encodes to at most 2 UTF-8 bytes after case-folding.
Words exceeding the limits are silently skipped (no hyphenation
applied), which is correct behaviour. The struct lives on the 8 KB
render-task stack so no permanent DRAM is consumed.
Verification: after the fix, MaxAlloc reads 77,812 bytes with German
hyphenation enabled — identical to the figure previously achievable only
with hyphenation off.
## Fix 2 — WiFi lifecycle in KOReaderSyncActivity
(KOReaderSyncActivity.cpp)
### What was happening
onEnter() called WiFi.mode(WIFI_STA) unconditionally before delegating
to WifiSelectionActivity. WifiSelectionActivity manages WiFi mode
internally (it calls WiFi.mode(WIFI_STA) again at scan start and at
connection attempt). The pre-emptive call from KOReaderSyncActivity
interfered with the sub-activity's own state machine, causing
intermittent connection failures that were difficult to reproduce.
Additionally, WiFi was only shut down in onExit(). If the user chose
"Apply remote progress" the activity exited without turning WiFi off
first, leaving the radio on and its memory allocated while the EPUB was
being decompressed — unnecessarily consuming the contiguous heap
headroom that inflate needed.
### What changed
* WiFi.mode(WIFI_STA) removed from onEnter(). WifiSelectionActivity owns
WiFi mode; KOReaderSyncActivity should not touch it before the
sub-activity runs.
* A wifiOff() helper (SNTP stop + disconnect + WIFI_OFF with settling
delays) is extracted into the anonymous namespace and called at every
web-session exit point:
- "Apply remote" path in loop() — before onSyncComplete()
- performUpload() success path
- performUpload() failure path
- onExit() (safety net for all other exit paths)
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**YES**_ and two days of
blood, sweat and heavy swearing...
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
Update "UI Font Size" to "Reader Font Size", to match the rest of the
"Reader" settings and clarify that the setting doesn't change the UI
font.
* **What changes are included?**
Changes the `english.yaml` string and USER_GUIDE.md entry.
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**< YES | PARTIALLY | NO
>**_
NO
## Summary
This PR aims to reduce the complexity of the status bar by splitting the
setting into 5:
- Chapter Page Count
- Book Progress %
- Progress Bar
- Chapter Title
- Battery Indicator
These are located within the new StausBarSettings activity, which also
shows a preview of the bar the user has created
<img width="513" height="806" alt="image"
src="https://github.com/user-attachments/assets/cdf852fb-15d8-4da2-a74f-fd69294d7b05"
/>
<img width="483" height="797" alt="image"
src="https://github.com/user-attachments/assets/66fc0c0d-ee51-4d31-b70d-e2bc043205d1"
/>
When updating from a previous version, the user's past settings are
honoured.
## Additional Context
The PR aims to remove any duplication of status bar code where possible,
and extracts the status bar rendering into a new component - StatusBar
It also adds a new (optional) padding option to the progress bar to
allow the status bar to be shifted upwards - this is only intended for
use in the settings.
---
### 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 - although did help to decode some C++ errors
---------
Co-authored-by: Arthur Tazhitdinov <lisnake@gmail.com>
## Summary
**What is the goal of this PR?**
Correction to #1157, which (embarrassingly) failed to actually include
the updated font header files. (Maybe we should generate these at build
time?)
Add Latin Extended-B glyphs for Croatian, Romanian, Pinyin, and European
diacritical variants. Fixes#921.
---
### 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, confirmed
codepoint ranges with Claude**_
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
* **What changes are included?**
Conrgegate the changes of #1074 , #1013 , and extended upon #911 by
@lkristensen
New function implemented in GfxRenderer.cpp
```C++
std::vector<std::string> GfxRenderer::wrappedText(const int fontId, const char* text, const int maxWidth,
const int maxLines, const EpdFontFamily::Style style) const
```
Applied logic to all uses in Lyra, Lyra Extended, and base theme
(continue reading card as pointed out by @znelson
## Additional Context



---
### 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: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
## Summary
* **What is the goal of this PR?** During low memory situations (which I
encounterde a lot during my recent bugfixing activities) a cover was
considered rendered even if the buffer could not be stored.
* **What changes are included?** Proper assignment of flag logic
## Additional Context
-
---
### 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**_
## Summary
**What is the goal of this PR?**
When update activity finds no update or fails, the "Back" button label
was missing. Fixes#1089.
---
### 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**_
## Summary
**What is the goal of this PR?**
Probable fix for #1118. `sanitizeFilename` was only passing through
ASCII characters from filenames. It now maintains valid UTF-8
codepoints, including non-ASCII multibyte sequences. Truncation happens
at a maximum number of bytes, rather than characters, to prevent
filenames with many multibyte sequences from unexpectedly exceeding
FAT32 limits.
---
### 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, I described #1118
to Claude and it suggested sanitizeFilename as the likely cause**_
## Summary
**What is the goal of this PR?**
Add Latin Extended-B glyphs for Croatian, Romanian, Pinyin, and European
diacritical variants. Fixes#921.
---
### 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, confirmed
codepoint ranges with Claude**_
Small edits of the French translation.
## Summary
* **What is the goal of this PR?**
Small fixes of the French translation : fixes on missing/unclear rows,
usage of technical terms better suited for an e-reader GUI, shorter
sentences.
* **What changes are included?**
See above and in the .yaml files; only translations have changed, no
code edit.
## Additional Context
* Nothing else
---
### 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: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
## Summary
**What is the goal of this PR?**
Following up on #1156: generated language header files should be
ignored.
---
### 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**_
## Summary
**What is the goal of this PR?**
I18nKeys.h and I18nStrings.h are generated by gen_i18n.py prior to each
build, so we do not need to maintain a checked-in copy of these files.
---
### 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**_
## Summary
* **What is the goal of this PR?**
Add Vietnamese glyphs support for the reader's built-in fonts, enabling
proper rendering of Vietnamese text in EPUB content.
* **What changes are included?**
- Added 3 new Unicode intervals to `fontconvert.py` covering Vietnamese
characters:
- **Latin Extended-B** (Vietnamese subset only): `U+01A0–U+01B0` — Ơ/ơ,
Ư/ư
- **Vietnamese Extended**: `U+1EA0–U+1EF9` — All precomposed Vietnamese
characters with tone marks (Ả, Ấ, Ầ, Ẩ, Ẫ, Ậ, Ắ, …, Ỹ)
- Re-generated all 54 built-in font header files (Bookerly, Noto Sans,
OpenDyslexic, Ubuntu across all sizes and styles) to include the new
Vietnamese glyphs.
## Additional Context
* **Scope**: This PR only covers the **reader** fonts. The outer UI
still uses the Ubuntu font which does not fully support Vietnamese — UI
and i18n will be addressed in a follow-up PR (per discussion in PR
#1124).
* **Memory impact**:
| Metric | Before | After | Delta |
|---|---|---|---|
| Flash Data (`.rodata`) | 2,971,028 B | 3,290,748 B | **+319,720 B
(+10.8%)** |
| Total image size | 4,663,235 B | 4,982,955 B | **+319,720 B (+6.9%)**
|
| Flash usage | 69.1% | 74.0% | **+4.9 pp** |
| RAM usage | 29.0% | 29.0% | **No change** |
* **Risk**: Low — this is a data-only change (font glyph tables in
`.rodata`). No logic changes, no RAM impact. Flash headroom remains
comfortable at 74%.
---
### AI Usage
Did you use AI tools to help write this code? _**PARTIALLY**_
AI was used to identify the minimal set of Unicode ranges needed for
Vietnamese support and to assist with the PR description.
---------
Co-authored-by: danoooob <danoooob@example.com>
## Summary
* **What is the goal of this PR?** During debugging of #1092 i
desperately needed to monitor the biggest allocatable block of memory on
the heap
* **What changes are included?** Added informaqtion to debug output,
amended monitor utility to pick it up
## Additional Context
---
---
### 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**_
## Summary
* **What is the goal of this PR?** Implements the Italian language
translation for CrossPoint Reader.
* **What changes are included?**
* Added [lib/I18n/translations/italian.yaml] with Italian translations
for all strings.
* Generated the necessary C++ files by running the [gen_i18n.py] script.
* Added myself to the [docs/translators.md] file under the Italian
section.
## Additional Context
---
### 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: Zach Nelson <zach@zdnelson.com>
## Summary
* **What is the goal of this PR?**
Add missing `STR_SCREENSHOT_BUTTON`
## Additional Context
After the screenshot feature was added, a new translation line was
introduced
---
### 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 **_
## Summary
**What is the goal of this PR?**
Improved typesetting, including
[kerning](https://en.wikipedia.org/wiki/Kerning) and
[ligatures](https://en.wikipedia.org/wiki/Ligature_(writing)#Latin_alphabet).
**What changes are included?**
- The script to convert built-in fonts now adds kerning and ligature
information to the generated font headers.
- Epub page layout calculates proper kerning spaces and makes ligature
substitutions according to the selected font.



## Additional Context
- I am not a typography expert.
- The implementation has been reworked from the earlier version, so it
is no longer necessary to omit Open Dyslexic, and kerning data now
covers all fonts, styles, and codepoints for which we include bitmap
data.
- Claude Opus 4.6 helped with a lot of this.
- There's an included test epub document with lots of kerning and
ligature examples, shown in the photos.
**_After some time to mature, I think this change is in decent shape to
merge and get people testing._**
After opening this PR I came across #660, which overlaps in adding
ligature support.
---
### 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 Opus 4.6**_
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
## Summary
* **What is the goal of this PR?**
speed increase gh workflows, optimized for pioarduino Platform
* **What changes are included?**
remove pip and pip cache
install and use `uv`
use pioarduino core instead of Platformio core for optimal performance
with pioarduino Platform
## Additional Context
- signed off by the maintainer of pioarduino
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**< YES | PARTIALLY | NO
>**_NO
## Summary
Bookerly's native TrueType hinting is effectively a no-op at the sizes
used here, causing FreeType to place stems at inconsistent sub-pixel
positions. This results in the 'k' stem (8-bit fringe: 0x38=56) falling
just below the 2-bit quantization threshold while 'l' and 'h' stems
(fringes: 0x4C=76, 0x40=64) land above it --- making 'k' visibly
narrower (2.00px vs 2.33px effective width).
FreeType's auto-hinter snaps all stems to consistent grid positions,
normalizing effective stem width to 2.67px across all glyphs.
Adds --force-autohint flag to fontconvert.py and applies it to Bookerly
only. NotoSans, OpenDyslexic, and Ubuntu fonts are unaffected.
Here is an example of before/after. Take notice of the vertical stems on
characters like `l`, `k`, `n`, `i`, etc. The font is Bookerly 12pt
regular:
**BEFORE**:

**AFTER**:

Claude generated this script to quantitatively determine that this
change makes the vertical stems on a variety of characters more
consistent for Bookerly _only_.
<details>
<summary>Python script</summary>
```python
#!/usr/bin/env python3
"""Compare stem consistency across all font families with and without
auto-hinting.
Run from repo root:
python3 compare_all_fonts.py
"""
import freetype
DPI = 150
CHARS = ["k", "l", "h", "i", "b", "d"]
SIZES = [12, 14, 16, 18]
FONTS = {
"Bookerly":
"lib/EpdFont/builtinFonts/source/Bookerly/Bookerly-Regular.ttf",
"NotoSans":
"lib/EpdFont/builtinFonts/source/NotoSans/NotoSans-Regular.ttf",
"OpenDyslexic":
"lib/EpdFont/builtinFonts/source/OpenDyslexic/OpenDyslexic-Regular.otf",
"Ubuntu": "lib/EpdFont/builtinFonts/source/Ubuntu/Ubuntu-Regular.ttf",
}
MODES = {
"default": freetype.FT_LOAD_RENDER,
"autohint": freetype.FT_LOAD_RENDER | freetype.FT_LOAD_FORCE_AUTOHINT,
}
def q4to2(v):
if v >= 12:
return 3
elif v >= 8:
return 2
elif v >= 4:
return 1
else:
return 0
def get_stem_eff(face, char, flags):
gi = face.get_char_index(ord(char))
if gi == 0:
return None
face.load_glyph(gi, flags)
bm = face.glyph.bitmap
w, h = bm.width, bm.rows
if w == 0 or h == 0:
return None
p2 = []
for y in range(h):
row = []
for x in range(w):
row.append(q4to2(bm.buffer[y * bm.pitch + x] >> 4))
p2.append(row)
# Measure leftmost stem in stable middle rows
mid_start, mid_end = h // 4, h - h // 4
widths = []
for y in range(mid_start, mid_end):
first = next((x for x in range(w) if p2[y][x] > 0), -1)
if first < 0:
continue
last = first
for x in range(first, w):
if p2[y][x] > 0:
last = x
else:
break
eff = sum(p2[y][x] for x in range(first, last + 1)) / 3.0
widths.append(eff)
return round(sum(widths) / len(widths), 2) if widths else None
def main():
for font_name, font_path in FONTS.items():
try:
freetype.Face(font_path)
except Exception:
print(f"\n {font_name}: SKIPPED (file not found)")
continue
print(f"\n{'=' * 80}")
print(f" {font_name}")
print(f"{'=' * 80}")
for size in SIZES:
print(f"\n {size}pt:")
print(f" {'':6s}", end="")
for c in CHARS:
print(f" '{c}' ", end="")
print(" | spread")
for mode_name, flags in MODES.items():
face = freetype.Face(font_path)
face.set_char_size(size << 6, size << 6, DPI, DPI)
vals = []
print(f" {mode_name:6s}", end="")
for c in CHARS:
v = get_stem_eff(face, c, flags)
vals.append(v)
print(f" {v:5.2f}" if v else " N/A", end="")
valid = [v for v in vals if v is not None]
spread = max(valid) - min(valid) if len(valid) >= 2 else 0
marker = " <-- inconsistent" if spread > 0.5 else ""
print(f" | {spread:.2f}{marker}")
if __name__ == "__main__":
main()
```
</details>
Here are the results. The table compares how the font-generation
`autohint` flag affects the range of widths of various characters. Lower
`spread` mean that glyph stroke widths should appear more consistent.
```
Spread = max stem width - min stem width across glyphs (lower = more consistent):
┌──────────────┬──────┬─────────┬──────────┬──────────┐
│ Font │ Size │ Default │ Autohint │ Winner │
├──────────────┼──────┼─────────┼──────────┼──────────┤
│ Bookerly │ 12pt │ 1.49 │ 1.12 │ autohint │
├──────────────┼──────┼─────────┼──────────┼──────────┤
│ │ 14pt │ 1.39 │ 1.13 │ autohint │
├──────────────┼──────┼─────────┼──────────┼──────────┤
│ │ 16pt │ 1.38 │ 1.16 │ autohint │
├──────────────┼──────┼─────────┼──────────┼──────────┤
│ │ 18pt │ 1.90 │ 1.58 │ autohint │
├──────────────┼──────┼─────────┼──────────┼──────────┤
│ NotoSans │ 12pt │ 1.16 │ 0.94 │ mixed │
├──────────────┼──────┼─────────┼──────────┼──────────┤
│ │ 14pt │ 0.83 │ 1.14 │ default │
├──────────────┼──────┼─────────┼──────────┼──────────┤
│ │ 16pt │ 1.41 │ 1.51 │ default │
├──────────────┼──────┼─────────┼──────────┼──────────┤
│ │ 18pt │ 1.74 │ 1.63 │ mixed │
├──────────────┼──────┼─────────┼──────────┼──────────┤
│ OpenDyslexic │ 12pt │ 2.22 │ 1.44 │ autohint │
├──────────────┼──────┼─────────┼──────────┼──────────┤
│ │ 14pt │ 2.57 │ 3.29 │ default │
├──────────────┼──────┼─────────┼──────────┼──────────┤
│ │ 16pt │ 3.13 │ 2.60 │ autohint │
├──────────────┼──────┼─────────┼──────────┼──────────┤
│ │ 18pt │ 3.21 │ 3.23 │ ~tied │
├──────────────┼──────┼─────────┼──────────┼──────────┤
│ Ubuntu │ 12pt │ 1.25 │ 1.31 │ default │
├──────────────┼──────┼─────────┼──────────┼──────────┤
│ │ 14pt │ 1.41 │ 1.64 │ default │
├──────────────┼──────┼─────────┼──────────┼──────────┤
│ │ 16pt │ 2.21 │ 1.71 │ autohint │
├──────────────┼──────┼─────────┼──────────┼──────────┤
│ │ 18pt │ 1.80 │ 1.71 │ autohint │
└──────────────┴──────┴─────────┴──────────┴──────────┘
```
---
### 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? I used AI to make sure I'm
not doing something stupid, since I'm not a typography expert. I made
the changes though.
## Summary
Replace the default esp32-XXXXXXXXXXXX hostname with
CrossPoint-Reader-AABBCCDDEEFF (full MAC address) so the device is
easily identifiable on the router's client list.
## Summary
Fix redefinition of `FILE_*` macro.
Note that there will still be 2 warning:
```
.pio/libdeps/default/WebSockets/src/WebSocketsClient.cpp: In member function 'void WebSocketsClient::clientDisconnect(WSclient_t*, const char*)':
.pio/libdeps/default/WebSockets/src/WebSocketsClient.cpp:573:31: warning: 'virtual void NetworkClient::flush()' is deprecated: Use clear() instead. [-Wdeprecated-declarations]
573 | client->tcp->flush();
| ~~~~~~~~~~~~~~~~~~^~
```
--> I assume the upstream library need to fix it
And:
```
src/activities/Activity.cpp:8:1: warning: 'noreturn' function does return
8 | }
| ^
```
Will be fixed in #1016
---
### 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**
## Summary
* **What is the goal of this PR?**
Fix inconsistent WiFi strings in Czech translation.
* **What changes are included?**
Only a few `Wi-Fi` strings changed to `WiFi` to maintain consistency.
---
### 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**_
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
Added Romanian translations for newly addded strings
* **What changes are included?**
Just the translations in the localisation file.
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**< NO >**_
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
- This PR updates the USER_GUIDE.md to match recent changes, in
particular 1.1.0, alongside some minor copyediting.
* **What changes are included?**
- Renamed titles for screens to match current UI.
- Sorted Settings section by order in UI, added subheadings for Settings
pages, and added all current settings.
- Updated System Navigation behaviour description to match #726.
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
This is an admittedly quick edit I did to get USER_GUIDE.md up to
scratch alongside the release of 1.1.0. I'm a writer, not a programmer,
so there are some things that will probably need improvement.
- ~Recent Books needs to be added, something I could add to this PR if
needed.~
- ~Remap Front Buttons might need to be updated.~
These have been added in later commits, Remap Front Buttons might still
need more detail.
- The UI Theme, Embedded Style, Hyphenation, WiFi Networks, KOReader
Sync, and Clear Reading Cache settings might need better (or more
technically specific) descriptions.
Two questions I have:
- ~Should the new Settings subheadings be added to the Table of
Contents?~ Added in later commits.
- The Manual of Style/formatting for USER_GUIDE.md, especially in the
Settings section, is somewhat inconsistent. Let me know if any of my
edits don't fit with this.
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**< YES | PARTIALLY | NO
>**_
NO
---------
Co-authored-by: Zach Nelson <zach@zdnelson.com>
## Summary
Ref: https://github.com/crosspoint-reader/crosspoint-reader/issues/1110
Power lock is automatically acquired on `render()`. However, instead of
using `render()`, sleep activity render everything right inside
`onEnter()`, so no power lock was acquired.
After https://github.com/crosspoint-reader/crosspoint-reader/pull/1016 ,
the power lock will also be acquired on activity transition.
---
### 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**
## Summary
* **What is the goal of this PR?** Address expected hyphenation issue
from
https://github.com/crosspoint-reader/crosspoint-reader/issues/998#issuecomment-3940533510
* Closes#998
* **What changes are included?** Add `„` (U+201E, _Double Low-9
Quotation Mark_), `‚` (U+201A, _Single Low-9 Quotation Mark_) and `‹`
(U+2039, _Single Left-pointing Angle Quotation Mark_) exceptions, other
quote types were handled correctly.
**Before**
<img width="480" height="155" alt="hyph3"
src="https://github.com/user-attachments/assets/e06b4071-2c8c-4814-965d-96fbe302a450"
/>
**After**
<img width="480" height="154" alt="hyph3-fix"
src="https://github.com/user-attachments/assets/4f7f5406-e200-451c-8bee-3f410cc84bbe"
/>
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**NO**_
## Summary
**What is the goal of this PR?**
Consolidated repeated logic to fall back to REPLACEMENT_GLYPH.
---
### 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**_
## Summary
**What is the goal of this PR?**
Small cleanup to make getTheme and getMetrics methods on UITheme const.
They return const refs, so updated call sites to use `const auto&`.
Realistically this won't make much performance difference, but it better
conveys the nature of theme metrics being shared const state.
---
### 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**_
## Summary
* **What is the goal of this PR?** Add a link to files to download on
web server
## Additional Context
* There was already an api to download files, i just added a link to it
for files.
---
### AI Usage
Did you use AI tools to help write this code? **NO**
Addresses #621
---------
Co-authored-by: Eliz Kilic <elizk@google.com>
## Summary
Ref:
https://github.com/crosspoint-reader/crosspoint-reader/pull/1047#discussion_r2838439305
To reproduce:
1. Open file transfer
2. Join a network
3. Once it's connected, press (hold) back
---
### 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**
## Summary
* **What is the goal of this PR?**
A Ukrainian translation for the GUI
* **What changes are included?**
Everything according to
https://github.com/crosspoint-reader/crosspoint-reader/blob/master/docs/i18n.md
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
Nope
---
### 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 >**_ as a
consistency validation
## Summary
* Remove miniz and move completely to uzlib
* Move uzlib interfacing to InflateReader to better modularise inflation
code
---
### 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 helped with
the extraction and refactor
## Summary
* Resolve several build warnings
---
### 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
## Summary
- Upgrade platform from espressif32 6.12.0 (Arduino Core 2.0.17) to
pioarduino 55.03.37 (Arduino Core 3.3.7, ESP-IDF 5.5.2)
- Add WebDAV Class 1 server (RFC 4918) - SD card can be mounted as a
network drive
- I also slightly fixed the SDK and also made a [pull request
](https://github.com/open-x4-epaper/community-sdk/pull/21)
First PR #1030 (was closed because the implementation was based on an
old version of the libraries)
Issue #439
---------
Co-authored-by: Dave Allie <dave@daveallie.com>
## Summary
* platformio.ini is a repository based config for platformio and cannot
be modified without constant nagging of git to include it into you
commits
* PlatformIO allows you to split your configuration into multiple files
using the extra_configs option in the [platformio] block. This
effectively merges other .ini files into your main one. This will be
silently ignored if such a file does not exist
## Additional Context
* Modifiy platformio.ini and add a .gitignore entry to ignore your local
config
* eg my own ``platformio.local.ini``:
```
[env:custom]
extends = base
build_flags =
${base.build_flags}
-DCROSSPOINT_VERSION=\"${crosspoint.version}-custom\"
; inclusion of additional fonts is disabled in custom builds to save space
-DOMIT_FONTS
```
---
### 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: Dave Allie <dave@daveallie.com>
## Summary
- **SleepActivity.cpp**: Add missing `file.close()` calls in 3 code
paths that open BMP files for sleep screen rendering but never close
them before returning. Affects random custom sleep images, the
`/sleep.bmp` fallback, and book cover sleep screens.
- **CrossPointWebServer.cpp**: Add missing `dir.close()` in the delete
handler when `Storage.open()` returns a valid `FsFile` that is not a
directory.
## Context
SdFat is configured with `DESTRUCTOR_CLOSES_FILE=0`, which means
`FsFile` objects are **not** automatically closed when they go out of
scope. Every opened file must be explicitly closed.
The SleepActivity leaks are particularly impactful because they occur on
every sleep cycle. While ESP32 deep sleep clears RAM on wake, these
leaks can still affect the current session if sleep screen rendering is
triggered multiple times (e.g., cover preview, or if deep sleep fails to
engage).
The web server leak in `handleDelete()` is a minor edge case (directory
path that opens successfully but `isDirectory()` returns false), but
it's still worth fixing for correctness.
## Test plan
- [x] Verify sleep screen still renders correctly (custom BMP, fallback,
cover modes)
- [x] Verify folder deletion still works via the web UI
- [ ] Monitor free heap before/after sleep screen rendering to confirm
no leak
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Jan Bažant <janbazant@Jan--Mac-mini.local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Dave Allie <dave@daveallie.com>
## Summary
* **What is the goal of this PR?** Enhances the file manager with
multi-select deletion functionality and improved UI formatting.
* **What changes are included?**
* Added multi-select capability for file deletion in the web interface
* Fixed formatting issues in file table for folder rows
* Updated [.gitignore] to exclude additional build artifacts and cache
files
* Refactored CrossPointWebServer.cpp to support batch file deletion
* Enhanced FilesPage.html with improved UI for file selection and
deletion
## Additional Context
* The file deletion endpoint now handles multiple files in a single
request, improving efficiency when removing multiple files
* Changes are focused on the web file manager component only
---
### 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: Jessica Harrison <jessica.harrison@entelect.co.za>
Co-authored-by: Dave Allie <dave@daveallie.com>
## Summary
* This PR introduces a migration from binary file storage to JSON-based
storage for application settings, state, and various credential stores.
This improves readability, maintainability, and allows for easier manual
configuration editing.
* Benefits:
- Settings files are now JSON and can be easily read/edited manually
- Easier to inspect application state and settings during development
- JSON structure is more flexible for future changes
* Drawback: around 15k of additional flash usage
* Compatibility: Seamless migration preserves existing user data
## Additional Context
1. New JSON I/O Infrastructure files:
- JsonSettingsIO: Core JSON serialization/deserialization logic using
ArduinoJson library
- ObfuscationUtils: XOR-based password obfuscation for sensitive data
2. Migrated Components (now use JSON storage with automatic binary
migration):
- CrossPointSettings (settings.json): Main application settings
- CrossPointState (state.json): Application state (open book, sleep
mode, etc.)
- WifiCredentialStore (wifi.json): WiFi network credentials (Password
Obfuscation: Sensitive data like WiFi passwords, uses XOR encryption
with fixed keys. Note: This is obfuscation, not cryptographic security -
passwords can be recovered with the key)
- KOReaderCredentialStore (koreader.json): KOReader sync credentials
- RecentBooksStore (recent.json): Recently opened books list
3. Migration Logic
- Forward Compatibility: New installations use JSON format
- Backward Compatibility: Existing binary files are automatically
migrated to JSON on first load
- Backup Safety: Original binary files are renamed with .bak extension
after successful migration
- Fallback Handling: If JSON parsing fails, system falls back to binary
loading
4. Infrastructure Updates
- HalStorage: Added rename() method for backup operations
---
### 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: Dave Allie <dave@daveallie.com>
### Summary
This PR introduces a lightweight contributor onboarding docs section
under `docs/contributing/` and improves local formatting ergonomics for
first-time contributors.
The goal is to make CrossPoint easier to contribute to for software
developers who are new to embedded systems (like me), while keeping
onboarding modular and aligned with existing project docs.
### What changed
- Added contributor docs hub: `docs/contributing/README.md`
- Added focused onboarding pages:
- `docs/contributing/getting-started.md`
- `docs/contributing/architecture.md`
- `docs/contributing/development-workflow.md`
- `docs/contributing/testing-debugging.md`
- Linked contributor docs from `README.md` for discoverability
- Expanded architecture documentation with Mermaid diagrams
- Improved `bin/clang-format-fix`:
- prefers `clang-format-21` when available
- validates formatter version and fails fast with a clear message if too
old
- handles missing positional arg safely
- Updated docs to explain common `clang-format` setup/version issues and
install paths (including fallback steps when `clang-format-21` is
unavailable in default apt sources)
### Why
- There was no dedicated contributor onboarding path; first-time
contributors had to infer workflow from multiple files.
- New contributors (especially from non-embedded backgrounds) need a
clear mental model of architecture, runtime flow, and debugging process.
- Local formatting setup caused avoidable friction due to clang-format
version mismatch (`.clang-format` expects newer keys used in CI).
- The updates make contribution setup more predictable, reduce
onboarding confusion, and align local checks with CI expectations.
### Additional context
- No firmware behavior/runtime logic was changed; this PR focuses on
contributor experience and tooling clarity.
---
### AI Usage
> Did you use AI tools to help write this code?
Yes, I used AI tools to assist with generating the documentation. I then
manually reviewed, tested, and refined the code to ensure it works
correctly. please feel free to point out any discrepancies or areas for
improvement.
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
Implement automatic dark theme on server files.
Instead of a big change proposed in
https://github.com/crosspoint-reader/crosspoint-reader/pull/837, this PR
introduces a simple implementation of light/dark themes.
* **What changes are included?**
- Choose `#6e9a82` as accent color (taken from
)
- Implement a very basic media query for dark themes (`@media
(prefers-color-scheme: dark)`)
- Update style using CSS variables
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
We can think of it as a incremental enhancement, this is the first phase
of a series of PRs (hopefully).
Next steps/Phases:
1. Light/Dark themes (this PR)
2. Load external CSS file to avoid duplication
3. HTML enhancement (for example, use dialog element instead of divs)
4. Use SVG instead of emojis
5. Use Vite + Typescript to improve DX and have better minification
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**< YES | PARTIALLY | NO
>**_
---------
Co-authored-by: carlosbonadeo <carlosbonadeo@skyscanner.net>
## Summary
_Revision to @blindbat's #802. Description comes from the original PR._
- Replace `std::list` with `std::vector` for word storage in `TextBlock`
and `ParsedText`
- Use index-based access (`words[i]`) instead of iterator advancement
(`std::advance(it, n)`)
- Remove the separate `continuesVec` copy that was built from
`wordContinues` for O(1) access — now unnecessary since
`std::vector<bool>` already provides O(1) indexing
## Why
`std::list` allocates each node individually on the heap with 16 bytes
of prev/next pointer overhead per node. For text layout with many small
words, this means:
- Scattered heap allocations instead of contiguous memory
- Poor cache locality during iteration (each node can be anywhere in
memory)
- Per-node malloc/free overhead during construction and destruction
`std::vector` stores elements contiguously, giving better cache
performance during the tight rendering and layout loops. The
`extractLine` function also benefits: list splice was O(1) but required
maintaining three parallel iterators, while vector range construction
with move iterators is simpler and still efficient for the small
line-sized chunks involved.
## Files changed
- `lib/Epub/Epub/blocks/TextBlock.h` / `.cpp`
- `lib/Epub/Epub/ParsedText.h` / `.cpp`
## AI Usage
YES
## Test plan
- [ ] Open an EPUB with mixed formatting (bold, italic, underline) —
verify text renders correctly
- [ ] Open a book with justified text — verify word spacing is correct
- [ ] Open a book with hyphenation enabled — verify words break
correctly at hyphens
- [ ] Navigate through pages rapidly — verify no rendering glitches or
crashes
- [ ] Open a book with long paragraphs — verify text layout matches
pre-change behavior
---------
Co-authored-by: Kuanysh Bekkulov <kbekkulov@gmail.com>
## Summary
* This PR fixes decomposed diacritic handling end-to-end:
- Hyphenation: normalize common Latin base+combining sequences to
precomposed codepoints before Liang pattern matching, so decomposed
words hyphenate correctly
- Rendering: correct combining-mark placement logic so non-spacing marks
are attached to the preceding base glyph in normal and rotated text
rendering paths, with corresponding text-bounds consistency updates.
- Hyphenation around non breaking space variants have been fixed (and
extended)
- Hyphenation of terms that already included of hyphens were fixed to
include Liang pattern application (eg "US-Satellitensystem" was
*exclusively* broken at the existing hyphen)
## Additional Context
* Before
<img width="800" height="480" alt="2"
src="https://github.com/user-attachments/assets/b9c515c4-ab75-45cc-8b52-f4d86bce519d"
/>
* After
<img width="480" height="800" alt="fix1"
src="https://github.com/user-attachments/assets/4999f6a8-f51c-4c0a-b144-f153f77ddb57"
/>
<img width="800" height="480" alt="fix2"
src="https://github.com/user-attachments/assets/7355126b-80c7-441f-b390-4e0897ee3fb6"
/>
* Note 1: the hyphenation fix is not a 100% bullet proof implementation.
It adds composition of *common* base+combining sequences (e.g. O +
U+0308 -> Ö) during codepoint collection. A complete solution would
require implementing proper Unicode normalization (at least NFC,
possibly NFKC in specific cases) before hyphenation and rendering,
instead of hand-mapping a few combining marks. That was beyond the scope
of this fix.
* Note 2: the render fix should be universal and not limited to the
constraints outlined above: it properly x-centers the compund glyph over
the previous one, and it uses at least 1pt of visual distance in y.
Before:
<img width="478" height="167" alt="Image"
src="https://github.com/user-attachments/assets/f8db60d5-35b1-4477-96d0-5003b4e4a2a1"
/>
After:
<img width="479" height="180" alt="Image"
src="https://github.com/user-attachments/assets/1b48ef97-3a77-475a-8522-23f4aca8e904"
/>
* This should resolve the issues described in #998
---
### 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**_
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
* The goal is to fix the title of books in the Home Screen.
Before

After:

* **What changes are included?**
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? YES, Cursor
## Summary
* **What is the goal of this PR?**
* improve Spanish translations
* **What changes are included?**
- Fix typos and accents (Librería, conexión, etc.)
- Translate untranslated strings (BOOTING, SLEEPING, etc.)
- Improve consistency and conciseness
- Fix question mark placement (¿...?)
- Standardize terminology (Punto de Acceso, Suspensión, etc.)
---
### 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 >**_
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
Implements new feature for viewing .bmp files directly from the "Browse
Files" menu.
* **What changes are included?**
You can now view .bmp files when browsing. You can click the select
button to open the file, and then click back to close it and continue
browsing in the same location. Once open a file will display on the
screen with no additional options to interact outside of exiting with
the back button.
The attached video shows this feature in action:
https://github.com/user-attachments/assets/9659b6da-abf7-4458-b158-e11c248c8bef
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
The changes implemented in #884 are also present here as this feature is
actually what led to me noticing this issue. I figured I would add that
PR as a separate request in case that one could be more easily merged
given this feature is significantly more complicated and will likely be
subject to more intense review.
---
### 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: Copilot <175728472+Copilot@users.noreply.github.com>
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
Add support for Catalan language user interface.
* **What changes are included?**
A new i18n file catalan.yml.
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? NO
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
To add upport for a romanian language user interface.
* **What changes are included?**
A new i18n file `romanian.yml`
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**< NO >**_
## Summary
Just fixed a typo `Xtink` -> `Xteink`
---
### 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 >**_
## Summary
* Destroy CSS Cache file when invalid
## Additional Context
* Fixes issue where it would attempt to rebuild every book open
---
### 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
## Summary
* **What is the goal of this PR?**
* **What changes are included?**
- Adapt card width to cover image aspect ratio in Classic theme
- Increase homeTopPadding from 20px to 40px to avoid overlap with
battery icon
- Card width now calculated from BMP dimensions instead of fixed 240px
- Maximum card width limited to 90% of screen width
- Falls back to original behavior (half screen width) when no cover
available
## Additional Context
* Solve conflicts in PR #683
Before:
<img width="1052" height="1014" alt="image"
src="https://github.com/user-attachments/assets/6c857913-d697-4e9e-9695-443c0a4c0804"
/>
PR:

---
### 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 >**_
## Summary
* In a sample book I loaded, it had 900+ CSS rules, and took up 180kB of
memory loading the cache in
* Looking at the rules, a lot of them were completely useless as we only
ever apply look for 3 kinds of CSS rules:
* `tag`
* `tag.class1`
* `.class1`
* Stripping out CSS rules with descendant, nested, attribute matching,
sibling matching, pseudo element selection (as we never actually read
these from the cache) reduced the rule count down to 200
## Additional Context
* I've left in `.class1.class2` rules for now, even though we
technically can never match on them as they're likely to be addressed
soonest out of the all the CSS expansion
* Because we don't ever delete the CSS cache, users will need to delete
the book cache through the menu in order to get this new logic
* A new PR should be done up to address this - tracked here
https://github.com/crosspoint-reader/crosspoint-reader/issues/1015
---
### 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
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
When no width is set for an image, the image currently automatically
sets to the width of the page. However, with this fix, the parser will
use the height and aspect ratio of the image to properly set a height
for it. See below example:
Before:

After:

* **What changes are included?✱
Changes to the CSS parser
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? YES, Cursor
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
Epub image support was added in #556. The goal of this PR is to document
that in the readme.
* **What changes are included?**
Only the checkmark in the readme.
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**< NO >**_
## Summary
Fixes https://github.com/crosspoint-reader/crosspoint-reader/issues/1011
Use double FAST_REFRESH for image pages to prevent grayscale washout,
HALF_REFRESH sets e-ink particles too firmly for the grayscale LUT to
adjust, causing washed-out images (especially large, light-gray ones).
Replace HALF_REFRESH with @pablohc's double FAST_REFRESH technique:
blank only the image bounding box area, then re-render with images. This
clears ghosting while keeping particles loosely set for grayscale.
## Additional Context
---
### 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 >**_
## Summary
* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)
Improve legibility of Cover Icons on the home page and elsewhere. Fixes
#898
Re implements the changes made in #907 that were overwritten by the new
lyra themes
* **What changes are included?**
Cover outline is now shown even when cover is found to prevent issues
with low contrast covers blending into the background. Photo is attached
below:
<img width="1137" height="758" alt="Untitled (4)"
src="https://github.com/user-attachments/assets/21ae6c94-4b43-4a0c-bec7-a6e4c642ffad"
/>
## Additional Context
* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
specific areas to focus on).
Re implements the changes made in #907 that were overwritten by the new
lyra themes
---
### 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**_
## Summary
* ``renderChar`` checked ``is2Bit`` on every pixel inside the inner
loop, even though the value is constant for the lifetime of a single
glyph
* Moved the branch above both loops so each path (2-bit antialiased /
1-bit monochrome) runs without a per-pixel conditional
* Eliminates redundant work in the two inner loops that render font
glyphs to the frame buffer, targeting ``renderChar`` and
``drawTextRotated90CW`` in ``GfxRenderer.cpp``
## Additional Context
* Measured on device using a dedicated framebuffer benchmark (no display
refresh). 100 repetitions of "The quick brown fox jumps".
| Test | Before | After | Change |
|-----------------|-----------------|-----------------|---------|
| drawText UI12 | 1,337 µs/call | 1,024 µs/call | −23%|
| drawText Bookerly14 | 2.174 µs / call | 1,847 µs/call | −15% |
---
### 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 did
the analysis and wrote the benchmarks
## Summary
* **What is the goal of this PR?** Small fix for bug I found.
## Additional Context
1. `RecentBooksActivity::loop()` calls
`onSelectBook(recentBooks[selectorIndex].path)` - passing a
**reference** to the path
2. `onSelectBook` is `onGoToReader` which first calls `exitActivity()`
3. `exitActivity()` triggers `RecentBooksActivity::onExit()` which call
`recentBooks.clear()`
4. The string reference `initialEpubPath` is now a **dangling
reference** - the underlying string has been destroyed
5. When the reference is then used in `new ReaderActivity(...)`, it
reads garbage memory
6. The same issue occurs in `HomeActivity` at line 200 with the same
pattern
The fix is to make a copy of the string in `onGoToReader` before calling
`exitActivity()`, so the path data persists even after the activity
clears its data structures.
---
### AI Usage
Did you use AI tools to help write this code? _**< YES >**_ Claude found
the bug, after I shared with it a serial log.
## Summary
The introduction of `HalGPIO` moved the `BatteryMonitor battery` object
into the member function `HalGPIO::getBatteryPercentage()`.
Then, with the introduction of `HalPowerManager`, this function was
moved to `HalPowerManager::getBatteryPercentage()`.
However, the original `BatteryMonitor battery` object is still utilized
by themes for displaying the battery percentage.
This PR replaces these deprecated uses of `BatteryMonitor battery` with
the new `HalPowerManager::getBatteryPercentage()` function.
---
### AI Usage
While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.
Did you use AI tools to help write this code? _**NO **_
## Summary
* Increased `PNG_MAX_BUFFERED_PIXELS` from 6402 to 16416 in
`platformio.ini` to support up to 2048px wide RGBA images
* adds a check to abort decoding and log an error if the required PNG
scanline buffer exceeds the configured `PNG_MAX_BUFFERED_PIXELS`,
preventing possible buffer overruns.
* fixes
https://github.com/crosspoint-reader/crosspoint-reader/issues/993
---
### 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 >**_
## Summary
* **What is the goal of this PR?**
I flashed the last revision before commit f1740dbe, and chapter indexing
worked without any crashes.
After applying f1740dbe, the same chapter consistently triggered a
device reboot during indexing.
The affected chapter contains inline equation images surrounded by
styled (bold/italic) text that includes special math/symbol characters.
## Additional Context
Prior to f1740dbe, both `getTextAdvanceX()` and `getSpaceWidth()` always
measured text using `EpdFontFamily::REGULAR`, regardless of the actual
style.
Commit f1740dbe improved correctness by passing the active style so
spacing is calculated using the actual bold/italic font variant.
However, bold and italic variants have narrower Unicode coverage than
the regular font. When a character exists in the regular font but not in
the selected styled variant, `pdFont::getGlyph()` returns `nullptr`.
The updated measurement functions did not check for this and immediately
dereferenced the pointer:
`width += font.getGlyph(cp, style)->advanceX; // nullptr->advanceX`
Because `advanceX` is located at byte offset 2 within `EpdGlyph`,
dereferencing a null pointer caused the CPU to attempt a load from
address `0x00000002`, resulting in a RISC-V:
Load access fault
MCAUSE = 5
MTVAL = 2
## Fix
Added null-safety checks to both `getTextAdvanceX()` and
`getSpaceWidth()`, following the same pattern used in the rendering
path:
If the glyph is missing in the selected style → fall back to the
replacement glyph.
If the replacement glyph is also unavailable → treat the character as
zero-width.
This preserves the improved style-correct spacing while preventing
crashes.
No behavioral changes occur for characters that are supported by the
selected font variant.
---
### AI Usage
Did you use AI tools to help write this code? _**< YES >**_
I encounter this bug while testing 1.1.0 RC.
I pasted the serial log to Claude, which identify the bug and fixed it.
I can confirm now the chapter in question is indexed and loaded
correctly.
2026-02-20 03:44:46 +11:00
303 changed files with 232642 additions and 150090 deletions
For more details about the scope of the project, see the [SCOPE.md](SCOPE.md) document.
@@ -154,6 +156,8 @@ For more details on the internal file structures, see the [file formats document
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.
@@ -39,6 +39,14 @@ usability over "swiss-army-knife" functionality.
***Complex Annotation:** No typed out notes. These features are better suited for devices with better input
capabilities and more powerful chips.
### In-scope — Technically Unsupported
*These features align with CrossPoint's goals but are impractical on the current hardware or produce poor UX.*
* **Clock Display:** The ESP32-C3's RTC drifts significantly during deep sleep; making the clock untrustworthy after any sleep cycle. NTP sync could help, but CrossPoint doesn't connect to the internet on every boot.
* **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.
## 3. Idea Evaluation
While I appreciate the desire to add new and exciting features to CrossPoint Reader, CrossPoint Reader is designed to be
Button layout can be customized in **[Settings](#35-settings)**.
Button layout can be customized in the **[Controls Settings](#363-controls)**.
### Taking a Screenshot
When the Power Button and Volume Down button are pressed at the same time, it will take a screenshot and save it in the folder `screenshots/`.
Alternatively, while reading a book, press the **Confirm** button to open the reader menu and select **Take screenshot**.
---
@@ -45,9 +56,9 @@ Button layout can be customized in **[Settings](#35-settings)**.
### Power On / Off
To turn the device on or off, **press and hold the Power button for approximately half a second**.
In **[Settings](#35-settings)** you can configure the power button to turn the device off with a short press instead of a long one.
In the **[Controls Settings](#363-controls)** you can configure the power button to turn the device off with a short press instead of a long one.
To reboot the device (for example if it's frozen, or after a firmware update), press and release the Reset button, and then quickly press and hold the Power button for a few seconds.
To reboot the device (for example after a firmware update or if it's frozen), press and release the Reset button, and then quickly press and hold the Power button for a few seconds.
### First Launch
@@ -62,29 +73,34 @@ Upon turning the device on for the first time, you will be placed on the **[Home
### 3.1 Home Screen
The Home Screen is the main entry point to the firmware. From here you can navigate to **[Reading Mode](#4-reading-mode)** with the most recently read book, **[Book Selection](#32-book-selection)**, **[Settings](#35-settings)**, or the **[File Upload](#34-file-upload-screen)** screen.
The Home screen is the main entry point to the firmware. From here you can navigate to **[Reading Mode](#4-reading-mode)** with the most recently read book, the**[Browse Files](#33-browse-files-screen)** screen, the **[Recent Books](#34-recent-books-screen)** screen, the **[File Transfer](#35-file-transfer-screen)** screen, or **[Settings](#36-settings)**.
### 3.2 Book Selection
The Book Selection acts as a folder and file browser.
* **Navigate List:** Use **Left** (or **Volume Up**), or **Right** (or **Volume Down**) to move the selection cursor up and down through folders and books. You can also long-press these buttons to scroll a full page up or down.
* **Open Selection:** Press **Confirm** to open a folder or read a selected book.
### 3.3 Reading Mode
### 3.2 Reading Mode
See [Reading Mode](#4-reading-mode) below for more information.
### 3.4 File Upload Screen
### 3.3 Browse Files Screen
The File Upload screen allows you to upload new e-books to the device. When you enter the screen, you'll be prompted with a WiFi selection dialog and then your X4 will start hosting a web server.
The Browse Files screen acts as a file and folder browser.
* **Navigate List:** Use **Left** (or **Volume Up**), or **Right** (or **Volume Down**) to move the selection cursor up and down through folders and books. You can also long-press these buttons to scroll a full page up or down.
* **Open Selection:** Press **Confirm** to open a folder or read a selected book.
* **Delete Files:** Hold and release **Confirm** to delete the selected file. You will be given an option to either confirm or cancel deletion. Folder deletion is not supported.
### 3.4 Recent Books Screen
The Recent Books screen lists the most recently opened books in a chronological view, displaying title and author.
### 3.5 File Transfer Screen
The File Transfer screen allows you to upload new e-books to the device. When you enter the screen, you'll be prompted with a WiFi selection dialog and then your X4 will start hosting a web server.
See the [webserver docs](./docs/webserver.md) for more information on how to connect to the web server and upload files.
> [!TIP]
> Advanced users can also manage files programmatically or via the command line using `curl`. See the [webserver docs](./docs/webserver.md) for details.
### 3.4.1 Calibre Wireless Transfers
### 3.5.1 Calibre Wireless Transfers
CrossPoint supports sending books from Calibre using the CrossPoint Reader device plugin.
@@ -96,23 +112,26 @@ CrossPoint supports sending books from Calibre using the CrossPoint Reader devic
3. Make sure your computer is on the same WiFi network.
4. In Calibre, click "Send to device" to transfer books.
### 3.5 Settings
### 3.6 Settings
The Settings screen allows you to configure the device's behavior. There are a few settings you can adjust:
#### 3.6.1 Display
- **Sleep Screen**: Which sleep screen to display when the device sleeps:
- "Dark" (default) - The default dark Crosspoint logo sleep screen
- "Light" - The same default sleep screen, on a white background
- "Custom" - Custom images from the SD card; see [Sleep Screen](#36-sleep-screen) below for more information
- "Custom" - Custom images from the SD card; see [Sleep Screen](#37-sleep-screen) below for more information
- "Cover" - The book cover image (Note: this is experimental and may not work as expected)
- "None" - A blank screen
- "Cover + Custom" - The book cover image, fallbacks to "Custom" behavior
- "Cover + Custom" - The book cover image, falls back to "Custom" behavior
- **Sleep Screen Cover Mode**: How to display the book cover when "Cover" sleep screen is selected:
- "Fit" (default) - Scale the image down to fit centered on the screen, padding with white borders as necessary
- "Crop" - Scale the image down and crop as necessary to try to to fill the screen (Note: this is experimental and may not work as expected)
- **Sleep Screen Cover Filter**: What filter will be applied to the book cover when "Cover" sleep screen is selected
- "Crop" - Scale the image down and crop as necessary to try to fill the screen (Note: this is experimental and may not work as expected)
- **Sleep Screen Cover Filter**: What filter will be applied to the book cover when "Cover" sleep screen is selected:
- "None" (default) - The cover image will be converted to a grayscale image and displayed as it is
- "Contrast" - The image will be displayed as a black & white image without grayscale conversion
- "Inverted" - The image will be inverted as in white&black and will be displayed without grayscale conversion
- "Inverted" - The image will be inverted as in white & black and will be displayed without grayscale conversion
- **Status Bar**: Configure the status bar displayed while reading:
- "None" - No status bar
- "No Progress" - Show status bar without reading progress
@@ -120,56 +139,199 @@ The Settings screen allows you to configure the device's behavior. There are a f
- "Full w/ Book Bar" - Show status bar with book progress (as bar)
- "Book Bar Only" - Show book progress (as bar)
- "Full w/ Chapter Bar" - Show status bar with chapter progress (as bar)
- **Hide Battery %**: Configure where to suppress the battery pecentage display in the status bar; the battery icon will still be shown:
- "Never" - Always show battery percentage (default)
- **Hide Battery %**: Configure where to suppress the battery percentage display in the status bar; the battery icon will still be shown:
- "Never" (default) - Always show battery percentage
- "In Reader" - Show battery percentage everywhere except in reading mode
- "Always" - Always hide battery percentage
- **Extra Paragraph Spacing**: If enabled, vertical space will be added between paragraphs in the book. If disabled, paragraphs will not have vertical space between them, but will have first-line indentation.
- **Text Anti-Aliasing**: Whether to show smooth grey edges (anti-aliasing) on text in reading mode. Note this slows down page turns slightly.
- **Short Power Button Click**: Controls the effect of a short click of the power button:
- "Ignore" - Require a long press to turn off the device
- "Sleep" - A short presspowers the device off
- "Page Turn" - A short press in reading mode turns to the next page; a long press turns the device off
- **Refresh Frequency**: Set how often the screen does a full refresh while reading to reduce ghosting; options are every 1, 5, 10, 15, or 30 pages.
- **UI Theme**: Set which UI theme to use:
- "Classic" - The original Crosspoint theme
- "Lyra" - The new theme for Crosspoint featuring rounded elements and menu icons
- "Lyra Extended" - Lyra, but displays 3 books instead of 1 on the **[Home Screen](#31-home-screen)**
- **Sunlight Fading Fix**: Configure whether to enable a software-fix for the issue where white X4 models may fade when used in direct sunlight:
- "OFF" (default) - Disable the fix
- "ON" - Enable the fix
#### 3.6.2 Reader
- **Reader Font Family**: Choose the font used for reading:
- "Bookerly" (default) - Amazon's reading font
- "Noto Sans" - Google's sans-serif font
- "Open Dyslexic" - Font designed for readers with dyslexia
- **Reader Font Size**: Adjust the text size for reading; options are "Small", "Medium" (default), "Large", or "X Large".
- **Reader Line Spacing**: Adjust the spacing between lines; options are "Tight", "Normal" (default), or "Wide".
- **Reader Screen Margin**: Controls the screen margins in Reading Mode between 5 and 40 pixels in 5-pixel increments.
- **Reader Paragraph Alignment**: Set the alignment of paragraphs; options are "Justified" (default), "Left", "Center", or "Right".
- **Embedded Style**: Whether to use the EPUB file's embedded HTML and CSS stylisation and formatting; options are "ON" or "OFF".
- **Hyphenation**: Whether to hyphenate text in Reading Mode; options are "ON" or "OFF".
- **Reading Orientation**: Set the screen orientation for reading EPUB files:
- "Portrait" (default) - Standard portrait orientation
- **Front Button Layout**: Configure the order of the bottom edge buttons:
-Back, Confirm, Left, Right (default)
-Left, Right, Back, Confirm
- Left, Back, Confirm, Right
- Back, Confirm, Right, Left
- **Side Button Layout (reader)**: Swap the order of the up and down volume buttons from Previous/Next to Next/Previous. This change is only in effect when reading.
- **Long-press Chapter Skip**: Set whether long-pressing page turn buttons skip to the next/previous chapter.
- **Extra Paragraph Spacing**: Set how to handle paragraph breaks:
-"ON" - Vertical space will be added between paragraphs in Reading Mode
-"OFF" - Paragraphs will not have vertical space added, but will have first-line indentation
- **Text Anti-Aliasing**: Whether to show smooth grey edges (anti-aliasing) on text in reading mode. Note this slows down page turns slightly.
#### 3.6.3 Controls
- **Remap Front Buttons**: A menu for customising the function of each bottom edge button.
- **Side Button Layout (reader)**: Swap the order of the up and down volume buttons from "Prev/Next" (default) to "Next/Prev". This change is only in effect when reading.
- **Long-press Chapter Skip**: Set whether long-pressing page turn buttons skips to the next/previous chapter:
- "Chapter Skip" (default) - Long-pressing skips to next/previous chapter
- "Page Scroll" - Long-pressing scrolls a page up/down
-Swap the order of the up and down volume buttons from Previous/Next to Next/Previous. This change is only in effect when reading.
- **Reader Font Family**: Choose the font used for reading:
- "Bookerly" (default) - Amazon's reading font
- "Noto Sans" - Google's sans-serif font
- "Open Dyslexic" - Font designed for readers with dyslexia
- **Reader Font Size**: Adjust the text size for reading; options are "Small", "Medium", "Large", or "X Large".
- **Reader Line Spacing**: Adjust the spacing between lines; options are "Tight", "Normal", or "Wide".
- **Reader Screen Margin**: Controls the screen margins in reader mode between 5 and 40 pixels in 5 pixel increments.
- **Reader Paragraph Alignment**: Set the alignment of paragraphs; options are "Justified" (default), "Left", "Center", or "Right".
- **Time to Sleep**: Set the duration of inactivity before the device automatically goes to sleep.
- **Refresh Frequency**: Set how often the screen does a full refresh while reading to reduce ghosting.
- **Sunlight Fading Fix**: Configure whether to enable a software-fix for the issue where white X4 models may fade when used in direct sunlight
- "OFF" (default) - Disable the fix
- "ON" - Enable the fix
-**Short Power Button Click**: Controls the effect of a short click of the power button:
- "Ignore" (default) - Require a long press to turn off the device
- "Sleep" - A short press puts the device into sleep mode
- "Page Turn" - A short press in reading mode turns to the next page; a long press turns the device off
#### 3.6.4 System
- **Time to Sleep**: Set the duration of inactivity before the device automatically goes to sleep; options are 1, 5, 10 (default), 15 or 30 minutes.
- **WiFi Networks**: Connect to WiFi networks for file transfers and firmware updates.
- **KOReader Sync**: Options for setting up KOReader for syncing book progress.
- **OPDS Browser**: Configure OPDS server settings for browsing and downloading books. Set the server URL (for Calibre Content Server, add `/opds` to the end), and optionally configure username and password for servers requiring authentication. Note: Only HTTP Basic authentication is supported. If using Calibre Content Server with authentication enabled, you must set it to use Basic authentication instead of the default Digest authentication.
- **Check for updates**: Check for firmware updates over WiFi.
- **Clear Reading Cache**: Clear the internal SD card cache.
- **Check for updates**: Check for Crosspoint firmware updates over WiFi.
- **Language**: Set the system language (see **[Supported Languages](#supported-languages)** for more information).
### 3.6 Sleep Screen
#### 3.6.5 KOReader Sync Quick Setup
You can customize the sleep screen by placing custom images in specific locations on the SD card:
CrossPoint can sync reading progress with KOReader-compatible sync servers.
It also interoperates with KOReader apps/devices when they use the same server and credentials.
- **Single Image:** Place a file named `sleep.bmp` in the root directory.
- **Multiple Images:** Create a `sleep` directory in the root of the SD card and place any number of `.bmp` images inside. If images are found in this directory, they will take priority over the `sleep.bmp` file, and one will be randomly selected each time the device sleeps.
##### Option A: Free Public Server (`sync.koreader.rocks`)
Already have KOReader Sync credentials? Skip registration; basic sync only requires using the same existing username/password on all devices.
When this returns `HTTP 402` with `{"code":2002,"message":"Username is already registered."}`, pick a different username or use that existing account.
2. On each CrossPoint device:
- Go to **Settings -> System -> KOReader Sync**.
- Set **Username** and **Password** (enter the plain password; CrossPoint computes MD5 internally, and use the same values on all devices).
- Set **Sync Server URL** to `https://sync.koreader.rocks`, or leave it empty (both use the same default KOReader sync server).
- Run **Authenticate**.
3. While reading, press **Confirm** to open the reader menu, then select **Sync Progress**.
- Choose **Apply Remote** to jump to remote progress.
- Choose **Upload Local** to push current progress.
##### Option B: Self-Hosted Server (Docker Compose)
1. Start a sync server:
```bash
mkdir -p kosync-quickstart
cd kosync-quickstart
cat > compose.yaml <<'YAML'
services:
kosync:
image: koreader/kosync:latest
ports:
- "7200:7200"
- "17200:17200"
volumes:
- ./data/redis:/var/lib/redis
environment:
- ENABLE_USER_REGISTRATION=true
restart: unless-stopped
YAML
# Docker
docker compose up -d
# Podman (alternative)
podman compose up -d
```
> [!NOTE]
> You'll need to set the **Sleep Screen** setting to **Custom** in order to use these images.
> `ENABLE_USER_REGISTRATION=true` is convenient for first setup. After creating your users, set it to `false` (or remove it) to avoid unexpected registrations.
If this returns `HTTP 402` with `{"code":2002,"message":"Username is already registered."}`, the account already exists.
4. On each CrossPoint device:
- Go to **Settings -> System -> KOReader Sync**.
- Set **Username** and **Password** (enter the plain password; CrossPoint computes MD5 internally, and use the same values on all devices).
- Set **Sync Server URL** to `http://<server-ip>:17200`.
- Run **Authenticate**.
If you use the HTTPS listener, use `https://<server-ip>:7200` (`curl -k` only for self-signed certificate testing).
5. While reading, press **Confirm** to open the reader menu, then select **Sync Progress**.
- Choose **Apply Remote** to jump to remote progress.
- Choose **Upload Local** to push current progress.
### 3.7 Sleep Screen
The **Sleep Screen** setting controls what is displayed when the device goes to sleep:
| Mode | Behavior |
|------|----------|
| **Dark** (default) | The CrossPoint logo on a dark background. |
| **Light** | The CrossPoint logo on a white background. |
| **Custom** | A custom image from the SD card (see below). Falls back to **Dark** if no custom image is found. |
| **Cover** | The cover of the currently open book. Falls back to **Dark** if no book is open. |
| **Cover + Custom** | The cover of the currently open book. Falls back to **Custom** behavior if no book is open. |
| **None** | A blank screen. |
#### Cover settings
When using **Cover** or **Cover + Custom**, two additional settings apply:
- **Sleep Screen Cover Mode**: **Fit** (scale to fit, white borders) or **Crop** (scale and crop to fill the screen).
- **Sleep Screen Cover Filter**: **None** (grayscale), **Contrast** (black & white), or **Inverted** (inverted black & white).
#### Custom images
To use custom sleep images, set the sleep screen mode to **Custom** or **Cover + Custom**, then place images on the SD card:
- **Multiple Images (recommended):** Create a `.sleep` directory in the root of the SD card and place any number of `.bmp` images inside. One will be randomly selected each time the device sleeps. (A directory named `sleep` is also accepted as a fallback.)
- **Single Image:** Place a file named `sleep.bmp` in the root directory. This is used as a fallback if no valid images are found in the `.sleep`/`sleep` directory.
> [!TIP]
> For best results:
@@ -188,7 +350,7 @@ Once you have opened a book, the button layout changes to facilitate reading.
The role of the volume (side) buttons can be swapped in **[Settings](#35-settings)**.
The role of the volume (side) buttons can be swapped in the **[Controls Settings](#363-controls)**.
If the **Short Power Button Click** setting is set to "Page Turn", you can also turn to the next page by briefly pressing the Power button.
@@ -196,13 +358,13 @@ If the **Short Power Button Click** setting is set to "Page Turn", you can also
* **Next Chapter:** Press and **hold** the **Right** (or **Volume Down**) button briefly, then release.
* **Previous Chapter:** Press and **hold** the **Left** (or **Volume Up**) button briefly, then release.
This feature can be disabled in **[Settings](#35-settings)** to help avoid changing chapters by mistake.
This feature can be disabled in the **[Controls Settings](#363-controls)** to help avoid changing chapters by mistake.
### System Navigation
* **Return to Book Selection:** Press **Back** to close the book and return to the **[Book Selection](#32-book-selection)** screen.
* **Return to Home:** Press and **hold** the **Back** button to close the book and return to the **[Home](#31-home-screen)** screen.
* **Chapter Menu:** Press **Confirm** to open the **[Table of Contents/Chapter Selection](#5-chapter-selection-screen)**.
* **Return to Home:** Press the **Back** button to close the book and return to the **[Home](#31-home-screen)** screen.
* **Return to Browse Files:** Press and hold the **Back** button to close the book and return to the **[Browse Files](#33-browse-files-screen)** screen.
* **Chapter Menu:** Press **Confirm** to open the **[Table of Contents/Chapter Selection](#5-chapter-selection-screen)** screen.
This document explains the refactoring from the original per-activity render task model to the centralized `ActivityManager` introduced in [PR #1016](https://github.com/crosspoint-reader/crosspoint-reader/pull/1016). It covers the architectural differences, what changed for activity authors, and the FreeRTOS task and locking model that underpins the system.
## Overview of Changes
| Aspect | Old Model | New Model |
|--------|-----------|-----------|
| Render task | One per activity (8KB stack each) | Single shared task in `ActivityManager` |
| Render mutex | Per-activity `renderingMutex` | Single global mutex in `ActivityManager` |
| `RenderLock` | Inner class of `Activity` | Standalone class, acquires global mutex |
| Subactivities | `ActivityWithSubactivity` base class | Activity stack managed by `ActivityManager` |
| Navigation | Free functions in `main.cpp` | `activityManager.goHome()`, `goToReader()`, etc. |
- **8KB per render task**: Each activity allocated an 8KB FreeRTOS stack for its render task, even though only one renders at a time
- **Dangerous deletion patterns**: `exitActivity()` + `enterNewActivity()` in callbacks led to `delete this` situations where the caller was destroyed while its code was still on the stack
- **Subactivity coupling**: Parents stored callbacks to child results, creating tight coupling and lifetime hazards
### New Model: Centralized ActivityManager
A single `ActivityManager` owns the render task and manages an activity stack:
The render task lifecycle is handled entirely by `ActivityManager::begin()`.
### 6. Update `requestUpdate()` Calls
The signature changed to accept an `immediate` flag:
```cpp
// BEFORE
voidrequestUpdate();// always immediate notification to per-activity render task
// AFTER
voidrequestUpdate(boolimmediate=false);
// immediate=false (default): deferred until end of current loop iteration
// immediate=true: sends notification to render task right away
```
**When to use `immediate`**: Almost never. Deferred updates are batched — if `loop()` triggers multiple state changes that each call `requestUpdate()`, only one render happens. Use `immediate` only when you need the render to start before the current function returns (e.g., before a blocking network call).
**`requestUpdateAndWait()`**: Blocks the calling task until the render completes. Use sparingly — it's designed for cases where you need the screen to reflect new state before proceeding (e.g., showing "Checking for update..." before calling a network API).
### 7. Remove Stored Navigation Callbacks
Old activities often stored `std::function` callbacks for navigation:
// Use finish() to go back, activityManager.goHome() to go home
};
```
This removes `std::function` overhead (~2-4KB per unique signature) and eliminates lifetime risks from captured `this` pointers.
## Technical Details
### FreeRTOS Task Model
The firmware runs on an ESP32-C3, a single-core RISC-V microcontroller. FreeRTOS provides cooperative and preemptive multitasking on this single core — only one task executes at any moment, and the scheduler switches between tasks at yield points (blocking calls, `vTaskDelay`, `taskYIELD`) or when a tick interrupt promotes a higher-priority task.
There are two tasks relevant to the activity system:
Both tasks run at priority 1. Since the ESP32-C3 is single-core, they alternate execution: the main task runs `loop()`, then at the end of the loop iteration, notifies the render task if an update was requested. The render task wakes, acquires the mutex, calls `render()`, releases the mutex, and blocks again.
Do not use `xTaskCreate` inside activities. If you have a use case that seems to require a background task, open a discussion to propose a lifecycle-aware `Worker` abstraction first.
### The Render Mutex and RenderLock
A single FreeRTOS mutex (`renderingMutex`) protects shared state between `loop()` and `render()`. Since these run on different tasks, any state read by `render()` and written by `loop()` must be guarded.
`RenderLock` is an RAII wrapper:
```cpp
// Standalone class (not tied to any specific activity)
explicitRenderLock(Activity&);// same — Activity& param kept for compatibility
~RenderLock();// releases mutex if still held
voidunlock();// early release
};
```
**Usage patterns:**
```cpp
// In loop(): protect state mutations that render() reads
voidMyActivity::loop(){
if(somethingChanged){
RenderLocklock;
state=newState;// safe — render() can't run while lock is held
}
requestUpdate();// trigger render after lock is released
}
// In render(): lock is passed in, held for duration of render
voidMyActivity::render(RenderLock&&){
// Lock is held — safe to read shared state
renderer.clearScreen();
renderer.drawText(...,stateString,...);
renderer.displayBuffer();
// Lock released when RenderLock destructor runs
}
```
**Critical rule**: Never call `requestUpdateAndWait()` while holding a `RenderLock`. The render task needs the mutex to call `render()`, so holding it while waiting for the render to complete is a deadlock:
```text
Main Task Render Task
────────── ───────────
RenderLock lock; (blocked on mutex)
requestUpdateAndWait();
→ notify render task
→ block waiting for
render to complete → wakes up
→ tries to acquire mutex
→ DEADLOCK: main holds mutex,
waits for render; render
waits for mutex
```
### requestUpdate() vs requestUpdateAndWait()
```text
requestUpdate(false) requestUpdate(true)
───────────────── ─────────────────
Sets flag only. Notifies render task
Render happens after immediately.
loop() returns and Render may start
ActivityManager checks before the calling
the flag. function returns.
(Does NOT wait for
render to complete.)
requestUpdateAndWait()
──────────────────────
Notifies render task AND
blocks calling task until
render is done. Uses
FreeRTOS direct-to-task
notification on the
caller's task handle.
```
`requestUpdateAndWait()` flow in detail:
```text
Calling Task Render Task
──────────── ───────────
requestUpdateAndWait()
├─ assert: not render task
├─ assert: not holding RenderLock
├─ store waitingTaskHandle
├─ xTaskNotify(renderTask) → wakes render task
└─ ulTaskNotifyTake() ─┐
(blocked) │ RenderLock lock;
│ activity->render();
│ // render complete
│ taskENTER_CRITICAL
│ waiter = waitingTaskHandle
│ waitingTaskHandle = nullptr
│ taskEXIT_CRITICAL
│ xTaskNotify(waiter) ───┐
│ │
┌──────────────────────┘ │
│ (woken by notification) ◄────────────────────────┘
└── requestUpdate() // automatic re-render for parent
```
### Common Pitfalls
**Calling `finish()` and continuing to access `this`**: `finish()` sets `pendingAction = Pop` but does not immediately destroy the activity. The activity is destroyed on the next `ActivityManager::loop()` iteration. It's safe to access member variables after `finish()` within the same function, but don't rely on the activity surviving past the current `loop()` call.
**Modifying shared state without `RenderLock`**: If `render()` reads a variable and `loop()` writes it, the write must be under a `RenderLock`. Without it, `render()` could see a half-written value (e.g., a partially updated string or struct).
**Creating background tasks that outlive the activity**: Any FreeRTOS task created in `onEnter()` must be deleted in `onExit()` before the activity is destroyed. The `ActivityManager` does not track or clean up background tasks.
**Holding `RenderLock` across blocking calls**: The render task is blocked on the mutex while you hold the lock. Keep critical sections short — acquire, mutate state, release, then do blocking work.
```cpp
// WRONG — blocks render for the entire network call
voidMyActivity::doNetworkStuff(){
RenderLocklock;
state=LOADING;
autoresult=http.get(url);// blocks for seconds with lock held
state=DONE;
}
// CORRECT — release lock before blocking
voidMyActivity::doNetworkStuff(){
{
RenderLocklock;
state=LOADING;
}
requestUpdate(true);// render "Loading..." immediately, before we block
CrossPoint is firmware for the Xteink X4 (unaffiliated with Xteink), built with PlatformIO targeting the ESP32-C3 microcontroller.
At a high level, it is firmware that uses an activity-driven application architecture loop with persistent settings/state, SD-card-first caching, and a rendering pipeline optimized for e-ink constraints.
In each loop iteration, the firmware updates input, runs the active activity, handles auto-sleep/power behavior, and applies a short delay policy to balance responsiveness and power.
## Activity model
Activities are screen-level controllers deriving from `src/activities/Activity.h`.
Some flows use `src/activities/ActivityWithSubactivity.h` to host nested activities.
-`onEnter()` and `onExit()` manage setup/teardown
-`loop()` handles per-frame behavior
-`skipLoopDelay()` and `preventAutoSleep()` are used by long-running flows (for example web server mode)
Top-level activity groups:
-`src/activities/home/`: home and library navigation
-`_language_name` — Native display name shown to the user (e.g. "Français")
-`_language_code` — C++ enum name (e.g. "FRENCH"). Must be a valid C++ identifier.
-`_language_code` — C++ enum name (e.g. "FR"). Please use the [ISO Code](https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes) of the language. Must be a valid C++ identifier.
-`_order` — Controls the position in the Language enum (English is always 0)
**Rules:**
- Use UTF-8 encoding
- Every line must follow the format: `KEY: "value"`
- Keys must be valid C++ identifiers (uppercase, strats with STR_)
- Keys must be valid C++ identifiers (uppercase, starts with STR_)
- Keys must be unique within a file
- String values must be quoted
- Use `\n` for newlines, `\\` for literal backslashes, `\"` for literal quotes inside values
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.