Commit Graph
60 Commits
Author SHA1 Message Date
jpirnay 97d4400b55 Merge branch 'refactor-i18n_offset-table' of https://github.com/jpirnay/crosspoint-reader into mybuild 2026-04-03 11:42:01 +02:00
jpirnay 03217d871d Switch to language_code instead of language _order key 2026-04-03 11:41:34 +02:00
jpirnay 6de5ebc69c Merge branch 'feat-weather' of https://github.com/jpirnay/crosspoint-reader into mybuild 2026-04-03 11:06:17 +02:00
jpirnay 01d05e654d Bugfixing and refactoring - proper menu icons 2026-04-03 11:03:10 +02:00
jpirnay adc4efa9e6 Switch to defusedxml 2026-04-03 10:17:42 +02:00
jpirnay 783828adb0 Merge branch 'feat-weather' of https://github.com/jpirnay/crosspoint-reader into mybuild 2026-04-02 20:19:43 +02:00
jpirnay b3a80c162a Review changes 2026-04-02 20:19:19 +02:00
jpirnay a4a0e6b5dc Merge branch 'feat-weather' of https://github.com/jpirnay/crosspoint-reader into mybuild 2026-04-02 18:37:55 +02:00
jpirnay d7bd88bec7 clang-format agnostic 2026-04-02 18:30:31 +02:00
jpirnay 811b4807cd Increase icon size a bit 2026-04-02 18:28:24 +02:00
jpirnay 706eb0e8e7 Improve look and feel 2026-04-02 18:16:49 +02:00
jpirnay cd69b4da77 Slight reformat 2026-03-31 15:09:15 +02:00
jpirnay cf252dc20d Merge branch 'refactor-i18n_offset-table' of https://github.com/jpirnay/crosspoint-reader into mybuild 2026-03-31 14:52:24 +02:00
jpirnay 59466b0625 A bit of reformatting 2026-03-31 14:45:31 +02:00
jpirnay c8b166606e Stage scripts 2026-03-30 18:12:52 +02:00
jpirnay f764d9c6a0 Merge remote-tracking branch 'pablohc/refactor/pr-1258-cover-overlay' into mybuild
# Conflicts:
#	lib/I18n/translations/english.yaml
#	scripts/gen_i18n.py
#	src/CrossPointSettings.h
#	src/SettingsList.h
#	src/activities/boot_sleep/SleepActivity.cpp
#	src/activities/boot_sleep/SleepActivity.h
2026-03-30 16:12:00 +02:00
pablohc 4e4fcd278a fix: gen_i18n.py wraps comment lines to avoid clang-format reflow 2026-03-29 22:48:15 +02:00
pablohc 4699ff0b88 fix: wrap long language comment in gen_i18n.py output 2026-03-29 22:44:43 +02:00
jpirnay a2aee4f4ae Merge branch 'fix-css-pre' of https://github.com/jpirnay/crosspoint-reader into mybuild 2026-03-28 18:16:11 +01:00
jpirnay 7f6933ab22 Recognize css pre elements 2026-03-28 16:47:55 +01:00
jpirnay 1d3405318c Integrate PT 1455 2026-03-26 11:16:53 +01:00
jpirnay 1af3f81336 PR 1507 2026-03-26 11:05:13 +01:00
jpirnay ac42d5ef36 Merge branch 'refactor-i18n_offset-table' of https://github.com/jpirnay/crosspoint-reader into mybuild 2026-03-26 11:02:18 +01:00
jpirnay 5da520eb9e Clean up language codes 2026-03-26 10:42:15 +01:00
jpirnay 3f5a2663f6 Exit if non-unique identifiers found 2026-03-26 10:30:14 +01:00
jpirnay e30fa6eb3a Merge branch 'perf-ws-upload-speed' of https://github.com/jpirnay/crosspoint-reader into mybuild 2026-03-25 13:07:32 +01:00
jpirnay e87f8035be Increase upload speed 2026-03-25 12:10:00 +01:00
jpirnay 84e3850d5d Merge branch 'feat-kosync-onlongpress' of https://github.com/jpirnay/crosspoint-reader into mybuild 2026-03-23 12:02:25 +01:00
pablohcandzgredex 5458155e79 feat: integrated epub optimizer (#1224)
## 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 (a01bb29) | 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>
2026-03-22 19:53:15 +00:00
jpirnay 5d8dc56f33 Minor changes 2026-03-19 17:58:45 +01:00
jpirnay f23d01093e Amend regex pattern for tr identification 2026-03-17 07:07:47 +01:00
jpirnay 2419cc43bf fix missing header 2026-03-16 23:52:13 +01:00
jpirnay 7c0095e5c5 Remove no longer needed include 2026-03-16 20:03:46 +01:00
jpirnay 2e8ee680a7 perf: strip unused strings during PlatformIO build 2026-03-16 15:47:35 +01:00
jpirnay 039337b4eb Show sizes 2026-03-16 15:41:25 +01:00
jpirnay 9b6035820d Show sizes, too 2026-03-16 15:31:59 +01:00
jpirnay 960cb91b23 Optimize output 2026-03-16 15:10:41 +01:00
fsocietyipaandOkzhetpes dabdfcd683 feat: Add Kazakh (kk) language support (#1377)
## 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>
2026-03-11 18:40:14 -05:00
595efc277f feat: add turkish translation (#1192)
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>
2026-03-05 18:24:44 -06:00
Zach Nelson 65473ecda7 chore: add firmware size history script (#1235)
## 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**_
2026-03-01 10:28:32 -06:00
martin brookandDave Allie 84bed9cb21 feat: replace picojpeg with JPEGDEC for JPEG image decoding (#1136)
## 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>
2026-03-01 12:24:58 +11:00
jpirnay e600befd35 feat: Add git branch to version information on settings screen (#1225)
## 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
>**_
2026-03-01 12:07:08 +11:00
679ff1aaf6 feat: Add Polish Language (#1155)
## Summary

* **What is the goal of this PR?** Implements Polish language
* **What changes are included?**

## Additional Context

* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
  specific areas to focus on).

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< YES | PARTIALLY | NO
>**_

---------

Co-authored-by: Zach Nelson <zach@zdnelson.com>
Co-authored-by: Arthur Tazhitdinov <lisnake@gmail.com>
2026-02-25 17:01:19 +00:00
ariel-lindemann 46eb66411d feat: sort languages in selection menu (#1071)
## Summary

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

Currently we are displaying the languages in the order they were added
(as in the `Language` enum). However, as new languages are coming in,
this will quickly be confusing to the users.

But we can't just change the ordering of the enum if we want to respect
bakwards compatibility.

So my proposal is to add a mapping of the alphabetical order of the
languages. I've made it so that it's generated by the `gen_i18n.py`
script, which will be used when a new language is added.


* **What changes are included?**

Added the array from the python script and changed
`LanguageSelectActivity` to use the indices from there. Also commited
the generated `I18nKeys.h`

## Additional Context

* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
  specific areas to focus on).

I was wondering if there is a better way to sort it. Currently, it's by
unicode value and Czech and Russian are last, which I don't know it it's
the most intuitive.

The current order is:
`Català, Deutsch, English, Español, Français, Português (Brasil),
Română, Svenska, Čeština, Русский`

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< PARTIALLY >**_
2026-02-25 19:44:17 +03:00
jpirnay 5d0ab16e41 feat: Add maxAlloc to memory information (#1152)
## Summary

* **What is the goal of this PR?** During debugging of #1092 i
desperately needed to monitor the biggest allocatable block of memory on
the heap
* **What changes are included?** Added informaqtion to debug output,
amended monitor utility to pick it up

## Additional Context

--- 

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**NO**_
2026-02-24 11:12:01 -06:00
Zach NelsonandCursor d391904f86 feat: Support for kerning and ligatures (#873)
## 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.


![3U1B1808](https://github.com/user-attachments/assets/1accb16f-2f1a-41e5-adca-89f1f1348494)

![3U1B1810](https://github.com/user-attachments/assets/2f6bd007-490e-420f-b774-3380b4add7ea)

![3U1B1815](https://github.com/user-attachments/assets/1986bb77-2db0-46e2-a5d6-8315dae9eb19)

## 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>
2026-02-24 11:31:43 +03:00
martin brook a2772ebf7b fix: image centering bleed (#1096)
## Summary

* Fixes #1026 
* Added a reproducer chapter to the epub generator for this case
* The fix is in endElement — when leaving a block/header element that
had an empty text block, reset the alignment to the user's default so it
doesn't bleed into the next sibling. This preserves accumulated margins
from parent elements while preventing stale alignment from carrying
across.

## Additional Context

### Before fix


![20260222_210029262](https://github.com/user-attachments/assets/263e4608-18cf-418b-871a-1c9a71822bdf)

![20260222_210040995](https://github.com/user-attachments/assets/9f0fdea1-5abf-4f1c-b35d-d35c8309456a)

![20260222_210052640](https://github.com/user-attachments/assets/b77dbadc-f347-400b-994a-17d0f5f073d8)

### After fix


![20260222_211037007](https://github.com/user-attachments/assets/294e15b3-ee40-4c21-8f5b-bd6b40d43d8d)

![20260222_211045139](https://github.com/user-attachments/assets/74107cf9-08a2-4737-be7f-ed0b5648ca6f)

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< PARTIALLY **_
2026-02-23 23:17:37 +03:00
Xuan-Son Nguyen 548f45182e feat: add script for listing objects in flash (#880)
## Summary

Adding a simple script to list objects and its size. I know `pio`
already had the "analyze" function but it never works in my case.

Ref discussion:
https://github.com/crosspoint-reader/crosspoint-reader/discussions/862

To use it:

```sh
scripts/script_profile_mem.sh
```

Example:

```
============================================
Top 10 largest symbols in section: .dram0.bss
Total section size: 85976 bytes (83.96 KB)
============================================
    0000bb98 (  46.90 KB)  display
    00000ed8 (   3.71 KB)  g_cnxMgr
    00000ad8 (   2.71 KB)  ftm_initiator
    00000830 (   2.05 KB)  xIsrStack
    000005b4 (   1.43 KB)  packet.8427
    000004e0 (   1.22 KB)  _ZN12_GLOBAL__N_17ctype_wE
    000004a0 (   1.16 KB)  dns_table
    0000049c (   1.15 KB)  s_wifi_nvs
    00000464 (   1.10 KB)  s_coredump_stack
    0000034c (   0.82 KB)  gWpaSm

============================================
Top 10 largest symbols in section: .dram0.data
Total section size: 13037 bytes (12.73 KB)
============================================
    000003c0 (   0.94 KB)  TxRxCxt
    00000328 (   0.79 KB)  phy_param
    000001d0 (   0.45 KB)  g_wifi_osi_funcs
    0000011b (   0.28 KB)  _ZN18CrossPointSettings8instanceE
    000000e6 (   0.22 KB)  country_info_24ghz
    000000dc (   0.21 KB)  g_eb_list_desc
    000000c0 (   0.19 KB)  s_fd_table
    000000b8 (   0.18 KB)  g_timer_info
    000000a8 (   0.16 KB)  rc11NSchedTbl
    000000a0 (   0.16 KB)  g_rmt_objects

============================================
Top 200 largest symbols in section: .flash.rodata
Total section size: 4564375 bytes (4457.40 KB)
============================================
    000325b7 ( 201.43 KB)  _ZL12de_trie_data
    0001cbd8 ( 114.96 KB)  _ZL29notosans_18_bolditalicBitmaps
    0001ca38 ( 114.55 KB)  _ZL29bookerly_18_bolditalicBitmaps
    0001bd3f ( 111.31 KB)  _ZL23bookerly_18_boldBitmaps
    0001af54 ( 107.83 KB)  _ZL23notosans_18_boldBitmaps
    0001abcc ( 106.95 KB)  _ZL25bookerly_18_italicBitmaps
    0001a341 ( 104.81 KB)  _ZL25notosans_18_italicBitmaps
    0001a0a5 ( 104.16 KB)  _ZL26bookerly_18_regularBitmaps
    0001890c (  98.26 KB)  _ZL26notosans_18_regularBitmaps
    000188cc (  98.20 KB)  _ZL33opendyslexic_14_bolditalicBitmaps
    000170ca (  92.20 KB)  _ZL29notosans_16_bolditalicBitmaps
    00015e7f (  87.62 KB)  _ZL29bookerly_16_bolditalicBitmaps
    00015acb (  86.70 KB)  _ZL23notosans_16_boldBitmaps
    00015140 (  84.31 KB)  _ZL23bookerly_16_boldBitmaps
    00014f0c (  83.76 KB)  _ZL25notosans_16_italicBitmaps
    00014d54 (  83.33 KB)  _ZL29opendyslexic_14_italicBitmaps
    00014766 (  81.85 KB)  _ZL27opendyslexic_14_boldBitmaps
    0001467f (  81.62 KB)  _ZL25bookerly_16_italicBitmaps
    00013c46 (  79.07 KB)  _ZL26bookerly_16_regularBitmaps
    00013934 (  78.30 KB)  _ZL26notosans_16_regularBitmaps
    00012572 (  73.36 KB)  _ZL33opendyslexic_12_bolditalicBitmaps
    00011cee (  71.23 KB)  _ZL29notosans_14_bolditalicBitmaps
    00011547 (  69.32 KB)  _ZL30opendyslexic_14_regularBitmaps
    0001153c (  69.31 KB)  _ZL29bookerly_14_bolditalicBitmaps
    00010c2e (  67.04 KB)  _ZL23notosans_14_boldBitmaps
    0001096e (  66.36 KB)  _ZL23bookerly_14_boldBitmaps
    000103d2 (  64.96 KB)  _ZL25notosans_14_italicBitmaps
    000100d5 (  64.21 KB)  _ZL25bookerly_14_italicBitmaps
    0000f83e (  62.06 KB)  _ZL26bookerly_14_regularBitmaps
    0000f7fc (  62.00 KB)  _ZL29opendyslexic_12_italicBitmaps
    0000f42b (  61.04 KB)  _ZL26notosans_14_regularBitmaps
    0000f229 (  60.54 KB)  _ZL27opendyslexic_12_boldBitmaps
    0000d301 (  52.75 KB)  _ZL29notosans_12_bolditalicBitmaps
    0000d22f (  52.55 KB)  _ZL30opendyslexic_12_regularBitmaps
    0000cff4 (  51.99 KB)  _ZL29bookerly_12_bolditalicBitmaps
    0000cd12 (  51.27 KB)  _ZL33opendyslexic_10_bolditalicBitmaps
    0000cad2 (  50.71 KB)  _ZL23bookerly_12_boldBitmaps
    0000c60a (  49.51 KB)  _ZL23notosans_12_boldBitmaps
    0000c147 (  48.32 KB)  _ZL25bookerly_12_italicBitmaps
    0000c120 (  48.28 KB)  _ZL25notosans_12_italicBitmaps
    0000ba7e (  46.62 KB)  _ZL26bookerly_12_regularBitmaps
    0000b454 (  45.08 KB)  _ZL26notosans_12_regularBitmaps
    0000b1e0 (  44.47 KB)  _ZL29opendyslexic_10_italicBitmaps
    0000a939 (  42.31 KB)  _ZL27opendyslexic_10_boldBitmaps
    0000a0dd (  40.22 KB)  _ZL13FilesPageHtml
    0000949d (  37.15 KB)  _ZL30opendyslexic_10_regularBitmaps
    00008415 (  33.02 KB)  _ZL32opendyslexic_8_bolditalicBitmaps
    00008240 (  32.56 KB)  _ZL15ru_ru_trie_data
    00007587 (  29.38 KB)  _ZL28opendyslexic_8_italicBitmaps
    00006f4d (  27.83 KB)  _ZL26opendyslexic_8_boldBitmaps
    00006943 (  26.32 KB)  _ZL15en_us_trie_data
    00006196 (  24.40 KB)  _ZL29opendyslexic_8_regularBitmaps
    00004990 (  18.39 KB)  _ZL21ubuntu_12_boldBitmaps
    000042ce (  16.70 KB)  _ZL24ubuntu_12_regularBitmaps
    000036d0 (  13.70 KB)  _ZL25notosans_18_regularGlyphs
    000036d0 (  13.70 KB)  _ZL25notosans_16_regularGlyphs
    000036d0 (  13.70 KB)  _ZL25notosans_14_regularGlyphs
    000036d0 (  13.70 KB)  _ZL25notosans_12_regularGlyphs
    000036d0 (  13.70 KB)  _ZL24notosans_8_regularGlyphs
    000036d0 (  13.70 KB)  _ZL22notosans_18_boldGlyphs
    000036d0 (  13.70 KB)  _ZL22notosans_16_boldGlyphs
    000036d0 (  13.70 KB)  _ZL22notosans_14_boldGlyphs
    000036d0 (  13.70 KB)  _ZL22notosans_12_boldGlyphs
    000036c0 (  13.69 KB)  _ZL28notosans_18_bolditalicGlyphs
    000036c0 (  13.69 KB)  _ZL28notosans_16_bolditalicGlyphs
    000036c0 (  13.69 KB)  _ZL28notosans_14_bolditalicGlyphs
    000036c0 (  13.69 KB)  _ZL28notosans_12_bolditalicGlyphs
    000036c0 (  13.69 KB)  _ZL24notosans_18_italicGlyphs
    000036c0 (  13.69 KB)  _ZL24notosans_16_italicGlyphs
    000036c0 (  13.69 KB)  _ZL24notosans_14_italicGlyphs
    000036c0 (  13.69 KB)  _ZL24notosans_12_italicGlyphs
    00003627 (  13.54 KB)  _ZL21ubuntu_10_boldBitmaps
    00003551 (  13.33 KB)  _ZL12es_trie_data
    000030c4 (  12.19 KB)  _ZL24ubuntu_10_regularBitmaps
    00002eb0 (  11.67 KB)  _ZL28bookerly_18_bolditalicGlyphs
    00002eb0 (  11.67 KB)  _ZL28bookerly_16_bolditalicGlyphs
    00002eb0 (  11.67 KB)  _ZL28bookerly_14_bolditalicGlyphs
    00002eb0 (  11.67 KB)  _ZL28bookerly_12_bolditalicGlyphs
    00002eb0 (  11.67 KB)  _ZL25bookerly_18_regularGlyphs
    00002eb0 (  11.67 KB)  _ZL25bookerly_16_regularGlyphs
    00002eb0 (  11.67 KB)  _ZL25bookerly_14_regularGlyphs
    00002eb0 (  11.67 KB)  _ZL25bookerly_12_regularGlyphs
    00002eb0 (  11.67 KB)  _ZL24bookerly_18_italicGlyphs
    00002eb0 (  11.67 KB)  _ZL24bookerly_16_italicGlyphs
    00002eb0 (  11.67 KB)  _ZL24bookerly_14_italicGlyphs
    00002eb0 (  11.67 KB)  _ZL24bookerly_12_italicGlyphs
    00002eb0 (  11.67 KB)  _ZL22bookerly_18_boldGlyphs
    00002eb0 (  11.67 KB)  _ZL22bookerly_16_boldGlyphs
    00002eb0 (  11.67 KB)  _ZL22bookerly_14_boldGlyphs
    00002eb0 (  11.67 KB)  _ZL22bookerly_12_boldGlyphs
    00002d50 (  11.33 KB)  _ZL32opendyslexic_14_bolditalicGlyphs
    00002d50 (  11.33 KB)  _ZL32opendyslexic_12_bolditalicGlyphs
    00002d50 (  11.33 KB)  _ZL32opendyslexic_10_bolditalicGlyphs
    00002d50 (  11.33 KB)  _ZL31opendyslexic_8_bolditalicGlyphs
    00002d50 (  11.33 KB)  _ZL29opendyslexic_14_regularGlyphs
    00002d50 (  11.33 KB)  _ZL29opendyslexic_12_regularGlyphs
    00002d50 (  11.33 KB)  _ZL29opendyslexic_10_regularGlyphs
    00002d50 (  11.33 KB)  _ZL28opendyslexic_8_regularGlyphs
    00002d50 (  11.33 KB)  _ZL28opendyslexic_14_italicGlyphs
    00002d50 (  11.33 KB)  _ZL28opendyslexic_12_italicGlyphs
    00002d50 (  11.33 KB)  _ZL28opendyslexic_10_italicGlyphs
    00002d50 (  11.33 KB)  _ZL27opendyslexic_8_italicGlyphs
    00002d50 (  11.33 KB)  _ZL26opendyslexic_14_boldGlyphs
    00002d50 (  11.33 KB)  _ZL26opendyslexic_12_boldGlyphs
    00002d50 (  11.33 KB)  _ZL26opendyslexic_10_boldGlyphs
    00002d50 (  11.33 KB)  _ZL25opendyslexic_8_boldGlyphs
    00002bca (  10.95 KB)  _ZL25notosans_8_regularBitmaps
    0000294c (  10.32 KB)  _ZL16SettingsPageHtml
    000024f0 (   9.23 KB)  _ZL23ubuntu_12_regularGlyphs
    000024f0 (   9.23 KB)  _ZL23ubuntu_10_regularGlyphs
    000024f0 (   9.23 KB)  _ZL20ubuntu_12_boldGlyphs
    000024f0 (   9.23 KB)  _ZL20ubuntu_10_boldGlyphs
    00001b4c (   6.82 KB)  _ZL12fr_trie_data
    000012e8 (   4.73 KB)  ciphersuite_definitions
    00000c8d (   3.14 KB)  _ZL12HomePageHtml
    00000708 (   1.76 KB)  _ZL7Logo120
    00000708 (   1.76 KB)  _ZL7Logo120
    00000688 (   1.63 KB)  esp_err_msg_table
    00000613 (   1.52 KB)  _ZL12it_trie_data
    00000500 (   1.25 KB)  namingBitmap
    00000450 (   1.08 KB)  _ZN4mime9mimeTableE
    00000404 (   1.00 KB)  _ZNSt8__detail12__prime_listE
    00000340 (   0.81 KB)  ciphersuite_preference
    00000300 (   0.75 KB)  _ZL31bookerly_18_bolditalicIntervals
    00000300 (   0.75 KB)  _ZL31bookerly_16_bolditalicIntervals
    00000300 (   0.75 KB)  _ZL31bookerly_14_bolditalicIntervals
    00000300 (   0.75 KB)  _ZL31bookerly_12_bolditalicIntervals
    00000300 (   0.75 KB)  _ZL28bookerly_18_regularIntervals
    00000300 (   0.75 KB)  _ZL28bookerly_16_regularIntervals
    00000300 (   0.75 KB)  _ZL28bookerly_14_regularIntervals
    00000300 (   0.75 KB)  _ZL28bookerly_12_regularIntervals
    00000300 (   0.75 KB)  _ZL27bookerly_18_italicIntervals
    00000300 (   0.75 KB)  _ZL27bookerly_16_italicIntervals
    00000300 (   0.75 KB)  _ZL27bookerly_14_italicIntervals
    00000300 (   0.75 KB)  _ZL27bookerly_12_italicIntervals
    00000300 (   0.75 KB)  _ZL25bookerly_18_boldIntervals
    00000300 (   0.75 KB)  _ZL25bookerly_16_boldIntervals
    00000300 (   0.75 KB)  _ZL25bookerly_14_boldIntervals
    00000300 (   0.75 KB)  _ZL25bookerly_12_boldIntervals
    000002a0 (   0.66 KB)  small_prime
    000002a0 (   0.66 KB)  _ZL35opendyslexic_14_bolditalicIntervals
    000002a0 (   0.66 KB)  _ZL35opendyslexic_12_bolditalicIntervals
    000002a0 (   0.66 KB)  _ZL35opendyslexic_10_bolditalicIntervals
    000002a0 (   0.66 KB)  _ZL34opendyslexic_8_bolditalicIntervals
    000002a0 (   0.66 KB)  _ZL32opendyslexic_14_regularIntervals
    000002a0 (   0.66 KB)  _ZL32opendyslexic_12_regularIntervals
    000002a0 (   0.66 KB)  _ZL32opendyslexic_10_regularIntervals
    000002a0 (   0.66 KB)  _ZL31opendyslexic_8_regularIntervals
    000002a0 (   0.66 KB)  _ZL31opendyslexic_14_italicIntervals
    000002a0 (   0.66 KB)  _ZL31opendyslexic_12_italicIntervals
    000002a0 (   0.66 KB)  _ZL31opendyslexic_10_italicIntervals
    000002a0 (   0.66 KB)  _ZL30opendyslexic_8_italicIntervals
    000002a0 (   0.66 KB)  _ZL29opendyslexic_14_boldIntervals
    000002a0 (   0.66 KB)  _ZL29opendyslexic_12_boldIntervals
    000002a0 (   0.66 KB)  _ZL29opendyslexic_10_boldIntervals
    000002a0 (   0.66 KB)  _ZL28opendyslexic_8_boldIntervals
    00000280 (   0.62 KB)  K
    000001c8 (   0.45 KB)  _ZL26ubuntu_12_regularIntervals
    000001c8 (   0.45 KB)  _ZL26ubuntu_10_regularIntervals
    000001c8 (   0.45 KB)  _ZL23ubuntu_12_boldIntervals
    000001c8 (   0.45 KB)  _ZL23ubuntu_10_boldIntervals
    0000016c (   0.36 KB)  utf8_encoding_ns
    0000016c (   0.36 KB)  utf8_encoding
    0000016c (   0.36 KB)  little2_encoding_ns
    0000016c (   0.36 KB)  little2_encoding
    0000016c (   0.36 KB)  latin1_encoding_ns
    0000016c (   0.36 KB)  latin1_encoding
    0000016c (   0.36 KB)  internal_utf8_encoding_ns
    0000016c (   0.36 KB)  internal_utf8_encoding
    0000016c (   0.36 KB)  big2_encoding_ns
    0000016c (   0.36 KB)  big2_encoding
    0000016c (   0.36 KB)  ascii_encoding_ns
    0000016c (   0.36 KB)  ascii_encoding
    0000016c (   0.36 KB)  __default_global_locale
    00000150 (   0.33 KB)  oid_sig_alg
    00000150 (   0.33 KB)  mbedtls_cipher_definitions
    00000140 (   0.31 KB)  adc_error_coef_atten
    00000140 (   0.31 KB)  NUM_ERROR_CORRECTION_CODEWORDS
    0000012c (   0.29 KB)  _ZL11lookupTable
    00000101 (   0.25 KB)  _ctype_
    00000100 (   0.25 KB)  unhex
    00000100 (   0.25 KB)  tokens
    00000100 (   0.25 KB)  nmstrtPages
    00000100 (   0.25 KB)  namePages
    00000100 (   0.25 KB)  __chclass
    00000100 (   0.25 KB)  FSb4
    00000100 (   0.25 KB)  FSb3
    00000100 (   0.25 KB)  FSb2
    00000100 (   0.25 KB)  FSb
    000000fc (   0.25 KB)  _C_time_locale
    000000f0 (   0.23 KB)  oid_ecp_grp
    000000d4 (   0.21 KB)  _ZL8mapTable
    000000c8 (   0.20 KB)  __mprec_tens
    000000c0 (   0.19 KB)  dh_group5_prime
    000000c0 (   0.19 KB)  dh_group5_order
    000000b4 (   0.18 KB)  _ZL31notosans_18_bolditalicIntervals
    000000b4 (   0.18 KB)  _ZL31notosans_16_bolditalicIntervals
    000000b4 (   0.18 KB)  _ZL31notosans_14_bolditalicIntervals
    000000b4 (   0.18 KB)  _ZL31notosans_12_bolditalicIntervals
    000000b4 (   0.18 KB)  _ZL28notosans_18_regularIntervals

============================================
Top 40 largest symbols in section: .flash.text
Total section size: 1431082 bytes (1397.54 KB)
============================================
    000025b8 (   9.43 KB)  http_parser_execute
    000023aa (   8.92 KB)  _vfprintf_r
    000022ce (   8.70 KB)  _svfprintf_r
    0000225a (   8.59 KB)  _svfwprintf_r
    00001fdc (   7.96 KB)  __ssvfscanf_r
    00001cb0 (   7.17 KB)  _Z15getSettingsListv
    00001bbc (   6.93 KB)  mbedtls_ssl_handshake_server_step
    00001ac2 (   6.69 KB)  __ssvfiscanf_r
    000018fe (   6.25 KB)  mbedtls_ssl_handshake_client_step
    000015fe (   5.50 KB)  mdns_parse_packet
    00001554 (   5.33 KB)  _vfiprintf_r
    0000146e (   5.11 KB)  _svfiprintf_r
    0000123a (   4.56 KB)  doProlog
    0000101e (   4.03 KB)  tcp_input
    0000100e (   4.01 KB)  unsignedCharToPrintable
    00000f4e (   3.83 KB)  pjpeg_decode_mcu
    00000ef6 (   3.74 KB)  nd6_input
    00000d4a (   3.32 KB)  _dtoa_r
    00000d44 (   3.32 KB)  little2_contentTok
    00000d44 (   3.32 KB)  big2_contentTok
    00000d36 (   3.30 KB)  _strtod_l
    00000d30 (   3.30 KB)  mbedtls_high_level_strerr
    00000cec (   3.23 KB)  ieee80211_sta_new_state
    00000ca6 (   3.16 KB)  tcp_receive
    00000c82 (   3.13 KB)  mbedtls_internal_sha512_process
    00000c14 (   3.02 KB)  _ZN9WebServer10_parseFormER10WiFiClient6Stringm
    00000bc0 (   2.94 KB)  qrcode_initBytes
    00000b62 (   2.85 KB)  __multf3
    00000b1c (   2.78 KB)  normal_contentTok
    00000a98 (   2.65 KB)  _ZN16ContentOpfParser12startElementEPvPKcPS2_
    00000a96 (   2.65 KB)  _ZN18JpegToBmpConverter27jpegFileToBmpStreamInternalER6FsFileR5Printiibb
    00000a82 (   2.63 KB)  _mdns_service_task
    00000a64 (   2.60 KB)  __strftime
    00000a64 (   2.60 KB)  _ZNK9BaseTheme19drawRecentBookCoverER11GfxRenderer4RectRKSt6vectorI10RecentBookSaIS4_EEiRbS9_S9_St8functionIFbvEE
    00000a60 (   2.59 KB)  __strftime
    000009cc (   2.45 KB)  _Z16start_ssl_clientP17sslclient_contextRK9IPAddressmPKciS5_bS5_S5_S5_S5_bPS5_
    000009c4 (   2.44 KB)  doContent
    0000099a (   2.40 KB)  wpa_sm_rx_eapol
    00000984 (   2.38 KB)  __divtf3
    00000974 (   2.36 KB)  _ZNSt6locale5_ImplC2Ej

============================================
Top 10 largest symbols in section: .iram0.text
Total section size: 57640 bytes (56.29 KB)
============================================
    00000668 (   1.60 KB)  rmt_driver_isr_default
    00000504 (   1.25 KB)  tlsf_realloc
    00000458 (   1.09 KB)  tlsf_free
    000003e8 (   0.98 KB)  tlsf_malloc
    000003d0 (   0.95 KB)  esp_sleep_start
    00000340 (   0.81 KB)  rtc_sleep_init
    00000218 (   0.52 KB)  spi_flash_mmap_pages
    000001fc (   0.50 KB)  esp_flash_erase_region
    000001fc (   0.50 KB)  call_start_cpu0
    000001de (   0.47 KB)  wdt_hal_init
```

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? **YES**
2026-02-19 22:36:43 +11:00
jpirnay d73afcd442 feat: Add 4bit bmp support (#944)
## Summary

* What is the goal of this PR?
- Allow users to create custom sleep screen images with standard tools
(ImageMagick, GIMP, etc.) that render cleanly on the e-ink display
without dithering artifacts. Previously, avoiding dithering required
non-standard 2-bit BMPs that no standard image editor can produce. ( see
issue #931 )

* What changes are included?
- Add 4-bit BMP format support to Bitmap.cpp (standard format, widely
supported by image tools)
- Auto-detect "native palette" images: if a BMP has ≤4 palette entries
and all luminances map within ±21 of the display's native gray levels
(0, 85, 170, 255), skip dithering entirely and direct-map pixels
- Clarify pixel processing strategy with three distinct paths:
error-diffusion dithering, simple quantization, or direct mapping
- Add scripts/generate_test_bmps.py for generating test images across
all supported BMP formats

## Additional Context

* The e-ink display has 4 native gray levels. When a BMP already uses
exactly those levels, dithering adds noise to what should be clean
output. The native palette detection uses a ±21 tolerance (~10%) to
handle slight rounding from color space conversions in image tools.
Users can now create a 4-color grayscale BMP with (imagemagic example):
```
convert input.png -colorspace Gray -colors 4 -depth 
```
---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _** YES**_
2026-02-19 22:33:29 +11:00
CaptainFritoandDave Allie 882f28942d feat: Lyra Icons (#725)
/!\ This PR depends on
https://github.com/crosspoint-reader/crosspoint-reader/pull/732 being
merged first

Also requires the
https://github.com/open-x4-epaper/community-sdk/pull/18 PR

## Summary

Lyra theme icons on the home menu, in the file browser and on empty book
covers

![IMG_8023
Medium](https://github.com/user-attachments/assets/ba7c1407-94d2-4353-80ff-d5b800c6ac5b)
![IMG_8024
Medium](https://github.com/user-attachments/assets/edb59e13-b1c9-4c86-bef3-c61cc8134e64)
![IMG_7958
Medium](https://github.com/user-attachments/assets/d3079ce1-95f0-43f4-bbc7-1f747cc70203)
![IMG_8033
Medium](https://github.com/user-attachments/assets/f3e2e03b-0fa8-47b7-8717-c0b71361b7a8)


## Additional Context

- Added a function to the open-x4-sdk renderer to draw transparent
images
- Added a scripts/convert_icon.py script to convert svg/png icons into a
C array that can be directly imported into the project. Usage:
```bash
python ./scripts/convert_icon.py 'path/to/icon.png' cover 32 32
```
This will create a components/icons/cover.h file with a C array called
CoverIcon, of size 32x32px. Lyra uses icons from
https://lucide.dev/icons with a stroke width of 2px, that can be
downloaded with any desired size on the site.

> The file browser is noticeably slower with the addition of icons, and
using an image buffer like on the home page doesn't help very much. Any
suggestions to optimize this are welcome.

---

### 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**_
The icon conversion python script was generated by Copilot as I am not a
python dev.

---------

Co-authored-by: Dave Allie <dave@daveallie.com>
2026-02-19 21:38:09 +11:00