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