## 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?** 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
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
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?** 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?**
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
* 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
_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.)
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
* 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