- Move Letterbox Fill setting to immediately after Sleep Screen Cover
Filter in the Display section where it is more relevant.
- Add Indexing Display setting to Display section with three modes:
Popup (default), Status Bar Text ("Indexing..."), and Status Bar Icon
(hourglass).
- Restore silent indexing logic in EpubReaderActivity::render() that
proactively indexes the next chapter when on a text-only page near
the end of the current chapter (non-popup mode only).
- Draw indexing indicator in renderStatusBar() when silentIndexingActive
is set and the user has chosen text or icon mode.
Made-with: Cursor
- Replace scattered book management actions (Archive, Delete, Reindex,
Delete Cache) with single "Manage Book" entry that opens
BookManageMenuActivity as a submenu.
- Replace scattered dictionary actions (Lookup Word, Lookup History,
Delete Dict Cache) with single "Dictionary" entry that opens new
DictionaryMenuActivity submenu.
- Add long-press Confirm (700ms) to open Table of Contents directly
from the reader, bypassing the menu.
- Add STR_DICTIONARY i18n key and regenerate I18nKeys.h/I18nStrings.h.
Made-with: Cursor
- Add clock rendering to BaseTheme::drawHeader() and LyraTheme::drawHeader()
after battery, before title. Respects clockFormat (OFF/AM-PM/24H) and
clockSize (Small/Medium/Large) settings.
- Fix PlaceholderCoverGenerator splitWords() to treat newlines, tabs, and
carriage returns as whitespace delimiters (not just spaces), preventing
one-character-per-line output from EPUB metadata with embedded newlines.
- Remove drawBorder() from placeholder covers since the UI already draws
its own frame around book cards.
Made-with: Cursor
- Update open-x4-sdk submodule to 9f76376 (BatteryMonitor ESP-IDF 5.x compat)
- Add RTC_NOINIT bounds check for logHead in Logging.cpp
- Add drawTextRotated90CCW to GfxRenderer for dictionary UI
- Add getWordXpos() accessor to TextBlock for dictionary word selection
- Fix bare include paths (ActivityResult.h, RenderLock.h) across 10 files
- Fix rvalue ref binding in setResult() lambdas (std::move pattern)
- Fix std::max type mismatch (uint8_t vs int) in EpubReaderActivity
- Fix FsFile forward declaration conflict in Dictionary.h
- Restore StringUtils::checkFileExtension() and sortFileList()
- Restore RecentBooksStore::removeBook()
Made-with: Cursor
Re-add KOReaderSyncActivity PUSH_ONLY mode (PR #1090):
- SyncMode enum with INTERACTIVE/PUSH_ONLY, deferFinish pattern
- Push & Sleep menu action in EpubReaderMenuActivity
- ActivityManager::requestSleep() for activity-initiated sleep
- main.cpp checks isSleepRequested() each loop iteration
Wire EndOfBookMenuActivity into EpubReaderActivity:
- pendingEndOfBookMenu deferred flag avoids render-lock deadlock
- Handles all 6 actions: ARCHIVE, DELETE, TABLE_OF_CONTENTS,
BACK_TO_BEGINNING, CLOSE_BOOK, CLOSE_MENU
Add book management to reader menu:
- ARCHIVE_BOOK, DELETE_BOOK, REINDEX_BOOK actions with handlers
Port silent next-chapter pre-indexing:
- silentIndexNextChapterIfNeeded() proactively indexes next chapter
when user is near end of current one, eliminating load screens
Add per-book letterbox fill toggle in reader menu:
- LETTERBOX_FILL cycles Default/Dithered/Solid/None
- Loads/saves per-book override via BookSettings
- bookCachePath constructor param added to EpubReaderMenuActivity
Made-with: Cursor
- Add drawPixelGray to GfxRenderer for letterbox fill rendering
- Add PRERENDER_THUMB_HEIGHTS to UITheme for placeholder cover generation
- Add [env:mod] build environment to platformio.ini
- Implement sleep screen letterbox fill (solid/dithered) with edge
caching in SleepActivity, including placeholder cover fallback
- Add Clock settings category to SettingsActivity with timezone,
NTP sync, and set-time actions; replace CalibreSettings with
OpdsServerListActivity; add DynamicEnum rendering support
- Add long-press book management to RecentBooksActivity
Made-with: Cursor
HomeActivity: Add mod features on top of upstream ActivityManager pattern:
- Multi-server OPDS support (OpdsServerStore instead of single URL)
- Long-press recent book for BookManageMenuActivity
- Long-press Browse Files to open archive folder
- Placeholder cover generation for books without covers
- startActivityForResult pattern for manage menu
EpubReaderMenuActivity: Replace upstream menu items with mod menu:
- Add/Remove Bookmark, Lookup Word, Go to Bookmark, Lookup History
- Table of Contents, Toggle Orientation, Toggle Font Size
- Close Book, Delete Dictionary Cache
- Pass isBookmarked and currentFontSize to constructor
- Show current orientation/font size value inline
EpubReaderActivity: Add mod action handlers:
- Bookmark add/remove via BookmarkStore
- Go to Bookmark via EpubReaderBookmarkSelectionActivity
- Dictionary word lookup via DictionaryWordSelectActivity
- Lookup history via LookedUpWordsActivity
- Delete dictionary cache
- Font size toggle with section re-layout
- Close Book action
ActivityResult: Add fontSize field to MenuResult
Made-with: Cursor
## 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>