Compare commits
350 Commits
0.5.0
...
crosspoint
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3853bfe113 | ||
|
|
fbe7d2feb4 | ||
|
|
520a0cb124 | ||
|
|
be8b02efd6 | ||
|
|
448ce55bb4 | ||
|
|
5464d9de3a | ||
|
|
48267ad848 | ||
|
|
dd630dcf72 | ||
|
|
ef705d3ac6 | ||
|
|
bab374a675 | ||
|
|
c171813045 | ||
|
|
d5e42b9e40 | ||
|
|
168c8fdb69 | ||
|
|
492cf976f5 | ||
|
|
25e255af50 | ||
|
|
a4adbb9dfe | ||
|
|
6ceba56620 | ||
|
|
62643ae933 | ||
|
|
8b41dccfb9 | ||
|
|
3204fa0339 | ||
|
|
bc6dc357eb | ||
|
|
ffe2aebd7e | ||
|
|
4965e63ad4 | ||
|
|
4db384edb6 | ||
|
|
f3075002c1 | ||
|
|
3e3be8bd23 | ||
|
|
800b07a2e5 | ||
|
|
2a31559747 | ||
|
|
c052512b1b | ||
|
|
bd95bfd44d | ||
|
|
fe446d4690 | ||
|
|
23e73312b4 | ||
|
|
e8d332e34f | ||
|
|
54004d5a5b | ||
|
|
d6e17c09ca | ||
|
|
7288e6499d | ||
|
|
5dab3ad5a3 | ||
|
|
82165c1022 | ||
|
|
e1fcec7d69 | ||
|
|
69a26ccb0e | ||
|
|
245d5a7dd8 | ||
|
|
e991fb10a6 | ||
|
|
4080184b27 | ||
|
|
8288cd2890 | ||
|
|
80c9e7a1d6 | ||
|
|
c2a966a6ea | ||
|
|
158caacfe0 | ||
|
|
1496ce68a6 | ||
|
|
0ab8e516f4 | ||
|
|
8687af738a | ||
|
|
a04388fd6c | ||
|
|
7349fbb208 | ||
|
|
9a723fead8 | ||
|
|
5fd1da5d2e | ||
|
|
0a2c661b8b | ||
|
|
8fb6402023 | ||
|
|
3b99459b82 | ||
|
|
1442521d0c | ||
|
|
7bbdf95aff | ||
|
|
76e9cf8f75 | ||
|
|
397abe1ef0 | ||
|
|
bc4edeef26 | ||
|
|
c90304f59b | ||
|
|
6ffd19a7e8 | ||
|
|
ff0392b9d2 | ||
|
|
03a18fb298 | ||
|
|
f01f3979bc | ||
|
|
3cee01b43d | ||
|
|
8920c62957 | ||
|
|
991b6b5a01 | ||
|
|
d8b8c5bad9 | ||
|
|
31199f9dd1 | ||
|
|
25b75b706f | ||
|
|
aa87b3f294 | ||
|
|
7a4af97ae8 | ||
|
|
703d95523b | ||
|
|
1a38fd96af | ||
|
|
5f4fa3bebe | ||
|
|
94a7c2c0b8 | ||
|
|
41eabba0d4 | ||
|
|
f739869519 | ||
|
|
99702a342c | ||
|
|
a707cc6da2 | ||
|
|
d8bee1d21f | ||
|
|
2f7312e6a0 | ||
|
|
d02e2e5b5e | ||
|
|
2c24ee3f81 | ||
|
|
cda8a5ec6d | ||
|
|
6e0cc4cf46 | ||
|
|
4d74cd795a | ||
|
|
08adc91bbe | ||
|
|
5996936c2e | ||
|
|
af58eb1987 | ||
|
|
e9e9ef68da | ||
|
|
91c8cc67ce | ||
|
|
fedc14bcb4 | ||
|
|
2b2bc95cf2 | ||
|
|
6bedc4ffec | ||
|
|
ecff988a29 | ||
|
|
c87a06edb8 | ||
|
|
7fce5b347d | ||
|
|
2f21f55512 | ||
|
|
1e20d30875 | ||
|
|
5c3828efe8 | ||
|
|
2952d7554c | ||
|
|
881f866d86 | ||
|
|
e3ae125f3c | ||
|
|
67494a7c90 | ||
|
|
59f493d293 | ||
|
|
ac1251282b | ||
|
|
16caa66b4a | ||
|
|
cf16d33710 | ||
|
|
51a4faddd4 | ||
|
|
a91bb0b1b8 | ||
|
|
481b8210fb | ||
|
|
c166b89f7b | ||
|
|
d5a9873bd7 | ||
|
|
6b533207e1 | ||
|
|
9493fb1f18 | ||
|
|
ff22a82563 | ||
|
|
3ce11f14ce | ||
|
|
47ef92e8fd | ||
|
|
1e506cce39 | ||
|
|
e3d6e32609 | ||
|
|
d399afb53d | ||
|
|
838993259d | ||
|
|
cc74039cab | ||
|
|
72fa6f8395 | ||
|
|
87d6c032a5 | ||
|
|
c9b5462370 | ||
|
|
e548bfc0e1 | ||
|
|
73c30748d8 | ||
|
|
fd6ea01f64 | ||
|
|
8f3d226bf3 | ||
|
|
5c9412b141 | ||
|
|
750a6ee1d8 | ||
|
|
be2de1123b | ||
|
|
20b6d4d055 | ||
|
|
57379a6590 | ||
|
|
6d68466891 | ||
|
|
8824c87490 | ||
|
|
5fef99c641 | ||
|
|
7a792a5384 | ||
|
|
f69cddf2cc | ||
|
|
7185e5d287 | ||
|
|
12940cc546 | ||
|
|
be10b90a71 | ||
|
|
94ce987f2c | ||
|
|
21277e03eb | ||
|
|
4eef2b5793 | ||
|
|
5a55fa1c6e | ||
|
|
c98ba142e8 | ||
|
|
c1c94c0112 | ||
|
|
eb84bcee7c | ||
|
|
d45f355e87 | ||
|
|
56ec3dfb6d | ||
|
|
e517945aaa | ||
|
|
489220832f | ||
|
|
3ee10b31ab | ||
|
|
a946c83a07 | ||
|
|
847786e342 | ||
|
|
c2fb8ce55d | ||
|
|
ed05554d74 | ||
|
|
9a9dc044ce | ||
|
|
1c027ce2cd | ||
|
|
49f97b69ca | ||
|
|
14643d0225 | ||
|
|
fecd1849b9 | ||
|
|
2040e088e7 | ||
|
|
65d23910a3 | ||
|
|
52995fa722 | ||
|
|
d4f8eda154 | ||
|
|
33b8fa0e19 | ||
|
|
16c760b2d2 | ||
|
|
8f3df7e10e | ||
|
|
0165fab581 | ||
|
|
66b100c6ca | ||
|
|
41bda43899 | ||
|
|
82f21f3c1d | ||
|
|
a9242fe61f | ||
|
|
88d0d90471 | ||
|
|
97c4871316 | ||
|
|
66811bf50b | ||
|
|
87287012ba | ||
|
|
d4ae108d9b | ||
|
|
7240cd52a9 | ||
|
|
0bae3bbf64 | ||
|
|
2b12a65011 | ||
|
|
46fa186b82 | ||
|
|
0cc2c64df2 | ||
|
|
1f956e972b | ||
|
|
9c573e6f7f | ||
|
|
0edb2baced | ||
|
|
b792b792bf | ||
|
|
afe9672156 | ||
|
|
9f95b31de5 | ||
|
|
c76507c937 | ||
|
|
881aa2e005 | ||
|
|
14972b34cb | ||
|
|
c8f4870d7c | ||
|
|
5fdf23f1d2 | ||
|
|
2fb417ee90 | ||
|
|
c8f6160fbc | ||
|
|
8e4484cd22 | ||
|
|
0332e1103a | ||
|
|
5790d6f5dc | ||
|
|
062d69dc2a | ||
|
|
5e9626eb2a | ||
|
|
00e83af4e8 | ||
|
|
39080c0e51 | ||
|
|
9e59a5106b | ||
|
|
a922e553ed | ||
|
|
04ad4e5aa4 | ||
|
|
6e9ba1006a | ||
|
|
40f9ed485c | ||
|
|
b82e044ac3 | ||
|
|
026733a4fe | ||
|
|
57b075ec97 | ||
|
|
648c688642 | ||
|
|
06065dfd8b | ||
|
|
93226c9fbb | ||
|
|
941643cf97 | ||
|
|
9bba41ed96 | ||
|
|
34cf5f0636 | ||
|
|
f2ca65d752 | ||
|
|
6a8971fc20 | ||
|
|
e2cba5be83 | ||
|
|
52a0b5bbe9 | ||
|
|
3abcd0d05d | ||
|
|
03f0ce04cc | ||
|
|
be1b5bad21 | ||
|
|
3dd52f30fa | ||
|
|
e43fec79be | ||
|
|
bf7bffd506 | ||
|
|
9f31f80c80 | ||
|
|
fb5fc32c5d | ||
|
|
d4bd119950 | ||
|
|
85d76da967 | ||
|
|
e4ac90f5c1 | ||
|
|
278b056bd0 | ||
|
|
b01eb50325 | ||
|
|
1bfe694807 | ||
|
|
7b32a87596 | ||
|
|
071ccb9d1b | ||
|
|
d7f4bd54f5 | ||
|
|
2437943c94 | ||
|
|
140d8749a6 | ||
|
|
534504cf7a | ||
|
|
b1763821b5 | ||
|
|
c0b83b626e | ||
|
|
f8c0b1acea | ||
|
|
f9b604f04e | ||
|
|
3dc5f6fec4 | ||
|
|
41c93e4eba | ||
|
|
1c33162368 | ||
|
|
27d42fbef3 | ||
|
|
dd280bdc97 | ||
|
|
bf031fd999 | ||
|
|
02350c6a9f | ||
|
|
9023b262a1 | ||
|
|
eabd149371 | ||
|
|
838246d147 | ||
|
|
f96b6ab29c | ||
|
|
e3d0201365 | ||
|
|
286b47f489 | ||
|
|
aff4dc6628 | ||
|
|
98a39374e8 | ||
|
|
e8c0fb42d4 | ||
|
|
b77af16caa | ||
|
|
e3c1e28b8f | ||
|
|
dc7544d944 | ||
|
|
504c7b307d | ||
|
|
b6bc1f7ed3 | ||
|
|
ea0abaf351 | ||
|
|
2771579007 | ||
|
|
27035b2b91 | ||
|
|
1107590b56 | ||
|
|
66ddb52103 | ||
|
|
9f4f71fabe | ||
|
|
d23020e268 | ||
|
|
f4491875ab | ||
|
|
6fe28da41b | ||
|
|
689b539c6b | ||
|
|
ce37c80c2d | ||
|
|
b39ce22e54 | ||
|
|
77c655fcf5 | ||
|
|
246afae6ef | ||
|
|
fcfa10bb1f | ||
|
|
febf79a98a | ||
|
|
424104f8ff | ||
|
|
955c78de64 | ||
|
|
958508eb6b | ||
|
|
6aa5d41a42 | ||
|
|
2a27c6d068 | ||
|
|
b73ae7fe74 | ||
|
|
f264efdb12 | ||
|
|
0d32d21d75 | ||
|
|
9b4dfbd180 | ||
|
|
926c786705 | ||
|
|
299623927e | ||
|
|
9a3bb81337 | ||
|
|
73d1839ddd | ||
|
|
cc86533e86 | ||
|
|
bf3f270067 | ||
|
|
cfe838e03b | ||
|
|
7484fe478c | ||
|
|
d41d539435 | ||
|
|
cf6fec78dc | ||
|
|
10d76dde12 | ||
|
|
7b5a63d220 | ||
|
|
c1d5f5d562 | ||
|
|
adfeee063f | ||
|
|
2d3928ed81 | ||
|
|
48249fbd1e | ||
|
|
1a53dccebd | ||
|
|
3e28724b62 | ||
|
|
d86b3fe134 | ||
|
|
1a3d6b125d | ||
|
|
b2020f5512 | ||
|
|
70dc0f018e | ||
|
|
424594488f | ||
|
|
57fdb1c0fb | ||
|
|
5e1694748c | ||
|
|
063a1df851 | ||
|
|
d429966dd4 | ||
|
|
c78f2a9840 | ||
|
|
11f01d3a41 | ||
|
|
973d372521 | ||
|
|
67da8139b3 | ||
|
|
c287aa03a4 | ||
|
|
5d68c8b305 | ||
|
|
def7abbd60 | ||
|
|
9ad8111ce7 | ||
|
|
57d1939be7 | ||
|
|
012992f904 | ||
|
|
c262f222de | ||
|
|
449b3ca161 | ||
|
|
6989035ef8 | ||
|
|
108cf57202 | ||
|
|
a640fbecf8 | ||
|
|
7a5719b46d | ||
|
|
8c3576e397 | ||
|
|
fdb5634ea6 | ||
|
|
5cabba7712 | ||
|
|
a86d405fb0 | ||
|
|
e4b5dc0e6a | ||
|
|
dfc74f94c2 | ||
|
|
3518cbb56d | ||
|
|
8994953254 | ||
|
|
ead39fd04b |
41
.gitea/workflows/ci.yml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
name: CI
|
||||||
|
'on':
|
||||||
|
push:
|
||||||
|
branches: [master, crosspoint-ef]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
run: |
|
||||||
|
# Use system Python on self-hosted runner
|
||||||
|
python3 --version
|
||||||
|
python3 -m pip install --upgrade pip
|
||||||
|
|
||||||
|
- name: Install PlatformIO Core
|
||||||
|
run: python3 -m pip install --upgrade platformio
|
||||||
|
|
||||||
|
- name: Run cppcheck
|
||||||
|
run: pio check --fail-on-defect low --fail-on-defect medium --fail-on-defect high
|
||||||
|
|
||||||
|
- name: Run clang-format
|
||||||
|
run: |
|
||||||
|
# Use system clang-format if available, skip if not
|
||||||
|
if command -v clang-format &> /dev/null; then
|
||||||
|
./bin/clang-format-fix && git diff --exit-code || (echo "Please run 'bin/clang-format-fix' to fix formatting issues" && exit 1)
|
||||||
|
else
|
||||||
|
echo "clang-format not found, skipping format check"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Generate Dictionary Index
|
||||||
|
run: |
|
||||||
|
python3 scripts/generate_dict_index.py --zip dict-en-en.zip --output lib/StarDict/DictPrefixIndex.generated.h
|
||||||
|
|
||||||
|
- name: Build CrossPoint
|
||||||
|
run: pio run
|
||||||
40
.gitea/workflows/pr-formatting-check.yml
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
name: "PR Formatting"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- reopened
|
||||||
|
- edited
|
||||||
|
- synchronize
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
title-check:
|
||||||
|
name: Title Check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check PR Title Format
|
||||||
|
run: |
|
||||||
|
PR_TITLE="${{ github.event.pull_request.title }}"
|
||||||
|
echo "Checking PR title: $PR_TITLE"
|
||||||
|
|
||||||
|
# Conventional commit pattern: type(scope): description or type: description
|
||||||
|
# Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
|
||||||
|
PATTERN="^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-zA-Z0-9_-]+\))?: .+"
|
||||||
|
|
||||||
|
if echo "$PR_TITLE" | grep -qE "$PATTERN"; then
|
||||||
|
echo "✓ PR title follows conventional commit format"
|
||||||
|
else
|
||||||
|
echo "✗ PR title does not follow conventional commit format"
|
||||||
|
echo ""
|
||||||
|
echo "Expected format: type(scope): description"
|
||||||
|
echo " or: type: description"
|
||||||
|
echo ""
|
||||||
|
echo "Valid types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert"
|
||||||
|
echo ""
|
||||||
|
echo "Examples:"
|
||||||
|
echo " feat(reader): add bookmark sync feature"
|
||||||
|
echo " fix: resolve memory leak in epub parser"
|
||||||
|
echo " docs: update README with new instructions"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
40
.gitea/workflows/release.yml
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
name: Compile Release
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
run: |
|
||||||
|
# Use system Python on self-hosted runner
|
||||||
|
python3 --version
|
||||||
|
python3 -m pip install --upgrade pip
|
||||||
|
|
||||||
|
- name: Install PlatformIO Core
|
||||||
|
run: python3 -m pip install --upgrade platformio
|
||||||
|
|
||||||
|
- name: Generate Dictionary Index
|
||||||
|
run: |
|
||||||
|
python3 scripts/generate_dict_index.py --zip dict-en-en.zip --output lib/StarDict/DictPrefixIndex.generated.h
|
||||||
|
|
||||||
|
- name: Build CrossPoint
|
||||||
|
run: pio run -e gh_release
|
||||||
|
|
||||||
|
- name: Upload Artifacts
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: CrossPoint-${{ github.ref_name }}
|
||||||
|
path: |
|
||||||
|
.pio/build/gh_release/bootloader.bin
|
||||||
|
.pio/build/gh_release/firmware.bin
|
||||||
|
.pio/build/gh_release/firmware.elf
|
||||||
|
.pio/build/gh_release/firmware.map
|
||||||
|
.pio/build/gh_release/partitions.bin
|
||||||
2
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
github: [daveallie]
|
||||||
|
ko_fi: daveallie
|
||||||
54
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
name: Bug Report
|
||||||
|
description: Report an issue or unexpected behavior
|
||||||
|
title: "Short, descriptive title of the issue"
|
||||||
|
labels: ["bug", "triage"]
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to report this bug! Please fill out the details below.
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: version
|
||||||
|
attributes:
|
||||||
|
label: Affected Version
|
||||||
|
description: What version of the project/library are you using? (e.g., v1.2.3, master branch commit SHA)
|
||||||
|
placeholder: Ex. v1.2.3
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: bug-description
|
||||||
|
attributes:
|
||||||
|
label: Describe the Bug
|
||||||
|
description: A clear and concise description of what the bug is.
|
||||||
|
placeholder:
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: steps-to-reproduce
|
||||||
|
attributes:
|
||||||
|
label: Steps to Reproduce
|
||||||
|
description: Clearly list the steps necessary to reproduce the unexpected behavior.
|
||||||
|
placeholder: |
|
||||||
|
1. Go to '...'
|
||||||
|
2. Select '...'
|
||||||
|
3. Crash
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: expected-behavior
|
||||||
|
attributes:
|
||||||
|
label: Expected Behavior
|
||||||
|
description: A clear and concise description of what you expected to happen.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Relevant Log Output/Screenshots
|
||||||
|
description: If applicable, error messages, or log output to help explain your problem. You can drag and drop images here.
|
||||||
|
render: shell
|
||||||
18
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
## Summary
|
||||||
|
|
||||||
|
* **What is the goal of this PR?** (e.g., Implements the new feature for file uploading.)
|
||||||
|
* **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 >**_
|
||||||
37
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
name: CI
|
||||||
|
'on':
|
||||||
|
push:
|
||||||
|
branches: [master]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: '3.14'
|
||||||
|
|
||||||
|
- name: Install PlatformIO Core
|
||||||
|
run: pip install --upgrade platformio
|
||||||
|
|
||||||
|
- name: Install clang-format-21
|
||||||
|
run: |
|
||||||
|
wget https://apt.llvm.org/llvm.sh
|
||||||
|
chmod +x llvm.sh
|
||||||
|
sudo ./llvm.sh 21
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y clang-format-21
|
||||||
|
|
||||||
|
- name: Run cppcheck
|
||||||
|
run: pio check --fail-on-defect low --fail-on-defect medium --fail-on-defect high
|
||||||
|
|
||||||
|
- name: Run clang-format
|
||||||
|
run: PATH="/usr/lib/llvm-21/bin:$PATH" ./bin/clang-format-fix && git diff --exit-code || (echo "Please run 'bin/clang-format-fix' to fix formatting issues" && exit 1)
|
||||||
|
|
||||||
|
- name: Build CrossPoint
|
||||||
|
run: pio run
|
||||||
26
.github/workflows/pr-formatting-check.yml
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
name: "PR Formatting"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- reopened
|
||||||
|
- edited
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
statuses: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
title-check:
|
||||||
|
name: Title Check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: Check PR Title
|
||||||
|
uses: amannn/action-semantic-pull-request@v6
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
41
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
name: Compile Release
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- uses: actions/cache@v5
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cache/pip
|
||||||
|
~/.platformio/.cache
|
||||||
|
key: ${{ runner.os }}-pio
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: '3.14'
|
||||||
|
|
||||||
|
- name: Install PlatformIO Core
|
||||||
|
run: pip install --upgrade platformio
|
||||||
|
|
||||||
|
- name: Build CrossPoint
|
||||||
|
run: pio run -e gh_release
|
||||||
|
|
||||||
|
- name: Upload Artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: CrossPoint-${{ github.ref_name }}
|
||||||
|
path: |
|
||||||
|
.pio/build/gh_release/bootloader.bin
|
||||||
|
.pio/build/gh_release/firmware.bin
|
||||||
|
.pio/build/gh_release/firmware.elf
|
||||||
|
.pio/build/gh_release/firmware.map
|
||||||
|
.pio/build/gh_release/partitions.bin
|
||||||
18
.gitignore
vendored
@ -1,3 +1,21 @@
|
|||||||
.pio
|
.pio
|
||||||
.idea
|
.idea
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.vscode
|
||||||
|
.cursor/
|
||||||
|
chat-summaries/
|
||||||
|
lib/EpdFont/fontsrc
|
||||||
|
*.generated.h
|
||||||
|
.vs
|
||||||
|
build
|
||||||
|
**/__pycache__/
|
||||||
|
test/epubs/
|
||||||
|
CrossPoint-ef.md
|
||||||
|
Serial_print.code-search
|
||||||
|
|
||||||
|
# Gitea Release note drafts
|
||||||
|
release-notes-*.md
|
||||||
|
|
||||||
|
# Gitea Actions runner config (contains credentials)
|
||||||
|
.runner
|
||||||
|
.runner.*
|
||||||
|
|||||||
4
.gitmodules
vendored
@ -1,3 +1,5 @@
|
|||||||
[submodule "open-x4-sdk"]
|
[submodule "open-x4-sdk"]
|
||||||
path = open-x4-sdk
|
path = open-x4-sdk
|
||||||
url = https://github.com/open-x4-epaper/community-sdk.git
|
url = https://code.cottongin.xyz/cottongin/community-sdk.git
|
||||||
|
branch = crosspoint-ef
|
||||||
|
ignore = dirty
|
||||||
|
|||||||
70
README.md
@ -1,4 +1,15 @@
|
|||||||
# CrossPoint Reader
|
# CrossPoint Reader (ef fork)
|
||||||
|
|
||||||
|
> **Note:** This is **crosspoint-ef**, a heavily customized fork of [CrossPoint Reader](https://github.com/crosspoint-reader/crosspoint-reader) with additional features, UI improvements, and bug fixes. It also uses a [forked community-sdk](https://code.cottongin.xyz/cottongin/community-sdk) with additional hardware support.
|
||||||
|
>
|
||||||
|
> **Documentation:**
|
||||||
|
> - [Feature Overview](./docs/crosspoint-ef-features.md) - What's new in this fork
|
||||||
|
> - [User Guide](./docs/crosspoint-ef-user-guide.md) - How to use the new features
|
||||||
|
> - [Technical Comparison](./docs/branch-comparison-summary.md) - Detailed diff from upstream
|
||||||
|
>
|
||||||
|
> **Disclaimer:** Much of the code in this fork was developed with assistance from [Claude](https://claude.ai), an AI assistant by Anthropic.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
Firmware for the **Xteink X4** e-paper display reader (unaffiliated with Xteink).
|
Firmware for the **Xteink X4** e-paper display reader (unaffiliated with Xteink).
|
||||||
Built using **PlatformIO** and targeting the **ESP32-C3** microcontroller.
|
Built using **PlatformIO** and targeting the **ESP32-C3** microcontroller.
|
||||||
@ -6,7 +17,7 @@ Built using **PlatformIO** and targeting the **ESP32-C3** microcontroller.
|
|||||||
CrossPoint Reader is a purpose-built firmware designed to be a drop-in, fully open-source replacement for the official
|
CrossPoint Reader is a purpose-built firmware designed to be a drop-in, fully open-source replacement for the official
|
||||||
Xteink firmware. It aims to match or improve upon the standard EPUB reading experience.
|
Xteink firmware. It aims to match or improve upon the standard EPUB reading experience.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Motivation
|
## Motivation
|
||||||
|
|
||||||
@ -23,18 +34,27 @@ CrossPoint Reader aims to:
|
|||||||
|
|
||||||
This project is **not affiliated with Xteink**; it's built as a community project.
|
This project is **not affiliated with Xteink**; it's built as a community project.
|
||||||
|
|
||||||
## Features
|
## Features & Usage
|
||||||
|
|
||||||
- [x] EPUB parsing and rendering
|
- [x] EPUB parsing and rendering (EPUB 2 and EPUB 3)
|
||||||
|
- [ ] Image support within EPUB
|
||||||
- [x] Saved reading position
|
- [x] Saved reading position
|
||||||
- [ ] File explorer with file picker
|
- [x] File explorer with file picker
|
||||||
- [x] Basic EPUB picker from root directory
|
- [x] Basic EPUB picker from root directory
|
||||||
- [x] Support nested folders
|
- [x] Support nested folders
|
||||||
- [ ] EPUB picker with cover art
|
- [ ] EPUB picker with cover art
|
||||||
- [ ] Image support within EPUB
|
- [x] Custom sleep screen
|
||||||
- [ ] Configurable font, layout, and display options
|
- [x] Cover sleep screen
|
||||||
- [ ] WiFi connectivity
|
- [x] Wifi book upload
|
||||||
- [ ] BLE connectivity
|
- [x] Wifi OTA updates
|
||||||
|
- [x] Configurable font, layout, and display options
|
||||||
|
- [ ] User provided fonts
|
||||||
|
- [ ] Full UTF support
|
||||||
|
- [x] Screen rotation
|
||||||
|
|
||||||
|
Multi-language support: Read EPUBs in various languages, including English, Spanish, French, German, Italian, Portuguese, Russian, Ukrainian, Polish, Swedish, Norwegian, [and more](./USER_GUIDE.md#supported-languages).
|
||||||
|
|
||||||
|
See [the user guide](./USER_GUIDE.md) for instructions on operating CrossPoint.
|
||||||
|
|
||||||
## Installing
|
## Installing
|
||||||
|
|
||||||
@ -93,9 +113,9 @@ CrossPoint Reader is pretty aggressive about caching data down to the SD card to
|
|||||||
has ~380KB of usable RAM, so we have to be careful. A lot of the decisions made in the design of the firmware were based
|
has ~380KB of usable RAM, so we have to be careful. A lot of the decisions made in the design of the firmware were based
|
||||||
on this constraint.
|
on this constraint.
|
||||||
|
|
||||||
### EPUB caching
|
### Data caching
|
||||||
|
|
||||||
The first time chapters of an EPUB are loaded, they are cached to the SD card. Subsequent loads are served from the
|
The first time chapters of a book are loaded, they are cached to the SD card. Subsequent loads are served from the
|
||||||
cache. This cache directory exists at `.crosspoint` on the SD card. The structure is as follows:
|
cache. This cache directory exists at `.crosspoint` on the SD card. The structure is as follows:
|
||||||
|
|
||||||
|
|
||||||
@ -103,30 +123,30 @@ cache. This cache directory exists at `.crosspoint` on the SD card. The structur
|
|||||||
.crosspoint/
|
.crosspoint/
|
||||||
├── epub_12471232/ # Each EPUB is cached to a subdirectory named `epub_<hash>`
|
├── epub_12471232/ # Each EPUB is cached to a subdirectory named `epub_<hash>`
|
||||||
│ ├── progress.bin # Stores reading progress (chapter, page, etc.)
|
│ ├── progress.bin # Stores reading progress (chapter, page, etc.)
|
||||||
│ ├── 0/ # Each chapter is stored in a subdirectory named by its index (based on the spine order)
|
│ ├── cover.bmp # Book cover image (once generated)
|
||||||
│ │ ├── section.bin # Section metadata (page count)
|
│ ├── book.bin # Book metadata (title, author, spine, table of contents, etc.)
|
||||||
│ │ ├── page_0.bin # Each page is stored in a separate file, it
|
│ └── sections/ # All chapter data is stored in the sections subdirectory
|
||||||
│ │ ├── page_1.bin # contains the position (x, y) and text for each word
|
│ ├── 0.bin # Chapter data (screen count, all text layout info, etc.)
|
||||||
│ │ └── ...
|
│ ├── 1.bin # files are named by their index in the spine
|
||||||
│ ├── 1/
|
│ └── ...
|
||||||
│ │ ├── section.bin
|
|
||||||
│ │ ├── page_0.bin
|
|
||||||
│ │ ├── page_1.bin
|
|
||||||
│ │ └── ...
|
|
||||||
│ └── ...
|
|
||||||
│
|
│
|
||||||
└── epub_189013891/
|
└── epub_189013891/
|
||||||
```
|
```
|
||||||
|
|
||||||
Deleting the `.crosspoint` directory will clear the cache.
|
Deleting the `.crosspoint` directory will clear the entire cache.
|
||||||
|
|
||||||
Due the way it's currently implemented, the cache is not automatically cleared when the EPUB is deleted and moving an
|
Due the way it's currently implemented, the cache is not automatically cleared when a book is deleted and moving a book
|
||||||
EPUB file will reset the reading progress.
|
file will use a new cache directory, resetting the reading progress.
|
||||||
|
|
||||||
|
For more details on the internal file structures, see the [file formats document](./docs/file-formats.md).
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Contributions are very welcome!
|
Contributions are very welcome!
|
||||||
|
|
||||||
|
If you're looking for a way to help out, take a look at the [ideas discussion board](https://github.com/daveallie/crosspoint-reader/discussions/categories/ideas).
|
||||||
|
If there's something there you'd like to work on, leave a comment so that we can avoid duplicated effort.
|
||||||
|
|
||||||
### To submit a contribution:
|
### To submit a contribution:
|
||||||
|
|
||||||
1. Fork the repo
|
1. Fork the repo
|
||||||
|
|||||||
222
USER_GUIDE.md
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
# CrossPoint User Guide
|
||||||
|
|
||||||
|
Welcome to the **CrossPoint** firmware. This guide outlines the hardware controls, navigation, and reading features of the device.
|
||||||
|
|
||||||
|
- [CrossPoint User Guide](#crosspoint-user-guide)
|
||||||
|
- [1. Hardware Overview](#1-hardware-overview)
|
||||||
|
- [Button Layout](#button-layout)
|
||||||
|
- [2. Power \& Startup](#2-power--startup)
|
||||||
|
- [Power On / Off](#power-on--off)
|
||||||
|
- [First Launch](#first-launch)
|
||||||
|
- [3. Screens](#3-screens)
|
||||||
|
- [3.1 Home Screen](#31-home-screen)
|
||||||
|
- [3.2 Book Selection](#32-book-selection)
|
||||||
|
- [3.3 Reading Mode](#33-reading-mode)
|
||||||
|
- [3.4 File Upload Screen](#34-file-upload-screen)
|
||||||
|
- [3.5 Settings](#35-settings)
|
||||||
|
- [3.6 Sleep Screen](#36-sleep-screen)
|
||||||
|
- [4. Reading Mode](#4-reading-mode)
|
||||||
|
- [Page Turning](#page-turning)
|
||||||
|
- [Chapter Navigation](#chapter-navigation)
|
||||||
|
- [System Navigation](#system-navigation)
|
||||||
|
- [5. Chapter Selection Screen](#5-chapter-selection-screen)
|
||||||
|
- [6. Current Limitations \& Roadmap](#6-current-limitations--roadmap)
|
||||||
|
|
||||||
|
|
||||||
|
## 1. Hardware Overview
|
||||||
|
|
||||||
|
The device utilises the standard buttons on the Xtink X4 (in the same layout as the manufacturer firmware, by default):
|
||||||
|
|
||||||
|
### Button Layout
|
||||||
|
| Location | Buttons |
|
||||||
|
| --------------- | ---------------------------------------------------- |
|
||||||
|
| **Bottom Edge** | **Back**, **Confirm**, **Left**, **Right** |
|
||||||
|
| **Right Side** | **Power**, **Volume Up**, **Volume Down**, **Reset** |
|
||||||
|
|
||||||
|
Button layout can be customized in **[Settings](#35-settings)**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Power & Startup
|
||||||
|
|
||||||
|
### Power On / Off
|
||||||
|
|
||||||
|
To turn the device on or off, **press and hold the Power button for approximately half a second**.
|
||||||
|
In **[Settings](#35-settings)** you can configure the power button to turn the device off with a short press instead of a long one.
|
||||||
|
|
||||||
|
To reboot the device (for example if it's frozen, or after a firmware update), press and release the Reset button, and then quickly press and hold the Power button for a few seconds.
|
||||||
|
|
||||||
|
### First Launch
|
||||||
|
|
||||||
|
Upon turning the device on for the first time, you will be placed on the **[Home](#31-home-screen)** screen.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> On subsequent restarts, the firmware will automatically reopen the last book you were reading.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Screens
|
||||||
|
|
||||||
|
### 3.1 Home Screen
|
||||||
|
|
||||||
|
The Home Screen is the main entry point to the firmware. From here you can navigate to **[Reading Mode](#4-reading-mode)** with the most recently read book, **[Book Selection](#32-book-selection)**, **[Settings](#35-settings)**, or the **[File Upload](#34-file-upload-screen)** screen.
|
||||||
|
|
||||||
|
### 3.2 Book Selection
|
||||||
|
|
||||||
|
The Book Selection acts as a folder and file browser.
|
||||||
|
|
||||||
|
* **Navigate List:** Use **Left** (or **Volume Up**), or **Right** (or **Volume Down**) to move the selection cursor up and down through folders and books. You can also long-press these buttons to scroll a full page up or down.
|
||||||
|
* **Open Selection:** Press **Confirm** to open a folder or read a selected book.
|
||||||
|
|
||||||
|
### 3.3 Reading Mode
|
||||||
|
|
||||||
|
See [Reading Mode](#4-reading-mode) below for more information.
|
||||||
|
|
||||||
|
### 3.4 File Upload Screen
|
||||||
|
|
||||||
|
The File Upload screen allows you to upload new e-books to the device. When you enter the screen, you'll be prompted with a WiFi selection dialog and then your X4 will start hosting a web server.
|
||||||
|
|
||||||
|
See the [webserver docs](./docs/webserver.md) for more information on how to connect to the web server and upload files.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> Advanced users can also manage files programmatically or via the command line using `curl`. See the [webserver docs](./docs/webserver.md) for details.
|
||||||
|
|
||||||
|
### 3.4.1 Calibre Wireless Transfers
|
||||||
|
|
||||||
|
CrossPoint supports sending books from Calibre using the CrossPoint Reader device plugin.
|
||||||
|
|
||||||
|
1. Install the plugin in Calibre:
|
||||||
|
- Head to https://github.com/crosspoint-reader/calibre-plugins/releases to download the latest version of the crosspoint_reader plugin.
|
||||||
|
- Download the zip file.
|
||||||
|
- Open Calibre → Preferences → Plugins → Load plugin from file → Select the zip file.
|
||||||
|
2. On the device: File Transfer → Connect to Calibre → Join a network.
|
||||||
|
3. Make sure your computer is on the same WiFi network.
|
||||||
|
4. In Calibre, click "Send to device" to transfer books.
|
||||||
|
|
||||||
|
### 3.5 Settings
|
||||||
|
|
||||||
|
The Settings screen allows you to configure the device's behavior. There are a few settings you can adjust:
|
||||||
|
- **Sleep Screen**: Which sleep screen to display when the device sleeps:
|
||||||
|
- "Dark" (default) - The default dark Crosspoint logo sleep screen
|
||||||
|
- "Light" - The same default sleep screen, on a white background
|
||||||
|
- "Custom" - Custom images from the SD card; see [Sleep Screen](#36-sleep-screen) below for more information
|
||||||
|
- "Cover" - The book cover image (Note: this is experimental and may not work as expected)
|
||||||
|
- "None" - A blank screen
|
||||||
|
- **Sleep Screen Cover Mode**: How to display the book cover when "Cover" sleep screen is selected:
|
||||||
|
- "Fit" (default) - Scale the image down to fit centered on the screen, padding with white borders as necessary
|
||||||
|
- "Crop" - Scale the image down and crop as necessary to try to to fill the screen (Note: this is experimental and may not work as expected)
|
||||||
|
- **Sleep Screen Cover Filter**: What filter will be applied to the book cover when "Cover" sleep screen is selected
|
||||||
|
- "None" (default) - The cover image will be converted to a grayscale image and displayed as it is
|
||||||
|
- "Contrast" - The image will be displayed as a black & white image without grayscale conversion
|
||||||
|
- "Inverted" - The image will be inverted as in white&black and will be displayed without grayscale conversion
|
||||||
|
- **Status Bar**: Configure the status bar displayed while reading:
|
||||||
|
- "None" - No status bar
|
||||||
|
- "No Progress" - Show status bar without reading progress
|
||||||
|
- "Full" - Show status bar with reading progress
|
||||||
|
- **Hide Battery %**: Configure where to suppress the battery pecentage display in the status bar; the battery icon will still be shown:
|
||||||
|
- "Never" - Always show battery percentage (default)
|
||||||
|
- "In Reader" - Show battery percentage everywhere except in reading mode
|
||||||
|
- "Always" - Always hide battery percentage
|
||||||
|
- **Extra Paragraph Spacing**: If enabled, vertical space will be added between paragraphs in the book. If disabled, paragraphs will not have vertical space between them, but will have first-line indentation.
|
||||||
|
- **Text Anti-Aliasing**: Whether to show smooth grey edges (anti-aliasing) on text in reading mode. Note this slows down page turns slightly.
|
||||||
|
- **Short Power Button Click**: Controls the effect of a short click of the power button:
|
||||||
|
- "Ignore" - Require a long press to turn off the device
|
||||||
|
- "Sleep" - A short press powers the device off
|
||||||
|
- "Page Turn" - A short press in reading mode turns to the next page; a long press turns the device off
|
||||||
|
- **Reading Orientation**: Set the screen orientation for reading EPUB files:
|
||||||
|
- "Portrait" (default) - Standard portrait orientation
|
||||||
|
- "Landscape CW" - Landscape, rotated clockwise
|
||||||
|
- "Inverted" - Portrait, upside down
|
||||||
|
- "Landscape CCW" - Landscape, rotated counter-clockwise
|
||||||
|
- **Front Button Layout**: Configure the order of the bottom edge buttons:
|
||||||
|
- Back, Confirm, Left, Right (default)
|
||||||
|
- Left, Right, Back, Confirm
|
||||||
|
- Left, Back, Confirm, Right
|
||||||
|
- Back, Confirm, Right, Left
|
||||||
|
- **Side Button Layout (reader)**: Swap the order of the up and down volume buttons from Previous/Next to Next/Previous. This change is only in effect when reading.
|
||||||
|
- **Long-press Chapter Skip**: Set whether long-pressing page turn buttons skip to the next/previous chapter.
|
||||||
|
- "Chapter Skip" (default) - Long-pressing skips to next/previous chapter
|
||||||
|
- "Page Scroll" - Long-pressing scrolls a page up/down
|
||||||
|
- Swap the order of the up and down volume buttons from Previous/Next to Next/Previous. This change is only in effect when reading.
|
||||||
|
- **Reader Font Family**: Choose the font used for reading:
|
||||||
|
- "Bookerly" (default) - Amazon's reading font
|
||||||
|
- "Noto Sans" - Google's sans-serif font
|
||||||
|
- "Open Dyslexic" - Font designed for readers with dyslexia
|
||||||
|
- **Reader Font Size**: Adjust the text size for reading; options are "Small", "Medium", "Large", or "X Large".
|
||||||
|
- **Reader Line Spacing**: Adjust the spacing between lines; options are "Tight", "Normal", or "Wide".
|
||||||
|
- **Reader Screen Margin**: Controls the screen margins in reader mode between 5 and 40 pixels in 5 pixel increments.
|
||||||
|
- **Reader Paragraph Alignment**: Set the alignment of paragraphs; options are "Justified" (default), "Left", "Center", or "Right".
|
||||||
|
- **Time to Sleep**: Set the duration of inactivity before the device automatically goes to sleep.
|
||||||
|
- **Refresh Frequency**: Set how often the screen does a full refresh while reading to reduce ghosting.
|
||||||
|
- **OPDS Browser**: Configure OPDS server settings for browsing and downloading books. Set the server URL (for Calibre Content Server, add `/opds` to the end), and optionally configure username and password for servers requiring authentication. Note: Only HTTP Basic authentication is supported. If using Calibre Content Server with authentication enabled, you must set it to use Basic authentication instead of the default Digest authentication.
|
||||||
|
- **Check for updates**: Check for firmware updates over WiFi.
|
||||||
|
|
||||||
|
### 3.6 Sleep Screen
|
||||||
|
|
||||||
|
You can customize the sleep screen by placing custom images in specific locations on the SD card:
|
||||||
|
|
||||||
|
- **Single Image:** Place a file named `sleep.bmp` in the root directory.
|
||||||
|
- **Multiple Images:** Create a `sleep` directory in the root of the SD card and place any number of `.bmp` images inside. If images are found in this directory, they will take priority over the `sleep.bmp` file, and one will be randomly selected each time the device sleeps.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> You'll need to set the **Sleep Screen** setting to **Custom** in order to use these images.
|
||||||
|
|
||||||
|
> [!TIP]
|
||||||
|
> For best results:
|
||||||
|
> - Use uncompressed BMP files with 24-bit color depth
|
||||||
|
> - Use a resolution of 480x800 pixels to match the device's screen resolution.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Reading Mode
|
||||||
|
|
||||||
|
Once you have opened a book, the button layout changes to facilitate reading.
|
||||||
|
|
||||||
|
### Page Turning
|
||||||
|
| Action | Buttons |
|
||||||
|
| ----------------- | ------------------------------------ |
|
||||||
|
| **Previous Page** | Press **Left** _or_ **Volume Up** |
|
||||||
|
| **Next Page** | Press **Right** _or_ **Volume Down** |
|
||||||
|
|
||||||
|
The role of the volume (side) buttons can be swapped in **[Settings](#35-settings)**.
|
||||||
|
|
||||||
|
If the **Short Power Button Click** setting is set to "Page Turn", you can also turn to the next page by briefly pressing the Power button.
|
||||||
|
|
||||||
|
### Chapter Navigation
|
||||||
|
* **Next Chapter:** Press and **hold** the **Right** (or **Volume Down**) button briefly, then release.
|
||||||
|
* **Previous Chapter:** Press and **hold** the **Left** (or **Volume Up**) button briefly, then release.
|
||||||
|
|
||||||
|
This feature can be disabled in **[Settings](#35-settings)** to help avoid changing chapters by mistake.
|
||||||
|
|
||||||
|
|
||||||
|
### System Navigation
|
||||||
|
* **Return to Book Selection:** Press **Back** to close the book and return to the **[Book Selection](#32-book-selection)** screen.
|
||||||
|
* **Return to Home:** Press and **hold** the **Back** button to close the book and return to the **[Home](#31-home-screen)** screen.
|
||||||
|
* **Chapter Menu:** Press **Confirm** to open the **[Table of Contents/Chapter Selection](#5-chapter-selection-screen)**.
|
||||||
|
|
||||||
|
### Supported Languages
|
||||||
|
|
||||||
|
CrossPoint renders text using the following Unicode character blocks, enabling support for a wide range of languages:
|
||||||
|
|
||||||
|
* **Latin Script (Basic, Supplement, Extended-A):** Covers English, German, French, Spanish, Portuguese, Italian, Dutch, Swedish, Norwegian, Danish, Finnish, Polish, Czech, Hungarian, Romanian, Slovak, Slovenian, Turkish, and others.
|
||||||
|
* **Cyrillic Script (Standard and Extended):** Covers Russian, Ukrainian, Belarusian, Bulgarian, Serbian, Macedonian, Kazakh, Kyrgyz, Mongolian, and others.
|
||||||
|
|
||||||
|
What is not supported: Chinese, Japanese, Korean, Vietnamese, Hebrew, Arabic and Farsi.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Chapter Selection Screen
|
||||||
|
|
||||||
|
Accessible by pressing **Confirm** while inside a book.
|
||||||
|
|
||||||
|
1. Use **Left** (or **Volume Up**), or **Right** (or **Volume Down**) to highlight the desired chapter.
|
||||||
|
2. Press **Confirm** to jump to that chapter.
|
||||||
|
3. *Alternatively, press **Back** to cancel and return to your current page.*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Current Limitations & Roadmap
|
||||||
|
|
||||||
|
Please note that this firmware is currently in active development. The following features are **not yet supported** but are planned for future updates:
|
||||||
|
|
||||||
|
* **Images:** Embedded images in e-books will not render.
|
||||||
@ -1,3 +1,19 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
find src lib \( -name "*.c" -o -name "*.cpp" -o -name "*.h" -o -name "*.hpp" \) -exec clang-format -style=file -i {} +
|
GIT_LS_FILES_FLAGS=""
|
||||||
|
if [[ "$1" == "-g" ]]; then
|
||||||
|
GIT_LS_FILES_FLAGS="--modified"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Main Logic ---
|
||||||
|
|
||||||
|
# Format all files (or only modified files if -g is passed)
|
||||||
|
|
||||||
|
# Use 'git ls-files' to get a list of all files tracked by git:
|
||||||
|
# --modified: files tracked by git that have been modified (staged or unstaged)
|
||||||
|
# --exclude-standard: ignores files in .gitignore
|
||||||
|
# Additionally exclude files in 'lib/EpdFont/builtinFonts/' as they are script-generated.
|
||||||
|
git ls-files --exclude-standard ${GIT_LS_FILES_FLAGS} \
|
||||||
|
| grep -E '\.(c|cpp|h|hpp)$' \
|
||||||
|
| grep -v -E '^lib/EpdFont/builtinFonts/' \
|
||||||
|
| xargs -r clang-format -style=file -i
|
||||||
|
|||||||
137
claude_notes/ghosting-bisect-debug_2026-01-27_09-42-35.md
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
# Ghosting Issue Bisect Debug Summary
|
||||||
|
|
||||||
|
**Date:** 2026-01-27
|
||||||
|
**Branch:** `catch-up-PR-merges`
|
||||||
|
**Issue:** Text ghosting on page turns when anti-aliasing enabled
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Problem Description
|
||||||
|
|
||||||
|
After merging 15 upstream PRs, ghosting artifacts appeared when turning pages in the EPUB reader. The ghosting manifested as residual edges/outlines of previous page text, visible only when text anti-aliasing was enabled.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The 15 Merged PRs (in merge order)
|
||||||
|
|
||||||
|
| Order | Commit | PR | Description |
|
||||||
|
|-------|--------|-----|-------------|
|
||||||
|
| 1 | `703d955` | #466 | fix: Add .vs folder to .gitignore |
|
||||||
|
| 2 | `7a4af97` | #530 | docs: Update README with supported languages for EPUB |
|
||||||
|
| 3 | `aa87b3f` | #547 | docs: add font generation commands to builtin font headers |
|
||||||
|
| 4 | `25b75b7` | #425 | fix: Allow line break after ellipsis and underscore |
|
||||||
|
| 5 | `31199f9` | #507 | fix: remove decimal places from progress % |
|
||||||
|
| 6 | `d8b8c5b` | #526 | fix: add txt books to recent tab |
|
||||||
|
| 7 | `991b6b5` | #498 | feat: treat .md files as .txt |
|
||||||
|
| 8 | `8920c62` | #525 | fix: line break - flush word before br tag |
|
||||||
|
| 9 | `3cee01b` | #460 | feat: add new configuration for front buttons |
|
||||||
|
| 10 | `f01f397` | #557 | fix: rotate origin in drawImage |
|
||||||
|
| 11 | `03a18fb` | #484 | UX improvement to Forget Network page |
|
||||||
|
| 12 | `ff0392b` | #492 | fix: Validate settings on read |
|
||||||
|
| 13 | `6ffd19a` | #482 | fix: short-press power button to wakeup |
|
||||||
|
| 14 | `c90304f` | #465 | fix: cover artifacts - merge crop parameter |
|
||||||
|
| 15 | `bc4edee` | #404 | Refactor: Replace CalibreWirelessActivity with CalibreConnectActivity |
|
||||||
|
|
||||||
|
**Base commit:** `1a38fd9` (before any PR merges)
|
||||||
|
**Checkpoint commit:** `397abe1` (after all PR merges)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bisect Process
|
||||||
|
|
||||||
|
### Initial State
|
||||||
|
- **GOOD:** `1a38fd9` - no ghosting
|
||||||
|
- **BAD:** `397abe1` (HEAD) - ghosting present
|
||||||
|
|
||||||
|
### Bisect Steps
|
||||||
|
|
||||||
|
| Step | Commit | PR | Result | Remaining |
|
||||||
|
|------|--------|-----|--------|-----------|
|
||||||
|
| 1 | `991b6b5` | #498 (midpoint) | NO ghosting | Bug in commits 8-15 |
|
||||||
|
| 2 | `ff0392b` | #492 (midpoint of 8-15) | NO ghosting | Bug in commits 13-15 |
|
||||||
|
| 3 | `c90304f` | #465 | NO ghosting | Bug in commits 15 or checkpoint |
|
||||||
|
| 4 | `bc4edee` | #404 | NO ghosting | Bug in checkpoint only |
|
||||||
|
|
||||||
|
### Compilation Issue During Bisect
|
||||||
|
|
||||||
|
A conflict from PR #526 merge left `RECENT_BOOKS.addBook()` with 1 argument instead of 3. This caused compilation failures at intermediate commits. Temporary fix applied at each step:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Changed from:
|
||||||
|
RECENT_BOOKS.addBook(txt->getPath());
|
||||||
|
// To:
|
||||||
|
RECENT_BOOKS.addBook(txt->getPath(), txt->getTitle(), "");
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Root Cause Finding
|
||||||
|
|
||||||
|
**The ghosting was NOT caused by any upstream PR.**
|
||||||
|
|
||||||
|
After testing all commits, the bisect pointed to the checkpoint commit `397abe1`. However, the diff between `bc4edee` and `397abe1` only contained:
|
||||||
|
- The `addBook` fix (unrelated to display)
|
||||||
|
- Removing duplicate `handleDownload` (unrelated to display)
|
||||||
|
|
||||||
|
### Actual Cause: Uncommitted Local Changes
|
||||||
|
|
||||||
|
The ghosting was caused by **uncommitted local changes in the IDE working directory**. These changes were being preserved across `git checkout` operations because they existed in IDE buffers.
|
||||||
|
|
||||||
|
When `git checkout -f catch-up-PR-merges` was executed (force checkout), all local changes were discarded and the ghosting disappeared.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Local Changes That Were Present
|
||||||
|
|
||||||
|
The following modifications existed in the working directory but were NOT in the checkpoint commit:
|
||||||
|
|
||||||
|
- `CrossPointSettings.h/cpp` - enum `_COUNT` suffixes, `readAndValidate`, OPDS auth fields
|
||||||
|
- `ChapterHtmlSlimParser.h/cpp` - `flushPartWordBuffer()` function
|
||||||
|
- `SleepActivity.cpp` - `drawImage` coordinate changes
|
||||||
|
- `JpegToBmpConverter.h/cpp` - `crop` parameter
|
||||||
|
- `HomeActivity.cpp` - "OPDS Browser" label
|
||||||
|
- `SettingsActivity.cpp` - front button layout options
|
||||||
|
- `CrossPointWebServer.h/cpp` - UDP discovery, `WsUploadStatus`
|
||||||
|
|
||||||
|
**Important:** These changes were actually already committed in the PR merge commits. The confusion arose because:
|
||||||
|
1. Checking out older commits removed these changes
|
||||||
|
2. IDE buffers or manual re-application restored them as "local changes"
|
||||||
|
3. This created a mismatch between committed code and working directory
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resolution
|
||||||
|
|
||||||
|
1. Force checkout to HEAD: `git checkout -f catch-up-PR-merges`
|
||||||
|
2. Verified all PR changes are properly committed
|
||||||
|
3. Built and tested - no ghosting
|
||||||
|
4. Working directory is now clean (matches committed state)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Lessons
|
||||||
|
|
||||||
|
1. **Always check `git status` before bisecting** - local changes can persist across checkouts
|
||||||
|
2. **Use `git checkout -f` or `git checkout <commit> -- .`** to ensure clean state
|
||||||
|
3. **IDE buffers can reintroduce changes** - close/reload files after checkout if needed
|
||||||
|
4. **Bisecting with compilation errors** requires temporary fixes that don't affect the bug being investigated
|
||||||
|
5. **The "bug" may not be in commits at all** - it could be in uncommitted working directory changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Commands Reference
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Force checkout to discard local changes
|
||||||
|
git checkout -f <branch>
|
||||||
|
|
||||||
|
# Checkout specific commit with clean state
|
||||||
|
git checkout <commit> -- . && git checkout <commit>
|
||||||
|
|
||||||
|
# Check for uncommitted changes
|
||||||
|
git status
|
||||||
|
git diff --stat
|
||||||
|
|
||||||
|
# View what's in a specific commit
|
||||||
|
git show <commit>:path/to/file
|
||||||
|
```
|
||||||
70
claude_notes/missing-serial-guards-2026-01-28.md
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# Serial.printf Calls Without `if (Serial)` Guards
|
||||||
|
|
||||||
|
**Date:** 2026-01-28
|
||||||
|
**Status:** Informational (not blocking issues)
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
The codebase contains **408 Serial print calls** across 27 files in `src/`. Of these, only **16 calls** (in 2 files) have explicit `if (Serial)` guards.
|
||||||
|
|
||||||
|
**This is not a problem** because `Serial.setTxTimeoutMs(0)` is called in `setup()` before any activity code runs, making all Serial output non-blocking globally.
|
||||||
|
|
||||||
|
## Protection Mechanism
|
||||||
|
|
||||||
|
In `src/main.cpp` (lines 467-468):
|
||||||
|
```cpp
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.setTxTimeoutMs(0); // Non-blocking TX - critical for USB disconnect handling
|
||||||
|
```
|
||||||
|
|
||||||
|
This ensures that even without `if (Serial)` guards, Serial.printf calls will return immediately when USB is disconnected instead of blocking indefinitely.
|
||||||
|
|
||||||
|
## Files with `if (Serial)` Guards (16 calls)
|
||||||
|
|
||||||
|
| File | Protected Calls |
|
||||||
|
|------|-----------------|
|
||||||
|
| `src/activities/reader/EpubReaderActivity.cpp` | 15 |
|
||||||
|
| `src/main.cpp` | 1 |
|
||||||
|
|
||||||
|
## Files Without Guards (392 calls)
|
||||||
|
|
||||||
|
These calls are protected by `Serial.setTxTimeoutMs(0)` but don't have explicit guards:
|
||||||
|
|
||||||
|
| File | Unguarded Calls |
|
||||||
|
|------|-----------------|
|
||||||
|
| `src/network/CrossPointWebServer.cpp` | 106 |
|
||||||
|
| `src/activities/network/CrossPointWebServerActivity.cpp` | 49 |
|
||||||
|
| `src/activities/boot_sleep/SleepActivity.cpp` | 33 |
|
||||||
|
| `src/BookManager.cpp` | 25 |
|
||||||
|
| `src/activities/reader/TxtReaderActivity.cpp` | 20 |
|
||||||
|
| `src/activities/home/HomeActivity.cpp` | 16 |
|
||||||
|
| `src/network/OtaUpdater.cpp` | 16 |
|
||||||
|
| `src/util/Md5Utils.cpp` | 15 |
|
||||||
|
| `src/main.cpp` | 13 (plus 1 guarded) |
|
||||||
|
| `src/WifiCredentialStore.cpp` | 12 |
|
||||||
|
| `src/network/HttpDownloader.cpp` | 12 |
|
||||||
|
| `src/BookListStore.cpp` | 11 |
|
||||||
|
| `src/activities/network/WifiSelectionActivity.cpp` | 11 |
|
||||||
|
| `src/activities/settings/OtaUpdateActivity.cpp` | 9 |
|
||||||
|
| `src/activities/browser/OpdsBookBrowserActivity.cpp` | 9 |
|
||||||
|
| `src/activities/settings/ClearCacheActivity.cpp` | 7 |
|
||||||
|
| `src/BookmarkStore.cpp` | 6 |
|
||||||
|
| `src/RecentBooksStore.cpp` | 5 |
|
||||||
|
| `src/activities/reader/ReaderActivity.cpp` | 4 |
|
||||||
|
| `src/activities/Activity.h` | 3 |
|
||||||
|
| `src/CrossPointSettings.cpp` | 3 |
|
||||||
|
| `src/activities/network/CalibreConnectActivity.cpp` | 2 |
|
||||||
|
| `src/activities/home/ListViewActivity.cpp` | 2 |
|
||||||
|
| `src/activities/home/MyLibraryActivity.cpp` | 1 |
|
||||||
|
| `src/activities/dictionary/DictionarySearchActivity.cpp` | 1 |
|
||||||
|
| `src/CrossPointState.cpp` | 1 |
|
||||||
|
|
||||||
|
## Recommendation
|
||||||
|
|
||||||
|
No immediate action required. The global `Serial.setTxTimeoutMs(0)` protection is sufficient.
|
||||||
|
|
||||||
|
If desired, `if (Serial)` guards could be added to high-frequency logging paths for minor performance optimization (skipping format string processing), but this is low priority.
|
||||||
|
|
||||||
|
## Note on open-x4-sdk
|
||||||
|
|
||||||
|
The `open-x4-sdk` submodule also contains Serial calls (in `EInkDisplay.cpp`, `SDCardManager.cpp`). These are also protected by the global timeout setting since `Serial.begin()` and `setTxTimeoutMs()` are called before any SDK code executes.
|
||||||
125
claude_notes/serial-blocking-debug-2026-01-28.md
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
# Serial Blocking Debug Session Summary
|
||||||
|
|
||||||
|
**Date:** 2026-01-28
|
||||||
|
**Issue:** Device freezes when booted without USB connected
|
||||||
|
**Resolution:** `Serial.setTxTimeoutMs(0)` - make Serial TX non-blocking
|
||||||
|
|
||||||
|
## Problem Description
|
||||||
|
|
||||||
|
During release preparation for ef-0.15.9, the device was discovered to freeze completely when:
|
||||||
|
1. Unplugged from USB
|
||||||
|
2. Powered on via power button
|
||||||
|
3. Book page displays, then device becomes unresponsive
|
||||||
|
4. No button presses register
|
||||||
|
|
||||||
|
The device worked perfectly when USB was connected.
|
||||||
|
|
||||||
|
## Investigation Process
|
||||||
|
|
||||||
|
### Initial Hypotheses Tested
|
||||||
|
|
||||||
|
Multiple hypotheses were systematically investigated:
|
||||||
|
|
||||||
|
1. **Hypothesis A-D:** Display/rendering mutex issues
|
||||||
|
- Added mutex logging to SD card
|
||||||
|
- Mutex operations completed successfully
|
||||||
|
- Ruled out as root cause
|
||||||
|
|
||||||
|
2. **Hypothesis E:** FreeRTOS task creation issues
|
||||||
|
- Task created and ran successfully
|
||||||
|
- First render completed normally
|
||||||
|
- Ruled out
|
||||||
|
|
||||||
|
3. **Hypothesis F-G:** Main loop execution
|
||||||
|
- Added loop counter logging to SD card
|
||||||
|
- **Key finding:** Main loop never started logging
|
||||||
|
- Setup() completed but loop() never executed meaningful work
|
||||||
|
|
||||||
|
4. **Hypothesis H-J:** Various timing and initialization issues
|
||||||
|
- Tested different delays and initialization orders
|
||||||
|
- No improvement
|
||||||
|
|
||||||
|
### Root Cause Discovery
|
||||||
|
|
||||||
|
The breakthrough came from analyzing the boot sequence:
|
||||||
|
|
||||||
|
1. `setup()` completes successfully
|
||||||
|
2. `EpubReaderActivity::onEnter()` runs and calls `Serial.printf()` to log progress
|
||||||
|
3. **Device hangs at Serial.printf() call**
|
||||||
|
|
||||||
|
On ESP32-C3 with USB CDC (USB serial), `Serial.printf()` blocks indefinitely waiting for the TX buffer to drain when USB is not connected. The default behavior expects a host to read the data.
|
||||||
|
|
||||||
|
### Evidence
|
||||||
|
|
||||||
|
- When USB connected: `Serial.printf()` returns immediately (data sent to host)
|
||||||
|
- When USB disconnected: `Serial.printf()` blocks forever waiting for TX buffer space
|
||||||
|
- The hang occurred specifically in `EpubReaderActivity.cpp` during progress logging
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
### Primary Fix
|
||||||
|
|
||||||
|
Configure Serial to be non-blocking in `src/main.cpp`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// Always initialize Serial but make it non-blocking
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.setTxTimeoutMs(0); // Non-blocking TX - critical for USB disconnect handling
|
||||||
|
```
|
||||||
|
|
||||||
|
`Serial.setTxTimeoutMs(0)` tells the ESP32 Arduino core to return immediately from Serial write operations if the buffer is full, rather than blocking.
|
||||||
|
|
||||||
|
### Secondary Protection (Belt and Suspenders)
|
||||||
|
|
||||||
|
Added `if (Serial)` guards to high-traffic Serial calls in `EpubReaderActivity.cpp`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
if (Serial) Serial.printf("[%lu] [ERS] Loaded progress...\n", millis());
|
||||||
|
```
|
||||||
|
|
||||||
|
This provides an additional check before attempting to print, though it's not strictly necessary with the timeout set to 0.
|
||||||
|
|
||||||
|
## Files Changed
|
||||||
|
|
||||||
|
| File | Change |
|
||||||
|
|------|--------|
|
||||||
|
| `src/main.cpp` | Added `Serial.setTxTimeoutMs(0)` after `Serial.begin()` |
|
||||||
|
| `src/main.cpp` | Added `if (Serial)` guard to auto-sleep log |
|
||||||
|
| `src/main.cpp` | Added `if (Serial)` guard to max loop duration log |
|
||||||
|
| `src/activities/reader/EpubReaderActivity.cpp` | Added 16 `if (Serial)` guards |
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
After applying the fix:
|
||||||
|
1. Device boots successfully when unplugged from USB
|
||||||
|
2. Book pages render correctly
|
||||||
|
3. Button presses register normally
|
||||||
|
4. Sleep/wake cycle works
|
||||||
|
5. No functionality lost when USB is connected
|
||||||
|
|
||||||
|
## Lessons Learned
|
||||||
|
|
||||||
|
1. **ESP32-C3 USB CDC behavior:** Serial output can block indefinitely without a connected host
|
||||||
|
2. **Always set non-blocking:** `Serial.setTxTimeoutMs(0)` should be standard for battery-powered devices
|
||||||
|
3. **Debug logging location matters:** When debugging hangs, SD card logging proved essential since Serial was the problem
|
||||||
|
4. **Systematic hypothesis testing:** Ruled out many red herrings (mutex, task, rendering) before finding the true cause
|
||||||
|
|
||||||
|
## Technical Details
|
||||||
|
|
||||||
|
### Why This Affects ESP32-C3 Specifically
|
||||||
|
|
||||||
|
The ESP32-C3 uses native USB CDC for serial communication (no external USB-UART chip). The Arduino core's default behavior is to wait for TX buffer space, which requires an active USB host connection.
|
||||||
|
|
||||||
|
### Alternative Approaches Considered
|
||||||
|
|
||||||
|
1. **Only initialize Serial when USB connected:** Partially implemented, but insufficient because USB can be disconnected after boot
|
||||||
|
2. **Add `if (Serial)` guards everywhere:** Too invasive (400+ calls)
|
||||||
|
3. **Disable Serial entirely:** Would lose debug output when USB connected
|
||||||
|
|
||||||
|
The chosen solution (`setTxTimeoutMs(0)`) provides the best balance: debug output works when USB is connected, device operates normally when disconnected.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- ESP32 Arduino Core Serial documentation
|
||||||
|
- ESP-IDF USB CDC documentation
|
||||||
|
- FreeRTOS queue behavior (initial red herring investigation)
|
||||||
132
claude_notes/usb-serial-blocking-fix-2026-01-28.md
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
# USB Serial Blocking Issue - Root Cause and Fix
|
||||||
|
|
||||||
|
**Date:** 2026-01-28
|
||||||
|
**Issue:** Device blocking/hanging when USB is not connected at boot
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Problem Description
|
||||||
|
|
||||||
|
The device would hang or behave unpredictably when booted without USB connected. This was traced to improper Serial handling on ESP32-C3 with USB CDC.
|
||||||
|
|
||||||
|
## Root Cause Analysis
|
||||||
|
|
||||||
|
### Factor A: `checkForFlashCommand()` Called Without Serial Initialization
|
||||||
|
|
||||||
|
The most critical issue was in `checkForFlashCommand()`, which is called at the start of every `loop()` iteration:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void loop() {
|
||||||
|
checkForFlashCommand(); // Called EVERY loop iteration
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkForFlashCommand() {
|
||||||
|
while (Serial.available()) { // Called even when Serial.begin() was never called!
|
||||||
|
char c = Serial.read();
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When USB is not connected at boot, `Serial.begin()` is never called. Then in `loop()`, `checkForFlashCommand()` calls `Serial.available()` and `Serial.read()` on an uninitialized Serial object. On ESP32-C3 with USB CDC, this causes undefined behavior or blocking.
|
||||||
|
|
||||||
|
### Factor B: Removed `while (!Serial)` Wait Loop
|
||||||
|
|
||||||
|
The upstream 0.16.0 code included a 3-second wait loop after `Serial.begin()`:
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
if (isUsbConnected()) {
|
||||||
|
Serial.begin(115200);
|
||||||
|
unsigned long start = millis();
|
||||||
|
while (!Serial && (millis() - start) < 3000) {
|
||||||
|
delay(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This wait loop was removed in an earlier attempt to fix boot delays, but it may be necessary for proper USB CDC initialization.
|
||||||
|
|
||||||
|
### Factor C: `Serial.setTxTimeoutMs(0)` Added Too Early
|
||||||
|
|
||||||
|
`Serial.setTxTimeoutMs(0)` was added immediately after `Serial.begin()` to make TX non-blocking. However, calling this before the Serial connection is fully established may interfere with USB CDC initialization.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The Fix
|
||||||
|
|
||||||
|
### 1. Guard `checkForFlashCommand()` with Serial Check
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void checkForFlashCommand() {
|
||||||
|
if (!Serial) return; // Early exit if Serial not initialized
|
||||||
|
while (Serial.available()) {
|
||||||
|
// ... rest unchanged
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Restore Upstream Serial Initialization Pattern
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
void setup() {
|
||||||
|
t1 = millis();
|
||||||
|
|
||||||
|
// Only start serial if USB connected
|
||||||
|
pinMode(UART0_RXD, INPUT);
|
||||||
|
if (isUsbConnected()) {
|
||||||
|
Serial.begin(115200);
|
||||||
|
// Wait up to 3 seconds for Serial to be ready to catch early logs
|
||||||
|
unsigned long start = millis();
|
||||||
|
while (!Serial && (millis() - start) < 3000) {
|
||||||
|
delay(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ... rest of setup
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Remove `Serial.setTxTimeoutMs(0)`
|
||||||
|
|
||||||
|
This call was removed entirely as it's not present in upstream and may cause issues.
|
||||||
|
|
||||||
|
### 4. Remove Unnecessary `if (Serial)` Guards
|
||||||
|
|
||||||
|
The 15 `if (Serial)` guards added to `EpubReaderActivity.cpp` were removed. `Serial.printf()` is safe to call when Serial isn't initialized (it simply returns 0), so guards around output calls are unnecessary.
|
||||||
|
|
||||||
|
**Key distinction:**
|
||||||
|
- `Serial.printf()` / `Serial.println()` - Safe without guards (no-op when not initialized)
|
||||||
|
- `Serial.available()` / `Serial.read()` - **MUST** be guarded (undefined behavior when not initialized)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files Changed
|
||||||
|
|
||||||
|
| File | Change |
|
||||||
|
|------|--------|
|
||||||
|
| `src/main.cpp` | Removed `Serial.setTxTimeoutMs(0)`, restored `while (!Serial)` wait, added guard to `checkForFlashCommand()` |
|
||||||
|
| `src/activities/reader/EpubReaderActivity.cpp` | Removed all 15 `if (Serial)` guards |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing Checklist
|
||||||
|
|
||||||
|
After applying fixes, verify:
|
||||||
|
|
||||||
|
1. ✅ Boot with USB connected, serial monitor open - should work
|
||||||
|
2. ✅ Boot with USB connected, NO serial monitor - should work (3s delay then continue)
|
||||||
|
3. ✅ Boot without USB - should work immediately (no blocking)
|
||||||
|
4. ✅ Sleep without USB, plug in USB during sleep, wake - should work
|
||||||
|
5. ✅ Sleep with USB, unplug during sleep, wake - should work
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Lessons Learned
|
||||||
|
|
||||||
|
1. **Always guard Serial input operations**: `Serial.available()` and `Serial.read()` must be guarded with `if (Serial)` or `if (!Serial) return` when Serial initialization is conditional.
|
||||||
|
|
||||||
|
2. **Serial output is safe without guards**: `Serial.printf()` and similar output functions are safe to call even when Serial is not initialized - they simply return 0.
|
||||||
|
|
||||||
|
3. **Don't remove initialization waits without understanding why they exist**: The `while (!Serial)` wait loop exists for proper USB CDC initialization and shouldn't be removed without careful testing.
|
||||||
|
|
||||||
|
4. **Upstream patterns exist for a reason**: When diverging from upstream behavior, especially around low-level hardware initialization, be extra cautious and test thoroughly.
|
||||||
BIN
dict-en-en.zip
Normal file
422
docs/branch-comparison-summary.md
Normal file
@ -0,0 +1,422 @@
|
|||||||
|
# Branch Comparison Summary: crosspoint-ef vs 0.16.0
|
||||||
|
|
||||||
|
This document provides a comprehensive comparison between the `crosspoint-ef` branch and the upstream `0.16.0` release for merge planning and implementation decisions.
|
||||||
|
|
||||||
|
## Branch History
|
||||||
|
|
||||||
|
| Branch | Base | Commits Since Base | Status |
|
||||||
|
|--------|------|-------------------|--------|
|
||||||
|
| `crosspoint-ef` | 0.15.0 | 90+ | Active development |
|
||||||
|
| `0.16.0` | 0.15.0 | 30 | Released |
|
||||||
|
|
||||||
|
Both branches diverged from `0.15.0` at commit `3ce11f14`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature Comparison Matrix
|
||||||
|
|
||||||
|
### Major Features
|
||||||
|
|
||||||
|
| Feature | crosspoint-ef | 0.16.0 | Notes |
|
||||||
|
|---------|:-------------:|:------:|-------|
|
||||||
|
| Dictionary Support | Yes | No | StarDict format with word selection |
|
||||||
|
| Bookmark System | Yes | No | Per-book bookmarks with visual indicator |
|
||||||
|
| Quick Menu | Yes | No | Power button quick access |
|
||||||
|
| Library Search | Yes | No | Character picker with weighted search |
|
||||||
|
| CSS Parsing | Yes | No | Element, class, inline styles |
|
||||||
|
| Inline Images (PNG/JPEG) | Yes | No | With caching and dithering |
|
||||||
|
| Custom Fonts | Yes | No | Atkinson Hyperlegible, Fern Micro |
|
||||||
|
| Enhanced Web Server | Yes | Partial | File ops, MD5 API, mDNS |
|
||||||
|
| Companion App API | Yes | No | Deep links, WebSocket uploads |
|
||||||
|
| Reading Lists | Yes | No | With pinning support |
|
||||||
|
| Tab Bar Enhancements | Yes | No | Scrolling, overflow indicators |
|
||||||
|
| High Contrast Mode | Yes | No | System-wide |
|
||||||
|
| Bezel Compensation | Yes | No | Edge defect compensation |
|
||||||
|
| Sleep Screen Edge Detection | Yes | No | Dominant color fill |
|
||||||
|
| Recents Improvements | Yes | No | Badges, removal, clearing |
|
||||||
|
| Progress Bar Status Bar | Yes | Yes | Same feature |
|
||||||
|
| Spanish Hyphenation | No | Yes | Missing in crosspoint-ef |
|
||||||
|
| XTC/XTCH Author Extraction | No | Yes | Missing in crosspoint-ef |
|
||||||
|
| OTA Rework | No | Yes | Different implementation |
|
||||||
|
| KOReader MD5 Binary Matching | No | Yes | Missing in crosspoint-ef |
|
||||||
|
| Relative Position on Settings Change | No | Yes | Missing in crosspoint-ef |
|
||||||
|
| Multi-line Keyboard Entry | No | Yes | Missing in crosspoint-ef |
|
||||||
|
| Italics on Image Alt | No | Yes | Missing in crosspoint-ef |
|
||||||
|
| Page Turn on Button Press (UX) | No | Yes | When chapter skip disabled |
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
| Fix | crosspoint-ef | 0.16.0 | Notes |
|
||||||
|
|-----|:-------------:|:------:|-------|
|
||||||
|
| Large EPUB indexing O(n²)→O(n) | Yes | Yes | Same fix |
|
||||||
|
| Settings validation on read | Yes | Yes | Same fix |
|
||||||
|
| Line break fixes | Yes | Yes | Similar fixes |
|
||||||
|
| Rotate origin in drawImage | Yes | Yes | Same fix |
|
||||||
|
| Short-press power wakeup | Yes | Yes | Same fix |
|
||||||
|
| TXT books in recent tab | Yes | Yes | Same fix |
|
||||||
|
| B&W filters for covers | Yes | Yes | Same fix |
|
||||||
|
| Cover fit artifacts | Yes | Yes | Same fix |
|
||||||
|
| Grayscale state corruption | Yes | No | Unique to crosspoint-ef |
|
||||||
|
| Memory graceful degradation | Yes | No | Unique to crosspoint-ef |
|
||||||
|
| Chapter Selection UI (KOReader) | No | Yes | Missing in crosspoint-ef |
|
||||||
|
| Front layout in mapLabels() | No | Yes | Missing in crosspoint-ef |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files Changed Summary
|
||||||
|
|
||||||
|
### crosspoint-ef Unique Files (New)
|
||||||
|
|
||||||
|
| Category | Files |
|
||||||
|
|----------|-------|
|
||||||
|
| Dictionary | `src/activities/dictionary/` (8 files), `lib/StarDict/` (4 files) |
|
||||||
|
| Bookmarks | `src/BookmarkStore.cpp/.h`, `src/activities/home/BookmarkListActivity.cpp/.h` |
|
||||||
|
| Quick Menu | `src/activities/util/QuickMenuActivity.cpp/.h` |
|
||||||
|
| CSS | `lib/Epub/Epub/css/` (3 files) |
|
||||||
|
| Images | `lib/Epub/Epub/blocks/ImageBlock.cpp/.h`, `lib/Epub/Epub/converters/` (6 files) |
|
||||||
|
| Custom Fonts | `src/customFonts.cpp`, `src/fontIds.h`, `lib/EpdFont/builtinFonts/custom/` (50+ files) |
|
||||||
|
| Utils | `src/util/Md5Utils.cpp/.h`, `src/util/StringUtils.cpp/.h` |
|
||||||
|
| Lists | `src/BookListStore.cpp/.h` |
|
||||||
|
| Docs | `docs/webserver-api-reference.md`, `docs/companion-app-deep-link-API.md`, `docs/troubleshooting.md` |
|
||||||
|
|
||||||
|
### crosspoint-ef Modified Files (Significant Changes)
|
||||||
|
|
||||||
|
| File | Changes |
|
||||||
|
|------|---------|
|
||||||
|
| `src/network/CrossPointWebServer.cpp` | +1083 lines (file ops, API, WebSocket) |
|
||||||
|
| `src/activities/home/MyLibraryActivity.cpp` | +700 lines (tabs, search, badges) |
|
||||||
|
| `src/main.cpp` | +255 lines (feature integration) |
|
||||||
|
| `lib/GfxRenderer/GfxRenderer.cpp` | +439 lines (contrast, bezel) |
|
||||||
|
| `lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp` | +596 lines (CSS integration) |
|
||||||
|
| `src/CrossPointSettings.cpp/.h` | New settings fields |
|
||||||
|
| `src/activities/boot_sleep/SleepActivity.cpp` | Edge detection, caching |
|
||||||
|
| `src/RecentBooksStore.cpp` | Badges, removal, metadata |
|
||||||
|
| `src/ScreenComponents.cpp` | Tab bar enhancements |
|
||||||
|
|
||||||
|
### 0.16.0 Unique Files/Changes
|
||||||
|
|
||||||
|
| File | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `lib/Epub/Epub/hyphenation/generated/hyph-es.trie.h` | Spanish hyphenation (removed in ef) |
|
||||||
|
| `lib/KOReaderSync/` | KOReader credential handling (removed in ef) |
|
||||||
|
| `src/network/OtaUpdater.cpp` | OTA rework |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Merge Strategy Recommendations
|
||||||
|
|
||||||
|
### Phase 1: Cherry-pick 0.16.0 Fixes into crosspoint-ef
|
||||||
|
|
||||||
|
**Low Risk - Recommended First:**
|
||||||
|
|
||||||
|
1. **Spanish hyphenation support** (#558)
|
||||||
|
- Add `hyph-es.trie.h` back
|
||||||
|
- Update `LanguageRegistry.cpp`
|
||||||
|
|
||||||
|
2. **Render keyboard entry over multiple lines** (#567)
|
||||||
|
- Update `KeyboardEntryActivity.cpp`
|
||||||
|
|
||||||
|
3. **Correctly render italics on image alt** (#569)
|
||||||
|
- Minimal change to text rendering
|
||||||
|
|
||||||
|
4. **Page turning on button pressed** (#451)
|
||||||
|
- UX improvement when chapter skip disabled
|
||||||
|
|
||||||
|
5. **Missing front layout in mapLabels()** (#564)
|
||||||
|
- Bug fix for button mapping
|
||||||
|
|
||||||
|
**Medium Risk:**
|
||||||
|
|
||||||
|
6. **KOReader document MD5 binary matching** (#529)
|
||||||
|
- May conflict with MD5Utils changes
|
||||||
|
|
||||||
|
7. **Chapter Selection UI bugs** (#501)
|
||||||
|
- Review for conflicts with tab bar changes
|
||||||
|
|
||||||
|
8. **Relative position on settings change** (#486)
|
||||||
|
- Reader state management change
|
||||||
|
|
||||||
|
**Higher Risk:**
|
||||||
|
|
||||||
|
9. **OTA feature rework** (#509)
|
||||||
|
- Compare implementations, may need reconciliation
|
||||||
|
- crosspoint-ef has different OTA changes
|
||||||
|
|
||||||
|
10. **Extract author from XTC/XTCH** (#563)
|
||||||
|
- XTC format was removed in crosspoint-ef
|
||||||
|
- Evaluate if needed
|
||||||
|
|
||||||
|
### Phase 2: Potential Upstream Contributions from crosspoint-ef
|
||||||
|
|
||||||
|
**High Value, Moderate Complexity:**
|
||||||
|
|
||||||
|
1. **Dictionary Support**
|
||||||
|
- Self-contained feature
|
||||||
|
- New files, minimal integration points
|
||||||
|
- Requires shipping dictionary data
|
||||||
|
|
||||||
|
2. **Bookmark System**
|
||||||
|
- Clean implementation
|
||||||
|
- New files with reader integration
|
||||||
|
|
||||||
|
3. **Quick Menu**
|
||||||
|
- Simple overlay feature
|
||||||
|
- Depends on bookmark and dictionary
|
||||||
|
|
||||||
|
4. **CSS Parsing**
|
||||||
|
- Significant EPUB improvement
|
||||||
|
- Well-isolated in `lib/Epub/Epub/css/`
|
||||||
|
|
||||||
|
**High Value, Higher Complexity:**
|
||||||
|
|
||||||
|
5. **Inline Image Support**
|
||||||
|
- Major EPUB enhancement
|
||||||
|
- Multiple new converters
|
||||||
|
- Memory management considerations
|
||||||
|
|
||||||
|
6. **Library Search**
|
||||||
|
- Integrated into MyLibraryActivity
|
||||||
|
- Tab bar changes included
|
||||||
|
|
||||||
|
7. **Enhanced Web Server**
|
||||||
|
- Large changes to CrossPointWebServer
|
||||||
|
- New API endpoints
|
||||||
|
- WebSocket uploads
|
||||||
|
|
||||||
|
**Medium Value:**
|
||||||
|
|
||||||
|
8. **Custom Fonts**
|
||||||
|
- Large binary additions (font headers)
|
||||||
|
- Clean integration
|
||||||
|
|
||||||
|
9. **Display Enhancements**
|
||||||
|
- High contrast, bezel compensation
|
||||||
|
- Settings additions
|
||||||
|
|
||||||
|
10. **Reading Lists**
|
||||||
|
- New feature with web API
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Potential Conflicts
|
||||||
|
|
||||||
|
### High Conflict Risk
|
||||||
|
|
||||||
|
| Area | crosspoint-ef | 0.16.0 | Resolution |
|
||||||
|
|------|---------------|--------|------------|
|
||||||
|
| `MyLibraryActivity.cpp` | Major restructure | Minor fixes | Manual merge required |
|
||||||
|
| `CrossPointWebServer.cpp` | Extensive additions | Minimal changes | crosspoint-ef likely compatible |
|
||||||
|
| `CrossPointSettings.h` | Many new fields | Few changes | Additive, low conflict |
|
||||||
|
| `main.cpp` | Feature integration | Minor changes | Review integration points |
|
||||||
|
| `OtaUpdater.cpp` | Modified | Reworked (#509) | Compare implementations |
|
||||||
|
|
||||||
|
### Low Conflict Risk
|
||||||
|
|
||||||
|
| Area | Notes |
|
||||||
|
|------|-------|
|
||||||
|
| Dictionary files | All new, no conflicts |
|
||||||
|
| Bookmark files | All new, no conflicts |
|
||||||
|
| CSS parser files | All new, no conflicts |
|
||||||
|
| Image converter files | All new, no conflicts |
|
||||||
|
| Custom font files | All new, no conflicts |
|
||||||
|
| StarDict library | All new, no conflicts |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing Considerations
|
||||||
|
|
||||||
|
### Regression Testing Required
|
||||||
|
|
||||||
|
After any merge:
|
||||||
|
|
||||||
|
1. **EPUB Reading**
|
||||||
|
- Page navigation
|
||||||
|
- Chapter selection
|
||||||
|
- CSS styling
|
||||||
|
- Image rendering
|
||||||
|
- Bookmark indicators
|
||||||
|
|
||||||
|
2. **Library Functions**
|
||||||
|
- Tab navigation
|
||||||
|
- Search functionality
|
||||||
|
- Recent books display
|
||||||
|
- List management
|
||||||
|
|
||||||
|
3. **Dictionary**
|
||||||
|
- Word selection
|
||||||
|
- Lookup accuracy
|
||||||
|
- Definition display
|
||||||
|
|
||||||
|
4. **Web Server**
|
||||||
|
- File upload/download
|
||||||
|
- API endpoints
|
||||||
|
- WebSocket uploads
|
||||||
|
- mDNS discovery
|
||||||
|
|
||||||
|
5. **Settings**
|
||||||
|
- All new settings persist correctly
|
||||||
|
- Settings migration from older versions
|
||||||
|
|
||||||
|
6. **Display**
|
||||||
|
- High contrast mode
|
||||||
|
- Bezel compensation (all orientations)
|
||||||
|
- Sleep screen variations
|
||||||
|
|
||||||
|
### Memory Testing
|
||||||
|
|
||||||
|
crosspoint-ef includes memory optimization fixes. After merge:
|
||||||
|
|
||||||
|
1. Test with large EPUBs (2000+ chapters)
|
||||||
|
2. Test opening multiple books in sequence
|
||||||
|
3. Test anti-aliasing under memory pressure
|
||||||
|
4. Monitor for ghosting/artifacts
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Priority Recommendations
|
||||||
|
|
||||||
|
### Immediate (For crosspoint-ef Stability)
|
||||||
|
|
||||||
|
1. Cherry-pick Spanish hyphenation (#558)
|
||||||
|
2. Cherry-pick multi-line keyboard entry (#567)
|
||||||
|
3. Cherry-pick italics on image alt (#569)
|
||||||
|
4. Cherry-pick front layout fix (#564)
|
||||||
|
|
||||||
|
### Short-term (Feature Completeness)
|
||||||
|
|
||||||
|
5. Evaluate OTA rework (#509) - compare implementations
|
||||||
|
6. Cherry-pick page turn UX (#451)
|
||||||
|
7. Cherry-pick relative position fix (#486)
|
||||||
|
|
||||||
|
### Long-term (Upstream Contribution)
|
||||||
|
|
||||||
|
8. Prepare dictionary feature as PR
|
||||||
|
9. Prepare bookmark system as PR
|
||||||
|
10. Prepare CSS parsing as PR
|
||||||
|
11. Evaluate inline image support for upstream
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Inventory for Merge
|
||||||
|
|
||||||
|
### Files to Add to 0.16.0 Base (for upstream contribution)
|
||||||
|
|
||||||
|
```
|
||||||
|
src/activities/dictionary/
|
||||||
|
DictionaryMargins.h
|
||||||
|
DictionaryMenuActivity.cpp
|
||||||
|
DictionaryMenuActivity.h
|
||||||
|
DictionaryResultActivity.cpp
|
||||||
|
DictionaryResultActivity.h
|
||||||
|
DictionarySearchActivity.cpp
|
||||||
|
DictionarySearchActivity.h
|
||||||
|
EpubWordSelectionActivity.cpp
|
||||||
|
EpubWordSelectionActivity.h
|
||||||
|
|
||||||
|
src/activities/util/
|
||||||
|
QuickMenuActivity.cpp
|
||||||
|
QuickMenuActivity.h
|
||||||
|
|
||||||
|
src/activities/home/
|
||||||
|
BookmarkListActivity.cpp
|
||||||
|
BookmarkListActivity.h
|
||||||
|
|
||||||
|
src/
|
||||||
|
BookmarkStore.cpp
|
||||||
|
BookmarkStore.h
|
||||||
|
BookListStore.cpp
|
||||||
|
BookListStore.h
|
||||||
|
customFonts.cpp
|
||||||
|
fontIds.h
|
||||||
|
BadgeConfig.h
|
||||||
|
|
||||||
|
src/util/
|
||||||
|
Md5Utils.cpp
|
||||||
|
Md5Utils.h
|
||||||
|
StringUtils.cpp
|
||||||
|
StringUtils.h
|
||||||
|
|
||||||
|
src/images/
|
||||||
|
LockIcon.h
|
||||||
|
|
||||||
|
lib/StarDict/
|
||||||
|
StarDict.cpp
|
||||||
|
StarDict.h
|
||||||
|
DictHtmlParser.cpp
|
||||||
|
DictHtmlParser.h
|
||||||
|
DictPrefixIndex.generated.h
|
||||||
|
|
||||||
|
lib/Epub/Epub/css/
|
||||||
|
CssParser.cpp
|
||||||
|
CssParser.h
|
||||||
|
CssStyle.h
|
||||||
|
|
||||||
|
lib/Epub/Epub/blocks/
|
||||||
|
ImageBlock.cpp
|
||||||
|
ImageBlock.h
|
||||||
|
BlockStyle.h
|
||||||
|
|
||||||
|
lib/Epub/Epub/converters/
|
||||||
|
FramebufferWriter.cpp
|
||||||
|
FramebufferWriter.h
|
||||||
|
ImageDecoderFactory.cpp
|
||||||
|
ImageDecoderFactory.h
|
||||||
|
ImageToFramebufferDecoder.cpp
|
||||||
|
ImageToFramebufferDecoder.h
|
||||||
|
JpegToFramebufferConverter.cpp
|
||||||
|
JpegToFramebufferConverter.h
|
||||||
|
PngToFramebufferConverter.cpp
|
||||||
|
PngToFramebufferConverter.h
|
||||||
|
|
||||||
|
lib/EpdFont/builtinFonts/custom/
|
||||||
|
[All font header files]
|
||||||
|
|
||||||
|
docs/
|
||||||
|
webserver-api-reference.md
|
||||||
|
companion-app-deep-link-API.md
|
||||||
|
troubleshooting.md
|
||||||
|
crosspoint-ef-features.md
|
||||||
|
crosspoint-ef-user-guide.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### Files to Merge Carefully
|
||||||
|
|
||||||
|
```
|
||||||
|
src/main.cpp
|
||||||
|
src/CrossPointSettings.cpp
|
||||||
|
src/CrossPointSettings.h
|
||||||
|
src/network/CrossPointWebServer.cpp
|
||||||
|
src/network/CrossPointWebServer.h
|
||||||
|
src/network/OtaUpdater.cpp
|
||||||
|
src/network/OtaUpdater.h
|
||||||
|
src/activities/home/MyLibraryActivity.cpp
|
||||||
|
src/activities/home/MyLibraryActivity.h
|
||||||
|
src/activities/reader/EpubReaderActivity.cpp
|
||||||
|
src/activities/settings/SettingsActivity.cpp
|
||||||
|
src/activities/settings/CategorySettingsActivity.cpp
|
||||||
|
src/ScreenComponents.cpp
|
||||||
|
src/ScreenComponents.h
|
||||||
|
src/RecentBooksStore.cpp
|
||||||
|
src/RecentBooksStore.h
|
||||||
|
lib/GfxRenderer/GfxRenderer.cpp
|
||||||
|
lib/GfxRenderer/GfxRenderer.h
|
||||||
|
lib/GfxRenderer/BitmapHelpers.cpp
|
||||||
|
lib/GfxRenderer/BitmapHelpers.h
|
||||||
|
lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
|
||||||
|
lib/Epub/Epub/parsers/ChapterHtmlSlimParser.h
|
||||||
|
lib/Epub/Epub/Section.cpp
|
||||||
|
lib/Epub/Epub/Section.h
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
The `crosspoint-ef` branch represents a significant enhancement over the `0.15.0` baseline with 14+ major features. Most features are cleanly isolated in new files, making selective upstream contribution feasible.
|
||||||
|
|
||||||
|
**Recommended approach:**
|
||||||
|
1. First, bring crosspoint-ef up to date with 0.16.0 bug fixes
|
||||||
|
2. Then, evaluate individual features for upstream PR submission
|
||||||
|
3. Prioritize dictionary, bookmarks, and CSS parsing as highest-value contributions
|
||||||
|
|
||||||
|
The grayscale state corruption fix in crosspoint-ef should also be submitted upstream as a critical bug fix, as it prevents display artifacts under memory pressure.
|
||||||
309
docs/companion-app-deep-link-API.md
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
# CrossPoint Companion Deep Link API
|
||||||
|
|
||||||
|
This document describes the deep link functionality that allows the CrossPoint Companion Android app to be launched from QR codes displayed on CrossPoint e-reader devices.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The CrossPoint firmware can generate QR codes containing deep link URLs. When scanned with a mobile device, these URLs launch the companion app directly to a specific tab and optionally auto-connect to the device.
|
||||||
|
|
||||||
|
## URL Scheme
|
||||||
|
|
||||||
|
```
|
||||||
|
crosspoint://<path>?<query_parameters>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Components
|
||||||
|
|
||||||
|
| Component | Description |
|
||||||
|
|-----------|-------------|
|
||||||
|
| `crosspoint://` | Custom URL scheme registered by the app |
|
||||||
|
| `<path>` | Target tab in the app (see [Path Mapping](#path-mapping)) |
|
||||||
|
| `<query_parameters>` | Optional device connection parameters |
|
||||||
|
|
||||||
|
## Path Mapping
|
||||||
|
|
||||||
|
The URL path determines which tab the app navigates to:
|
||||||
|
|
||||||
|
| Path | App Tab | Description |
|
||||||
|
|------|---------|-------------|
|
||||||
|
| `files` | Device | File browser for device storage |
|
||||||
|
| `library` | Library | Local book library |
|
||||||
|
| `lists` | Lists | Reading lists management |
|
||||||
|
| `settings` | Settings | App settings |
|
||||||
|
|
||||||
|
**Note:** Unknown paths default to the Library tab.
|
||||||
|
|
||||||
|
## Query Parameters
|
||||||
|
|
||||||
|
Query parameters provide device connection information for automatic connection:
|
||||||
|
|
||||||
|
| Parameter | Type | Default | Description |
|
||||||
|
|-----------|------|---------|-------------|
|
||||||
|
| `host` | string | *(required for auto-connect)* | IP address or hostname of the device |
|
||||||
|
| `port` | integer | `80` | HTTP API port |
|
||||||
|
| `wsPort` | integer | `81` | WebSocket port for file uploads |
|
||||||
|
|
||||||
|
## URL Examples
|
||||||
|
|
||||||
|
### Basic Navigation (No Auto-Connect)
|
||||||
|
|
||||||
|
Navigate to a specific tab without connecting to a device:
|
||||||
|
|
||||||
|
```
|
||||||
|
crosspoint://files
|
||||||
|
crosspoint://library
|
||||||
|
crosspoint://lists
|
||||||
|
crosspoint://settings
|
||||||
|
```
|
||||||
|
|
||||||
|
### Auto-Connect to Device
|
||||||
|
|
||||||
|
Navigate to Device tab and auto-connect:
|
||||||
|
|
||||||
|
```
|
||||||
|
crosspoint://files?host=192.168.1.100
|
||||||
|
crosspoint://files?host=192.168.1.100&port=80&wsPort=81
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Ports
|
||||||
|
|
||||||
|
Connect to a device with non-default ports:
|
||||||
|
|
||||||
|
```
|
||||||
|
crosspoint://files?host=192.168.1.100&port=8080&wsPort=8081
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hostname Instead of IP
|
||||||
|
|
||||||
|
```
|
||||||
|
crosspoint://files?host=crosspoint.local&port=80&wsPort=81
|
||||||
|
```
|
||||||
|
|
||||||
|
## Firmware Implementation
|
||||||
|
|
||||||
|
### QR Code Generation
|
||||||
|
|
||||||
|
The CrossPoint firmware should generate QR codes containing the deep link URL. Example format:
|
||||||
|
|
||||||
|
```
|
||||||
|
crosspoint://files?host=<device_ip>&port=<http_port>&wsPort=<ws_port>
|
||||||
|
```
|
||||||
|
|
||||||
|
Where:
|
||||||
|
- `<device_ip>` is the device's current IP address (e.g., from WiFi connection)
|
||||||
|
- `<http_port>` is the HTTP API port (default: 80)
|
||||||
|
- `<ws_port>` is the WebSocket port (default: 81)
|
||||||
|
|
||||||
|
### Example Firmware Code (C++)
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
String generateDeepLinkUrl(const char* path = "files") {
|
||||||
|
String url = "crosspoint://";
|
||||||
|
url += path;
|
||||||
|
url += "?host=";
|
||||||
|
url += WiFi.localIP().toString();
|
||||||
|
url += "&port=";
|
||||||
|
url += String(HTTP_PORT); // e.g., 80
|
||||||
|
url += "&wsPort=";
|
||||||
|
url += String(WS_PORT); // e.g., 81
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate QR code with:
|
||||||
|
// String url = generateDeepLinkUrl("files");
|
||||||
|
// displayQRCode(url);
|
||||||
|
```
|
||||||
|
|
||||||
|
## App Behavior
|
||||||
|
|
||||||
|
### Launch Scenarios
|
||||||
|
|
||||||
|
#### 1. App Not Running
|
||||||
|
|
||||||
|
When the app is launched via deep link:
|
||||||
|
1. App starts and parses the deep link URL
|
||||||
|
2. Navigates to the target tab
|
||||||
|
3. If device connection info is present and target is "files":
|
||||||
|
- Checks for existing device with matching IP
|
||||||
|
- If found: uses existing device (preserving user's custom name)
|
||||||
|
- If not found: creates temporary connection
|
||||||
|
- Attempts to connect automatically
|
||||||
|
|
||||||
|
#### 2. App Already Running
|
||||||
|
|
||||||
|
When a deep link is received while the app is open:
|
||||||
|
1. `onNewIntent` receives the new URL
|
||||||
|
2. Navigates to the target tab
|
||||||
|
3. Handles device connection (same as above)
|
||||||
|
|
||||||
|
### Device Matching Logic
|
||||||
|
|
||||||
|
When connecting via deep link:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Look up device by IP address in database
|
||||||
|
2. If device exists:
|
||||||
|
a. Check if ports match
|
||||||
|
b. If ports differ, update the stored device with new ports
|
||||||
|
c. Connect using the existing device (preserves custom name)
|
||||||
|
3. If device doesn't exist:
|
||||||
|
a. Create temporary Device object (not saved to database)
|
||||||
|
b. Connect using temporary device
|
||||||
|
c. Display as "CrossPoint (<ip>)"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
| Scenario | Behavior |
|
||||||
|
|----------|----------|
|
||||||
|
| Malformed URL | App opens to Library tab (default) |
|
||||||
|
| Unknown path | App opens to Library tab with warning logged |
|
||||||
|
| Invalid host format | Navigation succeeds, no auto-connect |
|
||||||
|
| Invalid port values | Default ports used (80, 81) |
|
||||||
|
| Connection failure | Error message displayed, user can retry |
|
||||||
|
| Device unreachable | Error message with device IP shown |
|
||||||
|
|
||||||
|
## Android Implementation Details
|
||||||
|
|
||||||
|
### Intent Filter (AndroidManifest.xml)
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="crosspoint" />
|
||||||
|
</intent-filter>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Classes
|
||||||
|
|
||||||
|
| Class | Purpose |
|
||||||
|
|-------|---------|
|
||||||
|
| `DeepLinkParser` | Parses URI into `DeepLinkData` |
|
||||||
|
| `DeepLinkData` | Data class holding parsed deep link info |
|
||||||
|
| `DeviceConnectionInfo` | Data class for host/port/wsPort |
|
||||||
|
| `MainActivity` | Handles incoming intents |
|
||||||
|
| `CrossPointApp` | Routes navigation based on deep link |
|
||||||
|
| `DeviceBrowserViewModel` | Handles `connectFromDeepLink()` |
|
||||||
|
|
||||||
|
### Data Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
QR Code Scan
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Android Intent (ACTION_VIEW)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
MainActivity.onCreate() / onNewIntent()
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
DeepLinkParser.parse(uri)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
DeepLinkData { targetTab, deviceConnection? }
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
CrossPointApp (LaunchedEffect)
|
||||||
|
│
|
||||||
|
├─► Navigate to targetTab
|
||||||
|
│
|
||||||
|
└─► If targetTab == Device && deviceConnection != null
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
DeviceBrowserScreen
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
DeviceBrowserViewModel.connectFromDeepLink()
|
||||||
|
│
|
||||||
|
├─► Check existing device by IP
|
||||||
|
├─► Update ports if needed
|
||||||
|
└─► Connect and load files
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation Rules
|
||||||
|
|
||||||
|
### Host Validation
|
||||||
|
|
||||||
|
Valid hosts:
|
||||||
|
- IPv4 addresses: `192.168.1.100`, `10.0.0.1`
|
||||||
|
- Hostnames: `crosspoint.local`, `my-device`
|
||||||
|
|
||||||
|
Invalid hosts (rejected):
|
||||||
|
- Empty strings
|
||||||
|
- Malformed IPs: `192.168.1.256`, `192.168.1`
|
||||||
|
- IPs with invalid octets
|
||||||
|
|
||||||
|
### Port Validation
|
||||||
|
|
||||||
|
- Valid range: 1-65535
|
||||||
|
- Out-of-range values default to 80 (HTTP) or 81 (WebSocket)
|
||||||
|
- Non-numeric values default to standard ports
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Manual Testing with ADB
|
||||||
|
|
||||||
|
Test deep links without a QR code using ADB:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Basic navigation
|
||||||
|
adb shell am start -a android.intent.action.VIEW -d "crosspoint://files"
|
||||||
|
adb shell am start -a android.intent.action.VIEW -d "crosspoint://library"
|
||||||
|
|
||||||
|
# With device connection
|
||||||
|
adb shell am start -a android.intent.action.VIEW -d "crosspoint://files?host=192.168.1.100"
|
||||||
|
adb shell am start -a android.intent.action.VIEW -d "crosspoint://files?host=192.168.1.100&port=80&wsPort=81"
|
||||||
|
|
||||||
|
# Test while app is running (onNewIntent)
|
||||||
|
adb shell am start -a android.intent.action.VIEW -d "crosspoint://settings"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Cases
|
||||||
|
|
||||||
|
1. **Valid deep link with connection info**
|
||||||
|
- URL: `crosspoint://files?host=192.168.1.100&port=80&wsPort=81`
|
||||||
|
- Expected: Opens Device tab, auto-connects to device
|
||||||
|
|
||||||
|
2. **Valid deep link without connection info**
|
||||||
|
- URL: `crosspoint://files`
|
||||||
|
- Expected: Opens Device tab, shows device selection
|
||||||
|
|
||||||
|
3. **Unknown path**
|
||||||
|
- URL: `crosspoint://unknown`
|
||||||
|
- Expected: Opens Library tab (default)
|
||||||
|
|
||||||
|
4. **Missing host parameter**
|
||||||
|
- URL: `crosspoint://files?port=80`
|
||||||
|
- Expected: Opens Device tab, no auto-connect
|
||||||
|
|
||||||
|
5. **Invalid host format**
|
||||||
|
- URL: `crosspoint://files?host=invalid..host`
|
||||||
|
- Expected: Opens Device tab, no auto-connect
|
||||||
|
|
||||||
|
6. **Device already exists in database**
|
||||||
|
- Precondition: Device with IP 192.168.1.100 saved as "My Reader"
|
||||||
|
- URL: `crosspoint://files?host=192.168.1.100`
|
||||||
|
- Expected: Connects using "My Reader" name
|
||||||
|
|
||||||
|
7. **Existing device with different ports**
|
||||||
|
- Precondition: Device saved with port=80, wsPort=81
|
||||||
|
- URL: `crosspoint://files?host=192.168.1.100&port=8080&wsPort=8081`
|
||||||
|
- Expected: Updates device ports, then connects
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
1. **Local Network Only**: Deep links should only contain local network addresses. The app does not validate this, but firmware should only generate URLs with local IPs.
|
||||||
|
|
||||||
|
2. **No Authentication**: The deep link does not include authentication. Device security relies on network-level access control.
|
||||||
|
|
||||||
|
3. **Temporary Devices**: Devices created from deep links (when no matching device exists) are not persisted, preventing automatic accumulation of device entries.
|
||||||
|
|
||||||
|
4. **No Sensitive Data**: Deep link URLs should not contain sensitive information as QR codes can be photographed.
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
| Version | Changes |
|
||||||
|
|---------|---------|
|
||||||
|
| 1.0.0 | Initial deep link support with `crosspoint://` scheme |
|
||||||
19
docs/comparison.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# CrossPoint vs XTOS
|
||||||
|
|
||||||
|
Below is like for like comparison of CrossPoint (version 0.5.1) and XTOS (version 3.1.1). CrossPoint is on the left,
|
||||||
|
XTOS is on the right. CrossPoint does not currently support all features of XTOS, so this comparison is just of key
|
||||||
|
features which both firmwares support.
|
||||||
|
|
||||||
|
## EPUB reading
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Menus
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
BIN
docs/cover.jpg
|
Before Width: | Height: | Size: 2.2 MiB |
603
docs/crosspoint-ef-features.md
Normal file
@ -0,0 +1,603 @@
|
|||||||
|
# CrossPoint-EF Branch Features
|
||||||
|
|
||||||
|
This document describes the features and enhancements unique to the `crosspoint-ef` branch, which diverged from CrossPoint Reader at version `0.15.0`.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The `crosspoint-ef` branch introduces significant new functionality including:
|
||||||
|
|
||||||
|
- **Dictionary Support** - Offline StarDict dictionary with word selection from reader
|
||||||
|
- **Bookmark System** - Per-book bookmarks with visual indicators and management
|
||||||
|
- **Quick Menu** - Fast access to common actions via power button
|
||||||
|
- **Library Search** - Search across all books by title, author, or filename
|
||||||
|
- **CSS Support** - Parse and apply CSS styles from EPUB files
|
||||||
|
- **Inline Images** - PNG and Baseline JPEG image rendering within EPUBs
|
||||||
|
- **Custom Fonts** - Atkinson Hyperlegible Next and Fern Micro accessibility fonts
|
||||||
|
- **Enhanced Web Server** - File management, companion app API, mDNS discovery
|
||||||
|
- **Reading Lists** - Create, manage, and pin custom book lists
|
||||||
|
- **Display Enhancements** - High contrast mode, bezel compensation, sleep screen improvements
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Major Features
|
||||||
|
|
||||||
|
### 1. Dictionary Support
|
||||||
|
|
||||||
|
Full offline dictionary lookup using the StarDict format with fast prefix-indexed search.
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Word selection directly from EPUB pages
|
||||||
|
- Manual word entry via on-screen keyboard
|
||||||
|
- Rich HTML formatting in definitions (bold, italic, lists)
|
||||||
|
- Multi-page definitions with pagination
|
||||||
|
- Synonym support
|
||||||
|
- Case-insensitive search with prefix optimization
|
||||||
|
|
||||||
|
**Access Methods:**
|
||||||
|
- **Quick Menu** → Dictionary
|
||||||
|
- **Power Button** (when configured to `Dictionary` action)
|
||||||
|
|
||||||
|
**Dictionary Format:**
|
||||||
|
- StarDict format with dictzip compression
|
||||||
|
- Files located at `/dictionaries/dict-data` on SD card:
|
||||||
|
- `dict-data.ifo` - Metadata
|
||||||
|
- `dict-data.idx` - Word index
|
||||||
|
- `dict-data.dict.dz` - Compressed definitions
|
||||||
|
- `dict-data.syn` - Synonyms (optional)
|
||||||
|
|
||||||
|
**Technical Implementation:**
|
||||||
|
- Files: `src/activities/dictionary/`, `lib/StarDict/`
|
||||||
|
- Prefix jump table for near-instant lookups
|
||||||
|
- On-demand chunk decompression using miniz
|
||||||
|
- HTML definition parsing with entity decoding
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Bookmark System
|
||||||
|
|
||||||
|
Per-book bookmark storage with visual indicators and dedicated management interface.
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Add/remove bookmarks from current page
|
||||||
|
- Visual folded-corner indicator on bookmarked pages
|
||||||
|
- Bookmarks tab in library showing all books with bookmarks
|
||||||
|
- Long-press to delete bookmarks
|
||||||
|
- Auto-generated bookmark names ("Chapter Title - Page X")
|
||||||
|
- Maximum 100 bookmarks per book
|
||||||
|
|
||||||
|
**Storage:**
|
||||||
|
- Binary file per book: `/.crosspoint/{epub_|txt_}<hash>/bookmarks.bin`
|
||||||
|
- Stores: name, spine index, content offset, page number, timestamp
|
||||||
|
|
||||||
|
**Technical Implementation:**
|
||||||
|
- Files: `src/BookmarkStore.cpp/.h`, `src/activities/home/BookmarkListActivity.cpp/.h`
|
||||||
|
- Bookmark identification by `spineIndex + contentOffset` (stable across re-renders)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Quick Menu
|
||||||
|
|
||||||
|
In-reader quick access menu for common actions, triggered by short power button press.
|
||||||
|
|
||||||
|
**Menu Options:**
|
||||||
|
1. **Dictionary** - Look up a word
|
||||||
|
2. **Bookmark** - Add/Remove bookmark (state-aware text)
|
||||||
|
3. **Clear Cache** - Free up storage space
|
||||||
|
4. **Settings** - Open settings menu
|
||||||
|
|
||||||
|
**Configuration:**
|
||||||
|
- Settings → Controls → Short Power Button Click → `Quick Menu`
|
||||||
|
|
||||||
|
**Technical Implementation:**
|
||||||
|
- File: `src/activities/util/QuickMenuActivity.cpp/.h`
|
||||||
|
- Renders overlay with navigation and selection
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Library Search with Character Picker
|
||||||
|
|
||||||
|
Search across all books using a dynamic character picker interface.
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Character picker with dynamically generated character set from library content
|
||||||
|
- Weighted search scoring:
|
||||||
|
- Title match: 100 points (+50 if at start)
|
||||||
|
- Author match: 80 points (+40 if at start)
|
||||||
|
- Path match: 30 points
|
||||||
|
- Results sorted by relevance score
|
||||||
|
- Special controls: SPC (space), ← (backspace), CLR (clear)
|
||||||
|
|
||||||
|
**Navigation:**
|
||||||
|
- Left/Right: Select character
|
||||||
|
- Confirm: Add character to query
|
||||||
|
- Up/Down: Switch between picker and results
|
||||||
|
|
||||||
|
**Technical Implementation:**
|
||||||
|
- Integrated in `src/activities/home/MyLibraryActivity.cpp`
|
||||||
|
- Search tab accessible from library tab bar
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. CSS Support for EPUBs
|
||||||
|
|
||||||
|
Parse and apply CSS styles from EPUB stylesheets.
|
||||||
|
|
||||||
|
**Supported Selectors:**
|
||||||
|
- Element selectors: `p`, `div`, `h1`, etc.
|
||||||
|
- Class selectors: `.classname`
|
||||||
|
- Combined selectors: `element.classname`
|
||||||
|
- Grouped selectors: `h1, h2, h3`
|
||||||
|
- Inline styles: `style="..."`
|
||||||
|
|
||||||
|
**Supported Properties:**
|
||||||
|
- `text-align` (left, center, right, justify)
|
||||||
|
- `font-style` (normal, italic)
|
||||||
|
- `font-weight` (normal, bold)
|
||||||
|
- `text-decoration` (underline)
|
||||||
|
- `text-indent`
|
||||||
|
- `margin-top`, `margin-bottom`
|
||||||
|
- `padding-top`, `padding-bottom`
|
||||||
|
|
||||||
|
**Cascade Order:**
|
||||||
|
1. Element styles
|
||||||
|
2. Class styles
|
||||||
|
3. Element.class styles
|
||||||
|
4. Inline styles
|
||||||
|
|
||||||
|
**Technical Implementation:**
|
||||||
|
- Files: `lib/Epub/Epub/css/CssParser.cpp/.h`, `CssStyle.h`
|
||||||
|
- CSS files parsed during EPUB loading
|
||||||
|
- Styles applied during HTML parsing via `ChapterHtmlSlimParser`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. Inline Image Support (PNG/Baseline JPEG)
|
||||||
|
|
||||||
|
Render embedded images within EPUB content.
|
||||||
|
|
||||||
|
**Supported Formats:**
|
||||||
|
- Baseline JPEG (.jpg, .jpeg)
|
||||||
|
- PNG (.png)
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Images decoded to 2-bit grayscale with dithering
|
||||||
|
- Image caching as `.pxc` files (2 bits per pixel, packed format)
|
||||||
|
- Row-by-row rendering to minimize memory usage
|
||||||
|
- Automatic scaling to fit page width
|
||||||
|
|
||||||
|
**Technical Implementation:**
|
||||||
|
- Files: `lib/Epub/Epub/blocks/ImageBlock.cpp/.h`
|
||||||
|
- Converters: `JpegToFramebufferConverter`, `PngToFramebufferConverter`
|
||||||
|
- Factory: `ImageDecoderFactory` routes to appropriate decoder
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. Custom Fonts
|
||||||
|
|
||||||
|
Additional accessibility-focused fonts beyond the standard Bookerly and Noto Sans.
|
||||||
|
|
||||||
|
**Available Fonts:**
|
||||||
|
1. **Atkinson Hyperlegible Next** - Designed for low-vision readers
|
||||||
|
2. **Fern Micro** - Optimized for small screens
|
||||||
|
|
||||||
|
**Font Sizes:**
|
||||||
|
- 12pt, 14pt, 16pt, 18pt for each font
|
||||||
|
- Full style support: Regular, Italic, Bold, Bold Italic
|
||||||
|
|
||||||
|
**Configuration:**
|
||||||
|
- Settings → Reader → Font Family → Custom
|
||||||
|
- Settings → Reader → Custom Font → [Select font]
|
||||||
|
- Settings → Reader → Fallback Font → [Bookerly/Noto Sans]
|
||||||
|
|
||||||
|
**Technical Implementation:**
|
||||||
|
- Files: `src/customFonts.cpp`, `src/fontIds.h`
|
||||||
|
- Font headers: `lib/EpdFont/builtinFonts/custom/`
|
||||||
|
- Conversion scripts: `lib/EpdFont/scripts/convert-builtin-fonts.sh`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8. Enhanced Web Server
|
||||||
|
|
||||||
|
Extended web server with file management operations and companion app support.
|
||||||
|
|
||||||
|
**File Operations:**
|
||||||
|
| Endpoint | Method | Description |
|
||||||
|
|----------|--------|-------------|
|
||||||
|
| `/api/files` | GET | List files with MD5 hashes for EPUBs |
|
||||||
|
| `/api/status` | GET | Device status (version, IP, heap, uptime) |
|
||||||
|
| `/api/archived` | GET | List archived books |
|
||||||
|
| `/api/hash` | GET | Compute/retrieve MD5 hash for sync |
|
||||||
|
| `/download` | GET | Download files |
|
||||||
|
| `/upload` | POST | Upload files (multipart) |
|
||||||
|
| `/delete` | POST | Delete files/folders |
|
||||||
|
| `/archive` | POST | Archive a book |
|
||||||
|
| `/unarchive` | POST | Restore archived book |
|
||||||
|
| `/rename` | POST | Rename files/folders |
|
||||||
|
| `/copy` | POST | Copy files/folders |
|
||||||
|
| `/move` | POST | Move files/folders |
|
||||||
|
| `/mkdir` | POST | Create folders |
|
||||||
|
| `/list` | GET/POST | Manage reading lists |
|
||||||
|
|
||||||
|
**WebSocket Upload (Port 81):**
|
||||||
|
- Fast binary uploads for large files
|
||||||
|
- Protocol: `START:<filename>:<size>:<path>` → `READY` → binary chunks → `DONE`
|
||||||
|
- Progress updates: `PROGRESS:<received>:<total>`
|
||||||
|
|
||||||
|
**mDNS Discovery:**
|
||||||
|
- Hostname: `crosspoint.local`
|
||||||
|
- Service: `_http._tcp` on port 80
|
||||||
|
- UDP discovery on port 8134
|
||||||
|
|
||||||
|
**Deep Link Support:**
|
||||||
|
- URL scheme: `crosspoint://<path>?host=<ip>&port=<port>&wsPort=<wsPort>`
|
||||||
|
- Paths: `files`, `library`, `lists`, `settings`
|
||||||
|
|
||||||
|
**Technical Implementation:**
|
||||||
|
- Files: `src/network/CrossPointWebServer.cpp/.h`
|
||||||
|
- MD5 Utils: `src/util/Md5Utils.cpp/.h`
|
||||||
|
- Docs: `docs/webserver-api-reference.md`, `docs/companion-app-deep-link-API.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 9. Reading Lists with Pinning
|
||||||
|
|
||||||
|
Create and manage custom book lists with pinning support.
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Create, load, delete lists
|
||||||
|
- Pin a list to show on home screen
|
||||||
|
- List contents displayed with book metadata
|
||||||
|
- Web API for list upload/download (CSV format)
|
||||||
|
|
||||||
|
**Storage:**
|
||||||
|
- Lists stored in `/.lists/` as `.bin` files
|
||||||
|
- CSV format for API: `order,title,author,path`
|
||||||
|
|
||||||
|
**Configuration:**
|
||||||
|
- Library → Lists tab → Long-press → Pin/Unpin
|
||||||
|
- Pinned list name shown on home screen Lists button
|
||||||
|
|
||||||
|
**Technical Implementation:**
|
||||||
|
- Files: `src/BookListStore.cpp/.h`
|
||||||
|
- Pinned list stored in `SETTINGS.pinnedListName`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 10. Display Enhancements
|
||||||
|
|
||||||
|
#### High Contrast Mode
|
||||||
|
|
||||||
|
System-wide display contrast adjustment for improved readability.
|
||||||
|
|
||||||
|
- **Normal mode:** Standard grayscale thresholds
|
||||||
|
- **High contrast mode:** Pushes mid-grays toward black/white
|
||||||
|
|
||||||
|
**Configuration:**
|
||||||
|
- Settings → Display → High Contrast → On/Off
|
||||||
|
|
||||||
|
#### Bezel Compensation
|
||||||
|
|
||||||
|
Compensate for physical screen edge defects with configurable margin.
|
||||||
|
|
||||||
|
- **Range:** 0-10 pixels
|
||||||
|
- **Edges:** Bottom, Top, Left, Right
|
||||||
|
- **Behavior:** Margin rotates with screen orientation
|
||||||
|
|
||||||
|
**Configuration:**
|
||||||
|
- Settings → Display → Bezel Compensation → [0-10]
|
||||||
|
- Settings → Display → Bezel Edge → [Bottom/Top/Left/Right]
|
||||||
|
|
||||||
|
#### Sleep Screen Improvements
|
||||||
|
|
||||||
|
Enhanced sleep screen with edge-aware color filling.
|
||||||
|
|
||||||
|
- **Edge luminance detection:** Samples edge pixels for dominant color
|
||||||
|
- **Letterbox fill:** Fills letterbox regions with edge colors for seamless appearance
|
||||||
|
- **Two-level caching:**
|
||||||
|
- Per-BMP cache: `.bmp.perim` files store edge luminance
|
||||||
|
- Book-level cache: `edge.bin` stores cover data
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 11. Recents View Improvements
|
||||||
|
|
||||||
|
Enhanced recent books display with metadata and management.
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- **Badges:** Extension and suffix tags (e.g., "epub", "X4")
|
||||||
|
- **Metadata display:** Title and author from EPUB
|
||||||
|
- **Remove from recents:** Long-press → Remove from Recents
|
||||||
|
- **Clear all:** Long-press → Clear All Recents
|
||||||
|
|
||||||
|
**Badge Configuration:**
|
||||||
|
- Extension badges: `.epub` → "epub", `.txt` → "txt"
|
||||||
|
- Suffix badges: `-x4` → "X4", `-x4p` → "X4P"
|
||||||
|
|
||||||
|
**Technical Implementation:**
|
||||||
|
- Files: `src/RecentBooksStore.cpp/.h`
|
||||||
|
- Badge config: `src/BadgeConfig.h`
|
||||||
|
- String utils: `src/util/StringUtils.cpp/.h`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 12. Enhanced Tab Bar
|
||||||
|
|
||||||
|
Unified tab bar with scrolling and overflow indicators.
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Horizontal scrolling when tabs exceed available width
|
||||||
|
- Overflow indicators (< >) when content extends beyond view
|
||||||
|
- Selected tab highlighting with underline
|
||||||
|
- Bullet cursors for focus mode
|
||||||
|
|
||||||
|
**Tabs:**
|
||||||
|
- Recent, Lists, Bookmarks, Search, Files
|
||||||
|
|
||||||
|
**Technical Implementation:**
|
||||||
|
- Files: `src/ScreenComponents.cpp/.h`
|
||||||
|
- Used in `MyLibraryActivity` for library navigation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 13. Progress Bar Status Bar
|
||||||
|
|
||||||
|
Additional status bar option showing visual progress bar.
|
||||||
|
|
||||||
|
**Options:**
|
||||||
|
- `Full w/ Progress Bar` - Full status bar with progress bar
|
||||||
|
- `Progress Bar` - Only progress bar, no other status info
|
||||||
|
|
||||||
|
**Configuration:**
|
||||||
|
- Settings → Display → Status Bar
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 14. Additional Settings
|
||||||
|
|
||||||
|
New configuration options unique to crosspoint-ef:
|
||||||
|
|
||||||
|
| Setting | Options | Description |
|
||||||
|
|---------|---------|-------------|
|
||||||
|
| Short Power Button Click | Ignore, Sleep, Page Turn, Dictionary, Quick Menu | Map power button to action |
|
||||||
|
| Bezel Compensation | 0-10 pixels | Edge defect compensation |
|
||||||
|
| Bezel Edge | Bottom, Top, Left, Right | Which edge to compensate |
|
||||||
|
| High Contrast | Off, On | System-wide contrast boost |
|
||||||
|
| Custom Font | [Font list] | Select custom font |
|
||||||
|
| Fallback Font | Bookerly, Noto Sans | Fallback for custom fonts |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 15. OPDS Browser Enhancements
|
||||||
|
|
||||||
|
Improved OPDS catalog browsing experience.
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- **Navigation history stack** - Back button navigates through visited feeds
|
||||||
|
- **Page skipping** - Hold Up/Down for 700ms to skip 23 items at once
|
||||||
|
- **Error retry mechanism** - Retry button on error screens with WiFi status check
|
||||||
|
- **HTTP Basic Authentication** - Username/password support for protected OPDS servers
|
||||||
|
|
||||||
|
**Configuration:**
|
||||||
|
- Settings → System → Calibre Settings → OPDS Server URL
|
||||||
|
- Settings → System → Calibre Settings → Username/Password
|
||||||
|
|
||||||
|
**Technical Implementation:**
|
||||||
|
- Files: `src/activities/browser/OpdsBookBrowserActivity.cpp`
|
||||||
|
- Authentication: `src/network/HttpDownloader.cpp`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 16. Development Tools
|
||||||
|
|
||||||
|
Scripts and utilities for firmware development.
|
||||||
|
|
||||||
|
**Scripts:**
|
||||||
|
| Script | Purpose |
|
||||||
|
|--------|---------|
|
||||||
|
| `pre_flash.py` | Displays "Flashing firmware..." screen during upload |
|
||||||
|
| `debugging_monitor.py` | Enhanced serial monitor with memory graphs and color output |
|
||||||
|
| `pio_helper.py` | Interactive PlatformIO workflow helper with presets |
|
||||||
|
| `version_hash.py` | Embeds git commit hash in dev builds |
|
||||||
|
| `build_html.py` | Minifies HTML files for web server |
|
||||||
|
|
||||||
|
**Firmware Flashing Screen:**
|
||||||
|
- Full-screen display during firmware upload
|
||||||
|
- Shows "Flashing firmware..." with version info
|
||||||
|
- Lock icon indicates USB port location
|
||||||
|
- Prevents accidental disconnection
|
||||||
|
|
||||||
|
**Debug/Memory Monitoring:**
|
||||||
|
- `DEBUG_MEMORY` build mode for heap tracking at activity transitions
|
||||||
|
- Periodic memory logging every 10 seconds (when Serial connected)
|
||||||
|
- Loop duration warnings when exceeding 50ms
|
||||||
|
- Detailed heap fragmentation info
|
||||||
|
|
||||||
|
**Technical Implementation:**
|
||||||
|
- Scripts: `scripts/pre_flash.py`, `scripts/debugging_monitor.py`, `scripts/pio_helper.py`
|
||||||
|
- Flash screen: `src/main.cpp` (lines 138-247)
|
||||||
|
- Memory monitoring: `src/main.cpp` (lines 549-562)
|
||||||
|
- Build config: `platformio.ini` (`debug_memory` environment)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 17. Power Management Enhancements
|
||||||
|
|
||||||
|
Optimizations for battery life and responsiveness.
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- **Auto-sleep prevention** - Background tasks (web server, OTA, downloads) prevent auto-sleep
|
||||||
|
- **USB connection detection** - Serial only starts when USB is connected (saves power)
|
||||||
|
- **Skip loop delay** - Activities can request faster loop execution for responsive HTTP handling
|
||||||
|
- **Power button release wait** - Prevents immediate wake if button is still held
|
||||||
|
|
||||||
|
**Technical Implementation:**
|
||||||
|
- Files: `src/main.cpp`, `src/activities/Activity.h`
|
||||||
|
- Methods: `preventAutoSleep()`, `skipLoopDelay()`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bug Fixes
|
||||||
|
|
||||||
|
### Unique to crosspoint-ef
|
||||||
|
|
||||||
|
1. **Grayscale state corruption fix** - Prevents ghosting and gray filter artifacts when anti-aliasing is enabled under memory pressure
|
||||||
|
2. **Memory optimization** - Graceful degradation when memory is low (skip anti-aliasing instead of corrupting display)
|
||||||
|
|
||||||
|
### Shared with 0.16.0
|
||||||
|
|
||||||
|
- Large EPUB indexing optimization (O(n²) → O(n))
|
||||||
|
- Settings validation on read
|
||||||
|
- Line break fixes (flush word before `<br/>`)
|
||||||
|
- Rotate origin in `drawImage()`
|
||||||
|
- Short-press power button to wakeup
|
||||||
|
- Add txt books to recent tab
|
||||||
|
- B&W filters for cover images
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Missing or Removed Features from 0.16.0
|
||||||
|
|
||||||
|
The following features are present in the upstream `0.16.0` release but are missing or were removed in `crosspoint-ef`:
|
||||||
|
|
||||||
|
### Removed Features
|
||||||
|
|
||||||
|
#### 1. KOReader Sync Support (Removed)
|
||||||
|
|
||||||
|
The entire KOReader sync functionality has been removed.
|
||||||
|
|
||||||
|
**What was removed:**
|
||||||
|
- `lib/KOReaderSync/` library (8 files deleted)
|
||||||
|
- Progress sync with KOReader sync server (`sync.koreader.rocks`)
|
||||||
|
- Document MD5 binary matching for progress synchronization
|
||||||
|
- KOReader credential storage
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
- Cannot sync reading progress with KOReader app
|
||||||
|
- Chapter Selection UI fixes for KOReader sync (#501) not applicable
|
||||||
|
|
||||||
|
**Files deleted:**
|
||||||
|
- `KOReaderSyncClient.cpp/.h`
|
||||||
|
- `KOReaderCredentialStore.cpp/.h`
|
||||||
|
- `KOReaderDocumentId.cpp/.h`
|
||||||
|
- `ProgressMapper.cpp/.h`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 2. Non-English Hyphenation Patterns (Removed)
|
||||||
|
|
||||||
|
Hyphenation pattern files for non-English languages have been removed.
|
||||||
|
|
||||||
|
**What was removed:**
|
||||||
|
- `hyph-es.trie.h` - Spanish hyphenation
|
||||||
|
- `hyph-de.trie.h` - German hyphenation
|
||||||
|
- `hyph-fr.trie.h` - French hyphenation
|
||||||
|
- `hyph-ru.trie.h` - Russian hyphenation
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
- Only English hyphenation patterns remain
|
||||||
|
- Non-English books will not hyphenate correctly
|
||||||
|
- Spanish hyphenation support (#558) not available
|
||||||
|
|
||||||
|
**Note:** These can be restored by copying the trie files from 0.16.0.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### 3. XTC/XTCH File Support (Removed)
|
||||||
|
|
||||||
|
Support for the XTC/XTCH proprietary format has been removed.
|
||||||
|
|
||||||
|
**What was removed:**
|
||||||
|
- Author extraction from XTC/XTCH files (#563)
|
||||||
|
- XTC format handling in file browsers
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
- XTC/XTCH files cannot be read
|
||||||
|
- Author metadata not extracted from these formats
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Missing Bug Fixes
|
||||||
|
|
||||||
|
The following bug fixes from 0.16.0 have not been applied to crosspoint-ef:
|
||||||
|
|
||||||
|
| PR | Description | Impact |
|
||||||
|
|----|-------------|--------|
|
||||||
|
| #567 | Multi-line keyboard entry | Long text input truncated with "..." instead of wrapping |
|
||||||
|
| #569 | Italics on image alt text | Image alt placeholders don't render in italics |
|
||||||
|
| #564 | Front layout in mapLabels() | Button mapping may be incorrect in some layouts |
|
||||||
|
| #486 | Relative position on settings change | Reader may jump to different location when settings change |
|
||||||
|
| #501 | Chapter Selection UI (KOReader) | N/A - KOReader sync removed |
|
||||||
|
| #529 | KOReader MD5 binary matching | N/A - KOReader sync removed |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Missing UX Enhancements
|
||||||
|
|
||||||
|
| PR | Description | Impact |
|
||||||
|
|----|-------------|--------|
|
||||||
|
| #451 | Page turn on button press | When long-press chapter skip is disabled, 0.16.0 allows page turn on button press; crosspoint-ef does not |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Different Implementation: OTA Updates
|
||||||
|
|
||||||
|
The OTA update mechanism uses a different implementation:
|
||||||
|
|
||||||
|
| Aspect | crosspoint-ef | 0.16.0 |
|
||||||
|
|--------|---------------|--------|
|
||||||
|
| HTTP Client | Arduino `HTTPClient` | ESP-IDF `esp_http_client` |
|
||||||
|
| OTA Library | Arduino `Update` | ESP-IDF `esp_https_ota` |
|
||||||
|
| Memory Management | Standard | Improved with custom buffer handling |
|
||||||
|
|
||||||
|
**Impact:**
|
||||||
|
- Both implementations work, but 0.16.0's ESP-IDF approach may be more memory-efficient
|
||||||
|
- Consider evaluating 0.16.0's OTA rework (#509) for potential adoption
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Recommendation for Missing Features
|
||||||
|
|
||||||
|
**High Priority to Cherry-pick:**
|
||||||
|
1. Multi-line keyboard entry (#567) - Improves UX for long inputs
|
||||||
|
2. Front layout fix (#564) - Bug fix for button mapping
|
||||||
|
3. Relative position on settings change (#486) - Improves reader UX
|
||||||
|
|
||||||
|
**Medium Priority:**
|
||||||
|
4. Restore hyphenation patterns for non-English languages
|
||||||
|
5. Italics on image alt (#569) - Minor visual improvement
|
||||||
|
6. Page turn on button press (#451) - UX enhancement
|
||||||
|
|
||||||
|
**Evaluate:**
|
||||||
|
7. OTA rework (#509) - Compare implementations for memory benefits
|
||||||
|
8. KOReader sync - Restore if sync functionality is desired
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Summary
|
||||||
|
|
||||||
|
| Feature | Primary Files |
|
||||||
|
|---------|---------------|
|
||||||
|
| Dictionary | `src/activities/dictionary/`, `lib/StarDict/` |
|
||||||
|
| Bookmarks | `src/BookmarkStore.*`, `src/activities/home/BookmarkListActivity.*` |
|
||||||
|
| Quick Menu | `src/activities/util/QuickMenuActivity.*` |
|
||||||
|
| Search | `src/activities/home/MyLibraryActivity.cpp` |
|
||||||
|
| CSS | `lib/Epub/Epub/css/` |
|
||||||
|
| Images | `lib/Epub/Epub/blocks/ImageBlock.*`, `lib/Epub/Epub/converters/` |
|
||||||
|
| Custom Fonts | `src/customFonts.cpp`, `lib/EpdFont/builtinFonts/custom/` |
|
||||||
|
| Web Server | `src/network/CrossPointWebServer.*`, `src/util/Md5Utils.*` |
|
||||||
|
| Lists | `src/BookListStore.*` |
|
||||||
|
| Settings | `src/CrossPointSettings.*` |
|
||||||
|
| Tab Bar | `src/ScreenComponents.*` |
|
||||||
|
| Recents | `src/RecentBooksStore.*`, `src/BadgeConfig.h` |
|
||||||
|
| OPDS Browser | `src/activities/browser/OpdsBookBrowserActivity.*` |
|
||||||
|
| Dev Tools | `scripts/pre_flash.py`, `scripts/debugging_monitor.py`, `scripts/pio_helper.py` |
|
||||||
|
| Power Management | `src/main.cpp`, `src/activities/Activity.h` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Version Information
|
||||||
|
|
||||||
|
- **Base version:** 0.15.0
|
||||||
|
- **Branch:** crosspoint-ef
|
||||||
|
- **Commits since divergence:** 90+
|
||||||
|
- **Files changed:** 250+
|
||||||
555
docs/crosspoint-ef-user-guide.md
Normal file
@ -0,0 +1,555 @@
|
|||||||
|
# CrossPoint-EF User Guide Supplement
|
||||||
|
|
||||||
|
This guide covers the additional features available in the `crosspoint-ef` branch. For basic operation, refer to the main [User Guide](../USER_GUIDE.md).
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Dictionary](#dictionary)
|
||||||
|
- [Bookmarks](#bookmarks)
|
||||||
|
- [Quick Menu](#quick-menu)
|
||||||
|
- [Library Search](#library-search)
|
||||||
|
- [Reading Lists](#reading-lists)
|
||||||
|
- [Display Settings](#display-settings)
|
||||||
|
- [Web Server Features](#web-server-features)
|
||||||
|
- [Custom Fonts](#custom-fonts)
|
||||||
|
- [Additional Settings](#additional-settings)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dictionary
|
||||||
|
|
||||||
|
The dictionary feature provides offline word lookup while reading.
|
||||||
|
|
||||||
|
### Setup
|
||||||
|
|
||||||
|
1. Download a StarDict dictionary (English-English dictionary provided as `dict-en-en.zip`)
|
||||||
|
2. Extract the dictionary files to `/dictionaries/dict-data/` on your SD card
|
||||||
|
3. You should have these files:
|
||||||
|
- `dict-data.ifo`
|
||||||
|
- `dict-data.idx`
|
||||||
|
- `dict-data.dict.dz`
|
||||||
|
- `dict-data.syn` (optional, for synonyms)
|
||||||
|
|
||||||
|
### Using the Dictionary
|
||||||
|
|
||||||
|
#### Method 1: Quick Menu
|
||||||
|
|
||||||
|
1. While reading, press the **Power** button briefly (requires Quick Menu to be configured)
|
||||||
|
2. Select **Dictionary** from the menu
|
||||||
|
3. Choose **Select from Screen** or **Enter a Word**
|
||||||
|
|
||||||
|
#### Method 2: Direct Power Button Access
|
||||||
|
|
||||||
|
1. Go to **Settings → Controls → Short Power Button Click**
|
||||||
|
2. Set to **Dictionary**
|
||||||
|
3. While reading, press the **Power** button briefly to open the dictionary
|
||||||
|
|
||||||
|
### Selecting a Word from the Page
|
||||||
|
|
||||||
|
1. Choose **Select from Screen** from the dictionary menu
|
||||||
|
2. The current page will display with word selection enabled
|
||||||
|
3. Use **Left/Right** to move between words
|
||||||
|
4. Use **Up/Down** to jump between lines
|
||||||
|
5. Press **Confirm** to look up the selected word
|
||||||
|
6. Press **Back** to cancel
|
||||||
|
|
||||||
|
### Viewing Definitions
|
||||||
|
|
||||||
|
- Definitions display with rich formatting (bold, italic, lists)
|
||||||
|
- Use **Left/Right** or **Volume Up/Down** to navigate between pages if the definition is long
|
||||||
|
- Press **Confirm** to search for another word
|
||||||
|
- Press **Back** to return to your book
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bookmarks
|
||||||
|
|
||||||
|
Create and manage bookmarks within your books.
|
||||||
|
|
||||||
|
### Adding a Bookmark
|
||||||
|
|
||||||
|
#### Method 1: Quick Menu
|
||||||
|
|
||||||
|
1. Press the **Power** button briefly (requires Quick Menu to be configured)
|
||||||
|
2. Select **Add Bookmark** (or **Remove Bookmark** if already bookmarked)
|
||||||
|
|
||||||
|
#### Method 2: Settings Configuration
|
||||||
|
|
||||||
|
1. Go to **Settings → Controls → Short Power Button Click**
|
||||||
|
2. Set to **Quick Menu**
|
||||||
|
3. Use Quick Menu to toggle bookmarks
|
||||||
|
|
||||||
|
### Bookmark Indicator
|
||||||
|
|
||||||
|
When a page is bookmarked, a small folded corner triangle appears in the top-right corner of the page.
|
||||||
|
|
||||||
|
### Viewing Bookmarks
|
||||||
|
|
||||||
|
1. Go to **Home → Library**
|
||||||
|
2. Select the **Bookmarks** tab
|
||||||
|
3. You'll see a list of books that have bookmarks
|
||||||
|
4. Select a book to view its bookmarks
|
||||||
|
5. Select a bookmark to jump to that location
|
||||||
|
|
||||||
|
### Deleting Bookmarks
|
||||||
|
|
||||||
|
1. Open a book's bookmark list (from Bookmarks tab)
|
||||||
|
2. Navigate to the bookmark you want to delete
|
||||||
|
3. **Long-press Confirm** (hold for about 1 second)
|
||||||
|
4. Confirm deletion when prompted
|
||||||
|
|
||||||
|
### Bookmark Naming
|
||||||
|
|
||||||
|
Bookmarks are automatically named based on:
|
||||||
|
- Chapter title and page number (e.g., "Chapter 3 - Page 42")
|
||||||
|
- Just page number if no chapter title (e.g., "Page 15")
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Menu
|
||||||
|
|
||||||
|
Fast access to common actions while reading.
|
||||||
|
|
||||||
|
### Enabling Quick Menu
|
||||||
|
|
||||||
|
1. Go to **Settings → Controls → Short Power Button Click**
|
||||||
|
2. Select **Quick Menu**
|
||||||
|
|
||||||
|
### Using Quick Menu
|
||||||
|
|
||||||
|
1. While reading, press the **Power** button briefly
|
||||||
|
2. Navigate with **Up/Down** or **Left/Right**
|
||||||
|
3. Press **Confirm** to select an option
|
||||||
|
4. Press **Back** to close the menu
|
||||||
|
|
||||||
|
### Quick Menu Options
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| **Dictionary** | Look up a word |
|
||||||
|
| **Add/Remove Bookmark** | Toggle bookmark on current page |
|
||||||
|
| **Clear Cache** | Free up storage space |
|
||||||
|
| **Settings** | Open settings menu |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Library Search
|
||||||
|
|
||||||
|
Search your library by title, author, or filename.
|
||||||
|
|
||||||
|
### Accessing Search
|
||||||
|
|
||||||
|
1. Go to **Home → Library**
|
||||||
|
2. Select the **Search** tab
|
||||||
|
3. Or from any tab, scroll to the bottom and select **Search...**
|
||||||
|
|
||||||
|
### Using the Character Picker
|
||||||
|
|
||||||
|
The search uses a character picker interface:
|
||||||
|
|
||||||
|
1. **Left/Right** - Move between characters
|
||||||
|
2. **Confirm** - Add character to search query
|
||||||
|
3. **SPC** - Add a space
|
||||||
|
4. **←** - Delete last character (backspace)
|
||||||
|
5. **CLR** - Clear entire query
|
||||||
|
|
||||||
|
### Navigating Results
|
||||||
|
|
||||||
|
1. After entering characters, results appear below
|
||||||
|
2. Press **Down** to move from character picker to results
|
||||||
|
3. **Left/Right** to navigate results
|
||||||
|
4. **Confirm** to open a book
|
||||||
|
5. **Up** to return to character picker
|
||||||
|
|
||||||
|
### Search Scoring
|
||||||
|
|
||||||
|
Results are ranked by relevance:
|
||||||
|
- Title matches rank highest
|
||||||
|
- Author matches rank second
|
||||||
|
- Filename matches rank lowest
|
||||||
|
- Matches at the start of a field rank higher
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Reading Lists
|
||||||
|
|
||||||
|
Create custom book lists for organizing your library.
|
||||||
|
|
||||||
|
### Viewing Lists
|
||||||
|
|
||||||
|
1. Go to **Home → Library**
|
||||||
|
2. Select the **Lists** tab
|
||||||
|
3. Available lists are displayed
|
||||||
|
|
||||||
|
### Opening a List
|
||||||
|
|
||||||
|
1. Navigate to a list name
|
||||||
|
2. Press **Confirm** to view the list contents
|
||||||
|
3. Select a book to start reading
|
||||||
|
|
||||||
|
### Pinning a List
|
||||||
|
|
||||||
|
Pin a list to quickly access it from the home screen:
|
||||||
|
|
||||||
|
1. In the Lists tab, navigate to a list
|
||||||
|
2. **Long-press Confirm** to open the action menu
|
||||||
|
3. Select **Pin List**
|
||||||
|
|
||||||
|
The pinned list name will appear on the Lists button on the home screen.
|
||||||
|
|
||||||
|
### Unpinning a List
|
||||||
|
|
||||||
|
1. Navigate to the pinned list
|
||||||
|
2. **Long-press Confirm**
|
||||||
|
3. Select **Unpin List**
|
||||||
|
|
||||||
|
### Deleting a List
|
||||||
|
|
||||||
|
1. Navigate to a list
|
||||||
|
2. **Long-press Confirm**
|
||||||
|
3. Select **Delete List**
|
||||||
|
4. Confirm deletion
|
||||||
|
|
||||||
|
### Creating Lists via Web Server
|
||||||
|
|
||||||
|
Lists can be created and uploaded via the web server API. See [Web Server Features](#web-server-features).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Display Settings
|
||||||
|
|
||||||
|
### High Contrast Mode
|
||||||
|
|
||||||
|
Increases contrast across the entire UI for better readability.
|
||||||
|
|
||||||
|
1. Go to **Settings → Display → High Contrast**
|
||||||
|
2. Set to **On** or **Off**
|
||||||
|
|
||||||
|
When enabled, mid-gray tones are pushed toward black or white.
|
||||||
|
|
||||||
|
### Bezel Compensation
|
||||||
|
|
||||||
|
Compensate for physical screen edge defects (common on some devices).
|
||||||
|
|
||||||
|
1. Go to **Settings → Display → Bezel Compensation**
|
||||||
|
2. Set value from **0** (disabled) to **10** pixels
|
||||||
|
3. If compensation is enabled, select **Bezel Edge**:
|
||||||
|
- **Bottom** - Default, compensates bottom edge
|
||||||
|
- **Top** - Compensates top edge
|
||||||
|
- **Left** - Compensates left edge
|
||||||
|
- **Right** - Compensates right edge
|
||||||
|
|
||||||
|
The compensation margin automatically rotates with screen orientation.
|
||||||
|
|
||||||
|
### Status Bar Options
|
||||||
|
|
||||||
|
Additional status bar display options:
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| None | No status bar |
|
||||||
|
| No Progress | Status bar without reading progress |
|
||||||
|
| Full w/ Percentage | Status bar with percentage progress |
|
||||||
|
| Full w/ Progress Bar | Status bar with visual progress bar |
|
||||||
|
| Progress Bar | Only progress bar, no other info |
|
||||||
|
|
||||||
|
Configure at **Settings → Display → Status Bar**.
|
||||||
|
|
||||||
|
### Sleep Screen Cover Filter
|
||||||
|
|
||||||
|
When using book cover as sleep screen:
|
||||||
|
|
||||||
|
| Filter | Effect |
|
||||||
|
|--------|--------|
|
||||||
|
| None | Grayscale image as-is |
|
||||||
|
| Contrast | Black and white only (no grays) |
|
||||||
|
| Inverted | Inverted black and white |
|
||||||
|
|
||||||
|
Configure at **Settings → Display → Sleep Screen Cover Filter**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Web Server Features
|
||||||
|
|
||||||
|
The web server provides extended file management and companion app support.
|
||||||
|
|
||||||
|
### Starting the Web Server
|
||||||
|
|
||||||
|
1. Go to **Home → File Transfer**
|
||||||
|
2. Select a WiFi network or create a hotspot
|
||||||
|
3. The web server URL will be displayed
|
||||||
|
|
||||||
|
### File Management
|
||||||
|
|
||||||
|
Access the file manager at `http://<device-ip>/files`
|
||||||
|
|
||||||
|
**Available Operations:**
|
||||||
|
- **Upload** - Upload files via drag-and-drop or file picker
|
||||||
|
- **Download** - Download files to your computer
|
||||||
|
- **Delete** - Remove files and folders
|
||||||
|
- **Rename** - Rename files and folders
|
||||||
|
- **Create Folder** - Create new directories
|
||||||
|
- **Archive/Unarchive** - Archive books (preserves reading progress)
|
||||||
|
- **Copy/Move** - Copy or move files and folders
|
||||||
|
|
||||||
|
### API Access
|
||||||
|
|
||||||
|
The web server provides a JSON API for programmatic access:
|
||||||
|
|
||||||
|
| Endpoint | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `GET /api/status` | Device status |
|
||||||
|
| `GET /api/files?path=/` | List files |
|
||||||
|
| `GET /api/archived` | List archived books |
|
||||||
|
| `GET /api/hash?path=/book.epub` | Get MD5 hash |
|
||||||
|
|
||||||
|
### mDNS Discovery
|
||||||
|
|
||||||
|
The device advertises itself as `crosspoint.local` on your network.
|
||||||
|
|
||||||
|
### Companion App Support
|
||||||
|
|
||||||
|
The web server supports the CrossPoint Companion Android app:
|
||||||
|
|
||||||
|
1. **QR Code** - Scan the QR code displayed on the web server screen
|
||||||
|
2. **Deep Links** - URLs like `crosspoint://files?host=192.168.1.100` open the app directly
|
||||||
|
|
||||||
|
### Managing Reading Lists via API
|
||||||
|
|
||||||
|
**Get all lists:**
|
||||||
|
```
|
||||||
|
GET /list
|
||||||
|
```
|
||||||
|
|
||||||
|
**Get specific list:**
|
||||||
|
```
|
||||||
|
GET /list?name=MyList
|
||||||
|
```
|
||||||
|
|
||||||
|
**Upload a list:**
|
||||||
|
```
|
||||||
|
POST /list?action=upload&name=MyList
|
||||||
|
Content-Type: text/plain
|
||||||
|
|
||||||
|
1,Book Title,Author Name,/path/to/book.epub
|
||||||
|
2,Another Book,Another Author,/path/to/another.epub
|
||||||
|
```
|
||||||
|
|
||||||
|
**Delete a list:**
|
||||||
|
```
|
||||||
|
POST /list?action=delete&name=MyList
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Custom Fonts
|
||||||
|
|
||||||
|
Two additional accessibility-focused fonts are available.
|
||||||
|
|
||||||
|
### Available Custom Fonts
|
||||||
|
|
||||||
|
1. **Atkinson Hyperlegible Next** - Designed for low-vision readers with high character differentiation
|
||||||
|
2. **Fern Micro** - Optimized for small screens
|
||||||
|
|
||||||
|
### Enabling Custom Fonts
|
||||||
|
|
||||||
|
1. Go to **Settings → Reader → Font Family**
|
||||||
|
2. Select **Custom**
|
||||||
|
3. Go to **Settings → Reader → Custom Font**
|
||||||
|
4. Select your preferred font
|
||||||
|
|
||||||
|
### Fallback Font
|
||||||
|
|
||||||
|
When using custom fonts, set a fallback for missing glyphs:
|
||||||
|
|
||||||
|
1. Go to **Settings → Reader → Fallback Font**
|
||||||
|
2. Choose **Bookerly** or **Noto Sans**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Additional Settings
|
||||||
|
|
||||||
|
### Short Power Button Actions
|
||||||
|
|
||||||
|
Configure what happens when you briefly press the Power button:
|
||||||
|
|
||||||
|
| Option | Action |
|
||||||
|
|--------|--------|
|
||||||
|
| Ignore | No action (default) |
|
||||||
|
| Sleep | Put device to sleep |
|
||||||
|
| Page Turn | Turn to next page |
|
||||||
|
| Dictionary | Open dictionary |
|
||||||
|
| Quick Menu | Open quick menu |
|
||||||
|
|
||||||
|
Configure at **Settings → Controls → Short Power Button Click**.
|
||||||
|
|
||||||
|
### Long-press Chapter Skip
|
||||||
|
|
||||||
|
Control side button long-press behavior:
|
||||||
|
|
||||||
|
- **On** (default) - Long-press Volume buttons to skip chapters
|
||||||
|
- **Off** - Long-press scrolls a page instead
|
||||||
|
|
||||||
|
Configure at **Settings → Controls → Long-press Chapter Skip**.
|
||||||
|
|
||||||
|
### Hyphenation
|
||||||
|
|
||||||
|
Enable word hyphenation for justified text:
|
||||||
|
|
||||||
|
1. Go to **Settings → Reader → Hyphenation**
|
||||||
|
2. Set to **On**
|
||||||
|
|
||||||
|
Hyphenation patterns are available for multiple languages (English, German, French, Spanish, Russian, etc.).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recents View Enhancements
|
||||||
|
|
||||||
|
### Badges
|
||||||
|
|
||||||
|
Books in the Recent tab display badges showing:
|
||||||
|
- **File extension** (epub, txt, md)
|
||||||
|
- **Suffix tags** (X4, X4P for files with `-x4` or `-x4p` suffixes)
|
||||||
|
|
||||||
|
### Removing from Recents
|
||||||
|
|
||||||
|
1. Navigate to a book in the Recent tab
|
||||||
|
2. **Long-press Confirm**
|
||||||
|
3. Select **Remove from Recents**
|
||||||
|
|
||||||
|
### Clearing All Recents
|
||||||
|
|
||||||
|
1. Navigate to any book in the Recent tab
|
||||||
|
2. **Long-press Confirm**
|
||||||
|
3. Select **Clear All Recents**
|
||||||
|
4. Confirm the action
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tab Navigation
|
||||||
|
|
||||||
|
The library uses a unified tab bar for navigation.
|
||||||
|
|
||||||
|
### Tabs Available
|
||||||
|
|
||||||
|
| Tab | Contents |
|
||||||
|
|-----|----------|
|
||||||
|
| Recent | Recently opened books |
|
||||||
|
| Lists | Custom reading lists |
|
||||||
|
| Bookmarks | Books with bookmarks |
|
||||||
|
| Search | Search all books |
|
||||||
|
| Files | File browser |
|
||||||
|
|
||||||
|
### Navigating Tabs
|
||||||
|
|
||||||
|
When the tab bar is focused:
|
||||||
|
- **Left/Right** - Switch between tabs
|
||||||
|
- **Down** - Enter the selected tab's content
|
||||||
|
- **Confirm** - Same as Down
|
||||||
|
|
||||||
|
### Tab Overflow
|
||||||
|
|
||||||
|
When tabs don't fit on screen:
|
||||||
|
- **<** indicator appears on left when more tabs exist to the left
|
||||||
|
- **>** indicator appears on right when more tabs exist to the right
|
||||||
|
- Scroll continues automatically when navigating past visible tabs
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Inline Images
|
||||||
|
|
||||||
|
EPUBs with embedded images now display them inline with text.
|
||||||
|
|
||||||
|
### Supported Formats
|
||||||
|
|
||||||
|
- JPEG (.jpg, .jpeg)
|
||||||
|
- PNG (.png)
|
||||||
|
|
||||||
|
### Image Display
|
||||||
|
|
||||||
|
- Images are automatically scaled to fit the page width
|
||||||
|
- Images are converted to 4-level grayscale with dithering
|
||||||
|
- First load may be slower as images are processed
|
||||||
|
- Subsequent loads use cached versions
|
||||||
|
|
||||||
|
### Image Cache
|
||||||
|
|
||||||
|
Processed images are cached as `.pxc` files in the book's cache directory for faster loading.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Dictionary Not Working
|
||||||
|
|
||||||
|
1. Verify dictionary files are in `/dictionaries/dict-data/`
|
||||||
|
2. Check that all required files exist (.ifo, .idx, .dict.dz)
|
||||||
|
3. File names must match exactly (case-sensitive)
|
||||||
|
|
||||||
|
### Bookmarks Not Saving
|
||||||
|
|
||||||
|
1. Ensure SD card is not write-protected
|
||||||
|
2. Check available storage space
|
||||||
|
3. Bookmarks are saved per-book in `/.crosspoint/`
|
||||||
|
|
||||||
|
### Search Not Finding Books
|
||||||
|
|
||||||
|
1. Search only indexes books in the library
|
||||||
|
2. Ensure books have proper EPUB metadata
|
||||||
|
3. Try searching by filename if metadata is missing
|
||||||
|
|
||||||
|
### Images Not Displaying
|
||||||
|
|
||||||
|
1. Only PNG and JPEG formats are supported
|
||||||
|
2. Very large images may fail to load due to memory constraints
|
||||||
|
3. Check for sufficient free memory (multiple large books open may exhaust memory)
|
||||||
|
|
||||||
|
### Web Server Connection Issues
|
||||||
|
|
||||||
|
1. Ensure device and computer are on the same network
|
||||||
|
2. Try accessing via IP address instead of `crosspoint.local`
|
||||||
|
3. Check that firewall isn't blocking port 80
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Keyboard Shortcuts Summary
|
||||||
|
|
||||||
|
### In Reader
|
||||||
|
|
||||||
|
| Button | Action |
|
||||||
|
|--------|--------|
|
||||||
|
| Left/Volume Up | Previous page |
|
||||||
|
| Right/Volume Down | Next page |
|
||||||
|
| Left (hold) | Previous chapter |
|
||||||
|
| Right (hold) | Next chapter |
|
||||||
|
| Back | Return to library |
|
||||||
|
| Back (hold) | Return to home |
|
||||||
|
| Confirm | Open chapter selection |
|
||||||
|
| Power (brief) | Configured action (Quick Menu/Dictionary/Sleep/Page Turn) |
|
||||||
|
|
||||||
|
### In Quick Menu
|
||||||
|
|
||||||
|
| Button | Action |
|
||||||
|
|--------|--------|
|
||||||
|
| Up/Down/Left/Right | Navigate options |
|
||||||
|
| Confirm | Select option |
|
||||||
|
| Back | Close menu |
|
||||||
|
|
||||||
|
### In Word Selection
|
||||||
|
|
||||||
|
| Button | Action |
|
||||||
|
|--------|--------|
|
||||||
|
| Left/Right | Move between words |
|
||||||
|
| Up/Down | Move between lines |
|
||||||
|
| Confirm | Look up word |
|
||||||
|
| Back | Cancel |
|
||||||
|
|
||||||
|
### In Library Tabs
|
||||||
|
|
||||||
|
| Button | Action |
|
||||||
|
|--------|--------|
|
||||||
|
| Left/Right | Switch tabs (when tab bar focused) |
|
||||||
|
| Up/Down | Navigate within tab |
|
||||||
|
| Confirm | Select item / Enter tab |
|
||||||
|
| Confirm (hold) | Action menu |
|
||||||
|
| Back | Go back / Exit to home |
|
||||||
221
docs/file-formats.md
Normal file
@ -0,0 +1,221 @@
|
|||||||
|
# File Formats
|
||||||
|
|
||||||
|
## `book.bin`
|
||||||
|
|
||||||
|
### Version 3
|
||||||
|
|
||||||
|
ImHex Pattern:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
import std.mem;
|
||||||
|
import std.string;
|
||||||
|
import std.core;
|
||||||
|
|
||||||
|
// === Configuration ===
|
||||||
|
#define EXPECTED_VERSION 3
|
||||||
|
#define MAX_STRING_LENGTH 65535
|
||||||
|
|
||||||
|
// === String Structure ===
|
||||||
|
|
||||||
|
struct String {
|
||||||
|
u32 length [[hidden, comment("String byte length")]];
|
||||||
|
if (length > MAX_STRING_LENGTH) {
|
||||||
|
std::warning(std::format("Unusually large string length: {} bytes", length));
|
||||||
|
}
|
||||||
|
char data[length] [[comment("UTF-8 string data")]];
|
||||||
|
} [[sealed, format("format_string"), comment("Length-prefixed UTF-8 string")]];
|
||||||
|
|
||||||
|
fn format_string(String s) {
|
||||||
|
return s.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// === Metadata Structure ===
|
||||||
|
|
||||||
|
struct Metadata {
|
||||||
|
String title [[comment("Book title")]];
|
||||||
|
String author [[comment("Book author")]];
|
||||||
|
String coverItemHref [[comment("Path to cover image")]];
|
||||||
|
String textReferenceHref [[comment("Path to guided first text reference")]];
|
||||||
|
} [[comment("Book metadata information")]];
|
||||||
|
|
||||||
|
// === Spine Entry Structure ===
|
||||||
|
|
||||||
|
struct SpineEntry {
|
||||||
|
String href [[comment("Resource path")]];
|
||||||
|
u32 cumulativeSize [[comment("Cumulative size in bytes"), color("FF6B6B")]];
|
||||||
|
s16 tocIndex [[comment("Index into TOC (-1 if none)"), color("4ECDC4")]];
|
||||||
|
} [[comment("Spine entry defining reading order")]];
|
||||||
|
|
||||||
|
// === TOC Entry Structure ===
|
||||||
|
|
||||||
|
struct TocEntry {
|
||||||
|
String title [[comment("Chapter/section title")]];
|
||||||
|
String href [[comment("Resource path")]];
|
||||||
|
String anchor [[comment("Fragment identifier")]];
|
||||||
|
u8 level [[comment("Nesting level (0-255)"), color("95E1D3")]];
|
||||||
|
s16 spineIndex [[comment("Index into spine (-1 if none)"), color("F38181")]];
|
||||||
|
} [[comment("Table of contents entry")]];
|
||||||
|
|
||||||
|
// === Book Bin Structure ===
|
||||||
|
|
||||||
|
struct BookBin {
|
||||||
|
// Header
|
||||||
|
u8 version [[comment("Format version"), color("FFD93D")]];
|
||||||
|
|
||||||
|
// Version validation
|
||||||
|
if (version != EXPECTED_VERSION) {
|
||||||
|
std::error(std::format("Unsupported version: {} (expected {})", version, EXPECTED_VERSION));
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 lutOffset [[comment("Offset to lookup tables"), color("6BCB77")]];
|
||||||
|
u16 spineCount [[comment("Number of spine entries"), color("4D96FF")]];
|
||||||
|
u16 tocCount [[comment("Number of TOC entries"), color("FF6B9D")]];
|
||||||
|
|
||||||
|
// Metadata section
|
||||||
|
Metadata metadata [[comment("Book metadata")]];
|
||||||
|
|
||||||
|
// Validate LUT offset alignment
|
||||||
|
u32 currentOffset = $;
|
||||||
|
if (currentOffset != lutOffset) {
|
||||||
|
std::warning(std::format("LUT offset mismatch: expected 0x{:X}, got 0x{:X}", lutOffset, currentOffset));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup Tables
|
||||||
|
u32 spineLut[spineCount] [[comment("Spine entry offsets"), color("4D96FF")]];
|
||||||
|
u32 tocLut[tocCount] [[comment("TOC entry offsets"), color("FF6B9D")]];
|
||||||
|
|
||||||
|
// Data Entries
|
||||||
|
SpineEntry spines[spineCount] [[comment("Spine entries (reading order)")]];
|
||||||
|
TocEntry toc[tocCount] [[comment("Table of contents entries")]];
|
||||||
|
};
|
||||||
|
|
||||||
|
// === File Parsing ===
|
||||||
|
|
||||||
|
BookBin book @ 0x00;
|
||||||
|
|
||||||
|
// Validate we've consumed the entire file
|
||||||
|
u32 fileSize = std::mem::size();
|
||||||
|
u32 parsedSize = $;
|
||||||
|
|
||||||
|
if (parsedSize != fileSize) {
|
||||||
|
std::warning(std::format("Unparsed data detected: {} bytes remaining at offset 0x{:X}", fileSize - parsedSize, parsedSize));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## `section.bin`
|
||||||
|
|
||||||
|
### Version 8
|
||||||
|
|
||||||
|
ImHex Pattern:
|
||||||
|
|
||||||
|
```c++
|
||||||
|
import std.mem;
|
||||||
|
import std.string;
|
||||||
|
import std.core;
|
||||||
|
|
||||||
|
// === Configuration ===
|
||||||
|
#define EXPECTED_VERSION 8
|
||||||
|
#define MAX_STRING_LENGTH 65535
|
||||||
|
|
||||||
|
// === String Structure ===
|
||||||
|
|
||||||
|
struct String {
|
||||||
|
u32 length [[hidden, comment("String byte length")]];
|
||||||
|
if (length > MAX_STRING_LENGTH) {
|
||||||
|
std::warning(std::format("Unusually large string length: {} bytes", length));
|
||||||
|
}
|
||||||
|
char data[length] [[comment("UTF-8 string data")]];
|
||||||
|
} [[sealed, format("format_string"), comment("Length-prefixed UTF-8 string")]];
|
||||||
|
|
||||||
|
fn format_string(String s) {
|
||||||
|
return s.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// === Page Structure ===
|
||||||
|
|
||||||
|
enum StorageType : u8 {
|
||||||
|
PageLine = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
enum WordStyle : u8 {
|
||||||
|
REGULAR = 0,
|
||||||
|
BOLD = 1,
|
||||||
|
ITALIC = 2,
|
||||||
|
BOLD_ITALIC = 3
|
||||||
|
};
|
||||||
|
|
||||||
|
enum BlockStyle : u8 {
|
||||||
|
JUSTIFIED = 0,
|
||||||
|
LEFT_ALIGN = 1,
|
||||||
|
CENTER_ALIGN = 2,
|
||||||
|
RIGHT_ALIGN = 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PageLine {
|
||||||
|
s16 xPos;
|
||||||
|
s16 yPos;
|
||||||
|
u16 wordCount;
|
||||||
|
String words[wordCount];
|
||||||
|
u16 wordXPos[wordCount];
|
||||||
|
WordStyle wordStyle[wordCount];
|
||||||
|
BlockStyle blockStyle;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PageElement {
|
||||||
|
u8 pageElementType;
|
||||||
|
if (pageElementType == 1) {
|
||||||
|
PageLine pageLine [[inline]];
|
||||||
|
} else {
|
||||||
|
std::error(std::format("Unknown page element type: {}", pageElementType));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Page {
|
||||||
|
u16 elementCount;
|
||||||
|
PageElement elements[elementCount] [[inline]];
|
||||||
|
};
|
||||||
|
|
||||||
|
// === Section Bin Structure ===
|
||||||
|
|
||||||
|
struct SectionBin {
|
||||||
|
// Header
|
||||||
|
u8 version [[comment("Format version"), color("FFD93D")]];
|
||||||
|
|
||||||
|
// Version validation
|
||||||
|
if (version != EXPECTED_VERSION) {
|
||||||
|
std::error(std::format("Unsupported version: {} (expected {})", version, EXPECTED_VERSION));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache busting parameters
|
||||||
|
s32 fontId;
|
||||||
|
float lineCompression;
|
||||||
|
bool extraParagraphSpacing;
|
||||||
|
u16 viewportWidth;
|
||||||
|
u16 vieportHeight;
|
||||||
|
u16 pageCount;
|
||||||
|
u32 lutOffset;
|
||||||
|
|
||||||
|
Page page[pageCount];
|
||||||
|
|
||||||
|
// Validate LUT offset alignment
|
||||||
|
u32 currentOffset = $;
|
||||||
|
if (currentOffset != lutOffset) {
|
||||||
|
std::warning(std::format("LUT offset mismatch: expected 0x{:X}, got 0x{:X}", lutOffset, currentOffset));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup Tables
|
||||||
|
u32 lut[pageCount];
|
||||||
|
};
|
||||||
|
|
||||||
|
// === File Parsing ===
|
||||||
|
|
||||||
|
SectionBin book @ 0x00;
|
||||||
|
|
||||||
|
// Validate we've consumed the entire file
|
||||||
|
u32 fileSize = std::mem::size();
|
||||||
|
u32 parsedSize = $;
|
||||||
|
|
||||||
|
if (parsedSize != fileSize) {
|
||||||
|
std::warning(std::format("Unparsed data detected: {} bytes remaining at offset 0x{:X}", fileSize - parsedSize, parsedSize));
|
||||||
|
}
|
||||||
|
```
|
||||||
66
docs/hyphenation-trie-format.md
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# Hypher Binary Tries
|
||||||
|
|
||||||
|
CrossPoint embeds the exact binary automata produced by
|
||||||
|
[Typst's `hypher`](https://github.com/typst/hypher).
|
||||||
|
|
||||||
|
## File layout
|
||||||
|
|
||||||
|
Each `.bin` blob is a single self-contained automaton:
|
||||||
|
|
||||||
|
```
|
||||||
|
uint32_t root_addr_be; // big-endian offset of the root node
|
||||||
|
uint8_t levels[]; // shared "levels" tape (dist/score pairs)
|
||||||
|
uint8_t nodes[]; // node records packed back-to-back
|
||||||
|
```
|
||||||
|
|
||||||
|
The size of the `levels` tape is implicit. Individual nodes reference slices
|
||||||
|
inside that tape via 12-bit offsets, so no additional pointers are required.
|
||||||
|
|
||||||
|
### Node encoding
|
||||||
|
|
||||||
|
Every node starts with a single control byte:
|
||||||
|
|
||||||
|
- Bit 7 – set when the node stores scores (`levels`).
|
||||||
|
- Bits 5-6 – stride of the target deltas (1, 2, or 3 bytes, big-endian).
|
||||||
|
- Bits 0-4 – transition count (values ≥ 31 spill into an extra byte).
|
||||||
|
|
||||||
|
If the `levels` flag is set, two more bytes follow. Together they encode a
|
||||||
|
12-bit offset into the global `levels` tape and a 4-bit length. Each byte in the
|
||||||
|
levels tape packs a distance/score pair as `dist * 10 + score`, where `dist`
|
||||||
|
counts how many UTF-8 bytes we advanced since the previous digit.
|
||||||
|
|
||||||
|
After the optional levels header come the transition labels (one byte per edge)
|
||||||
|
followed by the signed target deltas. Targets are stored as relative offsets
|
||||||
|
from the current node address. Deltas up to ±128 fit in a single byte, larger
|
||||||
|
distances grow to 2 or 3 bytes. The runtime walks the transitions with a simple
|
||||||
|
linear scan and materializes the absolute address by adding the decoded delta
|
||||||
|
to the current node’s base.
|
||||||
|
|
||||||
|
## Embedding blobs into the firmware
|
||||||
|
|
||||||
|
The helper script `scripts/generate_hyphenation_trie.py` acts as a thin
|
||||||
|
wrapper: it reads the hypher-generated `.bin` files, formats them as `constexpr`
|
||||||
|
byte arrays, and emits headers under
|
||||||
|
`lib/Epub/Epub/hyphenation/generated/`. Each header defines the raw data plus a
|
||||||
|
`SerializedHyphenationPatterns` descriptor so the reader can keep the automaton
|
||||||
|
in flash.
|
||||||
|
|
||||||
|
To refresh the firmware assets after updating the `.bin` files, run:
|
||||||
|
|
||||||
|
```
|
||||||
|
./scripts/generate_hyphenation_trie.py \
|
||||||
|
--input lib/Epub/Epub/hyphenation/tries/en.bin \
|
||||||
|
--output lib/Epub/Epub/hyphenation/generated/hyph-en.trie.h
|
||||||
|
|
||||||
|
./scripts/generate_hyphenation_trie.py \
|
||||||
|
--input lib/Epub/Epub/hyphenation/tries/fr.bin \
|
||||||
|
--output lib/Epub/Epub/hyphenation/generated/hyph-fr.trie.h
|
||||||
|
|
||||||
|
./scripts/generate_hyphenation_trie.py \
|
||||||
|
--input lib/Epub/Epub/hyphenation/tries/de.bin \
|
||||||
|
--output lib/Epub/Epub/hyphenation/generated/hyph-de.trie.h
|
||||||
|
|
||||||
|
./scripts/generate_hyphenation_trie.py \
|
||||||
|
--input lib/Epub/Epub/hyphenation/tries/ru.bin \
|
||||||
|
--output lib/Epub/Epub/hyphenation/generated/hyph-ru.trie.h
|
||||||
|
```
|
||||||
BIN
docs/images/comparison/chapter-menu.jpg
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
docs/images/comparison/menu.jpg
Normal file
|
After Width: | Height: | Size: 1.3 MiB |
BIN
docs/images/comparison/reading-1.jpg
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
docs/images/comparison/reading-2.jpg
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
docs/images/comparison/reading-3.jpg
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
docs/images/cover.jpg
Normal file
|
After Width: | Height: | Size: 899 KiB |
BIN
docs/images/wifi/webserver_files.png
Normal file
|
After Width: | Height: | Size: 193 KiB |
BIN
docs/images/wifi/webserver_homepage.png
Normal file
|
After Width: | Height: | Size: 144 KiB |
BIN
docs/images/wifi/webserver_upload.png
Normal file
|
After Width: | Height: | Size: 135 KiB |
BIN
docs/images/wifi/wifi_connected.jpeg
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
BIN
docs/images/wifi/wifi_networks.jpeg
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
docs/images/wifi/wifi_password.jpeg
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
86
docs/troubleshooting.md
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
# Troubleshooting
|
||||||
|
|
||||||
|
This document show most common issues and possible solutions while using the device features.
|
||||||
|
|
||||||
|
- [Troubleshooting](#troubleshooting)
|
||||||
|
- [Images Not Displaying in EPUBs](#images-not-displaying-in-epubs)
|
||||||
|
- [Cannot See the Device on the Network](#cannot-see-the-device-on-the-network)
|
||||||
|
- [Connection Drops or Times Out](#connection-drops-or-times-out)
|
||||||
|
- [Upload Fails](#upload-fails)
|
||||||
|
- [Saved Password Not Working](#saved-password-not-working)
|
||||||
|
|
||||||
|
### Images Not Displaying in EPUBs
|
||||||
|
|
||||||
|
**Problem:** Some images in EPUB books show as placeholders like "[Image: filename.jpg]" instead of the actual image
|
||||||
|
|
||||||
|
**Possible Causes:**
|
||||||
|
|
||||||
|
1. **Progressive JPEGs are not supported**
|
||||||
|
- The device uses a minimal JPEG decoder optimized for embedded systems
|
||||||
|
- Progressive/multi-scan JPEGs cannot be decoded due to memory constraints
|
||||||
|
- This affects some professionally published EPUBs, especially maps and high-quality photos
|
||||||
|
- **Workaround:** Use Calibre or another EPUB editor to convert progressive JPEGs to baseline JPEGs
|
||||||
|
|
||||||
|
2. **Unsupported image format**
|
||||||
|
- Only JPEG and PNG images are supported
|
||||||
|
- Other formats (GIF, WebP, SVG graphics) will show placeholders
|
||||||
|
|
||||||
|
3. **Image extraction failed**
|
||||||
|
- The image file may be corrupted or the EPUB structure malformed
|
||||||
|
- Try re-downloading the EPUB or converting it with Calibre
|
||||||
|
|
||||||
|
**How to check if an image is progressive JPEG:**
|
||||||
|
|
||||||
|
```python
|
||||||
|
from PIL import Image
|
||||||
|
print(Image.open("image.jpg").info.get('progressive', 0))
|
||||||
|
# Output: 1 = progressive (not supported), 0 = baseline (supported)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cannot See the Device on the Network
|
||||||
|
|
||||||
|
**Problem:** Browser shows "Cannot connect" or "Site can't be reached"
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
|
||||||
|
1. Verify both devices are on the **same WiFi network**
|
||||||
|
- Check your computer/phone WiFi settings
|
||||||
|
- Confirm the CrossPoint Reader shows "Connected" status
|
||||||
|
2. Double-check the IP address
|
||||||
|
- Make sure you typed it correctly
|
||||||
|
- Include `http://` at the beginning
|
||||||
|
3. Try disabling VPN if you're using one
|
||||||
|
4. Some networks have "client isolation" enabled - check with your network administrator
|
||||||
|
|
||||||
|
### Connection Drops or Times Out
|
||||||
|
|
||||||
|
**Problem:** WiFi connection is unstable
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
|
||||||
|
1. Move closer to the WiFi router
|
||||||
|
2. Check signal strength on the device (should be at least `||` or better)
|
||||||
|
3. Avoid interference from other devices
|
||||||
|
4. Try a different WiFi network if available
|
||||||
|
|
||||||
|
### Upload Fails
|
||||||
|
|
||||||
|
**Problem:** File upload doesn't complete or shows an error
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
|
||||||
|
1. Ensure the file is a valid `.epub` file
|
||||||
|
2. Check that the SD card has enough free space
|
||||||
|
3. Try uploading a smaller file first to test
|
||||||
|
4. Refresh the browser page and try again
|
||||||
|
|
||||||
|
### Saved Password Not Working
|
||||||
|
|
||||||
|
**Problem:** Device fails to connect with saved credentials
|
||||||
|
|
||||||
|
**Solutions:**
|
||||||
|
|
||||||
|
1. When connection fails, you'll be prompted to "Forget Network"
|
||||||
|
2. Select **Yes** to remove the saved password
|
||||||
|
3. Reconnect and enter the password again
|
||||||
|
4. Choose to save the new password
|
||||||
334
docs/webserver-api-reference.md
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
# CrossPointWebServer API Reference
|
||||||
|
|
||||||
|
Source: `src/network/CrossPointWebServer.cpp` and `CrossPointWebServer.h`
|
||||||
|
|
||||||
|
## Server Configuration
|
||||||
|
|
||||||
|
- HTTP port: 80 (default)
|
||||||
|
- WebSocket port: 81 (default)
|
||||||
|
- WiFi sleep disabled for responsiveness
|
||||||
|
- Supports both STA (station) and AP (access point) modes
|
||||||
|
|
||||||
|
## HTTP Endpoints
|
||||||
|
|
||||||
|
### GET /
|
||||||
|
**Handler:** `handleRoot()`
|
||||||
|
**Response:** HTML homepage from `HomePageHtml` (generated from `html/HomePage.html`)
|
||||||
|
**Content-Type:** text/html
|
||||||
|
|
||||||
|
### GET /files
|
||||||
|
**Handler:** `handleFileList()`
|
||||||
|
**Response:** HTML file browser page from `FilesPageHtml` (generated from `html/FilesPage.html`)
|
||||||
|
**Content-Type:** text/html
|
||||||
|
|
||||||
|
### GET /api/status
|
||||||
|
**Handler:** `handleStatus()`
|
||||||
|
**Response:** JSON device status
|
||||||
|
**Content-Type:** application/json
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": "CROSSPOINT_VERSION",
|
||||||
|
"ip": "192.168.x.x",
|
||||||
|
"mode": "AP" | "STA",
|
||||||
|
"rssi": -50, // 0 in AP mode
|
||||||
|
"freeHeap": 123456,
|
||||||
|
"uptime": 3600 // seconds
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### GET /api/files
|
||||||
|
**Handler:** `handleFileListData()`
|
||||||
|
**Query params:**
|
||||||
|
- `path` (optional): Directory path, defaults to "/"
|
||||||
|
- `showHidden` (optional): "true" to show dot-files (except .crosspoint)
|
||||||
|
**Response:** JSON array of files
|
||||||
|
**Content-Type:** application/json
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{"name": "book.epub", "size": 123456, "isDirectory": false, "isEpub": true},
|
||||||
|
{"name": "folder", "size": 0, "isDirectory": true, "isEpub": false}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
**Notes:**
|
||||||
|
- Hidden by default: files starting with ".", "System Volume Information", "XTCache"
|
||||||
|
- Always hidden: ".crosspoint" (internal cache folder)
|
||||||
|
- Streamed response (chunked encoding) to reduce memory usage
|
||||||
|
|
||||||
|
### GET /api/archived
|
||||||
|
**Handler:** `handleArchivedList()`
|
||||||
|
**Response:** JSON array of archived books
|
||||||
|
**Content-Type:** application/json
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{"filename": "archived_file.epub", "originalPath": "/Books/archived_file.epub"}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
**Notes:** Uses `BookManager::listArchivedBooks()` and `BookManager::getArchivedBookOriginalPath()`
|
||||||
|
|
||||||
|
### GET /download
|
||||||
|
**Handler:** `handleDownload()`
|
||||||
|
**Query params:**
|
||||||
|
- `path` (required): File path to download
|
||||||
|
**Response:** File binary with Content-Disposition attachment header
|
||||||
|
**Content-Type:** application/octet-stream
|
||||||
|
**Errors:**
|
||||||
|
- 400: Missing path, path is directory
|
||||||
|
- 403: Hidden/system file, protected item
|
||||||
|
- 404: File not found
|
||||||
|
**Notes:**
|
||||||
|
- Streams in 4KB chunks
|
||||||
|
- Updates `totalBytesDownloaded` and `totalFilesDownloaded` stats
|
||||||
|
- Security: rejects paths with "..", files starting with ".", protected items
|
||||||
|
|
||||||
|
### POST /upload
|
||||||
|
**Handler:** `handleUpload()` (multipart handler), `handleUploadPost()` (response handler)
|
||||||
|
**Query params:**
|
||||||
|
- `path` (optional): Upload directory, defaults to "/"
|
||||||
|
**Form data:** multipart/form-data with file
|
||||||
|
**Response:** "File uploaded successfully: filename" or error message
|
||||||
|
**Notes:**
|
||||||
|
- Uses 4KB write buffer for SD card efficiency
|
||||||
|
- Overwrites existing files
|
||||||
|
- Clears epub cache after upload via `clearEpubCacheIfNeeded()`
|
||||||
|
- Updates `totalBytesUploaded` and `totalFilesUploaded` stats
|
||||||
|
- Logs progress every 100KB
|
||||||
|
|
||||||
|
### POST /mkdir
|
||||||
|
**Handler:** `handleCreateFolder()`
|
||||||
|
**Form params:**
|
||||||
|
- `name` (required): Folder name
|
||||||
|
- `path` (optional): Parent directory, defaults to "/"
|
||||||
|
**Response:** "Folder created: foldername" or error
|
||||||
|
**Errors:**
|
||||||
|
- 400: Missing name, empty name, folder exists
|
||||||
|
|
||||||
|
### POST /delete
|
||||||
|
**Handler:** `handleDelete()`
|
||||||
|
**Form params:**
|
||||||
|
- `path` (required): Item path to delete
|
||||||
|
- `type` (optional): "file" (default) or "folder"
|
||||||
|
- `archived` (optional): "true" for archived books
|
||||||
|
**Response:** "Deleted successfully" or error
|
||||||
|
**Errors:**
|
||||||
|
- 400: Missing path, root directory, folder not empty
|
||||||
|
- 403: Hidden/system file, protected item
|
||||||
|
- 404: Item not found
|
||||||
|
- 500: Delete failed
|
||||||
|
**Notes:**
|
||||||
|
- For files: uses `BookManager::deleteBook()` which handles cache and recent books cleanup
|
||||||
|
- For folders: must be empty first
|
||||||
|
- For archived: passes filename to `BookManager::deleteBook(filename, true)`
|
||||||
|
|
||||||
|
### POST /archive
|
||||||
|
**Handler:** `handleArchive()`
|
||||||
|
**Form params:**
|
||||||
|
- `path` (required): Book path to archive
|
||||||
|
**Response:** "Book archived successfully" or error
|
||||||
|
**Notes:** Uses `BookManager::archiveBook()`
|
||||||
|
|
||||||
|
### POST /unarchive
|
||||||
|
**Handler:** `handleUnarchive()`
|
||||||
|
**Form params:**
|
||||||
|
- `filename` (required): Archived book filename
|
||||||
|
**Response:** JSON with original path
|
||||||
|
**Content-Type:** application/json
|
||||||
|
```json
|
||||||
|
{"success": true, "originalPath": "/Books/book.epub"}
|
||||||
|
```
|
||||||
|
**Notes:** Uses `BookManager::unarchiveBook()` and `BookManager::getArchivedBookOriginalPath()`
|
||||||
|
|
||||||
|
### POST /rename
|
||||||
|
**Handler:** `handleRename()`
|
||||||
|
**Form params:**
|
||||||
|
- `path` (required): Current item path
|
||||||
|
- `newName` (required): New name (filename only, no path separators)
|
||||||
|
**Response:** "Renamed successfully" or error
|
||||||
|
**Errors:**
|
||||||
|
- 400: Missing params, empty name, name contains "/" or "\\", root directory, destination exists
|
||||||
|
- 403: System file, protected item
|
||||||
|
- 404: Source not found
|
||||||
|
- 500: Rename failed
|
||||||
|
**Notes:**
|
||||||
|
- Renames in place (same directory, new name)
|
||||||
|
- Uses `SdMan.rename()`
|
||||||
|
- Clears epub cache after rename via `clearEpubCacheIfNeeded()`
|
||||||
|
|
||||||
|
### POST /copy
|
||||||
|
**Handler:** `handleCopy()`
|
||||||
|
**Form params:**
|
||||||
|
- `srcPath` (required): Source path
|
||||||
|
- `destPath` (required): Full destination path (including new name)
|
||||||
|
**Response:** "Copied successfully" or error
|
||||||
|
**Errors:**
|
||||||
|
- 400: Missing params, root directory, destination exists, copy into self
|
||||||
|
- 403: System file, protected item
|
||||||
|
- 404: Source not found
|
||||||
|
- 500: Copy failed
|
||||||
|
**Notes:**
|
||||||
|
- Uses `copyFile()` for files (4KB buffer chunks)
|
||||||
|
- Uses `copyFolder()` for recursive directory copy
|
||||||
|
- Skips hidden files in folder copy
|
||||||
|
|
||||||
|
### POST /move
|
||||||
|
**Handler:** `handleMove()`
|
||||||
|
**Form params:**
|
||||||
|
- `srcPath` (required): Source path
|
||||||
|
- `destPath` (required): Full destination path (including new name)
|
||||||
|
**Response:** "Moved successfully" or error
|
||||||
|
**Errors:** Same as copy
|
||||||
|
**Notes:**
|
||||||
|
- First attempts atomic `SdMan.rename()` (fast)
|
||||||
|
- Falls back to copy+delete if rename fails
|
||||||
|
- Uses `deleteFolderRecursive()` for folder cleanup
|
||||||
|
|
||||||
|
### GET /list
|
||||||
|
**Handler:** `handleListGet()`
|
||||||
|
**Query params:**
|
||||||
|
- `name` (optional): Specific list name to retrieve
|
||||||
|
**Response:** JSON array of lists (if no name) or single list details (if name specified)
|
||||||
|
**Content-Type:** application/json
|
||||||
|
|
||||||
|
**Response (all lists - no name param):**
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{"name": "MyReadingList", "path": "/.lists/MyReadingList.bin", "bookCount": 5},
|
||||||
|
{"name": "Favorites", "path": "/.lists/Favorites.bin", "bookCount": 12}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response (specific list - with name param):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "MyReadingList",
|
||||||
|
"path": "/.lists/MyReadingList.bin",
|
||||||
|
"books": [
|
||||||
|
{"order": 1, "title": "The Great Gatsby", "author": "F. Scott Fitzgerald", "path": "/Books/gatsby.epub"},
|
||||||
|
{"order": 2, "title": "1984", "author": "George Orwell", "path": "/Books/1984.epub"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Errors:**
|
||||||
|
- 404: List not found (when `name` specified but doesn't exist)
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Lists are stored in `/.lists/` directory as `.bin` files
|
||||||
|
- Uses `BookListStore::listAllLists()` and `BookListStore::loadList()`
|
||||||
|
|
||||||
|
### POST /list
|
||||||
|
**Handler:** `handleListPost()`
|
||||||
|
**Query params:**
|
||||||
|
- `action` (required): "upload" or "delete"
|
||||||
|
- `name` (required): List name (without .bin extension)
|
||||||
|
**Request body (for upload):** CSV text with one book per line
|
||||||
|
**Content-Type:** text/plain (for upload body)
|
||||||
|
|
||||||
|
**Input format (POST body for upload action):**
|
||||||
|
```
|
||||||
|
1,The Great Gatsby,F. Scott Fitzgerald,/Books/gatsby.epub
|
||||||
|
2,1984,George Orwell,/Books/1984.epub
|
||||||
|
3,Pride and Prejudice,Jane Austen,/Books/pride.epub
|
||||||
|
```
|
||||||
|
Format: `order,title,author,path` (one per line)
|
||||||
|
|
||||||
|
**Response (upload success):**
|
||||||
|
```json
|
||||||
|
{"success": true, "path": "/.lists/MyReadingList.bin"}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response (delete success):**
|
||||||
|
```json
|
||||||
|
{"success": true}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Errors:**
|
||||||
|
- 400: Missing action or name parameter, empty name, failed to parse list data
|
||||||
|
- 404: List not found (for delete action)
|
||||||
|
- 500: Failed to save/delete list
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Lists are stored as binary files in `/.lists/` directory
|
||||||
|
- Uses `BookListStore::parseFromText()`, `BookListStore::saveList()`, `BookListStore::deleteList()`
|
||||||
|
- Order field determines display order (books are sorted by order when loaded)
|
||||||
|
- Overwrites existing list with same name on upload
|
||||||
|
|
||||||
|
## WebSocket Protocol (port 81)
|
||||||
|
|
||||||
|
**Handler:** `onWebSocketEvent()` via `wsEventCallback()` trampoline
|
||||||
|
|
||||||
|
### Upload Protocol
|
||||||
|
|
||||||
|
1. Client connects
|
||||||
|
2. Server: (implicit connection acknowledgment)
|
||||||
|
3. Client TEXT: `START:<filename>:<size>:<path>`
|
||||||
|
4. Server TEXT: `READY` or `ERROR:<message>`
|
||||||
|
5. Client BIN: file data chunks (any size, recommend 64KB)
|
||||||
|
6. Server TEXT: `PROGRESS:<received>:<total>` (every 64KB or at end)
|
||||||
|
7. Server TEXT: `DONE` or `ERROR:<message>`
|
||||||
|
|
||||||
|
### Events
|
||||||
|
- `WStype_CONNECTED`: Client connected, logs connection
|
||||||
|
- `WStype_DISCONNECTED`: Cleanup incomplete upload, delete partial file
|
||||||
|
- `WStype_TEXT`: Parse control messages (START)
|
||||||
|
- `WStype_BIN`: Write file data, send progress, complete upload
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
- Faster than HTTP multipart for large files
|
||||||
|
- Direct binary writes to SD card
|
||||||
|
- Clears epub cache after upload
|
||||||
|
- Updates traffic statistics
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
### Protected Items (HIDDEN_ITEMS[])
|
||||||
|
- "System Volume Information"
|
||||||
|
- "XTCache"
|
||||||
|
|
||||||
|
### Always Hidden
|
||||||
|
- ".crosspoint" (internal cache)
|
||||||
|
|
||||||
|
### Security Checks Applied To
|
||||||
|
- `/delete`: Rejects dot-files (unless archived), protected items
|
||||||
|
- `/download`: Rejects dot-files, protected items, path traversal (..)
|
||||||
|
- `/rename`: Rejects dot-files, protected items
|
||||||
|
- `/copy`: Rejects dot-files, protected items
|
||||||
|
- `/move`: Rejects dot-files, protected items
|
||||||
|
|
||||||
|
## Helper Functions
|
||||||
|
|
||||||
|
### clearEpubCacheIfNeeded(filePath)
|
||||||
|
- Location: anonymous namespace at top of file
|
||||||
|
- Clears epub cache if file ends with ".epub"
|
||||||
|
- Uses `Epub(filePath, "/.crosspoint").clearCache()`
|
||||||
|
- Called by: upload, WebSocket upload, rename
|
||||||
|
|
||||||
|
### scanFiles(path, callback, showHidden)
|
||||||
|
- Iterates directory, calls callback for each FileInfo
|
||||||
|
- Yields and resets watchdog during iteration
|
||||||
|
- Filters hidden items based on showHidden flag
|
||||||
|
|
||||||
|
### copyFile(srcPath, destPath) / copyFolder(srcPath, destPath)
|
||||||
|
- 4KB buffer for file copy
|
||||||
|
- Recursive for folders
|
||||||
|
- Returns bool success
|
||||||
|
|
||||||
|
### deleteFolderRecursive(path)
|
||||||
|
- Static helper for move fallback
|
||||||
|
- Recursively deletes contents then directory
|
||||||
|
|
||||||
|
## Traffic Statistics (mutable, updated from const handlers)
|
||||||
|
- `totalBytesUploaded`
|
||||||
|
- `totalBytesDownloaded`
|
||||||
|
- `totalFilesUploaded`
|
||||||
|
- `totalFilesDownloaded`
|
||||||
|
- `serverStartTime` (for uptime calculation)
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
- `<WebServer.h>` - ESP32 HTTP server
|
||||||
|
- `<WebSocketsServer.h>` - WebSocket support
|
||||||
|
- `<ArduinoJson.h>` - JSON serialization
|
||||||
|
- `<SDCardManager.h>` - SD card operations (SdMan singleton)
|
||||||
|
- `<Epub.h>` - Epub cache management
|
||||||
|
- `BookListStore.h` - Book list management (lists feature)
|
||||||
|
- `BookManager.h` - Book deletion, archiving, recent books
|
||||||
|
- `StringUtils.h` - File extension checking
|
||||||
331
docs/webserver-endpoints.md
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
# Webserver Endpoints
|
||||||
|
|
||||||
|
This document describes all HTTP and WebSocket endpoints available on the CrossPoint Reader webserver.
|
||||||
|
|
||||||
|
- [Webserver Endpoints](#webserver-endpoints)
|
||||||
|
- [Overview](#overview)
|
||||||
|
- [HTTP Endpoints](#http-endpoints)
|
||||||
|
- [GET `/` - Home Page](#get----home-page)
|
||||||
|
- [GET `/files` - File Browser Page](#get-files---file-browser-page)
|
||||||
|
- [GET `/api/status` - Device Status](#get-apistatus---device-status)
|
||||||
|
- [GET `/api/files` - List Files](#get-apifiles---list-files)
|
||||||
|
- [POST `/upload` - Upload File](#post-upload---upload-file)
|
||||||
|
- [POST `/mkdir` - Create Folder](#post-mkdir---create-folder)
|
||||||
|
- [POST `/delete` - Delete File or Folder](#post-delete---delete-file-or-folder)
|
||||||
|
- [WebSocket Endpoint](#websocket-endpoint)
|
||||||
|
- [Port 81 - Fast Binary Upload](#port-81---fast-binary-upload)
|
||||||
|
- [Network Modes](#network-modes)
|
||||||
|
- [Station Mode (STA)](#station-mode-sta)
|
||||||
|
- [Access Point Mode (AP)](#access-point-mode-ap)
|
||||||
|
- [Notes](#notes)
|
||||||
|
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The CrossPoint Reader exposes a webserver for file management and device monitoring:
|
||||||
|
|
||||||
|
- **HTTP Server**: Port 80
|
||||||
|
- **WebSocket Server**: Port 81 (for fast binary uploads)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## HTTP Endpoints
|
||||||
|
|
||||||
|
### GET `/` - Home Page
|
||||||
|
|
||||||
|
Serves the home page HTML interface.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```bash
|
||||||
|
curl http://crosspoint.local/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:** HTML page (200 OK)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### GET `/files` - File Browser Page
|
||||||
|
|
||||||
|
Serves the file browser HTML interface.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```bash
|
||||||
|
curl http://crosspoint.local/files
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:** HTML page (200 OK)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### GET `/api/status` - Device Status
|
||||||
|
|
||||||
|
Returns JSON with device status information.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```bash
|
||||||
|
curl http://crosspoint.local/api/status
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response (200 OK):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"ip": "192.168.1.100",
|
||||||
|
"mode": "STA",
|
||||||
|
"rssi": -45,
|
||||||
|
"freeHeap": 123456,
|
||||||
|
"uptime": 3600
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
| ---------- | ------ | --------------------------------------------------------- |
|
||||||
|
| `version` | string | CrossPoint firmware version |
|
||||||
|
| `ip` | string | Device IP address |
|
||||||
|
| `mode` | string | `"STA"` (connected to WiFi) or `"AP"` (access point mode) |
|
||||||
|
| `rssi` | number | WiFi signal strength in dBm (0 in AP mode) |
|
||||||
|
| `freeHeap` | number | Free heap memory in bytes |
|
||||||
|
| `uptime` | number | Seconds since device boot |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### GET `/api/files` - List Files
|
||||||
|
|
||||||
|
Returns a JSON array of files and folders in the specified directory.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```bash
|
||||||
|
# List root directory
|
||||||
|
curl http://crosspoint.local/api/files
|
||||||
|
|
||||||
|
# List specific directory
|
||||||
|
curl "http://crosspoint.local/api/files?path=/Books"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Query Parameters:**
|
||||||
|
|
||||||
|
| Parameter | Required | Default | Description |
|
||||||
|
| --------- | -------- | ------- | ---------------------- |
|
||||||
|
| `path` | No | `/` | Directory path to list |
|
||||||
|
|
||||||
|
**Response (200 OK):**
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{"name": "MyBook.epub", "size": 1234567, "isDirectory": false, "isEpub": true},
|
||||||
|
{"name": "Notes", "size": 0, "isDirectory": true, "isEpub": false},
|
||||||
|
{"name": "document.pdf", "size": 54321, "isDirectory": false, "isEpub": false}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
| ------------- | ------- | ---------------------------------------- |
|
||||||
|
| `name` | string | File or folder name |
|
||||||
|
| `size` | number | Size in bytes (0 for directories) |
|
||||||
|
| `isDirectory` | boolean | `true` if the item is a folder |
|
||||||
|
| `isEpub` | boolean | `true` if the file has `.epub` extension |
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Hidden files (starting with `.`) are automatically filtered out
|
||||||
|
- System folders (`System Volume Information`, `XTCache`) are hidden
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### POST `/upload` - Upload File
|
||||||
|
|
||||||
|
Uploads a file to the SD card via multipart form data.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```bash
|
||||||
|
# Upload to root directory
|
||||||
|
curl -X POST -F "file=@mybook.epub" http://crosspoint.local/upload
|
||||||
|
|
||||||
|
# Upload to specific directory
|
||||||
|
curl -X POST -F "file=@mybook.epub" "http://crosspoint.local/upload?path=/Books"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Query Parameters:**
|
||||||
|
|
||||||
|
| Parameter | Required | Default | Description |
|
||||||
|
| --------- | -------- | ------- | ------------------------------- |
|
||||||
|
| `path` | No | `/` | Target directory for the upload |
|
||||||
|
|
||||||
|
**Response (200 OK):**
|
||||||
|
```
|
||||||
|
File uploaded successfully: mybook.epub
|
||||||
|
```
|
||||||
|
|
||||||
|
**Error Responses:**
|
||||||
|
|
||||||
|
| Status | Body | Cause |
|
||||||
|
| ------ | ----------------------------------------------- | --------------------------- |
|
||||||
|
| 400 | `Failed to create file on SD card` | Cannot create file |
|
||||||
|
| 400 | `Failed to write to SD card - disk may be full` | Write error during upload |
|
||||||
|
| 400 | `Failed to write final data to SD card` | Error flushing final buffer |
|
||||||
|
| 400 | `Upload aborted` | Client aborted the upload |
|
||||||
|
| 400 | `Unknown error during upload` | Unspecified error |
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Existing files with the same name will be overwritten
|
||||||
|
- Uses a 4KB buffer for efficient SD card writes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### POST `/mkdir` - Create Folder
|
||||||
|
|
||||||
|
Creates a new folder on the SD card.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```bash
|
||||||
|
curl -X POST -d "name=NewFolder&path=/" http://crosspoint.local/mkdir
|
||||||
|
```
|
||||||
|
|
||||||
|
**Form Parameters:**
|
||||||
|
|
||||||
|
| Parameter | Required | Default | Description |
|
||||||
|
| --------- | -------- | ------- | ---------------------------- |
|
||||||
|
| `name` | Yes | - | Name of the folder to create |
|
||||||
|
| `path` | No | `/` | Parent directory path |
|
||||||
|
|
||||||
|
**Response (200 OK):**
|
||||||
|
```
|
||||||
|
Folder created: NewFolder
|
||||||
|
```
|
||||||
|
|
||||||
|
**Error Responses:**
|
||||||
|
|
||||||
|
| Status | Body | Cause |
|
||||||
|
| ------ | ----------------------------- | ----------------------------- |
|
||||||
|
| 400 | `Missing folder name` | `name` parameter not provided |
|
||||||
|
| 400 | `Folder name cannot be empty` | Empty folder name |
|
||||||
|
| 400 | `Folder already exists` | Folder with same name exists |
|
||||||
|
| 500 | `Failed to create folder` | SD card error |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### POST `/delete` - Delete File or Folder
|
||||||
|
|
||||||
|
Deletes a file or folder from the SD card.
|
||||||
|
|
||||||
|
**Request:**
|
||||||
|
```bash
|
||||||
|
# Delete a file
|
||||||
|
curl -X POST -d "path=/Books/mybook.epub&type=file" http://crosspoint.local/delete
|
||||||
|
|
||||||
|
# Delete an empty folder
|
||||||
|
curl -X POST -d "path=/OldFolder&type=folder" http://crosspoint.local/delete
|
||||||
|
```
|
||||||
|
|
||||||
|
**Form Parameters:**
|
||||||
|
|
||||||
|
| Parameter | Required | Default | Description |
|
||||||
|
| --------- | -------- | ------- | -------------------------------- |
|
||||||
|
| `path` | Yes | - | Path to the item to delete |
|
||||||
|
| `type` | No | `file` | Type of item: `file` or `folder` |
|
||||||
|
|
||||||
|
**Response (200 OK):**
|
||||||
|
```
|
||||||
|
Deleted successfully
|
||||||
|
```
|
||||||
|
|
||||||
|
**Error Responses:**
|
||||||
|
|
||||||
|
| Status | Body | Cause |
|
||||||
|
| ------ | --------------------------------------------- | ----------------------------- |
|
||||||
|
| 400 | `Missing path` | `path` parameter not provided |
|
||||||
|
| 400 | `Cannot delete root directory` | Attempted to delete `/` |
|
||||||
|
| 400 | `Folder is not empty. Delete contents first.` | Non-empty folder |
|
||||||
|
| 403 | `Cannot delete system files` | Hidden file (starts with `.`) |
|
||||||
|
| 403 | `Cannot delete protected items` | Protected system folder |
|
||||||
|
| 404 | `Item not found` | Path does not exist |
|
||||||
|
| 500 | `Failed to delete item` | SD card error |
|
||||||
|
|
||||||
|
**Protected Items:**
|
||||||
|
- Files/folders starting with `.`
|
||||||
|
- `System Volume Information`
|
||||||
|
- `XTCache`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## WebSocket Endpoint
|
||||||
|
|
||||||
|
### Port 81 - Fast Binary Upload
|
||||||
|
|
||||||
|
A WebSocket endpoint for high-speed binary file uploads. More efficient than HTTP multipart for large files.
|
||||||
|
|
||||||
|
**Connection:**
|
||||||
|
```
|
||||||
|
ws://crosspoint.local:81/
|
||||||
|
```
|
||||||
|
|
||||||
|
**Protocol:**
|
||||||
|
|
||||||
|
1. **Client** sends TEXT message: `START:<filename>:<size>:<path>`
|
||||||
|
2. **Server** responds with TEXT: `READY`
|
||||||
|
3. **Client** sends BINARY messages with file data chunks
|
||||||
|
4. **Server** sends TEXT progress updates: `PROGRESS:<received>:<total>`
|
||||||
|
5. **Server** sends TEXT when complete: `DONE` or `ERROR:<message>`
|
||||||
|
|
||||||
|
**Example Session:**
|
||||||
|
|
||||||
|
```
|
||||||
|
Client -> "START:mybook.epub:1234567:/Books"
|
||||||
|
Server -> "READY"
|
||||||
|
Client -> [binary chunk 1]
|
||||||
|
Client -> [binary chunk 2]
|
||||||
|
Server -> "PROGRESS:65536:1234567"
|
||||||
|
Client -> [binary chunk 3]
|
||||||
|
...
|
||||||
|
Server -> "PROGRESS:1234567:1234567"
|
||||||
|
Server -> "DONE"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Error Messages:**
|
||||||
|
|
||||||
|
| Message | Cause |
|
||||||
|
| --------------------------------- | ---------------------------------- |
|
||||||
|
| `ERROR:Failed to create file` | Cannot create file on SD card |
|
||||||
|
| `ERROR:Invalid START format` | Malformed START message |
|
||||||
|
| `ERROR:No upload in progress` | Binary data received without START |
|
||||||
|
| `ERROR:Write failed - disk full?` | SD card write error |
|
||||||
|
|
||||||
|
**Example with `websocat`:**
|
||||||
|
```bash
|
||||||
|
# Interactive session
|
||||||
|
websocat ws://crosspoint.local:81
|
||||||
|
|
||||||
|
# Then type:
|
||||||
|
START:mybook.epub:1234567:/Books
|
||||||
|
# Wait for READY, then send binary data
|
||||||
|
```
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Progress updates are sent every 64KB or at completion
|
||||||
|
- Disconnection during upload will delete the incomplete file
|
||||||
|
- Existing files with the same name will be overwritten
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Network Modes
|
||||||
|
|
||||||
|
The device can operate in two network modes:
|
||||||
|
|
||||||
|
### Station Mode (STA)
|
||||||
|
- Device connects to an existing WiFi network
|
||||||
|
- IP address assigned by router/DHCP
|
||||||
|
- `mode` field in `/api/status` returns `"STA"`
|
||||||
|
- `rssi` field shows signal strength
|
||||||
|
|
||||||
|
### Access Point Mode (AP)
|
||||||
|
- Device creates its own WiFi hotspot
|
||||||
|
- Default IP is typically `192.168.4.1`
|
||||||
|
- `mode` field in `/api/status` returns `"AP"`
|
||||||
|
- `rssi` field returns `0`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- These examples use `crosspoint.local`. If your network does not support mDNS or the address does not resolve, replace it with the specific **IP Address** displayed on your device screen (e.g., `http://192.168.1.102/`).
|
||||||
|
- All paths on the SD card start with `/`
|
||||||
|
- Trailing slashes are automatically stripped (except for root `/`)
|
||||||
|
- The webserver uses chunked transfer encoding for file listings
|
||||||
225
docs/webserver.md
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
# Web Server Guide
|
||||||
|
|
||||||
|
This guide explains how to connect your CrossPoint Reader to WiFi and use the built-in web server to upload EPUB files from your computer or phone.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
CrossPoint Reader includes a built-in web server that allows you to:
|
||||||
|
|
||||||
|
- Upload EPUB files wirelessly from any device on the same WiFi network
|
||||||
|
- Browse and manage files on your device's SD card
|
||||||
|
- Create folders to organize your ebooks
|
||||||
|
- Delete files and folders
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Your CrossPoint Reader device
|
||||||
|
- A WiFi network
|
||||||
|
- A computer, phone, or tablet connected to the **same WiFi network**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1: Accessing the WiFi Screen
|
||||||
|
|
||||||
|
1. From the main menu or file browser, navigate to the **Settings** screen
|
||||||
|
2. Select the **WiFi** option
|
||||||
|
3. The device will automatically start scanning for available networks
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2: Connecting to WiFi
|
||||||
|
|
||||||
|
### Viewing Available Networks
|
||||||
|
|
||||||
|
Once the scan completes, you'll see a list of available WiFi networks with the following indicators:
|
||||||
|
|
||||||
|
- **Signal strength bars** (`||||`, `|||`, `||`, `|`) - Shows connection quality
|
||||||
|
- **`*` symbol** - Indicates the network is password-protected (encrypted)
|
||||||
|
- **`+` symbol** - Indicates you have previously saved credentials for this network
|
||||||
|
|
||||||
|
<img src="./images/wifi/wifi_networks.jpeg" height="500">
|
||||||
|
|
||||||
|
### Selecting a Network
|
||||||
|
|
||||||
|
1. Use the **Left/Right** (or **Volume Up/Down**) buttons to navigate through the network list
|
||||||
|
2. Press **Confirm** to select the highlighted network
|
||||||
|
|
||||||
|
### Entering Password (for encrypted networks)
|
||||||
|
|
||||||
|
If the network requires a password:
|
||||||
|
|
||||||
|
1. An on-screen keyboard will appear
|
||||||
|
2. Use the navigation buttons to select characters
|
||||||
|
3. Press **Confirm** to enter each character
|
||||||
|
4. When complete, select the **Done** option on the keyboard
|
||||||
|
|
||||||
|
<img src="./images/wifi/wifi_password.jpeg" height="500">
|
||||||
|
|
||||||
|
**Note:** If you've previously connected to this network, the saved password will be used automatically.
|
||||||
|
|
||||||
|
### Connection Process
|
||||||
|
|
||||||
|
The device will display "Connecting..." while establishing the connection. This typically takes 5-10 seconds.
|
||||||
|
|
||||||
|
### Saving Credentials
|
||||||
|
|
||||||
|
If this is a new network, you'll be prompted to save the password:
|
||||||
|
|
||||||
|
- Select **Yes** to save credentials for automatic connection next time (NOTE: These are stored in plaintext on the device's SD card. Do not use this for sensitive networks.)
|
||||||
|
- Select **No** to connect without saving
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3: Connection Success
|
||||||
|
|
||||||
|
Once connected, the screen will display:
|
||||||
|
|
||||||
|
- **Network name** (SSID)
|
||||||
|
- **IP Address** (e.g., `192.168.1.102`)
|
||||||
|
- **Web server URL** (e.g., `http://192.168.1.102/`)
|
||||||
|
|
||||||
|
<img src="./images/wifi/wifi_connected.jpeg" height="500">
|
||||||
|
|
||||||
|
**Important:** Make note of the IP address - you'll need this to access the web interface from your computer or phone.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4: Accessing the Web Interface
|
||||||
|
|
||||||
|
### From a Computer
|
||||||
|
|
||||||
|
1. Ensure your computer is connected to the **same WiFi network** as your CrossPoint Reader
|
||||||
|
2. Open any web browser (Chrome is recommended)
|
||||||
|
3. Type the IP address shown on your device into the browser's address bar
|
||||||
|
- Example: `http://192.168.1.102/`
|
||||||
|
4. Press Enter
|
||||||
|
|
||||||
|
### From a Phone or Tablet
|
||||||
|
|
||||||
|
1. Ensure your phone/tablet is connected to the **same WiFi network** as your CrossPoint Reader
|
||||||
|
2. Open your mobile browser (Safari, Chrome, etc.)
|
||||||
|
3. Type the IP address into the address bar
|
||||||
|
- Example: `http://192.168.1.102/`
|
||||||
|
4. Tap Go
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 5: Using the Web Interface
|
||||||
|
|
||||||
|
### Home Page
|
||||||
|
|
||||||
|
The home page displays:
|
||||||
|
|
||||||
|
- Device status and version information
|
||||||
|
- WiFi connection status
|
||||||
|
- Current IP address
|
||||||
|
- Available memory
|
||||||
|
|
||||||
|
Navigation links:
|
||||||
|
|
||||||
|
- **Home** - Returns to the status page
|
||||||
|
- **File Manager** - Access file management features
|
||||||
|
|
||||||
|
<img src="./images/wifi/webserver_homepage.png" width="600">
|
||||||
|
|
||||||
|
### File Manager
|
||||||
|
|
||||||
|
Click **File Manager** to access file management features.
|
||||||
|
|
||||||
|
#### Browsing Files
|
||||||
|
|
||||||
|
- The file manager displays all files and folders on your SD card
|
||||||
|
- **Folders** are highlighted in yellow with a 📁 icon
|
||||||
|
- **EPUB files** are highlighted in green with a 📗 icon
|
||||||
|
- Click on a folder name to navigate into it
|
||||||
|
- Use the breadcrumb navigation at the top to go back to parent folders
|
||||||
|
|
||||||
|
<img src="./images/wifi/webserver_files.png" width="600">
|
||||||
|
|
||||||
|
#### Uploading EPUB Files
|
||||||
|
|
||||||
|
1. Click the **+ Add** button in the top-right corner
|
||||||
|
2. Select **Upload eBook** from the dropdown menu
|
||||||
|
3. Click **Choose File** and select an `.epub` file from your device
|
||||||
|
4. Click **Upload**
|
||||||
|
5. A progress bar will show the upload status
|
||||||
|
6. The page will automatically refresh when the upload is complete
|
||||||
|
|
||||||
|
**Note:** Only `.epub` files are accepted. Other file types will be rejected.
|
||||||
|
|
||||||
|
<img src="./images/wifi/webserver_upload.png" width="600">
|
||||||
|
|
||||||
|
#### Creating Folders
|
||||||
|
|
||||||
|
1. Click the **+ Add** button in the top-right corner
|
||||||
|
2. Select **New Folder** from the dropdown menu
|
||||||
|
3. Enter a folder name (letters, numbers, underscores, and hyphens only)
|
||||||
|
4. Click **Create Folder**
|
||||||
|
|
||||||
|
This is useful for organizing your ebooks by genre, author, or series.
|
||||||
|
|
||||||
|
#### Deleting Files and Folders
|
||||||
|
|
||||||
|
1. Click the **🗑️** (trash) icon next to any file or folder
|
||||||
|
2. Confirm the deletion in the popup dialog
|
||||||
|
3. Click **Delete** to permanently remove the item
|
||||||
|
|
||||||
|
**Warning:** Deletion is permanent and cannot be undone!
|
||||||
|
|
||||||
|
**Note:** Folders must be empty before they can be deleted.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Command Line File Management
|
||||||
|
|
||||||
|
For power users, you can manage files directly from your terminal using `curl` while the device is in File Upload mode a detailed documentation can be found [here](./webserver-endpoints.md).
|
||||||
|
|
||||||
|
## Security Notes
|
||||||
|
|
||||||
|
- The web server runs on port 80 (standard HTTP)
|
||||||
|
- **No authentication is required** - anyone on the same network can access the interface
|
||||||
|
- The web server is only accessible while the WiFi screen shows "Connected"
|
||||||
|
- The web server automatically stops when you exit the WiFi screen
|
||||||
|
- For security, only use on trusted private networks
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Technical Details
|
||||||
|
|
||||||
|
- **Supported WiFi:** 2.4GHz networks (802.11 b/g/n)
|
||||||
|
- **Web Server Port:** 80 (HTTP)
|
||||||
|
- **Maximum Upload Size:** Limited by available SD card space
|
||||||
|
- **Supported File Format:** `.epub` only
|
||||||
|
- **Browser Compatibility:** All modern browsers (Chrome, Firefox, Safari, Edge)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tips and Best Practices
|
||||||
|
|
||||||
|
1. **Organize with folders** - Create folders before uploading to keep your library organized
|
||||||
|
2. **Check signal strength** - Stronger signals (`|||` or `||||`) provide faster, more reliable uploads
|
||||||
|
3. **Upload multiple files** - You can upload files one at a time; the page refreshes after each upload
|
||||||
|
4. **Use descriptive names** - Name your folders clearly (e.g., "SciFi", "Mystery", "Non-Fiction")
|
||||||
|
5. **Keep credentials saved** - Save your WiFi password for quick reconnection in the future
|
||||||
|
6. **Exit when done** - Press **Back** to exit the WiFi screen and save battery
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exiting WiFi Mode
|
||||||
|
|
||||||
|
When you're finished uploading files:
|
||||||
|
|
||||||
|
1. Press the **Back** button on your CrossPoint Reader
|
||||||
|
2. The web server will automatically stop
|
||||||
|
3. WiFi will disconnect to conserve battery
|
||||||
|
4. You'll return to the previous screen
|
||||||
|
|
||||||
|
Your uploaded files will be immediately available in the file browser!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Related Documentation
|
||||||
|
|
||||||
|
- [User Guide](../USER_GUIDE.md) - General device operation
|
||||||
|
- [Troubleshooting](./troubleshooting.md) - Troubleshooting
|
||||||
|
- [README](../README.md) - Project overview and features
|
||||||
195
ef-CHANGELOG.md
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
# crosspoint-ef Changelog
|
||||||
|
|
||||||
|
All notable changes to the crosspoint-ef fork are documented here.
|
||||||
|
|
||||||
|
Base: CrossPoint Reader 0.15.0
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ef-1.0.5
|
||||||
|
|
||||||
|
**Stability & Memory Improvements**
|
||||||
|
|
||||||
|
### Bug Fixes - Webserver
|
||||||
|
|
||||||
|
- **File Transfer Stability**: Removed blocking MD5 hash computation from file listings that caused EAGAIN errors and connection stalls
|
||||||
|
- **JSON Batching**: Implemented 2KB batch streaming for file listings with pacing to prevent TCP buffer overflow
|
||||||
|
- **Simplified Flow Control**: Removed unnecessary yield/delay logic from content streaming
|
||||||
|
|
||||||
|
### Bug Fixes - Memory
|
||||||
|
|
||||||
|
- **QR Code Caching**: Generate QR codes once on server start instead of regenerating on each screen render
|
||||||
|
- **WiFi Scan Optimization**: Replaced memory-heavy `std::map` deduplication with in-place vector search, limited results to 20 networks, earlier `WiFi.scanDelete()` for faster memory recovery
|
||||||
|
- **Cover Buffer Leak**: Fixed 48KB memory leak when navigating from Home to File Transfer (cover buffer now explicitly freed)
|
||||||
|
|
||||||
|
### Bug Fixes - EPUB Reader
|
||||||
|
|
||||||
|
- **Errant Underlining**: Fixed words before styled inline elements (like `<a>` tags with CSS underline) incorrectly receiving the element's style by flushing the text buffer before style changes
|
||||||
|
|
||||||
|
### Bug Fixes - Flashing Screen
|
||||||
|
|
||||||
|
- **Version String Overflow**: Fixed flash notification parsing failing on longer version strings (buffer limit increased from 30 to 50 characters)
|
||||||
|
- **Display Quality**: Changed flashing screen to half refresh for cleaner appearance
|
||||||
|
- **Timing**: Adjusted pre-flash script timing for half refresh completion
|
||||||
|
|
||||||
|
### Upstream Merges
|
||||||
|
|
||||||
|
- **PR #522 - HAL Abstraction Layer**: Merged hardware abstraction layer refactor introducing `HalDisplay` and `HalGPIO` classes, decoupling application code from direct hardware access
|
||||||
|
- **PR #603 - Sunlight Fading Fix**: Added user-toggleable setting to turn off display between refreshes, mitigating the sunlight fading issue on e-ink displays
|
||||||
|
- New "Sunlight Fading Fix" toggle in Display settings (OFF/ON)
|
||||||
|
- Passes `turnOffScreen` parameter through display stack when enabled
|
||||||
|
|
||||||
|
### Files Changed
|
||||||
|
|
||||||
|
- `src/main.cpp` - flash screen fixes, cover buffer free on File Transfer entry, fading fix integration
|
||||||
|
- `scripts/pre_flash.py` - timing adjustments for full refresh
|
||||||
|
- `src/network/CrossPointWebServer.cpp` - JSON batching, removed MD5 from listings
|
||||||
|
- `src/network/CrossPointWebServer.h` - removed md5 from FileInfo, simplified sendContentSafe
|
||||||
|
- `src/activities/network/CrossPointWebServerActivity.cpp` - QR code caching
|
||||||
|
- `src/activities/network/CrossPointWebServerActivity.h` - QR code cache members
|
||||||
|
- `src/activities/network/WifiSelectionActivity.cpp` - WiFi scan memory optimization
|
||||||
|
- `lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp` - flush buffer before style changes
|
||||||
|
- `lib/hal/HalDisplay.h` - new HAL abstraction for display (PR #522), turnOffScreen parameter (PR #603)
|
||||||
|
- `lib/hal/HalDisplay.cpp` - HAL display implementation with fading fix passthrough
|
||||||
|
- `lib/hal/HalGPIO.h` - new HAL abstraction for GPIO (PR #522)
|
||||||
|
- `lib/hal/HalGPIO.cpp` - HAL GPIO implementation
|
||||||
|
- `lib/GfxRenderer/GfxRenderer.h` - updated for HAL layer, added fadingFix member
|
||||||
|
- `lib/GfxRenderer/GfxRenderer.cpp` - updated for HAL layer, passes fadingFix to display
|
||||||
|
- `src/CrossPointSettings.h` - added fadingFix setting
|
||||||
|
- `src/CrossPointSettings.cpp` - fadingFix persistence
|
||||||
|
- `src/activities/settings/SettingsActivity.cpp` - added Sunlight Fading Fix toggle
|
||||||
|
- `open-x4-sdk` - updated submodule with turnOffScreen support in EInkDisplay
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ef-1.0.4
|
||||||
|
|
||||||
|
**EPUB Rendering & Stability**
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
|
||||||
|
- **End-of-Book "Start Over"**: Press next at end of book to wrap to first page
|
||||||
|
|
||||||
|
### EPUB Rendering Improvements
|
||||||
|
|
||||||
|
- CSS `margin-left`/`padding-left` parsing for block indentation
|
||||||
|
- Vertical bar and italic styling for blockquotes
|
||||||
|
- Left margin indentation for list items (`<ol>`/`<ul>`)
|
||||||
|
- Fixed ordered lists showing bullets instead of numbers
|
||||||
|
- Fixed nested `<p>` inside `<li>` causing marker on separate line
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- **Webserver**: Fixed file listing disconnection issues with flow control
|
||||||
|
- **Webserver**: Memory optimization for File Transfer mode (frees heap before starting)
|
||||||
|
- **Dictionary**: Fixed zip dictionary allocation order for better memory allocation success
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ef-1.0.3
|
||||||
|
|
||||||
|
**Maintenance Release**
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Fixed cppcheck CI failure: removed unused `screenWidth` variable in word selection activity
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ef-1.0.2
|
||||||
|
|
||||||
|
**Quick Menu Enhancements**
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
|
||||||
|
- **Screen Rotation Toggle**: Quick toggle between Portrait and Landscape CCW directly from the quick menu
|
||||||
|
- Automatically reindexes content for new screen dimensions
|
||||||
|
- Preserves reading position via content offset restoration
|
||||||
|
- **Customizable Menu Order**: Reorder quick menu items to your preference
|
||||||
|
- New "Edit List Order" option at bottom of menu
|
||||||
|
- Pick-and-place reordering: select item, navigate to destination, place
|
||||||
|
- Order persists across sessions
|
||||||
|
|
||||||
|
### UI Improvements
|
||||||
|
|
||||||
|
- Added navigation button hints to quick menu (prev/next on front buttons, up/down on side buttons)
|
||||||
|
- Fixed orientation-aware margins for button hint areas in landscape modes
|
||||||
|
- New default menu order: Bookmark, Dictionary, Rotate Screen, Settings, Clear Cache
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ef-1.0.1
|
||||||
|
|
||||||
|
**Dictionary Stability & UX Improvements**
|
||||||
|
|
||||||
|
### Bug Fixes - Stability
|
||||||
|
|
||||||
|
- Fixed dictionary crashes caused by heap fragmentation from repeated page navigation
|
||||||
|
- Refactored TextBlock/ParsedText from `std::list` to `std::vector`, reducing heap allocations by ~12x per TextBlock
|
||||||
|
- Affects EPUB reader page rendering, dictionary definition display, and word selection
|
||||||
|
- Contiguous memory improves cache locality during text layout and reduces heap fragmentation on the memory-constrained ESP32
|
||||||
|
- Added uncompressed dictionary (`.dict`) support to avoid decompression memory issues with large dictzip chunks (58KB chunks -> direct read)
|
||||||
|
- Implemented chunked on-demand HTML parsing for large definitions, parsing pages as user navigates rather than all at once
|
||||||
|
- Limited cached pages to 4 with re-parse capability for backward navigation beyond cache window
|
||||||
|
- Fixed double-button press bug when loading new dictionary chunks
|
||||||
|
|
||||||
|
### Bug Fixes - UI/Layout
|
||||||
|
|
||||||
|
- Restored proper orientation-aware button hint spacing (front: 45px, side: 50px)
|
||||||
|
- Added side button hints to definition screen with "<" / ">" labels for page navigation
|
||||||
|
- Added side button hints to word selection screen ("UP"/"DOWN" labels, borderless, small font)
|
||||||
|
- Added side button hints to dictionary menu ("< Prev", "Next >")
|
||||||
|
- Moved page indicator up to avoid bezel cutoff in landscape orientations
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ef-1.0.0
|
||||||
|
|
||||||
|
**First Official Release** (previously ef-0.15.99)
|
||||||
|
|
||||||
|
First milestone release of the crosspoint-ef fork, building on CrossPoint Reader 0.15.0 with 14+ major new features and enhancements.
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
|
||||||
|
- **Dictionary Support**: Offline StarDict dictionary with word selection from reader, fast prefix-indexed search, rich HTML formatting, and multi-page pagination
|
||||||
|
- **Bookmark System**: Per-book bookmarks with visual folded-corner indicators, dedicated management interface, and auto-generated bookmark names
|
||||||
|
- **Quick Menu**: In-reader quick access menu for common actions (Dictionary, Bookmark, Clear Cache, Settings) via short power button press
|
||||||
|
- **Library Search**: Search across all books by title, author, or filename with dynamic character picker and weighted relevance scoring
|
||||||
|
- **CSS Support**: Parse and apply CSS styles from EPUB stylesheets (text-align, font-style, font-weight, text-decoration, margins, padding)
|
||||||
|
- **Inline Image Support**: PNG and Baseline JPEG rendering within EPUB content with 2-bit grayscale dithering and caching
|
||||||
|
- **Custom Fonts**: Atkinson Hyperlegible Next (low-vision readers) and Fern Micro (small screens)
|
||||||
|
- **Enhanced Web Server**: File management (upload, download, delete, rename, copy, move, mkdir), companion app API, WebSocket uploads, mDNS discovery at `crosspoint.local`
|
||||||
|
- **Reading Lists**: Create, manage, and pin custom book lists with web API support (CSV format)
|
||||||
|
- **Enhanced Tab Bar**: Unified tab bar with horizontal scrolling and overflow indicators (Recent, Lists, Bookmarks, Search, Files)
|
||||||
|
- **Progress Bar Status**: Additional status bar option showing visual reading progress
|
||||||
|
- **OPDS Browser Enhancements**: Navigation history, page skipping (hold Up/Down), error retry, HTTP Basic Auth support
|
||||||
|
|
||||||
|
### Display Enhancements
|
||||||
|
|
||||||
|
- **High Contrast Mode**: System-wide contrast adjustment
|
||||||
|
- **Bezel Compensation**: Configurable margin (0-10px) for physical screen edge defects
|
||||||
|
- **Sleep Screen Improvements**: Edge-aware color filling for seamless letterbox appearance
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
- Fixed device hanging when booted without USB connected (Serial.available()/Serial.read() called without Serial.begin())
|
||||||
|
- Fixed grayscale state corruption causing ghosting artifacts when anti-aliasing enabled under memory pressure
|
||||||
|
- Memory optimization with graceful degradation when memory is low
|
||||||
|
|
||||||
|
### Development Tools
|
||||||
|
|
||||||
|
- `pre_flash.py`: Displays "Flashing firmware..." screen during upload
|
||||||
|
- `debugging_monitor.py`: Enhanced serial monitor with memory graphs
|
||||||
|
- `pio_helper.py`: Interactive PlatformIO workflow helper
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Differences from Upstream 0.16.0
|
||||||
|
|
||||||
|
This fork is based on upstream 0.15.0. The following 0.16.0 features are not included:
|
||||||
|
|
||||||
|
- KOReader sync support
|
||||||
|
- Non-English hyphenation patterns (Spanish, German, French, Russian)
|
||||||
|
- XTC/XTCH file format support
|
||||||
|
|
||||||
|
See [crosspoint-ef-features.md](docs/crosspoint-ef-features.md) for complete feature documentation.
|
||||||
@ -2,8 +2,7 @@
|
|||||||
|
|
||||||
#include <Utf8.h>
|
#include <Utf8.h>
|
||||||
|
|
||||||
inline int min(const int a, const int b) { return a < b ? a : b; }
|
#include <algorithm>
|
||||||
inline int max(const int a, const int b) { return a < b ? b : a; }
|
|
||||||
|
|
||||||
void EpdFont::getTextBounds(const char* string, const int startX, const int startY, int* minX, int* minY, int* maxX,
|
void EpdFont::getTextBounds(const char* string, const int startX, const int startY, int* minX, int* minY, int* maxX,
|
||||||
int* maxY) const {
|
int* maxY) const {
|
||||||
@ -23,8 +22,7 @@ void EpdFont::getTextBounds(const char* string, const int startX, const int star
|
|||||||
const EpdGlyph* glyph = getGlyph(cp);
|
const EpdGlyph* glyph = getGlyph(cp);
|
||||||
|
|
||||||
if (!glyph) {
|
if (!glyph) {
|
||||||
// TODO: Replace with fallback glyph property?
|
glyph = getGlyph(REPLACEMENT_GLYPH);
|
||||||
glyph = getGlyph('?');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!glyph) {
|
if (!glyph) {
|
||||||
@ -32,10 +30,10 @@ void EpdFont::getTextBounds(const char* string, const int startX, const int star
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
*minX = min(*minX, cursorX + glyph->left);
|
*minX = std::min(*minX, cursorX + glyph->left);
|
||||||
*maxX = max(*maxX, cursorX + glyph->left + glyph->width);
|
*maxX = std::max(*maxX, cursorX + glyph->left + glyph->width);
|
||||||
*minY = min(*minY, cursorY + glyph->top - glyph->height);
|
*minY = std::min(*minY, cursorY + glyph->top - glyph->height);
|
||||||
*maxY = max(*maxY, cursorY + glyph->top);
|
*maxY = std::max(*maxY, cursorY + glyph->top);
|
||||||
cursorX += glyph->advanceX;
|
cursorX += glyph->advanceX;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -59,14 +57,28 @@ bool EpdFont::hasPrintableChars(const char* string) const {
|
|||||||
|
|
||||||
const EpdGlyph* EpdFont::getGlyph(const uint32_t cp) const {
|
const EpdGlyph* EpdFont::getGlyph(const uint32_t cp) const {
|
||||||
const EpdUnicodeInterval* intervals = data->intervals;
|
const EpdUnicodeInterval* intervals = data->intervals;
|
||||||
for (int i = 0; i < data->intervalCount; i++) {
|
const int count = data->intervalCount;
|
||||||
const EpdUnicodeInterval* interval = &intervals[i];
|
|
||||||
if (cp >= interval->first && cp <= interval->last) {
|
if (count == 0) return nullptr;
|
||||||
|
|
||||||
|
// Binary search for O(log n) lookup instead of O(n)
|
||||||
|
// Critical for Korean fonts with many unicode intervals
|
||||||
|
int left = 0;
|
||||||
|
int right = count - 1;
|
||||||
|
|
||||||
|
while (left <= right) {
|
||||||
|
const int mid = left + (right - left) / 2;
|
||||||
|
const EpdUnicodeInterval* interval = &intervals[mid];
|
||||||
|
|
||||||
|
if (cp < interval->first) {
|
||||||
|
right = mid - 1;
|
||||||
|
} else if (cp > interval->last) {
|
||||||
|
left = mid + 1;
|
||||||
|
} else {
|
||||||
|
// Found: cp >= interval->first && cp <= interval->last
|
||||||
return &data->glyph[interval->offset + (cp - interval->first)];
|
return &data->glyph[interval->offset + (cp - interval->first)];
|
||||||
}
|
}
|
||||||
if (cp < interval->first) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
#include "EpdFontFamily.h"
|
#include "EpdFontFamily.h"
|
||||||
|
|
||||||
const EpdFont* EpdFontFamily::getFont(const EpdFontStyle style) const {
|
const EpdFont* EpdFontFamily::getFont(const Style style) const {
|
||||||
if (style == BOLD && bold) {
|
if (style == BOLD && bold) {
|
||||||
return bold;
|
return bold;
|
||||||
}
|
}
|
||||||
@ -22,16 +22,16 @@ const EpdFont* EpdFontFamily::getFont(const EpdFontStyle style) const {
|
|||||||
return regular;
|
return regular;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpdFontFamily::getTextDimensions(const char* string, int* w, int* h, const EpdFontStyle style) const {
|
void EpdFontFamily::getTextDimensions(const char* string, int* w, int* h, const Style style) const {
|
||||||
getFont(style)->getTextDimensions(string, w, h);
|
getFont(style)->getTextDimensions(string, w, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EpdFontFamily::hasPrintableChars(const char* string, const EpdFontStyle style) const {
|
bool EpdFontFamily::hasPrintableChars(const char* string, const Style style) const {
|
||||||
return getFont(style)->hasPrintableChars(string);
|
return getFont(style)->hasPrintableChars(string);
|
||||||
}
|
}
|
||||||
|
|
||||||
const EpdFontData* EpdFontFamily::getData(const EpdFontStyle style) const { return getFont(style)->data; }
|
const EpdFontData* EpdFontFamily::getData(const Style style) const { return getFont(style)->data; }
|
||||||
|
|
||||||
const EpdGlyph* EpdFontFamily::getGlyph(const uint32_t cp, const EpdFontStyle style) const {
|
const EpdGlyph* EpdFontFamily::getGlyph(const uint32_t cp, const Style style) const {
|
||||||
return getFont(style)->getGlyph(cp);
|
return getFont(style)->getGlyph(cp);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,24 +1,24 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "EpdFont.h"
|
#include "EpdFont.h"
|
||||||
|
|
||||||
enum EpdFontStyle { REGULAR, BOLD, ITALIC, BOLD_ITALIC };
|
|
||||||
|
|
||||||
class EpdFontFamily {
|
class EpdFontFamily {
|
||||||
|
public:
|
||||||
|
enum Style : uint8_t { REGULAR = 0, BOLD = 1, ITALIC = 2, BOLD_ITALIC = 3 };
|
||||||
|
|
||||||
|
explicit EpdFontFamily(const EpdFont* regular, const EpdFont* bold = nullptr, const EpdFont* italic = nullptr,
|
||||||
|
const EpdFont* boldItalic = nullptr)
|
||||||
|
: regular(regular), bold(bold), italic(italic), boldItalic(boldItalic) {}
|
||||||
|
~EpdFontFamily() = default;
|
||||||
|
void getTextDimensions(const char* string, int* w, int* h, Style style = REGULAR) const;
|
||||||
|
bool hasPrintableChars(const char* string, Style style = REGULAR) const;
|
||||||
|
const EpdFontData* getData(Style style = REGULAR) const;
|
||||||
|
const EpdGlyph* getGlyph(uint32_t cp, Style style = REGULAR) const;
|
||||||
|
|
||||||
|
private:
|
||||||
const EpdFont* regular;
|
const EpdFont* regular;
|
||||||
const EpdFont* bold;
|
const EpdFont* bold;
|
||||||
const EpdFont* italic;
|
const EpdFont* italic;
|
||||||
const EpdFont* boldItalic;
|
const EpdFont* boldItalic;
|
||||||
|
|
||||||
const EpdFont* getFont(EpdFontStyle style) const;
|
const EpdFont* getFont(Style style) const;
|
||||||
|
|
||||||
public:
|
|
||||||
explicit EpdFontFamily(const EpdFont* regular, const EpdFont* bold = nullptr, const EpdFont* italic = nullptr,
|
|
||||||
const EpdFont* boldItalic = nullptr)
|
|
||||||
: regular(regular), bold(bold), italic(italic), boldItalic(boldItalic) {}
|
|
||||||
~EpdFontFamily() = default;
|
|
||||||
void getTextDimensions(const char* string, int* w, int* h, EpdFontStyle style = REGULAR) const;
|
|
||||||
bool hasPrintableChars(const char* string, EpdFontStyle style = REGULAR) const;
|
|
||||||
|
|
||||||
const EpdFontData* getData(EpdFontStyle style = REGULAR) const;
|
|
||||||
const EpdGlyph* getGlyph(uint32_t cp, EpdFontStyle style = REGULAR) const;
|
|
||||||
};
|
};
|
||||||
|
|||||||
42
lib/EpdFont/builtinFonts/all.h
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <builtinFonts/bookerly_12_bold.h>
|
||||||
|
#include <builtinFonts/bookerly_12_bolditalic.h>
|
||||||
|
#include <builtinFonts/bookerly_12_italic.h>
|
||||||
|
#include <builtinFonts/bookerly_12_regular.h>
|
||||||
|
#include <builtinFonts/bookerly_14_bold.h>
|
||||||
|
#include <builtinFonts/bookerly_14_bolditalic.h>
|
||||||
|
#include <builtinFonts/bookerly_14_italic.h>
|
||||||
|
#include <builtinFonts/bookerly_14_regular.h>
|
||||||
|
#include <builtinFonts/bookerly_16_bold.h>
|
||||||
|
#include <builtinFonts/bookerly_16_bolditalic.h>
|
||||||
|
#include <builtinFonts/bookerly_16_italic.h>
|
||||||
|
#include <builtinFonts/bookerly_16_regular.h>
|
||||||
|
#include <builtinFonts/bookerly_18_bold.h>
|
||||||
|
#include <builtinFonts/bookerly_18_bolditalic.h>
|
||||||
|
#include <builtinFonts/bookerly_18_italic.h>
|
||||||
|
#include <builtinFonts/bookerly_18_regular.h>
|
||||||
|
#include <builtinFonts/notosans_8_regular.h>
|
||||||
|
#include <builtinFonts/notosans_12_bold.h>
|
||||||
|
#include <builtinFonts/notosans_12_bolditalic.h>
|
||||||
|
#include <builtinFonts/notosans_12_italic.h>
|
||||||
|
#include <builtinFonts/notosans_12_regular.h>
|
||||||
|
#include <builtinFonts/notosans_14_bold.h>
|
||||||
|
#include <builtinFonts/notosans_14_bolditalic.h>
|
||||||
|
#include <builtinFonts/notosans_14_italic.h>
|
||||||
|
#include <builtinFonts/notosans_14_regular.h>
|
||||||
|
#include <builtinFonts/notosans_16_bold.h>
|
||||||
|
#include <builtinFonts/notosans_16_bolditalic.h>
|
||||||
|
#include <builtinFonts/notosans_16_italic.h>
|
||||||
|
#include <builtinFonts/notosans_16_regular.h>
|
||||||
|
#include <builtinFonts/notosans_18_bold.h>
|
||||||
|
#include <builtinFonts/notosans_18_bolditalic.h>
|
||||||
|
#include <builtinFonts/notosans_18_italic.h>
|
||||||
|
#include <builtinFonts/notosans_18_regular.h>
|
||||||
|
#include <builtinFonts/ubuntu_10_bold.h>
|
||||||
|
#include <builtinFonts/ubuntu_10_regular.h>
|
||||||
|
#include <builtinFonts/ubuntu_12_bold.h>
|
||||||
|
#include <builtinFonts/ubuntu_12_regular.h>
|
||||||
|
|
||||||
|
// Custom fonts registry (generated by convert-builtin-fonts.sh)
|
||||||
|
#include <builtinFonts/custom/customFonts.h>
|
||||||
@ -1,505 +0,0 @@
|
|||||||
/**
|
|
||||||
* generated by fontconvert.py
|
|
||||||
* name: babyblue
|
|
||||||
* size: 8
|
|
||||||
* mode: 1-bit
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
#include "EpdFontData.h"
|
|
||||||
|
|
||||||
static const uint8_t babyblueBitmaps[3140] = {
|
|
||||||
0xFF, 0xFF, 0x30, 0xFF, 0xFF, 0xF0, 0x36, 0x1B, 0x0D, 0x9F, 0xFF, 0xF9, 0xB3, 0xFF, 0xFF, 0x36, 0x1B, 0x0D, 0x80,
|
|
||||||
0x18, 0x18, 0x7E, 0xFF, 0xDB, 0xD8, 0xFE, 0x7F, 0x9B, 0xDB, 0xFF, 0x7E, 0x18, 0x00, 0x71, 0x9F, 0x73, 0x6C, 0x6F,
|
|
||||||
0x8F, 0xE0, 0xFF, 0x83, 0xF8, 0xFB, 0x1B, 0x63, 0x7C, 0xC7, 0x00, 0x38, 0x3E, 0x1B, 0x0D, 0x87, 0xC3, 0xEF, 0xBF,
|
|
||||||
0x8E, 0xCF, 0x7F, 0x1E, 0xC0, 0xFF, 0x37, 0xEC, 0xCC, 0xCC, 0xCC, 0xCC, 0x67, 0x20, 0xCE, 0x73, 0x33, 0x33, 0x33,
|
|
||||||
0x33, 0x6C, 0x80, 0x32, 0xFF, 0xDE, 0xFE, 0x30, 0x18, 0x18, 0x18, 0xFF, 0xFF, 0x18, 0x18, 0x18, 0xBF, 0x00, 0xFF,
|
|
||||||
0xC0, 0x30, 0x0C, 0x31, 0xC6, 0x18, 0xE3, 0x1C, 0x63, 0x8C, 0x00, 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
|
|
||||||
0xC7, 0x7E, 0x3C, 0x19, 0xDF, 0xFD, 0x8C, 0x63, 0x18, 0xC6, 0x3C, 0x7E, 0xE7, 0x83, 0x07, 0x0E, 0x1C, 0x38, 0x70,
|
|
||||||
0xFE, 0xFF, 0x7E, 0xFF, 0xC3, 0x07, 0x3E, 0x3E, 0x07, 0x03, 0x83, 0xFF, 0x7E, 0x0E, 0x1E, 0x3E, 0x76, 0xE6, 0xFF,
|
|
||||||
0xFF, 0x06, 0x06, 0x06, 0x06, 0x7F, 0xFF, 0x06, 0x0F, 0xDF, 0xC1, 0x83, 0x87, 0xFD, 0xF0, 0x3E, 0x7F, 0xE3, 0xC0,
|
|
||||||
0xFC, 0xFE, 0xE7, 0xC3, 0xC7, 0x7E, 0x3C, 0xFF, 0xFF, 0xC0, 0xE0, 0xE0, 0x60, 0x70, 0x30, 0x18, 0x1C, 0x0C, 0x06,
|
|
||||||
0x00, 0x3C, 0x7E, 0x66, 0x66, 0x7E, 0x7E, 0xE7, 0xC3, 0xC7, 0x7E, 0x3C, 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC7, 0x7F,
|
|
||||||
0x3F, 0x07, 0x3E, 0x7C, 0xB0, 0x03, 0xB0, 0x03, 0xF8, 0x06, 0x3D, 0xF7, 0x8F, 0x0F, 0x87, 0x03, 0xFF, 0xFF, 0x00,
|
|
||||||
0xFF, 0xFF, 0x81, 0xE1, 0xF0, 0xF1, 0xEF, 0xBC, 0x40, 0x38, 0xFB, 0xBE, 0x30, 0xE3, 0x8E, 0x18, 0x30, 0x00, 0xC0,
|
|
||||||
0x0F, 0xE0, 0x7F, 0xC3, 0x83, 0x9D, 0xE7, 0xEF, 0xCF, 0x73, 0x3D, 0x8C, 0xF6, 0x33, 0xD8, 0xDB, 0x3F, 0xC6, 0x7E,
|
|
||||||
0x0C, 0x03, 0x18, 0x18, 0x7F, 0xE0, 0xFF, 0x00, 0x38, 0xFB, 0xBE, 0x3C, 0x7F, 0xFF, 0xE3, 0xC7, 0x8C, 0xFC, 0xFE,
|
|
||||||
0xC7, 0xC7, 0xFE, 0xFE, 0xC7, 0xC7, 0xFE, 0xFC, 0x3F, 0x3F, 0xF8, 0x78, 0x0C, 0x06, 0x03, 0x01, 0x83, 0x7F, 0x9F,
|
|
||||||
0x80, 0xFC, 0xFE, 0xC7, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0xFE, 0xFC, 0xFF, 0xFF, 0x06, 0x0F, 0xDF, 0xB0, 0x60, 0xFD,
|
|
||||||
0xFC, 0xFF, 0xFF, 0x06, 0x0F, 0xDF, 0xB0, 0x60, 0xC1, 0x80, 0x3F, 0x3F, 0xF8, 0x78, 0x0C, 0x7E, 0x3F, 0x07, 0x83,
|
|
||||||
0x7F, 0x9F, 0x80, 0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF, 0xF0, 0x0C, 0x30, 0xC3,
|
|
||||||
0x0C, 0x38, 0xF3, 0xFD, 0xE0, 0xC3, 0xC7, 0xCE, 0xDC, 0xF8, 0xF8, 0xDC, 0xCC, 0xC6, 0xC3, 0xC1, 0x83, 0x06, 0x0C,
|
|
||||||
0x18, 0x30, 0x60, 0xFD, 0xFC, 0xE1, 0xF8, 0x7F, 0x3F, 0xCF, 0xFF, 0xF7, 0xBD, 0xEF, 0x7B, 0xCC, 0xF3, 0x30, 0xC3,
|
|
||||||
0xE3, 0xF3, 0xF3, 0xFB, 0xDF, 0xCF, 0xCF, 0xC7, 0xC3, 0x3E, 0x3F, 0xB8, 0xF8, 0x3C, 0x1E, 0x0F, 0x07, 0x87, 0x7F,
|
|
||||||
0x1F, 0x00, 0xFE, 0xFF, 0xC3, 0xC3, 0xFF, 0xFE, 0xC0, 0xC0, 0xC0, 0xC0, 0x3E, 0x3F, 0xB8, 0xF8, 0x3C, 0x1E, 0x0F,
|
|
||||||
0x37, 0x9F, 0x7F, 0x1F, 0xC0, 0xFE, 0xFF, 0xC3, 0xC7, 0xFE, 0xFE, 0xC7, 0xC3, 0xC3, 0xC3, 0x7D, 0xFF, 0x1F, 0x87,
|
|
||||||
0xC3, 0xC1, 0xC3, 0xFE, 0xF8, 0xFF, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xC3, 0xC3, 0xC3, 0xC3,
|
|
||||||
0xC3, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0xC3, 0xC3, 0xC3, 0xE7, 0x66, 0x7E, 0x3C, 0x3C, 0x3C, 0x18, 0x83, 0x0F, 0x0C,
|
|
||||||
0x3C, 0x30, 0xF1, 0xE7, 0x67, 0x99, 0xBF, 0xE3, 0xCF, 0x0F, 0x3C, 0x3C, 0xF0, 0x61, 0x80, 0x80, 0xF8, 0x77, 0x38,
|
|
||||||
0xFC, 0x1E, 0x07, 0x83, 0xF1, 0xCE, 0xE1, 0xB0, 0x30, 0xC3, 0xC3, 0xE7, 0x7E, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18,
|
|
||||||
0xFF, 0xFF, 0x07, 0x0E, 0x1C, 0x38, 0x70, 0xE0, 0xFE, 0xFF, 0xFF, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xFE, 0x81, 0xC1,
|
|
||||||
0x83, 0x83, 0x83, 0x06, 0x06, 0x0C, 0x0C, 0xFF, 0x33, 0x33, 0x33, 0x33, 0x33, 0xFE, 0x18, 0x3C, 0x7E, 0x66, 0xE7,
|
|
||||||
0xC3, 0x83, 0xFF, 0xFF, 0x9D, 0x80, 0x7D, 0xFE, 0x1B, 0xFF, 0xF8, 0xFF, 0xBF, 0xC1, 0x83, 0xE7, 0xEC, 0xF8, 0xF1,
|
|
||||||
0xE7, 0xFD, 0xF0, 0x3C, 0xFF, 0x9E, 0x0C, 0x18, 0x9F, 0x9E, 0x06, 0x0C, 0xFB, 0xFE, 0x78, 0xF1, 0xE3, 0x7E, 0x7C,
|
|
||||||
0x3C, 0x7E, 0xE7, 0xFF, 0xFF, 0xC0, 0x7E, 0x3F, 0x3B, 0xD9, 0xFF, 0xB1, 0x8C, 0x63, 0x18, 0x3E, 0xFF, 0x9E, 0x3C,
|
|
||||||
0x78, 0xDF, 0x9F, 0x06, 0x0D, 0xF3, 0xC0, 0xC3, 0x0F, 0xBF, 0xEF, 0x3C, 0xF3, 0xCF, 0x30, 0xFB, 0xFF, 0xF0, 0x6D,
|
|
||||||
0x36, 0xDB, 0x6D, 0xBD, 0x00, 0xC3, 0x0C, 0xF7, 0xFB, 0xCE, 0x3C, 0xDB, 0x30, 0xFF, 0xFF, 0xF0, 0x7F, 0xBF, 0xFC,
|
|
||||||
0xCF, 0x33, 0xCC, 0xF3, 0x3C, 0xCF, 0x33, 0x7B, 0xFC, 0xF3, 0xCF, 0x3C, 0xF3, 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC7,
|
|
||||||
0x7E, 0x3C, 0x79, 0xFB, 0x3E, 0x3C, 0x79, 0xFF, 0x7C, 0xC1, 0x82, 0x00, 0x3C, 0xFF, 0x9E, 0x3C, 0x78, 0xDF, 0x9F,
|
|
||||||
0x06, 0x0C, 0x10, 0x77, 0xF7, 0x8C, 0x63, 0x18, 0x7D, 0xFF, 0x1F, 0xE7, 0xF0, 0xFF, 0xBE, 0x63, 0x3D, 0xE6, 0x31,
|
|
||||||
0x8C, 0x71, 0xC0, 0x8F, 0x3C, 0xF3, 0xCF, 0x3F, 0xDE, 0x83, 0xC3, 0xC7, 0x66, 0x66, 0x6E, 0x3C, 0x18, 0x80, 0xF3,
|
|
||||||
0x3D, 0xFD, 0xFE, 0x7F, 0x9F, 0xE3, 0x30, 0xCC, 0x83, 0xC7, 0x6E, 0x3C, 0x38, 0x7C, 0xE6, 0xC3, 0x83, 0xC7, 0x66,
|
|
||||||
0x6E, 0x3C, 0x3C, 0x18, 0x18, 0x18, 0x70, 0x60, 0xFF, 0xFC, 0x71, 0xC7, 0x1C, 0x3F, 0x7F, 0x19, 0xCC, 0x63, 0x3B,
|
|
||||||
0x98, 0x61, 0x8C, 0x63, 0x1C, 0x40, 0xFF, 0xFF, 0xFF, 0xF8, 0x83, 0x87, 0x0C, 0x30, 0xE1, 0xC7, 0x38, 0xC3, 0x0C,
|
|
||||||
0x63, 0x08, 0x00, 0x79, 0xFF, 0xE3, 0xC0, 0xF2, 0xFF, 0xFE, 0x18, 0x30, 0xF3, 0xFF, 0xFB, 0x36, 0x6E, 0x7E, 0x78,
|
|
||||||
0x60, 0xC1, 0x00, 0x3C, 0x7E, 0x66, 0x60, 0xFC, 0xFC, 0x30, 0x72, 0xFF, 0xFE, 0x83, 0xFF, 0x7E, 0x66, 0x66, 0x7E,
|
|
||||||
0xFE, 0x83, 0x83, 0xE7, 0x7E, 0x3C, 0xFF, 0xFF, 0xFF, 0xFF, 0x18, 0x18, 0xFF, 0xFC, 0xBF, 0xF8, 0x3C, 0x7E, 0x66,
|
|
||||||
0x7E, 0x7C, 0xEE, 0xC7, 0xC3, 0x77, 0x3E, 0x0C, 0x66, 0x66, 0x7C, 0x38, 0x9E, 0xE6, 0x3F, 0x1F, 0xEF, 0xFF, 0xFF,
|
|
||||||
0xF3, 0xFC, 0x3F, 0x3F, 0xFF, 0xDF, 0xDF, 0xE3, 0xF0, 0x77, 0xFF, 0xFF, 0xBC, 0x36, 0xFF, 0xF6, 0xCD, 0xCD, 0x8D,
|
|
||||||
0x80, 0xFF, 0xFF, 0x03, 0x03, 0x03, 0x3F, 0x1F, 0xEF, 0xFF, 0xFB, 0xF6, 0xFF, 0xBF, 0xEF, 0xDB, 0xF7, 0xDF, 0xE3,
|
|
||||||
0xF0, 0xFF, 0xFF, 0x6F, 0xFF, 0x60, 0x18, 0x18, 0x18, 0xFF, 0xFF, 0x18, 0x18, 0xFE, 0xFF, 0x77, 0xE6, 0x77, 0x73,
|
|
||||||
0xFF, 0x77, 0xEF, 0x7F, 0xB8, 0x7F, 0x00, 0xCF, 0x3C, 0xF3, 0xCF, 0x3F, 0xFE, 0xC3, 0x0C, 0x20, 0x7F, 0xFF, 0xFE,
|
|
||||||
0xFE, 0xFE, 0x7E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x16, 0xB0, 0x63, 0xEC, 0x37, 0xFB, 0x33, 0x30, 0x7B,
|
|
||||||
0xFC, 0xF3, 0xFD, 0xE0, 0x99, 0xF9, 0xF9, 0xB7, 0xFF, 0xB6, 0x00, 0x30, 0x07, 0x03, 0xF0, 0x7B, 0x0E, 0x31, 0xC3,
|
|
||||||
0x38, 0x37, 0x60, 0xEE, 0x1D, 0xE3, 0xBF, 0x33, 0xF6, 0x06, 0x30, 0x67, 0x0E, 0xF1, 0xCB, 0x38, 0x37, 0x03, 0xEF,
|
|
||||||
0x1D, 0xF3, 0x9F, 0x70, 0xEE, 0x0E, 0xC1, 0xF0, 0x70, 0x6F, 0x8E, 0xB9, 0xCB, 0xB8, 0xFF, 0x07, 0xE6, 0x1C, 0xE3,
|
|
||||||
0x9E, 0x73, 0xFE, 0x3F, 0xC0, 0x60, 0x30, 0x60, 0xC1, 0x87, 0x1C, 0x30, 0x60, 0xC6, 0xD9, 0xE1, 0x80, 0x30, 0x70,
|
|
||||||
0x61, 0xC7, 0xDD, 0xF1, 0xE3, 0xFF, 0xFF, 0x1E, 0x3C, 0x60, 0x18, 0x70, 0xC1, 0xC7, 0xDD, 0xF1, 0xE3, 0xFF, 0xFF,
|
|
||||||
0x1E, 0x3C, 0x60, 0x38, 0xF9, 0xB1, 0xC7, 0xDD, 0xF1, 0xE3, 0xFF, 0xFF, 0x1E, 0x3C, 0x60, 0x3C, 0xF9, 0xE1, 0xC7,
|
|
||||||
0xDD, 0xF1, 0xE3, 0xFF, 0xFF, 0x1E, 0x3C, 0x60, 0x6C, 0xD8, 0xE3, 0xEE, 0xF8, 0xF1, 0xFF, 0xFF, 0x8F, 0x1E, 0x30,
|
|
||||||
0x38, 0xF9, 0xF1, 0xC7, 0xDD, 0xF1, 0xE3, 0xFF, 0xFF, 0x1E, 0x3C, 0x60, 0x0F, 0xF8, 0x7F, 0xC7, 0xC0, 0x36, 0x03,
|
|
||||||
0xB0, 0x19, 0xF9, 0xFF, 0xCF, 0xE0, 0xE3, 0x06, 0x1F, 0xB0, 0xFE, 0x3F, 0x3F, 0xF8, 0x78, 0x0C, 0x06, 0x03, 0x01,
|
|
||||||
0x83, 0x7F, 0x9F, 0x86, 0x01, 0x83, 0x81, 0x80, 0x30, 0x70, 0x67, 0xFF, 0xF8, 0x30, 0x7E, 0xFD, 0x83, 0x07, 0xEF,
|
|
||||||
0xE0, 0x0C, 0x38, 0x67, 0xFF, 0xF8, 0x30, 0x7E, 0xFD, 0x83, 0x07, 0xEF, 0xE0, 0x18, 0x78, 0xF7, 0xFF, 0xF8, 0x30,
|
|
||||||
0x7E, 0xFD, 0x83, 0x07, 0xEF, 0xE0, 0x3C, 0x7B, 0xFF, 0xFC, 0x18, 0x3F, 0x7E, 0xC1, 0x83, 0xF7, 0xF0, 0x9D, 0x36,
|
|
||||||
0xDB, 0x6D, 0xB6, 0x7A, 0x6D, 0xB6, 0xDB, 0x6C, 0x6F, 0xB6, 0x66, 0x66, 0x66, 0x66, 0x60, 0xBB, 0x66, 0x66, 0x66,
|
|
||||||
0x66, 0x66, 0x7E, 0x3F, 0x98, 0xEC, 0x3F, 0xDF, 0xED, 0x86, 0xC7, 0x7F, 0x3F, 0x00, 0x1E, 0x3E, 0x3C, 0xC3, 0xE3,
|
|
||||||
0xF3, 0xF3, 0xFB, 0xDF, 0xCF, 0xCF, 0xC7, 0xC3, 0x18, 0x0E, 0x03, 0x07, 0xC7, 0xF7, 0x1F, 0x07, 0x83, 0xC1, 0xE0,
|
|
||||||
0xF0, 0xEF, 0xE3, 0xE0, 0x0C, 0x0E, 0x06, 0x07, 0xC7, 0xF7, 0x1F, 0x07, 0x83, 0xC1, 0xE0, 0xF0, 0xEF, 0xE3, 0xE0,
|
|
||||||
0x1C, 0x1F, 0x0D, 0x87, 0xC7, 0xF7, 0x1F, 0x07, 0x83, 0xC1, 0xE0, 0xF0, 0xEF, 0xE3, 0xE0, 0x1E, 0x1F, 0x0F, 0x07,
|
|
||||||
0xC7, 0xF7, 0x1F, 0x07, 0x83, 0xC1, 0xE0, 0xF0, 0xEF, 0xE3, 0xE0, 0x36, 0x1B, 0x0F, 0x8F, 0xEE, 0x3E, 0x0F, 0x07,
|
|
||||||
0x83, 0xC1, 0xE1, 0xDF, 0xC7, 0xC0, 0x8F, 0xF7, 0x9E, 0xFF, 0x30, 0x1F, 0xCF, 0xF7, 0x3D, 0x9F, 0x6E, 0xDF, 0x37,
|
|
||||||
0x8D, 0xC7, 0xFF, 0xB7, 0xC0, 0x30, 0x38, 0x18, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x0C,
|
|
||||||
0x1C, 0x18, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x18, 0x3C, 0x3C, 0xC3, 0xC3, 0xC3, 0xC3,
|
|
||||||
0xC3, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x3C, 0x3C, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x0C,
|
|
||||||
0x1C, 0x18, 0xC3, 0xC3, 0xE7, 0x7E, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0xC0, 0xFC, 0xFE, 0xC7, 0xC3, 0xC7, 0xFE,
|
|
||||||
0xFC, 0xC0, 0xC0, 0x38, 0xFB, 0xB6, 0x6D, 0xDB, 0x37, 0x67, 0xE7, 0xFF, 0x70, 0x30, 0x70, 0x63, 0xEF, 0xF0, 0xDF,
|
|
||||||
0xFF, 0xC7, 0xFD, 0xF8, 0x0C, 0x38, 0x63, 0xEF, 0xF0, 0xDF, 0xFF, 0xC7, 0xFD, 0xF8, 0x18, 0x78, 0xF3, 0xEF, 0xF0,
|
|
||||||
0xDF, 0xFF, 0xC7, 0xFD, 0xF8, 0x3C, 0xF9, 0xE3, 0xEF, 0xF0, 0xDF, 0xFF, 0xC7, 0xFD, 0xF8, 0x3C, 0x79, 0xF7, 0xF8,
|
|
||||||
0x6F, 0xFF, 0xE3, 0xFE, 0xFC, 0x18, 0x78, 0xF0, 0xC7, 0xDF, 0xE1, 0xBF, 0xFF, 0x8F, 0xFB, 0xF0, 0x7F, 0xEF, 0xFF,
|
|
||||||
0x86, 0x37, 0xFF, 0xFF, 0xFC, 0x61, 0xFF, 0xF7, 0xFE, 0x3C, 0xFF, 0x9E, 0x0C, 0x18, 0x9F, 0x9E, 0x18, 0x18, 0xE1,
|
|
||||||
0x80, 0x30, 0x38, 0x18, 0x3C, 0x7E, 0xE7, 0xFF, 0xFF, 0xC0, 0x7E, 0x3F, 0x0C, 0x1C, 0x18, 0x3C, 0x7E, 0xE7, 0xFF,
|
|
||||||
0xFF, 0xC0, 0x7E, 0x3F, 0x18, 0x3C, 0x3C, 0x3C, 0x7E, 0xE7, 0xFF, 0xFF, 0xC0, 0x7E, 0x3F, 0x3C, 0x3C, 0x3C, 0x7E,
|
|
||||||
0xE7, 0xFF, 0xFF, 0xC0, 0x7E, 0x3F, 0x9D, 0xA6, 0xDB, 0x6D, 0x80, 0x7F, 0x4D, 0xB6, 0xDB, 0x00, 0x6F, 0xB4, 0x66,
|
|
||||||
0x66, 0x66, 0x60, 0xBB, 0x46, 0x66, 0x66, 0x66, 0x3E, 0x3E, 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C,
|
|
||||||
0x3D, 0xF7, 0x9E, 0xFF, 0x3C, 0xF3, 0xCF, 0x3C, 0xC0, 0x30, 0x38, 0x18, 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC7, 0x7E,
|
|
||||||
0x3C, 0x0C, 0x1C, 0x18, 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x18, 0x3C, 0x3C, 0x3C, 0x7E, 0xE7, 0xC3,
|
|
||||||
0xC3, 0xC7, 0x7E, 0x3C, 0x1E, 0x3E, 0x3C, 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x3C, 0x3C, 0x3C, 0x7E,
|
|
||||||
0xE7, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x18, 0x18, 0xFF, 0xFF, 0x10, 0x18, 0x3F, 0x7F, 0xEF, 0xDF, 0xFB, 0xF7, 0xFE,
|
|
||||||
0xFC, 0x61, 0xC3, 0x23, 0xCF, 0x3C, 0xF3, 0xCF, 0xF7, 0x80, 0x18, 0xE3, 0x23, 0xCF, 0x3C, 0xF3, 0xCF, 0xF7, 0x80,
|
|
||||||
0x31, 0xE7, 0xA3, 0xCF, 0x3C, 0xF3, 0xCF, 0xF7, 0x80, 0x79, 0xE8, 0xF3, 0xCF, 0x3C, 0xF3, 0xFD, 0xE0, 0x0C, 0x1C,
|
|
||||||
0x18, 0x83, 0xC7, 0x66, 0x6E, 0x3C, 0x3C, 0x18, 0x18, 0x18, 0x70, 0x60, 0xC1, 0x83, 0xE7, 0xEE, 0xF8, 0xF1, 0xE7,
|
|
||||||
0xFD, 0xF3, 0x06, 0x08, 0x00, 0x3C, 0x3C, 0x83, 0xC7, 0x66, 0x6E, 0x3C, 0x3C, 0x18, 0x18, 0x18, 0x70, 0x60, 0x7C,
|
|
||||||
0xF8, 0xE3, 0xEE, 0xF8, 0xF1, 0xFF, 0xFF, 0x8F, 0x1E, 0x30, 0x7C, 0xF9, 0xF7, 0xF8, 0x6F, 0xFF, 0xE3, 0xFE, 0xFC,
|
|
||||||
0x6C, 0xF8, 0xE1, 0xC7, 0xDD, 0xF1, 0xE3, 0xFF, 0xFF, 0x1E, 0x3C, 0x60, 0x6C, 0xF8, 0xE3, 0xEF, 0xF0, 0xDF, 0xFF,
|
|
||||||
0xC7, 0xFD, 0xF8, 0x38, 0x7C, 0xEE, 0xC6, 0xC6, 0xFE, 0xFE, 0xC6, 0xC6, 0xC6, 0x0C, 0x0C, 0x0F, 0x07, 0x7C, 0xFE,
|
|
||||||
0x86, 0x7E, 0xFE, 0xC6, 0xFE, 0x7E, 0x06, 0x0C, 0x0F, 0x0F, 0x0C, 0x0E, 0x06, 0x07, 0xE7, 0xFF, 0x0F, 0x01, 0x80,
|
|
||||||
0xC0, 0x60, 0x30, 0x6F, 0xF3, 0xF0, 0x0C, 0x38, 0x61, 0xE7, 0xFC, 0xF0, 0x60, 0xC4, 0xFC, 0xF0, 0x0C, 0x0F, 0x07,
|
|
||||||
0x87, 0xE7, 0xFF, 0x0F, 0x01, 0x80, 0xC0, 0x60, 0x30, 0x6F, 0xF3, 0xF0, 0x18, 0x78, 0xF1, 0xE7, 0xFC, 0xF0, 0x60,
|
|
||||||
0xC4, 0xFC, 0xF0, 0x0C, 0x06, 0x0F, 0xCF, 0xFE, 0x1E, 0x03, 0x01, 0x80, 0xC0, 0x60, 0xDF, 0xE7, 0xE0, 0x18, 0x30,
|
|
||||||
0xF3, 0xFE, 0x78, 0x30, 0x62, 0x7E, 0x78, 0x1E, 0x0F, 0x03, 0x07, 0xE7, 0xFF, 0x0F, 0x01, 0x80, 0xC0, 0x60, 0x30,
|
|
||||||
0x6F, 0xF3, 0xF0, 0x3C, 0x78, 0x61, 0xE7, 0xFC, 0xF0, 0x60, 0xC4, 0xFC, 0xF0, 0x78, 0x78, 0x30, 0xFC, 0xFE, 0xC7,
|
|
||||||
0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0xFE, 0xFC, 0x01, 0x83, 0xC1, 0xE7, 0xC7, 0xE7, 0x33, 0x19, 0x8C, 0xC6, 0x3F, 0x0F,
|
|
||||||
0x80, 0x7E, 0x3F, 0x98, 0xEC, 0x3F, 0xDF, 0xED, 0x86, 0xC7, 0x7F, 0x3F, 0x00, 0x06, 0x1F, 0x1F, 0x3E, 0x7E, 0xE6,
|
|
||||||
0xC6, 0xC6, 0xC6, 0x7E, 0x3E, 0x3C, 0x7B, 0xFF, 0xFC, 0x18, 0x3F, 0x7E, 0xC1, 0x83, 0xF7, 0xF0, 0x3C, 0x3C, 0x3C,
|
|
||||||
0x7E, 0xE7, 0xFF, 0xFF, 0xC0, 0x7E, 0x3F, 0x66, 0xFC, 0xF7, 0xFF, 0xF8, 0x30, 0x7E, 0xFD, 0x83, 0x07, 0xEF, 0xE0,
|
|
||||||
0x66, 0x7E, 0x3C, 0x3C, 0x7E, 0xE7, 0xFF, 0xFF, 0xC0, 0x7E, 0x3F, 0x18, 0x33, 0xFF, 0xFC, 0x18, 0x3F, 0x7E, 0xC1,
|
|
||||||
0x83, 0xF7, 0xF0, 0x18, 0x18, 0x3C, 0x7E, 0xE7, 0xFF, 0xFF, 0xC0, 0x7E, 0x3F, 0xFE, 0xFE, 0xC0, 0xC0, 0xFC, 0xFC,
|
|
||||||
0xC0, 0xC0, 0xFC, 0xFE, 0x06, 0x0C, 0x0F, 0x0F, 0x3C, 0x7E, 0xE7, 0xFF, 0xFF, 0xC0, 0x7E, 0x3F, 0x06, 0x0C, 0x0F,
|
|
||||||
0x07, 0x3C, 0x78, 0x67, 0xFF, 0xF8, 0x30, 0x7E, 0xFD, 0x83, 0x07, 0xEF, 0xE0, 0x3C, 0x3C, 0x18, 0x3C, 0x7E, 0xE7,
|
|
||||||
0xFF, 0xFF, 0xC0, 0x7E, 0x3F, 0x0C, 0x0F, 0x07, 0x87, 0xE7, 0xFF, 0x0F, 0x01, 0x8F, 0xC7, 0xE0, 0xF0, 0x6F, 0xF3,
|
|
||||||
0xF0, 0x18, 0x78, 0xF1, 0xF7, 0xFC, 0xF1, 0xE3, 0xC6, 0xFC, 0xF8, 0x30, 0x6F, 0x9E, 0x00, 0x36, 0x1F, 0x07, 0x07,
|
|
||||||
0xE7, 0xFF, 0x0F, 0x01, 0x8F, 0xC7, 0xE0, 0xF0, 0x6F, 0xF3, 0xF0, 0x36, 0x7C, 0x71, 0xF7, 0xFC, 0xF1, 0xE3, 0xC6,
|
|
||||||
0xFC, 0xF8, 0x30, 0x6F, 0x9E, 0x00, 0x0C, 0x06, 0x0F, 0xCF, 0xFE, 0x1E, 0x03, 0x1F, 0x8F, 0xC1, 0xE0, 0xDF, 0xE7,
|
|
||||||
0xE0, 0x18, 0x30, 0xFB, 0xFE, 0x78, 0xF1, 0xE3, 0x7E, 0x7C, 0x18, 0x37, 0xCF, 0x00, 0x3F, 0x3F, 0xF8, 0x78, 0x0C,
|
|
||||||
0x7E, 0x3F, 0x07, 0x83, 0x7F, 0x9F, 0x83, 0x00, 0xC1, 0xC0, 0xE0, 0x18, 0x30, 0x61, 0xF7, 0xFC, 0xF1, 0xE3, 0xC6,
|
|
||||||
0xFC, 0xF8, 0x30, 0x6F, 0x9E, 0x00, 0x18, 0x3C, 0x3C, 0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3,
|
|
||||||
0x31, 0xE7, 0xB0, 0xC3, 0xEF, 0xFB, 0xCF, 0x3C, 0xF3, 0xCC, 0x61, 0xBF, 0xFF, 0xFD, 0x86, 0x7F, 0x9F, 0xE6, 0x19,
|
|
||||||
0x86, 0x61, 0x98, 0x60, 0x61, 0xF3, 0xE3, 0xE7, 0xEE, 0xD9, 0xB3, 0x66, 0xCD, 0x98, 0x7F, 0xEC, 0xC6, 0x31, 0x8C,
|
|
||||||
0x63, 0x18, 0xC6, 0x00, 0x7F, 0xEC, 0x86, 0x31, 0x8C, 0x63, 0x18, 0xFF, 0x66, 0x66, 0x66, 0x66, 0x66, 0xFF, 0x46,
|
|
||||||
0x66, 0x66, 0x66, 0x9F, 0xDC, 0xC6, 0x31, 0x8C, 0x63, 0x18, 0xC6, 0x00, 0x9F, 0xDC, 0x86, 0x31, 0x8C, 0x63, 0x18,
|
|
||||||
0x66, 0x66, 0x66, 0x66, 0x66, 0x6C, 0xF6, 0x66, 0x46, 0x66, 0x66, 0x66, 0x6C, 0xF6, 0xEF, 0xFF, 0xFF, 0xBF, 0xFF,
|
|
||||||
0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xE3, 0xF3, 0xFF, 0xDE, 0xDE, 0xE7, 0xBD, 0xEF, 0x7B, 0xDE, 0xC6, 0x33, 0x10,
|
|
||||||
0x0C, 0x3C, 0x78, 0x60, 0xC1, 0x83, 0x06, 0x0D, 0x1B, 0x37, 0xE7, 0x80, 0x6F, 0xB4, 0x66, 0x66, 0x66, 0x66, 0x6C,
|
|
||||||
0x80, 0xC3, 0xC7, 0xCE, 0xDC, 0xF8, 0xF8, 0xDC, 0xCC, 0xC6, 0xC3, 0x38, 0x1C, 0x38, 0x30, 0xC3, 0x0C, 0xF7, 0xFB,
|
|
||||||
0xCE, 0x3C, 0xDB, 0x37, 0x0E, 0x71, 0x80, 0x8F, 0x7F, 0xBC, 0xE3, 0xCD, 0xB3, 0x30, 0xE1, 0x86, 0x0C, 0x18, 0x30,
|
|
||||||
0x60, 0xC1, 0x83, 0x07, 0xEF, 0xE0, 0x7A, 0x6D, 0xB6, 0xDB, 0x6C, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xFD,
|
|
||||||
0xFD, 0xC1, 0xC7, 0x0C, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0xEF, 0xEC, 0x0D, 0x9B, 0x36, 0x6C, 0x18, 0x30, 0x60,
|
|
||||||
0xC1, 0xFB, 0xF8, 0x3F, 0xFF, 0xCC, 0xCC, 0xCC, 0xC0, 0xC1, 0x83, 0x06, 0x0C, 0xD9, 0xB0, 0x60, 0xFD, 0xFC, 0xCC,
|
|
||||||
0xCC, 0xFF, 0xCC, 0xCC, 0x60, 0x60, 0x78, 0x78, 0xF0, 0xE0, 0x60, 0x60, 0x7E, 0x7F, 0x66, 0x67, 0xFE, 0x66, 0x66,
|
|
||||||
0x0C, 0x1C, 0x18, 0xC3, 0xE3, 0xF3, 0xF3, 0xFB, 0xDF, 0xCF, 0xCF, 0xC7, 0xC3, 0x18, 0x63, 0x8C, 0x7B, 0xFC, 0xF3,
|
|
||||||
0xCF, 0x3C, 0xF3, 0xC3, 0xE3, 0xF3, 0xF3, 0xFB, 0xDF, 0xCF, 0xCF, 0xC7, 0xC3, 0x38, 0x1C, 0x38, 0x30, 0x7B, 0xFC,
|
|
||||||
0xF3, 0xCF, 0x3C, 0xF3, 0x70, 0xE7, 0x18, 0x3C, 0x3C, 0x18, 0xC3, 0xE3, 0xF3, 0xF3, 0xFB, 0xDF, 0xCF, 0xCF, 0xC7,
|
|
||||||
0xC3, 0x79, 0xE3, 0x1E, 0xFF, 0x3C, 0xF3, 0xCF, 0x3C, 0xC0, 0x81, 0x83, 0x05, 0xE7, 0xEC, 0xD9, 0xB3, 0x66, 0xCD,
|
|
||||||
0x98, 0xC3, 0xE3, 0xF3, 0xF3, 0xFB, 0xDF, 0xCF, 0xCF, 0xC7, 0xC3, 0x03, 0x03, 0x03, 0x7B, 0xFC, 0xF3, 0xCF, 0x3C,
|
|
||||||
0xF3, 0x0C, 0x31, 0x84, 0x3E, 0x1F, 0x0F, 0x8F, 0xEE, 0x3E, 0x0F, 0x07, 0x83, 0xC1, 0xE1, 0xDF, 0xC7, 0xC0, 0x3C,
|
|
||||||
0x3C, 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x36, 0x1F, 0x07, 0x07, 0xC7, 0xF7, 0x1F, 0x07, 0x83, 0xC1,
|
|
||||||
0xE0, 0xF0, 0xEF, 0xE3, 0xE0, 0x36, 0x3E, 0x1C, 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x1B, 0x1F, 0x8D,
|
|
||||||
0x87, 0xC7, 0xF7, 0x1F, 0x07, 0x83, 0xC1, 0xE0, 0xF0, 0xEF, 0xE3, 0xE0, 0x1E, 0x3E, 0x3C, 0x3C, 0x7E, 0xE7, 0xC3,
|
|
||||||
0xC3, 0xC7, 0x7E, 0x3C, 0x3F, 0xFB, 0xFF, 0xF9, 0xC1, 0x86, 0x0C, 0x3F, 0x61, 0xFB, 0x0C, 0x18, 0x60, 0xC7, 0x03,
|
|
||||||
0xFF, 0x0F, 0xFC, 0x3D, 0xE3, 0xFF, 0xB9, 0xCF, 0x87, 0xFC, 0x3F, 0xE3, 0x85, 0xFF, 0xE7, 0xBE, 0x18, 0x38, 0x30,
|
|
||||||
0xFE, 0xFF, 0xC3, 0xC7, 0xFE, 0xFE, 0xC7, 0xC3, 0xC3, 0xC3, 0x33, 0x98, 0xEF, 0xEF, 0x18, 0xC6, 0x30, 0xFE, 0xFF,
|
|
||||||
0xC3, 0xC7, 0xFE, 0xFE, 0xC7, 0xC3, 0xC3, 0xC3, 0x38, 0x1C, 0x38, 0x30, 0x77, 0xF7, 0x8C, 0x63, 0x18, 0xE3, 0xB9,
|
|
||||||
0x80, 0x3C, 0x3C, 0x18, 0xFE, 0xFF, 0xC3, 0xC7, 0xFE, 0xFE, 0xC7, 0xC3, 0xC3, 0xC3, 0xF7, 0x98, 0xEF, 0xEF, 0x18,
|
|
||||||
0xC6, 0x30, 0x18, 0x70, 0xC3, 0xEF, 0xF8, 0xFC, 0x3E, 0x1E, 0x0E, 0x1F, 0xF7, 0xC0, 0x18, 0x70, 0xC3, 0xEF, 0xF8,
|
|
||||||
0xFF, 0x3F, 0x87, 0xFD, 0xF0, 0x18, 0x78, 0xF3, 0xEF, 0xF8, 0xFC, 0x3E, 0x1E, 0x0E, 0x1F, 0xF7, 0xC0, 0x18, 0x78,
|
|
||||||
0xF3, 0xEF, 0xF8, 0xFF, 0x3F, 0x87, 0xFD, 0xF0, 0x7D, 0xFF, 0x1F, 0x87, 0xC3, 0xC1, 0xC3, 0xFE, 0xF8, 0xC0, 0xC7,
|
|
||||||
0x0C, 0x00, 0x7D, 0xFF, 0x1F, 0xE7, 0xF0, 0xFF, 0xBE, 0x18, 0x18, 0xE1, 0x80, 0x3C, 0x78, 0x63, 0xEF, 0xF8, 0xFC,
|
|
||||||
0x3E, 0x1E, 0x0E, 0x1F, 0xF7, 0xC0, 0x3C, 0x78, 0x63, 0xEF, 0xF8, 0xFF, 0x3F, 0x87, 0xFD, 0xF0, 0xFF, 0xFF, 0xC6,
|
|
||||||
0x03, 0x01, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x01, 0x83, 0x81, 0x80, 0x63, 0x3D, 0xE6, 0x31, 0x8C, 0x71,
|
|
||||||
0xCC, 0x37, 0x30, 0x3C, 0x3C, 0x18, 0xFF, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x0D, 0xB6, 0xFF,
|
|
||||||
0xF1, 0x86, 0x18, 0x61, 0xC3, 0x80, 0xFF, 0xFF, 0x18, 0x18, 0x7E, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x63, 0x3D, 0xE6,
|
|
||||||
0x73, 0xCC, 0x71, 0xC0, 0x1E, 0x3E, 0x3C, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x3D, 0xF7,
|
|
||||||
0xA3, 0xCF, 0x3C, 0xF3, 0xCF, 0xF7, 0x80, 0x3C, 0x3C, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C,
|
|
||||||
0x79, 0xE8, 0xF3, 0xCF, 0x3C, 0xF3, 0xFD, 0xE0, 0x36, 0x3E, 0x1C, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7,
|
|
||||||
0x7E, 0x3C, 0x6D, 0xF3, 0xA3, 0xCF, 0x3C, 0xF3, 0xCF, 0xF7, 0x80, 0x18, 0x3C, 0x3C, 0xDB, 0xC3, 0xC3, 0xC3, 0xC3,
|
|
||||||
0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x31, 0xE7, 0xAF, 0xCF, 0x3C, 0xF3, 0xCF, 0xF7, 0x80, 0x1E, 0x3E, 0x3C, 0xC3, 0xC3,
|
|
||||||
0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x3D, 0xF7, 0xA3, 0xCF, 0x3C, 0xF3, 0xCF, 0xF7, 0x80, 0xC3, 0xC3,
|
|
||||||
0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x18, 0x30, 0x3C, 0x1C, 0x8D, 0x9B, 0x36, 0x6C, 0xD9, 0xBF, 0x3E,
|
|
||||||
0x0C, 0x30, 0x78, 0x70, 0x03, 0x00, 0x1E, 0x00, 0x78, 0x20, 0xC3, 0xC3, 0x0F, 0x0C, 0x3C, 0x79, 0xD9, 0xE6, 0x6F,
|
|
||||||
0xF8, 0xF3, 0xC3, 0xCF, 0x0F, 0x3C, 0x18, 0x60, 0x0C, 0x07, 0x81, 0xE2, 0x03, 0xCC, 0xF7, 0xF7, 0xF9, 0xFE, 0x7F,
|
|
||||||
0x8C, 0xC3, 0x30, 0x18, 0x3C, 0x3C, 0xC3, 0xC3, 0xE7, 0x7E, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x3C,
|
|
||||||
0x83, 0xC7, 0x66, 0x6E, 0x3C, 0x3C, 0x18, 0x18, 0x18, 0x70, 0x60, 0x3C, 0x3C, 0xC3, 0xC3, 0xE7, 0x7E, 0x3C, 0x18,
|
|
||||||
0x18, 0x18, 0x18, 0x18, 0x0C, 0x1C, 0x18, 0xFF, 0xFF, 0x07, 0x0E, 0x1C, 0x38, 0x70, 0xE0, 0xFE, 0xFF, 0x0C, 0x38,
|
|
||||||
0x67, 0xFF, 0xE3, 0x8E, 0x38, 0xE1, 0xFB, 0xF8, 0x0C, 0x0C, 0xFF, 0xFF, 0x07, 0x0E, 0x1C, 0x38, 0x70, 0xE0, 0xFE,
|
|
||||||
0xFF, 0x18, 0x33, 0xFF, 0xF1, 0xC7, 0x1C, 0x70, 0xFD, 0xFC, 0x3C, 0x3C, 0x18, 0xFF, 0xFF, 0x07, 0x0E, 0x1C, 0x38,
|
|
||||||
0x70, 0xE0, 0xFE, 0xFF, 0x3C, 0x78, 0x67, 0xFF, 0xE3, 0x8E, 0x38, 0xE1, 0xFB, 0xF8, 0x1E, 0x3F, 0x73, 0xFE, 0xFE,
|
|
||||||
0xFE, 0xFE, 0x62, 0x3F, 0x1E,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const EpdGlyph babyblueGlyphs[] = {
|
|
||||||
{0, 0, 5, 0, 0, 0, 0}, //
|
|
||||||
{2, 10, 3, 1, 10, 3, 0}, // !
|
|
||||||
{4, 5, 5, 1, 11, 3, 3}, // "
|
|
||||||
{9, 11, 10, 0, 11, 13, 6}, // #
|
|
||||||
{8, 14, 9, 0, 12, 14, 19}, // $
|
|
||||||
{11, 11, 13, 1, 11, 16, 33}, // %
|
|
||||||
{9, 11, 11, 1, 11, 13, 49}, // &
|
|
||||||
{2, 4, 3, 1, 11, 1, 62}, // '
|
|
||||||
{4, 15, 5, 1, 11, 8, 63}, // (
|
|
||||||
{4, 15, 5, 1, 11, 8, 71}, // )
|
|
||||||
{6, 6, 6, 0, 11, 5, 79}, // *
|
|
||||||
{8, 8, 9, 0, 9, 8, 84}, // +
|
|
||||||
{2, 5, 3, 1, 3, 2, 92}, // ,
|
|
||||||
{5, 2, 5, 0, 5, 2, 94}, // -
|
|
||||||
{2, 2, 3, 1, 2, 1, 96}, // .
|
|
||||||
{6, 11, 6, 0, 11, 9, 97}, // /
|
|
||||||
{8, 11, 9, 0, 11, 11, 106}, // 0
|
|
||||||
{5, 11, 6, 1, 11, 7, 117}, // 1
|
|
||||||
{8, 11, 9, 0, 11, 11, 124}, // 2
|
|
||||||
{8, 11, 10, 1, 11, 11, 135}, // 3
|
|
||||||
{8, 11, 9, 0, 11, 11, 146}, // 4
|
|
||||||
{7, 11, 9, 1, 11, 10, 157}, // 5
|
|
||||||
{8, 11, 9, 0, 11, 11, 167}, // 6
|
|
||||||
{9, 11, 10, 0, 11, 13, 178}, // 7
|
|
||||||
{8, 11, 9, 0, 11, 11, 191}, // 8
|
|
||||||
{8, 11, 9, 0, 11, 11, 202}, // 9
|
|
||||||
{2, 8, 3, 1, 8, 2, 213}, // :
|
|
||||||
{2, 11, 3, 1, 8, 3, 215}, // ;
|
|
||||||
{7, 8, 9, 1, 9, 7, 218}, // <
|
|
||||||
{8, 5, 9, 0, 8, 5, 225}, // =
|
|
||||||
{7, 8, 7, 0, 9, 7, 230}, // >
|
|
||||||
{7, 11, 9, 1, 11, 10, 237}, // ?
|
|
||||||
{14, 15, 15, 0, 11, 27, 247}, // @
|
|
||||||
{7, 10, 9, 1, 10, 9, 274}, // A
|
|
||||||
{8, 10, 10, 1, 10, 10, 283}, // B
|
|
||||||
{9, 10, 11, 1, 10, 12, 293}, // C
|
|
||||||
{8, 10, 10, 1, 10, 10, 305}, // D
|
|
||||||
{7, 10, 9, 1, 10, 9, 315}, // E
|
|
||||||
{7, 10, 9, 1, 10, 9, 324}, // F
|
|
||||||
{9, 10, 11, 1, 10, 12, 333}, // G
|
|
||||||
{8, 10, 10, 1, 10, 10, 345}, // H
|
|
||||||
{2, 10, 3, 1, 10, 3, 355}, // I
|
|
||||||
{6, 10, 6, 0, 10, 8, 358}, // J
|
|
||||||
{8, 10, 10, 1, 10, 10, 366}, // K
|
|
||||||
{7, 10, 9, 1, 10, 9, 376}, // L
|
|
||||||
{10, 10, 12, 1, 10, 13, 385}, // M
|
|
||||||
{8, 10, 10, 1, 10, 10, 398}, // N
|
|
||||||
{9, 10, 11, 1, 10, 12, 408}, // O
|
|
||||||
{8, 10, 10, 1, 10, 10, 420}, // P
|
|
||||||
{9, 10, 11, 1, 10, 12, 430}, // Q
|
|
||||||
{8, 10, 10, 1, 10, 10, 442}, // R
|
|
||||||
{7, 10, 9, 1, 10, 9, 452}, // S
|
|
||||||
{8, 10, 9, 0, 10, 10, 461}, // T
|
|
||||||
{8, 10, 10, 1, 10, 10, 471}, // U
|
|
||||||
{8, 10, 10, 1, 10, 10, 481}, // V
|
|
||||||
{14, 10, 15, 0, 10, 18, 491}, // W
|
|
||||||
{10, 10, 11, 0, 10, 13, 509}, // X
|
|
||||||
{8, 10, 10, 1, 10, 10, 522}, // Y
|
|
||||||
{8, 10, 9, 0, 10, 10, 532}, // Z
|
|
||||||
{4, 14, 5, 1, 10, 7, 542}, // [
|
|
||||||
{7, 10, 7, 0, 10, 9, 549}, // <backslash>
|
|
||||||
{4, 14, 4, 0, 10, 7, 558}, // ]
|
|
||||||
{8, 7, 9, 0, 11, 7, 565}, // ^
|
|
||||||
{8, 2, 9, 0, -2, 2, 572}, // _
|
|
||||||
{3, 3, 3, 0, 11, 2, 574}, // `
|
|
||||||
{7, 8, 7, 0, 8, 7, 576}, // a
|
|
||||||
{7, 10, 9, 1, 10, 9, 583}, // b
|
|
||||||
{7, 8, 7, 0, 8, 7, 592}, // c
|
|
||||||
{7, 10, 7, 0, 10, 9, 599}, // d
|
|
||||||
{8, 8, 9, 0, 8, 8, 608}, // e
|
|
||||||
{5, 11, 5, 0, 11, 7, 616}, // f
|
|
||||||
{7, 12, 7, 0, 8, 11, 623}, // g
|
|
||||||
{6, 10, 7, 1, 10, 8, 634}, // h
|
|
||||||
{2, 10, 3, 1, 10, 3, 642}, // i
|
|
||||||
{3, 14, 3, 0, 10, 6, 645}, // j
|
|
||||||
{6, 10, 7, 1, 10, 8, 651}, // k
|
|
||||||
{2, 10, 3, 1, 10, 3, 659}, // l
|
|
||||||
{10, 8, 12, 1, 8, 10, 662}, // m
|
|
||||||
{6, 8, 7, 1, 8, 6, 672}, // n
|
|
||||||
{8, 8, 9, 0, 8, 8, 678}, // o
|
|
||||||
{7, 11, 9, 1, 8, 10, 686}, // p
|
|
||||||
{7, 11, 7, 0, 8, 10, 696}, // q
|
|
||||||
{5, 8, 6, 1, 8, 5, 706}, // r
|
|
||||||
{7, 8, 7, 0, 8, 7, 711}, // s
|
|
||||||
{5, 10, 5, 0, 10, 7, 718}, // t
|
|
||||||
{6, 8, 7, 1, 8, 6, 725}, // u
|
|
||||||
{8, 8, 9, 0, 8, 8, 731}, // v
|
|
||||||
{10, 8, 11, 0, 8, 10, 739}, // w
|
|
||||||
{8, 8, 9, 0, 8, 8, 749}, // x
|
|
||||||
{8, 11, 9, 0, 8, 11, 757}, // y
|
|
||||||
{7, 8, 7, 0, 8, 7, 768}, // z
|
|
||||||
{5, 15, 5, 0, 11, 10, 775}, // {
|
|
||||||
{2, 15, 3, 1, 11, 4, 785}, // |
|
|
||||||
{6, 15, 6, 0, 11, 12, 789}, // }
|
|
||||||
{9, 3, 10, 0, 7, 4, 801}, // ~
|
|
||||||
{2, 12, 4, 2, 8, 3, 805}, // ¡
|
|
||||||
{7, 13, 7, 0, 10, 12, 808}, // ¢
|
|
||||||
{8, 10, 9, 0, 10, 10, 820}, // £
|
|
||||||
{8, 8, 9, 0, 9, 8, 830}, // ¤
|
|
||||||
{8, 10, 9, 0, 10, 10, 838}, // ¥
|
|
||||||
{2, 15, 3, 1, 11, 4, 848}, // ¦
|
|
||||||
{8, 15, 9, 0, 11, 15, 852}, // §
|
|
||||||
{5, 3, 5, 0, 11, 2, 867}, // ¨
|
|
||||||
{10, 11, 11, 0, 11, 14, 869}, // ©
|
|
||||||
{5, 6, 5, 0, 11, 4, 883}, // ª
|
|
||||||
{7, 7, 9, 1, 8, 7, 887}, // «
|
|
||||||
{8, 5, 9, 0, 8, 5, 894}, // ¬
|
|
||||||
{10, 11, 11, 0, 11, 14, 899}, // ®
|
|
||||||
{8, 2, 9, 0, 12, 2, 913}, // ¯
|
|
||||||
{4, 5, 5, 1, 11, 3, 915}, // °
|
|
||||||
{8, 9, 9, 0, 9, 9, 918}, // ±
|
|
||||||
{5, 8, 5, 0, 11, 5, 927}, // ²
|
|
||||||
{5, 6, 5, 0, 11, 4, 932}, // ³
|
|
||||||
{3, 3, 4, 1, 11, 2, 936}, // ´
|
|
||||||
{6, 12, 9, 2, 8, 9, 938}, // µ
|
|
||||||
{8, 14, 9, 0, 10, 14, 947}, // ¶
|
|
||||||
{2, 2, 3, 1, 6, 1, 961}, // ·
|
|
||||||
{4, 4, 4, 0, 0, 2, 962}, // ¸
|
|
||||||
{4, 7, 4, 0, 12, 4, 964}, // ¹
|
|
||||||
{6, 6, 6, 0, 11, 5, 968}, // º
|
|
||||||
{7, 7, 9, 1, 8, 7, 973}, // »
|
|
||||||
{12, 12, 13, 0, 12, 18, 980}, // ¼
|
|
||||||
{12, 11, 13, 0, 11, 17, 998}, // ½
|
|
||||||
{12, 11, 13, 0, 11, 17, 1015}, // ¾
|
|
||||||
{7, 12, 9, 1, 8, 11, 1032}, // ¿
|
|
||||||
{7, 13, 9, 1, 13, 12, 1043}, // À
|
|
||||||
{7, 13, 9, 1, 13, 12, 1055}, // Á
|
|
||||||
{7, 13, 9, 1, 13, 12, 1067}, // Â
|
|
||||||
{7, 13, 9, 1, 13, 12, 1079}, // Ã
|
|
||||||
{7, 12, 9, 1, 12, 11, 1091}, // Ä
|
|
||||||
{7, 13, 9, 1, 13, 12, 1102}, // Å
|
|
||||||
{13, 11, 14, 0, 11, 18, 1114}, // Æ
|
|
||||||
{9, 14, 11, 1, 10, 16, 1132}, // Ç
|
|
||||||
{7, 13, 9, 1, 13, 12, 1148}, // È
|
|
||||||
{7, 13, 9, 1, 13, 12, 1160}, // É
|
|
||||||
{7, 13, 9, 1, 13, 12, 1172}, // Ê
|
|
||||||
{7, 12, 9, 1, 12, 11, 1184}, // Ë
|
|
||||||
{3, 13, 3, 0, 13, 5, 1195}, // Ì
|
|
||||||
{3, 13, 4, 1, 13, 5, 1200}, // Í
|
|
||||||
{4, 13, 4, 0, 13, 7, 1205}, // Î
|
|
||||||
{4, 12, 4, 0, 12, 6, 1212}, // Ï
|
|
||||||
{9, 10, 10, 0, 10, 12, 1218}, // Ð
|
|
||||||
{8, 13, 10, 1, 13, 13, 1230}, // Ñ
|
|
||||||
{9, 13, 11, 1, 13, 15, 1243}, // Ò
|
|
||||||
{9, 13, 11, 1, 13, 15, 1258}, // Ó
|
|
||||||
{9, 13, 11, 1, 13, 15, 1273}, // Ô
|
|
||||||
{9, 13, 11, 1, 13, 15, 1288}, // Õ
|
|
||||||
{9, 12, 11, 1, 12, 14, 1303}, // Ö
|
|
||||||
{6, 6, 7, 1, 8, 5, 1317}, // ×
|
|
||||||
{10, 10, 11, 0, 10, 13, 1322}, // Ø
|
|
||||||
{8, 13, 10, 1, 13, 13, 1335}, // Ù
|
|
||||||
{8, 13, 10, 1, 13, 13, 1348}, // Ú
|
|
||||||
{8, 13, 10, 1, 13, 13, 1361}, // Û
|
|
||||||
{8, 12, 10, 1, 12, 12, 1374}, // Ü
|
|
||||||
{8, 13, 10, 1, 13, 13, 1386}, // Ý
|
|
||||||
{8, 10, 10, 1, 10, 10, 1399}, // Þ
|
|
||||||
{7, 11, 9, 1, 11, 10, 1409}, // ß
|
|
||||||
{7, 11, 7, 0, 11, 10, 1419}, // à
|
|
||||||
{7, 11, 7, 0, 11, 10, 1429}, // á
|
|
||||||
{7, 11, 7, 0, 11, 10, 1439}, // â
|
|
||||||
{7, 11, 7, 0, 11, 10, 1449}, // ã
|
|
||||||
{7, 10, 7, 0, 10, 9, 1459}, // ä
|
|
||||||
{7, 12, 7, 0, 12, 11, 1468}, // å
|
|
||||||
{12, 8, 13, 0, 8, 12, 1479}, // æ
|
|
||||||
{7, 12, 7, 0, 8, 11, 1491}, // ç
|
|
||||||
{8, 11, 9, 0, 11, 11, 1502}, // è
|
|
||||||
{8, 11, 9, 0, 11, 11, 1513}, // é
|
|
||||||
{8, 11, 9, 0, 11, 11, 1524}, // ê
|
|
||||||
{8, 10, 9, 0, 10, 10, 1535}, // ë
|
|
||||||
{3, 11, 3, 0, 11, 5, 1545}, // ì
|
|
||||||
{3, 11, 4, 1, 11, 5, 1550}, // í
|
|
||||||
{4, 11, 4, 0, 11, 6, 1555}, // î
|
|
||||||
{4, 10, 4, 0, 10, 5, 1561}, // ï
|
|
||||||
{8, 11, 9, 0, 11, 11, 1566}, // ð
|
|
||||||
{6, 11, 7, 1, 11, 9, 1577}, // ñ
|
|
||||||
{8, 11, 9, 0, 11, 11, 1586}, // ò
|
|
||||||
{8, 11, 9, 0, 11, 11, 1597}, // ó
|
|
||||||
{8, 11, 9, 0, 11, 11, 1608}, // ô
|
|
||||||
{8, 11, 9, 0, 11, 11, 1619}, // õ
|
|
||||||
{8, 10, 9, 0, 10, 10, 1630}, // ö
|
|
||||||
{8, 6, 9, 0, 8, 6, 1640}, // ÷
|
|
||||||
{8, 8, 9, 0, 8, 8, 1646}, // ø
|
|
||||||
{6, 11, 7, 1, 11, 9, 1654}, // ù
|
|
||||||
{6, 11, 7, 1, 11, 9, 1663}, // ú
|
|
||||||
{6, 11, 7, 1, 11, 9, 1672}, // û
|
|
||||||
{6, 10, 7, 1, 10, 8, 1681}, // ü
|
|
||||||
{8, 14, 9, 0, 11, 14, 1689}, // ý
|
|
||||||
{7, 13, 9, 1, 10, 12, 1703}, // þ
|
|
||||||
{8, 13, 9, 0, 10, 13, 1715}, // ÿ
|
|
||||||
{7, 12, 9, 1, 12, 11, 1728}, // Ā
|
|
||||||
{7, 10, 7, 0, 10, 9, 1739}, // ā
|
|
||||||
{7, 13, 9, 1, 13, 12, 1748}, // Ă
|
|
||||||
{7, 11, 7, 0, 11, 10, 1760}, // ă
|
|
||||||
{8, 14, 10, 1, 10, 14, 1770}, // Ą
|
|
||||||
{8, 12, 9, 0, 8, 12, 1784}, // ą
|
|
||||||
{9, 13, 11, 1, 13, 15, 1796}, // Ć
|
|
||||||
{7, 11, 7, 0, 11, 10, 1811}, // ć
|
|
||||||
{9, 13, 11, 1, 13, 15, 1821}, // Ĉ
|
|
||||||
{7, 11, 7, 0, 11, 10, 1836}, // ĉ
|
|
||||||
{9, 12, 11, 1, 12, 14, 1846}, // Ċ
|
|
||||||
{7, 10, 7, 0, 10, 9, 1860}, // ċ
|
|
||||||
{9, 13, 11, 1, 13, 15, 1869}, // Č
|
|
||||||
{7, 11, 7, 0, 11, 10, 1884}, // č
|
|
||||||
{8, 13, 10, 1, 13, 13, 1894}, // Ď
|
|
||||||
{9, 11, 10, 0, 11, 13, 1907}, // ď
|
|
||||||
{9, 10, 10, 0, 10, 12, 1920}, // Đ
|
|
||||||
{8, 11, 9, 0, 11, 11, 1932}, // đ
|
|
||||||
{7, 12, 9, 1, 12, 11, 1943}, // Ē
|
|
||||||
{8, 10, 9, 0, 10, 10, 1954}, // ē
|
|
||||||
{7, 13, 9, 1, 13, 12, 1964}, // Ĕ
|
|
||||||
{8, 11, 9, 0, 11, 11, 1976}, // ĕ
|
|
||||||
{7, 12, 9, 1, 12, 11, 1987}, // Ė
|
|
||||||
{8, 10, 9, 0, 10, 10, 1998}, // ė
|
|
||||||
{8, 14, 10, 1, 10, 14, 2008}, // Ę
|
|
||||||
{8, 12, 9, 0, 8, 12, 2022}, // ę
|
|
||||||
{7, 13, 9, 1, 13, 12, 2034}, // Ě
|
|
||||||
{8, 11, 9, 0, 11, 11, 2046}, // ě
|
|
||||||
{9, 13, 11, 1, 13, 15, 2057}, // Ĝ
|
|
||||||
{7, 15, 7, 0, 11, 14, 2072}, // ĝ
|
|
||||||
{9, 13, 11, 1, 13, 15, 2086}, // Ğ
|
|
||||||
{7, 15, 7, 0, 11, 14, 2101}, // ğ
|
|
||||||
{9, 12, 11, 1, 12, 14, 2115}, // Ġ
|
|
||||||
{7, 14, 7, 0, 10, 13, 2129}, // ġ
|
|
||||||
{9, 14, 11, 1, 10, 16, 2142}, // Ģ
|
|
||||||
{7, 15, 7, 0, 11, 14, 2158}, // ģ
|
|
||||||
{8, 13, 10, 1, 13, 13, 2172}, // Ĥ
|
|
||||||
{6, 13, 7, 1, 13, 10, 2185}, // ĥ
|
|
||||||
{10, 10, 11, 0, 10, 13, 2195}, // Ħ
|
|
||||||
{7, 11, 7, 0, 11, 10, 2208}, // ħ
|
|
||||||
{5, 13, 5, 0, 13, 9, 2218}, // Ĩ
|
|
||||||
{5, 11, 5, 0, 11, 7, 2227}, // ĩ
|
|
||||||
{4, 12, 4, 0, 12, 6, 2234}, // Ī
|
|
||||||
{4, 10, 4, 0, 10, 5, 2240}, // ī
|
|
||||||
{5, 13, 5, 0, 13, 9, 2245}, // Ĭ
|
|
||||||
{5, 11, 5, 0, 11, 7, 2254}, // ĭ
|
|
||||||
{4, 14, 4, 0, 10, 7, 2261}, // Į
|
|
||||||
{4, 14, 4, 0, 10, 7, 2268}, // į
|
|
||||||
{2, 12, 3, 1, 12, 3, 2275}, // İ
|
|
||||||
{2, 8, 3, 1, 8, 2, 2278}, // ı
|
|
||||||
{8, 10, 10, 1, 10, 10, 2280}, // IJ
|
|
||||||
{5, 14, 6, 1, 10, 9, 2290}, // ij
|
|
||||||
{7, 13, 7, 0, 13, 12, 2299}, // Ĵ
|
|
||||||
{4, 15, 4, 0, 11, 8, 2311}, // ĵ
|
|
||||||
{8, 14, 10, 1, 10, 14, 2319}, // Ķ
|
|
||||||
{6, 14, 7, 1, 10, 11, 2333}, // ķ
|
|
||||||
{6, 8, 7, 1, 8, 6, 2344}, // ĸ
|
|
||||||
{7, 13, 9, 1, 13, 12, 2350}, // Ĺ
|
|
||||||
{3, 13, 4, 1, 13, 5, 2362}, // ĺ
|
|
||||||
{7, 14, 9, 1, 10, 13, 2367}, // Ļ
|
|
||||||
{4, 14, 4, 0, 10, 7, 2380}, // ļ
|
|
||||||
{7, 11, 9, 1, 11, 10, 2387}, // Ľ
|
|
||||||
{4, 11, 5, 1, 11, 6, 2397}, // ľ
|
|
||||||
{7, 10, 9, 1, 10, 9, 2403}, // Ŀ
|
|
||||||
{4, 10, 5, 1, 10, 5, 2412}, // ŀ
|
|
||||||
{8, 10, 9, 0, 10, 10, 2417}, // Ł
|
|
||||||
{4, 10, 4, 0, 10, 5, 2427}, // ł
|
|
||||||
{8, 13, 10, 1, 13, 13, 2432}, // Ń
|
|
||||||
{6, 12, 7, 1, 12, 9, 2445}, // ń
|
|
||||||
{8, 14, 10, 1, 10, 14, 2454}, // Ņ
|
|
||||||
{6, 12, 7, 1, 8, 9, 2468}, // ņ
|
|
||||||
{8, 13, 10, 1, 13, 13, 2477}, // Ň
|
|
||||||
{6, 11, 7, 1, 11, 9, 2490}, // ň
|
|
||||||
{7, 11, 7, 0, 11, 10, 2499}, // ʼn
|
|
||||||
{8, 13, 10, 1, 10, 13, 2509}, // Ŋ
|
|
||||||
{6, 12, 7, 1, 8, 9, 2522}, // ŋ
|
|
||||||
{9, 12, 11, 1, 12, 14, 2531}, // Ō
|
|
||||||
{8, 10, 9, 0, 10, 10, 2545}, // ō
|
|
||||||
{9, 13, 11, 1, 13, 15, 2555}, // Ŏ
|
|
||||||
{8, 11, 9, 0, 11, 11, 2570}, // ŏ
|
|
||||||
{9, 13, 11, 1, 13, 15, 2581}, // Ő
|
|
||||||
{8, 11, 9, 0, 11, 11, 2596}, // ő
|
|
||||||
{13, 11, 15, 1, 11, 18, 2607}, // Œ
|
|
||||||
{13, 8, 14, 0, 8, 13, 2625}, // œ
|
|
||||||
{8, 13, 10, 1, 13, 13, 2638}, // Ŕ
|
|
||||||
{5, 11, 6, 1, 11, 7, 2651}, // ŕ
|
|
||||||
{8, 14, 10, 1, 10, 14, 2658}, // Ŗ
|
|
||||||
{5, 12, 6, 1, 8, 8, 2672}, // ŗ
|
|
||||||
{8, 13, 10, 1, 13, 13, 2680}, // Ř
|
|
||||||
{5, 11, 6, 1, 11, 7, 2693}, // ř
|
|
||||||
{7, 13, 9, 1, 13, 12, 2700}, // Ś
|
|
||||||
{7, 11, 7, 0, 11, 10, 2712}, // ś
|
|
||||||
{7, 13, 9, 1, 13, 12, 2722}, // Ŝ
|
|
||||||
{7, 11, 7, 0, 11, 10, 2734}, // ŝ
|
|
||||||
{7, 14, 9, 1, 10, 13, 2744}, // Ş
|
|
||||||
{7, 12, 7, 0, 8, 11, 2757}, // ş
|
|
||||||
{7, 13, 9, 1, 13, 12, 2768}, // Š
|
|
||||||
{7, 11, 7, 0, 11, 10, 2780}, // š
|
|
||||||
{9, 14, 10, 0, 10, 16, 2790}, // Ţ
|
|
||||||
{5, 14, 5, 0, 10, 9, 2806}, // ţ
|
|
||||||
{8, 13, 9, 0, 13, 13, 2815}, // Ť
|
|
||||||
{6, 11, 6, 0, 11, 9, 2828}, // ť
|
|
||||||
{8, 10, 9, 0, 10, 10, 2837}, // Ŧ
|
|
||||||
{5, 10, 5, 0, 10, 7, 2847}, // ŧ
|
|
||||||
{8, 13, 10, 1, 13, 13, 2854}, // Ũ
|
|
||||||
{6, 11, 7, 1, 11, 9, 2867}, // ũ
|
|
||||||
{8, 12, 10, 1, 12, 12, 2876}, // Ū
|
|
||||||
{6, 10, 7, 1, 10, 8, 2888}, // ū
|
|
||||||
{8, 13, 10, 1, 13, 13, 2896}, // Ŭ
|
|
||||||
{6, 11, 7, 1, 11, 9, 2909}, // ŭ
|
|
||||||
{8, 13, 10, 1, 13, 13, 2918}, // Ů
|
|
||||||
{6, 11, 7, 1, 11, 9, 2931}, // ů
|
|
||||||
{8, 13, 10, 1, 13, 13, 2940}, // Ű
|
|
||||||
{6, 11, 7, 1, 11, 9, 2953}, // ű
|
|
||||||
{8, 14, 10, 1, 10, 14, 2962}, // Ų
|
|
||||||
{7, 12, 9, 1, 8, 11, 2976}, // ų
|
|
||||||
{14, 13, 15, 0, 13, 23, 2987}, // Ŵ
|
|
||||||
{10, 11, 11, 0, 11, 14, 3010}, // ŵ
|
|
||||||
{8, 13, 10, 1, 13, 13, 3024}, // Ŷ
|
|
||||||
{8, 14, 9, 0, 11, 14, 3037}, // ŷ
|
|
||||||
{8, 12, 10, 1, 12, 12, 3051}, // Ÿ
|
|
||||||
{8, 13, 9, 0, 13, 13, 3063}, // Ź
|
|
||||||
{7, 11, 7, 0, 11, 10, 3076}, // ź
|
|
||||||
{8, 12, 9, 0, 12, 12, 3086}, // Ż
|
|
||||||
{7, 10, 7, 0, 10, 9, 3098}, // ż
|
|
||||||
{8, 13, 9, 0, 13, 13, 3107}, // Ž
|
|
||||||
{7, 11, 7, 0, 11, 10, 3120}, // ž
|
|
||||||
{8, 10, 9, 0, 10, 10, 3130}, // €
|
|
||||||
};
|
|
||||||
|
|
||||||
static const EpdUnicodeInterval babyblueIntervals[] = {
|
|
||||||
{0x20, 0x7E, 0x0}, {0xA1, 0xAC, 0x5F}, {0xAE, 0xFF, 0x6B}, {0x100, 0x17E, 0xBD}, {0x20AC, 0x20AC, 0x13C},
|
|
||||||
};
|
|
||||||
|
|
||||||
static const EpdFontData babyblue = {
|
|
||||||
babyblueBitmaps, babyblueGlyphs, babyblueIntervals, 5, 17, 13, -4, false,
|
|
||||||
};
|
|
||||||
4086
lib/EpdFont/builtinFonts/bookerly_12_bold.h
Normal file
4168
lib/EpdFont/builtinFonts/bookerly_12_bolditalic.h
Normal file
3933
lib/EpdFont/builtinFonts/bookerly_12_italic.h
Normal file
3824
lib/EpdFont/builtinFonts/bookerly_12_regular.h
Normal file
5087
lib/EpdFont/builtinFonts/bookerly_14_bold.h
Normal file
5276
lib/EpdFont/builtinFonts/bookerly_14_bolditalic.h
Normal file
4950
lib/EpdFont/builtinFonts/bookerly_14_italic.h
Normal file
4812
lib/EpdFont/builtinFonts/bookerly_14_regular.h
Normal file
6236
lib/EpdFont/builtinFonts/bookerly_16_bold.h
Normal file
6448
lib/EpdFont/builtinFonts/bookerly_16_bolditalic.h
Normal file
6064
lib/EpdFont/builtinFonts/bookerly_16_italic.h
Normal file
5901
lib/EpdFont/builtinFonts/bookerly_16_regular.h
Normal file
7964
lib/EpdFont/builtinFonts/bookerly_18_bold.h
Normal file
8172
lib/EpdFont/builtinFonts/bookerly_18_bolditalic.h
Normal file
7685
lib/EpdFont/builtinFonts/bookerly_18_italic.h
Normal file
7507
lib/EpdFont/builtinFonts/bookerly_18_regular.h
Normal file
BIN
lib/EpdFont/builtinFonts/custom/FernMicro/FernMicro-Bold.ttf
Normal file
BIN
lib/EpdFont/builtinFonts/custom/FernMicro/FernMicro-Italic.ttf
Normal file
BIN
lib/EpdFont/builtinFonts/custom/FernMicro/FernMicro-Regular.ttf
Normal file
1555
lib/EpdFont/builtinFonts/custom/atkinsonhyperlegiblenext_12_bold.h
Normal file
1588
lib/EpdFont/builtinFonts/custom/atkinsonhyperlegiblenext_12_italic.h
Normal file
1986
lib/EpdFont/builtinFonts/custom/atkinsonhyperlegiblenext_14_bold.h
Normal file
2012
lib/EpdFont/builtinFonts/custom/atkinsonhyperlegiblenext_14_italic.h
Normal file
2393
lib/EpdFont/builtinFonts/custom/atkinsonhyperlegiblenext_16_bold.h
Normal file
2427
lib/EpdFont/builtinFonts/custom/atkinsonhyperlegiblenext_16_italic.h
Normal file
2978
lib/EpdFont/builtinFonts/custom/atkinsonhyperlegiblenext_18_bold.h
Normal file
3018
lib/EpdFont/builtinFonts/custom/atkinsonhyperlegiblenext_18_italic.h
Normal file
99
lib/EpdFont/builtinFonts/custom/customFonts.h
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
/**
|
||||||
|
* Generated by convert-builtin-fonts.sh
|
||||||
|
* Registry of available custom fonts
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <EpdFont.h>
|
||||||
|
#include <EpdFontFamily.h>
|
||||||
|
|
||||||
|
class GfxRenderer;
|
||||||
|
|
||||||
|
#define CUSTOM_FONT_COUNT 2
|
||||||
|
|
||||||
|
static const char* CUSTOM_FONT_NAMES[] = {
|
||||||
|
"AtkinsonHyperlegibleNext",
|
||||||
|
"FernMicro"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Include all custom font headers
|
||||||
|
#include <builtinFonts/custom/atkinsonhyperlegiblenext_12_regular.h>
|
||||||
|
#include <builtinFonts/custom/atkinsonhyperlegiblenext_12_italic.h>
|
||||||
|
#include <builtinFonts/custom/atkinsonhyperlegiblenext_12_bold.h>
|
||||||
|
#include <builtinFonts/custom/atkinsonhyperlegiblenext_12_bolditalic.h>
|
||||||
|
#include <builtinFonts/custom/atkinsonhyperlegiblenext_14_regular.h>
|
||||||
|
#include <builtinFonts/custom/atkinsonhyperlegiblenext_14_italic.h>
|
||||||
|
#include <builtinFonts/custom/atkinsonhyperlegiblenext_14_bold.h>
|
||||||
|
#include <builtinFonts/custom/atkinsonhyperlegiblenext_14_bolditalic.h>
|
||||||
|
#include <builtinFonts/custom/atkinsonhyperlegiblenext_16_regular.h>
|
||||||
|
#include <builtinFonts/custom/atkinsonhyperlegiblenext_16_italic.h>
|
||||||
|
#include <builtinFonts/custom/atkinsonhyperlegiblenext_16_bold.h>
|
||||||
|
#include <builtinFonts/custom/atkinsonhyperlegiblenext_16_bolditalic.h>
|
||||||
|
#include <builtinFonts/custom/atkinsonhyperlegiblenext_18_regular.h>
|
||||||
|
#include <builtinFonts/custom/atkinsonhyperlegiblenext_18_italic.h>
|
||||||
|
#include <builtinFonts/custom/atkinsonhyperlegiblenext_18_bold.h>
|
||||||
|
#include <builtinFonts/custom/atkinsonhyperlegiblenext_18_bolditalic.h>
|
||||||
|
#include <builtinFonts/custom/fernmicro_12_regular.h>
|
||||||
|
#include <builtinFonts/custom/fernmicro_12_italic.h>
|
||||||
|
#include <builtinFonts/custom/fernmicro_12_bold.h>
|
||||||
|
#include <builtinFonts/custom/fernmicro_12_bolditalic.h>
|
||||||
|
#include <builtinFonts/custom/fernmicro_14_regular.h>
|
||||||
|
#include <builtinFonts/custom/fernmicro_14_italic.h>
|
||||||
|
#include <builtinFonts/custom/fernmicro_14_bold.h>
|
||||||
|
#include <builtinFonts/custom/fernmicro_14_bolditalic.h>
|
||||||
|
#include <builtinFonts/custom/fernmicro_16_regular.h>
|
||||||
|
#include <builtinFonts/custom/fernmicro_16_italic.h>
|
||||||
|
#include <builtinFonts/custom/fernmicro_16_bold.h>
|
||||||
|
#include <builtinFonts/custom/fernmicro_16_bolditalic.h>
|
||||||
|
#include <builtinFonts/custom/fernmicro_18_regular.h>
|
||||||
|
#include <builtinFonts/custom/fernmicro_18_italic.h>
|
||||||
|
#include <builtinFonts/custom/fernmicro_18_bold.h>
|
||||||
|
#include <builtinFonts/custom/fernmicro_18_bolditalic.h>
|
||||||
|
|
||||||
|
// Extern EpdFont declarations for custom fonts
|
||||||
|
extern EpdFont atkinsonhyperlegiblenext12RegularFont;
|
||||||
|
extern EpdFont atkinsonhyperlegiblenext12ItalicFont;
|
||||||
|
extern EpdFont atkinsonhyperlegiblenext12BoldFont;
|
||||||
|
extern EpdFont atkinsonhyperlegiblenext12BoldItalicFont;
|
||||||
|
extern EpdFont atkinsonhyperlegiblenext14RegularFont;
|
||||||
|
extern EpdFont atkinsonhyperlegiblenext14ItalicFont;
|
||||||
|
extern EpdFont atkinsonhyperlegiblenext14BoldFont;
|
||||||
|
extern EpdFont atkinsonhyperlegiblenext14BoldItalicFont;
|
||||||
|
extern EpdFont atkinsonhyperlegiblenext16RegularFont;
|
||||||
|
extern EpdFont atkinsonhyperlegiblenext16ItalicFont;
|
||||||
|
extern EpdFont atkinsonhyperlegiblenext16BoldFont;
|
||||||
|
extern EpdFont atkinsonhyperlegiblenext16BoldItalicFont;
|
||||||
|
extern EpdFont atkinsonhyperlegiblenext18RegularFont;
|
||||||
|
extern EpdFont atkinsonhyperlegiblenext18ItalicFont;
|
||||||
|
extern EpdFont atkinsonhyperlegiblenext18BoldFont;
|
||||||
|
extern EpdFont atkinsonhyperlegiblenext18BoldItalicFont;
|
||||||
|
extern EpdFont fernmicro12RegularFont;
|
||||||
|
extern EpdFont fernmicro12ItalicFont;
|
||||||
|
extern EpdFont fernmicro12BoldFont;
|
||||||
|
extern EpdFont fernmicro12BoldItalicFont;
|
||||||
|
extern EpdFont fernmicro14RegularFont;
|
||||||
|
extern EpdFont fernmicro14ItalicFont;
|
||||||
|
extern EpdFont fernmicro14BoldFont;
|
||||||
|
extern EpdFont fernmicro14BoldItalicFont;
|
||||||
|
extern EpdFont fernmicro16RegularFont;
|
||||||
|
extern EpdFont fernmicro16ItalicFont;
|
||||||
|
extern EpdFont fernmicro16BoldFont;
|
||||||
|
extern EpdFont fernmicro16BoldItalicFont;
|
||||||
|
extern EpdFont fernmicro18RegularFont;
|
||||||
|
extern EpdFont fernmicro18ItalicFont;
|
||||||
|
extern EpdFont fernmicro18BoldFont;
|
||||||
|
extern EpdFont fernmicro18BoldItalicFont;
|
||||||
|
|
||||||
|
// Extern EpdFontFamily declarations for custom fonts
|
||||||
|
extern EpdFontFamily atkinsonhyperlegiblenext12FontFamily;
|
||||||
|
extern EpdFontFamily atkinsonhyperlegiblenext14FontFamily;
|
||||||
|
extern EpdFontFamily atkinsonhyperlegiblenext16FontFamily;
|
||||||
|
extern EpdFontFamily atkinsonhyperlegiblenext18FontFamily;
|
||||||
|
extern EpdFontFamily fernmicro12FontFamily;
|
||||||
|
extern EpdFontFamily fernmicro14FontFamily;
|
||||||
|
extern EpdFontFamily fernmicro16FontFamily;
|
||||||
|
extern EpdFontFamily fernmicro18FontFamily;
|
||||||
|
|
||||||
|
// Function to register all custom fonts with the renderer
|
||||||
|
void registerCustomFonts(GfxRenderer& renderer);
|
||||||
|
|
||||||