Skip to content

Commit f3b2cf9

Browse files
authored
Merge pull request #40 from uxjulia/release/v1.2.8
Release/v1.2.8
2 parents 3c34cf5 + d1a3ad6 commit f3b2cf9

154 files changed

Lines changed: 103945 additions & 87553 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/CONTEXT.md

Lines changed: 20 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1,123 +1,29 @@
1-
# CrossPoint Reader — Consolidated Project Context
1+
# CrossPoint Reader — Durable Context
22

3-
Any simulator patches should be made to the `crosspoint-simulator` project itself. If it is not co-located with this project, inform the user of all changes that should be made to the simulator project and request that they open a pull request for them.
3+
Keep this file focused on repo-specific gotchas that are worth reusing in future sessions.
44

5-
## Known Simulator Limitations
5+
## Simulator
66

7-
- **No image rendering**: `lib_ignore = hal, PNGdec, JPEGDEC` in `platformio.ini`. `ImageDecoderFactory::getDecoder()` returns null → images silently skipped. Would need `stb_image` to fix.
8-
- **JPEGDEC stub**: Always returns failure (`open=0`, `getLastError=-1`). Log message `JPEGDEC fallback: open failed (err=-1)` in simulator is expected and harmless.
9-
- **Deep sleep is a no-op**: `esp_deep_sleep_start()` returns immediately in simulator. `lastActivityTime = millis()` is reset after `enterDeepSleep()` calls in `src/main.cpp` to prevent infinite re-trigger loop.
10-
- **HalStorage**: Uses POSIX `::open()` with `./fs_` prefix; allows multiple readers unlike real hardware (SdFat with mutex, one reader per file at a time).
7+
- Simulator patches belong in the adjacent `crosspoint-simulator` repo.
8+
- The valid local simulator env in this repo is `simulator`, and `pio run -e simulator` currently builds cleanly.
9+
- The simulator `PNGdec` stub in `crosspoint-simulator/src/PNGdec.h` needs to mirror the real API shape used by app code, including `hasAlpha()` and `getTransparentColor()`, even though decode still fails intentionally.
10+
- Known simulator limits:
11+
- No image rendering: `platformio.ini` ignores `hal`, `PNGdec`, and `JPEGDEC`, so image decoders are intentionally absent.
12+
- JPEGDEC stub always fails; `JPEGDEC fallback: open failed (err=-1)` is expected in simulator.
13+
- `esp_deep_sleep_start()` is a no-op in simulator.
14+
- `HalStorage` uses POSIX file access under `./fs_` and allows multiple readers, unlike real hardware.
1115

12-
---
16+
## Real Hardware / Storage
1317

14-
## SdFat: One Reader Per File
18+
- SdFat on hardware allows only one open reader per file path at a time. If a fallback needs to reopen the same file, close the first handle before reopening.
1519

16-
On real hardware, SdFat cannot open a second read handle while the first is still open on the same path. Pattern when a fallback needs to reopen a file:
20+
## Rendering / Reader Pipeline
1721

18-
```cpp
19-
jpegFile.close(); // Release handle before fallback reopens it
20-
return fallbackFunction(filePath, ...);
21-
```
22+
- `lib/Epub/Epub/Page.cpp`: images must render only in `GfxRenderer::BW`; grayscale passes are text anti-aliasing passes only.
23+
- Kindle EPUBs may contain paired high-res and old-Kindle fallback images. `ChapterHtmlSlimParser` should skip `<img>` nodes with `data-AmznRemoved-M8` to avoid duplicate stacked images.
24+
- After image/layout pipeline changes that affect cached EPUB output, clear the affected `.crosspoint/epub_<hash>/` cache if behavior looks stale.
2225

23-
See: `lib/JpegToBmpConverter/JpegToBmpConverter.cpp``jpegFileToBmpStreamInternal()`.
26+
## Misc Repo Gotchas
2427

25-
---
26-
27-
## Image Rendering Pipeline
28-
29-
### PageImage::render — Grayscale Skip
30-
31-
`lib/Epub/Epub/Page.cpp:28-31` — Images must be skipped in GRAYSCALE_LSB/MSB passes:
32-
33-
```cpp
34-
void PageImage::render(GfxRenderer& renderer, const int fontId, const int xOffset, const int yOffset) {
35-
// Images are only rendered in BW mode; grayscale passes are for text anti-aliasing only
36-
if (renderer.getRenderMode() != GfxRenderer::BW) return;
37-
imageBlock->render(renderer, xPos + xOffset, yPos + yOffset);
38-
}
39-
```
40-
41-
**Why**: Dithered image pixels with values 1 or 2 would otherwise be picked up by the grayscale LUT and given gray waveforms, causing subtle ghosting around image edges. The LUT's `0b00` entry (what all image pixels become when skipped) = "no waveform" = pixel unchanged.
42-
43-
Note: `Page.cpp` must `#include <GfxRenderer.h>` explicitly — `Block.h` only forward-declares `GfxRenderer`, which is insufficient for member access.
44-
45-
### Kindle Dual-Image Pattern
46-
47-
Some Kindle-format EPUBs include two `<img>` tags for the same image — one high-res, one low-res fallback for old Kindle M8 hardware:
48-
49-
```html
50-
<img src="high-res.jpeg" class="high-res" data-AmznRemoved="mobi7" />
51-
<img src="low-res.jpeg" class="low-res" width="120" height="120" data-AmznRemoved-M8="true" />
52-
```
53-
54-
Kindle/Calibre hides one via CSS `@media` query. `ChapterHtmlSlimParser` does not evaluate media queries → both render → two stacked images of different sizes.
55-
56-
**Fix** (`lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp`): During `<img>` attribute scan, skip any image with `data-AmznRemoved-M8` attribute:
57-
58-
```cpp
59-
} else if (strncmp(atts[i], "data-AmznRemoved-M8", 19) == 0) {
60-
amznM8Removed = true;
61-
}
62-
// After loop:
63-
if (amznM8Removed) {
64-
LOG_DBG("EHP", "Skipping Kindle M8 low-res fallback image");
65-
return;
66-
}
67-
```
68-
69-
**Cache note**: After this fix, delete `.crosspoint/epub_<hash>/` for affected books — stale `.pxc` files for the skipped image may linger.
70-
71-
---
72-
73-
## Display / Grayscale Pipeline
74-
75-
### GfxRenderer Pixel Convention
76-
77-
- `drawPixel(x, y, true)` → CLEARS bit → 0 = **black**
78-
- `drawPixel(x, y, false)` → SETS bit → 1 = **white**
79-
- `clearScreen(0xFF)` = all white (default page clear)
80-
- `clearScreen(0x00)` = all black (used before grayscale passes)
81-
82-
### lut_grayscale entries (SSD1677)
83-
84-
Indexed by 2-bit value (RED_bit=MSB, BW_bit=LSB):
85-
86-
- `0b00`: no waveform → pixel **unchanged**
87-
- `0b01`: light gray waveform
88-
- `0b10`: gray waveform
89-
- `0b11`: dark gray waveform
90-
91-
### imagePageWithAA Render Sequence
92-
93-
Triggered when `page->hasImages() && SETTINGS.textAntiAliasing`:
94-
95-
1. BW render (image drawn to framebuffer)
96-
2. `fillRect` blanks image area → `FAST_REFRESH` (shows text only, image area white)
97-
3. Re-render with image → `FAST_REFRESH` (shows text + image)
98-
4. `storeBwBuffer()``clearScreen(0x00)` → GRAYSCALE_LSB render → `copyGrayscaleLsbBuffers()`
99-
5. `clearScreen(0x00)` → GRAYSCALE_MSB render → `copyGrayscaleMsbBuffers()`
100-
6. `displayGrayBuffer()` (custom LUT, 61ms) → `restoreBwBuffer()` + `cleanupGrayscaleBuffers()`
101-
102-
### Single Buffer Mode Post-Refresh
103-
104-
After every `displayBuffer(FAST_REFRESH)`, RED RAM is synced with current frameBuffer. After `restoreBwBuffer()`, `cleanupGrayscaleBuffers(frameBuffer)` also syncs RED RAM with the restored BW content — required for correct differential fast refreshes on subsequent page turns.
105-
106-
---
107-
108-
## POSIX TZ Offset Convention
109-
110-
POSIX TZ sign is **inverted** from ISO 8601. `"UTC-1"` = 1 hour EAST (UTC+1).
111-
112-
Formula used in `TimeStore::applyTimezone()`:
113-
114-
```cpp
115-
const int posixOffset = 12 - static_cast<int>(SETTINGS.timezoneIndex);
116-
// timezoneIndex: 0=UTC-12, 12=UTC+0, 26=UTC+14
117-
```
118-
119-
---
120-
121-
## LyraTheme Overrides BaseTheme
122-
123-
`LyraTheme::drawHeader()` overrides `BaseTheme::drawHeader()` and does **not** call super. Any rendering added to `BaseTheme::drawHeader()` will not appear with the Lyra theme unless explicitly duplicated in `LyraTheme::drawHeader()`.
28+
- POSIX TZ signs are inverted from ISO 8601 in `TimeStore::applyTimezone()`: `"UTC-1"` means UTC+1.
29+
- `LyraTheme::drawHeader()` does not call `BaseTheme::drawHeader()`, so header changes in the base theme must be duplicated in Lyra if needed.

.github/workflows/release_candidate.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,21 @@ jobs:
2020

2121
- uses: actions/setup-python@v6
2222
with:
23-
python-version: '3.14'
23+
python-version: "3.14"
2424

2525
- name: Install uv
2626
uses: astral-sh/setup-uv@v7
2727
with:
2828
version: "latest"
29-
enable-cache: false
29+
enable-cache: true
30+
31+
- name: Cache PlatformIO packages
32+
uses: actions/cache@v4
33+
with:
34+
path: ~/.platformio
35+
key: ${{ runner.os }}-platformio-${{ hashFiles('platformio.ini', 'platformio.local.example.ini') }}
36+
restore-keys: |
37+
${{ runner.os }}-platformio-
3038
3139
- name: Install PlatformIO Core
3240
run: uv pip install --system -U https://github.com/pioarduino/platformio-core/archive/refs/tags/v6.1.19.zip

0 commit comments

Comments
 (0)