Merge remote-tracking branch 'origin' into feature/add-epub-css-parsing
* origin: fix: truncate chapter names that are too long (#422) feat: dict based Hyphenation (#305) fix: render U+FFFD replacement character instead of ? (#366) fix: Invert colors on home screen cover overlay when recent book is selected (#390) Adds KOReader Sync support (#232) feat: Change keyboard "caps" to "shift" & Wrap Keyboard (#377) fix: XTC 1-bit thumb BMP polarity inversion (#373)
This commit is contained in:
commit
be2de1123b
2
.gitignore
vendored
2
.gitignore
vendored
@ -4,3 +4,5 @@
|
||||
.vscode
|
||||
lib/EpdFont/fontsrc
|
||||
*.generated.h
|
||||
build
|
||||
**/__pycache__/
|
||||
66
docs/hyphenation-trie-format.md
Normal file
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
|
||||
```
|
||||
@ -22,8 +22,7 @@ void EpdFont::getTextBounds(const char* string, const int startX, const int star
|
||||
const EpdGlyph* glyph = getGlyph(cp);
|
||||
|
||||
if (!glyph) {
|
||||
// TODO: Replace with fallback glyph property?
|
||||
glyph = getGlyph('?');
|
||||
glyph = getGlyph(REPLACEMENT_GLYPH);
|
||||
}
|
||||
|
||||
if (!glyph) {
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t bookerly_12_boldBitmaps[51217] = {
|
||||
static const uint8_t bookerly_12_boldBitmaps[51367] = {
|
||||
0x0A, 0x83, 0xFC, 0x3F, 0x87, 0xF4, 0x7F, 0x43, 0xF4, 0x3F, 0x03, 0xF0, 0x3F, 0x03, 0xF0, 0x3F,
|
||||
0x03, 0xF0, 0x2F, 0x01, 0xF0, 0x00, 0x00, 0x10, 0x2F, 0xC3, 0xFC, 0x3F, 0xC0, 0x50, 0x7E, 0x1F,
|
||||
0x7F, 0x8F, 0xDF, 0xD3, 0xF3, 0xF4, 0xFC, 0xFD, 0x3F, 0x2F, 0x0F, 0xCB, 0xC3, 0xF2, 0xE0, 0xF8,
|
||||
@ -3209,7 +3209,16 @@ static const uint8_t bookerly_12_boldBitmaps[51217] = {
|
||||
0x2F, 0xFF, 0xFF, 0xFE, 0xBF, 0xFF, 0xFF, 0xFA, 0xFF, 0xFF, 0xFF, 0xE0, 0x2F, 0x47, 0xFC, 0x7F,
|
||||
0xC3, 0xF4, 0x00, 0x00, 0x1F, 0x80, 0x0B, 0xC0, 0x0B, 0xD0, 0xFF, 0x40, 0xBF, 0x80, 0x7F, 0xC3,
|
||||
0xFD, 0x02, 0xFE, 0x01, 0xFF, 0x0B, 0xE0, 0x07, 0xF0, 0x03, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x7F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x74, 0x1F, 0x80, 0x00,
|
||||
0x00, 0x00, 0x74, 0x00, 0x78, 0x00, 0x00, 0x00, 0x7D, 0x04, 0x0B, 0x80, 0x00, 0x00, 0x7F, 0xFF,
|
||||
0xE0, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xFD, 0x2F, 0x80, 0x00, 0x7F, 0xFF, 0xFF, 0x87, 0xF8, 0x00,
|
||||
0x7F, 0xFF, 0xFF, 0xE1, 0xFF, 0x80, 0x7F, 0xFF, 0xFF, 0x80, 0xBF, 0xF8, 0x2F, 0xFF, 0xF8, 0x00,
|
||||
0x3F, 0xFE, 0x02, 0xFF, 0xFC, 0x00, 0xBF, 0xFE, 0x00, 0x2F, 0xFF, 0x42, 0xFF, 0xFE, 0x00, 0x02,
|
||||
0xFF, 0xE2, 0xFF, 0xFE, 0x00, 0x00, 0x2F, 0xFC, 0xBF, 0xFE, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFE,
|
||||
0x00, 0x00, 0x00, 0x2F, 0xD2, 0xFE, 0x00, 0x00, 0x00, 0x02, 0xF0, 0x3E, 0x00, 0x00, 0x00, 0x00,
|
||||
0x2D, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph bookerly_12_boldGlyphs[] = {
|
||||
@ -3941,6 +3950,7 @@ static const EpdGlyph bookerly_12_boldGlyphs[] = {
|
||||
{ 15, 18, 25, 5, 18, 68, 51112 }, // ⊥
|
||||
{ 6, 5, 16, 5, 9, 8, 51180 }, // ⋅
|
||||
{ 23, 5, 25, 1, 9, 29, 51188 }, // ⋯
|
||||
{ 25, 24, 25, 0, 21, 150, 51217 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval bookerly_12_boldIntervals[] = {
|
||||
@ -4004,13 +4014,14 @@ static const EpdUnicodeInterval bookerly_12_boldIntervals[] = {
|
||||
{ 0x22A5, 0x22A5, 0x2D5 },
|
||||
{ 0x22C5, 0x22C5, 0x2D6 },
|
||||
{ 0x22EF, 0x22EF, 0x2D7 },
|
||||
{ 0xFFFD, 0xFFFD, 0x2D8 },
|
||||
};
|
||||
|
||||
static const EpdFontData bookerly_12_bold = {
|
||||
bookerly_12_boldBitmaps,
|
||||
bookerly_12_boldGlyphs,
|
||||
bookerly_12_boldIntervals,
|
||||
60,
|
||||
61,
|
||||
33,
|
||||
27,
|
||||
-7,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t bookerly_12_bolditalicBitmaps[52522] = {
|
||||
static const uint8_t bookerly_12_bolditalicBitmaps[52672] = {
|
||||
0x00, 0x68, 0x01, 0xFC, 0x03, 0xF8, 0x07, 0xF4, 0x07, 0xF0, 0x0B, 0xE0, 0x0F, 0xD0, 0x0F, 0xC0,
|
||||
0x0F, 0xC0, 0x0F, 0x80, 0x1F, 0x40, 0x1F, 0x00, 0x2F, 0x00, 0x2D, 0x00, 0x00, 0x00, 0x04, 0x00,
|
||||
0x7F, 0x00, 0xBF, 0x00, 0xBD, 0x00, 0x10, 0x00, 0x0B, 0x87, 0xC2, 0xF4, 0xFC, 0x3F, 0x2F, 0x83,
|
||||
@ -3290,7 +3290,16 @@ static const uint8_t bookerly_12_bolditalicBitmaps[52522] = {
|
||||
0xC0, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x2F, 0xFF, 0xFF, 0xFE, 0xBF, 0xFF, 0xFF,
|
||||
0xFA, 0xFF, 0xFF, 0xFF, 0xE0, 0x2F, 0x47, 0xFC, 0x7F, 0xC3, 0xF4, 0x00, 0x00, 0x1F, 0x80, 0x0B,
|
||||
0xC0, 0x0B, 0xD0, 0xFF, 0x40, 0xBF, 0x80, 0x7F, 0xC3, 0xFD, 0x02, 0xFE, 0x01, 0xFF, 0x0B, 0xE0,
|
||||
0x07, 0xF0, 0x03, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x07, 0xF0, 0x03, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x7F,
|
||||
0xF8, 0x00, 0x00, 0x00, 0x00, 0x74, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x74, 0x00, 0x78, 0x00, 0x00,
|
||||
0x00, 0x7D, 0x04, 0x0B, 0x80, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0xF8, 0x00, 0x00, 0x7F, 0xFF, 0xFD,
|
||||
0x2F, 0x80, 0x00, 0x7F, 0xFF, 0xFF, 0x87, 0xF8, 0x00, 0x7F, 0xFF, 0xFF, 0xE1, 0xFF, 0x80, 0x7F,
|
||||
0xFF, 0xFF, 0x80, 0xBF, 0xF8, 0x2F, 0xFF, 0xF8, 0x00, 0x3F, 0xFE, 0x02, 0xFF, 0xFC, 0x00, 0xBF,
|
||||
0xFE, 0x00, 0x2F, 0xFF, 0x42, 0xFF, 0xFE, 0x00, 0x02, 0xFF, 0xE2, 0xFF, 0xFE, 0x00, 0x00, 0x2F,
|
||||
0xFC, 0xBF, 0xFE, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x2F, 0xD2, 0xFE, 0x00,
|
||||
0x00, 0x00, 0x02, 0xF0, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x2D, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x02,
|
||||
0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph bookerly_12_bolditalicGlyphs[] = {
|
||||
@ -4022,6 +4031,7 @@ static const EpdGlyph bookerly_12_bolditalicGlyphs[] = {
|
||||
{ 15, 18, 25, 5, 18, 68, 52417 }, // ⊥
|
||||
{ 6, 5, 16, 5, 9, 8, 52485 }, // ⋅
|
||||
{ 23, 5, 25, 1, 9, 29, 52493 }, // ⋯
|
||||
{ 25, 24, 25, 0, 21, 150, 52522 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval bookerly_12_bolditalicIntervals[] = {
|
||||
@ -4085,13 +4095,14 @@ static const EpdUnicodeInterval bookerly_12_bolditalicIntervals[] = {
|
||||
{ 0x22A5, 0x22A5, 0x2D5 },
|
||||
{ 0x22C5, 0x22C5, 0x2D6 },
|
||||
{ 0x22EF, 0x22EF, 0x2D7 },
|
||||
{ 0xFFFD, 0xFFFD, 0x2D8 },
|
||||
};
|
||||
|
||||
static const EpdFontData bookerly_12_bolditalic = {
|
||||
bookerly_12_bolditalicBitmaps,
|
||||
bookerly_12_bolditalicGlyphs,
|
||||
bookerly_12_bolditalicIntervals,
|
||||
60,
|
||||
61,
|
||||
33,
|
||||
27,
|
||||
-7,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t bookerly_12_italicBitmaps[48812] = {
|
||||
static const uint8_t bookerly_12_italicBitmaps[48962] = {
|
||||
0x00, 0x14, 0x03, 0xD0, 0x1F, 0x00, 0xF8, 0x03, 0xD0, 0x0F, 0x00, 0x7C, 0x02, 0xD0, 0x0B, 0x00,
|
||||
0x3C, 0x00, 0xE0, 0x03, 0x80, 0x1D, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x1D, 0x01, 0xF8, 0x07,
|
||||
0xC0, 0x04, 0x00, 0x0A, 0x07, 0x0B, 0x87, 0xC3, 0xD2, 0xE0, 0xF0, 0xB4, 0x38, 0x3C, 0x0D, 0x0E,
|
||||
@ -3058,7 +3058,17 @@ static const uint8_t bookerly_12_italicBitmaps[48812] = {
|
||||
0x00, 0x00, 0x2E, 0x00, 0x00, 0x00, 0xB8, 0x00, 0x00, 0x02, 0xE0, 0x00, 0x00, 0x0B, 0x80, 0x00,
|
||||
0x00, 0x2E, 0x00, 0x00, 0x00, 0xB8, 0x00, 0x05, 0x56, 0xE5, 0x54, 0xBF, 0xFF, 0xFF, 0xFA, 0xFF,
|
||||
0xFF, 0xFF, 0xE0, 0x10, 0xBD, 0xFE, 0xBC, 0x04, 0x00, 0x04, 0x00, 0x05, 0x1F, 0xC0, 0x0F, 0xC0,
|
||||
0x0B, 0xDB, 0xF0, 0x03, 0xF0, 0x03, 0xF8, 0xF4, 0x00, 0xF8, 0x00, 0x7C,
|
||||
0x0B, 0xDB, 0xF0, 0x03, 0xF0, 0x03, 0xF8, 0xF4, 0x00, 0xF8, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x40,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x80, 0x00, 0x00, 0x00,
|
||||
0x00, 0x7F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x74, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x74, 0x00, 0x78,
|
||||
0x00, 0x00, 0x00, 0x7D, 0x04, 0x0B, 0x80, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0xF8, 0x00, 0x00, 0x7F,
|
||||
0xFF, 0xFD, 0x2F, 0x80, 0x00, 0x7F, 0xFF, 0xFF, 0x87, 0xF8, 0x00, 0x7F, 0xFF, 0xFF, 0xE1, 0xFF,
|
||||
0x80, 0x7F, 0xFF, 0xFF, 0x80, 0xBF, 0xF8, 0x2F, 0xFF, 0xF8, 0x00, 0x3F, 0xFE, 0x02, 0xFF, 0xFC,
|
||||
0x00, 0xBF, 0xFE, 0x00, 0x2F, 0xFF, 0x42, 0xFF, 0xFE, 0x00, 0x02, 0xFF, 0xE2, 0xFF, 0xFE, 0x00,
|
||||
0x00, 0x2F, 0xFC, 0xBF, 0xFE, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x2F, 0xD2,
|
||||
0xFE, 0x00, 0x00, 0x00, 0x02, 0xF0, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x2D, 0x2E, 0x00, 0x00, 0x00,
|
||||
0x00, 0x02, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||
0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph bookerly_12_italicGlyphs[] = {
|
||||
@ -3790,6 +3800,7 @@ static const EpdGlyph bookerly_12_italicGlyphs[] = {
|
||||
{ 15, 18, 25, 5, 18, 68, 48719 }, // ⊥
|
||||
{ 4, 4, 16, 6, 9, 4, 48787 }, // ⋅
|
||||
{ 21, 4, 25, 2, 9, 21, 48791 }, // ⋯
|
||||
{ 25, 24, 25, 0, 21, 150, 48812 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval bookerly_12_italicIntervals[] = {
|
||||
@ -3853,13 +3864,14 @@ static const EpdUnicodeInterval bookerly_12_italicIntervals[] = {
|
||||
{ 0x22A5, 0x22A5, 0x2D5 },
|
||||
{ 0x22C5, 0x22C5, 0x2D6 },
|
||||
{ 0x22EF, 0x22EF, 0x2D7 },
|
||||
{ 0xFFFD, 0xFFFD, 0x2D8 },
|
||||
};
|
||||
|
||||
static const EpdFontData bookerly_12_italic = {
|
||||
bookerly_12_italicBitmaps,
|
||||
bookerly_12_italicGlyphs,
|
||||
bookerly_12_italicIntervals,
|
||||
60,
|
||||
61,
|
||||
33,
|
||||
27,
|
||||
-7,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t bookerly_12_regularBitmaps[47071] = {
|
||||
static const uint8_t bookerly_12_regularBitmaps[47221] = {
|
||||
0x28, 0xBC, 0xFC, 0xF8, 0xF8, 0xF8, 0xF4, 0xF4, 0xF4, 0xF4, 0xB4, 0xB4, 0x74, 0x74, 0x00, 0x00,
|
||||
0x78, 0xFD, 0xFC, 0x10, 0x28, 0x2E, 0xE1, 0xFB, 0x47, 0xED, 0x1E, 0xB4, 0x7A, 0xD1, 0xEB, 0x03,
|
||||
0x94, 0x04, 0x00, 0x0E, 0x02, 0xC0, 0x00, 0x0D, 0x03, 0x80, 0x00, 0x1D, 0x03, 0x40, 0x00, 0x2C,
|
||||
@ -2949,7 +2949,17 @@ static const uint8_t bookerly_12_regularBitmaps[47071] = {
|
||||
0x00, 0x0B, 0x80, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x00, 0xB8, 0x00, 0x00, 0x02, 0xE0, 0x00, 0x00,
|
||||
0x0B, 0x80, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x00, 0xB8, 0x00, 0x05, 0x56, 0xE5, 0x54, 0xBF, 0xFF,
|
||||
0xFF, 0xFA, 0xFF, 0xFF, 0xFF, 0xE0, 0x10, 0xBD, 0xFE, 0xBC, 0x04, 0x00, 0x04, 0x00, 0x05, 0x1F,
|
||||
0xC0, 0x0F, 0xC0, 0x0B, 0xDB, 0xF0, 0x03, 0xF0, 0x03, 0xF8, 0xF4, 0x00, 0xF8, 0x00, 0x7C,
|
||||
0xC0, 0x0F, 0xC0, 0x0B, 0xDB, 0xF0, 0x03, 0xF0, 0x03, 0xF8, 0xF4, 0x00, 0xF8, 0x00, 0x7C, 0x00,
|
||||
0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x80,
|
||||
0x00, 0x00, 0x00, 0x00, 0x7F, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x74, 0x1F, 0x80, 0x00, 0x00, 0x00,
|
||||
0x74, 0x00, 0x78, 0x00, 0x00, 0x00, 0x7D, 0x04, 0x0B, 0x80, 0x00, 0x00, 0x7F, 0xFF, 0xE0, 0xF8,
|
||||
0x00, 0x00, 0x7F, 0xFF, 0xFD, 0x2F, 0x80, 0x00, 0x7F, 0xFF, 0xFF, 0x87, 0xF8, 0x00, 0x7F, 0xFF,
|
||||
0xFF, 0xE1, 0xFF, 0x80, 0x7F, 0xFF, 0xFF, 0x80, 0xBF, 0xF8, 0x2F, 0xFF, 0xF8, 0x00, 0x3F, 0xFE,
|
||||
0x02, 0xFF, 0xFC, 0x00, 0xBF, 0xFE, 0x00, 0x2F, 0xFF, 0x42, 0xFF, 0xFE, 0x00, 0x02, 0xFF, 0xE2,
|
||||
0xFF, 0xFE, 0x00, 0x00, 0x2F, 0xFC, 0xBF, 0xFE, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFE, 0x00, 0x00,
|
||||
0x00, 0x2F, 0xD2, 0xFE, 0x00, 0x00, 0x00, 0x02, 0xF0, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x2D, 0x2E,
|
||||
0x00, 0x00, 0x00, 0x00, 0x02, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph bookerly_12_regularGlyphs[] = {
|
||||
@ -3681,6 +3691,7 @@ static const EpdGlyph bookerly_12_regularGlyphs[] = {
|
||||
{ 15, 18, 25, 5, 18, 68, 46978 }, // ⊥
|
||||
{ 4, 4, 16, 6, 9, 4, 47046 }, // ⋅
|
||||
{ 21, 4, 25, 2, 9, 21, 47050 }, // ⋯
|
||||
{ 25, 24, 25, 0, 21, 150, 47071 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval bookerly_12_regularIntervals[] = {
|
||||
@ -3744,13 +3755,14 @@ static const EpdUnicodeInterval bookerly_12_regularIntervals[] = {
|
||||
{ 0x22A5, 0x22A5, 0x2D5 },
|
||||
{ 0x22C5, 0x22C5, 0x2D6 },
|
||||
{ 0x22EF, 0x22EF, 0x2D7 },
|
||||
{ 0xFFFD, 0xFFFD, 0x2D8 },
|
||||
};
|
||||
|
||||
static const EpdFontData bookerly_12_regular = {
|
||||
bookerly_12_regularBitmaps,
|
||||
bookerly_12_regularGlyphs,
|
||||
bookerly_12_regularIntervals,
|
||||
60,
|
||||
61,
|
||||
33,
|
||||
27,
|
||||
-7,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t bookerly_14_boldBitmaps[67020] = {
|
||||
static const uint8_t bookerly_14_boldBitmaps[67209] = {
|
||||
0x1A, 0x4B, 0xF8, 0xFF, 0x8F, 0xF4, 0xFF, 0x4F, 0xF0, 0xFF, 0x0F, 0xF0, 0xFF, 0x0F, 0xF0, 0xBF,
|
||||
0x0B, 0xF0, 0x7F, 0x07, 0xF0, 0x3F, 0x03, 0xF0, 0x10, 0x00, 0x00, 0x2F, 0x4B, 0xFC, 0xBF, 0xC7,
|
||||
0xFC, 0x15, 0x00, 0x2F, 0x42, 0xE7, 0xF4, 0xFF, 0xBF, 0x0F, 0xEB, 0xF0, 0xFE, 0xBF, 0x0F, 0xEB,
|
||||
@ -4196,7 +4196,19 @@ static const uint8_t bookerly_14_boldBitmaps[67020] = {
|
||||
0xFF, 0xFF, 0xFF, 0xFA, 0xFF, 0xFF, 0xFF, 0xFE, 0xBF, 0xFF, 0xFF, 0xFF, 0x80, 0x01, 0x03, 0xFC,
|
||||
0xBF, 0xEB, 0xFE, 0x3F, 0xC0, 0x00, 0x05, 0x00, 0x00, 0x40, 0x00, 0x04, 0x0F, 0xF0, 0x02, 0xFE,
|
||||
0x00, 0x3F, 0xDB, 0xFD, 0x00, 0xFF, 0xC0, 0x1F, 0xFA, 0xFF, 0x40, 0x3F, 0xF0, 0x07, 0xFE, 0x7F,
|
||||
0xC0, 0x0B, 0xF4, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xC0, 0x0B, 0xF4, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xE0, 0x00, 0x00,
|
||||
0x00, 0x00, 0x02, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x5B, 0xE0, 0x00, 0x00, 0x00, 0x02,
|
||||
0xC0, 0x02, 0xE0, 0x00, 0x00, 0x00, 0x2E, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x02, 0xFC, 0x69, 0x02,
|
||||
0xE0, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0x03, 0xE0, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0x0F, 0xE0, 0x00,
|
||||
0x2F, 0xFF, 0xFF, 0xFC, 0x2F, 0xE0, 0x02, 0xFF, 0xFF, 0xFF, 0xF0, 0xBF, 0xE0, 0x2F, 0xFF, 0xFF,
|
||||
0xFF, 0x42, 0xFF, 0xE2, 0xFF, 0xFF, 0xFE, 0x40, 0x0F, 0xFF, 0xE3, 0xFF, 0xFF, 0xC0, 0x00, 0xFF,
|
||||
0xFF, 0x03, 0xFF, 0xFF, 0x00, 0x2F, 0xFF, 0xF0, 0x03, 0xFF, 0xFC, 0x0B, 0xFF, 0xFF, 0x00, 0x03,
|
||||
0xFF, 0xF0, 0xBF, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xD1, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF,
|
||||
0xFF, 0xF0, 0x00, 0x00, 0x03, 0xFF, 0xEF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0xFC, 0x0F, 0xF0, 0x00,
|
||||
0x00, 0x00, 0x03, 0xF0, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x03, 0xD2, 0xF0, 0x00, 0x00, 0x00, 0x00,
|
||||
0x03, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph bookerly_14_boldGlyphs[] = {
|
||||
@ -4928,6 +4940,7 @@ static const EpdGlyph bookerly_14_boldGlyphs[] = {
|
||||
{ 17, 21, 29, 6, 21, 90, 66883 }, // ⊥
|
||||
{ 6, 6, 18, 6, 11, 9, 66973 }, // ⋅
|
||||
{ 25, 6, 29, 2, 11, 38, 66982 }, // ⋯
|
||||
{ 27, 28, 29, 1, 24, 189, 67020 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval bookerly_14_boldIntervals[] = {
|
||||
@ -4991,13 +5004,14 @@ static const EpdUnicodeInterval bookerly_14_boldIntervals[] = {
|
||||
{ 0x22A5, 0x22A5, 0x2D5 },
|
||||
{ 0x22C5, 0x22C5, 0x2D6 },
|
||||
{ 0x22EF, 0x22EF, 0x2D7 },
|
||||
{ 0xFFFD, 0xFFFD, 0x2D8 },
|
||||
};
|
||||
|
||||
static const EpdFontData bookerly_14_bold = {
|
||||
bookerly_14_boldBitmaps,
|
||||
bookerly_14_boldGlyphs,
|
||||
bookerly_14_boldIntervals,
|
||||
60,
|
||||
61,
|
||||
38,
|
||||
31,
|
||||
-8,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t bookerly_14_bolditalicBitmaps[70024] = {
|
||||
static const uint8_t bookerly_14_bolditalicBitmaps[70213] = {
|
||||
0x00, 0x1A, 0x40, 0x2F, 0xD0, 0x0F, 0xF0, 0x0B, 0xF8, 0x03, 0xFD, 0x00, 0xFF, 0x00, 0x3F, 0x80,
|
||||
0x1F, 0xD0, 0x07, 0xF0, 0x02, 0xFC, 0x00, 0xBE, 0x00, 0x3F, 0x40, 0x0F, 0xC0, 0x03, 0xF0, 0x00,
|
||||
0xF8, 0x00, 0x3D, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0xF4, 0x01, 0xFF, 0x00, 0xBF, 0x80, 0x1F,
|
||||
@ -4384,7 +4384,19 @@ static const uint8_t bookerly_14_bolditalicBitmaps[70024] = {
|
||||
0xFF, 0xFF, 0xFF, 0xFE, 0xBF, 0xFF, 0xFF, 0xFF, 0x80, 0x01, 0x03, 0xFC, 0xBF, 0xEB, 0xFE, 0x3F,
|
||||
0xC0, 0x00, 0x05, 0x00, 0x00, 0x40, 0x00, 0x04, 0x0F, 0xF0, 0x02, 0xFE, 0x00, 0x3F, 0xDB, 0xFD,
|
||||
0x00, 0xFF, 0xC0, 0x1F, 0xFA, 0xFF, 0x40, 0x3F, 0xF0, 0x07, 0xFE, 0x7F, 0xC0, 0x0B, 0xF4, 0x00,
|
||||
0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x02, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF,
|
||||
0xE0, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x5B, 0xE0, 0x00, 0x00, 0x00, 0x02, 0xC0, 0x02, 0xE0, 0x00,
|
||||
0x00, 0x00, 0x2E, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x02, 0xFC, 0x69, 0x02, 0xE0, 0x00, 0x00, 0x2F,
|
||||
0xFF, 0xFF, 0x03, 0xE0, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0x0F, 0xE0, 0x00, 0x2F, 0xFF, 0xFF, 0xFC,
|
||||
0x2F, 0xE0, 0x02, 0xFF, 0xFF, 0xFF, 0xF0, 0xBF, 0xE0, 0x2F, 0xFF, 0xFF, 0xFF, 0x42, 0xFF, 0xE2,
|
||||
0xFF, 0xFF, 0xFE, 0x40, 0x0F, 0xFF, 0xE3, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0x03, 0xFF, 0xFF,
|
||||
0x00, 0x2F, 0xFF, 0xF0, 0x03, 0xFF, 0xFC, 0x0B, 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xF0, 0xBF, 0xFF,
|
||||
0xF0, 0x00, 0x03, 0xFF, 0xD1, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00,
|
||||
0x03, 0xFF, 0xEF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0xFC, 0x0F, 0xF0, 0x00, 0x00, 0x00, 0x03, 0xF0,
|
||||
0x3F, 0x00, 0x00, 0x00, 0x00, 0x03, 0xD2, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x03, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph bookerly_14_bolditalicGlyphs[] = {
|
||||
@ -5116,6 +5128,7 @@ static const EpdGlyph bookerly_14_bolditalicGlyphs[] = {
|
||||
{ 17, 21, 29, 6, 21, 90, 69887 }, // ⊥
|
||||
{ 6, 6, 18, 6, 11, 9, 69977 }, // ⋅
|
||||
{ 25, 6, 29, 2, 11, 38, 69986 }, // ⋯
|
||||
{ 27, 28, 29, 1, 24, 189, 70024 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval bookerly_14_bolditalicIntervals[] = {
|
||||
@ -5179,13 +5192,14 @@ static const EpdUnicodeInterval bookerly_14_bolditalicIntervals[] = {
|
||||
{ 0x22A5, 0x22A5, 0x2D5 },
|
||||
{ 0x22C5, 0x22C5, 0x2D6 },
|
||||
{ 0x22EF, 0x22EF, 0x2D7 },
|
||||
{ 0xFFFD, 0xFFFD, 0x2D8 },
|
||||
};
|
||||
|
||||
static const EpdFontData bookerly_14_bolditalic = {
|
||||
bookerly_14_bolditalicBitmaps,
|
||||
bookerly_14_bolditalicGlyphs,
|
||||
bookerly_14_bolditalicIntervals,
|
||||
60,
|
||||
61,
|
||||
38,
|
||||
31,
|
||||
-8,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t bookerly_14_italicBitmaps[64856] = {
|
||||
static const uint8_t bookerly_14_italicBitmaps[65045] = {
|
||||
0x00, 0x05, 0x00, 0x0F, 0x80, 0x0B, 0xD0, 0x03, 0xF0, 0x00, 0xF8, 0x00, 0x7D, 0x00, 0x2F, 0x00,
|
||||
0x0F, 0x80, 0x03, 0xD0, 0x00, 0xF0, 0x00, 0x7C, 0x00, 0x1E, 0x00, 0x0B, 0x40, 0x02, 0xC0, 0x00,
|
||||
0xF0, 0x00, 0x3C, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBC, 0x00, 0x3F, 0x40, 0x0F,
|
||||
@ -4061,7 +4061,19 @@ static const uint8_t bookerly_14_italicBitmaps[64856] = {
|
||||
0xFC, 0x00, 0x06, 0xAA, 0xBF, 0xAA, 0xA6, 0xFF, 0xFF, 0xFF, 0xFE, 0xBF, 0xFF, 0xFF, 0xFF, 0x80,
|
||||
0x0A, 0x43, 0xFC, 0x3F, 0xC2, 0xF8, 0x00, 0x00, 0x0A, 0x00, 0x01, 0xA0, 0x00, 0x29, 0x0F, 0xF0,
|
||||
0x01, 0xFD, 0x00, 0x2F, 0xC3, 0xFC, 0x00, 0xBF, 0x80, 0x0F, 0xF0, 0xFD, 0x00, 0x0F, 0xC0, 0x01,
|
||||
0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x02, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF,
|
||||
0xE0, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x5B, 0xE0, 0x00, 0x00, 0x00, 0x02, 0xC0, 0x02, 0xE0, 0x00,
|
||||
0x00, 0x00, 0x2E, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x02, 0xFC, 0x69, 0x02, 0xE0, 0x00, 0x00, 0x2F,
|
||||
0xFF, 0xFF, 0x03, 0xE0, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0x0F, 0xE0, 0x00, 0x2F, 0xFF, 0xFF, 0xFC,
|
||||
0x2F, 0xE0, 0x02, 0xFF, 0xFF, 0xFF, 0xF0, 0xBF, 0xE0, 0x2F, 0xFF, 0xFF, 0xFF, 0x42, 0xFF, 0xE2,
|
||||
0xFF, 0xFF, 0xFE, 0x40, 0x0F, 0xFF, 0xE3, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0x03, 0xFF, 0xFF,
|
||||
0x00, 0x2F, 0xFF, 0xF0, 0x03, 0xFF, 0xFC, 0x0B, 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xF0, 0xBF, 0xFF,
|
||||
0xF0, 0x00, 0x03, 0xFF, 0xD1, 0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00,
|
||||
0x03, 0xFF, 0xEF, 0xFF, 0x00, 0x00, 0x00, 0x03, 0xFC, 0x0F, 0xF0, 0x00, 0x00, 0x00, 0x03, 0xF0,
|
||||
0x3F, 0x00, 0x00, 0x00, 0x00, 0x03, 0xD2, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x03, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph bookerly_14_italicGlyphs[] = {
|
||||
@ -4793,6 +4805,7 @@ static const EpdGlyph bookerly_14_italicGlyphs[] = {
|
||||
{ 17, 21, 29, 6, 21, 90, 64726 }, // ⊥
|
||||
{ 6, 5, 18, 6, 10, 8, 64816 }, // ⋅
|
||||
{ 25, 5, 29, 2, 10, 32, 64824 }, // ⋯
|
||||
{ 27, 28, 29, 1, 24, 189, 64856 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval bookerly_14_italicIntervals[] = {
|
||||
@ -4856,13 +4869,14 @@ static const EpdUnicodeInterval bookerly_14_italicIntervals[] = {
|
||||
{ 0x22A5, 0x22A5, 0x2D5 },
|
||||
{ 0x22C5, 0x22C5, 0x2D6 },
|
||||
{ 0x22EF, 0x22EF, 0x2D7 },
|
||||
{ 0xFFFD, 0xFFFD, 0x2D8 },
|
||||
};
|
||||
|
||||
static const EpdFontData bookerly_14_italic = {
|
||||
bookerly_14_italicBitmaps,
|
||||
bookerly_14_italicGlyphs,
|
||||
bookerly_14_italicIntervals,
|
||||
60,
|
||||
61,
|
||||
38,
|
||||
31,
|
||||
-8,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t bookerly_14_regularBitmaps[62675] = {
|
||||
static const uint8_t bookerly_14_regularBitmaps[62864] = {
|
||||
0x1A, 0x1F, 0xCB, 0xE2, 0xF4, 0xBD, 0x2F, 0x4B, 0xC2, 0xF0, 0xBC, 0x2F, 0x07, 0xC1, 0xF0, 0x7C,
|
||||
0x0F, 0x03, 0xC0, 0xF0, 0x00, 0x00, 0x00, 0x40, 0xFC, 0xBF, 0x5F, 0xC1, 0x40, 0x18, 0x06, 0x1F,
|
||||
0x07, 0xCB, 0xC2, 0xF1, 0xF0, 0xBC, 0x7C, 0x1F, 0x1F, 0x07, 0xC7, 0xC1, 0xF1, 0xF0, 0x7C, 0x34,
|
||||
@ -3925,7 +3925,18 @@ static const uint8_t bookerly_14_regularBitmaps[62675] = {
|
||||
0xAA, 0xA6, 0xFF, 0xFF, 0xFF, 0xFE, 0xBF, 0xFF, 0xFF, 0xFF, 0x80, 0x0A, 0x43, 0xFC, 0x3F, 0xC2,
|
||||
0xF8, 0x00, 0x00, 0x0A, 0x00, 0x01, 0xA0, 0x00, 0x29, 0x0F, 0xF0, 0x01, 0xFD, 0x00, 0x2F, 0xC3,
|
||||
0xFC, 0x00, 0xBF, 0x80, 0x0F, 0xF0, 0xFD, 0x00, 0x0F, 0xC0, 0x01, 0xF8, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xE0, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x2F, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00,
|
||||
0x2E, 0x5B, 0xE0, 0x00, 0x00, 0x00, 0x02, 0xC0, 0x02, 0xE0, 0x00, 0x00, 0x00, 0x2E, 0x00, 0x01,
|
||||
0xE0, 0x00, 0x00, 0x02, 0xFC, 0x69, 0x02, 0xE0, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0x03, 0xE0, 0x00,
|
||||
0x02, 0xFF, 0xFF, 0xFF, 0x0F, 0xE0, 0x00, 0x2F, 0xFF, 0xFF, 0xFC, 0x2F, 0xE0, 0x02, 0xFF, 0xFF,
|
||||
0xFF, 0xF0, 0xBF, 0xE0, 0x2F, 0xFF, 0xFF, 0xFF, 0x42, 0xFF, 0xE2, 0xFF, 0xFF, 0xFE, 0x40, 0x0F,
|
||||
0xFF, 0xE3, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0x03, 0xFF, 0xFF, 0x00, 0x2F, 0xFF, 0xF0, 0x03,
|
||||
0xFF, 0xFC, 0x0B, 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xF0, 0xBF, 0xFF, 0xF0, 0x00, 0x03, 0xFF, 0xD1,
|
||||
0xFF, 0xFF, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x03, 0xFF, 0xEF, 0xFF, 0x00,
|
||||
0x00, 0x00, 0x03, 0xFC, 0x0F, 0xF0, 0x00, 0x00, 0x00, 0x03, 0xF0, 0x3F, 0x00, 0x00, 0x00, 0x00,
|
||||
0x03, 0xD2, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF0,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph bookerly_14_regularGlyphs[] = {
|
||||
@ -4657,6 +4668,7 @@ static const EpdGlyph bookerly_14_regularGlyphs[] = {
|
||||
{ 17, 21, 29, 6, 21, 90, 62545 }, // ⊥
|
||||
{ 6, 5, 18, 6, 10, 8, 62635 }, // ⋅
|
||||
{ 25, 5, 29, 2, 10, 32, 62643 }, // ⋯
|
||||
{ 27, 28, 29, 1, 24, 189, 62675 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval bookerly_14_regularIntervals[] = {
|
||||
@ -4720,13 +4732,14 @@ static const EpdUnicodeInterval bookerly_14_regularIntervals[] = {
|
||||
{ 0x22A5, 0x22A5, 0x2D5 },
|
||||
{ 0x22C5, 0x22C5, 0x2D6 },
|
||||
{ 0x22EF, 0x22EF, 0x2D7 },
|
||||
{ 0xFFFD, 0xFFFD, 0x2D8 },
|
||||
};
|
||||
|
||||
static const EpdFontData bookerly_14_regular = {
|
||||
bookerly_14_regularBitmaps,
|
||||
bookerly_14_regularGlyphs,
|
||||
bookerly_14_regularIntervals,
|
||||
60,
|
||||
61,
|
||||
38,
|
||||
31,
|
||||
-8,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t bookerly_16_boldBitmaps[85154] = {
|
||||
static const uint8_t bookerly_16_boldBitmaps[85402] = {
|
||||
0x0A, 0x90, 0xFF, 0x87, 0xFE, 0x2F, 0xF4, 0xBF, 0xC2, 0xFF, 0x0B, 0xFC, 0x2F, 0xF0, 0xBF, 0xC2,
|
||||
0xFF, 0x0B, 0xFC, 0x1F, 0xF0, 0x7F, 0xC0, 0xFE, 0x03, 0xF8, 0x0F, 0xE0, 0x3F, 0xC0, 0xBF, 0x01,
|
||||
0x40, 0x00, 0x00, 0x05, 0x40, 0xFF, 0xC7, 0xFF, 0x1F, 0xFC, 0x3F, 0xF0, 0x19, 0x00, 0x0A, 0x80,
|
||||
@ -5330,7 +5330,22 @@ static const uint8_t bookerly_16_boldBitmaps[85154] = {
|
||||
0x2F, 0xFC, 0x7F, 0xE0, 0x15, 0x00, 0x0A, 0x90, 0x00, 0x2A, 0x40, 0x00, 0xA9, 0x0F, 0xFC, 0x00,
|
||||
0x3F, 0xF0, 0x00, 0xFF, 0xC7, 0xFF, 0x40, 0x1F, 0xFD, 0x00, 0x7F, 0xF5, 0xFF, 0xD0, 0x07, 0xFF,
|
||||
0x40, 0x1F, 0xFD, 0x3F, 0xF0, 0x00, 0xFF, 0xC0, 0x03, 0xFF, 0x01, 0x50, 0x00, 0x05, 0x40, 0x00,
|
||||
0x15, 0x00,
|
||||
0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFC, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xEF, 0xFC, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0xF0, 0x01, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0xBC, 0x00, 0x00,
|
||||
0x00, 0x00, 0xFC, 0x00, 0x00, 0xBC, 0x00, 0x00, 0x00, 0x0F, 0xF9, 0xBE, 0x40, 0xFC, 0x00, 0x00,
|
||||
0x00, 0xFF, 0xFF, 0xFF, 0x81, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x43, 0xFC, 0x00, 0x00,
|
||||
0xFF, 0xFF, 0xFF, 0xFE, 0x0F, 0xFC, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFC, 0x2F, 0xFC, 0x00, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xD0, 0xFF, 0xFC, 0x0F, 0xFF, 0xFF, 0xFF, 0xE4, 0x03, 0xFF, 0xFC, 0x7F, 0xFF,
|
||||
0xFF, 0xD0, 0x00, 0x2F, 0xFF, 0xF4, 0x7F, 0xFF, 0xFE, 0x00, 0x02, 0xFF, 0xFF, 0x40, 0x7F, 0xFF,
|
||||
0xF8, 0x00, 0x7F, 0xFF, 0xF4, 0x00, 0x7F, 0xFF, 0xE0, 0x2F, 0xFF, 0xFF, 0x40, 0x00, 0x7F, 0xFF,
|
||||
0xC2, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x7F, 0xFF, 0x07, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x7F, 0xFE,
|
||||
0x7F, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x7F, 0xF4,
|
||||
0x7F, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x80, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x03,
|
||||
0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7D, 0x2F, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xF4,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph bookerly_16_boldGlyphs[] = {
|
||||
@ -6062,6 +6077,7 @@ static const EpdGlyph bookerly_16_boldGlyphs[] = {
|
||||
{ 19, 23, 33, 7, 23, 110, 84989 }, // ⊥
|
||||
{ 7, 6, 21, 7, 12, 11, 85099 }, // ⋅
|
||||
{ 29, 6, 33, 2, 12, 44, 85110 }, // ⋯
|
||||
{ 31, 32, 33, 1, 28, 248, 85154 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval bookerly_16_boldIntervals[] = {
|
||||
@ -6125,13 +6141,14 @@ static const EpdUnicodeInterval bookerly_16_boldIntervals[] = {
|
||||
{ 0x22A5, 0x22A5, 0x2D5 },
|
||||
{ 0x22C5, 0x22C5, 0x2D6 },
|
||||
{ 0x22EF, 0x22EF, 0x2D7 },
|
||||
{ 0xFFFD, 0xFFFD, 0x2D8 },
|
||||
};
|
||||
|
||||
static const EpdFontData bookerly_16_bold = {
|
||||
bookerly_16_boldBitmaps,
|
||||
bookerly_16_boldGlyphs,
|
||||
bookerly_16_boldIntervals,
|
||||
60,
|
||||
61,
|
||||
44,
|
||||
36,
|
||||
-9,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t bookerly_16_bolditalicBitmaps[88512] = {
|
||||
static const uint8_t bookerly_16_bolditalicBitmaps[88760] = {
|
||||
0x00, 0x06, 0xA0, 0x00, 0xBF, 0xC0, 0x07, 0xFD, 0x00, 0x3F, 0xF0, 0x00, 0xFF, 0x80, 0x07, 0xFD,
|
||||
0x00, 0x1F, 0xF0, 0x00, 0xBF, 0x80, 0x03, 0xFD, 0x00, 0x0F, 0xF0, 0x00, 0x3F, 0xC0, 0x00, 0xFE,
|
||||
0x00, 0x07, 0xF4, 0x00, 0x1F, 0xC0, 0x00, 0xBF, 0x00, 0x02, 0xF8, 0x00, 0x0B, 0xD0, 0x00, 0x3F,
|
||||
@ -5540,6 +5540,22 @@ static const uint8_t bookerly_16_bolditalicBitmaps[88512] = {
|
||||
0x7F, 0xE0, 0x15, 0x00, 0x0A, 0x90, 0x00, 0x2A, 0x40, 0x00, 0xA9, 0x0F, 0xFC, 0x00, 0x3F, 0xF0,
|
||||
0x00, 0xFF, 0xC7, 0xFF, 0x40, 0x1F, 0xFD, 0x00, 0x7F, 0xF5, 0xFF, 0xD0, 0x07, 0xFF, 0x40, 0x1F,
|
||||
0xFD, 0x3F, 0xF0, 0x00, 0xFF, 0xC0, 0x03, 0xFF, 0x01, 0x50, 0x00, 0x05, 0x40, 0x00, 0x15, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFC, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xEF, 0xFC, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0xF0, 0x01, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0xBC, 0x00, 0x00, 0x00, 0x00,
|
||||
0xFC, 0x00, 0x00, 0xBC, 0x00, 0x00, 0x00, 0x0F, 0xF9, 0xBE, 0x40, 0xFC, 0x00, 0x00, 0x00, 0xFF,
|
||||
0xFF, 0xFF, 0x81, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x43, 0xFC, 0x00, 0x00, 0xFF, 0xFF,
|
||||
0xFF, 0xFE, 0x0F, 0xFC, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFC, 0x2F, 0xFC, 0x00, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xD0, 0xFF, 0xFC, 0x0F, 0xFF, 0xFF, 0xFF, 0xE4, 0x03, 0xFF, 0xFC, 0x7F, 0xFF, 0xFF, 0xD0,
|
||||
0x00, 0x2F, 0xFF, 0xF4, 0x7F, 0xFF, 0xFE, 0x00, 0x02, 0xFF, 0xFF, 0x40, 0x7F, 0xFF, 0xF8, 0x00,
|
||||
0x7F, 0xFF, 0xF4, 0x00, 0x7F, 0xFF, 0xE0, 0x2F, 0xFF, 0xFF, 0x40, 0x00, 0x7F, 0xFF, 0xC2, 0xFF,
|
||||
0xFF, 0xF4, 0x00, 0x00, 0x7F, 0xFF, 0x07, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFF,
|
||||
0xF4, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x7F, 0xF4, 0x7F, 0xF4,
|
||||
0x00, 0x00, 0x00, 0x00, 0x7F, 0x80, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x03, 0xF4, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x7D, 0x2F, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xF4, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x7F, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph bookerly_16_bolditalicGlyphs[] = {
|
||||
@ -6271,6 +6287,7 @@ static const EpdGlyph bookerly_16_bolditalicGlyphs[] = {
|
||||
{ 19, 23, 33, 7, 23, 110, 88347 }, // ⊥
|
||||
{ 7, 6, 21, 7, 12, 11, 88457 }, // ⋅
|
||||
{ 29, 6, 33, 2, 12, 44, 88468 }, // ⋯
|
||||
{ 31, 32, 33, 1, 28, 248, 88512 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval bookerly_16_bolditalicIntervals[] = {
|
||||
@ -6334,13 +6351,14 @@ static const EpdUnicodeInterval bookerly_16_bolditalicIntervals[] = {
|
||||
{ 0x22A5, 0x22A5, 0x2D5 },
|
||||
{ 0x22C5, 0x22C5, 0x2D6 },
|
||||
{ 0x22EF, 0x22EF, 0x2D7 },
|
||||
{ 0xFFFD, 0xFFFD, 0x2D8 },
|
||||
};
|
||||
|
||||
static const EpdFontData bookerly_16_bolditalic = {
|
||||
bookerly_16_bolditalicBitmaps,
|
||||
bookerly_16_bolditalicGlyphs,
|
||||
bookerly_16_bolditalicIntervals,
|
||||
60,
|
||||
61,
|
||||
44,
|
||||
36,
|
||||
-9,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t bookerly_16_italicBitmaps[82412] = {
|
||||
static const uint8_t bookerly_16_italicBitmaps[82660] = {
|
||||
0x00, 0x01, 0x40, 0x00, 0xFC, 0x00, 0x3F, 0x80, 0x03, 0xF0, 0x00, 0x7F, 0x00, 0x0B, 0xE0, 0x00,
|
||||
0xFC, 0x00, 0x0F, 0xC0, 0x00, 0xF8, 0x00, 0x1F, 0x40, 0x02, 0xF0, 0x00, 0x2F, 0x00, 0x03, 0xE0,
|
||||
0x00, 0x3D, 0x00, 0x03, 0xC0, 0x00, 0x7C, 0x00, 0x07, 0x80, 0x00, 0x74, 0x00, 0x0A, 0x00, 0x00,
|
||||
@ -5158,7 +5158,23 @@ static const uint8_t bookerly_16_italicBitmaps[82412] = {
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFC, 0x3F, 0xE3, 0xFE,
|
||||
0x2F, 0xC0, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBD, 0x00, 0x03, 0xF0, 0x00, 0x0B,
|
||||
0xDB, 0xFC, 0x00, 0x2F, 0xF0, 0x00, 0xBF, 0xFF, 0xF0, 0x00, 0xFF, 0xC0, 0x03, 0xFF, 0x7F, 0x40,
|
||||
0x01, 0xFD, 0x00, 0x07, 0xF4, 0x10, 0x00, 0x00, 0x40, 0x00, 0x01, 0x00,
|
||||
0x01, 0xFD, 0x00, 0x07, 0xF4, 0x10, 0x00, 0x00, 0x40, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFC,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xEF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x01, 0xFC,
|
||||
0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0xBC, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0xBC,
|
||||
0x00, 0x00, 0x00, 0x0F, 0xF9, 0xBE, 0x40, 0xFC, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x81, 0xFC,
|
||||
0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x43, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFE, 0x0F, 0xFC,
|
||||
0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFC, 0x2F, 0xFC, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0xFF, 0xFC,
|
||||
0x0F, 0xFF, 0xFF, 0xFF, 0xE4, 0x03, 0xFF, 0xFC, 0x7F, 0xFF, 0xFF, 0xD0, 0x00, 0x2F, 0xFF, 0xF4,
|
||||
0x7F, 0xFF, 0xFE, 0x00, 0x02, 0xFF, 0xFF, 0x40, 0x7F, 0xFF, 0xF8, 0x00, 0x7F, 0xFF, 0xF4, 0x00,
|
||||
0x7F, 0xFF, 0xE0, 0x2F, 0xFF, 0xFF, 0x40, 0x00, 0x7F, 0xFF, 0xC2, 0xFF, 0xFF, 0xF4, 0x00, 0x00,
|
||||
0x7F, 0xFF, 0x07, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFF, 0xF4, 0x00, 0x00, 0x00,
|
||||
0x7F, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x7F, 0xF4, 0x7F, 0xF4, 0x00, 0x00, 0x00, 0x00,
|
||||
0x7F, 0x80, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x03, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x7D, 0x2F, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x7F, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph bookerly_16_italicGlyphs[] = {
|
||||
@ -5890,6 +5906,7 @@ static const EpdGlyph bookerly_16_italicGlyphs[] = {
|
||||
{ 19, 23, 33, 7, 23, 110, 82252 }, // ⊥
|
||||
{ 6, 6, 21, 7, 12, 9, 82362 }, // ⋅
|
||||
{ 27, 6, 33, 3, 12, 41, 82371 }, // ⋯
|
||||
{ 31, 32, 33, 1, 28, 248, 82412 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval bookerly_16_italicIntervals[] = {
|
||||
@ -5953,13 +5970,14 @@ static const EpdUnicodeInterval bookerly_16_italicIntervals[] = {
|
||||
{ 0x22A5, 0x22A5, 0x2D5 },
|
||||
{ 0x22C5, 0x22C5, 0x2D6 },
|
||||
{ 0x22EF, 0x22EF, 0x2D7 },
|
||||
{ 0xFFFD, 0xFFFD, 0x2D8 },
|
||||
};
|
||||
|
||||
static const EpdFontData bookerly_16_italic = {
|
||||
bookerly_16_italicBitmaps,
|
||||
bookerly_16_italicGlyphs,
|
||||
bookerly_16_italicIntervals,
|
||||
60,
|
||||
61,
|
||||
44,
|
||||
36,
|
||||
-9,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t bookerly_16_regularBitmaps[79871] = {
|
||||
static const uint8_t bookerly_16_regularBitmaps[80119] = {
|
||||
0x0A, 0x83, 0xF8, 0x3F, 0x47, 0xF4, 0x7F, 0x07, 0xF0, 0x7F, 0x07, 0xF0, 0x3F, 0x03, 0xF0, 0x3F,
|
||||
0x03, 0xF0, 0x3F, 0x03, 0xF0, 0x3F, 0x03, 0xF0, 0x2F, 0x02, 0xF0, 0x04, 0x00, 0x00, 0x00, 0x01,
|
||||
0xA4, 0x3F, 0xC3, 0xFC, 0x3F, 0xC0, 0x50, 0x05, 0x01, 0x93, 0xE0, 0x7D, 0x7E, 0x0B, 0xD7, 0xE0,
|
||||
@ -4999,7 +4999,23 @@ static const uint8_t bookerly_16_regularBitmaps[79871] = {
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x01, 0xFC,
|
||||
0x3F, 0xE3, 0xFE, 0x2F, 0xC0, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBD, 0x00, 0x03,
|
||||
0xF0, 0x00, 0x0B, 0xDB, 0xFC, 0x00, 0x2F, 0xF0, 0x00, 0xBF, 0xFF, 0xF0, 0x00, 0xFF, 0xC0, 0x03,
|
||||
0xFF, 0x7F, 0x40, 0x01, 0xFD, 0x00, 0x07, 0xF4, 0x10, 0x00, 0x00, 0x40, 0x00, 0x01, 0x00,
|
||||
0xFF, 0x7F, 0x40, 0x01, 0xFD, 0x00, 0x07, 0xF4, 0x10, 0x00, 0x00, 0x40, 0x00, 0x01, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xEF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xF0, 0x01, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0xBC, 0x00, 0x00, 0x00, 0x00, 0xFC,
|
||||
0x00, 0x00, 0xBC, 0x00, 0x00, 0x00, 0x0F, 0xF9, 0xBE, 0x40, 0xFC, 0x00, 0x00, 0x00, 0xFF, 0xFF,
|
||||
0xFF, 0x81, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x43, 0xFC, 0x00, 0x00, 0xFF, 0xFF, 0xFF,
|
||||
0xFE, 0x0F, 0xFC, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFC, 0x2F, 0xFC, 0x00, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xD0, 0xFF, 0xFC, 0x0F, 0xFF, 0xFF, 0xFF, 0xE4, 0x03, 0xFF, 0xFC, 0x7F, 0xFF, 0xFF, 0xD0, 0x00,
|
||||
0x2F, 0xFF, 0xF4, 0x7F, 0xFF, 0xFE, 0x00, 0x02, 0xFF, 0xFF, 0x40, 0x7F, 0xFF, 0xF8, 0x00, 0x7F,
|
||||
0xFF, 0xF4, 0x00, 0x7F, 0xFF, 0xE0, 0x2F, 0xFF, 0xFF, 0x40, 0x00, 0x7F, 0xFF, 0xC2, 0xFF, 0xFF,
|
||||
0xF4, 0x00, 0x00, 0x7F, 0xFF, 0x07, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFF, 0xF4,
|
||||
0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x7F, 0xF4, 0x7F, 0xF4, 0x00,
|
||||
0x00, 0x00, 0x00, 0x7F, 0x80, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x03, 0xF4, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x7D, 0x2F, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xF4, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x7F, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph bookerly_16_regularGlyphs[] = {
|
||||
@ -5731,6 +5747,7 @@ static const EpdGlyph bookerly_16_regularGlyphs[] = {
|
||||
{ 19, 23, 33, 7, 23, 110, 79711 }, // ⊥
|
||||
{ 6, 6, 21, 7, 12, 9, 79821 }, // ⋅
|
||||
{ 27, 6, 33, 3, 12, 41, 79830 }, // ⋯
|
||||
{ 31, 32, 33, 1, 28, 248, 79871 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval bookerly_16_regularIntervals[] = {
|
||||
@ -5794,13 +5811,14 @@ static const EpdUnicodeInterval bookerly_16_regularIntervals[] = {
|
||||
{ 0x22A5, 0x22A5, 0x2D5 },
|
||||
{ 0x22C5, 0x22C5, 0x2D6 },
|
||||
{ 0x22EF, 0x22EF, 0x2D7 },
|
||||
{ 0xFFFD, 0xFFFD, 0x2D8 },
|
||||
};
|
||||
|
||||
static const EpdFontData bookerly_16_regular = {
|
||||
bookerly_16_regularBitmaps,
|
||||
bookerly_16_regularGlyphs,
|
||||
bookerly_16_regularIntervals,
|
||||
60,
|
||||
61,
|
||||
44,
|
||||
36,
|
||||
-9,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t bookerly_18_boldBitmaps[112410] = {
|
||||
static const uint8_t bookerly_18_boldBitmaps[112734] = {
|
||||
0x01, 0x54, 0x1F, 0xFC, 0x3F, 0xF8, 0x3F, 0xF8, 0x7F, 0xF4, 0x7F, 0xF4, 0x7F, 0xF0, 0x7F, 0xF0,
|
||||
0x7F, 0xF0, 0x7F, 0xF0, 0x3F, 0xF0, 0x3F, 0xF0, 0x3F, 0xF0, 0x3F, 0xF0, 0x3F, 0xF0, 0x3F, 0xF0,
|
||||
0x2F, 0xF0, 0x2F, 0xF0, 0x1F, 0xF0, 0x1F, 0xF0, 0x0F, 0xF0, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
@ -7033,7 +7033,27 @@ static const uint8_t bookerly_18_boldBitmaps[112410] = {
|
||||
0x00, 0x00, 0x54, 0x00, 0x00, 0x54, 0x01, 0xFF, 0xC0, 0x00, 0xBF, 0xF0, 0x00, 0x3F, 0xF4, 0x3F,
|
||||
0xFE, 0x00, 0x0F, 0xFF, 0x40, 0x0B, 0xFF, 0xC3, 0xFF, 0xF0, 0x01, 0xFF, 0xF4, 0x00, 0xBF, 0xFC,
|
||||
0x3F, 0xFE, 0x00, 0x1F, 0xFF, 0x00, 0x0B, 0xFF, 0xC1, 0xFF, 0xC0, 0x00, 0xFF, 0xE0, 0x00, 0x3F,
|
||||
0xF4, 0x01, 0x50, 0x00, 0x01, 0x50, 0x00, 0x00, 0x54, 0x00,
|
||||
0xF4, 0x01, 0x50, 0x00, 0x01, 0x50, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xBE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x0B, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xF8, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0xBE, 0x56, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xE0, 0x00, 0x2F, 0x80,
|
||||
0x00, 0x00, 0x00, 0x00, 0x0B, 0xC0, 0x00, 0x07, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xC0, 0x00,
|
||||
0x01, 0xF8, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xD1, 0xA9, 0x00, 0xBE, 0x00, 0x00, 0x00, 0x02, 0xFF,
|
||||
0xFF, 0xFF, 0xD0, 0x3F, 0x80, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xF0, 0x2F, 0xE0, 0x00, 0x00,
|
||||
0x2F, 0xFF, 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFC, 0x0F, 0xFE,
|
||||
0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x0F, 0xFF, 0x80, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4,
|
||||
0x1F, 0xFF, 0xE0, 0x2F, 0xFF, 0xFF, 0xFF, 0xFE, 0x40, 0x1F, 0xFF, 0xF8, 0x7F, 0xFF, 0xFF, 0xFF,
|
||||
0x80, 0x00, 0x3F, 0xFF, 0xFD, 0x1F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0xF4, 0x07, 0xFF,
|
||||
0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xFF, 0xD0, 0x01, 0xFF, 0xFF, 0xF8, 0x00, 0xBF, 0xFF, 0xFF, 0x40,
|
||||
0x00, 0x7F, 0xFF, 0xFC, 0x0B, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x0F, 0xFF, 0xFF,
|
||||
0xF4, 0x00, 0x00, 0x07, 0xFF, 0xFD, 0x0F, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x01, 0xFF, 0xFE, 0x1F,
|
||||
0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x1F,
|
||||
0xFF, 0xEB, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0x00, 0xFF, 0xD0, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0xFE, 0x00, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x00, 0xFD, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x1F, 0x97, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x7D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph bookerly_18_boldGlyphs[] = {
|
||||
@ -7765,6 +7785,7 @@ static const EpdGlyph bookerly_18_boldGlyphs[] = {
|
||||
{ 22, 27, 38, 8, 27, 149, 112187 }, // ⊥
|
||||
{ 8, 7, 24, 8, 14, 14, 112336 }, // ⋅
|
||||
{ 34, 7, 38, 2, 14, 60, 112350 }, // ⋯
|
||||
{ 36, 36, 38, 1, 32, 324, 112410 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval bookerly_18_boldIntervals[] = {
|
||||
@ -7828,13 +7849,14 @@ static const EpdUnicodeInterval bookerly_18_boldIntervals[] = {
|
||||
{ 0x22A5, 0x22A5, 0x2D5 },
|
||||
{ 0x22C5, 0x22C5, 0x2D6 },
|
||||
{ 0x22EF, 0x22EF, 0x2D7 },
|
||||
{ 0xFFFD, 0xFFFD, 0x2D8 },
|
||||
};
|
||||
|
||||
static const EpdFontData bookerly_18_bold = {
|
||||
bookerly_18_boldBitmaps,
|
||||
bookerly_18_boldGlyphs,
|
||||
bookerly_18_boldIntervals,
|
||||
60,
|
||||
61,
|
||||
49,
|
||||
40,
|
||||
-10,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t bookerly_18_bolditalicBitmaps[115736] = {
|
||||
static const uint8_t bookerly_18_bolditalicBitmaps[116060] = {
|
||||
0x00, 0x00, 0x54, 0x00, 0x07, 0xFE, 0x00, 0x0F, 0xFC, 0x00, 0x2F, 0xFC, 0x00, 0x3F, 0xF4, 0x00,
|
||||
0x7F, 0xF0, 0x00, 0xBF, 0xF0, 0x00, 0xFF, 0xD0, 0x00, 0xFF, 0xC0, 0x00, 0xFF, 0xC0, 0x01, 0xFF,
|
||||
0x80, 0x01, 0xFF, 0x40, 0x02, 0xFF, 0x00, 0x02, 0xFE, 0x00, 0x03, 0xFD, 0x00, 0x03, 0xFC, 0x00,
|
||||
@ -7241,7 +7241,27 @@ static const uint8_t bookerly_18_bolditalicBitmaps[115736] = {
|
||||
0x54, 0x00, 0x00, 0x54, 0x01, 0xFF, 0xC0, 0x00, 0xBF, 0xF0, 0x00, 0x3F, 0xF4, 0x3F, 0xFE, 0x00,
|
||||
0x0F, 0xFF, 0x40, 0x0B, 0xFF, 0xC3, 0xFF, 0xF0, 0x01, 0xFF, 0xF4, 0x00, 0xBF, 0xFC, 0x3F, 0xFE,
|
||||
0x00, 0x1F, 0xFF, 0x00, 0x0B, 0xFF, 0xC1, 0xFF, 0xC0, 0x00, 0xFF, 0xE0, 0x00, 0x3F, 0xF4, 0x01,
|
||||
0x50, 0x00, 0x01, 0x50, 0x00, 0x00, 0x54, 0x00,
|
||||
0x50, 0x00, 0x01, 0x50, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBE, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B,
|
||||
0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0xBE, 0x56, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xE0, 0x00, 0x2F, 0x80, 0x00, 0x00,
|
||||
0x00, 0x00, 0x0B, 0xC0, 0x00, 0x07, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xC0, 0x00, 0x01, 0xF8,
|
||||
0x00, 0x00, 0x00, 0x00, 0xBF, 0xD1, 0xA9, 0x00, 0xBE, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF,
|
||||
0xD0, 0x3F, 0x80, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xF0, 0x2F, 0xE0, 0x00, 0x00, 0x2F, 0xFF,
|
||||
0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFC, 0x0F, 0xFE, 0x00, 0x02,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x0F, 0xFF, 0x80, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x1F, 0xFF,
|
||||
0xE0, 0x2F, 0xFF, 0xFF, 0xFF, 0xFE, 0x40, 0x1F, 0xFF, 0xF8, 0x7F, 0xFF, 0xFF, 0xFF, 0x80, 0x00,
|
||||
0x3F, 0xFF, 0xFD, 0x1F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0xF4, 0x07, 0xFF, 0xFF, 0xF8,
|
||||
0x00, 0x07, 0xFF, 0xFF, 0xD0, 0x01, 0xFF, 0xFF, 0xF8, 0x00, 0xBF, 0xFF, 0xFF, 0x40, 0x00, 0x7F,
|
||||
0xFF, 0xFC, 0x0B, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x0F, 0xFF, 0xFF, 0xF4, 0x00,
|
||||
0x00, 0x07, 0xFF, 0xFD, 0x0F, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x01, 0xFF, 0xFE, 0x1F, 0xFF, 0xFF,
|
||||
0x40, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xEB,
|
||||
0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0x00, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x01,
|
||||
0xFE, 0x00, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x00, 0xFD, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x1F, 0x97, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7D, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph bookerly_18_bolditalicGlyphs[] = {
|
||||
@ -7973,6 +7993,7 @@ static const EpdGlyph bookerly_18_bolditalicGlyphs[] = {
|
||||
{ 22, 27, 38, 8, 27, 149, 115513 }, // ⊥
|
||||
{ 8, 7, 24, 8, 14, 14, 115662 }, // ⋅
|
||||
{ 34, 7, 38, 2, 14, 60, 115676 }, // ⋯
|
||||
{ 36, 36, 38, 1, 32, 324, 115736 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval bookerly_18_bolditalicIntervals[] = {
|
||||
@ -8036,13 +8057,14 @@ static const EpdUnicodeInterval bookerly_18_bolditalicIntervals[] = {
|
||||
{ 0x22A5, 0x22A5, 0x2D5 },
|
||||
{ 0x22C5, 0x22C5, 0x2D6 },
|
||||
{ 0x22EF, 0x22EF, 0x2D7 },
|
||||
{ 0xFFFD, 0xFFFD, 0x2D8 },
|
||||
};
|
||||
|
||||
static const EpdFontData bookerly_18_bolditalic = {
|
||||
bookerly_18_bolditalicBitmaps,
|
||||
bookerly_18_bolditalicGlyphs,
|
||||
bookerly_18_bolditalicIntervals,
|
||||
60,
|
||||
61,
|
||||
49,
|
||||
40,
|
||||
-10,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t bookerly_18_italicBitmaps[108017] = {
|
||||
static const uint8_t bookerly_18_italicBitmaps[108341] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0xBE, 0x00, 0x2F, 0xD0, 0x03, 0xFC, 0x00, 0x7F, 0x80, 0x0B, 0xF0, 0x00,
|
||||
0xFF, 0x00, 0x0F, 0xE0, 0x00, 0xFC, 0x00, 0x1F, 0xC0, 0x02, 0xF8, 0x00, 0x2F, 0x40, 0x03, 0xF0,
|
||||
0x00, 0x3F, 0x00, 0x03, 0xE0, 0x00, 0x7D, 0x00, 0x07, 0xC0, 0x00, 0xBC, 0x00, 0x0F, 0x80, 0x00,
|
||||
@ -6759,7 +6759,27 @@ static const uint8_t bookerly_18_italicBitmaps[108017] = {
|
||||
0x50, 0x0A, 0x80, 0x00, 0x02, 0x90, 0x00, 0x01, 0xA0, 0x3F, 0xF0, 0x00, 0x1F, 0xF4, 0x00, 0x0B,
|
||||
0xFC, 0x7F, 0xF0, 0x00, 0x2F, 0xF8, 0x00, 0x0F, 0xFD, 0x7F, 0xF0, 0x00, 0x2F, 0xF8, 0x00, 0x0F,
|
||||
0xFD, 0x3F, 0xD0, 0x00, 0x0F, 0xF0, 0x00, 0x07, 0xF8, 0x05, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00,
|
||||
0x40,
|
||||
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
|
||||
0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x2F, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBE, 0x56, 0xFE, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x02, 0xE0, 0x00, 0x2F, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0B, 0xC0, 0x00, 0x07, 0xE0,
|
||||
0x00, 0x00, 0x00, 0x00, 0x2F, 0xC0, 0x00, 0x01, 0xF8, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xD1, 0xA9,
|
||||
0x00, 0xBE, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xD0, 0x3F, 0x80, 0x00, 0x00, 0x0B, 0xFF,
|
||||
0xFF, 0xFF, 0xF0, 0x2F, 0xE0, 0x00, 0x00, 0x2F, 0xFF, 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x00, 0x00,
|
||||
0xBF, 0xFF, 0xFF, 0xFF, 0xFC, 0x0F, 0xFE, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x0F, 0xFF,
|
||||
0x80, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x1F, 0xFF, 0xE0, 0x2F, 0xFF, 0xFF, 0xFF, 0xFE, 0x40,
|
||||
0x1F, 0xFF, 0xF8, 0x7F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x3F, 0xFF, 0xFD, 0x1F, 0xFF, 0xFF, 0xF8,
|
||||
0x00, 0x00, 0xFF, 0xFF, 0xF4, 0x07, 0xFF, 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xFF, 0xD0, 0x01, 0xFF,
|
||||
0xFF, 0xF8, 0x00, 0xBF, 0xFF, 0xFF, 0x40, 0x00, 0x7F, 0xFF, 0xFC, 0x0B, 0xFF, 0xFF, 0xFD, 0x00,
|
||||
0x00, 0x1F, 0xFF, 0xFC, 0x0F, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x07, 0xFF, 0xFD, 0x0F, 0xFF, 0xFF,
|
||||
0xD0, 0x00, 0x00, 0x01, 0xFF, 0xFE, 0x1F, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF,
|
||||
0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xEB, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x07,
|
||||
0xFF, 0x00, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFE, 0x00, 0xFF, 0x40, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x7F, 0x00, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x97, 0xF4, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0x40,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x14, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph bookerly_18_italicGlyphs[] = {
|
||||
@ -7491,6 +7511,7 @@ static const EpdGlyph bookerly_18_italicGlyphs[] = {
|
||||
{ 22, 27, 38, 8, 27, 149, 107811 }, // ⊥
|
||||
{ 6, 6, 24, 9, 13, 9, 107960 }, // ⋅
|
||||
{ 32, 6, 38, 3, 13, 48, 107969 }, // ⋯
|
||||
{ 36, 36, 38, 1, 32, 324, 108017 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval bookerly_18_italicIntervals[] = {
|
||||
@ -7554,13 +7575,14 @@ static const EpdUnicodeInterval bookerly_18_italicIntervals[] = {
|
||||
{ 0x22A5, 0x22A5, 0x2D5 },
|
||||
{ 0x22C5, 0x22C5, 0x2D6 },
|
||||
{ 0x22EF, 0x22EF, 0x2D7 },
|
||||
{ 0xFFFD, 0xFFFD, 0x2D8 },
|
||||
};
|
||||
|
||||
static const EpdFontData bookerly_18_italic = {
|
||||
bookerly_18_italicBitmaps,
|
||||
bookerly_18_italicGlyphs,
|
||||
bookerly_18_italicIntervals,
|
||||
60,
|
||||
61,
|
||||
49,
|
||||
40,
|
||||
-10,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t bookerly_18_regularBitmaps[105210] = {
|
||||
static const uint8_t bookerly_18_regularBitmaps[105534] = {
|
||||
0x05, 0x43, 0xF8, 0xBF, 0x4F, 0xF4, 0xFF, 0x0F, 0xF0, 0xFF, 0x0F, 0xF0, 0xFF, 0x0F, 0xF0, 0xFE,
|
||||
0x0F, 0xE0, 0xBE, 0x0B, 0xE0, 0xBE, 0x0B, 0xE0, 0x7E, 0x07, 0xE0, 0x7E, 0x03, 0xE0, 0x3E, 0x01,
|
||||
0x00, 0x00, 0x00, 0x00, 0x04, 0x03, 0xFC, 0xBF, 0xCF, 0xFC, 0xBF, 0xC1, 0x90, 0x00, 0x00, 0x00,
|
||||
@ -6583,7 +6583,27 @@ static const uint8_t bookerly_18_regularBitmaps[105210] = {
|
||||
0xF0, 0x1A, 0x47, 0xFC, 0xFF, 0xDF, 0xFD, 0x7F, 0xC0, 0x50, 0x0A, 0x80, 0x00, 0x02, 0x90, 0x00,
|
||||
0x01, 0xA0, 0x3F, 0xF0, 0x00, 0x1F, 0xF4, 0x00, 0x0B, 0xFC, 0x7F, 0xF0, 0x00, 0x2F, 0xF8, 0x00,
|
||||
0x0F, 0xFD, 0x7F, 0xF0, 0x00, 0x2F, 0xF8, 0x00, 0x0F, 0xFD, 0x3F, 0xD0, 0x00, 0x0F, 0xF0, 0x00,
|
||||
0x07, 0xF8, 0x05, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x40,
|
||||
0x07, 0xF8, 0x05, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xBE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x0B, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xFF, 0xF8, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0xBE, 0x56, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xE0, 0x00, 0x2F, 0x80,
|
||||
0x00, 0x00, 0x00, 0x00, 0x0B, 0xC0, 0x00, 0x07, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x2F, 0xC0, 0x00,
|
||||
0x01, 0xF8, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xD1, 0xA9, 0x00, 0xBE, 0x00, 0x00, 0x00, 0x02, 0xFF,
|
||||
0xFF, 0xFF, 0xD0, 0x3F, 0x80, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xF0, 0x2F, 0xE0, 0x00, 0x00,
|
||||
0x2F, 0xFF, 0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 0xFC, 0x0F, 0xFE,
|
||||
0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x0F, 0xFF, 0x80, 0x0B, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4,
|
||||
0x1F, 0xFF, 0xE0, 0x2F, 0xFF, 0xFF, 0xFF, 0xFE, 0x40, 0x1F, 0xFF, 0xF8, 0x7F, 0xFF, 0xFF, 0xFF,
|
||||
0x80, 0x00, 0x3F, 0xFF, 0xFD, 0x1F, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0xFF, 0xFF, 0xF4, 0x07, 0xFF,
|
||||
0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xFF, 0xD0, 0x01, 0xFF, 0xFF, 0xF8, 0x00, 0xBF, 0xFF, 0xFF, 0x40,
|
||||
0x00, 0x7F, 0xFF, 0xFC, 0x0B, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x1F, 0xFF, 0xFC, 0x0F, 0xFF, 0xFF,
|
||||
0xF4, 0x00, 0x00, 0x07, 0xFF, 0xFD, 0x0F, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x01, 0xFF, 0xFE, 0x1F,
|
||||
0xFF, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x1F,
|
||||
0xFF, 0xEB, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0x00, 0xFF, 0xD0, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0xFE, 0x00, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x00, 0xFD, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x1F, 0x97, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0xD0,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x7D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph bookerly_18_regularGlyphs[] = {
|
||||
@ -7315,6 +7335,7 @@ static const EpdGlyph bookerly_18_regularGlyphs[] = {
|
||||
{ 22, 27, 38, 8, 27, 149, 105004 }, // ⊥
|
||||
{ 6, 6, 24, 9, 13, 9, 105153 }, // ⋅
|
||||
{ 32, 6, 38, 3, 13, 48, 105162 }, // ⋯
|
||||
{ 36, 36, 38, 1, 32, 324, 105210 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval bookerly_18_regularIntervals[] = {
|
||||
@ -7378,13 +7399,14 @@ static const EpdUnicodeInterval bookerly_18_regularIntervals[] = {
|
||||
{ 0x22A5, 0x22A5, 0x2D5 },
|
||||
{ 0x22C5, 0x22C5, 0x2D6 },
|
||||
{ 0x22EF, 0x22EF, 0x2D7 },
|
||||
{ 0xFFFD, 0xFFFD, 0x2D8 },
|
||||
};
|
||||
|
||||
static const EpdFontData bookerly_18_regular = {
|
||||
bookerly_18_regularBitmaps,
|
||||
bookerly_18_regularGlyphs,
|
||||
bookerly_18_regularIntervals,
|
||||
60,
|
||||
61,
|
||||
49,
|
||||
40,
|
||||
-10,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t notosans_12_boldBitmaps[49639] = {
|
||||
static const uint8_t notosans_12_boldBitmaps[49772] = {
|
||||
0x7F, 0xAF, 0xE7, 0xF9, 0xFD, 0x7F, 0x5F, 0xD7, 0xF4, 0xFD, 0x3F, 0x0F, 0xC3, 0xF0, 0xFC, 0x00,
|
||||
0x00, 0x02, 0xE2, 0xFE, 0xBF, 0x9F, 0xD0, 0x40, 0x7F, 0x0F, 0xD7, 0xF0, 0xFD, 0x7F, 0x0F, 0xC3,
|
||||
0xF0, 0xFC, 0x3F, 0x0F, 0xC3, 0xE0, 0xBC, 0x29, 0x06, 0x80, 0x00, 0x0F, 0x43, 0xD0, 0x00, 0x1F,
|
||||
@ -3110,7 +3110,15 @@ static const uint8_t notosans_12_boldBitmaps[49639] = {
|
||||
0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xBF, 0x40, 0x00, 0x7F,
|
||||
0xC0, 0x14, 0x3F, 0xFF, 0xF8, 0x0F, 0xFF, 0xF8, 0x02, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0xFF, 0xFF, 0xFC, 0xFF, 0xFF, 0xFC, 0x55, 0x55, 0x54, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF,
|
||||
0xDF, 0xFF, 0xFF, 0xF5, 0xAA, 0xAA, 0xA8,
|
||||
0xDF, 0xFF, 0xFF, 0xF5, 0xAA, 0xAA, 0xA8, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F,
|
||||
0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0x00, 0x00, 0x00, 0x03,
|
||||
0xFF, 0xFF, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x2F, 0x00, 0x00, 0x03, 0xE0, 0x00, 0x2F, 0x00, 0x00,
|
||||
0x3F, 0xC6, 0xD0, 0x7F, 0x00, 0x03, 0xFF, 0xFF, 0xC1, 0xFF, 0x00, 0x3F, 0xFF, 0xFE, 0x07, 0xFF,
|
||||
0x03, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0x2F, 0xFF, 0xFE, 0x03, 0xFF, 0xFE, 0x3F, 0xFF, 0xF0, 0x3F,
|
||||
0xFF, 0xF0, 0x3F, 0xFF, 0x82, 0xFF, 0xFF, 0x00, 0x3F, 0xFF, 0x5F, 0xFF, 0xF0, 0x00, 0x3F, 0xFF,
|
||||
0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xF5, 0xFF, 0xF0, 0x00, 0x00, 0x3F, 0x41, 0xFF, 0x00, 0x00, 0x00,
|
||||
0x3D, 0x07, 0xF0, 0x00, 0x00, 0x00, 0x3E, 0xBF, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF0, 0x00, 0x00,
|
||||
0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph notosans_12_boldGlyphs[] = {
|
||||
@ -3948,6 +3956,7 @@ static const EpdGlyph notosans_12_boldGlyphs[] = {
|
||||
{ 12, 24, 14, 2, 21, 72, 49497 }, // ₿
|
||||
{ 12, 19, 13, 1, 14, 57, 49569 }, // ⃀
|
||||
{ 13, 4, 15, 1, 11, 13, 49626 }, // −
|
||||
{ 23, 23, 25, 1, 19, 133, 49639 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval notosans_12_boldIntervals[] = {
|
||||
@ -3962,13 +3971,14 @@ static const EpdUnicodeInterval notosans_12_boldIntervals[] = {
|
||||
{ 0x2066, 0x206F, 0x316 },
|
||||
{ 0x20A0, 0x20C0, 0x320 },
|
||||
{ 0x2212, 0x2212, 0x341 },
|
||||
{ 0xFFFD, 0xFFFD, 0x342 },
|
||||
};
|
||||
|
||||
static const EpdFontData notosans_12_bold = {
|
||||
notosans_12_boldBitmaps,
|
||||
notosans_12_boldGlyphs,
|
||||
notosans_12_boldIntervals,
|
||||
11,
|
||||
12,
|
||||
34,
|
||||
27,
|
||||
-8,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t notosans_12_bolditalicBitmaps[52863] = {
|
||||
static const uint8_t notosans_12_bolditalicBitmaps[52996] = {
|
||||
0x01, 0xFE, 0x02, 0xFE, 0x02, 0xFD, 0x03, 0xFC, 0x03, 0xFC, 0x03, 0xF8, 0x07, 0xF4, 0x0B, 0xF0,
|
||||
0x0B, 0xF0, 0x0F, 0xE0, 0x0F, 0xD0, 0x0F, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x2F, 0x00, 0x7F, 0x80,
|
||||
0xBF, 0x80, 0x7F, 0x00, 0x04, 0x00, 0x2F, 0x4B, 0xD3, 0xF4, 0xFD, 0x3F, 0x0F, 0xC3, 0xE0, 0xF8,
|
||||
@ -3311,7 +3311,16 @@ static const uint8_t notosans_12_bolditalicBitmaps[52863] = {
|
||||
0xFC, 0x0F, 0xE0, 0x3F, 0x82, 0xFD, 0x03, 0xFF, 0xFF, 0x40, 0x7F, 0xFF, 0x80, 0x0B, 0xFF, 0xFD,
|
||||
0x00, 0xFF, 0x1F, 0xF0, 0x0F, 0xE0, 0x7F, 0x80, 0xFD, 0x03, 0xF8, 0x1F, 0xC0, 0x7F, 0x42, 0xFC,
|
||||
0x1F, 0xF0, 0x3F, 0xFF, 0xFE, 0x03, 0xFF, 0xFF, 0x80, 0x3F, 0xFF, 0x90, 0x00, 0x34, 0xF0, 0x00,
|
||||
0x07, 0x0E, 0x00, 0x00, 0x50, 0x40, 0x00, 0x05, 0x54, 0x3F, 0xFD, 0x7F, 0xFC, 0x7F, 0xFC,
|
||||
0x07, 0x0E, 0x00, 0x00, 0x50, 0x40, 0x00, 0x05, 0x54, 0x3F, 0xFD, 0x7F, 0xFC, 0x7F, 0xFC, 0x00,
|
||||
0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x00, 0x00,
|
||||
0x00, 0x00, 0x3F, 0xFF, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x2F,
|
||||
0x00, 0x00, 0x03, 0xE0, 0x00, 0x2F, 0x00, 0x00, 0x3F, 0xC6, 0xD0, 0x7F, 0x00, 0x03, 0xFF, 0xFF,
|
||||
0xC1, 0xFF, 0x00, 0x3F, 0xFF, 0xFE, 0x07, 0xFF, 0x03, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0x2F, 0xFF,
|
||||
0xFE, 0x03, 0xFF, 0xFE, 0x3F, 0xFF, 0xF0, 0x3F, 0xFF, 0xF0, 0x3F, 0xFF, 0x82, 0xFF, 0xFF, 0x00,
|
||||
0x3F, 0xFF, 0x5F, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xF5, 0xFF, 0xF0,
|
||||
0x00, 0x00, 0x3F, 0x41, 0xFF, 0x00, 0x00, 0x00, 0x3D, 0x07, 0xF0, 0x00, 0x00, 0x00, 0x3E, 0xBF,
|
||||
0x00, 0x00, 0x00, 0x00, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x20, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph notosans_12_bolditalicGlyphs[] = {
|
||||
@ -4148,6 +4157,7 @@ static const EpdGlyph notosans_12_bolditalicGlyphs[] = {
|
||||
{ 18, 21, 15, -1, 21, 95, 52676 }, // ₾
|
||||
{ 14, 24, 14, 0, 21, 84, 52771 }, // ₿
|
||||
{ 8, 4, 8, 0, 9, 8, 52855 }, // −
|
||||
{ 23, 23, 25, 1, 19, 133, 52863 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval notosans_12_bolditalicIntervals[] = {
|
||||
@ -4162,13 +4172,14 @@ static const EpdUnicodeInterval notosans_12_bolditalicIntervals[] = {
|
||||
{ 0x2066, 0x206F, 0x316 },
|
||||
{ 0x20A0, 0x20BF, 0x320 },
|
||||
{ 0x2212, 0x2212, 0x340 },
|
||||
{ 0xFFFD, 0xFFFD, 0x341 },
|
||||
};
|
||||
|
||||
static const EpdFontData notosans_12_bolditalic = {
|
||||
notosans_12_bolditalicBitmaps,
|
||||
notosans_12_bolditalicGlyphs,
|
||||
notosans_12_bolditalicIntervals,
|
||||
11,
|
||||
12,
|
||||
34,
|
||||
27,
|
||||
-8,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t notosans_12_italicBitmaps[48397] = {
|
||||
static const uint8_t notosans_12_italicBitmaps[48530] = {
|
||||
0x00, 0xB8, 0x03, 0xE0, 0x0F, 0x40, 0x7C, 0x02, 0xF0, 0x0F, 0x80, 0x3D, 0x00, 0xF0, 0x07, 0xC0,
|
||||
0x1E, 0x00, 0xB4, 0x03, 0xC0, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x40, 0x7E, 0x01, 0xF4, 0x01,
|
||||
0x00, 0x00, 0x1E, 0x0F, 0x0B, 0x87, 0xC2, 0xD1, 0xE0, 0xF0, 0xB4, 0x38, 0x2C, 0x0D, 0x0E, 0x02,
|
||||
@ -3032,7 +3032,16 @@ static const uint8_t notosans_12_italicBitmaps[48397] = {
|
||||
0x0F, 0x00, 0x2E, 0x07, 0xC0, 0x0B, 0x82, 0xE0, 0x07, 0xC0, 0xF9, 0x5B, 0xD0, 0x3F, 0xFF, 0x80,
|
||||
0x0F, 0xAB, 0xF4, 0x07, 0xC0, 0x1F, 0x02, 0xE0, 0x03, 0xD0, 0xF4, 0x00, 0xF8, 0x3C, 0x00, 0x3D,
|
||||
0x0F, 0x00, 0x1F, 0x07, 0xC0, 0x1F, 0x82, 0xFF, 0xFF, 0x80, 0xFF, 0xFE, 0x40, 0x03, 0x8B, 0x00,
|
||||
0x00, 0xD3, 0x80, 0x00, 0x10, 0x40, 0x00, 0x2A, 0xA8, 0xFF, 0xE1, 0x55, 0x00,
|
||||
0x00, 0xD3, 0x80, 0x00, 0x10, 0x40, 0x00, 0x2A, 0xA8, 0xFF, 0xE1, 0x55, 0x00, 0x00, 0x00, 0x03,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x00, 0x00, 0x00, 0x00,
|
||||
0x3F, 0xFF, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x2F, 0x00, 0x00,
|
||||
0x03, 0xE0, 0x00, 0x2F, 0x00, 0x00, 0x3F, 0xC6, 0xD0, 0x7F, 0x00, 0x03, 0xFF, 0xFF, 0xC1, 0xFF,
|
||||
0x00, 0x3F, 0xFF, 0xFE, 0x07, 0xFF, 0x03, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0x2F, 0xFF, 0xFE, 0x03,
|
||||
0xFF, 0xFE, 0x3F, 0xFF, 0xF0, 0x3F, 0xFF, 0xF0, 0x3F, 0xFF, 0x82, 0xFF, 0xFF, 0x00, 0x3F, 0xFF,
|
||||
0x5F, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xF5, 0xFF, 0xF0, 0x00, 0x00,
|
||||
0x3F, 0x41, 0xFF, 0x00, 0x00, 0x00, 0x3D, 0x07, 0xF0, 0x00, 0x00, 0x00, 0x3E, 0xBF, 0x00, 0x00,
|
||||
0x00, 0x00, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00,
|
||||
0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph notosans_12_italicGlyphs[] = {
|
||||
@ -3869,6 +3878,7 @@ static const EpdGlyph notosans_12_italicGlyphs[] = {
|
||||
{ 17, 21, 15, -1, 21, 90, 48223 }, // ₾
|
||||
{ 13, 24, 14, 1, 21, 78, 48313 }, // ₿
|
||||
{ 7, 3, 8, 0, 8, 6, 48391 }, // −
|
||||
{ 23, 23, 25, 1, 19, 133, 48397 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval notosans_12_italicIntervals[] = {
|
||||
@ -3883,13 +3893,14 @@ static const EpdUnicodeInterval notosans_12_italicIntervals[] = {
|
||||
{ 0x2066, 0x206F, 0x316 },
|
||||
{ 0x20A0, 0x20BF, 0x320 },
|
||||
{ 0x2212, 0x2212, 0x340 },
|
||||
{ 0xFFFD, 0xFFFD, 0x341 },
|
||||
};
|
||||
|
||||
static const EpdFontData notosans_12_italic = {
|
||||
notosans_12_italicBitmaps,
|
||||
notosans_12_italicGlyphs,
|
||||
notosans_12_italicIntervals,
|
||||
11,
|
||||
12,
|
||||
34,
|
||||
27,
|
||||
-8,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t notosans_12_regularBitmaps[45168] = {
|
||||
static const uint8_t notosans_12_regularBitmaps[45301] = {
|
||||
0x3E, 0x3E, 0x3E, 0x3E, 0x3D, 0x2D, 0x2D, 0x2D, 0x2D, 0x2C, 0x1C, 0x1C, 0x1C, 0x00, 0x00, 0x3E,
|
||||
0x3F, 0x3E, 0x04, 0x7C, 0x2D, 0x7C, 0x2D, 0x3C, 0x2D, 0x38, 0x2D, 0x38, 0x1C, 0x38, 0x1C, 0x24,
|
||||
0x08, 0x00, 0x0B, 0x01, 0xC0, 0x00, 0x0E, 0x02, 0xC0, 0x00, 0x0E, 0x03, 0xC0, 0x00, 0x1D, 0x03,
|
||||
@ -2831,6 +2831,15 @@ static const uint8_t notosans_12_regularBitmaps[45168] = {
|
||||
0x95, 0x93, 0xE0, 0x00, 0x7C, 0x00, 0x0B, 0x80, 0x00, 0xF8, 0x00, 0x0F, 0x40, 0x00, 0xB8, 0x00,
|
||||
0x0B, 0x80, 0x00, 0x7C, 0x00, 0x03, 0xF0, 0x00, 0x0F, 0xEA, 0xF0, 0x2F, 0xFE, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xBF, 0xFF, 0xF6, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x03, 0xFF, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3F, 0xFF, 0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x3D, 0x00,
|
||||
0x2F, 0x00, 0x00, 0x03, 0xE0, 0x00, 0x2F, 0x00, 0x00, 0x3F, 0xC6, 0xD0, 0x7F, 0x00, 0x03, 0xFF,
|
||||
0xFF, 0xC1, 0xFF, 0x00, 0x3F, 0xFF, 0xFE, 0x07, 0xFF, 0x03, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0x2F,
|
||||
0xFF, 0xFE, 0x03, 0xFF, 0xFE, 0x3F, 0xFF, 0xF0, 0x3F, 0xFF, 0xF0, 0x3F, 0xFF, 0x82, 0xFF, 0xFF,
|
||||
0x00, 0x3F, 0xFF, 0x5F, 0xFF, 0xF0, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xF5, 0xFF,
|
||||
0xF0, 0x00, 0x00, 0x3F, 0x41, 0xFF, 0x00, 0x00, 0x00, 0x3D, 0x07, 0xF0, 0x00, 0x00, 0x00, 0x3E,
|
||||
0xBF, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x20, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph notosans_12_regularGlyphs[] = {
|
||||
@ -3668,6 +3677,7 @@ static const EpdGlyph notosans_12_regularGlyphs[] = {
|
||||
{ 12, 24, 14, 2, 21, 72, 45042 }, // ₿
|
||||
{ 10, 18, 12, 1, 14, 45, 45114 }, // ⃀
|
||||
{ 12, 3, 14, 1, 10, 9, 45159 }, // −
|
||||
{ 23, 23, 25, 1, 19, 133, 45168 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval notosans_12_regularIntervals[] = {
|
||||
@ -3682,13 +3692,14 @@ static const EpdUnicodeInterval notosans_12_regularIntervals[] = {
|
||||
{ 0x2066, 0x206F, 0x316 },
|
||||
{ 0x20A0, 0x20C0, 0x320 },
|
||||
{ 0x2212, 0x2212, 0x341 },
|
||||
{ 0xFFFD, 0xFFFD, 0x342 },
|
||||
};
|
||||
|
||||
static const EpdFontData notosans_12_regular = {
|
||||
notosans_12_regularBitmaps,
|
||||
notosans_12_regularGlyphs,
|
||||
notosans_12_regularIntervals,
|
||||
11,
|
||||
12,
|
||||
34,
|
||||
27,
|
||||
-8,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t notosans_14_boldBitmaps[67224] = {
|
||||
static const uint8_t notosans_14_boldBitmaps[67413] = {
|
||||
0x7F, 0xD7, 0xFE, 0x3F, 0xD3, 0xFD, 0x3F, 0xD3, 0xFD, 0x3F, 0xD3, 0xFC, 0x3F, 0xC3, 0xFC, 0x3F,
|
||||
0xC3, 0xFC, 0x2F, 0xC2, 0xFC, 0x00, 0x00, 0x00, 0x05, 0x03, 0xFD, 0x7F, 0xE7, 0xFE, 0x3F, 0xC0,
|
||||
0x50, 0x3F, 0xC3, 0xFC, 0x3F, 0xC3, 0xFC, 0x3F, 0xC3, 0xFC, 0x3F, 0x82, 0xFC, 0x3F, 0x82, 0xFC,
|
||||
@ -4209,7 +4209,19 @@ static const uint8_t notosans_14_boldBitmaps[67224] = {
|
||||
0x00, 0x00, 0x7F, 0xE0, 0x00, 0x03, 0xFF, 0x95, 0xB8, 0x1F, 0xFF, 0xFF, 0x80, 0x7F, 0xFF, 0xF8,
|
||||
0x00, 0xBF, 0xFE, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAA, 0xAA, 0xA8, 0xBF, 0xFF,
|
||||
0xFF, 0xCB, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xF6, 0xFF, 0xFF, 0xFF,
|
||||
0xEB, 0xFF, 0xFF, 0xFF, 0x85, 0x55, 0x55, 0x54,
|
||||
0xEB, 0xFF, 0xFF, 0xFF, 0x85, 0x55, 0x55, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
|
||||
0xD0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xD0, 0x00,
|
||||
0x00, 0x00, 0x0F, 0xE9, 0x6F, 0xD0, 0x00, 0x00, 0x00, 0xF4, 0x00, 0x0F, 0xD0, 0x00, 0x00, 0x0F,
|
||||
0xC0, 0x00, 0x0F, 0xD0, 0x00, 0x00, 0xFF, 0x86, 0xE0, 0x2F, 0xD0, 0x00, 0x0F, 0xFF, 0xFF, 0xC0,
|
||||
0x7F, 0xD0, 0x00, 0xFF, 0xFF, 0xFF, 0x02, 0xFF, 0xD0, 0x0F, 0xFF, 0xFF, 0xF4, 0x0F, 0xFF, 0xD0,
|
||||
0xFF, 0xFF, 0xFF, 0x40, 0xBF, 0xFF, 0xD7, 0xFF, 0xFF, 0xF4, 0x0B, 0xFF, 0xFF, 0x87, 0xFF, 0xFF,
|
||||
0x80, 0xFF, 0xFF, 0xF8, 0x07, 0xFF, 0xFE, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xF8, 0x2F, 0xFF,
|
||||
0xF8, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00,
|
||||
0x07, 0xFD, 0x07, 0xFF, 0x80, 0x00, 0x00, 0x07, 0xF0, 0x0F, 0xF8, 0x00, 0x00, 0x00, 0x07, 0xD0,
|
||||
0x3F, 0x80, 0x00, 0x00, 0x00, 0x07, 0xD7, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0x80, 0x00,
|
||||
0x00, 0x00, 0x00, 0x07, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x04, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph notosans_14_boldGlyphs[] = {
|
||||
@ -5047,6 +5059,7 @@ static const EpdGlyph notosans_14_boldGlyphs[] = {
|
||||
{ 14, 27, 17, 2, 24, 95, 67033 }, // ₿
|
||||
{ 14, 23, 15, 1, 17, 81, 67128 }, // ⃀
|
||||
{ 15, 4, 17, 1, 12, 15, 67209 }, // −
|
||||
{ 27, 28, 29, 1, 23, 189, 67224 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval notosans_14_boldIntervals[] = {
|
||||
@ -5061,13 +5074,14 @@ static const EpdUnicodeInterval notosans_14_boldIntervals[] = {
|
||||
{ 0x2066, 0x206F, 0x316 },
|
||||
{ 0x20A0, 0x20C0, 0x320 },
|
||||
{ 0x2212, 0x2212, 0x341 },
|
||||
{ 0xFFFD, 0xFFFD, 0x342 },
|
||||
};
|
||||
|
||||
static const EpdFontData notosans_14_bold = {
|
||||
notosans_14_boldBitmaps,
|
||||
notosans_14_boldGlyphs,
|
||||
notosans_14_boldIntervals,
|
||||
11,
|
||||
12,
|
||||
40,
|
||||
32,
|
||||
-9,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t notosans_14_bolditalicBitmaps[71405] = {
|
||||
static const uint8_t notosans_14_bolditalicBitmaps[71594] = {
|
||||
0x00, 0xBF, 0xC0, 0x0F, 0xFC, 0x00, 0xFF, 0x80, 0x0F, 0xF4, 0x01, 0xFF, 0x00, 0x2F, 0xF0, 0x02,
|
||||
0xFE, 0x00, 0x3F, 0xD0, 0x03, 0xFC, 0x00, 0x3F, 0xC0, 0x07, 0xF8, 0x00, 0xBF, 0x40, 0x0F, 0xF0,
|
||||
0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x03, 0xFC, 0x00, 0xBF, 0xD0, 0x0B,
|
||||
@ -4470,7 +4470,19 @@ static const uint8_t notosans_14_bolditalicBitmaps[71405] = {
|
||||
0x00, 0x1F, 0xE0, 0x7F, 0xD0, 0x0B, 0xF4, 0x0F, 0xF8, 0x03, 0xFC, 0x03, 0xFE, 0x00, 0xFF, 0x00,
|
||||
0xFF, 0x80, 0x7F, 0xC0, 0xBF, 0xD0, 0x1F, 0xFA, 0xFF, 0xF0, 0x0B, 0xFF, 0xFF, 0xF4, 0x03, 0xFF,
|
||||
0xFF, 0xF4, 0x00, 0xFF, 0xFF, 0xD0, 0x00, 0x03, 0xC3, 0xC0, 0x00, 0x00, 0xF0, 0xF0, 0x00, 0x00,
|
||||
0x38, 0x38, 0x00, 0x00, 0x2A, 0xAA, 0x4F, 0xFF, 0xE3, 0xFF, 0xF5, 0xFF, 0xFC,
|
||||
0x38, 0x38, 0x00, 0x00, 0x2A, 0xAA, 0x4F, 0xFF, 0xE3, 0xFF, 0xF5, 0xFF, 0xFC, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xD0, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xD0, 0x00, 0x00, 0x00,
|
||||
0x00, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x0F, 0xE9, 0x6F, 0xD0, 0x00, 0x00, 0x00, 0xF4, 0x00,
|
||||
0x0F, 0xD0, 0x00, 0x00, 0x0F, 0xC0, 0x00, 0x0F, 0xD0, 0x00, 0x00, 0xFF, 0x86, 0xE0, 0x2F, 0xD0,
|
||||
0x00, 0x0F, 0xFF, 0xFF, 0xC0, 0x7F, 0xD0, 0x00, 0xFF, 0xFF, 0xFF, 0x02, 0xFF, 0xD0, 0x0F, 0xFF,
|
||||
0xFF, 0xF4, 0x0F, 0xFF, 0xD0, 0xFF, 0xFF, 0xFF, 0x40, 0xBF, 0xFF, 0xD7, 0xFF, 0xFF, 0xF4, 0x0B,
|
||||
0xFF, 0xFF, 0x87, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xF8, 0x07, 0xFF, 0xFE, 0x07, 0xFF, 0xFF, 0x80,
|
||||
0x07, 0xFF, 0xF8, 0x2F, 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x07, 0xFF,
|
||||
0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x07, 0xFD, 0x07, 0xFF, 0x80, 0x00, 0x00, 0x07, 0xF0, 0x0F, 0xF8,
|
||||
0x00, 0x00, 0x00, 0x07, 0xD0, 0x3F, 0x80, 0x00, 0x00, 0x00, 0x07, 0xD7, 0xF8, 0x00, 0x00, 0x00,
|
||||
0x00, 0x07, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07,
|
||||
0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph notosans_14_bolditalicGlyphs[] = {
|
||||
@ -5307,6 +5319,7 @@ static const EpdGlyph notosans_14_bolditalicGlyphs[] = {
|
||||
{ 20, 24, 18, -1, 24, 120, 71161 }, // ₾
|
||||
{ 17, 27, 16, 0, 24, 115, 71281 }, // ₿
|
||||
{ 9, 4, 9, 0, 10, 9, 71396 }, // −
|
||||
{ 27, 28, 29, 1, 23, 189, 71405 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval notosans_14_bolditalicIntervals[] = {
|
||||
@ -5321,13 +5334,14 @@ static const EpdUnicodeInterval notosans_14_bolditalicIntervals[] = {
|
||||
{ 0x2066, 0x206F, 0x316 },
|
||||
{ 0x20A0, 0x20BF, 0x320 },
|
||||
{ 0x2212, 0x2212, 0x340 },
|
||||
{ 0xFFFD, 0xFFFD, 0x341 },
|
||||
};
|
||||
|
||||
static const EpdFontData notosans_14_bolditalic = {
|
||||
notosans_14_bolditalicBitmaps,
|
||||
notosans_14_bolditalicGlyphs,
|
||||
notosans_14_bolditalicIntervals,
|
||||
11,
|
||||
12,
|
||||
40,
|
||||
32,
|
||||
-9,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t notosans_14_italicBitmaps[65135] = {
|
||||
static const uint8_t notosans_14_italicBitmaps[65324] = {
|
||||
0x00, 0x3F, 0x00, 0x1F, 0x80, 0x0B, 0xD0, 0x02, 0xF0, 0x00, 0xFC, 0x00, 0x3E, 0x00, 0x0F, 0x40,
|
||||
0x07, 0xC0, 0x02, 0xF0, 0x00, 0xF8, 0x00, 0x3D, 0x00, 0x0F, 0x00, 0x07, 0xC0, 0x01, 0xE0, 0x00,
|
||||
0xB4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x03, 0xF0, 0x01, 0xFC, 0x00, 0x7F, 0x00, 0x05,
|
||||
@ -4078,7 +4078,19 @@ static const uint8_t notosans_14_italicBitmaps[65135] = {
|
||||
0x0B, 0xFF, 0xFD, 0x00, 0x0F, 0xC0, 0x6F, 0x80, 0x0F, 0x80, 0x0B, 0xD0, 0x0F, 0x40, 0x03, 0xE0,
|
||||
0x1F, 0x00, 0x03, 0xE0, 0x2F, 0x00, 0x03, 0xE0, 0x3E, 0x00, 0x07, 0xD0, 0x3E, 0x00, 0x0F, 0xC0,
|
||||
0x7D, 0x01, 0xBF, 0x40, 0x7F, 0xFF, 0xFD, 0x00, 0xBF, 0xFF, 0xD0, 0x00, 0x07, 0x4B, 0x00, 0x00,
|
||||
0x0B, 0x0F, 0x00, 0x00, 0x0E, 0x0E, 0x00, 0x00, 0x3F, 0xFF, 0x0F, 0xFF, 0xC1, 0x55, 0x50,
|
||||
0x0B, 0x0F, 0x00, 0x00, 0x0E, 0x0E, 0x00, 0x00, 0x3F, 0xFF, 0x0F, 0xFF, 0xC1, 0x55, 0x50, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F,
|
||||
0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xD0, 0x00,
|
||||
0x00, 0x00, 0x00, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x0F, 0xE9, 0x6F, 0xD0, 0x00, 0x00, 0x00,
|
||||
0xF4, 0x00, 0x0F, 0xD0, 0x00, 0x00, 0x0F, 0xC0, 0x00, 0x0F, 0xD0, 0x00, 0x00, 0xFF, 0x86, 0xE0,
|
||||
0x2F, 0xD0, 0x00, 0x0F, 0xFF, 0xFF, 0xC0, 0x7F, 0xD0, 0x00, 0xFF, 0xFF, 0xFF, 0x02, 0xFF, 0xD0,
|
||||
0x0F, 0xFF, 0xFF, 0xF4, 0x0F, 0xFF, 0xD0, 0xFF, 0xFF, 0xFF, 0x40, 0xBF, 0xFF, 0xD7, 0xFF, 0xFF,
|
||||
0xF4, 0x0B, 0xFF, 0xFF, 0x87, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xF8, 0x07, 0xFF, 0xFE, 0x07, 0xFF,
|
||||
0xFF, 0x80, 0x07, 0xFF, 0xF8, 0x2F, 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00,
|
||||
0x07, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x07, 0xFD, 0x07, 0xFF, 0x80, 0x00, 0x00, 0x07, 0xF0,
|
||||
0x0F, 0xF8, 0x00, 0x00, 0x00, 0x07, 0xD0, 0x3F, 0x80, 0x00, 0x00, 0x00, 0x07, 0xD7, 0xF8, 0x00,
|
||||
0x00, 0x00, 0x00, 0x07, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0xF8, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x07, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph notosans_14_italicGlyphs[] = {
|
||||
@ -4915,6 +4927,7 @@ static const EpdGlyph notosans_14_italicGlyphs[] = {
|
||||
{ 20, 24, 17, -1, 24, 120, 64900 }, // ₾
|
||||
{ 16, 27, 16, 1, 24, 108, 65020 }, // ₿
|
||||
{ 9, 3, 9, 0, 9, 7, 65128 }, // −
|
||||
{ 27, 28, 29, 1, 23, 189, 65135 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval notosans_14_italicIntervals[] = {
|
||||
@ -4929,13 +4942,14 @@ static const EpdUnicodeInterval notosans_14_italicIntervals[] = {
|
||||
{ 0x2066, 0x206F, 0x316 },
|
||||
{ 0x20A0, 0x20BF, 0x320 },
|
||||
{ 0x2212, 0x2212, 0x340 },
|
||||
{ 0xFFFD, 0xFFFD, 0x341 },
|
||||
};
|
||||
|
||||
static const EpdFontData notosans_14_italic = {
|
||||
notosans_14_italicBitmaps,
|
||||
notosans_14_italicGlyphs,
|
||||
notosans_14_italicIntervals,
|
||||
11,
|
||||
12,
|
||||
40,
|
||||
32,
|
||||
-9,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t notosans_14_regularBitmaps[61202] = {
|
||||
static const uint8_t notosans_14_regularBitmaps[61391] = {
|
||||
0xBD, 0xBD, 0xBD, 0x7D, 0x7D, 0x7C, 0x7C, 0x7C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x3C, 0x00,
|
||||
0x00, 0x14, 0xBE, 0xFE, 0xBD, 0x14, 0x3E, 0x0B, 0xCF, 0x82, 0xF3, 0xD0, 0xBC, 0xF4, 0x2E, 0x3D,
|
||||
0x07, 0x8B, 0x41, 0xE2, 0xC0, 0x78, 0x60, 0x09, 0x00, 0x02, 0xC0, 0x1D, 0x00, 0x00, 0x0F, 0x00,
|
||||
@ -3833,7 +3833,18 @@ static const uint8_t notosans_14_regularBitmaps[61202] = {
|
||||
0x00, 0x00, 0xBC, 0x00, 0x00, 0x7D, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x1F, 0xD0,
|
||||
0x05, 0x07, 0xFF, 0xFE, 0x00, 0xBF, 0xFD, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFE,
|
||||
0x7F, 0xFF, 0xFE, 0x15, 0x55, 0x54, 0x15, 0x55, 0x55, 0x52, 0xFF, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF,
|
||||
0xFF, 0x00,
|
||||
0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD0, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x0F, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x0F,
|
||||
0xFF, 0xD0, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xD0, 0x00, 0x00, 0x00, 0x0F, 0xE9, 0x6F, 0xD0,
|
||||
0x00, 0x00, 0x00, 0xF4, 0x00, 0x0F, 0xD0, 0x00, 0x00, 0x0F, 0xC0, 0x00, 0x0F, 0xD0, 0x00, 0x00,
|
||||
0xFF, 0x86, 0xE0, 0x2F, 0xD0, 0x00, 0x0F, 0xFF, 0xFF, 0xC0, 0x7F, 0xD0, 0x00, 0xFF, 0xFF, 0xFF,
|
||||
0x02, 0xFF, 0xD0, 0x0F, 0xFF, 0xFF, 0xF4, 0x0F, 0xFF, 0xD0, 0xFF, 0xFF, 0xFF, 0x40, 0xBF, 0xFF,
|
||||
0xD7, 0xFF, 0xFF, 0xF4, 0x0B, 0xFF, 0xFF, 0x87, 0xFF, 0xFF, 0x80, 0xFF, 0xFF, 0xF8, 0x07, 0xFF,
|
||||
0xFE, 0x07, 0xFF, 0xFF, 0x80, 0x07, 0xFF, 0xF8, 0x2F, 0xFF, 0xF8, 0x00, 0x07, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0x80, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x07, 0xFD, 0x07, 0xFF, 0x80, 0x00,
|
||||
0x00, 0x07, 0xF0, 0x0F, 0xF8, 0x00, 0x00, 0x00, 0x07, 0xD0, 0x3F, 0x80, 0x00, 0x00, 0x00, 0x07,
|
||||
0xD7, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x07, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0xF8, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph notosans_14_regularGlyphs[] = {
|
||||
@ -4671,6 +4682,7 @@ static const EpdGlyph notosans_14_regularGlyphs[] = {
|
||||
{ 14, 27, 17, 2, 24, 95, 61032 }, // ₿
|
||||
{ 12, 21, 14, 1, 16, 63, 61127 }, // ⃀
|
||||
{ 15, 3, 17, 1, 12, 12, 61190 }, // −
|
||||
{ 27, 28, 29, 1, 23, 189, 61202 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval notosans_14_regularIntervals[] = {
|
||||
@ -4685,13 +4697,14 @@ static const EpdUnicodeInterval notosans_14_regularIntervals[] = {
|
||||
{ 0x2066, 0x206F, 0x316 },
|
||||
{ 0x20A0, 0x20C0, 0x320 },
|
||||
{ 0x2212, 0x2212, 0x341 },
|
||||
{ 0xFFFD, 0xFFFD, 0x342 },
|
||||
};
|
||||
|
||||
static const EpdFontData notosans_14_regular = {
|
||||
notosans_14_regularBitmaps,
|
||||
notosans_14_regularGlyphs,
|
||||
notosans_14_regularIntervals,
|
||||
11,
|
||||
12,
|
||||
40,
|
||||
32,
|
||||
-9,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t notosans_16_boldBitmaps[86944] = {
|
||||
static const uint8_t notosans_16_boldBitmaps[87192] = {
|
||||
0x3F, 0xF4, 0xFF, 0xD3, 0xFF, 0x4F, 0xFD, 0x3F, 0xF4, 0xFF, 0xD3, 0xFF, 0x0F, 0xFC, 0x2F, 0xF0,
|
||||
0xBF, 0xC2, 0xFF, 0x0B, 0xFC, 0x1F, 0xF0, 0x7F, 0xC1, 0xFF, 0x07, 0xF8, 0x05, 0x40, 0x00, 0x00,
|
||||
0x00, 0x07, 0xF8, 0x3F, 0xF4, 0xFF, 0xE3, 0xFF, 0x4B, 0xFC, 0x01, 0x40, 0xFF, 0x82, 0xFE, 0xFF,
|
||||
@ -5442,6 +5442,22 @@ static const uint8_t notosans_16_boldBitmaps[86944] = {
|
||||
0xFF, 0xE0, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x55, 0x55, 0x54, 0x7F, 0xFF,
|
||||
0xFF, 0xFC, 0x7F, 0xFF, 0xFF, 0xFC, 0x7F, 0xFF, 0xFF, 0xFC, 0x2A, 0xAA, 0xAA, 0xAA, 0x5F, 0xFF,
|
||||
0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0xFF, 0xF9, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFD, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xE4, 0x06, 0xFD, 0x00, 0x00, 0x00, 0x00,
|
||||
0xF8, 0x00, 0x00, 0xFD, 0x00, 0x00, 0x00, 0x0F, 0xE0, 0x00, 0x00, 0xFD, 0x00, 0x00, 0x00, 0xFF,
|
||||
0xC1, 0xA8, 0x03, 0xFD, 0x00, 0x00, 0x0F, 0xFF, 0xBF, 0xF4, 0x0B, 0xFD, 0x00, 0x00, 0xFF, 0xFF,
|
||||
0xFF, 0xE0, 0x2F, 0xFD, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFD, 0x00, 0xFF, 0xFF, 0xFF,
|
||||
0xF0, 0x07, 0xFF, 0xFD, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xFD, 0x3F, 0xFF, 0xFF, 0xF0,
|
||||
0x07, 0xFF, 0xFF, 0xF4, 0x3F, 0xFF, 0xFF, 0x80, 0xBF, 0xFF, 0xFF, 0x40, 0x3F, 0xFF, 0xFD, 0x03,
|
||||
0xFF, 0xFF, 0xF4, 0x00, 0x3F, 0xFF, 0xF4, 0x1F, 0xFF, 0xFF, 0x40, 0x00, 0x3F, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xF4, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x3F, 0xFF, 0x5B, 0xFF,
|
||||
0xF4, 0x00, 0x00, 0x00, 0x3F, 0xF0, 0x0F, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x3F, 0xC0, 0x2F, 0xF4,
|
||||
0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x5B, 0xF4, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF4, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x3F, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph notosans_16_boldGlyphs[] = {
|
||||
@ -6279,6 +6295,7 @@ static const EpdGlyph notosans_16_boldGlyphs[] = {
|
||||
{ 15, 32, 19, 3, 28, 120, 86702 }, // ₿
|
||||
{ 16, 25, 17, 1, 19, 100, 86822 }, // ⃀
|
||||
{ 17, 5, 19, 1, 14, 22, 86922 }, // −
|
||||
{ 31, 32, 33, 1, 26, 248, 86944 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval notosans_16_boldIntervals[] = {
|
||||
@ -6293,13 +6310,14 @@ static const EpdUnicodeInterval notosans_16_boldIntervals[] = {
|
||||
{ 0x2066, 0x206F, 0x316 },
|
||||
{ 0x20A0, 0x20C0, 0x320 },
|
||||
{ 0x2212, 0x2212, 0x341 },
|
||||
{ 0xFFFD, 0xFFFD, 0x342 },
|
||||
};
|
||||
|
||||
static const EpdFontData notosans_16_bold = {
|
||||
notosans_16_boldBitmaps,
|
||||
notosans_16_boldGlyphs,
|
||||
notosans_16_boldIntervals,
|
||||
11,
|
||||
12,
|
||||
45,
|
||||
36,
|
||||
-10,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t notosans_16_bolditalicBitmaps[92423] = {
|
||||
static const uint8_t notosans_16_bolditalicBitmaps[92671] = {
|
||||
0x00, 0x3F, 0xF4, 0x01, 0xFF, 0xD0, 0x07, 0xFF, 0x00, 0x2F, 0xF8, 0x00, 0xFF, 0xD0, 0x03, 0xFF,
|
||||
0x00, 0x0F, 0xFC, 0x00, 0x7F, 0xE0, 0x01, 0xFF, 0x40, 0x0B, 0xFC, 0x00, 0x3F, 0xF0, 0x00, 0xFF,
|
||||
0x80, 0x03, 0xFD, 0x00, 0x1F, 0xF0, 0x00, 0x7F, 0xC0, 0x02, 0xFE, 0x00, 0x01, 0x50, 0x00, 0x00,
|
||||
@ -5784,7 +5784,22 @@ static const uint8_t notosans_16_bolditalicBitmaps[92423] = {
|
||||
0xFE, 0x07, 0xFF, 0xC0, 0x1F, 0xFF, 0xFF, 0xFE, 0x00, 0xBF, 0xFF, 0xFF, 0xF0, 0x03, 0xFF, 0xFF,
|
||||
0xFE, 0x00, 0x0F, 0xFF, 0xFF, 0x40, 0x00, 0x01, 0xF0, 0xF4, 0x00, 0x00, 0x07, 0x83, 0xC0, 0x00,
|
||||
0x00, 0x2E, 0x1F, 0x00, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x2F, 0xFF, 0xF3, 0xFF, 0xFE, 0x3F,
|
||||
0xFF, 0xD7, 0xFF, 0xFD, 0x00, 0x00, 0x00,
|
||||
0xFF, 0xD7, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x0F, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x0F, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x0F,
|
||||
0xE4, 0x06, 0xFD, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00, 0x00, 0xFD, 0x00, 0x00, 0x00, 0x0F, 0xE0,
|
||||
0x00, 0x00, 0xFD, 0x00, 0x00, 0x00, 0xFF, 0xC1, 0xA8, 0x03, 0xFD, 0x00, 0x00, 0x0F, 0xFF, 0xBF,
|
||||
0xF4, 0x0B, 0xFD, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xE0, 0x2F, 0xFD, 0x00, 0x0F, 0xFF, 0xFF, 0xFF,
|
||||
0x00, 0xFF, 0xFD, 0x00, 0xFF, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xFD, 0x0F, 0xFF, 0xFF, 0xFF, 0x00,
|
||||
0x7F, 0xFF, 0xFD, 0x3F, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0xF4, 0x3F, 0xFF, 0xFF, 0x80, 0xBF,
|
||||
0xFF, 0xFF, 0x40, 0x3F, 0xFF, 0xFD, 0x03, 0xFF, 0xFF, 0xF4, 0x00, 0x3F, 0xFF, 0xF4, 0x1F, 0xFF,
|
||||
0xFF, 0x40, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0x40, 0x00, 0x00, 0x3F, 0xFF, 0x5B, 0xFF, 0xF4, 0x00, 0x00, 0x00, 0x3F, 0xF0, 0x0F, 0xFF, 0x40,
|
||||
0x00, 0x00, 0x00, 0x3F, 0xC0, 0x2F, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0xFF, 0x40, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3F, 0x5B, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0x40, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3F, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x40, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph notosans_16_bolditalicGlyphs[] = {
|
||||
@ -6621,6 +6636,7 @@ static const EpdGlyph notosans_16_bolditalicGlyphs[] = {
|
||||
{ 23, 28, 20, -1, 28, 161, 92097 }, // ₾
|
||||
{ 19, 32, 18, 0, 28, 152, 92258 }, // ₿
|
||||
{ 10, 5, 11, 0, 11, 13, 92410 }, // −
|
||||
{ 31, 32, 33, 1, 26, 248, 92423 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval notosans_16_bolditalicIntervals[] = {
|
||||
@ -6635,13 +6651,14 @@ static const EpdUnicodeInterval notosans_16_bolditalicIntervals[] = {
|
||||
{ 0x2066, 0x206F, 0x316 },
|
||||
{ 0x20A0, 0x20BF, 0x320 },
|
||||
{ 0x2212, 0x2212, 0x340 },
|
||||
{ 0xFFFD, 0xFFFD, 0x341 },
|
||||
};
|
||||
|
||||
static const EpdFontData notosans_16_bolditalic = {
|
||||
notosans_16_bolditalicBitmaps,
|
||||
notosans_16_bolditalicGlyphs,
|
||||
notosans_16_bolditalicIntervals,
|
||||
11,
|
||||
12,
|
||||
45,
|
||||
36,
|
||||
-10,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t notosans_16_italicBitmaps[83982] = {
|
||||
static const uint8_t notosans_16_italicBitmaps[84230] = {
|
||||
0x00, 0x1F, 0xC0, 0x02, 0xFC, 0x00, 0x3F, 0x80, 0x03, 0xF4, 0x00, 0x3F, 0x00, 0x07, 0xF0, 0x00,
|
||||
0xBE, 0x00, 0x0B, 0xD0, 0x00, 0xFC, 0x00, 0x0F, 0xC0, 0x00, 0xF8, 0x00, 0x1F, 0x40, 0x02, 0xF0,
|
||||
0x00, 0x3F, 0x00, 0x03, 0xE0, 0x00, 0x3D, 0x00, 0x07, 0xC0, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00,
|
||||
@ -5256,7 +5256,23 @@ static const uint8_t notosans_16_italicBitmaps[83982] = {
|
||||
0x00, 0x7F, 0x01, 0xF8, 0x00, 0x07, 0xE0, 0x1F, 0x40, 0x00, 0xBE, 0x02, 0xF4, 0x00, 0x0F, 0xC0,
|
||||
0x3F, 0x00, 0x07, 0xF8, 0x03, 0xFA, 0xAB, 0xFF, 0x00, 0x7F, 0xFF, 0xFF, 0x80, 0x0B, 0xFF, 0xFF,
|
||||
0x80, 0x00, 0x03, 0xC2, 0xD0, 0x00, 0x00, 0x3C, 0x3C, 0x00, 0x00, 0x07, 0x83, 0xC0, 0x00, 0x00,
|
||||
0x10, 0x10, 0x00, 0x00, 0x05, 0x55, 0x42, 0xFF, 0xFC, 0x3F, 0xFF, 0xC1, 0x55, 0x54,
|
||||
0x10, 0x10, 0x00, 0x00, 0x05, 0x55, 0x42, 0xFF, 0xFC, 0x3F, 0xFF, 0xC1, 0x55, 0x54, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
|
||||
0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xE4, 0x06, 0xFD, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x00,
|
||||
0x00, 0xFD, 0x00, 0x00, 0x00, 0x0F, 0xE0, 0x00, 0x00, 0xFD, 0x00, 0x00, 0x00, 0xFF, 0xC1, 0xA8,
|
||||
0x03, 0xFD, 0x00, 0x00, 0x0F, 0xFF, 0xBF, 0xF4, 0x0B, 0xFD, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xE0,
|
||||
0x2F, 0xFD, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFD, 0x00, 0xFF, 0xFF, 0xFF, 0xF0, 0x07,
|
||||
0xFF, 0xFD, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xFD, 0x3F, 0xFF, 0xFF, 0xF0, 0x07, 0xFF,
|
||||
0xFF, 0xF4, 0x3F, 0xFF, 0xFF, 0x80, 0xBF, 0xFF, 0xFF, 0x40, 0x3F, 0xFF, 0xFD, 0x03, 0xFF, 0xFF,
|
||||
0xF4, 0x00, 0x3F, 0xFF, 0xF4, 0x1F, 0xFF, 0xFF, 0x40, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF4,
|
||||
0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x3F, 0xFF, 0x5B, 0xFF, 0xF4, 0x00,
|
||||
0x00, 0x00, 0x3F, 0xF0, 0x0F, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x3F, 0xC0, 0x2F, 0xF4, 0x00, 0x00,
|
||||
0x00, 0x00, 0x3F, 0x00, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x5B, 0xF4, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x3F, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF4, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x3F, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph notosans_16_italicGlyphs[] = {
|
||||
@ -6093,6 +6109,7 @@ static const EpdGlyph notosans_16_italicGlyphs[] = {
|
||||
{ 22, 28, 20, -1, 28, 154, 83674 }, // ₾
|
||||
{ 18, 32, 18, 1, 28, 144, 83828 }, // ₿
|
||||
{ 10, 4, 10, 0, 11, 10, 83972 }, // −
|
||||
{ 31, 32, 33, 1, 26, 248, 83982 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval notosans_16_italicIntervals[] = {
|
||||
@ -6107,13 +6124,14 @@ static const EpdUnicodeInterval notosans_16_italicIntervals[] = {
|
||||
{ 0x2066, 0x206F, 0x316 },
|
||||
{ 0x20A0, 0x20BF, 0x320 },
|
||||
{ 0x2212, 0x2212, 0x340 },
|
||||
{ 0xFFFD, 0xFFFD, 0x341 },
|
||||
};
|
||||
|
||||
static const EpdFontData notosans_16_italic = {
|
||||
notosans_16_italicBitmaps,
|
||||
notosans_16_italicGlyphs,
|
||||
notosans_16_italicIntervals,
|
||||
11,
|
||||
12,
|
||||
45,
|
||||
36,
|
||||
-10,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t notosans_16_regularBitmaps[78480] = {
|
||||
static const uint8_t notosans_16_regularBitmaps[78728] = {
|
||||
0x3F, 0x0F, 0xC3, 0xF0, 0xFC, 0x3F, 0x0F, 0xC3, 0xF0, 0xFC, 0x3F, 0x0F, 0xC3, 0xF0, 0xF8, 0x2E,
|
||||
0x0B, 0x82, 0xE0, 0xB8, 0x2D, 0x01, 0x00, 0x00, 0x00, 0x3F, 0x2F, 0xDB, 0xF9, 0xFC, 0x04, 0x00,
|
||||
0xBC, 0x0B, 0xDB, 0xC0, 0xBD, 0xBC, 0x0B, 0xDB, 0xC0, 0x7C, 0xBC, 0x07, 0xC7, 0xC0, 0x7C, 0x7C,
|
||||
@ -4913,6 +4913,22 @@ static const uint8_t notosans_16_regularBitmaps[78480] = {
|
||||
0x2F, 0x80, 0x00, 0x01, 0xFD, 0x00, 0x00, 0x0B, 0xF9, 0x06, 0xD0, 0x2F, 0xFF, 0xFD, 0x00, 0x7F,
|
||||
0xFF, 0x80, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x01, 0x55, 0x55, 0x54, 0x3F, 0xFF, 0xFF, 0xE3,
|
||||
0xFF, 0xFF, 0xFD, 0x7F, 0xFF, 0xFF, 0xFF, 0x5F, 0xFF, 0xFF, 0xFF, 0xD1, 0x55, 0x55, 0x55, 0x50,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFD, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0xFF, 0xFF, 0xFD, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xE4, 0x06, 0xFD, 0x00, 0x00, 0x00, 0x00,
|
||||
0xF8, 0x00, 0x00, 0xFD, 0x00, 0x00, 0x00, 0x0F, 0xE0, 0x00, 0x00, 0xFD, 0x00, 0x00, 0x00, 0xFF,
|
||||
0xC1, 0xA8, 0x03, 0xFD, 0x00, 0x00, 0x0F, 0xFF, 0xBF, 0xF4, 0x0B, 0xFD, 0x00, 0x00, 0xFF, 0xFF,
|
||||
0xFF, 0xE0, 0x2F, 0xFD, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0xFD, 0x00, 0xFF, 0xFF, 0xFF,
|
||||
0xF0, 0x07, 0xFF, 0xFD, 0x0F, 0xFF, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xFD, 0x3F, 0xFF, 0xFF, 0xF0,
|
||||
0x07, 0xFF, 0xFF, 0xF4, 0x3F, 0xFF, 0xFF, 0x80, 0xBF, 0xFF, 0xFF, 0x40, 0x3F, 0xFF, 0xFD, 0x03,
|
||||
0xFF, 0xFF, 0xF4, 0x00, 0x3F, 0xFF, 0xF4, 0x1F, 0xFF, 0xFF, 0x40, 0x00, 0x3F, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xF4, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0x40, 0x00, 0x00, 0x3F, 0xFF, 0x5B, 0xFF,
|
||||
0xF4, 0x00, 0x00, 0x00, 0x3F, 0xF0, 0x0F, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x3F, 0xC0, 0x2F, 0xF4,
|
||||
0x00, 0x00, 0x00, 0x00, 0x3F, 0x00, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x5B, 0xF4, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xF4, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x3F, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph notosans_16_regularGlyphs[] = {
|
||||
@ -5750,6 +5766,7 @@ static const EpdGlyph notosans_16_regularGlyphs[] = {
|
||||
{ 15, 32, 19, 3, 28, 120, 78263 }, // ₿
|
||||
{ 14, 24, 16, 1, 19, 84, 78383 }, // ⃀
|
||||
{ 17, 3, 19, 1, 13, 13, 78467 }, // −
|
||||
{ 31, 32, 33, 1, 26, 248, 78480 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval notosans_16_regularIntervals[] = {
|
||||
@ -5764,13 +5781,14 @@ static const EpdUnicodeInterval notosans_16_regularIntervals[] = {
|
||||
{ 0x2066, 0x206F, 0x316 },
|
||||
{ 0x20A0, 0x20C0, 0x320 },
|
||||
{ 0x2212, 0x2212, 0x341 },
|
||||
{ 0xFFFD, 0xFFFD, 0x342 },
|
||||
};
|
||||
|
||||
static const EpdFontData notosans_16_regular = {
|
||||
notosans_16_regularBitmaps,
|
||||
notosans_16_regularGlyphs,
|
||||
notosans_16_regularIntervals,
|
||||
11,
|
||||
12,
|
||||
45,
|
||||
36,
|
||||
-10,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t notosans_18_boldBitmaps[108125] = {
|
||||
static const uint8_t notosans_18_boldBitmaps[108432] = {
|
||||
0xBF, 0xF7, 0xFF, 0xDF, 0xFF, 0x6F, 0xFD, 0xBF, 0xF2, 0xFF, 0xCB, 0xFF, 0x2F, 0xFC, 0x7F, 0xF1,
|
||||
0xFF, 0xC7, 0xFF, 0x1F, 0xFC, 0x7F, 0xF0, 0xFF, 0xC3, 0xFE, 0x0F, 0xF8, 0x3F, 0xE0, 0xFF, 0x81,
|
||||
0x54, 0x00, 0x00, 0x00, 0x00, 0x6E, 0x0B, 0xFF, 0x3F, 0xFD, 0xFF, 0xF7, 0xFF, 0xD7, 0xFE, 0x01,
|
||||
@ -6765,7 +6765,26 @@ static const uint8_t notosans_18_boldBitmaps[108125] = {
|
||||
0xF8, 0x00, 0x1F, 0xFF, 0xFD, 0x00, 0x00, 0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xC3, 0xFF, 0xFF, 0xFF, 0xFC, 0x3F, 0xFF, 0xFF, 0xFF,
|
||||
0xC2, 0xAA, 0xAA, 0xAA, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xF3, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0xF8,
|
||||
0xFF, 0xFF, 0xFF, 0xCF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x1F, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xE0, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x1F, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFA, 0xFF,
|
||||
0xE0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xE0, 0x00, 0x2F, 0xE0, 0x00, 0x00, 0x00, 0x01, 0xFC, 0x00,
|
||||
0x00, 0x0F, 0xE0, 0x00, 0x00, 0x00, 0x1F, 0xF0, 0x00, 0x00, 0x1F, 0xE0, 0x00, 0x00, 0x01, 0xFF,
|
||||
0xD0, 0x6A, 0x00, 0x3F, 0xE0, 0x00, 0x00, 0x1F, 0xFF, 0xDF, 0xFE, 0x00, 0xFF, 0xE0, 0x00, 0x01,
|
||||
0xFF, 0xFF, 0xFF, 0xFC, 0x03, 0xFF, 0xE0, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xE0, 0x0F, 0xFF, 0xE0,
|
||||
0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0xFF, 0xE0, 0x03, 0xFF,
|
||||
0xFF, 0xE0, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x3F, 0xFF, 0xFF, 0xE2, 0xFF, 0xFF, 0xFF, 0xF0, 0x07,
|
||||
0xFF, 0xFF, 0xFF, 0x02, 0xFF, 0xFF, 0xFF, 0x40, 0x7F, 0xFF, 0xFF, 0xF0, 0x02, 0xFF, 0xFF, 0xFC,
|
||||
0x03, 0xFF, 0xFF, 0xFF, 0x00, 0x02, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xF0, 0x00, 0x02, 0xFF,
|
||||
0xFF, 0xEA, 0xBF, 0xFF, 0xFF, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00,
|
||||
0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xF4, 0x0F, 0xFF, 0xF0, 0x00,
|
||||
0x00, 0x00, 0x02, 0xFF, 0x80, 0x1F, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFE, 0x00, 0x7F, 0xF0,
|
||||
0x00, 0x00, 0x00, 0x00, 0x02, 0xFC, 0x02, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xF9, 0x6F,
|
||||
0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
|
||||
0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x02, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph notosans_18_boldGlyphs[] = {
|
||||
@ -7603,6 +7622,7 @@ static const EpdGlyph notosans_18_boldGlyphs[] = {
|
||||
{ 18, 35, 21, 3, 31, 158, 107817 }, // ₿
|
||||
{ 18, 28, 20, 1, 21, 126, 107975 }, // ⃀
|
||||
{ 19, 5, 22, 1, 16, 24, 108101 }, // −
|
||||
{ 35, 35, 38, 1, 29, 307, 108125 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval notosans_18_boldIntervals[] = {
|
||||
@ -7617,13 +7637,14 @@ static const EpdUnicodeInterval notosans_18_boldIntervals[] = {
|
||||
{ 0x2066, 0x206F, 0x316 },
|
||||
{ 0x20A0, 0x20C0, 0x320 },
|
||||
{ 0x2212, 0x2212, 0x341 },
|
||||
{ 0xFFFD, 0xFFFD, 0x342 },
|
||||
};
|
||||
|
||||
static const EpdFontData notosans_18_bold = {
|
||||
notosans_18_boldBitmaps,
|
||||
notosans_18_boldGlyphs,
|
||||
notosans_18_boldIntervals,
|
||||
11,
|
||||
12,
|
||||
51,
|
||||
41,
|
||||
-11,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t notosans_18_bolditalicBitmaps[115270] = {
|
||||
static const uint8_t notosans_18_bolditalicBitmaps[115577] = {
|
||||
0x00, 0x1F, 0xFE, 0x00, 0x2F, 0xFE, 0x00, 0x3F, 0xFD, 0x00, 0x3F, 0xFC, 0x00, 0x3F, 0xFC, 0x00,
|
||||
0x7F, 0xF8, 0x00, 0x7F, 0xF4, 0x00, 0xBF, 0xF0, 0x00, 0xFF, 0xF0, 0x00, 0xFF, 0xE0, 0x00, 0xFF,
|
||||
0xD0, 0x01, 0xFF, 0xC0, 0x02, 0xFF, 0x80, 0x02, 0xFF, 0x40, 0x03, 0xFF, 0x00, 0x03, 0xFF, 0x00,
|
||||
@ -7212,7 +7212,26 @@ static const uint8_t notosans_18_bolditalicBitmaps[115270] = {
|
||||
0xFF, 0xFF, 0xFF, 0x40, 0x0B, 0xFF, 0xFF, 0xFF, 0x00, 0x03, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x03,
|
||||
0xE0, 0xF8, 0x00, 0x00, 0x00, 0xF8, 0x3E, 0x00, 0x00, 0x00, 0x3D, 0x0F, 0x40, 0x00, 0x00, 0x1A,
|
||||
0x06, 0x80, 0x00, 0x00, 0x05, 0x55, 0x54, 0x2F, 0xFF, 0xFC, 0x3F, 0xFF, 0xFC, 0x3F, 0xFF, 0xF8,
|
||||
0x3F, 0xFF, 0xF4, 0x15, 0x55, 0x50,
|
||||
0x3F, 0xFF, 0xF4, 0x15, 0x55, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xE0, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xE0, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF,
|
||||
0xE0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFA, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xE0,
|
||||
0x00, 0x2F, 0xE0, 0x00, 0x00, 0x00, 0x01, 0xFC, 0x00, 0x00, 0x0F, 0xE0, 0x00, 0x00, 0x00, 0x1F,
|
||||
0xF0, 0x00, 0x00, 0x1F, 0xE0, 0x00, 0x00, 0x01, 0xFF, 0xD0, 0x6A, 0x00, 0x3F, 0xE0, 0x00, 0x00,
|
||||
0x1F, 0xFF, 0xDF, 0xFE, 0x00, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFC, 0x03, 0xFF, 0xE0,
|
||||
0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xE0, 0x0F, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x7F,
|
||||
0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0xFF, 0xFE, 0x00,
|
||||
0x3F, 0xFF, 0xFF, 0xE2, 0xFF, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0xFF, 0x02, 0xFF, 0xFF, 0xFF,
|
||||
0x40, 0x7F, 0xFF, 0xFF, 0xF0, 0x02, 0xFF, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, 0xFF, 0x00, 0x02, 0xFF,
|
||||
0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xF0, 0x00, 0x02, 0xFF, 0xFF, 0xEA, 0xBF, 0xFF, 0xFF, 0x00, 0x00,
|
||||
0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
|
||||
0x00, 0x00, 0x02, 0xFF, 0xF4, 0x0F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x02, 0xFF, 0x80, 0x1F, 0xFF,
|
||||
0x00, 0x00, 0x00, 0x00, 0x02, 0xFE, 0x00, 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFC, 0x02,
|
||||
0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xF9, 0x6F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
|
||||
0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x02, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xF0, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph notosans_18_bolditalicGlyphs[] = {
|
||||
@ -8049,6 +8068,7 @@ static const EpdGlyph notosans_18_bolditalicGlyphs[] = {
|
||||
{ 26, 31, 23, -1, 31, 202, 114866 }, // ₾
|
||||
{ 21, 35, 21, 0, 31, 184, 115068 }, // ₿
|
||||
{ 12, 6, 12, 0, 13, 18, 115252 }, // −
|
||||
{ 35, 35, 38, 1, 29, 307, 115270 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval notosans_18_bolditalicIntervals[] = {
|
||||
@ -8063,13 +8083,14 @@ static const EpdUnicodeInterval notosans_18_bolditalicIntervals[] = {
|
||||
{ 0x2066, 0x206F, 0x316 },
|
||||
{ 0x20A0, 0x20BF, 0x320 },
|
||||
{ 0x2212, 0x2212, 0x340 },
|
||||
{ 0xFFFD, 0xFFFD, 0x341 },
|
||||
};
|
||||
|
||||
static const EpdFontData notosans_18_bolditalic = {
|
||||
notosans_18_bolditalicBitmaps,
|
||||
notosans_18_bolditalicGlyphs,
|
||||
notosans_18_bolditalicIntervals,
|
||||
11,
|
||||
12,
|
||||
51,
|
||||
41,
|
||||
-11,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t notosans_18_italicBitmaps[105127] = {
|
||||
static const uint8_t notosans_18_italicBitmaps[105434] = {
|
||||
0x00, 0x0B, 0xF0, 0x00, 0x3F, 0xC0, 0x00, 0xFF, 0x00, 0x07, 0xF8, 0x00, 0x1F, 0xD0, 0x00, 0xBF,
|
||||
0x00, 0x03, 0xFC, 0x00, 0x0F, 0xE0, 0x00, 0x3F, 0x40, 0x01, 0xFC, 0x00, 0x0B, 0xF0, 0x00, 0x2F,
|
||||
0x80, 0x00, 0xFD, 0x00, 0x03, 0xF0, 0x00, 0x0F, 0xC0, 0x00, 0x7E, 0x00, 0x02, 0xF4, 0x00, 0x0F,
|
||||
@ -6578,7 +6578,26 @@ static const uint8_t notosans_18_italicBitmaps[105127] = {
|
||||
0xC0, 0x00, 0x3F, 0xC0, 0x2F, 0x80, 0x02, 0xFF, 0x40, 0x3F, 0xFF, 0xFF, 0xFD, 0x00, 0x3F, 0xFF,
|
||||
0xFF, 0xF4, 0x00, 0x7F, 0xFF, 0xFE, 0x40, 0x00, 0x01, 0xF0, 0xB8, 0x00, 0x00, 0x02, 0xE0, 0xF4,
|
||||
0x00, 0x00, 0x03, 0xD0, 0xF4, 0x00, 0x00, 0x02, 0x80, 0xA0, 0x00, 0x00, 0x0A, 0xAA, 0xA0, 0xBF,
|
||||
0xFF, 0xD3, 0xFF, 0xFF, 0x05, 0x55, 0x54,
|
||||
0xFF, 0xD3, 0xFF, 0xFF, 0x05, 0x55, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xE0, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xE0,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF,
|
||||
0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFA, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x1F,
|
||||
0xE0, 0x00, 0x2F, 0xE0, 0x00, 0x00, 0x00, 0x01, 0xFC, 0x00, 0x00, 0x0F, 0xE0, 0x00, 0x00, 0x00,
|
||||
0x1F, 0xF0, 0x00, 0x00, 0x1F, 0xE0, 0x00, 0x00, 0x01, 0xFF, 0xD0, 0x6A, 0x00, 0x3F, 0xE0, 0x00,
|
||||
0x00, 0x1F, 0xFF, 0xDF, 0xFE, 0x00, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFC, 0x03, 0xFF,
|
||||
0xE0, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xE0, 0x0F, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x00,
|
||||
0x7F, 0xFF, 0xE0, 0x1F, 0xFF, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0xFF, 0xFE,
|
||||
0x00, 0x3F, 0xFF, 0xFF, 0xE2, 0xFF, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0xFF, 0x02, 0xFF, 0xFF,
|
||||
0xFF, 0x40, 0x7F, 0xFF, 0xFF, 0xF0, 0x02, 0xFF, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, 0xFF, 0x00, 0x02,
|
||||
0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xF0, 0x00, 0x02, 0xFF, 0xFF, 0xEA, 0xBF, 0xFF, 0xFF, 0x00,
|
||||
0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0x00, 0x00, 0x00, 0x02, 0xFF, 0xF4, 0x0F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x02, 0xFF, 0x80, 0x1F,
|
||||
0xFF, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFE, 0x00, 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFC,
|
||||
0x02, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xF9, 0x6F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x02, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x02, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xF0, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph notosans_18_italicGlyphs[] = {
|
||||
@ -7415,6 +7434,7 @@ static const EpdGlyph notosans_18_italicGlyphs[] = {
|
||||
{ 25, 31, 22, -1, 31, 194, 104747 }, // ₾
|
||||
{ 20, 35, 21, 1, 31, 175, 104941 }, // ₿
|
||||
{ 11, 4, 12, 0, 12, 11, 105116 }, // −
|
||||
{ 35, 35, 38, 1, 29, 307, 105127 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval notosans_18_italicIntervals[] = {
|
||||
@ -7429,13 +7449,14 @@ static const EpdUnicodeInterval notosans_18_italicIntervals[] = {
|
||||
{ 0x2066, 0x206F, 0x316 },
|
||||
{ 0x20A0, 0x20BF, 0x320 },
|
||||
{ 0x2212, 0x2212, 0x340 },
|
||||
{ 0xFFFD, 0xFFFD, 0x341 },
|
||||
};
|
||||
|
||||
static const EpdFontData notosans_18_italic = {
|
||||
notosans_18_italicBitmaps,
|
||||
notosans_18_italicGlyphs,
|
||||
notosans_18_italicIntervals,
|
||||
11,
|
||||
12,
|
||||
51,
|
||||
41,
|
||||
-11,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t notosans_18_regularBitmaps[98532] = {
|
||||
static const uint8_t notosans_18_regularBitmaps[98839] = {
|
||||
0x2F, 0xC3, 0xFC, 0x3F, 0xC3, 0xFC, 0x3F, 0xC2, 0xFC, 0x2F, 0x82, 0xF8, 0x2F, 0x82, 0xF8, 0x1F,
|
||||
0x81, 0xF4, 0x1F, 0x41, 0xF4, 0x1F, 0x40, 0xF4, 0x0F, 0x40, 0xF0, 0x0F, 0x00, 0x50, 0x00, 0x00,
|
||||
0x00, 0x05, 0x03, 0xFC, 0x7F, 0xD3, 0xFD, 0x3F, 0xC0, 0x50, 0x7F, 0x02, 0xF9, 0xFC, 0x0B, 0xF7,
|
||||
@ -6166,7 +6166,26 @@ static const uint8_t notosans_18_regularBitmaps[98532] = {
|
||||
0xBC, 0x00, 0xFF, 0xFF, 0xFC, 0x00, 0x1F, 0xFF, 0xF8, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFD, 0x3F, 0xFF, 0xFF, 0xFD, 0x2A, 0xAA, 0xAA,
|
||||
0xA8, 0x2A, 0xAA, 0xAA, 0xAA, 0xA4, 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xFF, 0xFF, 0x80,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xE0, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0xFF, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xE0, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0xFF, 0xFA, 0xFF, 0xE0, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xE0, 0x00, 0x2F,
|
||||
0xE0, 0x00, 0x00, 0x00, 0x01, 0xFC, 0x00, 0x00, 0x0F, 0xE0, 0x00, 0x00, 0x00, 0x1F, 0xF0, 0x00,
|
||||
0x00, 0x1F, 0xE0, 0x00, 0x00, 0x01, 0xFF, 0xD0, 0x6A, 0x00, 0x3F, 0xE0, 0x00, 0x00, 0x1F, 0xFF,
|
||||
0xDF, 0xFE, 0x00, 0xFF, 0xE0, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFC, 0x03, 0xFF, 0xE0, 0x00, 0x1F,
|
||||
0xFF, 0xFF, 0xFF, 0xE0, 0x0F, 0xFF, 0xE0, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x7F, 0xFF, 0xE0,
|
||||
0x1F, 0xFF, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xE0, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x3F, 0xFF,
|
||||
0xFF, 0xE2, 0xFF, 0xFF, 0xFF, 0xF0, 0x07, 0xFF, 0xFF, 0xFF, 0x02, 0xFF, 0xFF, 0xFF, 0x40, 0x7F,
|
||||
0xFF, 0xFF, 0xF0, 0x02, 0xFF, 0xFF, 0xFC, 0x03, 0xFF, 0xFF, 0xFF, 0x00, 0x02, 0xFF, 0xFF, 0xF0,
|
||||
0x0F, 0xFF, 0xFF, 0xF0, 0x00, 0x02, 0xFF, 0xFF, 0xEA, 0xBF, 0xFF, 0xFF, 0x00, 0x00, 0x02, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00,
|
||||
0x02, 0xFF, 0xF4, 0x0F, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x02, 0xFF, 0x80, 0x1F, 0xFF, 0x00, 0x00,
|
||||
0x00, 0x00, 0x02, 0xFE, 0x00, 0x7F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFC, 0x02, 0xFF, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x02, 0xF9, 0x6F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
|
||||
0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const EpdGlyph notosans_18_regularGlyphs[] = {
|
||||
@ -7004,6 +7023,7 @@ static const EpdGlyph notosans_18_regularGlyphs[] = {
|
||||
{ 18, 35, 21, 3, 31, 158, 98247 }, // ₿
|
||||
{ 16, 27, 18, 1, 21, 108, 98405 }, // ⃀
|
||||
{ 19, 4, 21, 1, 15, 19, 98513 }, // −
|
||||
{ 35, 35, 38, 1, 29, 307, 98532 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval notosans_18_regularIntervals[] = {
|
||||
@ -7018,13 +7038,14 @@ static const EpdUnicodeInterval notosans_18_regularIntervals[] = {
|
||||
{ 0x2066, 0x206F, 0x316 },
|
||||
{ 0x20A0, 0x20C0, 0x320 },
|
||||
{ 0x2212, 0x2212, 0x341 },
|
||||
{ 0xFFFD, 0xFFFD, 0x342 },
|
||||
};
|
||||
|
||||
static const EpdFontData notosans_18_regular = {
|
||||
notosans_18_regularBitmaps,
|
||||
notosans_18_regularGlyphs,
|
||||
notosans_18_regularIntervals,
|
||||
11,
|
||||
12,
|
||||
51,
|
||||
41,
|
||||
-11,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#pragma once
|
||||
#include "EpdFontData.h"
|
||||
|
||||
static const uint8_t notosans_8_regularBitmaps[10949] = {
|
||||
static const uint8_t notosans_8_regularBitmaps[10981] = {
|
||||
0xDB, 0x6D, 0xB6, 0xC3, 0xF4, 0xDE, 0xF7, 0xBD, 0x80, 0x0D, 0x83, 0x30, 0x66, 0x3F, 0xF7, 0xFE,
|
||||
0x36, 0x04, 0xC7, 0xFE, 0xFF, 0xC6, 0x40, 0xD8, 0x1B, 0x00, 0x18, 0x18, 0xFE, 0xFE, 0xD8, 0xF8,
|
||||
0xFC, 0x3E, 0x1F, 0x1B, 0xFF, 0xFE, 0x38, 0x18, 0x00, 0x01, 0xE1, 0x86, 0xCC, 0x13, 0x30, 0xCD,
|
||||
@ -692,7 +692,9 @@ static const uint8_t notosans_8_regularBitmaps[10949] = {
|
||||
0x07, 0x01, 0xC0, 0x1B, 0x03, 0xE1, 0xFF, 0x3D, 0xED, 0xBF, 0xB6, 0xF6, 0xDE, 0x53, 0xC0, 0x1C,
|
||||
0x01, 0x80, 0x3F, 0xE7, 0xFC, 0x3C, 0x3C, 0xFE, 0xFF, 0xE3, 0xE3, 0xE7, 0xFE, 0xFF, 0xE3, 0xE3,
|
||||
0xE3, 0xFF, 0xFE, 0x3C, 0x3C, 0x00, 0x3F, 0x7E, 0x60, 0x60, 0xE0, 0xE0, 0x60, 0x7E, 0x3F, 0x0C,
|
||||
0x7F, 0x7E, 0x7F, 0xFF, 0xC0,
|
||||
0x7F, 0x7E, 0x7F, 0xFF, 0xC0, 0x00, 0x80, 0x01, 0xC0, 0x03, 0xE0, 0x07, 0xF0, 0x0C, 0x38, 0x1F,
|
||||
0xDC, 0x3F, 0xDE, 0x7F, 0xBF, 0x7F, 0x7F, 0x3F, 0x7E, 0x1F, 0xFC, 0x0F, 0xF8, 0x06, 0x70, 0x03,
|
||||
0xE0, 0x01, 0xC0, 0x00, 0x80,
|
||||
};
|
||||
|
||||
static const EpdGlyph notosans_8_regularGlyphs[] = {
|
||||
@ -1530,6 +1532,7 @@ static const EpdGlyph notosans_8_regularGlyphs[] = {
|
||||
{ 8, 16, 10, 1, 14, 16, 10917 }, // ₿
|
||||
{ 8, 13, 8, 0, 10, 13, 10933 }, // ⃀
|
||||
{ 9, 2, 10, 0, 7, 3, 10946 }, // −
|
||||
{ 16, 16, 17, 0, 13, 32, 10949 }, // <20>
|
||||
};
|
||||
|
||||
static const EpdUnicodeInterval notosans_8_regularIntervals[] = {
|
||||
@ -1544,13 +1547,14 @@ static const EpdUnicodeInterval notosans_8_regularIntervals[] = {
|
||||
{ 0x2066, 0x206F, 0x316 },
|
||||
{ 0x20A0, 0x20C0, 0x320 },
|
||||
{ 0x2212, 0x2212, 0x341 },
|
||||
{ 0xFFFD, 0xFFFD, 0x342 },
|
||||
};
|
||||
|
||||
static const EpdFontData notosans_8_regular = {
|
||||
notosans_8_regularBitmaps,
|
||||
notosans_8_regularGlyphs,
|
||||
notosans_8_regularIntervals,
|
||||
11,
|
||||
12,
|
||||
23,
|
||||
18,
|
||||
-5,
|
||||
|
||||
@ -99,6 +99,9 @@ intervals = [
|
||||
# (0xFE30, 0xFE4F),
|
||||
# # CJK Compatibility Ideographs
|
||||
# (0xF900, 0xFAFF),
|
||||
### Specials
|
||||
# Replacement Character
|
||||
(0xFFFD, 0xFFFD),
|
||||
]
|
||||
|
||||
add_ints = []
|
||||
|
||||
@ -74,6 +74,7 @@ bool Epub::parseContentOpf(BookMetadataCache::BookMetadata& bookMetadata) {
|
||||
// Grab data from opfParser into epub
|
||||
bookMetadata.title = opfParser.title;
|
||||
bookMetadata.author = opfParser.author;
|
||||
bookMetadata.language = opfParser.language;
|
||||
bookMetadata.coverItemHref = opfParser.coverItemHref;
|
||||
bookMetadata.textReferenceHref = opfParser.textReferenceHref;
|
||||
|
||||
@ -405,6 +406,15 @@ const std::string& Epub::getAuthor() const {
|
||||
return bookMetadataCache->coreMetadata.author;
|
||||
}
|
||||
|
||||
const std::string& Epub::getLanguage() const {
|
||||
static std::string blank;
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
||||
return blank;
|
||||
}
|
||||
|
||||
return bookMetadataCache->coreMetadata.language;
|
||||
}
|
||||
|
||||
std::string Epub::getCoverBmpPath(bool cropped) const {
|
||||
const auto coverFileName = "cover" + cropped ? "_crop" : "";
|
||||
return cachePath + "/" + coverFileName + ".bmp";
|
||||
@ -666,14 +676,15 @@ int Epub::getSpineIndexForTextReference() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Calculate progress in book
|
||||
uint8_t Epub::calculateProgress(const int currentSpineIndex, const float currentSpineRead) const {
|
||||
// Calculate progress in book (returns 0.0-1.0)
|
||||
float Epub::calculateProgress(const int currentSpineIndex, const float currentSpineRead) const {
|
||||
const size_t bookSize = getBookSize();
|
||||
if (bookSize == 0) {
|
||||
return 0;
|
||||
return 0.0f;
|
||||
}
|
||||
const size_t prevChapterSize = (currentSpineIndex >= 1) ? getCumulativeSpineItemSize(currentSpineIndex - 1) : 0;
|
||||
const size_t curChapterSize = getCumulativeSpineItemSize(currentSpineIndex) - prevChapterSize;
|
||||
const size_t sectionProgSize = currentSpineRead * curChapterSize;
|
||||
return round(static_cast<float>(prevChapterSize + sectionProgSize) / bookSize * 100.0);
|
||||
const float sectionProgSize = currentSpineRead * static_cast<float>(curChapterSize);
|
||||
const float totalProgress = static_cast<float>(prevChapterSize) + sectionProgSize;
|
||||
return totalProgress / static_cast<float>(bookSize);
|
||||
}
|
||||
|
||||
@ -48,6 +48,7 @@ class Epub {
|
||||
const std::string& getPath() const;
|
||||
const std::string& getTitle() const;
|
||||
const std::string& getAuthor() const;
|
||||
const std::string& getLanguage() const;
|
||||
std::string getCoverBmpPath(bool cropped = false) const;
|
||||
bool generateCoverBmp(bool cropped = false) const;
|
||||
std::string getThumbBmpPath() const;
|
||||
@ -66,6 +67,6 @@ class Epub {
|
||||
int getSpineIndexForTextReference() const;
|
||||
|
||||
size_t getBookSize() const;
|
||||
uint8_t calculateProgress(int currentSpineIndex, float currentSpineRead) const;
|
||||
float calculateProgress(int currentSpineIndex, float currentSpineRead) const;
|
||||
const CssParser* getCssParser() const { return cssParser.get(); }
|
||||
};
|
||||
|
||||
@ -92,8 +92,9 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
|
||||
for (const auto& css : metadata.cssFiles) {
|
||||
cssFilesSize += sizeof(uint32_t) + css.size();
|
||||
}
|
||||
const uint32_t metadataSize = metadata.title.size() + metadata.author.size() + metadata.coverItemHref.size() +
|
||||
metadata.textReferenceHref.size() + sizeof(uint32_t) * 4 + cssFilesSize;
|
||||
const uint32_t metadataSize = metadata.title.size() + metadata.author.size() + metadata.language.size() +
|
||||
metadata.coverItemHref.size() + metadata.textReferenceHref.size() +
|
||||
sizeof(uint32_t) * 5 + cssFilesSize;
|
||||
const uint32_t lutSize = sizeof(uint32_t) * spineCount + sizeof(uint32_t) * tocCount;
|
||||
const uint32_t lutOffset = headerASize + metadataSize;
|
||||
|
||||
@ -105,6 +106,7 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
|
||||
// Metadata
|
||||
serialization::writeString(bookFile, metadata.title);
|
||||
serialization::writeString(bookFile, metadata.author);
|
||||
serialization::writeString(bookFile, metadata.language);
|
||||
serialization::writeString(bookFile, metadata.coverItemHref);
|
||||
serialization::writeString(bookFile, metadata.textReferenceHref);
|
||||
// CSS files
|
||||
@ -299,6 +301,7 @@ bool BookMetadataCache::load() {
|
||||
|
||||
serialization::readString(bookFile, coreMetadata.title);
|
||||
serialization::readString(bookFile, coreMetadata.author);
|
||||
serialization::readString(bookFile, coreMetadata.language);
|
||||
serialization::readString(bookFile, coreMetadata.coverItemHref);
|
||||
serialization::readString(bookFile, coreMetadata.textReferenceHref);
|
||||
// CSS files
|
||||
|
||||
@ -10,6 +10,7 @@ class BookMetadataCache {
|
||||
struct BookMetadata {
|
||||
std::string title;
|
||||
std::string author;
|
||||
std::string language;
|
||||
std::string coverItemHref;
|
||||
std::string textReferenceHref;
|
||||
std::vector<std::string> cssFiles;
|
||||
|
||||
@ -5,11 +5,50 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
#include "hyphenation/Hyphenator.h"
|
||||
|
||||
constexpr int MAX_COST = std::numeric_limits<int>::max();
|
||||
|
||||
namespace {
|
||||
|
||||
// Soft hyphen byte pattern used throughout EPUBs (UTF-8 for U+00AD).
|
||||
constexpr char SOFT_HYPHEN_UTF8[] = "\xC2\xAD";
|
||||
constexpr size_t SOFT_HYPHEN_BYTES = 2;
|
||||
|
||||
bool containsSoftHyphen(const std::string& word) { return word.find(SOFT_HYPHEN_UTF8) != std::string::npos; }
|
||||
|
||||
// Removes every soft hyphen in-place so rendered glyphs match measured widths.
|
||||
void stripSoftHyphensInPlace(std::string& word) {
|
||||
size_t pos = 0;
|
||||
while ((pos = word.find(SOFT_HYPHEN_UTF8, pos)) != std::string::npos) {
|
||||
word.erase(pos, SOFT_HYPHEN_BYTES);
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the rendered width for a word while ignoring soft hyphen glyphs and optionally appending a visible hyphen.
|
||||
uint16_t measureWordWidth(const GfxRenderer& renderer, const int fontId, const std::string& word,
|
||||
const EpdFontFamily::Style style, const bool appendHyphen = false) {
|
||||
const bool hasSoftHyphen = containsSoftHyphen(word);
|
||||
if (!hasSoftHyphen && !appendHyphen) {
|
||||
return renderer.getTextWidth(fontId, word.c_str(), style);
|
||||
}
|
||||
|
||||
std::string sanitized = word;
|
||||
if (hasSoftHyphen) {
|
||||
stripSoftHyphensInPlace(sanitized);
|
||||
}
|
||||
if (appendHyphen) {
|
||||
sanitized.push_back('-');
|
||||
}
|
||||
return renderer.getTextWidth(fontId, sanitized.c_str(), style);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ParsedText::addWord(std::string word, const EpdFontFamily::Style fontStyle, const bool underline) {
|
||||
if (word.empty()) return;
|
||||
|
||||
@ -26,10 +65,19 @@ void ParsedText::layoutAndExtractLines(const GfxRenderer& renderer, const int fo
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply fixed transforms before any per-line layout work.
|
||||
applyParagraphIndent();
|
||||
|
||||
const int pageWidth = viewportWidth;
|
||||
const int spaceWidth = renderer.getSpaceWidth(fontId);
|
||||
const auto wordWidths = calculateWordWidths(renderer, fontId);
|
||||
const auto lineBreakIndices = computeLineBreaks(pageWidth, spaceWidth, wordWidths);
|
||||
auto wordWidths = calculateWordWidths(renderer, fontId);
|
||||
std::vector<size_t> lineBreakIndices;
|
||||
if (hyphenationEnabled) {
|
||||
// Use greedy layout that can split words mid-loop when a hyphenated prefix fits.
|
||||
lineBreakIndices = computeHyphenatedLineBreaks(renderer, fontId, pageWidth, spaceWidth, wordWidths);
|
||||
} else {
|
||||
lineBreakIndices = computeLineBreaks(renderer, fontId, pageWidth, spaceWidth, wordWidths);
|
||||
}
|
||||
const size_t lineCount = includeLastLine ? lineBreakIndices.size() : lineBreakIndices.size() - 1;
|
||||
|
||||
for (size_t i = 0; i < lineCount; ++i) {
|
||||
@ -43,23 +91,12 @@ std::vector<uint16_t> ParsedText::calculateWordWidths(const GfxRenderer& rendere
|
||||
std::vector<uint16_t> wordWidths;
|
||||
wordWidths.reserve(totalWordCount);
|
||||
|
||||
// Apply text indent: either from CSS blockStyle or default em-space for justified/left-aligned
|
||||
const bool shouldIndent = (style == TextBlock::JUSTIFIED || style == TextBlock::LEFT_ALIGN) && !extraParagraphSpacing;
|
||||
if (blockStyle.textIndent > 0) {
|
||||
// CSS text-indent is handled via first word width adjustment
|
||||
// We'll add the indent value directly to the first word's width
|
||||
} else if (shouldIndent) {
|
||||
// Default: add em-space at the beginning of first word in paragraph to indent
|
||||
std::string& first_word = words.front();
|
||||
first_word.insert(0, "\xe2\x80\x83");
|
||||
}
|
||||
|
||||
auto wordsIt = words.begin();
|
||||
auto wordStylesIt = wordStyles.begin();
|
||||
bool isFirst = true;
|
||||
|
||||
while (wordsIt != words.end()) {
|
||||
uint16_t width = renderer.getTextWidth(fontId, wordsIt->c_str(), *wordStylesIt);
|
||||
uint16_t width = measureWordWidth(renderer, fontId, *wordsIt, *wordStylesIt);
|
||||
|
||||
// Add CSS text-indent to first word width
|
||||
if (isFirst && blockStyle.textIndent > 0 && shouldIndent) {
|
||||
@ -78,8 +115,21 @@ std::vector<uint16_t> ParsedText::calculateWordWidths(const GfxRenderer& rendere
|
||||
return wordWidths;
|
||||
}
|
||||
|
||||
std::vector<size_t> ParsedText::computeLineBreaks(const int pageWidth, const int spaceWidth,
|
||||
const std::vector<uint16_t>& wordWidths) const {
|
||||
std::vector<size_t> ParsedText::computeLineBreaks(const GfxRenderer& renderer, const int fontId, const int pageWidth,
|
||||
const int spaceWidth, std::vector<uint16_t>& wordWidths) {
|
||||
if (words.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Ensure any word that would overflow even as the first entry on a line is split using fallback hyphenation.
|
||||
for (size_t i = 0; i < wordWidths.size(); ++i) {
|
||||
while (wordWidths[i] > pageWidth) {
|
||||
if (!hyphenateWordAtIndex(i, pageWidth, renderer, fontId, wordWidths, /*allowFallbackBreaks=*/true)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const size_t totalWordCount = words.size();
|
||||
|
||||
// DP table to store the minimum badness (cost) of lines starting at index i
|
||||
@ -157,6 +207,141 @@ std::vector<size_t> ParsedText::computeLineBreaks(const int pageWidth, const int
|
||||
return lineBreakIndices;
|
||||
}
|
||||
|
||||
void ParsedText::applyParagraphIndent() {
|
||||
if (extraParagraphSpacing || words.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (blockStyle.textIndent > 0) {
|
||||
// CSS text-indent is handled via first word width adjustment
|
||||
// We'll add the indent value directly to the first word's width
|
||||
} else if (style == TextBlock::JUSTIFIED || style == TextBlock::LEFT_ALIGN) {
|
||||
words.front().insert(0, "\xe2\x80\x83");
|
||||
}
|
||||
}
|
||||
|
||||
// Builds break indices while opportunistically splitting the word that would overflow the current line.
|
||||
std::vector<size_t> ParsedText::computeHyphenatedLineBreaks(const GfxRenderer& renderer, const int fontId,
|
||||
const int pageWidth, const int spaceWidth,
|
||||
std::vector<uint16_t>& wordWidths) {
|
||||
std::vector<size_t> lineBreakIndices;
|
||||
size_t currentIndex = 0;
|
||||
|
||||
while (currentIndex < wordWidths.size()) {
|
||||
const size_t lineStart = currentIndex;
|
||||
int lineWidth = 0;
|
||||
|
||||
// Consume as many words as possible for current line, splitting when prefixes fit
|
||||
while (currentIndex < wordWidths.size()) {
|
||||
const bool isFirstWord = currentIndex == lineStart;
|
||||
const int spacing = isFirstWord ? 0 : spaceWidth;
|
||||
const int candidateWidth = spacing + wordWidths[currentIndex];
|
||||
|
||||
// Word fits on current line
|
||||
if (lineWidth + candidateWidth <= pageWidth) {
|
||||
lineWidth += candidateWidth;
|
||||
++currentIndex;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Word would overflow — try to split based on hyphenation points
|
||||
const int availableWidth = pageWidth - lineWidth - spacing;
|
||||
const bool allowFallbackBreaks = isFirstWord; // Only for first word on line
|
||||
|
||||
if (availableWidth > 0 &&
|
||||
hyphenateWordAtIndex(currentIndex, availableWidth, renderer, fontId, wordWidths, allowFallbackBreaks)) {
|
||||
// Prefix now fits; append it to this line and move to next line
|
||||
lineWidth += spacing + wordWidths[currentIndex];
|
||||
++currentIndex;
|
||||
break;
|
||||
}
|
||||
|
||||
// Could not split: force at least one word per line to avoid infinite loop
|
||||
if (currentIndex == lineStart) {
|
||||
lineWidth += candidateWidth;
|
||||
++currentIndex;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
lineBreakIndices.push_back(currentIndex);
|
||||
}
|
||||
|
||||
return lineBreakIndices;
|
||||
}
|
||||
|
||||
// Splits words[wordIndex] into prefix (adding a hyphen only when needed) and remainder when a legal breakpoint fits the
|
||||
// available width.
|
||||
bool ParsedText::hyphenateWordAtIndex(const size_t wordIndex, const int availableWidth, const GfxRenderer& renderer,
|
||||
const int fontId, std::vector<uint16_t>& wordWidths,
|
||||
const bool allowFallbackBreaks) {
|
||||
// Guard against invalid indices or zero available width before attempting to split.
|
||||
if (availableWidth <= 0 || wordIndex >= words.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get iterators to target word and style.
|
||||
auto wordIt = words.begin();
|
||||
auto styleIt = wordStyles.begin();
|
||||
std::advance(wordIt, wordIndex);
|
||||
std::advance(styleIt, wordIndex);
|
||||
|
||||
const std::string& word = *wordIt;
|
||||
const auto style = *styleIt;
|
||||
|
||||
// Collect candidate breakpoints (byte offsets and hyphen requirements).
|
||||
auto breakInfos = Hyphenator::breakOffsets(word, allowFallbackBreaks);
|
||||
if (breakInfos.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t chosenOffset = 0;
|
||||
int chosenWidth = -1;
|
||||
bool chosenNeedsHyphen = true;
|
||||
|
||||
// Iterate over each legal breakpoint and retain the widest prefix that still fits.
|
||||
for (const auto& info : breakInfos) {
|
||||
const size_t offset = info.byteOffset;
|
||||
if (offset == 0 || offset >= word.size()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool needsHyphen = info.requiresInsertedHyphen;
|
||||
const int prefixWidth = measureWordWidth(renderer, fontId, word.substr(0, offset), style, needsHyphen);
|
||||
if (prefixWidth > availableWidth || prefixWidth <= chosenWidth) {
|
||||
continue; // Skip if too wide or not an improvement
|
||||
}
|
||||
|
||||
chosenWidth = prefixWidth;
|
||||
chosenOffset = offset;
|
||||
chosenNeedsHyphen = needsHyphen;
|
||||
}
|
||||
|
||||
if (chosenWidth < 0) {
|
||||
// No hyphenation point produced a prefix that fits in the remaining space.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Split the word at the selected breakpoint and append a hyphen if required.
|
||||
std::string remainder = word.substr(chosenOffset);
|
||||
wordIt->resize(chosenOffset);
|
||||
if (chosenNeedsHyphen) {
|
||||
wordIt->push_back('-');
|
||||
}
|
||||
|
||||
// Insert the remainder word (with matching style) directly after the prefix.
|
||||
auto insertWordIt = std::next(wordIt);
|
||||
auto insertStyleIt = std::next(styleIt);
|
||||
words.insert(insertWordIt, remainder);
|
||||
wordStyles.insert(insertStyleIt, style);
|
||||
|
||||
// Update cached widths to reflect the new prefix/remainder pairing.
|
||||
wordWidths[wordIndex] = static_cast<uint16_t>(chosenWidth);
|
||||
const uint16_t remainderWidth = measureWordWidth(renderer, fontId, remainder, style);
|
||||
wordWidths.insert(wordWidths.begin() + wordIndex + 1, remainderWidth);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ParsedText::extractLine(const size_t breakIndex, const int pageWidth, const int spaceWidth,
|
||||
const std::vector<uint16_t>& wordWidths, const std::vector<size_t>& lineBreakIndices,
|
||||
const std::function<void(std::shared_ptr<TextBlock>)>& processLine) {
|
||||
@ -212,6 +397,12 @@ void ParsedText::extractLine(const size_t breakIndex, const int pageWidth, const
|
||||
std::list<bool> lineWordUnderlines;
|
||||
lineWordUnderlines.splice(lineWordUnderlines.begin(), wordUnderlines, wordUnderlines.begin(), wordUnderlineEndIt);
|
||||
|
||||
for (auto& word : lineWords) {
|
||||
if (containsSoftHyphen(word)) {
|
||||
stripSoftHyphensInPlace(word);
|
||||
}
|
||||
}
|
||||
|
||||
processLine(std::make_shared<TextBlock>(std::move(lineWords), std::move(lineXPos), std::move(lineWordStyles), style,
|
||||
blockStyle, std::move(lineWordUnderlines)));
|
||||
}
|
||||
|
||||
@ -20,8 +20,15 @@ class ParsedText {
|
||||
TextBlock::Style style;
|
||||
BlockStyle blockStyle;
|
||||
bool extraParagraphSpacing;
|
||||
bool hyphenationEnabled;
|
||||
|
||||
std::vector<size_t> computeLineBreaks(int pageWidth, int spaceWidth, const std::vector<uint16_t>& wordWidths) const;
|
||||
void applyParagraphIndent();
|
||||
std::vector<size_t> computeLineBreaks(const GfxRenderer& renderer, int fontId, int pageWidth, int spaceWidth,
|
||||
std::vector<uint16_t>& wordWidths);
|
||||
std::vector<size_t> computeHyphenatedLineBreaks(const GfxRenderer& renderer, int fontId, int pageWidth,
|
||||
int spaceWidth, std::vector<uint16_t>& wordWidths);
|
||||
bool hyphenateWordAtIndex(size_t wordIndex, int availableWidth, const GfxRenderer& renderer, int fontId,
|
||||
std::vector<uint16_t>& wordWidths, bool allowFallbackBreaks);
|
||||
void extractLine(size_t breakIndex, int pageWidth, int spaceWidth, const std::vector<uint16_t>& wordWidths,
|
||||
const std::vector<size_t>& lineBreakIndices,
|
||||
const std::function<void(std::shared_ptr<TextBlock>)>& processLine);
|
||||
@ -29,8 +36,9 @@ class ParsedText {
|
||||
|
||||
public:
|
||||
explicit ParsedText(const TextBlock::Style style, const bool extraParagraphSpacing,
|
||||
const BlockStyle& blockStyle = BlockStyle())
|
||||
: style(style), blockStyle(blockStyle), extraParagraphSpacing(extraParagraphSpacing) {}
|
||||
const bool hyphenationEnabled = false,
|
||||
const BlockStyle& blockStyle = BlockStyle()))
|
||||
: style(style), extraParagraphSpacing(extraParagraphSpacing), hyphenationEnabled(hyphenationEnabled, blockStyle(blockStyle)) {}
|
||||
~ParsedText() = default;
|
||||
|
||||
void addWord(std::string word, EpdFontFamily::Style fontStyle, bool underline = false);
|
||||
@ -43,4 +51,4 @@ class ParsedText {
|
||||
void layoutAndExtractLines(const GfxRenderer& renderer, int fontId, uint16_t viewportWidth,
|
||||
const std::function<void(std::shared_ptr<TextBlock>)>& processLine,
|
||||
bool includeLastLine = true);
|
||||
};
|
||||
};
|
||||
@ -4,12 +4,14 @@
|
||||
#include <Serialization.h>
|
||||
|
||||
#include "Page.h"
|
||||
#include "hyphenation/Hyphenator.h"
|
||||
#include "parsers/ChapterHtmlSlimParser.h"
|
||||
|
||||
namespace {
|
||||
constexpr uint8_t SECTION_FILE_VERSION = 10;
|
||||
constexpr uint32_t HEADER_SIZE = sizeof(uint8_t) + sizeof(int) + sizeof(float) + sizeof(bool) + sizeof(uint8_t) +
|
||||
sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t);
|
||||
sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint16_t) + sizeof(bool) +
|
||||
sizeof(uint32_t);
|
||||
} // namespace
|
||||
|
||||
uint32_t Section::onPageComplete(std::unique_ptr<Page> page) {
|
||||
@ -31,14 +33,15 @@ uint32_t Section::onPageComplete(std::unique_ptr<Page> page) {
|
||||
|
||||
void Section::writeSectionFileHeader(const int fontId, const float lineCompression, const bool extraParagraphSpacing,
|
||||
const uint8_t paragraphAlignment, const uint16_t viewportWidth,
|
||||
const uint16_t viewportHeight) {
|
||||
const uint16_t viewportHeight, const bool hyphenationEnabled) {
|
||||
if (!file) {
|
||||
Serial.printf("[%lu] [SCT] File not open for writing header\n", millis());
|
||||
return;
|
||||
}
|
||||
static_assert(HEADER_SIZE == sizeof(SECTION_FILE_VERSION) + sizeof(fontId) + sizeof(lineCompression) +
|
||||
sizeof(extraParagraphSpacing) + sizeof(paragraphAlignment) + sizeof(viewportWidth) +
|
||||
sizeof(viewportHeight) + sizeof(pageCount) + sizeof(uint32_t),
|
||||
sizeof(viewportHeight) + sizeof(pageCount) + sizeof(hyphenationEnabled) +
|
||||
sizeof(uint32_t),
|
||||
"Header size mismatch");
|
||||
serialization::writePod(file, SECTION_FILE_VERSION);
|
||||
serialization::writePod(file, fontId);
|
||||
@ -47,13 +50,14 @@ void Section::writeSectionFileHeader(const int fontId, const float lineCompressi
|
||||
serialization::writePod(file, paragraphAlignment);
|
||||
serialization::writePod(file, viewportWidth);
|
||||
serialization::writePod(file, viewportHeight);
|
||||
serialization::writePod(file, hyphenationEnabled);
|
||||
serialization::writePod(file, pageCount); // Placeholder for page count (will be initially 0 when written)
|
||||
serialization::writePod(file, static_cast<uint32_t>(0)); // Placeholder for LUT offset
|
||||
}
|
||||
|
||||
bool Section::loadSectionFile(const int fontId, const float lineCompression, const bool extraParagraphSpacing,
|
||||
const uint8_t paragraphAlignment, const uint16_t viewportWidth,
|
||||
const uint16_t viewportHeight) {
|
||||
const uint16_t viewportHeight, const bool hyphenationEnabled) {
|
||||
if (!SdMan.openFileForRead("SCT", filePath, file)) {
|
||||
return false;
|
||||
}
|
||||
@ -74,16 +78,19 @@ bool Section::loadSectionFile(const int fontId, const float lineCompression, con
|
||||
float fileLineCompression;
|
||||
bool fileExtraParagraphSpacing;
|
||||
uint8_t fileParagraphAlignment;
|
||||
bool fileHyphenationEnabled;
|
||||
serialization::readPod(file, fileFontId);
|
||||
serialization::readPod(file, fileLineCompression);
|
||||
serialization::readPod(file, fileExtraParagraphSpacing);
|
||||
serialization::readPod(file, fileParagraphAlignment);
|
||||
serialization::readPod(file, fileViewportWidth);
|
||||
serialization::readPod(file, fileViewportHeight);
|
||||
serialization::readPod(file, fileHyphenationEnabled);
|
||||
|
||||
if (fontId != fileFontId || lineCompression != fileLineCompression ||
|
||||
extraParagraphSpacing != fileExtraParagraphSpacing || paragraphAlignment != fileParagraphAlignment ||
|
||||
viewportWidth != fileViewportWidth || viewportHeight != fileViewportHeight) {
|
||||
viewportWidth != fileViewportWidth || viewportHeight != fileViewportHeight ||
|
||||
hyphenationEnabled != fileHyphenationEnabled) {
|
||||
file.close();
|
||||
Serial.printf("[%lu] [SCT] Deserialization failed: Parameters do not match\n", millis());
|
||||
clearCache();
|
||||
@ -115,7 +122,8 @@ bool Section::clearCache() const {
|
||||
|
||||
bool Section::createSectionFile(const int fontId, const float lineCompression, const bool extraParagraphSpacing,
|
||||
const uint8_t paragraphAlignment, const uint16_t viewportWidth,
|
||||
const uint16_t viewportHeight, const std::function<void()>& progressSetupFn,
|
||||
const uint16_t viewportHeight, const bool hyphenationEnabled,
|
||||
const std::function<void()>& progressSetupFn,
|
||||
const std::function<void(int)>& progressFn) {
|
||||
constexpr uint32_t MIN_SIZE_FOR_PROGRESS = 50 * 1024; // 50KB
|
||||
const auto localPath = epub->getSpineItem(spineIndex).href;
|
||||
@ -172,14 +180,15 @@ bool Section::createSectionFile(const int fontId, const float lineCompression, c
|
||||
return false;
|
||||
}
|
||||
writeSectionFileHeader(fontId, lineCompression, extraParagraphSpacing, paragraphAlignment, viewportWidth,
|
||||
viewportHeight);
|
||||
viewportHeight, hyphenationEnabled);
|
||||
std::vector<uint32_t> lut = {};
|
||||
|
||||
ChapterHtmlSlimParser visitor(
|
||||
tmpHtmlPath, renderer, fontId, lineCompression, extraParagraphSpacing, paragraphAlignment, viewportWidth,
|
||||
viewportHeight,
|
||||
[this, &lut](std::unique_ptr<Page> page) { lut.emplace_back(this->onPageComplete(std::move(page))); }, progressFn,
|
||||
epub->getCssParser());
|
||||
viewportHeight, hyphenationEnabled,
|
||||
[this, &lut](std::unique_ptr<Page> page) { lut.emplace_back(this->onPageComplete(std::move(page))); },
|
||||
progressFn, epub->getCssParser());
|
||||
Hyphenator::setPreferredLanguage(epub->getLanguage());
|
||||
success = visitor.parseAndBuildPages();
|
||||
|
||||
SdMan.remove(tmpHtmlPath.c_str());
|
||||
|
||||
@ -15,7 +15,7 @@ class Section {
|
||||
FsFile file;
|
||||
|
||||
void writeSectionFileHeader(int fontId, float lineCompression, bool extraParagraphSpacing, uint8_t paragraphAlignment,
|
||||
uint16_t viewportWidth, uint16_t viewportHeight);
|
||||
uint16_t viewportWidth, uint16_t viewportHeight, bool hyphenationEnabled);
|
||||
uint32_t onPageComplete(std::unique_ptr<Page> page);
|
||||
|
||||
public:
|
||||
@ -29,10 +29,10 @@ class Section {
|
||||
filePath(epub->getCachePath() + "/sections/" + std::to_string(spineIndex) + ".bin") {}
|
||||
~Section() = default;
|
||||
bool loadSectionFile(int fontId, float lineCompression, bool extraParagraphSpacing, uint8_t paragraphAlignment,
|
||||
uint16_t viewportWidth, uint16_t viewportHeight);
|
||||
uint16_t viewportWidth, uint16_t viewportHeight, bool hyphenationEnabled);
|
||||
bool clearCache() const;
|
||||
bool createSectionFile(int fontId, float lineCompression, bool extraParagraphSpacing, uint8_t paragraphAlignment,
|
||||
uint16_t viewportWidth, uint16_t viewportHeight,
|
||||
uint16_t viewportWidth, uint16_t viewportHeight, bool hyphenationEnabled,
|
||||
const std::function<void()>& progressSetupFn = nullptr,
|
||||
const std::function<void(int)>& progressFn = nullptr);
|
||||
std::unique_ptr<Page> loadPageFromSectionFile();
|
||||
|
||||
179
lib/Epub/Epub/hyphenation/HyphenationCommon.cpp
Normal file
179
lib/Epub/Epub/hyphenation/HyphenationCommon.cpp
Normal file
@ -0,0 +1,179 @@
|
||||
#include "HyphenationCommon.h"
|
||||
|
||||
#include <Utf8.h>
|
||||
|
||||
namespace {
|
||||
|
||||
// Convert Latin uppercase letters (ASCII plus Latin-1 supplement) to lowercase
|
||||
uint32_t toLowerLatinImpl(const uint32_t cp) {
|
||||
if (cp >= 'A' && cp <= 'Z') {
|
||||
return cp - 'A' + 'a';
|
||||
}
|
||||
if ((cp >= 0x00C0 && cp <= 0x00D6) || (cp >= 0x00D8 && cp <= 0x00DE)) {
|
||||
return cp + 0x20;
|
||||
}
|
||||
|
||||
switch (cp) {
|
||||
case 0x0152: // Œ
|
||||
return 0x0153; // œ
|
||||
case 0x0178: // Ÿ
|
||||
return 0x00FF; // ÿ
|
||||
case 0x1E9E: // ẞ
|
||||
return 0x00DF; // ß
|
||||
default:
|
||||
return cp;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert Cyrillic uppercase letters to lowercase
|
||||
// Cyrillic uppercase range 0x0410-0x042F maps to lowercase by adding 0x20
|
||||
// Special case: Cyrillic capital IO (0x0401) maps to lowercase io (0x0451)
|
||||
uint32_t toLowerCyrillicImpl(const uint32_t cp) {
|
||||
if (cp >= 0x0410 && cp <= 0x042F) {
|
||||
return cp + 0x20;
|
||||
}
|
||||
if (cp == 0x0401) {
|
||||
return 0x0451;
|
||||
}
|
||||
return cp;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
uint32_t toLowerLatin(const uint32_t cp) { return toLowerLatinImpl(cp); }
|
||||
|
||||
uint32_t toLowerCyrillic(const uint32_t cp) { return toLowerCyrillicImpl(cp); }
|
||||
|
||||
bool isLatinLetter(const uint32_t cp) {
|
||||
if ((cp >= 'A' && cp <= 'Z') || (cp >= 'a' && cp <= 'z')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (((cp >= 0x00C0 && cp <= 0x00D6) || (cp >= 0x00D8 && cp <= 0x00F6) || (cp >= 0x00F8 && cp <= 0x00FF)) &&
|
||||
cp != 0x00D7 && cp != 0x00F7) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (cp) {
|
||||
case 0x0152: // Œ
|
||||
case 0x0153: // œ
|
||||
case 0x0178: // Ÿ
|
||||
case 0x1E9E: // ẞ
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool isCyrillicLetter(const uint32_t cp) { return (cp >= 0x0400 && cp <= 0x052F); }
|
||||
|
||||
bool isAlphabetic(const uint32_t cp) { return isLatinLetter(cp) || isCyrillicLetter(cp); }
|
||||
|
||||
bool isPunctuation(const uint32_t cp) {
|
||||
switch (cp) {
|
||||
case '-':
|
||||
case '.':
|
||||
case ',':
|
||||
case '!':
|
||||
case '?':
|
||||
case ';':
|
||||
case ':':
|
||||
case '"':
|
||||
case '\'':
|
||||
case ')':
|
||||
case '(':
|
||||
case 0x00AB: // «
|
||||
case 0x00BB: // »
|
||||
case 0x2018: // ‘
|
||||
case 0x2019: // ’
|
||||
case 0x201C: // “
|
||||
case 0x201D: // ”
|
||||
case 0x00A0: // no-break space
|
||||
case '{':
|
||||
case '}':
|
||||
case '[':
|
||||
case ']':
|
||||
case '/':
|
||||
case 0x203A: // ›
|
||||
case 0x2026: // …
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool isAsciiDigit(const uint32_t cp) { return cp >= '0' && cp <= '9'; }
|
||||
|
||||
bool isExplicitHyphen(const uint32_t cp) {
|
||||
switch (cp) {
|
||||
case '-':
|
||||
case 0x00AD: // soft hyphen
|
||||
case 0x058A: // Armenian hyphen
|
||||
case 0x2010: // hyphen
|
||||
case 0x2011: // non-breaking hyphen
|
||||
case 0x2012: // figure dash
|
||||
case 0x2013: // en dash
|
||||
case 0x2014: // em dash
|
||||
case 0x2015: // horizontal bar
|
||||
case 0x2043: // hyphen bullet
|
||||
case 0x207B: // superscript minus
|
||||
case 0x208B: // subscript minus
|
||||
case 0x2212: // minus sign
|
||||
case 0x2E17: // double oblique hyphen
|
||||
case 0x2E3A: // two-em dash
|
||||
case 0x2E3B: // three-em dash
|
||||
case 0xFE58: // small em dash
|
||||
case 0xFE63: // small hyphen-minus
|
||||
case 0xFF0D: // fullwidth hyphen-minus
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool isSoftHyphen(const uint32_t cp) { return cp == 0x00AD; }
|
||||
|
||||
void trimSurroundingPunctuationAndFootnote(std::vector<CodepointInfo>& cps) {
|
||||
if (cps.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove trailing footnote references like [12], even if punctuation trails after the closing bracket.
|
||||
if (cps.size() >= 3) {
|
||||
int end = static_cast<int>(cps.size()) - 1;
|
||||
while (end >= 0 && isPunctuation(cps[end].value)) {
|
||||
--end;
|
||||
}
|
||||
int pos = end;
|
||||
if (pos >= 0 && isAsciiDigit(cps[pos].value)) {
|
||||
while (pos >= 0 && isAsciiDigit(cps[pos].value)) {
|
||||
--pos;
|
||||
}
|
||||
if (pos >= 0 && cps[pos].value == '[' && end - pos > 1) {
|
||||
cps.erase(cps.begin() + pos, cps.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (!cps.empty() && isPunctuation(cps.front().value)) {
|
||||
cps.erase(cps.begin());
|
||||
}
|
||||
while (!cps.empty() && isPunctuation(cps.back().value)) {
|
||||
cps.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<CodepointInfo> collectCodepoints(const std::string& word) {
|
||||
std::vector<CodepointInfo> cps;
|
||||
cps.reserve(word.size());
|
||||
|
||||
const unsigned char* base = reinterpret_cast<const unsigned char*>(word.c_str());
|
||||
const unsigned char* ptr = base;
|
||||
while (*ptr != 0) {
|
||||
const unsigned char* current = ptr;
|
||||
const uint32_t cp = utf8NextCodepoint(&ptr);
|
||||
cps.push_back({cp, static_cast<size_t>(current - base)});
|
||||
}
|
||||
|
||||
return cps;
|
||||
}
|
||||
25
lib/Epub/Epub/hyphenation/HyphenationCommon.h
Normal file
25
lib/Epub/Epub/hyphenation/HyphenationCommon.h
Normal file
@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct CodepointInfo {
|
||||
uint32_t value;
|
||||
size_t byteOffset;
|
||||
};
|
||||
|
||||
uint32_t toLowerLatin(uint32_t cp);
|
||||
uint32_t toLowerCyrillic(uint32_t cp);
|
||||
|
||||
bool isLatinLetter(uint32_t cp);
|
||||
bool isCyrillicLetter(uint32_t cp);
|
||||
|
||||
bool isAlphabetic(uint32_t cp);
|
||||
bool isPunctuation(uint32_t cp);
|
||||
bool isAsciiDigit(uint32_t cp);
|
||||
bool isExplicitHyphen(uint32_t cp);
|
||||
bool isSoftHyphen(uint32_t cp);
|
||||
void trimSurroundingPunctuationAndFootnote(std::vector<CodepointInfo>& cps);
|
||||
std::vector<CodepointInfo> collectCodepoints(const std::string& word);
|
||||
97
lib/Epub/Epub/hyphenation/Hyphenator.cpp
Normal file
97
lib/Epub/Epub/hyphenation/Hyphenator.cpp
Normal file
@ -0,0 +1,97 @@
|
||||
#include "Hyphenator.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "HyphenationCommon.h"
|
||||
#include "LanguageRegistry.h"
|
||||
|
||||
const LanguageHyphenator* Hyphenator::cachedHyphenator_ = nullptr;
|
||||
|
||||
namespace {
|
||||
|
||||
// Maps a BCP-47 language tag to a language-specific hyphenator.
|
||||
const LanguageHyphenator* hyphenatorForLanguage(const std::string& langTag) {
|
||||
if (langTag.empty()) return nullptr;
|
||||
|
||||
// Extract primary subtag and normalize to lowercase (e.g., "en-US" -> "en").
|
||||
std::string primary;
|
||||
primary.reserve(langTag.size());
|
||||
for (char c : langTag) {
|
||||
if (c == '-' || c == '_') break;
|
||||
if (c >= 'A' && c <= 'Z') c = static_cast<char>(c - 'A' + 'a');
|
||||
primary.push_back(c);
|
||||
}
|
||||
if (primary.empty()) return nullptr;
|
||||
|
||||
return getLanguageHyphenatorForPrimaryTag(primary);
|
||||
}
|
||||
|
||||
// Maps a codepoint index back to its byte offset inside the source word.
|
||||
size_t byteOffsetForIndex(const std::vector<CodepointInfo>& cps, const size_t index) {
|
||||
return (index < cps.size()) ? cps[index].byteOffset : (cps.empty() ? 0 : cps.back().byteOffset);
|
||||
}
|
||||
|
||||
// Builds a vector of break information from explicit hyphen markers in the given codepoints.
|
||||
std::vector<Hyphenator::BreakInfo> buildExplicitBreakInfos(const std::vector<CodepointInfo>& cps) {
|
||||
std::vector<Hyphenator::BreakInfo> breaks;
|
||||
|
||||
// Scan every codepoint looking for explicit/soft hyphen markers that are surrounded by letters.
|
||||
for (size_t i = 1; i + 1 < cps.size(); ++i) {
|
||||
const uint32_t cp = cps[i].value;
|
||||
if (!isExplicitHyphen(cp) || !isAlphabetic(cps[i - 1].value) || !isAlphabetic(cps[i + 1].value)) {
|
||||
continue;
|
||||
}
|
||||
// Offset points to the next codepoint so rendering starts after the hyphen marker.
|
||||
breaks.push_back({cps[i + 1].byteOffset, isSoftHyphen(cp)});
|
||||
}
|
||||
|
||||
return breaks;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<Hyphenator::BreakInfo> Hyphenator::breakOffsets(const std::string& word, const bool includeFallback) {
|
||||
if (word.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Convert to codepoints and normalize word boundaries.
|
||||
auto cps = collectCodepoints(word);
|
||||
trimSurroundingPunctuationAndFootnote(cps);
|
||||
const auto* hyphenator = cachedHyphenator_;
|
||||
|
||||
// Explicit hyphen markers (soft or hard) take precedence over language breaks.
|
||||
auto explicitBreakInfos = buildExplicitBreakInfos(cps);
|
||||
if (!explicitBreakInfos.empty()) {
|
||||
return explicitBreakInfos;
|
||||
}
|
||||
|
||||
// Ask language hyphenator for legal break points.
|
||||
std::vector<size_t> indexes;
|
||||
if (hyphenator) {
|
||||
indexes = hyphenator->breakIndexes(cps);
|
||||
}
|
||||
|
||||
// Only add fallback breaks if needed
|
||||
if (includeFallback && indexes.empty()) {
|
||||
const size_t minPrefix = hyphenator ? hyphenator->minPrefix() : LiangWordConfig::kDefaultMinPrefix;
|
||||
const size_t minSuffix = hyphenator ? hyphenator->minSuffix() : LiangWordConfig::kDefaultMinSuffix;
|
||||
for (size_t idx = minPrefix; idx + minSuffix <= cps.size(); ++idx) {
|
||||
indexes.push_back(idx);
|
||||
}
|
||||
}
|
||||
|
||||
if (indexes.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<Hyphenator::BreakInfo> breaks;
|
||||
breaks.reserve(indexes.size());
|
||||
for (const size_t idx : indexes) {
|
||||
breaks.push_back({byteOffsetForIndex(cps, idx), true});
|
||||
}
|
||||
|
||||
return breaks;
|
||||
}
|
||||
|
||||
void Hyphenator::setPreferredLanguage(const std::string& lang) { cachedHyphenator_ = hyphenatorForLanguage(lang); }
|
||||
24
lib/Epub/Epub/hyphenation/Hyphenator.h
Normal file
24
lib/Epub/Epub/hyphenation/Hyphenator.h
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class LanguageHyphenator;
|
||||
|
||||
class Hyphenator {
|
||||
public:
|
||||
struct BreakInfo {
|
||||
size_t byteOffset;
|
||||
bool requiresInsertedHyphen;
|
||||
};
|
||||
// Returns byte offsets where the word may be hyphenated. When includeFallback is true, all positions obeying the
|
||||
// minimum prefix/suffix constraints are returned even if no language-specific rule matches.
|
||||
static std::vector<BreakInfo> breakOffsets(const std::string& word, bool includeFallback);
|
||||
|
||||
// Provide a publication-level language hint (e.g. "en", "en-US", "ru") used to select hyphenation rules.
|
||||
static void setPreferredLanguage(const std::string& lang);
|
||||
|
||||
private:
|
||||
static const LanguageHyphenator* cachedHyphenator_;
|
||||
};
|
||||
23
lib/Epub/Epub/hyphenation/LanguageHyphenator.h
Normal file
23
lib/Epub/Epub/hyphenation/LanguageHyphenator.h
Normal file
@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "LiangHyphenation.h"
|
||||
|
||||
// Generic Liang-backed hyphenator that stores pattern metadata plus language-specific helpers.
|
||||
class LanguageHyphenator {
|
||||
public:
|
||||
LanguageHyphenator(const SerializedHyphenationPatterns& patterns, bool (*isLetterFn)(uint32_t),
|
||||
uint32_t (*toLowerFn)(uint32_t), size_t minPrefix = LiangWordConfig::kDefaultMinPrefix,
|
||||
size_t minSuffix = LiangWordConfig::kDefaultMinSuffix)
|
||||
: patterns_(patterns), config_(isLetterFn, toLowerFn, minPrefix, minSuffix) {}
|
||||
|
||||
std::vector<size_t> breakIndexes(const std::vector<CodepointInfo>& cps) const {
|
||||
return liangBreakIndexes(cps, patterns_, config_);
|
||||
}
|
||||
|
||||
size_t minPrefix() const { return config_.minPrefix; }
|
||||
size_t minSuffix() const { return config_.minSuffix; }
|
||||
|
||||
protected:
|
||||
const SerializedHyphenationPatterns& patterns_;
|
||||
LiangWordConfig config_;
|
||||
};
|
||||
42
lib/Epub/Epub/hyphenation/LanguageRegistry.cpp
Normal file
42
lib/Epub/Epub/hyphenation/LanguageRegistry.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
#include "LanguageRegistry.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
#include "HyphenationCommon.h"
|
||||
#include "generated/hyph-de.trie.h"
|
||||
#include "generated/hyph-en.trie.h"
|
||||
#include "generated/hyph-fr.trie.h"
|
||||
#include "generated/hyph-ru.trie.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// English hyphenation patterns (3/3 minimum prefix/suffix length)
|
||||
LanguageHyphenator englishHyphenator(en_us_patterns, isLatinLetter, toLowerLatin, 3, 3);
|
||||
LanguageHyphenator frenchHyphenator(fr_patterns, isLatinLetter, toLowerLatin);
|
||||
LanguageHyphenator germanHyphenator(de_patterns, isLatinLetter, toLowerLatin);
|
||||
LanguageHyphenator russianHyphenator(ru_ru_patterns, isCyrillicLetter, toLowerCyrillic);
|
||||
|
||||
using EntryArray = std::array<LanguageEntry, 4>;
|
||||
|
||||
const EntryArray& entries() {
|
||||
static const EntryArray kEntries = {{{"english", "en", &englishHyphenator},
|
||||
{"french", "fr", &frenchHyphenator},
|
||||
{"german", "de", &germanHyphenator},
|
||||
{"russian", "ru", &russianHyphenator}}};
|
||||
return kEntries;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const LanguageHyphenator* getLanguageHyphenatorForPrimaryTag(const std::string& primaryTag) {
|
||||
const auto& allEntries = entries();
|
||||
const auto it = std::find_if(allEntries.begin(), allEntries.end(),
|
||||
[&primaryTag](const LanguageEntry& entry) { return primaryTag == entry.primaryTag; });
|
||||
return (it != allEntries.end()) ? it->hyphenator : nullptr;
|
||||
}
|
||||
|
||||
LanguageEntryView getLanguageEntries() {
|
||||
const auto& allEntries = entries();
|
||||
return LanguageEntryView{allEntries.data(), allEntries.size()};
|
||||
}
|
||||
26
lib/Epub/Epub/hyphenation/LanguageRegistry.h
Normal file
26
lib/Epub/Epub/hyphenation/LanguageRegistry.h
Normal file
@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
#include "LanguageHyphenator.h"
|
||||
|
||||
struct LanguageEntry {
|
||||
const char* cliName;
|
||||
const char* primaryTag;
|
||||
const LanguageHyphenator* hyphenator;
|
||||
};
|
||||
|
||||
struct LanguageEntryView {
|
||||
const LanguageEntry* data;
|
||||
size_t size;
|
||||
|
||||
const LanguageEntry* begin() const { return data; }
|
||||
const LanguageEntry* end() const { return data + size; }
|
||||
};
|
||||
|
||||
// Returns the Liang-backed hyphenator for a given primary language tag (e.g., "en", "fr").
|
||||
const LanguageHyphenator* getLanguageHyphenatorForPrimaryTag(const std::string& primaryTag);
|
||||
|
||||
// Exposes the list of supported languages primarily for tooling/tests.
|
||||
LanguageEntryView getLanguageEntries();
|
||||
405
lib/Epub/Epub/hyphenation/LiangHyphenation.cpp
Normal file
405
lib/Epub/Epub/hyphenation/LiangHyphenation.cpp
Normal file
@ -0,0 +1,405 @@
|
||||
#include "LiangHyphenation.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
/*
|
||||
* Liang hyphenation pipeline overview (Typst-style binary trie variant)
|
||||
* --------------------------------------------------------------------
|
||||
* 1. Input normalization (buildAugmentedWord)
|
||||
* - Accepts a vector of CodepointInfo structs emitted by the EPUB text
|
||||
* parser. Each codepoint is validated with LiangWordConfig::isLetter so
|
||||
* we abort early on digits, punctuation, etc. If the word is valid we
|
||||
* build an "augmented" byte sequence: leading '.', lowercase UTF-8 bytes
|
||||
* for every letter, then a trailing '.'. While doing this we capture the
|
||||
* UTF-8 byte offset for each character and a reverse lookup table that
|
||||
* maps UTF-8 byte indexes back to codepoint indexes. This lets the rest
|
||||
* of the algorithm stay byte-oriented (matching the serialized automaton)
|
||||
* while still emitting hyphen positions in codepoint space.
|
||||
*
|
||||
* 2. Automaton decoding
|
||||
* - SerializedHyphenationPatterns stores a contiguous blob generated from
|
||||
* Typst's binary tries. The first 4 bytes contain the root offset. Each
|
||||
* node packs transitions, variable-stride relative offsets to child
|
||||
* nodes, and an optional pointer into a shared "levels" list. We parse
|
||||
* that layout lazily via decodeState/transition, keeping everything in
|
||||
* flash memory; no heap allocations besides the stack-local AutomatonState
|
||||
* structs. getAutomaton caches parseAutomaton results per blob pointer so
|
||||
* multiple words hitting the same language only pay the cost once.
|
||||
*
|
||||
* 3. Pattern application
|
||||
* - We walk the augmented bytes left-to-right. For each starting byte we
|
||||
* stream transitions through the trie, terminating when a transition
|
||||
* fails. Whenever a node exposes level data we expand the packed
|
||||
* "dist+level" bytes: `dist` is the delta (in UTF-8 bytes) from the
|
||||
* starting cursor and `level` is the Liang priority digit. Using the
|
||||
* byte→codepoint lookup we mark the corresponding index in `scores`.
|
||||
* Scores are only updated if the new level is higher, mirroring Liang's
|
||||
* "max digit wins" rule.
|
||||
*
|
||||
* 4. Output filtering
|
||||
* - collectBreakIndexes converts odd-valued score entries back to codepoint
|
||||
* break positions while enforcing `minPrefix`/`minSuffix` constraints from
|
||||
* LiangWordConfig. The caller (language-specific hyphenators) can then
|
||||
* translate these indexes into renderer glyph offsets, page layout data,
|
||||
* etc.
|
||||
*
|
||||
* Keeping the entire algorithm small and deterministic is critical on the
|
||||
* ESP32-C3: we avoid recursion, dynamic allocations per node, or copying the
|
||||
* trie. All lookups stay within the generated blob, which lives in flash, and
|
||||
* the working buffers (augmented bytes/scores) scale with the word length rather
|
||||
* than the pattern corpus.
|
||||
*/
|
||||
|
||||
namespace {
|
||||
|
||||
struct AugmentedWord {
|
||||
std::vector<uint8_t> bytes;
|
||||
std::vector<size_t> charByteOffsets;
|
||||
std::vector<int32_t> byteToCharIndex;
|
||||
|
||||
bool empty() const { return bytes.empty(); }
|
||||
size_t charCount() const { return charByteOffsets.size(); }
|
||||
};
|
||||
|
||||
// Encode a single Unicode codepoint into UTF-8 and append to the provided buffer.
|
||||
size_t encodeUtf8(uint32_t cp, std::vector<uint8_t>& out) {
|
||||
if (cp <= 0x7Fu) {
|
||||
out.push_back(static_cast<uint8_t>(cp));
|
||||
return 1;
|
||||
}
|
||||
if (cp <= 0x7FFu) {
|
||||
out.push_back(static_cast<uint8_t>(0xC0u | ((cp >> 6) & 0x1Fu)));
|
||||
out.push_back(static_cast<uint8_t>(0x80u | (cp & 0x3Fu)));
|
||||
return 2;
|
||||
}
|
||||
if (cp <= 0xFFFFu) {
|
||||
out.push_back(static_cast<uint8_t>(0xE0u | ((cp >> 12) & 0x0Fu)));
|
||||
out.push_back(static_cast<uint8_t>(0x80u | ((cp >> 6) & 0x3Fu)));
|
||||
out.push_back(static_cast<uint8_t>(0x80u | (cp & 0x3Fu)));
|
||||
return 3;
|
||||
}
|
||||
out.push_back(static_cast<uint8_t>(0xF0u | ((cp >> 18) & 0x07u)));
|
||||
out.push_back(static_cast<uint8_t>(0x80u | ((cp >> 12) & 0x3Fu)));
|
||||
out.push_back(static_cast<uint8_t>(0x80u | ((cp >> 6) & 0x3Fu)));
|
||||
out.push_back(static_cast<uint8_t>(0x80u | (cp & 0x3Fu)));
|
||||
return 4;
|
||||
}
|
||||
|
||||
// Build the dotted, lowercase UTF-8 representation plus lookup tables.
|
||||
AugmentedWord buildAugmentedWord(const std::vector<CodepointInfo>& cps, const LiangWordConfig& config) {
|
||||
AugmentedWord word;
|
||||
if (cps.empty()) {
|
||||
return word;
|
||||
}
|
||||
|
||||
word.bytes.reserve(cps.size() * 2 + 2);
|
||||
word.charByteOffsets.reserve(cps.size() + 2);
|
||||
|
||||
word.charByteOffsets.push_back(0);
|
||||
word.bytes.push_back('.');
|
||||
|
||||
for (const auto& info : cps) {
|
||||
if (!config.isLetter(info.value)) {
|
||||
word.bytes.clear();
|
||||
word.charByteOffsets.clear();
|
||||
word.byteToCharIndex.clear();
|
||||
return word;
|
||||
}
|
||||
word.charByteOffsets.push_back(word.bytes.size());
|
||||
encodeUtf8(config.toLower(info.value), word.bytes);
|
||||
}
|
||||
|
||||
word.charByteOffsets.push_back(word.bytes.size());
|
||||
word.bytes.push_back('.');
|
||||
|
||||
word.byteToCharIndex.assign(word.bytes.size(), -1);
|
||||
for (size_t i = 0; i < word.charByteOffsets.size(); ++i) {
|
||||
const size_t offset = word.charByteOffsets[i];
|
||||
if (offset < word.byteToCharIndex.size()) {
|
||||
word.byteToCharIndex[offset] = static_cast<int32_t>(i);
|
||||
}
|
||||
}
|
||||
return word;
|
||||
}
|
||||
|
||||
// Decoded view of a single trie node pulled straight out of the serialized blob.
|
||||
// - transitions: contiguous list of next-byte values
|
||||
// - targets: packed relative offsets (1/2/3 bytes) for each transition
|
||||
// - levels: optional pointer into the global levels list with packed dist/level pairs
|
||||
struct AutomatonState {
|
||||
const uint8_t* data = nullptr;
|
||||
size_t size = 0;
|
||||
size_t addr = 0;
|
||||
uint8_t stride = 1;
|
||||
size_t childCount = 0;
|
||||
const uint8_t* transitions = nullptr;
|
||||
const uint8_t* targets = nullptr;
|
||||
const uint8_t* levels = nullptr;
|
||||
size_t levelsLen = 0;
|
||||
|
||||
bool valid() const { return data != nullptr; }
|
||||
};
|
||||
|
||||
// Lightweight descriptor for the entire embedded automaton.
|
||||
// The blob format is:
|
||||
// [0..3] - big-endian root offset
|
||||
// [4....] - node heap containing variable-sized headers + transition data
|
||||
struct EmbeddedAutomaton {
|
||||
const uint8_t* data = nullptr;
|
||||
size_t size = 0;
|
||||
uint32_t rootOffset = 0;
|
||||
|
||||
bool valid() const { return data != nullptr && size >= 4 && rootOffset < size; }
|
||||
};
|
||||
|
||||
// Decode the serialized automaton header and root offset.
|
||||
EmbeddedAutomaton parseAutomaton(const SerializedHyphenationPatterns& patterns) {
|
||||
EmbeddedAutomaton automaton;
|
||||
if (!patterns.data || patterns.size < 4) {
|
||||
return automaton;
|
||||
}
|
||||
|
||||
automaton.data = patterns.data;
|
||||
automaton.size = patterns.size;
|
||||
automaton.rootOffset = (static_cast<uint32_t>(patterns.data[0]) << 24) |
|
||||
(static_cast<uint32_t>(patterns.data[1]) << 16) |
|
||||
(static_cast<uint32_t>(patterns.data[2]) << 8) | static_cast<uint32_t>(patterns.data[3]);
|
||||
if (automaton.rootOffset >= automaton.size) {
|
||||
automaton.data = nullptr;
|
||||
automaton.size = 0;
|
||||
}
|
||||
return automaton;
|
||||
}
|
||||
|
||||
// Cache parsed automata per blob pointer to avoid reparsing.
|
||||
const EmbeddedAutomaton& getAutomaton(const SerializedHyphenationPatterns& patterns) {
|
||||
struct CacheEntry {
|
||||
const SerializedHyphenationPatterns* key;
|
||||
EmbeddedAutomaton automaton;
|
||||
};
|
||||
static std::vector<CacheEntry> cache;
|
||||
|
||||
for (const auto& entry : cache) {
|
||||
if (entry.key == &patterns) {
|
||||
return entry.automaton;
|
||||
}
|
||||
}
|
||||
|
||||
cache.push_back({&patterns, parseAutomaton(patterns)});
|
||||
return cache.back().automaton;
|
||||
}
|
||||
|
||||
// Interpret the node located at `addr`, returning transition metadata.
|
||||
AutomatonState decodeState(const EmbeddedAutomaton& automaton, size_t addr) {
|
||||
AutomatonState state;
|
||||
if (!automaton.valid() || addr >= automaton.size) {
|
||||
return state;
|
||||
}
|
||||
|
||||
const uint8_t* base = automaton.data + addr;
|
||||
size_t remaining = automaton.size - addr;
|
||||
size_t pos = 0;
|
||||
|
||||
const uint8_t header = base[pos++];
|
||||
// Header layout (bits):
|
||||
// 7 - hasLevels flag
|
||||
// 6..5 - stride selector (0 -> 1 byte, otherwise 1|2|3)
|
||||
// 4..0 - child count (5 bits), 31 == overflow -> extra byte
|
||||
const bool hasLevels = (header >> 7) != 0;
|
||||
uint8_t stride = static_cast<uint8_t>((header >> 5) & 0x03u);
|
||||
if (stride == 0) {
|
||||
stride = 1;
|
||||
}
|
||||
size_t childCount = static_cast<size_t>(header & 0x1Fu);
|
||||
if (childCount == 31u) {
|
||||
if (pos >= remaining) {
|
||||
return AutomatonState{};
|
||||
}
|
||||
childCount = base[pos++];
|
||||
}
|
||||
|
||||
const uint8_t* levelsPtr = nullptr;
|
||||
size_t levelsLen = 0;
|
||||
if (hasLevels) {
|
||||
if (pos + 1 >= remaining) {
|
||||
return AutomatonState{};
|
||||
}
|
||||
const uint8_t offsetHi = base[pos++];
|
||||
const uint8_t offsetLoLen = base[pos++];
|
||||
// The 12-bit offset (hi<<4 | top nibble) points into the blob-level levels list.
|
||||
// The bottom nibble stores how many packed entries belong to this node.
|
||||
const size_t offset = (static_cast<size_t>(offsetHi) << 4) | (offsetLoLen >> 4);
|
||||
levelsLen = offsetLoLen & 0x0Fu;
|
||||
if (offset + levelsLen > automaton.size) {
|
||||
return AutomatonState{};
|
||||
}
|
||||
levelsPtr = automaton.data + offset;
|
||||
}
|
||||
|
||||
if (pos + childCount > remaining) {
|
||||
return AutomatonState{};
|
||||
}
|
||||
const uint8_t* transitions = base + pos;
|
||||
pos += childCount;
|
||||
|
||||
const size_t targetsBytes = childCount * stride;
|
||||
if (pos + targetsBytes > remaining) {
|
||||
return AutomatonState{};
|
||||
}
|
||||
const uint8_t* targets = base + pos;
|
||||
|
||||
state.data = automaton.data;
|
||||
state.size = automaton.size;
|
||||
state.addr = addr;
|
||||
state.stride = stride;
|
||||
state.childCount = childCount;
|
||||
state.transitions = transitions;
|
||||
state.targets = targets;
|
||||
state.levels = levelsPtr;
|
||||
state.levelsLen = levelsLen;
|
||||
return state;
|
||||
}
|
||||
|
||||
// Convert the packed stride-sized delta back into a signed offset.
|
||||
int32_t decodeDelta(const uint8_t* buf, uint8_t stride) {
|
||||
if (stride == 1) {
|
||||
return static_cast<int8_t>(buf[0]);
|
||||
}
|
||||
if (stride == 2) {
|
||||
return static_cast<int16_t>((static_cast<uint16_t>(buf[0]) << 8) | static_cast<uint16_t>(buf[1]));
|
||||
}
|
||||
const int32_t unsignedVal =
|
||||
(static_cast<int32_t>(buf[0]) << 16) | (static_cast<int32_t>(buf[1]) << 8) | static_cast<int32_t>(buf[2]);
|
||||
return unsignedVal - (1 << 23);
|
||||
}
|
||||
|
||||
// Follow a single byte transition from `state`, decoding the child node on success.
|
||||
bool transition(const EmbeddedAutomaton& automaton, const AutomatonState& state, uint8_t letter, AutomatonState& out) {
|
||||
if (!state.valid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Children remain sorted by letter in the serialized blob, but the lists are
|
||||
// short enough that a linear scan keeps code size down compared to binary search.
|
||||
for (size_t idx = 0; idx < state.childCount; ++idx) {
|
||||
if (state.transitions[idx] != letter) {
|
||||
continue;
|
||||
}
|
||||
const uint8_t* deltaPtr = state.targets + idx * state.stride;
|
||||
const int32_t delta = decodeDelta(deltaPtr, state.stride);
|
||||
// Deltas are relative to the current node's address, allowing us to keep all
|
||||
// targets within 24 bits while still referencing further nodes in the blob.
|
||||
const int64_t nextAddr = static_cast<int64_t>(state.addr) + delta;
|
||||
if (nextAddr < 0 || static_cast<size_t>(nextAddr) >= automaton.size) {
|
||||
return false;
|
||||
}
|
||||
out = decodeState(automaton, static_cast<size_t>(nextAddr));
|
||||
return out.valid();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Converts odd score positions back into codepoint indexes, honoring min prefix/suffix constraints.
|
||||
// Each break corresponds to scores[breakIndex + 1] because of the leading '.' sentinel.
|
||||
// Convert odd score entries into hyphen positions while honoring prefix/suffix limits.
|
||||
std::vector<size_t> collectBreakIndexes(const std::vector<CodepointInfo>& cps, const std::vector<uint8_t>& scores,
|
||||
const size_t minPrefix, const size_t minSuffix) {
|
||||
std::vector<size_t> indexes;
|
||||
const size_t cpCount = cps.size();
|
||||
if (cpCount < 2) {
|
||||
return indexes;
|
||||
}
|
||||
|
||||
for (size_t breakIndex = 1; breakIndex < cpCount; ++breakIndex) {
|
||||
if (breakIndex < minPrefix) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t suffixCount = cpCount - breakIndex;
|
||||
if (suffixCount < minSuffix) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const size_t scoreIdx = breakIndex + 1;
|
||||
if (scoreIdx >= scores.size()) {
|
||||
break;
|
||||
}
|
||||
if ((scores[scoreIdx] & 1u) == 0) {
|
||||
continue;
|
||||
}
|
||||
indexes.push_back(breakIndex);
|
||||
}
|
||||
|
||||
return indexes;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Entry point that runs the full Liang pipeline for a single word.
|
||||
std::vector<size_t> liangBreakIndexes(const std::vector<CodepointInfo>& cps,
|
||||
const SerializedHyphenationPatterns& patterns, const LiangWordConfig& config) {
|
||||
const auto augmented = buildAugmentedWord(cps, config);
|
||||
if (augmented.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const EmbeddedAutomaton& automaton = getAutomaton(patterns);
|
||||
if (!automaton.valid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const AutomatonState root = decodeState(automaton, automaton.rootOffset);
|
||||
if (!root.valid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Liang scores: one entry per augmented char (leading/trailing dots included).
|
||||
std::vector<uint8_t> scores(augmented.charCount(), 0);
|
||||
|
||||
// Walk every starting character position and stream bytes through the trie.
|
||||
for (size_t charStart = 0; charStart < augmented.charByteOffsets.size(); ++charStart) {
|
||||
const size_t byteStart = augmented.charByteOffsets[charStart];
|
||||
AutomatonState state = root;
|
||||
|
||||
for (size_t cursor = byteStart; cursor < augmented.bytes.size(); ++cursor) {
|
||||
AutomatonState next;
|
||||
if (!transition(automaton, state, augmented.bytes[cursor], next)) {
|
||||
break; // No more matches for this prefix.
|
||||
}
|
||||
state = next;
|
||||
|
||||
if (state.levels && state.levelsLen > 0) {
|
||||
size_t offset = 0;
|
||||
// Each packed byte stores the byte-distance delta and the Liang level digit.
|
||||
for (size_t i = 0; i < state.levelsLen; ++i) {
|
||||
const uint8_t packed = state.levels[i];
|
||||
const size_t dist = static_cast<size_t>(packed / 10);
|
||||
const uint8_t level = static_cast<uint8_t>(packed % 10);
|
||||
|
||||
offset += dist;
|
||||
const size_t splitByte = byteStart + offset;
|
||||
if (splitByte >= augmented.byteToCharIndex.size()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int32_t boundary = augmented.byteToCharIndex[splitByte];
|
||||
if (boundary < 0) {
|
||||
continue; // Mid-codepoint byte, wait for the next one.
|
||||
}
|
||||
if (boundary < 2 || boundary + 2 > static_cast<int32_t>(augmented.charCount())) {
|
||||
continue; // Skip splits that land in the leading/trailing sentinels.
|
||||
}
|
||||
|
||||
const size_t idx = static_cast<size_t>(boundary);
|
||||
if (idx >= scores.size()) {
|
||||
continue;
|
||||
}
|
||||
scores[idx] = std::max(scores[idx], level);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return collectBreakIndexes(cps, scores, config.minPrefix, config.minSuffix);
|
||||
}
|
||||
38
lib/Epub/Epub/hyphenation/LiangHyphenation.h
Normal file
38
lib/Epub/Epub/hyphenation/LiangHyphenation.h
Normal file
@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "HyphenationCommon.h"
|
||||
#include "SerializedHyphenationTrie.h"
|
||||
|
||||
// Encapsulates every language-specific dial the Liang algorithm needs at runtime. The helpers are
|
||||
// intentionally represented as bare function pointers because we invoke them inside tight loops and
|
||||
// want to avoid the overhead of std::function or functors. The minima default to the TeX-recommended
|
||||
// "2/2" split but individual languages (English, for example) can override them.
|
||||
struct LiangWordConfig {
|
||||
static constexpr size_t kDefaultMinPrefix = 2;
|
||||
static constexpr size_t kDefaultMinSuffix = 2;
|
||||
// Predicate used to reject non-alphabetic characters before pattern lookup. Returning false causes
|
||||
// the entire word to be skipped, matching the behavior of classic TeX hyphenation tables.
|
||||
bool (*isLetter)(uint32_t);
|
||||
// Language-specific case folding that matches how the TeX patterns were authored (usually lower-case
|
||||
// ASCII for Latin and lowercase Cyrillic for Russian). Patterns are stored in UTF-8, so this must
|
||||
// operate on Unicode scalars rather than bytes.
|
||||
uint32_t (*toLower)(uint32_t);
|
||||
// Minimum codepoints required on the left/right of any break. These correspond to TeX's
|
||||
// lefthyphenmin and righthyphenmin knobs.
|
||||
size_t minPrefix;
|
||||
size_t minSuffix;
|
||||
|
||||
// Lightweight aggregate constructor so call sites can declare `const LiangWordConfig config(...)`
|
||||
// without verbose member assignment boilerplate.
|
||||
LiangWordConfig(bool (*letterFn)(uint32_t), uint32_t (*lowerFn)(uint32_t), size_t prefix = kDefaultMinPrefix,
|
||||
size_t suffix = kDefaultMinSuffix)
|
||||
: isLetter(letterFn), toLower(lowerFn), minPrefix(prefix), minSuffix(suffix) {}
|
||||
};
|
||||
|
||||
// Shared Liang pattern evaluator used by every language-specific hyphenator.
|
||||
std::vector<size_t> liangBreakIndexes(const std::vector<CodepointInfo>& cps,
|
||||
const SerializedHyphenationPatterns& patterns, const LiangWordConfig& config);
|
||||
10
lib/Epub/Epub/hyphenation/SerializedHyphenationTrie.h
Normal file
10
lib/Epub/Epub/hyphenation/SerializedHyphenationTrie.h
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
// Lightweight descriptor that points at a serialized Liang hyphenation trie stored in flash.
|
||||
struct SerializedHyphenationPatterns {
|
||||
const std::uint8_t* data;
|
||||
size_t size;
|
||||
};
|
||||
10871
lib/Epub/Epub/hyphenation/generated/hyph-de.trie.h
Normal file
10871
lib/Epub/Epub/hyphenation/generated/hyph-de.trie.h
Normal file
File diff suppressed because it is too large
Load Diff
1434
lib/Epub/Epub/hyphenation/generated/hyph-en.trie.h
Normal file
1434
lib/Epub/Epub/hyphenation/generated/hyph-en.trie.h
Normal file
File diff suppressed because it is too large
Load Diff
383
lib/Epub/Epub/hyphenation/generated/hyph-fr.trie.h
Normal file
383
lib/Epub/Epub/hyphenation/generated/hyph-fr.trie.h
Normal file
@ -0,0 +1,383 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "../SerializedHyphenationTrie.h"
|
||||
|
||||
// Auto-generated by generate_hyphenation_trie.py. Do not edit manually.
|
||||
alignas(4) constexpr uint8_t fr_trie_data[] = {
|
||||
0x00, 0x00, 0x1A, 0xF4, 0x02, 0x0C, 0x18, 0x22, 0x16, 0x21, 0x0B, 0x16, 0x21, 0x0E, 0x01, 0x0C, 0x0B, 0x3D, 0x0C,
|
||||
0x2B, 0x0E, 0x0C, 0x0C, 0x33, 0x0C, 0x33, 0x16, 0x34, 0x2A, 0x0D, 0x20, 0x0D, 0x0C, 0x0D, 0x2A, 0x17, 0x04, 0x1F,
|
||||
0x0C, 0x29, 0x0C, 0x20, 0x0B, 0x0C, 0x17, 0x17, 0x0C, 0x3F, 0x35, 0x53, 0x4A, 0x36, 0x34, 0x21, 0x2A, 0x0D, 0x0C,
|
||||
0x2A, 0x0D, 0x16, 0x02, 0x17, 0x15, 0x15, 0x0C, 0x15, 0x16, 0x2C, 0x47, 0x0C, 0x49, 0x2B, 0x0C, 0x0D, 0x34, 0x0D,
|
||||
0x2A, 0x0B, 0x16, 0x2B, 0x0C, 0x17, 0x2A, 0x0B, 0x0C, 0x03, 0x0C, 0x16, 0x0D, 0x01, 0x16, 0x0C, 0x0B, 0x0C, 0x3E,
|
||||
0x48, 0x2C, 0x0B, 0x29, 0x16, 0x37, 0x40, 0x1F, 0x16, 0x20, 0x17, 0x36, 0x0D, 0x52, 0x3D, 0x16, 0x1F, 0x0C, 0x16,
|
||||
0x3E, 0x0D, 0x49, 0x0C, 0x03, 0x16, 0x35, 0x0C, 0x22, 0x0F, 0x02, 0x0D, 0x51, 0x0C, 0x21, 0x0C, 0x20, 0x0B, 0x16,
|
||||
0x21, 0x0C, 0x17, 0x21, 0x0C, 0x0D, 0xA0, 0x00, 0x91, 0x21, 0x61, 0xFD, 0x21, 0xA9, 0xFD, 0x21, 0xC3, 0xFD, 0x21,
|
||||
0x72, 0xFD, 0xA0, 0x00, 0xC2, 0x21, 0x68, 0xFD, 0x21, 0x63, 0xFD, 0x21, 0x73, 0xFD, 0xA0, 0x00, 0x51, 0x21, 0x6C,
|
||||
0xFD, 0x21, 0x6F, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0x63, 0xFD, 0xA0, 0x01, 0x12, 0x21, 0x63, 0xFD, 0x21, 0x61, 0xFD,
|
||||
0x21, 0x6F, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x69, 0xFD, 0xA0, 0x01, 0x32, 0x21, 0x72, 0xFD, 0x21, 0x74, 0xFD, 0x21,
|
||||
0x73, 0xFD, 0xA0, 0x01, 0x52, 0x21, 0x69, 0xFD, 0x21, 0x73, 0xFD, 0x21, 0xA9, 0xFD, 0x21, 0xC3, 0xFD, 0x21, 0x68,
|
||||
0xFD, 0x21, 0x74, 0xFD, 0x21, 0x73, 0xFD, 0xA0, 0x01, 0x72, 0xA0, 0x01, 0xB1, 0x21, 0x65, 0xFD, 0x21, 0x6E, 0xFD,
|
||||
0xA1, 0x01, 0x72, 0x6E, 0xFD, 0xA0, 0x01, 0x92, 0x21, 0xA9, 0xFD, 0x24, 0x61, 0x65, 0xC3, 0x73, 0xE9, 0xF5, 0xFD,
|
||||
0xE9, 0x21, 0x69, 0xF7, 0x23, 0x61, 0x65, 0x74, 0xC2, 0xDA, 0xFD, 0xA0, 0x01, 0xC2, 0x21, 0x61, 0xFD, 0x21, 0x74,
|
||||
0xFD, 0x21, 0x73, 0xFD, 0x21, 0x6F, 0xFD, 0xA0, 0x01, 0xE1, 0x21, 0x61, 0xFD, 0x21, 0x74, 0xFD, 0x41, 0x2E, 0xFF,
|
||||
0x5E, 0x21, 0x74, 0xFC, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x22, 0x67, 0x70, 0xFD, 0xFD, 0xA0, 0x05, 0x72, 0x21,
|
||||
0x74, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x6E, 0xFD, 0xC9, 0x00, 0x61, 0x62, 0x65, 0x6C, 0x6D, 0x6E, 0x70, 0x73, 0x72,
|
||||
0x67, 0xFF, 0x4C, 0xFF, 0x58, 0xFF, 0x67, 0xFF, 0x79, 0xFF, 0xC3, 0xFF, 0xD6, 0xFF, 0xDF, 0xFF, 0xEF, 0xFF, 0xFD,
|
||||
0xA0, 0x00, 0x71, 0x27, 0xA2, 0xAA, 0xA9, 0xA8, 0xAE, 0xB4, 0xBB, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xFD, 0xA0,
|
||||
0x02, 0x52, 0x22, 0x61, 0x6F, 0xFD, 0xFD, 0xA0, 0x02, 0x93, 0x21, 0x61, 0xFD, 0x21, 0x72, 0xFD, 0xA2, 0x00, 0x61,
|
||||
0x6E, 0x75, 0xF2, 0xFD, 0x21, 0xA9, 0xAC, 0x42, 0xC3, 0x69, 0xFF, 0xFD, 0xFF, 0xA9, 0x21, 0x6E, 0xF9, 0x41, 0x74,
|
||||
0xFF, 0x06, 0x21, 0x61, 0xFC, 0x21, 0x6D, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x6F, 0xFD, 0xA0, 0x01, 0xE2, 0x21, 0x74,
|
||||
0xFD, 0x21, 0x69, 0xFD, 0x41, 0x72, 0xFF, 0x6B, 0x21, 0x75, 0xFC, 0x21, 0x67, 0xFD, 0xA2, 0x02, 0x52, 0x6E, 0x75,
|
||||
0xF3, 0xFD, 0x41, 0x62, 0xFF, 0x5A, 0x21, 0x61, 0xFC, 0x21, 0x66, 0xFD, 0x41, 0x74, 0xFF, 0x50, 0x41, 0x72, 0xFF,
|
||||
0x4F, 0x21, 0x6F, 0xFC, 0xC4, 0x02, 0x52, 0x66, 0x70, 0x72, 0x78, 0xFF, 0xF2, 0xFF, 0xF5, 0xFF, 0x45, 0xFF, 0xFD,
|
||||
0xA0, 0x06, 0x82, 0x21, 0x61, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x63, 0xFD, 0x21, 0x75, 0xFD, 0x21, 0x72, 0xF4, 0x21,
|
||||
0x72, 0xFD, 0x21, 0x61, 0xFD, 0xA2, 0x06, 0x62, 0x6C, 0x6E, 0xF4, 0xFD, 0x21, 0xA9, 0xF9, 0x41, 0x69, 0xFF, 0xA0,
|
||||
0x21, 0x74, 0xFC, 0x21, 0x69, 0xFD, 0xC3, 0x02, 0x52, 0x6D, 0x71, 0x74, 0xFF, 0xFD, 0xFF, 0x96, 0xFF, 0x96, 0x41,
|
||||
0x6C, 0xFF, 0x8A, 0x21, 0x75, 0xFC, 0x41, 0x64, 0xFE, 0xF7, 0xA2, 0x02, 0x52, 0x63, 0x6E, 0xF9, 0xFC, 0x41, 0x62,
|
||||
0xFF, 0x43, 0x21, 0x61, 0xFC, 0x21, 0x74, 0xFD, 0xA0, 0x05, 0xF1, 0xA0, 0x06, 0xC1, 0x21, 0xA9, 0xFD, 0xA7, 0x06,
|
||||
0xA2, 0x61, 0x65, 0xC3, 0x69, 0x6F, 0x75, 0x73, 0xF7, 0xF7, 0xFD, 0xF7, 0xF7, 0xF7, 0xF7, 0x21, 0x72, 0xEF, 0x21,
|
||||
0x65, 0xFD, 0xC2, 0x02, 0x52, 0x69, 0x6C, 0xFF, 0x72, 0xFF, 0x4E, 0x49, 0x66, 0x61, 0x65, 0xC3, 0x69, 0x6F, 0x73,
|
||||
0x74, 0x75, 0xFF, 0x42, 0xFF, 0x58, 0xFF, 0x74, 0xFF, 0xA2, 0xFF, 0xAF, 0xFF, 0xC6, 0xFF, 0xD4, 0xFF, 0xF4, 0xFF,
|
||||
0xF7, 0xC2, 0x00, 0x61, 0x67, 0x6E, 0xFF, 0x16, 0xFF, 0xE4, 0x41, 0x75, 0xFE, 0xA7, 0x21, 0x67, 0xFC, 0x41, 0x65,
|
||||
0xFF, 0x09, 0x21, 0x74, 0xFC, 0xA0, 0x02, 0x71, 0x21, 0x75, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0x61, 0xFD, 0xA0, 0x02,
|
||||
0x72, 0x21, 0x63, 0xFD, 0x21, 0x73, 0xFD, 0x21, 0x69, 0xFD, 0xA4, 0x00, 0x61, 0x6E, 0x63, 0x75, 0x76, 0xDE, 0xE5,
|
||||
0xF1, 0xFD, 0xA0, 0x00, 0x61, 0xC7, 0x00, 0x42, 0x61, 0xC3, 0x65, 0x69, 0x6F, 0x75, 0x79, 0xFE, 0x87, 0xFE, 0xA8,
|
||||
0xFE, 0xC8, 0xFF, 0xC3, 0xFF, 0xF2, 0xFF, 0xFD, 0xFF, 0xFD, 0x42, 0x61, 0x74, 0xFD, 0xF4, 0xFE, 0x2F, 0x43, 0x64,
|
||||
0x67, 0x70, 0xFE, 0x54, 0xFE, 0x54, 0xFE, 0x54, 0xC8, 0x00, 0x61, 0x62, 0x65, 0x6D, 0x6E, 0x70, 0x73, 0x72, 0x67,
|
||||
0xFD, 0xAA, 0xFD, 0xB6, 0xFD, 0xD7, 0xFF, 0xEF, 0xFE, 0x34, 0xFE, 0x3D, 0xFF, 0xF6, 0xFE, 0x5B, 0xA0, 0x03, 0x01,
|
||||
0x21, 0x2E, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x69, 0xFD, 0xA1,
|
||||
0x00, 0x71, 0x6D, 0xFD, 0x47, 0xA2, 0xAA, 0xA9, 0xA8, 0xAE, 0xB4, 0xBB, 0xFE, 0x47, 0xFE, 0x47, 0xFF, 0xFB, 0xFE,
|
||||
0x47, 0xFE, 0x47, 0xFE, 0x47, 0xFE, 0x47, 0xA0, 0x02, 0x22, 0x21, 0x6E, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x61, 0xFD,
|
||||
0x21, 0x6D, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x73, 0xFD, 0x21, 0x69, 0xFD, 0xA0, 0x02, 0x51, 0x43, 0x63, 0x74, 0x75,
|
||||
0xFE, 0x28, 0xFE, 0x28, 0xFF, 0xFD, 0x41, 0x61, 0xFF, 0x4D, 0x44, 0x61, 0x6F, 0x73, 0x75, 0xFF, 0xF2, 0xFF, 0xFC,
|
||||
0xFE, 0x25, 0xFE, 0x1A, 0x22, 0x61, 0x69, 0xDF, 0xF3, 0xA0, 0x03, 0x42, 0x21, 0x65, 0xFD, 0x21, 0x6C, 0xFD, 0x21,
|
||||
0x6C, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x75, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x66, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x72,
|
||||
0xFD, 0x21, 0x76, 0xFD, 0x21, 0xA8, 0xFD, 0xA1, 0x00, 0x71, 0xC3, 0xFD, 0xA0, 0x02, 0x92, 0x21, 0x70, 0xFD, 0x21,
|
||||
0x6C, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x73, 0xFD, 0xA0, 0x03, 0x31, 0xA0, 0x04, 0x42, 0x21, 0x63, 0xFD, 0xA0, 0x04,
|
||||
0x61, 0x21, 0x65, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0xAE, 0xFD, 0x21, 0xC3, 0xFD, 0x21, 0x61, 0xFD,
|
||||
0x22, 0x73, 0x6D, 0xE8, 0xFD, 0x21, 0x65, 0xFB, 0x21, 0x72, 0xFD, 0xA2, 0x04, 0x31, 0x73, 0x74, 0xD7, 0xFD, 0x41,
|
||||
0x65, 0xFD, 0xD5, 0x21, 0x69, 0xFC, 0xA1, 0x02, 0x52, 0x6C, 0xFD, 0xA0, 0x01, 0x31, 0x21, 0x2E, 0xFD, 0x21, 0x74,
|
||||
0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x6D, 0xFD, 0x23, 0x6E, 0x6F, 0x6D, 0xDB, 0xE9, 0xFD, 0xA0, 0x04,
|
||||
0x31, 0x21, 0x6C, 0xFD, 0x44, 0x68, 0x69, 0x6F, 0x75, 0xFF, 0x91, 0xFF, 0xA2, 0xFF, 0xF3, 0xFF, 0xFD, 0x41, 0x61,
|
||||
0xFF, 0x9B, 0x21, 0x6F, 0xFC, 0x21, 0x79, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x63, 0xFD, 0x41, 0x6F, 0xFE, 0x7B, 0xA0,
|
||||
0x04, 0x73, 0x21, 0x72, 0xFD, 0xA0, 0x04, 0xA2, 0x21, 0x6C, 0xF7, 0x21, 0x6C, 0xFD, 0x21, 0x65, 0xFD, 0xA0, 0x04,
|
||||
0x72, 0x21, 0x72, 0xFD, 0x21, 0x74, 0xFD, 0x24, 0x63, 0x6D, 0x74, 0x73, 0xE8, 0xEB, 0xF4, 0xFD, 0xA0, 0x04, 0xF3,
|
||||
0x21, 0x72, 0xFD, 0xA1, 0x04, 0xC3, 0x67, 0xFD, 0x21, 0xA9, 0xFB, 0x21, 0x62, 0xE0, 0x21, 0x69, 0xFD, 0x21, 0x73,
|
||||
0xFD, 0x21, 0x74, 0xD7, 0x21, 0x75, 0xD4, 0x23, 0x6E, 0x72, 0x78, 0xF7, 0xFA, 0xFD, 0x21, 0x6E, 0xB8, 0x21, 0x69,
|
||||
0xB5, 0x21, 0x6F, 0xC4, 0x22, 0x65, 0x76, 0xF7, 0xFD, 0xC6, 0x05, 0x23, 0x64, 0x67, 0x6C, 0x6E, 0x72, 0x73, 0xFF,
|
||||
0xAA, 0xFF, 0xF2, 0xFF, 0xF5, 0xFF, 0xFB, 0xFF, 0xAA, 0xFF, 0xE5, 0x41, 0xA9, 0xFF, 0x95, 0x21, 0xC3, 0xFC, 0x41,
|
||||
0x69, 0xFF, 0x97, 0x42, 0x6D, 0x70, 0xFF, 0x9C, 0xFF, 0x9C, 0x41, 0x66, 0xFF, 0x98, 0x45, 0x64, 0x6C, 0x70, 0x72,
|
||||
0x75, 0xFF, 0xEE, 0xFF, 0x7F, 0xFF, 0xF1, 0xFF, 0xF5, 0xFF, 0xFC, 0xA0, 0x04, 0xC2, 0x21, 0x93, 0xFD, 0xA0, 0x05,
|
||||
0x23, 0x21, 0x6E, 0xFD, 0xCA, 0x01, 0xC1, 0x61, 0x63, 0xC3, 0x65, 0x69, 0x6F, 0xC5, 0x70, 0x74, 0x75, 0xFF, 0x7E,
|
||||
0xFF, 0x75, 0xFF, 0x92, 0xFF, 0xA4, 0xFF, 0xB9, 0xFF, 0xE4, 0xFF, 0xF7, 0xFF, 0x75, 0xFF, 0x75, 0xFF, 0xFD, 0x44,
|
||||
0x61, 0x69, 0x6F, 0x73, 0xFD, 0xC5, 0xFF, 0x3E, 0xFD, 0xC5, 0xFF, 0xDF, 0x21, 0xA9, 0xF3, 0x41, 0xA9, 0xFC, 0x86,
|
||||
0x41, 0x64, 0xFC, 0x82, 0x22, 0xC3, 0x69, 0xF8, 0xFC, 0x41, 0x64, 0xFE, 0x4E, 0x41, 0x69, 0xFC, 0x75, 0x41, 0x6D,
|
||||
0xFC, 0x71, 0x21, 0x6F, 0xFC, 0x24, 0x63, 0x6C, 0x6D, 0x74, 0xEC, 0xF1, 0xF5, 0xFD, 0x41, 0x6E, 0xFC, 0x61, 0x41,
|
||||
0x68, 0xFC, 0x92, 0x23, 0x61, 0x65, 0x73, 0xEF, 0xF8, 0xFC, 0xC4, 0x01, 0xE2, 0x61, 0x69, 0x6F, 0x75, 0xFC, 0x5A,
|
||||
0xFC, 0x5A, 0xFC, 0x5A, 0xFC, 0x5A, 0x21, 0x73, 0xF1, 0x41, 0x6C, 0xFB, 0xFC, 0x45, 0x61, 0xC3, 0x69, 0x79, 0x6F,
|
||||
0xFE, 0xE1, 0xFF, 0xB3, 0xFF, 0xE3, 0xFF, 0xF9, 0xFF, 0xFC, 0x48, 0x61, 0x65, 0xC3, 0x69, 0x6F, 0x73, 0x74, 0x75,
|
||||
0xFC, 0x74, 0xFC, 0x90, 0xFC, 0xBE, 0xFC, 0xCB, 0xFC, 0xE2, 0xFC, 0xF0, 0xFD, 0x10, 0xFD, 0x13, 0xC2, 0x00, 0x61,
|
||||
0x67, 0x6E, 0xFC, 0x35, 0xFF, 0xE7, 0x41, 0x64, 0xFE, 0x6A, 0x21, 0x69, 0xFC, 0x41, 0x61, 0xFC, 0x3B, 0x21, 0x63,
|
||||
0xFC, 0x21, 0x69, 0xFD, 0x22, 0x63, 0x66, 0xF3, 0xFD, 0x41, 0x6D, 0xFC, 0x29, 0x22, 0x69, 0x75, 0xF7, 0xFC, 0x21,
|
||||
0x6E, 0xFB, 0x41, 0x73, 0xFB, 0x25, 0x21, 0x6F, 0xFC, 0x42, 0x6B, 0x72, 0xFC, 0x16, 0xFF, 0xFD, 0x41, 0x73, 0xFB,
|
||||
0xE2, 0x42, 0x65, 0x6F, 0xFF, 0xFC, 0xFB, 0xDE, 0x21, 0x72, 0xF9, 0x41, 0xA9, 0xFD, 0xED, 0x21, 0xC3, 0xFC, 0x21,
|
||||
0x73, 0xFD, 0x44, 0x64, 0x69, 0x70, 0x76, 0xFF, 0xF3, 0xFF, 0xFD, 0xFD, 0xE3, 0xFB, 0xCA, 0x41, 0x6E, 0xFD, 0xD6,
|
||||
0x41, 0x74, 0xFD, 0xD2, 0x21, 0x6E, 0xFC, 0x42, 0x63, 0x64, 0xFD, 0xCB, 0xFB, 0xB2, 0x24, 0x61, 0x65, 0x69, 0x6F,
|
||||
0xE1, 0xEE, 0xF6, 0xF9, 0x41, 0x78, 0xFD, 0xBB, 0x24, 0x67, 0x63, 0x6C, 0x72, 0xAB, 0xB5, 0xF3, 0xFC, 0x41, 0x68,
|
||||
0xFE, 0xCA, 0x21, 0x6F, 0xFC, 0xC1, 0x01, 0xC1, 0x6E, 0xFD, 0xF2, 0x41, 0x73, 0xFE, 0xBD, 0x41, 0x73, 0xFE, 0xBF,
|
||||
0x44, 0x61, 0x65, 0x69, 0x75, 0xFF, 0xF2, 0xFF, 0xF8, 0xFE, 0xB5, 0xFF, 0xFC, 0x41, 0x61, 0xFA, 0xA5, 0x21, 0x74,
|
||||
0xFC, 0x21, 0x73, 0xFD, 0x21, 0x61, 0xFD, 0x23, 0x67, 0x73, 0x74, 0xD5, 0xE6, 0xFD, 0x21, 0xA9, 0xF9, 0xA0, 0x01,
|
||||
0x11, 0x21, 0x6D, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x6C, 0xFD, 0x21, 0x6C, 0xFD, 0x41, 0xC3, 0xFA,
|
||||
0xC6, 0x21, 0x64, 0xFC, 0x42, 0xA9, 0xAF, 0xFA, 0xBC, 0xFF, 0xFD, 0x47, 0x61, 0x65, 0xC3, 0x69, 0x6F, 0x75, 0x73,
|
||||
0xFA, 0xA4, 0xFA, 0xA4, 0xFF, 0xF9, 0xFA, 0xA4, 0xFA, 0xA4, 0xFA, 0xA4, 0xFA, 0xA4, 0x21, 0x6F, 0xEA, 0x21, 0x6E,
|
||||
0xFD, 0x44, 0x61, 0xC3, 0x69, 0x6F, 0xFF, 0x82, 0xFF, 0xC1, 0xFF, 0xD3, 0xFF, 0xFD, 0x41, 0x68, 0xFA, 0xA5, 0x21,
|
||||
0x74, 0xFC, 0x21, 0x61, 0xFD, 0x21, 0x6E, 0xFD, 0xA0, 0x06, 0x22, 0x21, 0xA9, 0xFD, 0x41, 0xA9, 0xFC, 0x27, 0x21,
|
||||
0xC3, 0xFC, 0x21, 0x63, 0xFD, 0xA0, 0x07, 0x82, 0x21, 0x68, 0xFD, 0x21, 0x64, 0xFD, 0x24, 0x67, 0xC3, 0x73, 0x75,
|
||||
0xE4, 0xEA, 0xF4, 0xFD, 0x41, 0x61, 0xFD, 0x8E, 0xC2, 0x01, 0x72, 0x6C, 0x75, 0xFF, 0xFC, 0xFA, 0x4B, 0x47, 0x61,
|
||||
0xC3, 0x65, 0x69, 0x6F, 0x75, 0x73, 0xFF, 0xF7, 0xFA, 0x53, 0xFA, 0x3F, 0xFA, 0x3F, 0xFA, 0x3F, 0xFA, 0x3F, 0xFA,
|
||||
0x3F, 0x21, 0xA9, 0xEA, 0x22, 0x6F, 0xC3, 0xD1, 0xFD, 0x41, 0xA9, 0xFA, 0xB9, 0x21, 0xC3, 0xFC, 0x43, 0x66, 0x6D,
|
||||
0x72, 0xFA, 0xB2, 0xFF, 0xFD, 0xFA, 0xB5, 0x41, 0x73, 0xFC, 0xC1, 0x42, 0x68, 0x74, 0xFA, 0xA4, 0xFC, 0xBD, 0x21,
|
||||
0x70, 0xF9, 0x23, 0x61, 0x69, 0x6F, 0xE8, 0xF2, 0xFD, 0x41, 0xA8, 0xFA, 0x93, 0x42, 0x65, 0xC3, 0xFA, 0x8F, 0xFF,
|
||||
0xFC, 0x21, 0x68, 0xF9, 0x42, 0x63, 0x73, 0xFF, 0xFD, 0xF9, 0xED, 0x41, 0xA9, 0xFA, 0xAB, 0x21, 0xC3, 0xFC, 0x43,
|
||||
0x61, 0x68, 0x65, 0xFF, 0xF2, 0xFF, 0xFD, 0xFA, 0x28, 0x43, 0x6E, 0x72, 0x74, 0xFF, 0xD3, 0xFF, 0xF6, 0xFA, 0x21,
|
||||
0xA0, 0x01, 0xC1, 0x21, 0x61, 0xFD, 0x21, 0x74, 0xFD, 0xC6, 0x00, 0x71, 0x61, 0x65, 0xC3, 0x69, 0x6F, 0x75, 0xFB,
|
||||
0x81, 0xFB, 0x81, 0xFF, 0x57, 0xFB, 0x81, 0xFB, 0x81, 0xFB, 0x81, 0x22, 0x6E, 0x72, 0xE8, 0xEB, 0x41, 0x73, 0xFE,
|
||||
0xE4, 0xA0, 0x07, 0x22, 0x21, 0x61, 0xFD, 0xA2, 0x01, 0x12, 0x73, 0x74, 0xFA, 0xFD, 0x43, 0x6F, 0x73, 0x75, 0xFF,
|
||||
0xEF, 0xFF, 0xF9, 0xF9, 0x61, 0x21, 0x69, 0xF6, 0x21, 0x72, 0xFD, 0x21, 0xA9, 0xFD, 0xA0, 0x07, 0x42, 0x21, 0x74,
|
||||
0xFD, 0x21, 0x73, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x6C, 0xFD, 0xA1, 0x00, 0x71, 0x61, 0xFD, 0x41,
|
||||
0x61, 0xFE, 0xA9, 0x21, 0x69, 0xFC, 0x21, 0x72, 0xFD, 0x21, 0x75, 0xFD, 0x41, 0x74, 0xFF, 0x95, 0x21, 0x65, 0xFC,
|
||||
0x21, 0x74, 0xFD, 0x41, 0x6E, 0xFD, 0x23, 0x45, 0x68, 0x69, 0x6F, 0x72, 0x73, 0xF9, 0x7C, 0xFF, 0xFC, 0xFD, 0x25,
|
||||
0xF9, 0x7C, 0xF9, 0x52, 0x21, 0x74, 0xF0, 0x22, 0x6E, 0x73, 0xE6, 0xFD, 0x41, 0x6E, 0xFB, 0xFD, 0x21, 0x61, 0xFC,
|
||||
0x21, 0x6F, 0xFD, 0x21, 0x68, 0xFD, 0x21, 0x63, 0xFD, 0x21, 0x79, 0xFD, 0x41, 0x6C, 0xFA, 0xE6, 0x21, 0x64, 0xFC,
|
||||
0x21, 0x64, 0xFD, 0x49, 0x72, 0x61, 0x65, 0xC3, 0x68, 0x6C, 0x6F, 0x73, 0x75, 0xFE, 0xF7, 0xFF, 0x48, 0xFF, 0x70,
|
||||
0xFF, 0x96, 0xFF, 0xAB, 0xFF, 0xBA, 0xFF, 0xDE, 0xFF, 0xF3, 0xFF, 0xFD, 0x41, 0x6E, 0xF9, 0x2B, 0x21, 0x67, 0xFC,
|
||||
0x41, 0x6C, 0xFB, 0x17, 0x21, 0x6C, 0xFC, 0x22, 0x61, 0x69, 0xF6, 0xFD, 0x41, 0x67, 0xFE, 0x7D, 0x21, 0x6E, 0xFC,
|
||||
0x41, 0x72, 0xFB, 0xF2, 0x41, 0x65, 0xFF, 0x18, 0x21, 0x6C, 0xFC, 0x42, 0x72, 0x75, 0xFB, 0xE7, 0xFF, 0xFD, 0x41,
|
||||
0x68, 0xFB, 0xEA, 0xA0, 0x08, 0x02, 0x21, 0x74, 0xFD, 0xA1, 0x02, 0x93, 0x6C, 0xFD, 0xA0, 0x08, 0x53, 0xA1, 0x08,
|
||||
0x23, 0x72, 0xFD, 0x21, 0xA9, 0xFB, 0x41, 0x6E, 0xF9, 0x80, 0x21, 0x69, 0xFC, 0x42, 0x6D, 0x6E, 0xFF, 0xFD, 0xF9,
|
||||
0x79, 0x42, 0x69, 0x75, 0xFF, 0xF9, 0xF9, 0x72, 0x41, 0x72, 0xFB, 0x57, 0x45, 0x61, 0xC3, 0x69, 0x6C, 0x75, 0xFF,
|
||||
0xD7, 0xFF, 0xE4, 0xFD, 0x7D, 0xFF, 0xF5, 0xFF, 0xFC, 0xA0, 0x08, 0x83, 0xA1, 0x02, 0x93, 0x74, 0xFD, 0x21, 0x75,
|
||||
0xB9, 0x21, 0x6C, 0xB6, 0xA3, 0x02, 0x93, 0x61, 0x6C, 0x74, 0xFA, 0xFD, 0xB3, 0xA0, 0x08, 0x23, 0x21, 0xA9, 0xFD,
|
||||
0x42, 0x66, 0x74, 0xFB, 0x26, 0xFB, 0x26, 0x42, 0x6D, 0x6E, 0xF9, 0x06, 0xFF, 0xF9, 0x42, 0x66, 0x78, 0xFB, 0x18,
|
||||
0xFB, 0x18, 0x46, 0x61, 0x65, 0xC3, 0x68, 0x69, 0x6F, 0xFF, 0xD1, 0xFF, 0xDC, 0xFF, 0xE8, 0xF9, 0x25, 0xFF, 0xF2,
|
||||
0xFF, 0xF9, 0x22, 0x62, 0x72, 0xAB, 0xED, 0x41, 0x76, 0xFB, 0x50, 0x21, 0x75, 0xFC, 0x48, 0x74, 0x79, 0x61, 0x65,
|
||||
0x63, 0x68, 0x75, 0x6F, 0xFF, 0x4E, 0xFF, 0x57, 0xFF, 0x5A, 0xFF, 0x65, 0xFF, 0x6C, 0xF8, 0xBF, 0xFF, 0xF4, 0xFF,
|
||||
0xFD, 0xC3, 0x00, 0x61, 0x6E, 0x75, 0x76, 0xF9, 0xD1, 0xF9, 0xE4, 0xF9, 0xF0, 0x41, 0x68, 0xF8, 0x9A, 0x43, 0x63,
|
||||
0x6E, 0x74, 0xF9, 0xD7, 0xF9, 0xD7, 0xF9, 0xD7, 0x41, 0x6E, 0xF9, 0xCD, 0x22, 0x61, 0x6F, 0xF2, 0xFC, 0x21, 0x69,
|
||||
0xFB, 0x43, 0x61, 0x68, 0x72, 0xFC, 0x52, 0xF8, 0x80, 0xFF, 0xFD, 0x41, 0x2E, 0xFE, 0x2D, 0x21, 0x74, 0xFC, 0x21,
|
||||
0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x6D, 0xFD, 0x21, 0x6D, 0xFD, 0x21, 0x65, 0xFD, 0x41, 0x62, 0xFD, 0xD2, 0x21,
|
||||
0x6F, 0xFC, 0x21, 0x6E, 0xFD, 0x21, 0x6F, 0xFD, 0x42, 0x73, 0x74, 0xF7, 0xFF, 0xF7, 0xFF, 0x42, 0x65, 0x69, 0xF7,
|
||||
0xF8, 0xFF, 0xF9, 0x41, 0x78, 0xFD, 0xFC, 0xA2, 0x02, 0x72, 0x6C, 0x75, 0xF5, 0xFC, 0x41, 0x72, 0xFD, 0xF1, 0x42,
|
||||
0xA9, 0xA8, 0xFD, 0x4A, 0xFF, 0xFC, 0xC2, 0x02, 0x72, 0x6C, 0x72, 0xFD, 0xE6, 0xFD, 0xE6, 0x41, 0x69, 0xF7, 0xD2,
|
||||
0xA1, 0x02, 0x72, 0x66, 0xFC, 0x41, 0x73, 0xFD, 0xD4, 0xA1, 0x01, 0xB1, 0x73, 0xFC, 0x41, 0x72, 0xFA, 0xC2, 0x47,
|
||||
0x61, 0xC3, 0x65, 0x69, 0x6F, 0x75, 0x74, 0xFF, 0xCF, 0xFF, 0xDA, 0xFF, 0xE1, 0xFF, 0xEE, 0xF9, 0x51, 0xFF, 0xF7,
|
||||
0xFF, 0xFC, 0x21, 0xA9, 0xEA, 0x41, 0x70, 0xF8, 0x3E, 0x42, 0x69, 0x6F, 0xF8, 0x3A, 0xF8, 0x3A, 0x21, 0x73, 0xF9,
|
||||
0x41, 0x75, 0xF8, 0x30, 0x44, 0x61, 0x69, 0x6F, 0x72, 0xFF, 0xEE, 0xFF, 0xF9, 0xFF, 0xFC, 0xF8, 0x8C, 0x41, 0x63,
|
||||
0xF8, 0x22, 0x41, 0x72, 0xF8, 0x1B, 0x41, 0x64, 0xF8, 0x17, 0x21, 0x6E, 0xFC, 0x21, 0x65, 0xFD, 0x41, 0x73, 0xF8,
|
||||
0x0D, 0x21, 0x6E, 0xFC, 0x24, 0x65, 0x69, 0x6C, 0x6F, 0xE7, 0xEB, 0xF6, 0xFD, 0x41, 0x69, 0xF8, 0x73, 0x21, 0x75,
|
||||
0xFC, 0xC1, 0x01, 0xE2, 0x65, 0xFA, 0x36, 0x41, 0x64, 0xF6, 0xDA, 0x44, 0x62, 0x67, 0x6E, 0x74, 0xF6, 0xD6, 0xF6,
|
||||
0xD6, 0xFF, 0xFC, 0xF6, 0xD6, 0x42, 0x6E, 0x72, 0xF6, 0xC9, 0xF6, 0xC9, 0x21, 0xA9, 0xF9, 0x42, 0x6D, 0x70, 0xF6,
|
||||
0xBF, 0xF6, 0xBF, 0x42, 0x63, 0x70, 0xF6, 0xB8, 0xF6, 0xB8, 0xA0, 0x07, 0xA2, 0x21, 0x6E, 0xFD, 0x21, 0x69, 0xFD,
|
||||
0x21, 0x74, 0xF7, 0x22, 0x63, 0x6E, 0xFD, 0xF4, 0xA2, 0x00, 0xC2, 0x65, 0x69, 0xF5, 0xFB, 0xC7, 0x01, 0xE2, 0x61,
|
||||
0xC3, 0x69, 0x6F, 0x72, 0x75, 0x79, 0xFF, 0xC3, 0xFF, 0xD7, 0xFF, 0xDA, 0xFF, 0xE1, 0xFF, 0xF9, 0xF6, 0x99, 0xF6,
|
||||
0x99, 0xC5, 0x02, 0x52, 0x63, 0x70, 0x71, 0x73, 0x74, 0xFF, 0x6B, 0xFF, 0x91, 0xFF, 0x9E, 0xFF, 0xA1, 0xFF, 0xE8,
|
||||
0x21, 0x73, 0xEE, 0x42, 0xC3, 0x65, 0xFF, 0x41, 0xFF, 0xFD, 0x41, 0x74, 0xF7, 0x02, 0x21, 0x61, 0xFC, 0x53, 0x61,
|
||||
0xC3, 0x62, 0x63, 0x64, 0x65, 0x69, 0x6D, 0x70, 0x73, 0x6F, 0x6B, 0x74, 0x67, 0x6E, 0x72, 0x6C, 0x75, 0x79, 0xF8,
|
||||
0xB1, 0xF8, 0xE6, 0xF9, 0x32, 0xF9, 0xCA, 0xFB, 0x03, 0xF7, 0x50, 0xFB, 0x2C, 0xFC, 0x27, 0xFD, 0x92, 0xFE, 0x6E,
|
||||
0xFE, 0x87, 0xFE, 0x93, 0xFE, 0xAD, 0xFE, 0xCA, 0xFE, 0xD7, 0xFF, 0xF2, 0xFF, 0xFD, 0xF8, 0x85, 0xF8, 0x85, 0xA0,
|
||||
0x00, 0x81, 0x41, 0xAE, 0xFE, 0x87, 0xA0, 0x02, 0x31, 0x21, 0x2E, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x6E, 0xFD, 0x42,
|
||||
0x74, 0x65, 0xF8, 0x91, 0xFF, 0xFD, 0x23, 0x68, 0xC3, 0x73, 0xE6, 0xE9, 0xF9, 0x21, 0x68, 0xDF, 0xA0, 0x00, 0xA2,
|
||||
0x21, 0x65, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x64, 0xFD, 0x21, 0xA8, 0xFD, 0xA0, 0x00, 0xE1, 0x21, 0x6C, 0xFD, 0x21,
|
||||
0x6F, 0xFD, 0x21, 0x6F, 0xFD, 0xA0, 0x00, 0xF2, 0x21, 0x69, 0xFD, 0x21, 0x67, 0xFD, 0x21, 0x6C, 0xFD, 0x22, 0x63,
|
||||
0x61, 0xF1, 0xFD, 0xA0, 0x00, 0xE2, 0x21, 0x69, 0xFD, 0x21, 0x73, 0xFD, 0x21, 0xA9, 0xFD, 0x21, 0xC3, 0xFD, 0x21,
|
||||
0x68, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x73, 0xFD, 0x41, 0x2E, 0xF6, 0x46, 0x21, 0x74, 0xFC, 0x21, 0x6E, 0xFD, 0x21,
|
||||
0x65, 0xFD, 0x21, 0x6D, 0xFD, 0x41, 0x2E, 0xF8, 0xC6, 0x21, 0x74, 0xFC, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x21,
|
||||
0x6D, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x66, 0xFD, 0x21, 0x69, 0xFD, 0x23, 0x65, 0x69, 0x74, 0xD1,
|
||||
0xE1, 0xFD, 0x41, 0x74, 0xFE, 0x84, 0x21, 0x73, 0xFC, 0x41, 0x72, 0xF8, 0xDB, 0x21, 0x61, 0xFC, 0x22, 0x6F, 0x70,
|
||||
0xF6, 0xFD, 0x41, 0x73, 0xF5, 0xD8, 0x21, 0x69, 0xFC, 0x21, 0x70, 0xFD, 0x21, 0xA9, 0xFD, 0x21, 0xC3, 0xFD, 0x21,
|
||||
0x69, 0xFD, 0x21, 0x68, 0xFD, 0xA0, 0x06, 0x41, 0x21, 0x6C, 0xFD, 0x21, 0x6C, 0xFD, 0x41, 0x2E, 0xFF, 0x33, 0x21,
|
||||
0x74, 0xFC, 0x21, 0x6E, 0xFD, 0x22, 0x69, 0x65, 0xF3, 0xFD, 0x22, 0x63, 0x6D, 0xE5, 0xFB, 0xA0, 0x02, 0x02, 0x21,
|
||||
0x6F, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x65, 0xEA, 0x22, 0x74, 0x6D, 0xFA, 0xFD, 0x41, 0x65, 0xFF, 0x1E, 0xA0, 0x03,
|
||||
0x21, 0x21, 0x2E, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x63, 0xFD, 0x21, 0x73, 0xFD,
|
||||
0x21, 0x65, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x75, 0xFD, 0x22, 0x63, 0x71, 0xDE, 0xFD, 0x21, 0x73, 0xC8, 0x21, 0x6F,
|
||||
0xFD, 0x21, 0x6E, 0xFD, 0x41, 0x6C, 0xF8, 0x6B, 0x21, 0x69, 0xFC, 0xA0, 0x05, 0xE1, 0x21, 0x2E, 0xFD, 0x21, 0x74,
|
||||
0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x6D, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x67, 0xFD, 0x21, 0x6C, 0xFD,
|
||||
0x21, 0x61, 0xFD, 0x41, 0x6D, 0xFF, 0xA3, 0x4E, 0x62, 0x64, 0xC3, 0x6C, 0x6E, 0x70, 0x72, 0x73, 0x63, 0x67, 0x76,
|
||||
0x6D, 0x69, 0x75, 0xFE, 0xCF, 0xFE, 0xD6, 0xFE, 0xE5, 0xFF, 0x00, 0xFF, 0x49, 0xFF, 0x5E, 0xFF, 0x91, 0xFF, 0xA2,
|
||||
0xFF, 0xC9, 0xFF, 0xD4, 0xFF, 0xDB, 0xFF, 0xF9, 0xFF, 0xFC, 0xFF, 0xFC, 0x47, 0xA2, 0xA9, 0xA8, 0xAA, 0xAE, 0xB4,
|
||||
0xBB, 0xFE, 0xBD, 0xFE, 0xBD, 0xFE, 0xBD, 0xFE, 0xBD, 0xFE, 0xBD, 0xFE, 0xBD, 0xFE, 0xBD, 0xA0, 0x02, 0x41, 0x21,
|
||||
0x2E, 0xFD, 0xA0, 0x00, 0x41, 0x21, 0x2E, 0xFD, 0x21, 0x74, 0xFD, 0xA3, 0x00, 0xE1, 0x2E, 0x73, 0x6E, 0xF1, 0xF4,
|
||||
0xFD, 0x23, 0x2E, 0x73, 0x6E, 0xE8, 0xEB, 0xF4, 0xA1, 0x00, 0xE2, 0x65, 0xF9, 0xA0, 0x02, 0xF1, 0x21, 0x6C, 0xFD,
|
||||
0x21, 0x6C, 0xFD, 0x21, 0x69, 0xFD, 0x42, 0x74, 0x6D, 0xFF, 0xFD, 0xFE, 0xB6, 0xA1, 0x00, 0xE1, 0x75, 0xF9, 0xC2,
|
||||
0x00, 0xE2, 0x65, 0x75, 0xFF, 0xDC, 0xFE, 0xAD, 0x49, 0x61, 0xC3, 0x65, 0x69, 0x6C, 0x6F, 0x72, 0x75, 0x79, 0xFE,
|
||||
0x62, 0xFF, 0xA5, 0xFF, 0xCA, 0xFE, 0x62, 0xFF, 0xDA, 0xFF, 0xF2, 0xFF, 0xF7, 0xFE, 0x62, 0xFE, 0x62, 0x43, 0x65,
|
||||
0x69, 0x75, 0xFE, 0x23, 0xFC, 0x9D, 0xFC, 0x9D, 0x41, 0x69, 0xF4, 0xB7, 0xA0, 0x05, 0x92, 0x21, 0x65, 0xFD, 0x21,
|
||||
0x75, 0xFD, 0x22, 0x65, 0x71, 0xF7, 0xFD, 0x21, 0x69, 0xFB, 0x43, 0x65, 0x68, 0x72, 0xFE, 0x04, 0xFF, 0xEB, 0xFF,
|
||||
0xFD, 0x21, 0x72, 0xE5, 0x21, 0x74, 0xFD, 0x21, 0x63, 0xFD, 0x21, 0x74, 0xDC, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD,
|
||||
0x21, 0x6D, 0xFD, 0x21, 0xA9, 0xFD, 0x41, 0x75, 0xF7, 0x4F, 0x21, 0x71, 0xFC, 0x44, 0x65, 0xC3, 0x69, 0x6F, 0xFF,
|
||||
0xE7, 0xFF, 0xF6, 0xFC, 0x55, 0xFF, 0xFD, 0x21, 0x67, 0xB9, 0x21, 0x72, 0xFD, 0x41, 0x74, 0xF7, 0x35, 0x22, 0x65,
|
||||
0x69, 0xF9, 0xFC, 0xC1, 0x01, 0xC2, 0x65, 0xF4, 0x00, 0x21, 0x70, 0xFA, 0x21, 0x6F, 0xFD, 0x21, 0x63, 0xFD, 0x21,
|
||||
0x73, 0xFD, 0x21, 0x69, 0xFD, 0x41, 0x6C, 0xF6, 0xCF, 0x21, 0x6C, 0xFC, 0x21, 0x69, 0xFD, 0x41, 0x6C, 0xFE, 0x92,
|
||||
0x21, 0x61, 0xFC, 0x41, 0x74, 0xFE, 0x0B, 0x21, 0x6F, 0xFC, 0x22, 0x76, 0x70, 0xF6, 0xFD, 0x42, 0x69, 0x65, 0xFF,
|
||||
0xFB, 0xFD, 0x8D, 0x21, 0x75, 0xF9, 0x48, 0x63, 0x64, 0x6C, 0x6E, 0x70, 0x6D, 0x71, 0x72, 0xFF, 0x60, 0xFF, 0x7F,
|
||||
0xFF, 0xA8, 0xFF, 0xBF, 0xFF, 0xD6, 0xFF, 0xE0, 0xFF, 0xFD, 0xFE, 0x65, 0x45, 0xA7, 0xA9, 0xA2, 0xA8, 0xB4, 0xFD,
|
||||
0x8D, 0xFF, 0xE7, 0xFE, 0xA1, 0xFE, 0xA1, 0xFE, 0xA1, 0xA0, 0x02, 0xC3, 0x21, 0x74, 0xFD, 0x21, 0x75, 0xFD, 0x41,
|
||||
0x69, 0xFA, 0xC0, 0x41, 0x2E, 0xF3, 0xB5, 0x21, 0x74, 0xFC, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x6D, 0xFD,
|
||||
0x21, 0xAA, 0xFD, 0x21, 0xC3, 0xFD, 0xA3, 0x00, 0xE1, 0x6F, 0x70, 0x72, 0xE3, 0xE6, 0xFD, 0xA0, 0x06, 0x51, 0x21,
|
||||
0x6C, 0xFD, 0x21, 0x6C, 0xFD, 0x21, 0x69, 0xFD, 0x44, 0x2E, 0x73, 0x6E, 0x76, 0xFE, 0x9E, 0xFE, 0xA1, 0xFE, 0xAA,
|
||||
0xFF, 0xFD, 0x42, 0x2E, 0x73, 0xFE, 0x91, 0xFE, 0x94, 0xA0, 0x03, 0x63, 0x21, 0x63, 0xFD, 0xA0, 0x03, 0x93, 0x21,
|
||||
0x74, 0xFD, 0x21, 0xA9, 0xFD, 0x22, 0x61, 0xC3, 0xF4, 0xFD, 0x21, 0x72, 0xFB, 0xA2, 0x00, 0x81, 0x65, 0x6F, 0xE2,
|
||||
0xFD, 0xC2, 0x00, 0x81, 0x65, 0x6F, 0xFF, 0xDB, 0xFB, 0x6A, 0x41, 0x64, 0xF5, 0x75, 0x21, 0x6E, 0xFC, 0x21, 0x65,
|
||||
0xFD, 0xCD, 0x00, 0xE2, 0x2E, 0x62, 0x65, 0x67, 0x6C, 0x6D, 0x6E, 0x70, 0x72, 0x73, 0x74, 0x77, 0x69, 0xFE, 0x59,
|
||||
0xFE, 0x5F, 0xFF, 0xBB, 0xFE, 0x5F, 0xFF, 0xE6, 0xFE, 0x5F, 0xFE, 0x5F, 0xFE, 0x5F, 0xFF, 0xED, 0xFE, 0x5F, 0xFE,
|
||||
0x5F, 0xFE, 0x5F, 0xFF, 0xFD, 0x41, 0x6C, 0xF2, 0xB8, 0xA1, 0x00, 0xE1, 0x6C, 0xFC, 0xA0, 0x03, 0xC2, 0xC9, 0x00,
|
||||
0xE2, 0x2E, 0x62, 0x65, 0x66, 0x67, 0x68, 0x70, 0x73, 0x74, 0xFE, 0x23, 0xFE, 0x29, 0xFE, 0x3B, 0xFE, 0x29, 0xFE,
|
||||
0x29, 0xFF, 0xFD, 0xFE, 0x29, 0xFE, 0x29, 0xFE, 0x29, 0xC2, 0x00, 0xE2, 0x65, 0x61, 0xFE, 0x1D, 0xFC, 0xEE, 0xA0,
|
||||
0x03, 0xE1, 0x22, 0x63, 0x71, 0xFD, 0xFD, 0xA0, 0x03, 0xF2, 0x21, 0x63, 0xF5, 0x21, 0x72, 0xF2, 0x22, 0x6F, 0x75,
|
||||
0xFA, 0xFD, 0x21, 0x73, 0xFB, 0x27, 0x63, 0x64, 0x70, 0x72, 0x73, 0x75, 0x78, 0xEA, 0xEF, 0xE7, 0xE7, 0xFD, 0xE7,
|
||||
0xE7, 0xA0, 0x04, 0x12, 0x21, 0xA9, 0xFD, 0x23, 0x66, 0x6E, 0x78, 0xD2, 0xD2, 0xD2, 0x41, 0x62, 0xFC, 0x3B, 0x21,
|
||||
0x72, 0xFC, 0x41, 0x69, 0xFF, 0x5D, 0x41, 0x2E, 0xFD, 0xE0, 0x21, 0x74, 0xFC, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD,
|
||||
0x42, 0x67, 0x65, 0xFF, 0xFD, 0xF4, 0xBE, 0x21, 0x6E, 0xF9, 0x21, 0x69, 0xFD, 0x41, 0x76, 0xF4, 0xB4, 0x21, 0x69,
|
||||
0xFC, 0x24, 0x75, 0x66, 0x74, 0x6E, 0xD8, 0xDB, 0xF6, 0xFD, 0x41, 0x69, 0xF2, 0xCF, 0x21, 0x74, 0xFC, 0x21, 0x69,
|
||||
0xFD, 0x21, 0x6E, 0xFD, 0x41, 0x6C, 0xF4, 0x97, 0x21, 0x75, 0xFC, 0x21, 0x70, 0xFD, 0x21, 0x74, 0xC9, 0x21, 0xA9,
|
||||
0xFD, 0x21, 0xC3, 0xFD, 0x21, 0x70, 0xFD, 0xC7, 0x00, 0xE1, 0x61, 0xC3, 0x65, 0x6E, 0x67, 0x72, 0x6D, 0xFF, 0x8C,
|
||||
0xFF, 0x9E, 0xFF, 0xA1, 0xFF, 0xD4, 0xFF, 0xE7, 0xFF, 0xF1, 0xFF, 0xFD, 0x41, 0x93, 0xFB, 0xFE, 0x41, 0x72, 0xF2,
|
||||
0x88, 0xA1, 0x00, 0xE1, 0x72, 0xFC, 0xC1, 0x00, 0xE1, 0x72, 0xFE, 0x7D, 0x41, 0x64, 0xF2, 0x79, 0x21, 0x69, 0xFC,
|
||||
0x4D, 0x61, 0xC3, 0x65, 0x68, 0x69, 0x6B, 0x6C, 0x6F, 0xC5, 0x72, 0x75, 0x79, 0x63, 0xFE, 0x8A, 0xFD, 0x27, 0xFD,
|
||||
0x4C, 0xFE, 0xE4, 0xFF, 0x12, 0xFF, 0x1A, 0xFF, 0x38, 0xFF, 0xCE, 0xFF, 0xE6, 0xFD, 0x5C, 0xFF, 0xEE, 0xFF, 0xF3,
|
||||
0xFF, 0xFD, 0x41, 0x63, 0xFC, 0x7B, 0xC3, 0x00, 0xE1, 0x61, 0x6B, 0x65, 0xFF, 0xFC, 0xFD, 0x17, 0xFD, 0x29, 0x41,
|
||||
0x63, 0xFF, 0x53, 0x21, 0x69, 0xFC, 0x21, 0x66, 0xFD, 0x21, 0x69, 0xFD, 0xA1, 0x00, 0xE1, 0x6E, 0xFD, 0x41, 0x74,
|
||||
0xF2, 0x5A, 0xA1, 0x00, 0x91, 0x65, 0xFC, 0x21, 0x6C, 0xFB, 0xC3, 0x00, 0xE1, 0x6C, 0x6D, 0x74, 0xFF, 0xFD, 0xFC,
|
||||
0x45, 0xFB, 0x1A, 0x41, 0x6C, 0xFF, 0x29, 0x21, 0x61, 0xFC, 0x21, 0x76, 0xFD, 0x41, 0x61, 0xF2, 0xF5, 0x21, 0xA9,
|
||||
0xFC, 0x21, 0xC3, 0xFD, 0x21, 0x72, 0xFD, 0x22, 0x6F, 0x74, 0xF0, 0xFD, 0xA0, 0x04, 0xC3, 0x21, 0x67, 0xFD, 0x21,
|
||||
0xA2, 0xFD, 0x21, 0xC3, 0xFD, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0xA2, 0x00, 0xE1, 0x6E, 0x79, 0xE9, 0xFD, 0x41,
|
||||
0x6E, 0xFF, 0x2B, 0x21, 0x6F, 0xFC, 0xA1, 0x00, 0xE1, 0x63, 0xFD, 0x47, 0xA2, 0xA9, 0xA8, 0xAA, 0xAE, 0xB4, 0xBB,
|
||||
0xFB, 0x41, 0xFF, 0xFB, 0xFB, 0x41, 0xFB, 0x41, 0xFB, 0x41, 0xFB, 0x41, 0xFB, 0x41, 0xC2, 0x00, 0xE1, 0x2E, 0x73,
|
||||
0xFC, 0x84, 0xFC, 0x87, 0x41, 0x6F, 0xFB, 0x3F, 0x42, 0x6D, 0x73, 0xFF, 0xFC, 0xFB, 0x3E, 0x41, 0x73, 0xFB, 0x34,
|
||||
0x22, 0xA9, 0xA8, 0xF5, 0xFC, 0x21, 0xC3, 0xFB, 0xA0, 0x02, 0xA2, 0x4A, 0x75, 0x69, 0x6F, 0x61, 0xC3, 0x65, 0x6E,
|
||||
0xC5, 0x73, 0x79, 0xFF, 0x69, 0xFF, 0x7A, 0xFF, 0xB4, 0xFB, 0x08, 0xFF, 0xC7, 0xFF, 0xDD, 0xFF, 0xFA, 0xFF, 0x0A,
|
||||
0xFF, 0xFD, 0xFB, 0x08, 0x41, 0x63, 0xF3, 0x54, 0x21, 0x69, 0xFC, 0x41, 0x67, 0xFE, 0x89, 0x21, 0x72, 0xFC, 0x21,
|
||||
0x75, 0xFD, 0x41, 0x61, 0xF3, 0x46, 0xC4, 0x00, 0xE1, 0x74, 0x67, 0x73, 0x6D, 0xFF, 0xEF, 0xF1, 0x62, 0xFF, 0xF9,
|
||||
0xFF, 0xFC, 0x47, 0xA9, 0xA2, 0xA8, 0xAA, 0xAE, 0xB4, 0xBB, 0xFF, 0xF1, 0xFA, 0xC5, 0xFA, 0xC5, 0xFA, 0xC5, 0xFA,
|
||||
0xC5, 0xFA, 0xC5, 0xFA, 0xC5, 0x41, 0x67, 0xF1, 0x3D, 0xC2, 0x00, 0xE1, 0x6E, 0x6D, 0xFF, 0xFC, 0xFB, 0x62, 0x42,
|
||||
0x65, 0x69, 0xFA, 0x7F, 0xF8, 0xF9, 0xC5, 0x00, 0xE1, 0x6C, 0x70, 0x2E, 0x73, 0x6E, 0xFF, 0xF9, 0xFB, 0x5A, 0xFB,
|
||||
0xF4, 0xFB, 0xF7, 0xFC, 0x00, 0xC1, 0x00, 0xE1, 0x6C, 0xFB, 0x48, 0x41, 0x6D, 0xF1, 0x11, 0x41, 0x61, 0xF0, 0xC1,
|
||||
0x21, 0x6F, 0xFC, 0x21, 0x69, 0xFD, 0xC3, 0x00, 0xE1, 0x6D, 0x69, 0x64, 0xFB, 0x2C, 0xFF, 0xF2, 0xFF, 0xFD, 0x41,
|
||||
0x68, 0xF8, 0xC0, 0xA1, 0x00, 0xE1, 0x74, 0xFC, 0xA0, 0x07, 0xC2, 0x21, 0x72, 0xFD, 0x43, 0x2E, 0x73, 0x75, 0xFB,
|
||||
0xB3, 0xFB, 0xB6, 0xFF, 0xFD, 0x21, 0x64, 0xF3, 0xA2, 0x00, 0xE2, 0x65, 0x79, 0xF3, 0xFD, 0x4A, 0xC3, 0x69, 0x63,
|
||||
0x6D, 0x65, 0x75, 0x61, 0x79, 0x68, 0x6F, 0xFF, 0x81, 0xFF, 0x9B, 0xFB, 0x39, 0xFB, 0x39, 0xFF, 0xAB, 0xFF, 0xBD,
|
||||
0xFF, 0xD1, 0xFF, 0xE1, 0xFF, 0xF9, 0xFA, 0x46, 0xA0, 0x03, 0x11, 0x21, 0x2E, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x6E,
|
||||
0xFD, 0x21, 0x65, 0xFD, 0x22, 0x63, 0x7A, 0xFD, 0xFD, 0x21, 0x6F, 0xFB, 0x21, 0x64, 0xFD, 0x21, 0x74, 0xFD, 0x21,
|
||||
0x61, 0xFD, 0x21, 0x76, 0xFD, 0x21, 0x6E, 0xE9, 0x21, 0x69, 0xFD, 0x21, 0x6D, 0xFD, 0x21, 0xA9, 0xFD, 0x42, 0xC3,
|
||||
0x73, 0xFF, 0xFD, 0xF3, 0x42, 0x21, 0xA9, 0xF9, 0x41, 0x6E, 0xFA, 0x3D, 0x21, 0x69, 0xFC, 0x21, 0x6D, 0xFD, 0x21,
|
||||
0xA9, 0xFD, 0x41, 0x74, 0xF4, 0xB0, 0x22, 0xC3, 0x73, 0xF9, 0xFC, 0xC5, 0x00, 0xE2, 0x69, 0x75, 0xC3, 0x6F, 0x65,
|
||||
0xFF, 0xD1, 0xFD, 0xED, 0xFF, 0xE7, 0xFF, 0xFB, 0xFB, 0x49, 0x41, 0x65, 0xF0, 0x5C, 0x21, 0x6C, 0xFC, 0x42, 0x62,
|
||||
0x63, 0xFF, 0xFD, 0xF0, 0x55, 0x21, 0x61, 0xF9, 0x21, 0x6E, 0xFD, 0xC3, 0x00, 0xE1, 0x67, 0x70, 0x73, 0xFF, 0xFD,
|
||||
0xFC, 0x3E, 0xFC, 0x3E, 0x41, 0x6D, 0xF2, 0x05, 0x44, 0x61, 0x65, 0x69, 0x6F, 0xF2, 0x01, 0xF2, 0x01, 0xF2, 0x01,
|
||||
0xFF, 0xFC, 0x21, 0x6C, 0xF3, 0x21, 0x6C, 0xFD, 0x21, 0x69, 0xFD, 0xA0, 0x06, 0xD2, 0x21, 0xA9, 0xFD, 0x21, 0xC3,
|
||||
0xFD, 0x21, 0x6F, 0xFD, 0x21, 0xA9, 0xFD, 0x21, 0xC3, 0xFD, 0xA2, 0x00, 0xE1, 0x70, 0x6C, 0xEB, 0xFD, 0x42, 0xA9,
|
||||
0xA8, 0xF5, 0x47, 0xF5, 0x47, 0x48, 0x76, 0x61, 0x65, 0xC3, 0x69, 0x6F, 0x73, 0x75, 0xFD, 0xEE, 0xF1, 0x6D, 0xF1,
|
||||
0x6D, 0xFF, 0xF9, 0xF1, 0x6D, 0xF1, 0x6D, 0xF1, 0x6D, 0xF1, 0x6D, 0x21, 0x79, 0xE7, 0x41, 0x65, 0xFC, 0xAD, 0x21,
|
||||
0x72, 0xFC, 0x21, 0x74, 0xFD, 0x21, 0x73, 0xFD, 0xA2, 0x00, 0xE1, 0x6C, 0x61, 0xF0, 0xFD, 0xC2, 0x00, 0xE2, 0x75,
|
||||
0x65, 0xF9, 0x7E, 0xFA, 0xAD, 0x43, 0x6D, 0x74, 0x68, 0xFE, 0x5B, 0xF1, 0xA4, 0xEF, 0x15, 0xC4, 0x00, 0xE1, 0x72,
|
||||
0x2E, 0x73, 0x6E, 0xFF, 0xF6, 0xFA, 0x82, 0xFA, 0x85, 0xFA, 0x8E, 0x41, 0x6C, 0xEF, 0x95, 0x21, 0x75, 0xFC, 0xA0,
|
||||
0x06, 0xF3, 0x21, 0x71, 0xFD, 0x21, 0xA9, 0xFD, 0x21, 0xC3, 0xFD, 0xA2, 0x00, 0xE1, 0x6E, 0x72, 0xF1, 0xFD, 0x47,
|
||||
0xA2, 0xA9, 0xA8, 0xAA, 0xAE, 0xB4, 0xBB, 0xF9, 0x00, 0xFF, 0xF9, 0xF9, 0x00, 0xF9, 0x00, 0xF9, 0x00, 0xF9, 0x00,
|
||||
0xF9, 0x00, 0xC1, 0x00, 0x81, 0x65, 0xFB, 0xB2, 0x41, 0x73, 0xEF, 0x26, 0x21, 0x6F, 0xFC, 0x21, 0x74, 0xFD, 0xA0,
|
||||
0x07, 0x62, 0x21, 0xA9, 0xFD, 0x21, 0xC3, 0xFD, 0x21, 0x6C, 0xFD, 0x21, 0x73, 0xF4, 0xA2, 0x00, 0x41, 0x61, 0x69,
|
||||
0xFA, 0xFD, 0xC8, 0x00, 0xE2, 0x2E, 0x65, 0x6C, 0x6E, 0x6F, 0x72, 0x73, 0x74, 0xFA, 0x1D, 0xFA, 0x35, 0xFF, 0xDA,
|
||||
0xFA, 0x23, 0xFF, 0xE7, 0xFF, 0xDA, 0xFA, 0x23, 0xFF, 0xF9, 0x41, 0xA9, 0xF8, 0xC6, 0x41, 0x75, 0xF8, 0xC2, 0x22,
|
||||
0xC3, 0x65, 0xF8, 0xFC, 0x41, 0x68, 0xF8, 0xB9, 0x21, 0x63, 0xFC, 0x21, 0x79, 0xFD, 0x41, 0x72, 0xF8, 0xAF, 0x22,
|
||||
0xA8, 0xA9, 0xFC, 0xFC, 0x21, 0xC3, 0xFB, 0x4D, 0x72, 0x75, 0x61, 0x69, 0x6F, 0x6C, 0x65, 0xC3, 0x68, 0x6E, 0x73,
|
||||
0x74, 0x79, 0xFE, 0xAE, 0xFE, 0xD4, 0xFF, 0x0C, 0xFC, 0x95, 0xFF, 0x43, 0xFF, 0x4A, 0xFF, 0x5D, 0xFF, 0x86, 0xFF,
|
||||
0xC2, 0xFF, 0xE5, 0xFF, 0xF1, 0xFF, 0xFD, 0xF8, 0x86, 0x41, 0x63, 0xF1, 0xA8, 0x21, 0x6F, 0xFC, 0x41, 0x64, 0xF1,
|
||||
0xA1, 0x21, 0x69, 0xFC, 0x41, 0x67, 0xF1, 0x9A, 0x41, 0x67, 0xF0, 0xB7, 0x21, 0x6C, 0xFC, 0x41, 0x6C, 0xF1, 0x8F,
|
||||
0x23, 0x69, 0x75, 0x6F, 0xF1, 0xF9, 0xFC, 0x41, 0x67, 0xF8, 0x89, 0x21, 0x69, 0xFC, 0x21, 0x6C, 0xFD, 0x21, 0x6C,
|
||||
0xFD, 0x42, 0x65, 0x69, 0xFF, 0xFD, 0xF6, 0x84, 0x42, 0x74, 0x6F, 0xF9, 0xAC, 0xFF, 0xE1, 0x41, 0x74, 0xF8, 0x1F,
|
||||
0x21, 0x61, 0xFC, 0x21, 0x6D, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x6F, 0xFD, 0x26, 0x6E, 0x63, 0x64, 0x74, 0x73, 0x66,
|
||||
0xB5, 0xBC, 0xCE, 0xE2, 0xE9, 0xFD, 0x41, 0xA9, 0xF8, 0xB0, 0x42, 0x61, 0x6F, 0xF8, 0xAC, 0xF8, 0xAC, 0x22, 0xC3,
|
||||
0x69, 0xF5, 0xF9, 0x42, 0x65, 0x68, 0xF7, 0xCF, 0xFF, 0xFB, 0x41, 0x74, 0xFC, 0xE0, 0x21, 0x61, 0xFC, 0x22, 0x63,
|
||||
0x74, 0xF2, 0xFD, 0x41, 0x2E, 0xF0, 0xE1, 0x21, 0x74, 0xFC, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x63, 0xFD,
|
||||
0x42, 0x73, 0x6E, 0xFF, 0xFD, 0xF1, 0x19, 0x41, 0x6E, 0xF1, 0x12, 0x22, 0x69, 0x61, 0xF5, 0xFC, 0x42, 0x75, 0x6F,
|
||||
0xFF, 0x68, 0xF9, 0xD4, 0x22, 0x6D, 0x70, 0xF4, 0xF9, 0xA0, 0x00, 0xA1, 0x21, 0x69, 0xFD, 0x21, 0x67, 0xFD, 0x21,
|
||||
0x72, 0xF7, 0x21, 0x68, 0xFD, 0x21, 0x74, 0xFD, 0x22, 0x6C, 0x72, 0xF4, 0xFD, 0x41, 0x6C, 0xF7, 0x69, 0x41, 0x72,
|
||||
0xFA, 0x24, 0x41, 0x74, 0xFA, 0xF9, 0x21, 0x63, 0xFC, 0x21, 0x79, 0xDA, 0x22, 0x61, 0x78, 0xFA, 0xFD, 0x41, 0x61,
|
||||
0xF2, 0x17, 0x49, 0x6E, 0x73, 0x6D, 0x61, 0xC3, 0x6C, 0x62, 0x6F, 0x76, 0xFF, 0x72, 0xFF, 0x9D, 0xFF, 0xC9, 0xFF,
|
||||
0xE0, 0xF7, 0x7E, 0xFF, 0xE5, 0xFF, 0xE9, 0xFF, 0xF7, 0xFF, 0xFC, 0x41, 0x70, 0xF8, 0x13, 0x43, 0x65, 0x6F, 0x68,
|
||||
0xF7, 0x3E, 0xFF, 0xFC, 0xF8, 0x0F, 0x41, 0x69, 0xF5, 0xAE, 0x22, 0x63, 0x74, 0xF2, 0xFC, 0xA0, 0x05, 0xB3, 0x21,
|
||||
0x72, 0xFD, 0x21, 0x76, 0xFD, 0x41, 0x65, 0xFE, 0xF9, 0x21, 0x72, 0xFC, 0x22, 0x69, 0x74, 0xF6, 0xFD, 0x41, 0x61,
|
||||
0xFF, 0xA5, 0x21, 0x74, 0xFC, 0x21, 0x73, 0xFD, 0xC2, 0x01, 0x71, 0x63, 0x69, 0xED, 0x74, 0xED, 0x74, 0x21, 0x61,
|
||||
0xF7, 0x21, 0x72, 0xFD, 0x21, 0x74, 0xFD, 0x45, 0x73, 0x6E, 0x75, 0x78, 0x72, 0xFF, 0xCA, 0xFF, 0xDF, 0xFF, 0xEB,
|
||||
0xFF, 0xFD, 0xF8, 0x31, 0xC1, 0x00, 0xE1, 0x6D, 0xF7, 0xC4, 0x41, 0x61, 0xF9, 0xFD, 0x41, 0x6D, 0xFA, 0xAA, 0x21,
|
||||
0x69, 0xFC, 0x21, 0x72, 0xFD, 0xA2, 0x00, 0xE1, 0x63, 0x74, 0xF2, 0xFD, 0x47, 0xA2, 0xA9, 0xA8, 0xAA, 0xAE, 0xB4,
|
||||
0xBB, 0xF6, 0xF2, 0xFF, 0xF9, 0xF6, 0xF2, 0xF6, 0xF2, 0xF6, 0xF2, 0xF6, 0xF2, 0xF6, 0xF2, 0x41, 0x68, 0xFB, 0xD1,
|
||||
0x41, 0x70, 0xED, 0x6E, 0x21, 0x6F, 0xFC, 0x43, 0x73, 0x63, 0x74, 0xFA, 0x6A, 0xFF, 0xFD, 0xF8, 0x57, 0x41, 0x69,
|
||||
0xFE, 0x77, 0x41, 0x2E, 0xEE, 0x5F, 0x21, 0x74, 0xFC, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x21, 0x6D, 0xFD, 0x21,
|
||||
0x67, 0xFD, 0x21, 0x61, 0xFD, 0x21, 0x72, 0xFD, 0x21, 0x68, 0xFD, 0x21, 0x70, 0xFD, 0xA3, 0x00, 0xE1, 0x73, 0x6C,
|
||||
0x61, 0xD3, 0xDD, 0xFD, 0xA0, 0x05, 0x52, 0x21, 0x6C, 0xFD, 0x21, 0x64, 0xFA, 0x21, 0x75, 0xFD, 0x22, 0x61, 0x6F,
|
||||
0xF7, 0xFD, 0x41, 0x6E, 0xF7, 0xEF, 0x21, 0x65, 0xFC, 0x4D, 0x27, 0x61, 0xC3, 0x64, 0x65, 0x69, 0x68, 0x6C, 0x6F,
|
||||
0x72, 0x73, 0x75, 0x79, 0xF6, 0x83, 0xFF, 0x76, 0xFF, 0x91, 0xFF, 0xA7, 0xF7, 0xEB, 0xFF, 0xDF, 0xFF, 0xF4, 0xFF,
|
||||
0xFD, 0xF6, 0x83, 0xF7, 0xFB, 0xFB, 0x78, 0xF6, 0x83, 0xF6, 0x83, 0x41, 0x63, 0xFA, 0x33, 0x41, 0x72, 0xF6, 0xA6,
|
||||
0xA1, 0x01, 0xC2, 0x61, 0xFC, 0x41, 0x73, 0xEF, 0xDE, 0xC2, 0x05, 0x23, 0x63, 0x74, 0xF0, 0x03, 0xFF, 0xFC, 0x45,
|
||||
0x70, 0x61, 0x68, 0x6F, 0x75, 0xFF, 0xEE, 0xFF, 0xF7, 0xEC, 0xAD, 0xF0, 0x56, 0xF0, 0x56, 0x21, 0x73, 0xF0, 0x21,
|
||||
0x6E, 0xFD, 0xC4, 0x00, 0xE2, 0x69, 0x75, 0x61, 0x65, 0xFA, 0x40, 0xFF, 0xD0, 0xFF, 0xFD, 0xF7, 0x9C, 0x41, 0x79,
|
||||
0xFB, 0x9D, 0x21, 0x68, 0xFC, 0xC3, 0x00, 0xE1, 0x6E, 0x6D, 0x63, 0xFB, 0x66, 0xF6, 0xCC, 0xFF, 0xFD, 0x41, 0x6D,
|
||||
0xFB, 0xEE, 0x21, 0x61, 0xFC, 0x21, 0x72, 0xFD, 0x21, 0xA9, 0xFD, 0x21, 0xC3, 0xFD, 0x21, 0x70, 0xFD, 0x41, 0x6D,
|
||||
0xEE, 0x61, 0x21, 0x61, 0xFC, 0x42, 0x74, 0x2E, 0xFF, 0xFD, 0xF7, 0x48, 0xC5, 0x00, 0xE1, 0x72, 0x6D, 0x73, 0x2E,
|
||||
0x6E, 0xFB, 0x39, 0xFF, 0xEF, 0xFF, 0xF9, 0xF7, 0x41, 0xF7, 0x4D, 0xC2, 0x00, 0x81, 0x69, 0x65, 0xF3, 0x22, 0xF8,
|
||||
0x9E, 0x41, 0x73, 0xEB, 0xD9, 0x21, 0x6F, 0xFC, 0x21, 0x6D, 0xFD, 0x44, 0x2E, 0x73, 0x72, 0x75, 0xF7, 0x1C, 0xF7,
|
||||
0x1F, 0xFF, 0xFD, 0xFB, 0x66, 0xC7, 0x00, 0xE2, 0x72, 0x2E, 0x65, 0x6C, 0x6D, 0x6E, 0x73, 0xFF, 0xE0, 0xF7, 0x0F,
|
||||
0xFF, 0xF3, 0xF7, 0x15, 0xF7, 0x15, 0xF7, 0x15, 0xF7, 0x15, 0x41, 0x62, 0xF9, 0x76, 0x41, 0x73, 0xEC, 0x06, 0x21,
|
||||
0x67, 0xFC, 0xC3, 0x00, 0xE1, 0x72, 0x6D, 0x6E, 0xFF, 0xF5, 0xF6, 0x4A, 0xFF, 0xFD, 0xC2, 0x00, 0xE1, 0x6D, 0x72,
|
||||
0xF6, 0x3E, 0xF9, 0x8D, 0x42, 0x62, 0x70, 0xEB, 0x8A, 0xEB, 0x8A, 0x44, 0x65, 0x69, 0x6F, 0x73, 0xEB, 0x83, 0xEB,
|
||||
0x83, 0xFF, 0xF9, 0xEB, 0x83, 0x21, 0xA9, 0xF3, 0x21, 0xC3, 0xFD, 0xA1, 0x00, 0xE1, 0x6C, 0xFD, 0x48, 0xA2, 0xA0,
|
||||
0xA9, 0xA8, 0xAA, 0xAE, 0xB4, 0xBB, 0xF5, 0x5F, 0xF5, 0x5F, 0xFF, 0xFB, 0xF5, 0x5F, 0xF5, 0x5F, 0xF5, 0x5F, 0xF5,
|
||||
0x5F, 0xF5, 0x5F, 0x41, 0x74, 0xF1, 0x2A, 0x21, 0x6E, 0xFC, 0x21, 0x69, 0xFD, 0x21, 0x68, 0xFD, 0x41, 0x6C, 0xFA,
|
||||
0x2E, 0x4B, 0x72, 0x61, 0x65, 0x68, 0x75, 0x6F, 0xC3, 0x63, 0x69, 0x74, 0x79, 0xFF, 0x0A, 0xFF, 0x20, 0xFF, 0x4D,
|
||||
0xFF, 0x7F, 0xFF, 0xA2, 0xFF, 0xAE, 0xFF, 0xD6, 0xFF, 0xF9, 0xF5, 0x35, 0xFF, 0xFC, 0xF5, 0x35, 0xC1, 0x00, 0xE1,
|
||||
0x63, 0xF8, 0xEB, 0x47, 0xA2, 0xA9, 0xA8, 0xAA, 0xAE, 0xB4, 0xBB, 0xF5, 0x0D, 0xFF, 0xFA, 0xF5, 0x0D, 0xF5, 0x0D,
|
||||
0xF5, 0x0D, 0xF5, 0x0D, 0xF5, 0x0D, 0x41, 0x75, 0xFF, 0x01, 0x21, 0x68, 0xFC, 0xC2, 0x00, 0xE1, 0x72, 0x63, 0xF5,
|
||||
0x32, 0xFF, 0xFD, 0xC2, 0x00, 0xE2, 0x65, 0x61, 0xF6, 0x58, 0xF3, 0x41, 0x41, 0x74, 0xF6, 0x64, 0xC2, 0x00, 0xE2,
|
||||
0x65, 0x69, 0xF6, 0x4B, 0xFF, 0xFC, 0x4A, 0x61, 0xC3, 0x65, 0x69, 0x6C, 0x6F, 0x72, 0x73, 0x75, 0x79, 0xFD, 0xC4,
|
||||
0xFF, 0xC4, 0xF6, 0x39, 0xFF, 0xE1, 0xFF, 0xEA, 0xF4, 0xD1, 0xFF, 0xF7, 0xF9, 0xC6, 0xFD, 0xC4, 0xF4, 0xD1, 0x45,
|
||||
0x61, 0x65, 0x69, 0x6F, 0x79, 0xF4, 0xCF, 0xF4, 0xCF, 0xF4, 0xCF, 0xF4, 0xCF, 0xF4, 0xCF, 0x41, 0x75, 0xFA, 0x87,
|
||||
0x21, 0x71, 0xFC, 0x21, 0x6F, 0xFD, 0x21, 0x6C, 0xFD, 0x21, 0x69, 0xFD, 0x21, 0x64, 0xFD, 0x42, 0x6D, 0x6E, 0xF2,
|
||||
0xE6, 0xFF, 0xFD, 0xC2, 0x00, 0xE2, 0x65, 0x61, 0xF5, 0xF9, 0xFF, 0xF9, 0xC1, 0x00, 0xE1, 0x65, 0xF5, 0xF0, 0x4C,
|
||||
0x61, 0xC3, 0x65, 0x68, 0x69, 0x6C, 0x6E, 0x6F, 0x72, 0x75, 0x73, 0x79, 0xF4, 0x79, 0xF5, 0xBC, 0xF5, 0xE1, 0xFF,
|
||||
0xC7, 0xF7, 0xA7, 0xF5, 0xF1, 0xF5, 0xF1, 0xF4, 0x79, 0xFF, 0xF1, 0xFF, 0xFA, 0xF9, 0x6E, 0xF4, 0x79, 0x41, 0x69,
|
||||
0xEF, 0xBB, 0x21, 0x75, 0xFC, 0x42, 0x71, 0x2E, 0xFF, 0xFD, 0xF5, 0xA6, 0xC5, 0x00, 0xE1, 0x72, 0x6D, 0x73, 0x2E,
|
||||
0x6E, 0xEA, 0xD7, 0xF6, 0x80, 0xFF, 0xF9, 0xF5, 0x9F, 0xF5, 0xAB, 0x41, 0x69, 0xF6, 0xD1, 0x42, 0x6C, 0x73, 0xFF,
|
||||
0xFC, 0xEB, 0x02, 0xA0, 0x02, 0xD2, 0x21, 0x68, 0xFD, 0x42, 0xC3, 0x61, 0xFA, 0x3F, 0xFF, 0xFD, 0xC2, 0x06, 0x02,
|
||||
0x6F, 0x73, 0xF5, 0x12, 0xF5, 0x12, 0x21, 0x72, 0xF7, 0x21, 0x65, 0xFD, 0xC5, 0x00, 0xE1, 0x63, 0x62, 0x6D, 0x72,
|
||||
0x70, 0xFD, 0xB2, 0xFF, 0xDD, 0xF4, 0xC4, 0xFF, 0xEA, 0xFF, 0xFD, 0x41, 0x6C, 0xFC, 0x26, 0xA1, 0x00, 0xE2, 0x75,
|
||||
0xFC, 0x21, 0x72, 0xFB, 0x41, 0x61, 0xF4, 0x0C, 0x21, 0x69, 0xFC, 0x21, 0x74, 0xFD, 0x41, 0x6D, 0xF4, 0x02, 0x21,
|
||||
0x72, 0xFC, 0x41, 0x6C, 0xF3, 0xFB, 0x41, 0x6F, 0xF8, 0xC3, 0x22, 0x65, 0x72, 0xF8, 0xFC, 0x45, 0x6F, 0x61, 0x65,
|
||||
0x68, 0x69, 0xFF, 0xDF, 0xFF, 0xE9, 0xFF, 0xF0, 0xFB, 0x48, 0xFF, 0xFB, 0x41, 0x6F, 0xF6, 0x5E, 0x42, 0x6C, 0x76,
|
||||
0xFF, 0xFC, 0xF3, 0xDA, 0x41, 0x76, 0xF3, 0xD3, 0x22, 0x61, 0x6F, 0xF5, 0xFC, 0x41, 0x70, 0xFB, 0x11, 0x41, 0xA9,
|
||||
0xFB, 0x17, 0x21, 0xC3, 0xFC, 0x41, 0x70, 0xF3, 0xBF, 0xC3, 0x00, 0xE2, 0x2E, 0x65, 0x73, 0xF4, 0xF7, 0xF6, 0x66,
|
||||
0xF4, 0xFD, 0x24, 0x61, 0x6C, 0x6F, 0x68, 0xE5, 0xED, 0xF0, 0xF4, 0x41, 0x6D, 0xF9, 0x29, 0xC6, 0x00, 0xE2, 0x2E,
|
||||
0x65, 0x6D, 0x6F, 0x72, 0x73, 0xF4, 0xDE, 0xF4, 0xF6, 0xF4, 0xE4, 0xFF, 0xFC, 0xF4, 0xE4, 0xF4, 0xE4, 0x41, 0x64,
|
||||
0xF3, 0x8D, 0x21, 0x72, 0xFC, 0x21, 0x61, 0xFD, 0x21, 0x64, 0xFD, 0x21, 0x6E, 0xFD, 0x41, 0x6E, 0xF3, 0x7D, 0x21,
|
||||
0x69, 0xFC, 0xA0, 0x07, 0xE2, 0x21, 0x73, 0xFD, 0x21, 0x6F, 0xFD, 0x21, 0xA9, 0xFD, 0x21, 0xC3, 0xFD, 0x21, 0x72,
|
||||
0xFD, 0x21, 0xA9, 0xFD, 0x41, 0x67, 0xFF, 0x5F, 0x41, 0x6B, 0xF3, 0x5D, 0x42, 0x63, 0x6D, 0xFF, 0xFC, 0xFF, 0x62,
|
||||
0x41, 0x74, 0xFA, 0x90, 0x21, 0x63, 0xFC, 0x42, 0x6F, 0x75, 0xFF, 0x81, 0xFF, 0xFD, 0x41, 0x65, 0xF3, 0x44, 0x21,
|
||||
0x6C, 0xFC, 0x27, 0x61, 0x65, 0xC3, 0x69, 0x6F, 0x72, 0x79, 0xBD, 0xC4, 0xD9, 0xDC, 0xE4, 0xF2, 0xFD, 0x4D, 0x65,
|
||||
0x75, 0x70, 0x6C, 0x61, 0xC3, 0x63, 0x68, 0x69, 0x6F, 0xC5, 0x74, 0x79, 0xFE, 0xCB, 0xFF, 0x04, 0xFF, 0x40, 0xFF,
|
||||
0x5F, 0xF3, 0x11, 0xF4, 0x54, 0xFF, 0x7F, 0xFF, 0x8C, 0xF3, 0x11, 0xF3, 0x11, 0xF7, 0x13, 0xFF, 0xF1, 0xF3, 0x11,
|
||||
0x41, 0x69, 0xF3, 0x97, 0x21, 0x6E, 0xFC, 0x21, 0x6F, 0xFD, 0x22, 0x6D, 0x73, 0xFD, 0xF6, 0x21, 0x6F, 0xFB, 0x21,
|
||||
0x6E, 0xFD, 0x41, 0x75, 0xED, 0x66, 0x41, 0x73, 0xEC, 0x54, 0x21, 0x64, 0xFC, 0x21, 0x75, 0xFD, 0x41, 0x6F, 0xF6,
|
||||
0xA4, 0x42, 0x73, 0x70, 0xEA, 0xC3, 0xFF, 0xFC, 0x21, 0x69, 0xF9, 0x43, 0x6D, 0x62, 0x6E, 0xF3, 0x6F, 0xFF, 0xEF,
|
||||
0xFF, 0xFD, 0x41, 0x67, 0xF3, 0x5C, 0x21, 0x6E, 0xFC, 0x21, 0x6F, 0xFD, 0x21, 0x6C, 0xFD, 0x41, 0x65, 0xFA, 0x82,
|
||||
0x21, 0x74, 0xFC, 0x41, 0x6E, 0xFA, 0xEA, 0x21, 0x6F, 0xFC, 0x42, 0x73, 0x74, 0xF7, 0x88, 0xF7, 0x88, 0x41, 0x6F,
|
||||
0xF7, 0x81, 0x21, 0x72, 0xFC, 0x21, 0xA9, 0xFD, 0x41, 0x6D, 0xF7, 0x77, 0x41, 0x75, 0xF7, 0x73, 0x42, 0x64, 0x74,
|
||||
0xF7, 0x6F, 0xFF, 0xFC, 0x41, 0x6E, 0xF7, 0x68, 0x21, 0x6F, 0xFC, 0x21, 0x69, 0xFD, 0x21, 0x74, 0xFD, 0x21, 0x63,
|
||||
0xFD, 0x22, 0x61, 0x69, 0xE9, 0xFD, 0x25, 0x61, 0xC3, 0x69, 0x6F, 0x72, 0xCB, 0xD9, 0xDC, 0xDC, 0xFB, 0x21, 0x74,
|
||||
0xF5, 0x41, 0x61, 0xE9, 0x22, 0x21, 0x79, 0xFC, 0x4B, 0x67, 0x70, 0x6D, 0x72, 0x62, 0x63, 0x64, 0xC3, 0x69, 0x73,
|
||||
0x78, 0xFF, 0x72, 0xFF, 0x75, 0xFF, 0x91, 0xF3, 0x5D, 0xFF, 0xA5, 0xFF, 0xAC, 0xFD, 0x10, 0xF2, 0x46, 0xFF, 0xB3,
|
||||
0xFF, 0xF6, 0xFF, 0xFD, 0x41, 0x6E, 0xE8, 0xBD, 0xA1, 0x00, 0xE1, 0x67, 0xFC, 0x46, 0x61, 0x65, 0x69, 0x6F, 0x75,
|
||||
0x72, 0xFF, 0xFB, 0xF3, 0x86, 0xF2, 0x1E, 0xF2, 0x1E, 0xF2, 0x1E, 0xF2, 0x3B, 0xA0, 0x01, 0x71, 0x21, 0xA9, 0xFD,
|
||||
0x21, 0xC3, 0xFD, 0x41, 0x74, 0xE8, 0x44, 0x21, 0x70, 0xFC, 0x22, 0x69, 0x6F, 0xF6, 0xFD, 0xA1, 0x00, 0xE1, 0x6D,
|
||||
0xFB, 0x47, 0xA2, 0xA9, 0xA8, 0xAA, 0xAE, 0xB4, 0xBB, 0xF1, 0xF1, 0xFF, 0xFB, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1, 0xF1,
|
||||
0xF1, 0xF1, 0xF1, 0xF1, 0x41, 0xA9, 0xE9, 0x74, 0xC7, 0x06, 0x02, 0x61, 0x65, 0xC3, 0x69, 0x6F, 0x73, 0x75, 0xF2,
|
||||
0xCD, 0xF2, 0xCD, 0xFF, 0xFC, 0xF2, 0xCD, 0xF2, 0xCD, 0xF2, 0xCD, 0xF2, 0xCD, 0x21, 0x72, 0xE8, 0x47, 0x61, 0x65,
|
||||
0xC3, 0x69, 0x6F, 0x73, 0x75, 0xE9, 0xBD, 0xE9, 0xBD, 0xED, 0x93, 0xE9, 0xBD, 0xE9, 0xBD, 0xE9, 0xBD, 0xE9, 0xBD,
|
||||
0x22, 0x65, 0x6F, 0xE7, 0xEA, 0xA1, 0x00, 0xE1, 0x70, 0xFB, 0x47, 0x61, 0xC3, 0x65, 0x69, 0x6F, 0x75, 0x79, 0xF1,
|
||||
0x9C, 0xFF, 0xAB, 0xF6, 0x71, 0xF4, 0xCA, 0xF1, 0x9C, 0xFA, 0x8F, 0xFF, 0xFB, 0x41, 0x76, 0xF3, 0xC0, 0x41, 0x76,
|
||||
0xE8, 0x54, 0x41, 0x78, 0xE8, 0x50, 0x22, 0x6F, 0x61, 0xF8, 0xFC, 0x21, 0x69, 0xFB, 0x41, 0x72, 0xF2, 0x20, 0x21,
|
||||
0x74, 0xFC, 0x45, 0x63, 0x65, 0x76, 0x6E, 0x73, 0xF2, 0x5E, 0xFF, 0xE5, 0xF2, 0x5E, 0xFF, 0xF6, 0xFF, 0xFD, 0x42,
|
||||
0x6E, 0x73, 0xE9, 0xBA, 0xE9, 0xBA, 0x21, 0x69, 0xF9, 0x21, 0x6C, 0xFD, 0x21, 0x6C, 0xFD, 0x21, 0x69, 0xFD, 0xC2,
|
||||
0x00, 0xE1, 0x63, 0x6E, 0xF3, 0x82, 0xFF, 0xFD, 0xC2, 0x00, 0xE1, 0x6C, 0x64, 0xF4, 0x69, 0xF9, 0xE8, 0x41, 0x74,
|
||||
0xF7, 0x1B, 0x21, 0x6F, 0xFC, 0x21, 0x70, 0xFD, 0x21, 0x69, 0xFD, 0x42, 0x72, 0x2E, 0xFF, 0xFD, 0xF2, 0x88, 0x42,
|
||||
0x69, 0x74, 0xEF, 0x79, 0xFF, 0xF9, 0xC3, 0x00, 0xE1, 0x6E, 0x2E, 0x73, 0xFF, 0xF9, 0xF2, 0x74, 0xF2, 0x77, 0x41,
|
||||
0x69, 0xE7, 0x51, 0x21, 0x6B, 0xFC, 0x21, 0x73, 0xFD, 0x21, 0x6F, 0xFD, 0xA1, 0x00, 0xE1, 0x6C, 0xFD, 0x47, 0xA2,
|
||||
0xA9, 0xA8, 0xAA, 0xAE, 0xB4, 0xBB, 0xF0, 0xFD, 0xFF, 0xFB, 0xF0, 0xFD, 0xF0, 0xFD, 0xF0, 0xFD, 0xF0, 0xFD, 0xF0,
|
||||
0xFD, 0x41, 0x6D, 0xE9, 0xDD, 0x21, 0x61, 0xFC, 0x21, 0x74, 0xFD, 0xA1, 0x00, 0xE1, 0x6C, 0xFD, 0x48, 0x61, 0x69,
|
||||
0x65, 0xC3, 0x6F, 0x72, 0x75, 0x79, 0xFF, 0x90, 0xFF, 0x99, 0xFF, 0xBD, 0xFF, 0xDB, 0xFF, 0xFB, 0xF2, 0x50, 0xF0,
|
||||
0xD8, 0xF0, 0xD8, 0xA0, 0x01, 0xD1, 0x21, 0x6E, 0xFD, 0x21, 0x6F, 0xFD, 0x42, 0x69, 0x75, 0xFF, 0xFD, 0xF0, 0xF8,
|
||||
0x41, 0x72, 0xF6, 0xE9, 0xA1, 0x00, 0xE1, 0x77, 0xFC, 0x48, 0xA2, 0xA0, 0xA9, 0xA8, 0xAA, 0xAE, 0xB4, 0xBB, 0xF0,
|
||||
0xA6, 0xF0, 0xA6, 0xF0, 0xA6, 0xF0, 0xA6, 0xF0, 0xA6, 0xF0, 0xA6, 0xF0, 0xA6, 0xF0, 0xA6, 0x41, 0x2E, 0xE6, 0x8A,
|
||||
0x21, 0x74, 0xFC, 0x21, 0x6E, 0xFD, 0x21, 0x65, 0xFD, 0x4A, 0x69, 0x6C, 0x61, 0xC3, 0x65, 0x6F, 0x73, 0x75, 0x79,
|
||||
0x6D, 0xF3, 0xAE, 0xFF, 0xCA, 0xFF, 0xD5, 0xFF, 0xDA, 0xF1, 0xE8, 0xF0, 0x80, 0xF8, 0x95, 0xF0, 0x80, 0xF0, 0x80,
|
||||
0xFF, 0xFD, 0x41, 0x6C, 0xF3, 0x8B, 0x42, 0x69, 0x65, 0xFF, 0xFC, 0xF9, 0xD3, 0xC1, 0x00, 0xE2, 0x2E, 0xF1, 0xAF,
|
||||
0x49, 0x61, 0xC3, 0x65, 0x68, 0x69, 0x6F, 0x72, 0x75, 0x79, 0xF0, 0x50, 0xF1, 0x93, 0xF1, 0xB8, 0xFF, 0xFA, 0xF0,
|
||||
0x50, 0xF0, 0x50, 0xF0, 0x6D, 0xF0, 0x50, 0xF0, 0x50, 0x42, 0x61, 0x65, 0xF0, 0x76, 0xF1, 0xA5, 0xA1, 0x00, 0xE1,
|
||||
0x75, 0xF9, 0x41, 0x69, 0xFA, 0x32, 0x21, 0x72, 0xFC, 0xA1, 0x00, 0xE1, 0x74, 0xFD, 0xA0, 0x01, 0xF2, 0x21, 0x2E,
|
||||
0xFD, 0x22, 0x2E, 0x73, 0xFA, 0xFD, 0x21, 0x74, 0xFB, 0x21, 0x61, 0xFD, 0x4A, 0x75, 0x61, 0xC3, 0x65, 0x69, 0x6F,
|
||||
0xC5, 0x73, 0x78, 0x79, 0xFF, 0xEA, 0xF0, 0x0B, 0xF1, 0x4E, 0xF1, 0x73, 0xF0, 0x0B, 0xF0, 0x0B, 0xF4, 0x0D, 0xFF,
|
||||
0xFD, 0xF8, 0x58, 0xF0, 0x0B, 0x41, 0x68, 0xF8, 0x39, 0x21, 0x74, 0xFC, 0x42, 0x73, 0x6C, 0xFF, 0xFD, 0xF8, 0x38,
|
||||
0x41, 0x6F, 0xFD, 0x5C, 0x21, 0x74, 0xFC, 0x22, 0x61, 0x73, 0xF2, 0xFD, 0x42, 0xA9, 0xA8, 0xEF, 0xD2, 0xEF, 0xD2,
|
||||
0x47, 0x61, 0x65, 0xC3, 0x69, 0x6F, 0x75, 0x79, 0xEF, 0xCB, 0xF1, 0x33, 0xFF, 0xF9, 0xEF, 0xCB, 0xEF, 0xCB, 0xEF,
|
||||
0xCB, 0xEF, 0xCB, 0x5D, 0x27, 0x2E, 0x61, 0x62, 0xC3, 0x63, 0x6A, 0x6D, 0x72, 0x70, 0x69, 0x65, 0x64, 0x74, 0x66,
|
||||
0x67, 0x73, 0x6F, 0x77, 0x68, 0x75, 0x76, 0x6C, 0x78, 0x6B, 0x71, 0x6E, 0x79, 0x7A, 0xE7, 0xD0, 0xEF, 0x48, 0xF0,
|
||||
0xCD, 0xF1, 0x53, 0xF2, 0x28, 0xF3, 0xD1, 0xF3, 0xFD, 0xF4, 0xAD, 0xF5, 0x6F, 0xF7, 0x2F, 0xF8, 0x34, 0xF8, 0x98,
|
||||
0xF9, 0x32, 0xFA, 0x80, 0xFA, 0xE4, 0xFB, 0x3C, 0xFC, 0xA4, 0xFD, 0x6C, 0xFD, 0x97, 0xFE, 0x19, 0xFE, 0x4A, 0xFE,
|
||||
0xDD, 0xFF, 0x35, 0xFF, 0x58, 0xFF, 0x65, 0xFF, 0x88, 0xFF, 0xAA, 0xFF, 0xDE, 0xFF, 0xEA,
|
||||
};
|
||||
|
||||
constexpr SerializedHyphenationPatterns fr_patterns = {
|
||||
fr_trie_data,
|
||||
sizeof(fr_trie_data),
|
||||
};
|
||||
1770
lib/Epub/Epub/hyphenation/generated/hyph-ru.trie.h
Normal file
1770
lib/Epub/Epub/hyphenation/generated/hyph-ru.trie.h
Normal file
File diff suppressed because it is too large
Load Diff
@ -88,7 +88,7 @@ void ChapterHtmlSlimParser::startNewTextBlock(const TextBlock::Style style, cons
|
||||
|
||||
makePages();
|
||||
}
|
||||
currentTextBlock.reset(new ParsedText(style, extraParagraphSpacing, blockStyle));
|
||||
currentTextBlock.reset(new ParsedText(style, extraParagraphSpacing, hyphenationEnabled, blockStyle));
|
||||
}
|
||||
|
||||
void ChapterHtmlSlimParser::startNewTextBlock(const TextBlock::Style style) { startNewTextBlock(style, BlockStyle{}); }
|
||||
@ -356,21 +356,6 @@ void XMLCALL ChapterHtmlSlimParser::characterData(void* userData, const XML_Char
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip soft-hyphen with UTF-8 representation (U+00AD) = 0xC2 0xAD
|
||||
const XML_Char SHY_BYTE_1 = static_cast<XML_Char>(0xC2);
|
||||
const XML_Char SHY_BYTE_2 = static_cast<XML_Char>(0xAD);
|
||||
// 1. Check for the start of the 2-byte Soft Hyphen sequence
|
||||
if (s[i] == SHY_BYTE_1) {
|
||||
// 2. Check if the next byte exists AND if it completes the sequence
|
||||
// We must check i + 1 < len to prevent reading past the end of the buffer.
|
||||
if ((i + 1 < len) && (s[i + 1] == SHY_BYTE_2)) {
|
||||
// Sequence 0xC2 0xAD found!
|
||||
// Skip the current byte (0xC2) and the next byte (0xAD)
|
||||
i++; // Increment 'i' one more time to skip the 0xAD byte
|
||||
continue; // Skip the rest of the loop and move to the next iteration
|
||||
}
|
||||
}
|
||||
|
||||
// Skip Zero Width No-Break Space / BOM (U+FEFF) = 0xEF 0xBB 0xBF
|
||||
const XML_Char FEFF_BYTE_1 = static_cast<XML_Char>(0xEF);
|
||||
const XML_Char FEFF_BYTE_2 = static_cast<XML_Char>(0xBB);
|
||||
|
||||
@ -39,6 +39,7 @@ class ChapterHtmlSlimParser {
|
||||
uint8_t paragraphAlignment;
|
||||
uint16_t viewportWidth;
|
||||
uint16_t viewportHeight;
|
||||
bool hyphenationEnabled;
|
||||
const CssParser* cssParser;
|
||||
|
||||
// Style tracking (replaces depth-based approach)
|
||||
@ -67,7 +68,7 @@ class ChapterHtmlSlimParser {
|
||||
explicit ChapterHtmlSlimParser(const std::string& filepath, GfxRenderer& renderer, const int fontId,
|
||||
const float lineCompression, const bool extraParagraphSpacing,
|
||||
const uint8_t paragraphAlignment, const uint16_t viewportWidth,
|
||||
const uint16_t viewportHeight,
|
||||
const uint16_t viewportHeight, const bool hyphenationEnabled,
|
||||
const std::function<void(std::unique_ptr<Page>)>& completePageFn,
|
||||
const std::function<void(int)>& progressFn = nullptr,
|
||||
const CssParser* cssParser = nullptr)
|
||||
@ -79,6 +80,7 @@ class ChapterHtmlSlimParser {
|
||||
paragraphAlignment(paragraphAlignment),
|
||||
viewportWidth(viewportWidth),
|
||||
viewportHeight(viewportHeight),
|
||||
hyphenationEnabled(hyphenationEnabled),
|
||||
completePageFn(completePageFn),
|
||||
progressFn(progressFn),
|
||||
cssParser(cssParser) {}
|
||||
|
||||
@ -108,6 +108,11 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_METADATA && strcmp(name, "dc:language") == 0) {
|
||||
self->state = IN_BOOK_LANGUAGE;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_PACKAGE && (strcmp(name, "manifest") == 0 || strcmp(name, "opf:manifest") == 0)) {
|
||||
self->state = IN_MANIFEST;
|
||||
if (!SdMan.openFileForWrite("COF", self->cachePath + itemCacheFile, self->tempItemStore)) {
|
||||
@ -272,6 +277,11 @@ void XMLCALL ContentOpfParser::characterData(void* userData, const XML_Char* s,
|
||||
self->author.append(s, len);
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_BOOK_LANGUAGE) {
|
||||
self->language.append(s, len);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void XMLCALL ContentOpfParser::endElement(void* userData, const XML_Char* name) {
|
||||
@ -306,6 +316,11 @@ void XMLCALL ContentOpfParser::endElement(void* userData, const XML_Char* name)
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_BOOK_LANGUAGE && strcmp(name, "dc:language") == 0) {
|
||||
self->state = IN_METADATA;
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->state == IN_METADATA && (strcmp(name, "metadata") == 0 || strcmp(name, "opf:metadata") == 0)) {
|
||||
self->state = IN_PACKAGE;
|
||||
return;
|
||||
|
||||
@ -15,6 +15,7 @@ class ContentOpfParser final : public Print {
|
||||
IN_METADATA,
|
||||
IN_BOOK_TITLE,
|
||||
IN_BOOK_AUTHOR,
|
||||
IN_BOOK_LANGUAGE,
|
||||
IN_MANIFEST,
|
||||
IN_SPINE,
|
||||
IN_GUIDE,
|
||||
@ -36,6 +37,7 @@ class ContentOpfParser final : public Print {
|
||||
public:
|
||||
std::string title;
|
||||
std::string author;
|
||||
std::string language;
|
||||
std::string tocNcxPath;
|
||||
std::string tocNavPath; // EPUB 3 nav document path
|
||||
std::string coverItemHref;
|
||||
|
||||
@ -596,7 +596,7 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y
|
||||
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) {
|
||||
const EpdGlyph* glyph = font.getGlyph(cp, style);
|
||||
if (!glyph) {
|
||||
glyph = font.getGlyph('?', style);
|
||||
glyph = font.getGlyph(REPLACEMENT_GLYPH, style);
|
||||
}
|
||||
if (!glyph) {
|
||||
continue;
|
||||
@ -774,8 +774,7 @@ void GfxRenderer::renderChar(const EpdFontFamily& fontFamily, const uint32_t cp,
|
||||
const bool pixelState, const EpdFontFamily::Style style) const {
|
||||
const EpdGlyph* glyph = fontFamily.getGlyph(cp, style);
|
||||
if (!glyph) {
|
||||
// TODO: Replace with fallback glyph property?
|
||||
glyph = fontFamily.getGlyph('?', style);
|
||||
glyph = fontFamily.getGlyph(REPLACEMENT_GLYPH, style);
|
||||
}
|
||||
|
||||
// no glyph?
|
||||
|
||||
168
lib/KOReaderSync/KOReaderCredentialStore.cpp
Normal file
168
lib/KOReaderSync/KOReaderCredentialStore.cpp
Normal file
@ -0,0 +1,168 @@
|
||||
#include "KOReaderCredentialStore.h"
|
||||
|
||||
#include <HardwareSerial.h>
|
||||
#include <MD5Builder.h>
|
||||
#include <SDCardManager.h>
|
||||
#include <Serialization.h>
|
||||
|
||||
// Initialize the static instance
|
||||
KOReaderCredentialStore KOReaderCredentialStore::instance;
|
||||
|
||||
namespace {
|
||||
// File format version
|
||||
constexpr uint8_t KOREADER_FILE_VERSION = 1;
|
||||
|
||||
// KOReader credentials file path
|
||||
constexpr char KOREADER_FILE[] = "/.crosspoint/koreader.bin";
|
||||
|
||||
// Default sync server URL
|
||||
constexpr char DEFAULT_SERVER_URL[] = "https://sync.koreader.rocks:443";
|
||||
|
||||
// Obfuscation key - "KOReader" in ASCII
|
||||
// This is NOT cryptographic security, just prevents casual file reading
|
||||
constexpr uint8_t OBFUSCATION_KEY[] = {0x4B, 0x4F, 0x52, 0x65, 0x61, 0x64, 0x65, 0x72};
|
||||
constexpr size_t KEY_LENGTH = sizeof(OBFUSCATION_KEY);
|
||||
} // namespace
|
||||
|
||||
void KOReaderCredentialStore::obfuscate(std::string& data) const {
|
||||
for (size_t i = 0; i < data.size(); i++) {
|
||||
data[i] ^= OBFUSCATION_KEY[i % KEY_LENGTH];
|
||||
}
|
||||
}
|
||||
|
||||
bool KOReaderCredentialStore::saveToFile() const {
|
||||
// Make sure the directory exists
|
||||
SdMan.mkdir("/.crosspoint");
|
||||
|
||||
FsFile file;
|
||||
if (!SdMan.openFileForWrite("KRS", KOREADER_FILE, file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write header
|
||||
serialization::writePod(file, KOREADER_FILE_VERSION);
|
||||
|
||||
// Write username (plaintext - not particularly sensitive)
|
||||
serialization::writeString(file, username);
|
||||
Serial.printf("[%lu] [KRS] Saving username: %s\n", millis(), username.c_str());
|
||||
|
||||
// Write password (obfuscated)
|
||||
std::string obfuscatedPwd = password;
|
||||
obfuscate(obfuscatedPwd);
|
||||
serialization::writeString(file, obfuscatedPwd);
|
||||
|
||||
// Write server URL
|
||||
serialization::writeString(file, serverUrl);
|
||||
|
||||
// Write match method
|
||||
serialization::writePod(file, static_cast<uint8_t>(matchMethod));
|
||||
|
||||
file.close();
|
||||
Serial.printf("[%lu] [KRS] Saved KOReader credentials to file\n", millis());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool KOReaderCredentialStore::loadFromFile() {
|
||||
FsFile file;
|
||||
if (!SdMan.openFileForRead("KRS", KOREADER_FILE, file)) {
|
||||
Serial.printf("[%lu] [KRS] No credentials file found\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read and verify version
|
||||
uint8_t version;
|
||||
serialization::readPod(file, version);
|
||||
if (version != KOREADER_FILE_VERSION) {
|
||||
Serial.printf("[%lu] [KRS] Unknown file version: %u\n", millis(), version);
|
||||
file.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read username
|
||||
if (file.available()) {
|
||||
serialization::readString(file, username);
|
||||
} else {
|
||||
username.clear();
|
||||
}
|
||||
|
||||
// Read and deobfuscate password
|
||||
if (file.available()) {
|
||||
serialization::readString(file, password);
|
||||
obfuscate(password); // XOR is symmetric, so same function deobfuscates
|
||||
} else {
|
||||
password.clear();
|
||||
}
|
||||
|
||||
// Read server URL
|
||||
if (file.available()) {
|
||||
serialization::readString(file, serverUrl);
|
||||
} else {
|
||||
serverUrl.clear();
|
||||
}
|
||||
|
||||
// Read match method
|
||||
if (file.available()) {
|
||||
uint8_t method;
|
||||
serialization::readPod(file, method);
|
||||
matchMethod = static_cast<DocumentMatchMethod>(method);
|
||||
} else {
|
||||
matchMethod = DocumentMatchMethod::FILENAME;
|
||||
}
|
||||
|
||||
file.close();
|
||||
Serial.printf("[%lu] [KRS] Loaded KOReader credentials for user: %s\n", millis(), username.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
void KOReaderCredentialStore::setCredentials(const std::string& user, const std::string& pass) {
|
||||
username = user;
|
||||
password = pass;
|
||||
Serial.printf("[%lu] [KRS] Set credentials for user: %s\n", millis(), user.c_str());
|
||||
}
|
||||
|
||||
std::string KOReaderCredentialStore::getMd5Password() const {
|
||||
if (password.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Calculate MD5 hash of password using ESP32's MD5Builder
|
||||
MD5Builder md5;
|
||||
md5.begin();
|
||||
md5.add(password.c_str());
|
||||
md5.calculate();
|
||||
|
||||
return md5.toString().c_str();
|
||||
}
|
||||
|
||||
bool KOReaderCredentialStore::hasCredentials() const { return !username.empty() && !password.empty(); }
|
||||
|
||||
void KOReaderCredentialStore::clearCredentials() {
|
||||
username.clear();
|
||||
password.clear();
|
||||
saveToFile();
|
||||
Serial.printf("[%lu] [KRS] Cleared KOReader credentials\n", millis());
|
||||
}
|
||||
|
||||
void KOReaderCredentialStore::setServerUrl(const std::string& url) {
|
||||
serverUrl = url;
|
||||
Serial.printf("[%lu] [KRS] Set server URL: %s\n", millis(), url.empty() ? "(default)" : url.c_str());
|
||||
}
|
||||
|
||||
std::string KOReaderCredentialStore::getBaseUrl() const {
|
||||
if (serverUrl.empty()) {
|
||||
return DEFAULT_SERVER_URL;
|
||||
}
|
||||
|
||||
// Normalize URL: add http:// if no protocol specified (local servers typically don't have SSL)
|
||||
if (serverUrl.find("://") == std::string::npos) {
|
||||
return "http://" + serverUrl;
|
||||
}
|
||||
|
||||
return serverUrl;
|
||||
}
|
||||
|
||||
void KOReaderCredentialStore::setMatchMethod(DocumentMatchMethod method) {
|
||||
matchMethod = method;
|
||||
Serial.printf("[%lu] [KRS] Set match method: %s\n", millis(),
|
||||
method == DocumentMatchMethod::FILENAME ? "Filename" : "Binary");
|
||||
}
|
||||
69
lib/KOReaderSync/KOReaderCredentialStore.h
Normal file
69
lib/KOReaderSync/KOReaderCredentialStore.h
Normal file
@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
// Document matching method for KOReader sync
|
||||
enum class DocumentMatchMethod : uint8_t {
|
||||
FILENAME = 0, // Match by filename (simpler, works across different file sources)
|
||||
BINARY = 1, // Match by partial MD5 of file content (more accurate, but files must be identical)
|
||||
};
|
||||
|
||||
/**
|
||||
* Singleton class for storing KOReader sync credentials on the SD card.
|
||||
* Credentials are stored in /sd/.crosspoint/koreader.bin with basic
|
||||
* XOR obfuscation to prevent casual reading (not cryptographically secure).
|
||||
*/
|
||||
class KOReaderCredentialStore {
|
||||
private:
|
||||
static KOReaderCredentialStore instance;
|
||||
std::string username;
|
||||
std::string password;
|
||||
std::string serverUrl; // Custom sync server URL (empty = default)
|
||||
DocumentMatchMethod matchMethod = DocumentMatchMethod::FILENAME; // Default to filename for compatibility
|
||||
|
||||
// Private constructor for singleton
|
||||
KOReaderCredentialStore() = default;
|
||||
|
||||
// XOR obfuscation (symmetric - same for encode/decode)
|
||||
void obfuscate(std::string& data) const;
|
||||
|
||||
public:
|
||||
// Delete copy constructor and assignment
|
||||
KOReaderCredentialStore(const KOReaderCredentialStore&) = delete;
|
||||
KOReaderCredentialStore& operator=(const KOReaderCredentialStore&) = delete;
|
||||
|
||||
// Get singleton instance
|
||||
static KOReaderCredentialStore& getInstance() { return instance; }
|
||||
|
||||
// Save/load from SD card
|
||||
bool saveToFile() const;
|
||||
bool loadFromFile();
|
||||
|
||||
// Credential management
|
||||
void setCredentials(const std::string& user, const std::string& pass);
|
||||
const std::string& getUsername() const { return username; }
|
||||
const std::string& getPassword() const { return password; }
|
||||
|
||||
// Get MD5 hash of password for API authentication
|
||||
std::string getMd5Password() const;
|
||||
|
||||
// Check if credentials are set
|
||||
bool hasCredentials() const;
|
||||
|
||||
// Clear credentials
|
||||
void clearCredentials();
|
||||
|
||||
// Server URL management
|
||||
void setServerUrl(const std::string& url);
|
||||
const std::string& getServerUrl() const { return serverUrl; }
|
||||
|
||||
// Get base URL for API calls (with http:// normalization if no protocol, falls back to default)
|
||||
std::string getBaseUrl() const;
|
||||
|
||||
// Document matching method
|
||||
void setMatchMethod(DocumentMatchMethod method);
|
||||
DocumentMatchMethod getMatchMethod() const { return matchMethod; }
|
||||
};
|
||||
|
||||
// Helper macro to access credential store
|
||||
#define KOREADER_STORE KOReaderCredentialStore::getInstance()
|
||||
96
lib/KOReaderSync/KOReaderDocumentId.cpp
Normal file
96
lib/KOReaderSync/KOReaderDocumentId.cpp
Normal file
@ -0,0 +1,96 @@
|
||||
#include "KOReaderDocumentId.h"
|
||||
|
||||
#include <HardwareSerial.h>
|
||||
#include <MD5Builder.h>
|
||||
#include <SDCardManager.h>
|
||||
|
||||
namespace {
|
||||
// Extract filename from path (everything after last '/')
|
||||
std::string getFilename(const std::string& path) {
|
||||
const size_t pos = path.rfind('/');
|
||||
if (pos == std::string::npos) {
|
||||
return path;
|
||||
}
|
||||
return path.substr(pos + 1);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::string KOReaderDocumentId::calculateFromFilename(const std::string& filePath) {
|
||||
const std::string filename = getFilename(filePath);
|
||||
if (filename.empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
MD5Builder md5;
|
||||
md5.begin();
|
||||
md5.add(filename.c_str());
|
||||
md5.calculate();
|
||||
|
||||
std::string result = md5.toString().c_str();
|
||||
Serial.printf("[%lu] [KODoc] Filename hash: %s (from '%s')\n", millis(), result.c_str(), filename.c_str());
|
||||
return result;
|
||||
}
|
||||
|
||||
size_t KOReaderDocumentId::getOffset(int i) {
|
||||
// Offset = 1024 << (2*i)
|
||||
// For i = -1: 1024 >> 2 = 256
|
||||
// For i >= 0: 1024 << (2*i)
|
||||
if (i < 0) {
|
||||
return CHUNK_SIZE >> (-2 * i);
|
||||
}
|
||||
return CHUNK_SIZE << (2 * i);
|
||||
}
|
||||
|
||||
std::string KOReaderDocumentId::calculate(const std::string& filePath) {
|
||||
FsFile file;
|
||||
if (!SdMan.openFileForRead("KODoc", filePath, file)) {
|
||||
Serial.printf("[%lu] [KODoc] Failed to open file: %s\n", millis(), filePath.c_str());
|
||||
return "";
|
||||
}
|
||||
|
||||
const size_t fileSize = file.fileSize();
|
||||
Serial.printf("[%lu] [KODoc] Calculating hash for file: %s (size: %zu)\n", millis(), filePath.c_str(), fileSize);
|
||||
|
||||
// Initialize MD5 builder
|
||||
MD5Builder md5;
|
||||
md5.begin();
|
||||
|
||||
// Buffer for reading chunks
|
||||
uint8_t buffer[CHUNK_SIZE];
|
||||
size_t totalBytesRead = 0;
|
||||
|
||||
// Read from each offset (i = -1 to 10)
|
||||
for (int i = -1; i < OFFSET_COUNT - 1; i++) {
|
||||
const size_t offset = getOffset(i);
|
||||
|
||||
// Skip if offset is beyond file size
|
||||
if (offset >= fileSize) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Seek to offset
|
||||
if (!file.seekSet(offset)) {
|
||||
Serial.printf("[%lu] [KODoc] Failed to seek to offset %zu\n", millis(), offset);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read up to CHUNK_SIZE bytes
|
||||
const size_t bytesToRead = std::min(CHUNK_SIZE, fileSize - offset);
|
||||
const size_t bytesRead = file.read(buffer, bytesToRead);
|
||||
|
||||
if (bytesRead > 0) {
|
||||
md5.add(buffer, bytesRead);
|
||||
totalBytesRead += bytesRead;
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
// Calculate final hash
|
||||
md5.calculate();
|
||||
std::string result = md5.toString().c_str();
|
||||
|
||||
Serial.printf("[%lu] [KODoc] Hash calculated: %s (from %zu bytes)\n", millis(), result.c_str(), totalBytesRead);
|
||||
|
||||
return result;
|
||||
}
|
||||
45
lib/KOReaderSync/KOReaderDocumentId.h
Normal file
45
lib/KOReaderSync/KOReaderDocumentId.h
Normal file
@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* Calculate KOReader document ID (partial MD5 hash).
|
||||
*
|
||||
* KOReader identifies documents using a partial MD5 hash of the file content.
|
||||
* The algorithm reads 1024 bytes at specific offsets and computes the MD5 hash
|
||||
* of the concatenated data.
|
||||
*
|
||||
* Offsets are calculated as: 1024 << (2*i) for i = -1 to 10
|
||||
* Producing: 256, 1024, 4096, 16384, 65536, 262144, 1048576, 4194304,
|
||||
* 16777216, 67108864, 268435456, 1073741824 bytes
|
||||
*
|
||||
* If an offset is beyond the file size, it is skipped.
|
||||
*/
|
||||
class KOReaderDocumentId {
|
||||
public:
|
||||
/**
|
||||
* Calculate the KOReader document hash for a file (binary/content-based).
|
||||
*
|
||||
* @param filePath Path to the file (typically an EPUB)
|
||||
* @return 32-character lowercase hex string, or empty string on failure
|
||||
*/
|
||||
static std::string calculate(const std::string& filePath);
|
||||
|
||||
/**
|
||||
* Calculate document hash from filename only (filename-based sync mode).
|
||||
* This is simpler and works when files have the same name across devices.
|
||||
*
|
||||
* @param filePath Path to the file (only the filename portion is used)
|
||||
* @return 32-character lowercase hex MD5 of the filename
|
||||
*/
|
||||
static std::string calculateFromFilename(const std::string& filePath);
|
||||
|
||||
private:
|
||||
// Size of each chunk to read at each offset
|
||||
static constexpr size_t CHUNK_SIZE = 1024;
|
||||
|
||||
// Number of offsets to try (i = -1 to 10, so 12 offsets)
|
||||
static constexpr int OFFSET_COUNT = 12;
|
||||
|
||||
// Calculate offset for index i: 1024 << (2*i)
|
||||
static size_t getOffset(int i);
|
||||
};
|
||||
198
lib/KOReaderSync/KOReaderSyncClient.cpp
Normal file
198
lib/KOReaderSync/KOReaderSyncClient.cpp
Normal file
@ -0,0 +1,198 @@
|
||||
#include "KOReaderSyncClient.h"
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <HTTPClient.h>
|
||||
#include <HardwareSerial.h>
|
||||
#include <WiFi.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
|
||||
#include <ctime>
|
||||
|
||||
#include "KOReaderCredentialStore.h"
|
||||
|
||||
namespace {
|
||||
// Device identifier for CrossPoint reader
|
||||
constexpr char DEVICE_NAME[] = "CrossPoint";
|
||||
constexpr char DEVICE_ID[] = "crosspoint-reader";
|
||||
|
||||
void addAuthHeaders(HTTPClient& http) {
|
||||
http.addHeader("Accept", "application/vnd.koreader.v1+json");
|
||||
http.addHeader("x-auth-user", KOREADER_STORE.getUsername().c_str());
|
||||
http.addHeader("x-auth-key", KOREADER_STORE.getMd5Password().c_str());
|
||||
}
|
||||
|
||||
bool isHttpsUrl(const std::string& url) { return url.rfind("https://", 0) == 0; }
|
||||
} // namespace
|
||||
|
||||
KOReaderSyncClient::Error KOReaderSyncClient::authenticate() {
|
||||
if (!KOREADER_STORE.hasCredentials()) {
|
||||
Serial.printf("[%lu] [KOSync] No credentials configured\n", millis());
|
||||
return NO_CREDENTIALS;
|
||||
}
|
||||
|
||||
std::string url = KOREADER_STORE.getBaseUrl() + "/users/auth";
|
||||
Serial.printf("[%lu] [KOSync] Authenticating: %s\n", millis(), url.c_str());
|
||||
|
||||
HTTPClient http;
|
||||
std::unique_ptr<WiFiClientSecure> secureClient;
|
||||
WiFiClient plainClient;
|
||||
|
||||
if (isHttpsUrl(url)) {
|
||||
secureClient.reset(new WiFiClientSecure);
|
||||
secureClient->setInsecure();
|
||||
http.begin(*secureClient, url.c_str());
|
||||
} else {
|
||||
http.begin(plainClient, url.c_str());
|
||||
}
|
||||
addAuthHeaders(http);
|
||||
|
||||
const int httpCode = http.GET();
|
||||
http.end();
|
||||
|
||||
Serial.printf("[%lu] [KOSync] Auth response: %d\n", millis(), httpCode);
|
||||
|
||||
if (httpCode == 200) {
|
||||
return OK;
|
||||
} else if (httpCode == 401) {
|
||||
return AUTH_FAILED;
|
||||
} else if (httpCode < 0) {
|
||||
return NETWORK_ERROR;
|
||||
}
|
||||
return SERVER_ERROR;
|
||||
}
|
||||
|
||||
KOReaderSyncClient::Error KOReaderSyncClient::getProgress(const std::string& documentHash,
|
||||
KOReaderProgress& outProgress) {
|
||||
if (!KOREADER_STORE.hasCredentials()) {
|
||||
Serial.printf("[%lu] [KOSync] No credentials configured\n", millis());
|
||||
return NO_CREDENTIALS;
|
||||
}
|
||||
|
||||
std::string url = KOREADER_STORE.getBaseUrl() + "/syncs/progress/" + documentHash;
|
||||
Serial.printf("[%lu] [KOSync] Getting progress: %s\n", millis(), url.c_str());
|
||||
|
||||
HTTPClient http;
|
||||
std::unique_ptr<WiFiClientSecure> secureClient;
|
||||
WiFiClient plainClient;
|
||||
|
||||
if (isHttpsUrl(url)) {
|
||||
secureClient.reset(new WiFiClientSecure);
|
||||
secureClient->setInsecure();
|
||||
http.begin(*secureClient, url.c_str());
|
||||
} else {
|
||||
http.begin(plainClient, url.c_str());
|
||||
}
|
||||
addAuthHeaders(http);
|
||||
|
||||
const int httpCode = http.GET();
|
||||
|
||||
if (httpCode == 200) {
|
||||
// Parse JSON response from response string
|
||||
String responseBody = http.getString();
|
||||
http.end();
|
||||
|
||||
JsonDocument doc;
|
||||
const DeserializationError error = deserializeJson(doc, responseBody);
|
||||
|
||||
if (error) {
|
||||
Serial.printf("[%lu] [KOSync] JSON parse failed: %s\n", millis(), error.c_str());
|
||||
return JSON_ERROR;
|
||||
}
|
||||
|
||||
outProgress.document = documentHash;
|
||||
outProgress.progress = doc["progress"].as<std::string>();
|
||||
outProgress.percentage = doc["percentage"].as<float>();
|
||||
outProgress.device = doc["device"].as<std::string>();
|
||||
outProgress.deviceId = doc["device_id"].as<std::string>();
|
||||
outProgress.timestamp = doc["timestamp"].as<int64_t>();
|
||||
|
||||
Serial.printf("[%lu] [KOSync] Got progress: %.2f%% at %s\n", millis(), outProgress.percentage * 100,
|
||||
outProgress.progress.c_str());
|
||||
return OK;
|
||||
}
|
||||
|
||||
http.end();
|
||||
|
||||
Serial.printf("[%lu] [KOSync] Get progress response: %d\n", millis(), httpCode);
|
||||
|
||||
if (httpCode == 401) {
|
||||
return AUTH_FAILED;
|
||||
} else if (httpCode == 404) {
|
||||
return NOT_FOUND;
|
||||
} else if (httpCode < 0) {
|
||||
return NETWORK_ERROR;
|
||||
}
|
||||
return SERVER_ERROR;
|
||||
}
|
||||
|
||||
KOReaderSyncClient::Error KOReaderSyncClient::updateProgress(const KOReaderProgress& progress) {
|
||||
if (!KOREADER_STORE.hasCredentials()) {
|
||||
Serial.printf("[%lu] [KOSync] No credentials configured\n", millis());
|
||||
return NO_CREDENTIALS;
|
||||
}
|
||||
|
||||
std::string url = KOREADER_STORE.getBaseUrl() + "/syncs/progress";
|
||||
Serial.printf("[%lu] [KOSync] Updating progress: %s\n", millis(), url.c_str());
|
||||
|
||||
HTTPClient http;
|
||||
std::unique_ptr<WiFiClientSecure> secureClient;
|
||||
WiFiClient plainClient;
|
||||
|
||||
if (isHttpsUrl(url)) {
|
||||
secureClient.reset(new WiFiClientSecure);
|
||||
secureClient->setInsecure();
|
||||
http.begin(*secureClient, url.c_str());
|
||||
} else {
|
||||
http.begin(plainClient, url.c_str());
|
||||
}
|
||||
addAuthHeaders(http);
|
||||
http.addHeader("Content-Type", "application/json");
|
||||
|
||||
// Build JSON body (timestamp not required per API spec)
|
||||
JsonDocument doc;
|
||||
doc["document"] = progress.document;
|
||||
doc["progress"] = progress.progress;
|
||||
doc["percentage"] = progress.percentage;
|
||||
doc["device"] = DEVICE_NAME;
|
||||
doc["device_id"] = DEVICE_ID;
|
||||
|
||||
std::string body;
|
||||
serializeJson(doc, body);
|
||||
|
||||
Serial.printf("[%lu] [KOSync] Request body: %s\n", millis(), body.c_str());
|
||||
|
||||
const int httpCode = http.PUT(body.c_str());
|
||||
http.end();
|
||||
|
||||
Serial.printf("[%lu] [KOSync] Update progress response: %d\n", millis(), httpCode);
|
||||
|
||||
if (httpCode == 200 || httpCode == 202) {
|
||||
return OK;
|
||||
} else if (httpCode == 401) {
|
||||
return AUTH_FAILED;
|
||||
} else if (httpCode < 0) {
|
||||
return NETWORK_ERROR;
|
||||
}
|
||||
return SERVER_ERROR;
|
||||
}
|
||||
|
||||
const char* KOReaderSyncClient::errorString(Error error) {
|
||||
switch (error) {
|
||||
case OK:
|
||||
return "Success";
|
||||
case NO_CREDENTIALS:
|
||||
return "No credentials configured";
|
||||
case NETWORK_ERROR:
|
||||
return "Network error";
|
||||
case AUTH_FAILED:
|
||||
return "Authentication failed";
|
||||
case SERVER_ERROR:
|
||||
return "Server error (try again later)";
|
||||
case JSON_ERROR:
|
||||
return "JSON parse error";
|
||||
case NOT_FOUND:
|
||||
return "No progress found";
|
||||
default:
|
||||
return "Unknown error";
|
||||
}
|
||||
}
|
||||
59
lib/KOReaderSync/KOReaderSyncClient.h
Normal file
59
lib/KOReaderSync/KOReaderSyncClient.h
Normal file
@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* Progress data from KOReader sync server.
|
||||
*/
|
||||
struct KOReaderProgress {
|
||||
std::string document; // Document hash
|
||||
std::string progress; // XPath-like progress string
|
||||
float percentage; // Progress percentage (0.0 to 1.0)
|
||||
std::string device; // Device name
|
||||
std::string deviceId; // Device ID
|
||||
int64_t timestamp; // Unix timestamp of last update
|
||||
};
|
||||
|
||||
/**
|
||||
* HTTP client for KOReader sync API.
|
||||
*
|
||||
* Base URL: https://sync.koreader.rocks:443/
|
||||
*
|
||||
* API Endpoints:
|
||||
* GET /users/auth - Authenticate (validate credentials)
|
||||
* GET /syncs/progress/:document - Get progress for a document
|
||||
* PUT /syncs/progress - Update progress for a document
|
||||
*
|
||||
* Authentication:
|
||||
* x-auth-user: username
|
||||
* x-auth-key: MD5 hash of password
|
||||
*/
|
||||
class KOReaderSyncClient {
|
||||
public:
|
||||
enum Error { OK = 0, NO_CREDENTIALS, NETWORK_ERROR, AUTH_FAILED, SERVER_ERROR, JSON_ERROR, NOT_FOUND };
|
||||
|
||||
/**
|
||||
* Authenticate with the sync server (validate credentials).
|
||||
* @return OK on success, error code on failure
|
||||
*/
|
||||
static Error authenticate();
|
||||
|
||||
/**
|
||||
* Get reading progress for a document.
|
||||
* @param documentHash The document hash (from KOReaderDocumentId)
|
||||
* @param outProgress Output: the progress data
|
||||
* @return OK on success, NOT_FOUND if no progress exists, error code on failure
|
||||
*/
|
||||
static Error getProgress(const std::string& documentHash, KOReaderProgress& outProgress);
|
||||
|
||||
/**
|
||||
* Update reading progress for a document.
|
||||
* @param progress The progress data to upload
|
||||
* @return OK on success, error code on failure
|
||||
*/
|
||||
static Error updateProgress(const KOReaderProgress& progress);
|
||||
|
||||
/**
|
||||
* Get human-readable error message.
|
||||
*/
|
||||
static const char* errorString(Error error);
|
||||
};
|
||||
112
lib/KOReaderSync/ProgressMapper.cpp
Normal file
112
lib/KOReaderSync/ProgressMapper.cpp
Normal file
@ -0,0 +1,112 @@
|
||||
#include "ProgressMapper.h"
|
||||
|
||||
#include <HardwareSerial.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
KOReaderPosition ProgressMapper::toKOReader(const std::shared_ptr<Epub>& epub, const CrossPointPosition& pos) {
|
||||
KOReaderPosition result;
|
||||
|
||||
// Calculate page progress within current spine item
|
||||
float intraSpineProgress = 0.0f;
|
||||
if (pos.totalPages > 0) {
|
||||
intraSpineProgress = static_cast<float>(pos.pageNumber) / static_cast<float>(pos.totalPages);
|
||||
}
|
||||
|
||||
// Calculate overall book progress (0.0-1.0)
|
||||
result.percentage = epub->calculateProgress(pos.spineIndex, intraSpineProgress);
|
||||
|
||||
// Generate XPath with estimated paragraph position based on page
|
||||
result.xpath = generateXPath(pos.spineIndex, pos.pageNumber, pos.totalPages);
|
||||
|
||||
// Get chapter info for logging
|
||||
const int tocIndex = epub->getTocIndexForSpineIndex(pos.spineIndex);
|
||||
const std::string chapterName = (tocIndex >= 0) ? epub->getTocItem(tocIndex).title : "unknown";
|
||||
|
||||
Serial.printf("[%lu] [ProgressMapper] CrossPoint -> KOReader: chapter='%s', page=%d/%d -> %.2f%% at %s\n", millis(),
|
||||
chapterName.c_str(), pos.pageNumber, pos.totalPages, result.percentage * 100, result.xpath.c_str());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
CrossPointPosition ProgressMapper::toCrossPoint(const std::shared_ptr<Epub>& epub, const KOReaderPosition& koPos,
|
||||
int totalPagesInSpine) {
|
||||
CrossPointPosition result;
|
||||
result.spineIndex = 0;
|
||||
result.pageNumber = 0;
|
||||
result.totalPages = totalPagesInSpine;
|
||||
|
||||
const size_t bookSize = epub->getBookSize();
|
||||
if (bookSize == 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// First, try to get spine index from XPath (DocFragment)
|
||||
int xpathSpineIndex = parseDocFragmentIndex(koPos.xpath);
|
||||
if (xpathSpineIndex >= 0 && xpathSpineIndex < epub->getSpineItemsCount()) {
|
||||
result.spineIndex = xpathSpineIndex;
|
||||
// When we have XPath, go to page 0 of the spine - byte-based page calculation is unreliable
|
||||
result.pageNumber = 0;
|
||||
} else {
|
||||
// Fall back to percentage-based lookup for both spine and page
|
||||
const size_t targetBytes = static_cast<size_t>(bookSize * koPos.percentage);
|
||||
|
||||
// Find the spine item that contains this byte position
|
||||
for (int i = 0; i < epub->getSpineItemsCount(); i++) {
|
||||
const size_t cumulativeSize = epub->getCumulativeSpineItemSize(i);
|
||||
if (cumulativeSize >= targetBytes) {
|
||||
result.spineIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Estimate page number within the spine item using percentage (only when no XPath)
|
||||
if (totalPagesInSpine > 0 && result.spineIndex < epub->getSpineItemsCount()) {
|
||||
const size_t prevCumSize = (result.spineIndex > 0) ? epub->getCumulativeSpineItemSize(result.spineIndex - 1) : 0;
|
||||
const size_t currentCumSize = epub->getCumulativeSpineItemSize(result.spineIndex);
|
||||
const size_t spineSize = currentCumSize - prevCumSize;
|
||||
|
||||
if (spineSize > 0) {
|
||||
const size_t bytesIntoSpine = (targetBytes > prevCumSize) ? (targetBytes - prevCumSize) : 0;
|
||||
const float intraSpineProgress = static_cast<float>(bytesIntoSpine) / static_cast<float>(spineSize);
|
||||
const float clampedProgress = std::max(0.0f, std::min(1.0f, intraSpineProgress));
|
||||
result.pageNumber = static_cast<int>(clampedProgress * totalPagesInSpine);
|
||||
result.pageNumber = std::max(0, std::min(result.pageNumber, totalPagesInSpine - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Serial.printf("[%lu] [ProgressMapper] KOReader -> CrossPoint: %.2f%% at %s -> spine=%d, page=%d\n", millis(),
|
||||
koPos.percentage * 100, koPos.xpath.c_str(), result.spineIndex, result.pageNumber);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string ProgressMapper::generateXPath(int spineIndex, int pageNumber, int totalPages) {
|
||||
// KOReader uses 1-based DocFragment indices
|
||||
// Use a simple xpath pointing to the DocFragment - KOReader will use the percentage for fine positioning
|
||||
// Avoid specifying paragraph numbers as they may not exist in the target document
|
||||
return "/body/DocFragment[" + std::to_string(spineIndex + 1) + "]/body";
|
||||
}
|
||||
|
||||
int ProgressMapper::parseDocFragmentIndex(const std::string& xpath) {
|
||||
// Look for DocFragment[N] pattern
|
||||
const size_t start = xpath.find("DocFragment[");
|
||||
if (start == std::string::npos) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const size_t numStart = start + 12; // Length of "DocFragment["
|
||||
const size_t numEnd = xpath.find(']', numStart);
|
||||
if (numEnd == std::string::npos) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
try {
|
||||
const int docFragmentIndex = std::stoi(xpath.substr(numStart, numEnd - numStart));
|
||||
// KOReader uses 1-based indices, we use 0-based
|
||||
return docFragmentIndex - 1;
|
||||
} catch (...) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
72
lib/KOReaderSync/ProgressMapper.h
Normal file
72
lib/KOReaderSync/ProgressMapper.h
Normal file
@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
#include <Epub.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* CrossPoint position representation.
|
||||
*/
|
||||
struct CrossPointPosition {
|
||||
int spineIndex; // Current spine item (chapter) index
|
||||
int pageNumber; // Current page within the spine item
|
||||
int totalPages; // Total pages in the current spine item
|
||||
};
|
||||
|
||||
/**
|
||||
* KOReader position representation.
|
||||
*/
|
||||
struct KOReaderPosition {
|
||||
std::string xpath; // XPath-like progress string
|
||||
float percentage; // Progress percentage (0.0 to 1.0)
|
||||
};
|
||||
|
||||
/**
|
||||
* Maps between CrossPoint and KOReader position formats.
|
||||
*
|
||||
* CrossPoint tracks position as (spineIndex, pageNumber).
|
||||
* KOReader uses XPath-like strings + percentage.
|
||||
*
|
||||
* Since CrossPoint discards HTML structure during parsing, we generate
|
||||
* synthetic XPath strings based on spine index, using percentage as the
|
||||
* primary sync mechanism.
|
||||
*/
|
||||
class ProgressMapper {
|
||||
public:
|
||||
/**
|
||||
* Convert CrossPoint position to KOReader format.
|
||||
*
|
||||
* @param epub The EPUB book
|
||||
* @param pos CrossPoint position
|
||||
* @return KOReader position
|
||||
*/
|
||||
static KOReaderPosition toKOReader(const std::shared_ptr<Epub>& epub, const CrossPointPosition& pos);
|
||||
|
||||
/**
|
||||
* Convert KOReader position to CrossPoint format.
|
||||
*
|
||||
* Note: The returned pageNumber may be approximate since different
|
||||
* rendering settings produce different page counts.
|
||||
*
|
||||
* @param epub The EPUB book
|
||||
* @param koPos KOReader position
|
||||
* @param totalPagesInSpine Total pages in the target spine item (for page estimation)
|
||||
* @return CrossPoint position
|
||||
*/
|
||||
static CrossPointPosition toCrossPoint(const std::shared_ptr<Epub>& epub, const KOReaderPosition& koPos,
|
||||
int totalPagesInSpine = 0);
|
||||
|
||||
private:
|
||||
/**
|
||||
* Generate XPath for KOReader compatibility.
|
||||
* Format: /body/DocFragment[spineIndex+1]/body/p[estimatedParagraph]
|
||||
* Paragraph is estimated based on page position within the chapter.
|
||||
*/
|
||||
static std::string generateXPath(int spineIndex, int pageNumber, int totalPages);
|
||||
|
||||
/**
|
||||
* Parse DocFragment index from XPath string.
|
||||
* Returns -1 if not found.
|
||||
*/
|
||||
static int parseDocFragmentIndex(const std::string& xpath);
|
||||
};
|
||||
@ -2,4 +2,6 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#define REPLACEMENT_GLYPH 0xFFFD
|
||||
|
||||
uint32_t utf8NextCodepoint(const unsigned char** string);
|
||||
|
||||
@ -203,7 +203,7 @@ bool Xtc::generateCoverBmp() const {
|
||||
coverBmp.write(reinterpret_cast<const uint8_t*>(&colorsImportant), 4);
|
||||
|
||||
// Color palette (2 colors for 1-bit)
|
||||
// XTC uses inverted polarity: 0 = black, 1 = white
|
||||
// XTC 1-bit polarity: 0 = black, 1 = white (standard BMP palette order)
|
||||
// Color 0: Black (text/foreground in XTC)
|
||||
uint8_t black[4] = {0x00, 0x00, 0x00, 0x00};
|
||||
coverBmp.write(black, 4);
|
||||
@ -506,8 +506,8 @@ bool Xtc::generateThumbBmp() const {
|
||||
// Bounds check for buffer access
|
||||
if (byteIdx < bitmapSize) {
|
||||
const uint8_t pixelBit = (pageBuffer[byteIdx] >> bitIdx) & 1;
|
||||
// XTC polarity: 1=black, 0=white
|
||||
grayValue = pixelBit ? 0 : 255;
|
||||
// XTC 1-bit polarity: 0=black, 1=white (same as BMP palette)
|
||||
grayValue = pixelBit ? 255 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
82
scripts/generate_hyphenation_trie.py
Executable file
82
scripts/generate_hyphenation_trie.py
Executable file
@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Embed hypher-generated `.bin` tries into constexpr headers."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import pathlib
|
||||
|
||||
|
||||
def _format_bytes(blob: bytes, per_line: int = 16) -> str:
|
||||
# Render the blob as a comma separated list of hex literals with consistent wrapping.
|
||||
lines = []
|
||||
for i in range(0, len(blob), per_line):
|
||||
chunk = ', '.join(f"0x{b:02X}" for b in blob[i : i + per_line])
|
||||
lines.append(f" {chunk},")
|
||||
if not lines:
|
||||
lines.append(" 0x00,")
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def _symbol_from_output(path: pathlib.Path) -> str:
|
||||
# Derive a stable C identifier from the destination header name (e.g., hyph-en.trie.h -> en).
|
||||
name = path.name
|
||||
if name.endswith('.trie.h'):
|
||||
name = name[:-7]
|
||||
if name.startswith('hyph-'):
|
||||
name = name[5:]
|
||||
name = name.replace('-', '_')
|
||||
if name.endswith('.trie'):
|
||||
name = name[:-5]
|
||||
return name
|
||||
|
||||
|
||||
def write_header(path: pathlib.Path, blob: bytes, symbol: str) -> None:
|
||||
# Emit a constexpr header containing the raw bytes plus a SerializedHyphenationPatterns descriptor.
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
data_symbol = f"{symbol}_trie_data"
|
||||
patterns_symbol = f"{symbol}_patterns"
|
||||
bytes_literal = _format_bytes(blob)
|
||||
content = f"""#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "../SerializedHyphenationTrie.h"
|
||||
|
||||
// Auto-generated by generate_hyphenation_trie.py. Do not edit manually.
|
||||
alignas(4) constexpr uint8_t {data_symbol}[] = {{
|
||||
{bytes_literal}
|
||||
}};
|
||||
|
||||
constexpr SerializedHyphenationPatterns {patterns_symbol} = {{
|
||||
{data_symbol},
|
||||
sizeof({data_symbol}),
|
||||
}};
|
||||
"""
|
||||
path.write_text(content)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--input', dest='inputs', action='append', required=True,
|
||||
help='Path to a hypher-generated .bin trie')
|
||||
parser.add_argument('--output', dest='outputs', action='append', required=True,
|
||||
help='Destination header path (hyph-*.trie.h)')
|
||||
args = parser.parse_args()
|
||||
|
||||
if len(args.inputs) != len(args.outputs):
|
||||
raise SystemExit('input/output counts must match')
|
||||
|
||||
for src, dst in zip(args.inputs, args.outputs):
|
||||
# Process each input/output pair independently so mixed-language refreshes work in one invocation.
|
||||
src_path = pathlib.Path(src)
|
||||
blob = src_path.read_bytes()
|
||||
out_path = pathlib.Path(dst)
|
||||
symbol = _symbol_from_output(out_path)
|
||||
write_header(out_path, blob, symbol)
|
||||
print(f'wrote {dst} ({len(blob)} bytes payload)')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@ -14,7 +14,7 @@ CrossPointSettings CrossPointSettings::instance;
|
||||
namespace {
|
||||
constexpr uint8_t SETTINGS_FILE_VERSION = 1;
|
||||
// Increment this when adding new persisted settings fields
|
||||
constexpr uint8_t SETTINGS_COUNT = 18;
|
||||
constexpr uint8_t SETTINGS_COUNT = 20;
|
||||
constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
|
||||
} // namespace
|
||||
|
||||
@ -48,6 +48,7 @@ bool CrossPointSettings::saveToFile() const {
|
||||
serialization::writePod(outputFile, textAntiAliasing);
|
||||
serialization::writePod(outputFile, hideBatteryPercentage);
|
||||
serialization::writePod(outputFile, longPressChapterSkip);
|
||||
serialization::writePod(outputFile, hyphenationEnabled);
|
||||
outputFile.close();
|
||||
|
||||
Serial.printf("[%lu] [CPS] Settings saved to file\n", millis());
|
||||
@ -110,12 +111,15 @@ bool CrossPointSettings::loadFromFile() {
|
||||
strncpy(opdsServerUrl, urlStr.c_str(), sizeof(opdsServerUrl) - 1);
|
||||
opdsServerUrl[sizeof(opdsServerUrl) - 1] = '\0';
|
||||
}
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
serialization::readPod(inputFile, textAntiAliasing);
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
serialization::readPod(inputFile, hideBatteryPercentage);
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
serialization::readPod(inputFile, longPressChapterSkip);
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
serialization::readPod(inputFile, hyphenationEnabled);
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
} while (false);
|
||||
|
||||
inputFile.close();
|
||||
|
||||
@ -84,6 +84,8 @@ class CrossPointSettings {
|
||||
uint8_t sleepTimeout = SLEEP_10_MIN;
|
||||
// E-ink refresh frequency (default 15 pages)
|
||||
uint8_t refreshFrequency = REFRESH_15;
|
||||
uint8_t hyphenationEnabled = 0;
|
||||
|
||||
// Reader screen margin settings
|
||||
uint8_t screenMargin = 5;
|
||||
// OPDS browser settings
|
||||
|
||||
@ -325,6 +325,10 @@ void HomeActivity::render() {
|
||||
}
|
||||
|
||||
if (hasContinueReading) {
|
||||
// Invert text colors based on selection state:
|
||||
// - With cover: selected = white text on black box, unselected = black text on white box
|
||||
// - Without cover: selected = white text on black card, unselected = black text on white card
|
||||
|
||||
// Split into words (avoid stringstream to keep this light on the MCU)
|
||||
std::vector<std::string> words;
|
||||
words.reserve(8);
|
||||
@ -407,7 +411,7 @@ void HomeActivity::render() {
|
||||
// Vertically center the title block within the card
|
||||
int titleYStart = bookY + (bookHeight - totalTextHeight) / 2;
|
||||
|
||||
// If cover image was rendered, draw white box behind title and author
|
||||
// If cover image was rendered, draw box behind title and author
|
||||
if (coverRendered) {
|
||||
constexpr int boxPadding = 8;
|
||||
// Calculate the max text width for the box
|
||||
@ -438,14 +442,14 @@ void HomeActivity::render() {
|
||||
const int boxX = (pageWidth - boxWidth) / 2;
|
||||
const int boxY = titleYStart - boxPadding;
|
||||
|
||||
// Draw white filled box
|
||||
renderer.fillRect(boxX, boxY, boxWidth, boxHeight, false);
|
||||
// Draw black border around the box
|
||||
renderer.drawRect(boxX, boxY, boxWidth, boxHeight, true);
|
||||
// Draw box (inverted when selected: black box instead of white)
|
||||
renderer.fillRect(boxX, boxY, boxWidth, boxHeight, bookSelected);
|
||||
// Draw border around the box (inverted when selected: white border instead of black)
|
||||
renderer.drawRect(boxX, boxY, boxWidth, boxHeight, !bookSelected);
|
||||
}
|
||||
|
||||
for (const auto& line : lines) {
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, titleYStart, line.c_str(), !bookSelected || coverRendered);
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, titleYStart, line.c_str(), !bookSelected);
|
||||
titleYStart += renderer.getLineHeight(UI_12_FONT_ID);
|
||||
}
|
||||
|
||||
@ -466,13 +470,13 @@ void HomeActivity::render() {
|
||||
}
|
||||
trimmedAuthor.append("...");
|
||||
}
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, titleYStart, trimmedAuthor.c_str(), !bookSelected || coverRendered);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, titleYStart, trimmedAuthor.c_str(), !bookSelected);
|
||||
}
|
||||
|
||||
// "Continue Reading" label at the bottom
|
||||
const int continueY = bookY + bookHeight - renderer.getLineHeight(UI_10_FONT_ID) * 3 / 2;
|
||||
if (coverRendered) {
|
||||
// Draw white box behind "Continue Reading" text
|
||||
// Draw box behind "Continue Reading" text (inverted when selected: black box instead of white)
|
||||
const char* continueText = "Continue Reading";
|
||||
const int continueTextWidth = renderer.getTextWidth(UI_10_FONT_ID, continueText);
|
||||
constexpr int continuePadding = 6;
|
||||
@ -480,9 +484,9 @@ void HomeActivity::render() {
|
||||
const int continueBoxHeight = renderer.getLineHeight(UI_10_FONT_ID) + continuePadding;
|
||||
const int continueBoxX = (pageWidth - continueBoxWidth) / 2;
|
||||
const int continueBoxY = continueY - continuePadding / 2;
|
||||
renderer.fillRect(continueBoxX, continueBoxY, continueBoxWidth, continueBoxHeight, false);
|
||||
renderer.drawRect(continueBoxX, continueBoxY, continueBoxWidth, continueBoxHeight, true);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, continueY, continueText, true);
|
||||
renderer.fillRect(continueBoxX, continueBoxY, continueBoxWidth, continueBoxHeight, bookSelected);
|
||||
renderer.drawRect(continueBoxX, continueBoxY, continueBoxWidth, continueBoxHeight, !bookSelected);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, continueY, continueText, !bookSelected);
|
||||
} else {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, continueY, "Continue Reading", !bookSelected);
|
||||
}
|
||||
|
||||
@ -118,9 +118,11 @@ void EpubReaderActivity::loop() {
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||
// Don't start activity transition while rendering
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
const int currentPage = section ? section->currentPage : 0;
|
||||
const int totalPages = section ? section->pageCount : 0;
|
||||
exitActivity();
|
||||
enterNewActivity(new EpubReaderChapterSelectionActivity(
|
||||
this->renderer, this->mappedInput, epub, currentSpineIndex,
|
||||
this->renderer, this->mappedInput, epub, epub->getPath(), currentSpineIndex, currentPage, totalPages,
|
||||
[this] {
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
@ -133,6 +135,16 @@ void EpubReaderActivity::loop() {
|
||||
}
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
},
|
||||
[this](const int newSpineIndex, const int newPage) {
|
||||
// Handle sync position
|
||||
if (currentSpineIndex != newSpineIndex || (section && section->currentPage != newPage)) {
|
||||
currentSpineIndex = newSpineIndex;
|
||||
nextPageNumber = newPage;
|
||||
section.reset();
|
||||
}
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
}));
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
@ -268,7 +280,7 @@ void EpubReaderActivity::renderScreen() {
|
||||
|
||||
if (!section->loadSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(),
|
||||
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
|
||||
viewportHeight)) {
|
||||
viewportHeight, SETTINGS.hyphenationEnabled)) {
|
||||
Serial.printf("[%lu] [ERS] Cache not found, building...\n", millis());
|
||||
|
||||
// Progress bar dimensions
|
||||
@ -313,7 +325,7 @@ void EpubReaderActivity::renderScreen() {
|
||||
|
||||
if (!section->createSectionFile(SETTINGS.getReaderFontId(), SETTINGS.getReaderLineCompression(),
|
||||
SETTINGS.extraParagraphSpacing, SETTINGS.paragraphAlignment, viewportWidth,
|
||||
viewportHeight, progressSetup, progressCallback)) {
|
||||
viewportHeight, SETTINGS.hyphenationEnabled, progressSetup, progressCallback)) {
|
||||
Serial.printf("[%lu] [ERS] Failed to persist page data to SD\n", millis());
|
||||
section.reset();
|
||||
return;
|
||||
@ -430,11 +442,13 @@ void EpubReaderActivity::renderStatusBar(const int orientedMarginRight, const in
|
||||
if (showProgress) {
|
||||
// Calculate progress in book
|
||||
const float sectionChapterProg = static_cast<float>(section->currentPage) / section->pageCount;
|
||||
const uint8_t bookProgress = epub->calculateProgress(currentSpineIndex, sectionChapterProg);
|
||||
const float bookProgress = epub->calculateProgress(currentSpineIndex, sectionChapterProg) * 100;
|
||||
|
||||
// Right aligned text for progress counter
|
||||
const std::string progress = std::to_string(section->currentPage + 1) + "/" + std::to_string(section->pageCount) +
|
||||
" " + std::to_string(bookProgress) + "%";
|
||||
char progressStr[32];
|
||||
snprintf(progressStr, sizeof(progressStr), "%d/%d %.1f%%", section->currentPage + 1, section->pageCount,
|
||||
bookProgress);
|
||||
const std::string progress = progressStr;
|
||||
progressTextWidth = renderer.getTextWidth(SMALL_FONT_ID, progress.c_str());
|
||||
renderer.drawText(SMALL_FONT_ID, renderer.getScreenWidth() - orientedMarginRight - progressTextWidth, textY,
|
||||
progress.c_str());
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
|
||||
#include "KOReaderCredentialStore.h"
|
||||
#include "KOReaderSyncActivity.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "fontIds.h"
|
||||
|
||||
@ -10,6 +12,26 @@ namespace {
|
||||
constexpr int SKIP_PAGE_MS = 700;
|
||||
} // namespace
|
||||
|
||||
bool EpubReaderChapterSelectionActivity::hasSyncOption() const { return KOREADER_STORE.hasCredentials(); }
|
||||
|
||||
int EpubReaderChapterSelectionActivity::getTotalItems() const {
|
||||
// Add 2 for sync options (top and bottom) if credentials are configured
|
||||
const int syncCount = hasSyncOption() ? 2 : 0;
|
||||
return epub->getTocItemsCount() + syncCount;
|
||||
}
|
||||
|
||||
bool EpubReaderChapterSelectionActivity::isSyncItem(int index) const {
|
||||
if (!hasSyncOption()) return false;
|
||||
// First item and last item are sync options
|
||||
return index == 0 || index == getTotalItems() - 1;
|
||||
}
|
||||
|
||||
int EpubReaderChapterSelectionActivity::tocIndexFromItemIndex(int itemIndex) const {
|
||||
// Account for the sync option at the top
|
||||
const int offset = hasSyncOption() ? 1 : 0;
|
||||
return itemIndex - offset;
|
||||
}
|
||||
|
||||
int EpubReaderChapterSelectionActivity::getPageItems() const {
|
||||
// Layout constants used in renderScreen
|
||||
constexpr int startY = 60;
|
||||
@ -34,17 +56,21 @@ void EpubReaderChapterSelectionActivity::taskTrampoline(void* param) {
|
||||
}
|
||||
|
||||
void EpubReaderChapterSelectionActivity::onEnter() {
|
||||
Activity::onEnter();
|
||||
ActivityWithSubactivity::onEnter();
|
||||
|
||||
if (!epub) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderingMutex = xSemaphoreCreateMutex();
|
||||
|
||||
// Account for sync option offset when finding current TOC index
|
||||
const int syncOffset = hasSyncOption() ? 1 : 0;
|
||||
selectorIndex = epub->getTocIndexForSpineIndex(currentSpineIndex);
|
||||
if (selectorIndex == -1) {
|
||||
selectorIndex = 0;
|
||||
}
|
||||
selectorIndex += syncOffset; // Offset for top sync option
|
||||
|
||||
// Trigger first update
|
||||
updateRequired = true;
|
||||
@ -57,7 +83,7 @@ void EpubReaderChapterSelectionActivity::onEnter() {
|
||||
}
|
||||
|
||||
void EpubReaderChapterSelectionActivity::onExit() {
|
||||
Activity::onExit();
|
||||
ActivityWithSubactivity::onExit();
|
||||
|
||||
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
@ -69,7 +95,30 @@ void EpubReaderChapterSelectionActivity::onExit() {
|
||||
renderingMutex = nullptr;
|
||||
}
|
||||
|
||||
void EpubReaderChapterSelectionActivity::launchSyncActivity() {
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
exitActivity();
|
||||
enterNewActivity(new KOReaderSyncActivity(
|
||||
renderer, mappedInput, epub, epubPath, currentSpineIndex, currentPage, totalPagesInSpine,
|
||||
[this]() {
|
||||
// On cancel
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
},
|
||||
[this](int newSpineIndex, int newPage) {
|
||||
// On sync complete
|
||||
exitActivity();
|
||||
onSyncPosition(newSpineIndex, newPage);
|
||||
}));
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
|
||||
void EpubReaderChapterSelectionActivity::loop() {
|
||||
if (subActivity) {
|
||||
subActivity->loop();
|
||||
return;
|
||||
}
|
||||
|
||||
const bool prevReleased = mappedInput.wasReleased(MappedInputManager::Button::Up) ||
|
||||
mappedInput.wasReleased(MappedInputManager::Button::Left);
|
||||
const bool nextReleased = mappedInput.wasReleased(MappedInputManager::Button::Down) ||
|
||||
@ -77,9 +126,18 @@ void EpubReaderChapterSelectionActivity::loop() {
|
||||
|
||||
const bool skipPage = mappedInput.getHeldTime() > SKIP_PAGE_MS;
|
||||
const int pageItems = getPageItems();
|
||||
const int totalItems = getTotalItems();
|
||||
|
||||
if (mappedInput.wasReleased(MappedInputManager::Button::Confirm)) {
|
||||
const auto newSpineIndex = epub->getSpineIndexForTocIndex(selectorIndex);
|
||||
// Check if sync option is selected (first or last item)
|
||||
if (isSyncItem(selectorIndex)) {
|
||||
launchSyncActivity();
|
||||
return;
|
||||
}
|
||||
|
||||
// Get TOC index (account for top sync offset)
|
||||
const int tocIndex = tocIndexFromItemIndex(selectorIndex);
|
||||
const auto newSpineIndex = epub->getSpineIndexForTocIndex(tocIndex);
|
||||
if (newSpineIndex == -1) {
|
||||
onGoBack();
|
||||
} else {
|
||||
@ -89,17 +147,16 @@ void EpubReaderChapterSelectionActivity::loop() {
|
||||
onGoBack();
|
||||
} else if (prevReleased) {
|
||||
if (skipPage) {
|
||||
selectorIndex =
|
||||
((selectorIndex / pageItems - 1) * pageItems + epub->getTocItemsCount()) % epub->getTocItemsCount();
|
||||
selectorIndex = ((selectorIndex / pageItems - 1) * pageItems + totalItems) % totalItems;
|
||||
} else {
|
||||
selectorIndex = (selectorIndex + epub->getTocItemsCount() - 1) % epub->getTocItemsCount();
|
||||
selectorIndex = (selectorIndex + totalItems - 1) % totalItems;
|
||||
}
|
||||
updateRequired = true;
|
||||
} else if (nextReleased) {
|
||||
if (skipPage) {
|
||||
selectorIndex = ((selectorIndex / pageItems + 1) * pageItems) % epub->getTocItemsCount();
|
||||
selectorIndex = ((selectorIndex / pageItems + 1) * pageItems) % totalItems;
|
||||
} else {
|
||||
selectorIndex = (selectorIndex + 1) % epub->getTocItemsCount();
|
||||
selectorIndex = (selectorIndex + 1) % totalItems;
|
||||
}
|
||||
updateRequired = true;
|
||||
}
|
||||
@ -107,7 +164,7 @@ void EpubReaderChapterSelectionActivity::loop() {
|
||||
|
||||
void EpubReaderChapterSelectionActivity::displayTaskLoop() {
|
||||
while (true) {
|
||||
if (updateRequired) {
|
||||
if (updateRequired && !subActivity) {
|
||||
updateRequired = false;
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
renderScreen();
|
||||
@ -122,6 +179,7 @@ void EpubReaderChapterSelectionActivity::renderScreen() {
|
||||
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
const int pageItems = getPageItems();
|
||||
const int totalItems = getTotalItems();
|
||||
|
||||
const std::string title =
|
||||
renderer.truncatedText(UI_12_FONT_ID, epub->getTitle().c_str(), pageWidth - 40, EpdFontFamily::BOLD);
|
||||
@ -129,11 +187,24 @@ void EpubReaderChapterSelectionActivity::renderScreen() {
|
||||
|
||||
const auto pageStartIndex = selectorIndex / pageItems * pageItems;
|
||||
renderer.fillRect(0, 60 + (selectorIndex % pageItems) * 30 - 2, pageWidth - 1, 30);
|
||||
for (int tocIndex = pageStartIndex; tocIndex < epub->getTocItemsCount() && tocIndex < pageStartIndex + pageItems;
|
||||
tocIndex++) {
|
||||
auto item = epub->getTocItem(tocIndex);
|
||||
renderer.drawText(UI_10_FONT_ID, 20 + (item.level - 1) * 15, 60 + (tocIndex % pageItems) * 30, item.title.c_str(),
|
||||
tocIndex != selectorIndex);
|
||||
|
||||
for (int itemIndex = pageStartIndex; itemIndex < totalItems && itemIndex < pageStartIndex + pageItems; itemIndex++) {
|
||||
const int displayY = 60 + (itemIndex % pageItems) * 30;
|
||||
const bool isSelected = (itemIndex == selectorIndex);
|
||||
|
||||
if (isSyncItem(itemIndex)) {
|
||||
// Draw sync option (at top or bottom)
|
||||
renderer.drawText(UI_10_FONT_ID, 20, displayY, ">> Sync Progress", !isSelected);
|
||||
} else {
|
||||
// Draw TOC item (account for top sync offset)
|
||||
const int tocIndex = tocIndexFromItemIndex(itemIndex);
|
||||
auto item = epub->getTocItem(tocIndex);
|
||||
const int indentSize = 20 + (item.level - 1) * 15;
|
||||
const std::string chapterName =
|
||||
renderer.truncatedText(UI_10_FONT_ID, item.title.c_str(), pageWidth - 40 - indentSize);
|
||||
renderer.drawText(UI_10_FONT_ID, indentSize, 60 + (tocIndex % pageItems) * 30, chapterName.c_str(),
|
||||
tocIndex != selectorIndex);
|
||||
}
|
||||
}
|
||||
|
||||
const auto labels = mappedInput.mapLabels("« Back", "Select", "Up", "Down");
|
||||
|
||||
@ -6,36 +6,59 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "../Activity.h"
|
||||
#include "../ActivityWithSubactivity.h"
|
||||
|
||||
class EpubReaderChapterSelectionActivity final : public Activity {
|
||||
class EpubReaderChapterSelectionActivity final : public ActivityWithSubactivity {
|
||||
std::shared_ptr<Epub> epub;
|
||||
std::string epubPath;
|
||||
TaskHandle_t displayTaskHandle = nullptr;
|
||||
SemaphoreHandle_t renderingMutex = nullptr;
|
||||
int currentSpineIndex = 0;
|
||||
int currentPage = 0;
|
||||
int totalPagesInSpine = 0;
|
||||
int selectorIndex = 0;
|
||||
bool updateRequired = false;
|
||||
const std::function<void()> onGoBack;
|
||||
const std::function<void(int newSpineIndex)> onSelectSpineIndex;
|
||||
const std::function<void(int newSpineIndex, int newPage)> onSyncPosition;
|
||||
|
||||
// Number of items that fit on a page, derived from logical screen height.
|
||||
// This adapts automatically when switching between portrait and landscape.
|
||||
int getPageItems() const;
|
||||
|
||||
// Total items including sync options (top and bottom)
|
||||
int getTotalItems() const;
|
||||
|
||||
// Check if sync option is available (credentials configured)
|
||||
bool hasSyncOption() const;
|
||||
|
||||
// Check if given item index is a sync option (first or last)
|
||||
bool isSyncItem(int index) const;
|
||||
|
||||
// Convert item index to TOC index (accounting for top sync option offset)
|
||||
int tocIndexFromItemIndex(int itemIndex) const;
|
||||
|
||||
static void taskTrampoline(void* param);
|
||||
[[noreturn]] void displayTaskLoop();
|
||||
void renderScreen();
|
||||
void launchSyncActivity();
|
||||
|
||||
public:
|
||||
explicit EpubReaderChapterSelectionActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::shared_ptr<Epub>& epub, const int currentSpineIndex,
|
||||
const std::function<void()>& onGoBack,
|
||||
const std::function<void(int newSpineIndex)>& onSelectSpineIndex)
|
||||
: Activity("EpubReaderChapterSelection", renderer, mappedInput),
|
||||
const std::shared_ptr<Epub>& epub, const std::string& epubPath,
|
||||
const int currentSpineIndex, const int currentPage,
|
||||
const int totalPagesInSpine, const std::function<void()>& onGoBack,
|
||||
const std::function<void(int newSpineIndex)>& onSelectSpineIndex,
|
||||
const std::function<void(int newSpineIndex, int newPage)>& onSyncPosition)
|
||||
: ActivityWithSubactivity("EpubReaderChapterSelection", renderer, mappedInput),
|
||||
epub(epub),
|
||||
epubPath(epubPath),
|
||||
currentSpineIndex(currentSpineIndex),
|
||||
currentPage(currentPage),
|
||||
totalPagesInSpine(totalPagesInSpine),
|
||||
onGoBack(onGoBack),
|
||||
onSelectSpineIndex(onSelectSpineIndex) {}
|
||||
onSelectSpineIndex(onSelectSpineIndex),
|
||||
onSyncPosition(onSyncPosition) {}
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
|
||||
439
src/activities/reader/KOReaderSyncActivity.cpp
Normal file
439
src/activities/reader/KOReaderSyncActivity.cpp
Normal file
@ -0,0 +1,439 @@
|
||||
#include "KOReaderSyncActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <WiFi.h>
|
||||
#include <esp_sntp.h>
|
||||
|
||||
#include "KOReaderCredentialStore.h"
|
||||
#include "KOReaderDocumentId.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "activities/network/WifiSelectionActivity.h"
|
||||
#include "fontIds.h"
|
||||
|
||||
namespace {
|
||||
void syncTimeWithNTP() {
|
||||
// Stop SNTP if already running (can't reconfigure while running)
|
||||
if (esp_sntp_enabled()) {
|
||||
esp_sntp_stop();
|
||||
}
|
||||
|
||||
// Configure SNTP
|
||||
esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
|
||||
esp_sntp_setservername(0, "pool.ntp.org");
|
||||
esp_sntp_init();
|
||||
|
||||
// Wait for time to sync (with timeout)
|
||||
int retry = 0;
|
||||
const int maxRetries = 50; // 5 seconds max
|
||||
while (sntp_get_sync_status() != SNTP_SYNC_STATUS_COMPLETED && retry < maxRetries) {
|
||||
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||
retry++;
|
||||
}
|
||||
|
||||
if (retry < maxRetries) {
|
||||
Serial.printf("[%lu] [KOSync] NTP time synced\n", millis());
|
||||
} else {
|
||||
Serial.printf("[%lu] [KOSync] NTP sync timeout, using fallback\n", millis());
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void KOReaderSyncActivity::taskTrampoline(void* param) {
|
||||
auto* self = static_cast<KOReaderSyncActivity*>(param);
|
||||
self->displayTaskLoop();
|
||||
}
|
||||
|
||||
void KOReaderSyncActivity::onWifiSelectionComplete(const bool success) {
|
||||
exitActivity();
|
||||
|
||||
if (!success) {
|
||||
Serial.printf("[%lu] [KOSync] WiFi connection failed, exiting\n", millis());
|
||||
onCancel();
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.printf("[%lu] [KOSync] WiFi connected, starting sync\n", millis());
|
||||
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
state = SYNCING;
|
||||
statusMessage = "Syncing time...";
|
||||
xSemaphoreGive(renderingMutex);
|
||||
updateRequired = true;
|
||||
|
||||
// Sync time with NTP before making API requests
|
||||
syncTimeWithNTP();
|
||||
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
statusMessage = "Calculating document hash...";
|
||||
xSemaphoreGive(renderingMutex);
|
||||
updateRequired = true;
|
||||
|
||||
performSync();
|
||||
}
|
||||
|
||||
void KOReaderSyncActivity::performSync() {
|
||||
// Calculate document hash based on user's preferred method
|
||||
if (KOREADER_STORE.getMatchMethod() == DocumentMatchMethod::FILENAME) {
|
||||
documentHash = KOReaderDocumentId::calculateFromFilename(epubPath);
|
||||
} else {
|
||||
documentHash = KOReaderDocumentId::calculate(epubPath);
|
||||
}
|
||||
if (documentHash.empty()) {
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
state = SYNC_FAILED;
|
||||
statusMessage = "Failed to calculate document hash";
|
||||
xSemaphoreGive(renderingMutex);
|
||||
updateRequired = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.printf("[%lu] [KOSync] Document hash: %s\n", millis(), documentHash.c_str());
|
||||
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
statusMessage = "Fetching remote progress...";
|
||||
xSemaphoreGive(renderingMutex);
|
||||
updateRequired = true;
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
|
||||
// Fetch remote progress
|
||||
const auto result = KOReaderSyncClient::getProgress(documentHash, remoteProgress);
|
||||
|
||||
if (result == KOReaderSyncClient::NOT_FOUND) {
|
||||
// No remote progress - offer to upload
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
state = NO_REMOTE_PROGRESS;
|
||||
hasRemoteProgress = false;
|
||||
xSemaphoreGive(renderingMutex);
|
||||
updateRequired = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (result != KOReaderSyncClient::OK) {
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
state = SYNC_FAILED;
|
||||
statusMessage = KOReaderSyncClient::errorString(result);
|
||||
xSemaphoreGive(renderingMutex);
|
||||
updateRequired = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert remote progress to CrossPoint position
|
||||
hasRemoteProgress = true;
|
||||
KOReaderPosition koPos = {remoteProgress.progress, remoteProgress.percentage};
|
||||
remotePosition = ProgressMapper::toCrossPoint(epub, koPos, totalPagesInSpine);
|
||||
|
||||
// Calculate local progress in KOReader format (for display)
|
||||
CrossPointPosition localPos = {currentSpineIndex, currentPage, totalPagesInSpine};
|
||||
localProgress = ProgressMapper::toKOReader(epub, localPos);
|
||||
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
state = SHOWING_RESULT;
|
||||
selectedOption = 0; // Default to "Apply"
|
||||
xSemaphoreGive(renderingMutex);
|
||||
updateRequired = true;
|
||||
}
|
||||
|
||||
void KOReaderSyncActivity::performUpload() {
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
state = UPLOADING;
|
||||
statusMessage = "Uploading progress...";
|
||||
xSemaphoreGive(renderingMutex);
|
||||
updateRequired = true;
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
|
||||
// Convert current position to KOReader format
|
||||
CrossPointPosition localPos = {currentSpineIndex, currentPage, totalPagesInSpine};
|
||||
KOReaderPosition koPos = ProgressMapper::toKOReader(epub, localPos);
|
||||
|
||||
KOReaderProgress progress;
|
||||
progress.document = documentHash;
|
||||
progress.progress = koPos.xpath;
|
||||
progress.percentage = koPos.percentage;
|
||||
|
||||
const auto result = KOReaderSyncClient::updateProgress(progress);
|
||||
|
||||
if (result != KOReaderSyncClient::OK) {
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
state = SYNC_FAILED;
|
||||
statusMessage = KOReaderSyncClient::errorString(result);
|
||||
xSemaphoreGive(renderingMutex);
|
||||
updateRequired = true;
|
||||
return;
|
||||
}
|
||||
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
state = UPLOAD_COMPLETE;
|
||||
xSemaphoreGive(renderingMutex);
|
||||
updateRequired = true;
|
||||
}
|
||||
|
||||
void KOReaderSyncActivity::onEnter() {
|
||||
ActivityWithSubactivity::onEnter();
|
||||
|
||||
renderingMutex = xSemaphoreCreateMutex();
|
||||
|
||||
xTaskCreate(&KOReaderSyncActivity::taskTrampoline, "KOSyncTask",
|
||||
4096, // Stack size (larger for network operations)
|
||||
this, // Parameters
|
||||
1, // Priority
|
||||
&displayTaskHandle // Task handle
|
||||
);
|
||||
|
||||
// Check for credentials first
|
||||
if (!KOREADER_STORE.hasCredentials()) {
|
||||
state = NO_CREDENTIALS;
|
||||
updateRequired = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Turn on WiFi
|
||||
Serial.printf("[%lu] [KOSync] Turning on WiFi...\n", millis());
|
||||
WiFi.mode(WIFI_STA);
|
||||
|
||||
// Check if already connected
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
Serial.printf("[%lu] [KOSync] Already connected to WiFi\n", millis());
|
||||
state = SYNCING;
|
||||
statusMessage = "Syncing time...";
|
||||
updateRequired = true;
|
||||
|
||||
// Perform sync directly (will be handled in loop)
|
||||
xTaskCreate(
|
||||
[](void* param) {
|
||||
auto* self = static_cast<KOReaderSyncActivity*>(param);
|
||||
// Sync time first
|
||||
syncTimeWithNTP();
|
||||
xSemaphoreTake(self->renderingMutex, portMAX_DELAY);
|
||||
self->statusMessage = "Calculating document hash...";
|
||||
xSemaphoreGive(self->renderingMutex);
|
||||
self->updateRequired = true;
|
||||
self->performSync();
|
||||
vTaskDelete(nullptr);
|
||||
},
|
||||
"SyncTask", 4096, this, 1, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
// Launch WiFi selection subactivity
|
||||
Serial.printf("[%lu] [KOSync] Launching WifiSelectionActivity...\n", millis());
|
||||
enterNewActivity(new WifiSelectionActivity(renderer, mappedInput,
|
||||
[this](const bool connected) { onWifiSelectionComplete(connected); }));
|
||||
}
|
||||
|
||||
void KOReaderSyncActivity::onExit() {
|
||||
ActivityWithSubactivity::onExit();
|
||||
|
||||
// Turn off wifi
|
||||
WiFi.disconnect(false);
|
||||
delay(100);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
delay(100);
|
||||
|
||||
// Wait until not rendering to delete task
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
if (displayTaskHandle) {
|
||||
vTaskDelete(displayTaskHandle);
|
||||
displayTaskHandle = nullptr;
|
||||
}
|
||||
vSemaphoreDelete(renderingMutex);
|
||||
renderingMutex = nullptr;
|
||||
}
|
||||
|
||||
void KOReaderSyncActivity::displayTaskLoop() {
|
||||
while (true) {
|
||||
if (updateRequired) {
|
||||
updateRequired = false;
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
render();
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
void KOReaderSyncActivity::render() {
|
||||
if (subActivity) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
|
||||
renderer.clearScreen();
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "KOReader Sync", true, EpdFontFamily::BOLD);
|
||||
|
||||
if (state == NO_CREDENTIALS) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 280, "No credentials configured", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 320, "Set up KOReader account in Settings");
|
||||
|
||||
const auto labels = mappedInput.mapLabels("Back", "", "", "");
|
||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == SYNCING || state == UPLOADING) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 300, statusMessage.c_str(), true, EpdFontFamily::BOLD);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == SHOWING_RESULT) {
|
||||
// Show comparison
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 120, "Progress found!", true, EpdFontFamily::BOLD);
|
||||
|
||||
// Get chapter names from TOC
|
||||
const int remoteTocIndex = epub->getTocIndexForSpineIndex(remotePosition.spineIndex);
|
||||
const int localTocIndex = epub->getTocIndexForSpineIndex(currentSpineIndex);
|
||||
const std::string remoteChapter = (remoteTocIndex >= 0)
|
||||
? epub->getTocItem(remoteTocIndex).title
|
||||
: ("Section " + std::to_string(remotePosition.spineIndex + 1));
|
||||
const std::string localChapter = (localTocIndex >= 0) ? epub->getTocItem(localTocIndex).title
|
||||
: ("Section " + std::to_string(currentSpineIndex + 1));
|
||||
|
||||
// Remote progress - chapter and page
|
||||
renderer.drawText(UI_10_FONT_ID, 20, 160, "Remote:", true);
|
||||
char remoteChapterStr[128];
|
||||
snprintf(remoteChapterStr, sizeof(remoteChapterStr), " %s", remoteChapter.c_str());
|
||||
renderer.drawText(UI_10_FONT_ID, 20, 185, remoteChapterStr);
|
||||
char remotePageStr[64];
|
||||
snprintf(remotePageStr, sizeof(remotePageStr), " Page %d, %.2f%% overall", remotePosition.pageNumber + 1,
|
||||
remoteProgress.percentage * 100);
|
||||
renderer.drawText(UI_10_FONT_ID, 20, 210, remotePageStr);
|
||||
|
||||
if (!remoteProgress.device.empty()) {
|
||||
char deviceStr[64];
|
||||
snprintf(deviceStr, sizeof(deviceStr), " From: %s", remoteProgress.device.c_str());
|
||||
renderer.drawText(UI_10_FONT_ID, 20, 235, deviceStr);
|
||||
}
|
||||
|
||||
// Local progress - chapter and page
|
||||
renderer.drawText(UI_10_FONT_ID, 20, 270, "Local:", true);
|
||||
char localChapterStr[128];
|
||||
snprintf(localChapterStr, sizeof(localChapterStr), " %s", localChapter.c_str());
|
||||
renderer.drawText(UI_10_FONT_ID, 20, 295, localChapterStr);
|
||||
char localPageStr[64];
|
||||
snprintf(localPageStr, sizeof(localPageStr), " Page %d/%d, %.2f%% overall", currentPage + 1, totalPagesInSpine,
|
||||
localProgress.percentage * 100);
|
||||
renderer.drawText(UI_10_FONT_ID, 20, 320, localPageStr);
|
||||
|
||||
// Options
|
||||
const int optionY = 350;
|
||||
const int optionHeight = 30;
|
||||
|
||||
// Apply option
|
||||
if (selectedOption == 0) {
|
||||
renderer.fillRect(0, optionY - 2, pageWidth - 1, optionHeight);
|
||||
}
|
||||
renderer.drawText(UI_10_FONT_ID, 20, optionY, "Apply remote progress", selectedOption != 0);
|
||||
|
||||
// Upload option
|
||||
if (selectedOption == 1) {
|
||||
renderer.fillRect(0, optionY + optionHeight - 2, pageWidth - 1, optionHeight);
|
||||
}
|
||||
renderer.drawText(UI_10_FONT_ID, 20, optionY + optionHeight, "Upload local progress", selectedOption != 1);
|
||||
|
||||
// Cancel option
|
||||
if (selectedOption == 2) {
|
||||
renderer.fillRect(0, optionY + optionHeight * 2 - 2, pageWidth - 1, optionHeight);
|
||||
}
|
||||
renderer.drawText(UI_10_FONT_ID, 20, optionY + optionHeight * 2, "Cancel", selectedOption != 2);
|
||||
|
||||
const auto labels = mappedInput.mapLabels("", "Select", "", "");
|
||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == NO_REMOTE_PROGRESS) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 280, "No remote progress found", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 320, "Upload current position?");
|
||||
|
||||
const auto labels = mappedInput.mapLabels("Cancel", "Upload", "", "");
|
||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == UPLOAD_COMPLETE) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 300, "Progress uploaded!", true, EpdFontFamily::BOLD);
|
||||
|
||||
const auto labels = mappedInput.mapLabels("Back", "", "", "");
|
||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == SYNC_FAILED) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 280, "Sync failed", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 320, statusMessage.c_str());
|
||||
|
||||
const auto labels = mappedInput.mapLabels("Back", "", "", "");
|
||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void KOReaderSyncActivity::loop() {
|
||||
if (subActivity) {
|
||||
subActivity->loop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == NO_CREDENTIALS || state == SYNC_FAILED || state == UPLOAD_COMPLETE) {
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
onCancel();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == SHOWING_RESULT) {
|
||||
// Navigate options
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
||||
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
||||
selectedOption = (selectedOption + 2) % 3; // Wrap around
|
||||
updateRequired = true;
|
||||
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
||||
mappedInput.wasPressed(MappedInputManager::Button::Right)) {
|
||||
selectedOption = (selectedOption + 1) % 3;
|
||||
updateRequired = true;
|
||||
}
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||
if (selectedOption == 0) {
|
||||
// Apply remote progress
|
||||
onSyncComplete(remotePosition.spineIndex, remotePosition.pageNumber);
|
||||
} else if (selectedOption == 1) {
|
||||
// Upload local progress
|
||||
performUpload();
|
||||
} else {
|
||||
// Cancel
|
||||
onCancel();
|
||||
}
|
||||
}
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
onCancel();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == NO_REMOTE_PROGRESS) {
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||
// Calculate hash if not done yet
|
||||
if (documentHash.empty()) {
|
||||
if (KOREADER_STORE.getMatchMethod() == DocumentMatchMethod::FILENAME) {
|
||||
documentHash = KOReaderDocumentId::calculateFromFilename(epubPath);
|
||||
} else {
|
||||
documentHash = KOReaderDocumentId::calculate(epubPath);
|
||||
}
|
||||
}
|
||||
performUpload();
|
||||
}
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
onCancel();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
98
src/activities/reader/KOReaderSyncActivity.h
Normal file
98
src/activities/reader/KOReaderSyncActivity.h
Normal file
@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
#include <Epub.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "KOReaderSyncClient.h"
|
||||
#include "ProgressMapper.h"
|
||||
#include "activities/ActivityWithSubactivity.h"
|
||||
|
||||
/**
|
||||
* Activity for syncing reading progress with KOReader sync server.
|
||||
*
|
||||
* Flow:
|
||||
* 1. Connect to WiFi (if not connected)
|
||||
* 2. Calculate document hash
|
||||
* 3. Fetch remote progress
|
||||
* 4. Show comparison and options (Apply/Upload/Cancel)
|
||||
* 5. Apply or upload progress
|
||||
*/
|
||||
class KOReaderSyncActivity final : public ActivityWithSubactivity {
|
||||
public:
|
||||
using OnCancelCallback = std::function<void()>;
|
||||
using OnSyncCompleteCallback = std::function<void(int newSpineIndex, int newPageNumber)>;
|
||||
|
||||
explicit KOReaderSyncActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::shared_ptr<Epub>& epub, const std::string& epubPath, int currentSpineIndex,
|
||||
int currentPage, int totalPagesInSpine, OnCancelCallback onCancel,
|
||||
OnSyncCompleteCallback onSyncComplete)
|
||||
: ActivityWithSubactivity("KOReaderSync", renderer, mappedInput),
|
||||
epub(epub),
|
||||
epubPath(epubPath),
|
||||
currentSpineIndex(currentSpineIndex),
|
||||
currentPage(currentPage),
|
||||
totalPagesInSpine(totalPagesInSpine),
|
||||
remoteProgress{},
|
||||
remotePosition{},
|
||||
localProgress{},
|
||||
onCancel(std::move(onCancel)),
|
||||
onSyncComplete(std::move(onSyncComplete)) {}
|
||||
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
bool preventAutoSleep() override { return state == CONNECTING || state == SYNCING; }
|
||||
|
||||
private:
|
||||
enum State {
|
||||
WIFI_SELECTION,
|
||||
CONNECTING,
|
||||
SYNCING,
|
||||
SHOWING_RESULT,
|
||||
UPLOADING,
|
||||
UPLOAD_COMPLETE,
|
||||
NO_REMOTE_PROGRESS,
|
||||
SYNC_FAILED,
|
||||
NO_CREDENTIALS
|
||||
};
|
||||
|
||||
std::shared_ptr<Epub> epub;
|
||||
std::string epubPath;
|
||||
int currentSpineIndex;
|
||||
int currentPage;
|
||||
int totalPagesInSpine;
|
||||
|
||||
TaskHandle_t displayTaskHandle = nullptr;
|
||||
SemaphoreHandle_t renderingMutex = nullptr;
|
||||
bool updateRequired = false;
|
||||
|
||||
State state = WIFI_SELECTION;
|
||||
std::string statusMessage;
|
||||
std::string documentHash;
|
||||
|
||||
// Remote progress data
|
||||
bool hasRemoteProgress = false;
|
||||
KOReaderProgress remoteProgress;
|
||||
CrossPointPosition remotePosition;
|
||||
|
||||
// Local progress as KOReader format (for display)
|
||||
KOReaderPosition localProgress;
|
||||
|
||||
// Selection in result screen (0=Apply, 1=Upload, 2=Cancel)
|
||||
int selectedOption = 0;
|
||||
|
||||
OnCancelCallback onCancel;
|
||||
OnSyncCompleteCallback onSyncComplete;
|
||||
|
||||
void onWifiSelectionComplete(bool success);
|
||||
void performSync();
|
||||
void performUpload();
|
||||
|
||||
static void taskTrampoline(void* param);
|
||||
[[noreturn]] void displayTaskLoop();
|
||||
void render();
|
||||
};
|
||||
167
src/activities/settings/KOReaderAuthActivity.cpp
Normal file
167
src/activities/settings/KOReaderAuthActivity.cpp
Normal file
@ -0,0 +1,167 @@
|
||||
#include "KOReaderAuthActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
#include "KOReaderCredentialStore.h"
|
||||
#include "KOReaderSyncClient.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "activities/network/WifiSelectionActivity.h"
|
||||
#include "fontIds.h"
|
||||
|
||||
void KOReaderAuthActivity::taskTrampoline(void* param) {
|
||||
auto* self = static_cast<KOReaderAuthActivity*>(param);
|
||||
self->displayTaskLoop();
|
||||
}
|
||||
|
||||
void KOReaderAuthActivity::onWifiSelectionComplete(const bool success) {
|
||||
exitActivity();
|
||||
|
||||
if (!success) {
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
state = FAILED;
|
||||
errorMessage = "WiFi connection failed";
|
||||
xSemaphoreGive(renderingMutex);
|
||||
updateRequired = true;
|
||||
return;
|
||||
}
|
||||
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
state = AUTHENTICATING;
|
||||
statusMessage = "Authenticating...";
|
||||
xSemaphoreGive(renderingMutex);
|
||||
updateRequired = true;
|
||||
|
||||
performAuthentication();
|
||||
}
|
||||
|
||||
void KOReaderAuthActivity::performAuthentication() {
|
||||
const auto result = KOReaderSyncClient::authenticate();
|
||||
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
if (result == KOReaderSyncClient::OK) {
|
||||
state = SUCCESS;
|
||||
statusMessage = "Successfully authenticated!";
|
||||
} else {
|
||||
state = FAILED;
|
||||
errorMessage = KOReaderSyncClient::errorString(result);
|
||||
}
|
||||
xSemaphoreGive(renderingMutex);
|
||||
updateRequired = true;
|
||||
}
|
||||
|
||||
void KOReaderAuthActivity::onEnter() {
|
||||
ActivityWithSubactivity::onEnter();
|
||||
|
||||
renderingMutex = xSemaphoreCreateMutex();
|
||||
|
||||
xTaskCreate(&KOReaderAuthActivity::taskTrampoline, "KOAuthTask",
|
||||
4096, // Stack size
|
||||
this, // Parameters
|
||||
1, // Priority
|
||||
&displayTaskHandle // Task handle
|
||||
);
|
||||
|
||||
// Turn on WiFi
|
||||
WiFi.mode(WIFI_STA);
|
||||
|
||||
// Check if already connected
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
state = AUTHENTICATING;
|
||||
statusMessage = "Authenticating...";
|
||||
updateRequired = true;
|
||||
|
||||
// Perform authentication in a separate task
|
||||
xTaskCreate(
|
||||
[](void* param) {
|
||||
auto* self = static_cast<KOReaderAuthActivity*>(param);
|
||||
self->performAuthentication();
|
||||
vTaskDelete(nullptr);
|
||||
},
|
||||
"AuthTask", 4096, this, 1, nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
// Launch WiFi selection
|
||||
enterNewActivity(new WifiSelectionActivity(renderer, mappedInput,
|
||||
[this](const bool connected) { onWifiSelectionComplete(connected); }));
|
||||
}
|
||||
|
||||
void KOReaderAuthActivity::onExit() {
|
||||
ActivityWithSubactivity::onExit();
|
||||
|
||||
// Turn off wifi
|
||||
WiFi.disconnect(false);
|
||||
delay(100);
|
||||
WiFi.mode(WIFI_OFF);
|
||||
delay(100);
|
||||
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
if (displayTaskHandle) {
|
||||
vTaskDelete(displayTaskHandle);
|
||||
displayTaskHandle = nullptr;
|
||||
}
|
||||
vSemaphoreDelete(renderingMutex);
|
||||
renderingMutex = nullptr;
|
||||
}
|
||||
|
||||
void KOReaderAuthActivity::displayTaskLoop() {
|
||||
while (true) {
|
||||
if (updateRequired && !subActivity) {
|
||||
updateRequired = false;
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
render();
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
void KOReaderAuthActivity::render() {
|
||||
if (subActivity) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderer.clearScreen();
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "KOReader Auth", true, EpdFontFamily::BOLD);
|
||||
|
||||
if (state == AUTHENTICATING) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 300, statusMessage.c_str(), true, EpdFontFamily::BOLD);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == SUCCESS) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 280, "Success!", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 320, "KOReader sync is ready to use");
|
||||
|
||||
const auto labels = mappedInput.mapLabels("Done", "", "", "");
|
||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == FAILED) {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 280, "Authentication Failed", true, EpdFontFamily::BOLD);
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, 320, errorMessage.c_str());
|
||||
|
||||
const auto labels = mappedInput.mapLabels("Back", "", "", "");
|
||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void KOReaderAuthActivity::loop() {
|
||||
if (subActivity) {
|
||||
subActivity->loop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == SUCCESS || state == FAILED) {
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back) ||
|
||||
mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||
onComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
44
src/activities/settings/KOReaderAuthActivity.h
Normal file
44
src/activities/settings/KOReaderAuthActivity.h
Normal file
@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "activities/ActivityWithSubactivity.h"
|
||||
|
||||
/**
|
||||
* Activity for testing KOReader credentials.
|
||||
* Connects to WiFi and authenticates with the KOReader sync server.
|
||||
*/
|
||||
class KOReaderAuthActivity final : public ActivityWithSubactivity {
|
||||
public:
|
||||
explicit KOReaderAuthActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::function<void()>& onComplete)
|
||||
: ActivityWithSubactivity("KOReaderAuth", renderer, mappedInput), onComplete(onComplete) {}
|
||||
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
bool preventAutoSleep() override { return state == CONNECTING || state == AUTHENTICATING; }
|
||||
|
||||
private:
|
||||
enum State { WIFI_SELECTION, CONNECTING, AUTHENTICATING, SUCCESS, FAILED };
|
||||
|
||||
TaskHandle_t displayTaskHandle = nullptr;
|
||||
SemaphoreHandle_t renderingMutex = nullptr;
|
||||
bool updateRequired = false;
|
||||
|
||||
State state = WIFI_SELECTION;
|
||||
std::string statusMessage;
|
||||
std::string errorMessage;
|
||||
|
||||
const std::function<void()> onComplete;
|
||||
|
||||
void onWifiSelectionComplete(bool success);
|
||||
void performAuthentication();
|
||||
|
||||
static void taskTrampoline(void* param);
|
||||
[[noreturn]] void displayTaskLoop();
|
||||
void render();
|
||||
};
|
||||
213
src/activities/settings/KOReaderSettingsActivity.cpp
Normal file
213
src/activities/settings/KOReaderSettingsActivity.cpp
Normal file
@ -0,0 +1,213 @@
|
||||
#include "KOReaderSettingsActivity.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "KOReaderAuthActivity.h"
|
||||
#include "KOReaderCredentialStore.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "activities/util/KeyboardEntryActivity.h"
|
||||
#include "fontIds.h"
|
||||
|
||||
namespace {
|
||||
constexpr int MENU_ITEMS = 5;
|
||||
const char* menuNames[MENU_ITEMS] = {"Username", "Password", "Sync Server URL", "Document Matching", "Authenticate"};
|
||||
} // namespace
|
||||
|
||||
void KOReaderSettingsActivity::taskTrampoline(void* param) {
|
||||
auto* self = static_cast<KOReaderSettingsActivity*>(param);
|
||||
self->displayTaskLoop();
|
||||
}
|
||||
|
||||
void KOReaderSettingsActivity::onEnter() {
|
||||
ActivityWithSubactivity::onEnter();
|
||||
|
||||
renderingMutex = xSemaphoreCreateMutex();
|
||||
selectedIndex = 0;
|
||||
updateRequired = true;
|
||||
|
||||
xTaskCreate(&KOReaderSettingsActivity::taskTrampoline, "KOReaderSettingsTask",
|
||||
4096, // Stack size
|
||||
this, // Parameters
|
||||
1, // Priority
|
||||
&displayTaskHandle // Task handle
|
||||
);
|
||||
}
|
||||
|
||||
void KOReaderSettingsActivity::onExit() {
|
||||
ActivityWithSubactivity::onExit();
|
||||
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
if (displayTaskHandle) {
|
||||
vTaskDelete(displayTaskHandle);
|
||||
displayTaskHandle = nullptr;
|
||||
}
|
||||
vSemaphoreDelete(renderingMutex);
|
||||
renderingMutex = nullptr;
|
||||
}
|
||||
|
||||
void KOReaderSettingsActivity::loop() {
|
||||
if (subActivity) {
|
||||
subActivity->loop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
onBack();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Confirm)) {
|
||||
handleSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Up) ||
|
||||
mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
||||
selectedIndex = (selectedIndex + MENU_ITEMS - 1) % MENU_ITEMS;
|
||||
updateRequired = true;
|
||||
} else if (mappedInput.wasPressed(MappedInputManager::Button::Down) ||
|
||||
mappedInput.wasPressed(MappedInputManager::Button::Right)) {
|
||||
selectedIndex = (selectedIndex + 1) % MENU_ITEMS;
|
||||
updateRequired = true;
|
||||
}
|
||||
}
|
||||
|
||||
void KOReaderSettingsActivity::handleSelection() {
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
|
||||
if (selectedIndex == 0) {
|
||||
// Username
|
||||
exitActivity();
|
||||
enterNewActivity(new KeyboardEntryActivity(
|
||||
renderer, mappedInput, "KOReader Username", KOREADER_STORE.getUsername(), 10,
|
||||
64, // maxLength
|
||||
false, // not password
|
||||
[this](const std::string& username) {
|
||||
KOREADER_STORE.setCredentials(username, KOREADER_STORE.getPassword());
|
||||
KOREADER_STORE.saveToFile();
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
},
|
||||
[this]() {
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
}));
|
||||
} else if (selectedIndex == 1) {
|
||||
// Password
|
||||
exitActivity();
|
||||
enterNewActivity(new KeyboardEntryActivity(
|
||||
renderer, mappedInput, "KOReader Password", KOREADER_STORE.getPassword(), 10,
|
||||
64, // maxLength
|
||||
false, // show characters
|
||||
[this](const std::string& password) {
|
||||
KOREADER_STORE.setCredentials(KOREADER_STORE.getUsername(), password);
|
||||
KOREADER_STORE.saveToFile();
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
},
|
||||
[this]() {
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
}));
|
||||
} else if (selectedIndex == 2) {
|
||||
// Sync Server URL - prefill with https:// if empty to save typing
|
||||
const std::string currentUrl = KOREADER_STORE.getServerUrl();
|
||||
const std::string prefillUrl = currentUrl.empty() ? "https://" : currentUrl;
|
||||
exitActivity();
|
||||
enterNewActivity(new KeyboardEntryActivity(
|
||||
renderer, mappedInput, "Sync Server URL", prefillUrl, 10,
|
||||
128, // maxLength - URLs can be long
|
||||
false, // not password
|
||||
[this](const std::string& url) {
|
||||
// Clear if user just left the prefilled https://
|
||||
const std::string urlToSave = (url == "https://" || url == "http://") ? "" : url;
|
||||
KOREADER_STORE.setServerUrl(urlToSave);
|
||||
KOREADER_STORE.saveToFile();
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
},
|
||||
[this]() {
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
}));
|
||||
} else if (selectedIndex == 3) {
|
||||
// Document Matching - toggle between Filename and Binary
|
||||
const auto current = KOREADER_STORE.getMatchMethod();
|
||||
const auto newMethod =
|
||||
(current == DocumentMatchMethod::FILENAME) ? DocumentMatchMethod::BINARY : DocumentMatchMethod::FILENAME;
|
||||
KOREADER_STORE.setMatchMethod(newMethod);
|
||||
KOREADER_STORE.saveToFile();
|
||||
updateRequired = true;
|
||||
} else if (selectedIndex == 4) {
|
||||
// Authenticate
|
||||
if (!KOREADER_STORE.hasCredentials()) {
|
||||
// Can't authenticate without credentials - just show message briefly
|
||||
xSemaphoreGive(renderingMutex);
|
||||
return;
|
||||
}
|
||||
exitActivity();
|
||||
enterNewActivity(new KOReaderAuthActivity(renderer, mappedInput, [this] {
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
}));
|
||||
}
|
||||
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
|
||||
void KOReaderSettingsActivity::displayTaskLoop() {
|
||||
while (true) {
|
||||
if (updateRequired && !subActivity) {
|
||||
updateRequired = false;
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
render();
|
||||
xSemaphoreGive(renderingMutex);
|
||||
}
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
void KOReaderSettingsActivity::render() {
|
||||
renderer.clearScreen();
|
||||
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
|
||||
// Draw header
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "KOReader Sync", true, EpdFontFamily::BOLD);
|
||||
|
||||
// Draw selection highlight
|
||||
renderer.fillRect(0, 60 + selectedIndex * 30 - 2, pageWidth - 1, 30);
|
||||
|
||||
// Draw menu items
|
||||
for (int i = 0; i < MENU_ITEMS; i++) {
|
||||
const int settingY = 60 + i * 30;
|
||||
const bool isSelected = (i == selectedIndex);
|
||||
|
||||
renderer.drawText(UI_10_FONT_ID, 20, settingY, menuNames[i], !isSelected);
|
||||
|
||||
// Draw status for each item
|
||||
const char* status = "";
|
||||
if (i == 0) {
|
||||
status = KOREADER_STORE.getUsername().empty() ? "[Not Set]" : "[Set]";
|
||||
} else if (i == 1) {
|
||||
status = KOREADER_STORE.getPassword().empty() ? "[Not Set]" : "[Set]";
|
||||
} else if (i == 2) {
|
||||
status = KOREADER_STORE.getServerUrl().empty() ? "[Not Set]" : "[Set]";
|
||||
} else if (i == 3) {
|
||||
status = KOREADER_STORE.getMatchMethod() == DocumentMatchMethod::FILENAME ? "[Filename]" : "[Binary]";
|
||||
} else if (i == 4) {
|
||||
status = KOREADER_STORE.hasCredentials() ? "" : "[Set credentials first]";
|
||||
}
|
||||
|
||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, status);
|
||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, status, !isSelected);
|
||||
}
|
||||
|
||||
// Draw button hints
|
||||
const auto labels = mappedInput.mapLabels("« Back", "Select", "", "");
|
||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
|
||||
renderer.displayBuffer();
|
||||
}
|
||||
36
src/activities/settings/KOReaderSettingsActivity.h
Normal file
36
src/activities/settings/KOReaderSettingsActivity.h
Normal file
@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/semphr.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "activities/ActivityWithSubactivity.h"
|
||||
|
||||
/**
|
||||
* Submenu for KOReader Sync settings.
|
||||
* Shows username, password, and authenticate options.
|
||||
*/
|
||||
class KOReaderSettingsActivity final : public ActivityWithSubactivity {
|
||||
public:
|
||||
explicit KOReaderSettingsActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
const std::function<void()>& onBack)
|
||||
: ActivityWithSubactivity("KOReaderSettings", renderer, mappedInput), onBack(onBack) {}
|
||||
|
||||
void onEnter() override;
|
||||
void onExit() override;
|
||||
void loop() override;
|
||||
|
||||
private:
|
||||
TaskHandle_t displayTaskHandle = nullptr;
|
||||
SemaphoreHandle_t renderingMutex = nullptr;
|
||||
bool updateRequired = false;
|
||||
|
||||
int selectedIndex = 0;
|
||||
const std::function<void()> onBack;
|
||||
|
||||
static void taskTrampoline(void* param);
|
||||
[[noreturn]] void displayTaskLoop();
|
||||
void render();
|
||||
void handleSelection();
|
||||
};
|
||||
@ -7,13 +7,14 @@
|
||||
|
||||
#include "CalibreSettingsActivity.h"
|
||||
#include "CrossPointSettings.h"
|
||||
#include "KOReaderSettingsActivity.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "OtaUpdateActivity.h"
|
||||
#include "fontIds.h"
|
||||
|
||||
// Define the static settings list
|
||||
namespace {
|
||||
constexpr int settingsCount = 20;
|
||||
constexpr int settingsCount = 22;
|
||||
const SettingInfo settingsList[settingsCount] = {
|
||||
// Should match with SLEEP_SCREEN_MODE
|
||||
SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}),
|
||||
@ -37,10 +38,12 @@ const SettingInfo settingsList[settingsCount] = {
|
||||
SettingInfo::Value("Reader Screen Margin", &CrossPointSettings::screenMargin, {5, 40, 5}),
|
||||
SettingInfo::Enum("Reader Paragraph Alignment", &CrossPointSettings::paragraphAlignment,
|
||||
{"Justify", "Left", "Center", "Right"}),
|
||||
SettingInfo::Toggle("Hyphenation", &CrossPointSettings::hyphenationEnabled),
|
||||
SettingInfo::Enum("Time to Sleep", &CrossPointSettings::sleepTimeout,
|
||||
{"1 min", "5 min", "10 min", "15 min", "30 min"}),
|
||||
SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency,
|
||||
{"1 page", "5 pages", "10 pages", "15 pages", "30 pages"}),
|
||||
SettingInfo::Action("KOReader Sync"),
|
||||
SettingInfo::Action("Calibre Settings"),
|
||||
SettingInfo::Action("Check for updates")};
|
||||
} // namespace
|
||||
@ -115,7 +118,6 @@ void SettingsActivity::loop() {
|
||||
}
|
||||
|
||||
void SettingsActivity::toggleCurrentSetting() {
|
||||
// Validate index
|
||||
if (selectedSettingIndex < 0 || selectedSettingIndex >= settingsCount) {
|
||||
return;
|
||||
}
|
||||
@ -139,7 +141,15 @@ void SettingsActivity::toggleCurrentSetting() {
|
||||
SETTINGS.*(setting.valuePtr) = currentValue + setting.valueRange.step;
|
||||
}
|
||||
} else if (setting.type == SettingType::ACTION) {
|
||||
if (strcmp(setting.name, "Calibre Settings") == 0) {
|
||||
if (strcmp(setting.name, "KOReader Sync") == 0) {
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
exitActivity();
|
||||
enterNewActivity(new KOReaderSettingsActivity(renderer, mappedInput, [this] {
|
||||
exitActivity();
|
||||
updateRequired = true;
|
||||
}));
|
||||
xSemaphoreGive(renderingMutex);
|
||||
} else if (strcmp(setting.name, "Calibre Settings") == 0) {
|
||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||
exitActivity();
|
||||
enterNewActivity(new CalibreSettingsActivity(renderer, mappedInput, [this] {
|
||||
@ -186,18 +196,19 @@ void SettingsActivity::render() const {
|
||||
// Draw header
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Settings", true, EpdFontFamily::BOLD);
|
||||
|
||||
// Draw selection
|
||||
// Draw selection highlight
|
||||
renderer.fillRect(0, 60 + selectedSettingIndex * 30 - 2, pageWidth - 1, 30);
|
||||
|
||||
// Draw all settings
|
||||
for (int i = 0; i < settingsCount; i++) {
|
||||
const int settingY = 60 + i * 30; // 30 pixels between settings
|
||||
const bool isSelected = (i == selectedSettingIndex);
|
||||
|
||||
// Draw setting name
|
||||
renderer.drawText(UI_10_FONT_ID, 20, settingY, settingsList[i].name, i != selectedSettingIndex);
|
||||
renderer.drawText(UI_10_FONT_ID, 20, settingY, settingsList[i].name, !isSelected);
|
||||
|
||||
// Draw value based on setting type
|
||||
std::string valueText = "";
|
||||
std::string valueText;
|
||||
if (settingsList[i].type == SettingType::TOGGLE && settingsList[i].valuePtr != nullptr) {
|
||||
const bool value = SETTINGS.*(settingsList[i].valuePtr);
|
||||
valueText = value ? "ON" : "OFF";
|
||||
@ -207,8 +218,10 @@ void SettingsActivity::render() const {
|
||||
} else if (settingsList[i].type == SettingType::VALUE && settingsList[i].valuePtr != nullptr) {
|
||||
valueText = std::to_string(SETTINGS.*(settingsList[i].valuePtr));
|
||||
}
|
||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str());
|
||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, valueText.c_str(), i != selectedSettingIndex);
|
||||
if (!valueText.empty()) {
|
||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str());
|
||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, valueText.c_str(), !isSelected);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw version text above button hints
|
||||
|
||||
@ -73,7 +73,7 @@ int KeyboardEntryActivity::getRowLength(const int row) const {
|
||||
case 3:
|
||||
return 10; // zxcvbnm,./
|
||||
case 4:
|
||||
return 10; // caps (2 wide), space (5 wide), backspace (2 wide), OK
|
||||
return 10; // shift (2 wide), space (5 wide), backspace (2 wide), OK
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@ -145,6 +145,11 @@ void KeyboardEntryActivity::loop() {
|
||||
// Clamp column to valid range for new row
|
||||
const int maxCol = getRowLength(selectedRow) - 1;
|
||||
if (selectedCol > maxCol) selectedCol = maxCol;
|
||||
} else {
|
||||
// Wrap to bottom row
|
||||
selectedRow = NUM_ROWS - 1;
|
||||
const int maxCol = getRowLength(selectedRow) - 1;
|
||||
if (selectedCol > maxCol) selectedCol = maxCol;
|
||||
}
|
||||
updateRequired = true;
|
||||
}
|
||||
@ -154,16 +159,24 @@ void KeyboardEntryActivity::loop() {
|
||||
selectedRow++;
|
||||
const int maxCol = getRowLength(selectedRow) - 1;
|
||||
if (selectedCol > maxCol) selectedCol = maxCol;
|
||||
} else {
|
||||
// Wrap to top row
|
||||
selectedRow = 0;
|
||||
const int maxCol = getRowLength(selectedRow) - 1;
|
||||
if (selectedCol > maxCol) selectedCol = maxCol;
|
||||
}
|
||||
updateRequired = true;
|
||||
}
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Left)) {
|
||||
const int maxCol = getRowLength(selectedRow) - 1;
|
||||
|
||||
// Special bottom row case
|
||||
if (selectedRow == SPECIAL_ROW) {
|
||||
// Bottom row has special key widths
|
||||
if (selectedCol >= SHIFT_COL && selectedCol < SPACE_COL) {
|
||||
// In shift key, do nothing
|
||||
// In shift key, wrap to end of row
|
||||
selectedCol = maxCol;
|
||||
} else if (selectedCol >= SPACE_COL && selectedCol < BACKSPACE_COL) {
|
||||
// In space bar, move to shift
|
||||
selectedCol = SHIFT_COL;
|
||||
@ -180,10 +193,9 @@ void KeyboardEntryActivity::loop() {
|
||||
|
||||
if (selectedCol > 0) {
|
||||
selectedCol--;
|
||||
} else if (selectedRow > 0) {
|
||||
// Wrap to previous row
|
||||
selectedRow--;
|
||||
selectedCol = getRowLength(selectedRow) - 1;
|
||||
} else {
|
||||
// Wrap to end of current row
|
||||
selectedCol = maxCol;
|
||||
}
|
||||
updateRequired = true;
|
||||
}
|
||||
@ -204,7 +216,8 @@ void KeyboardEntryActivity::loop() {
|
||||
// In backspace, move to done
|
||||
selectedCol = DONE_COL;
|
||||
} else if (selectedCol >= DONE_COL) {
|
||||
// At done button, do nothing
|
||||
// At done button, wrap to beginning of row
|
||||
selectedCol = SHIFT_COL;
|
||||
}
|
||||
updateRequired = true;
|
||||
return;
|
||||
@ -212,9 +225,8 @@ void KeyboardEntryActivity::loop() {
|
||||
|
||||
if (selectedCol < maxCol) {
|
||||
selectedCol++;
|
||||
} else if (selectedRow < NUM_ROWS - 1) {
|
||||
// Wrap to next row
|
||||
selectedRow++;
|
||||
} else {
|
||||
// Wrap to beginning of current row
|
||||
selectedCol = 0;
|
||||
}
|
||||
updateRequired = true;
|
||||
@ -288,14 +300,14 @@ void KeyboardEntryActivity::render() const {
|
||||
|
||||
// Handle bottom row (row 4) specially with proper multi-column keys
|
||||
if (row == 4) {
|
||||
// Bottom row layout: CAPS (2 cols) | SPACE (5 cols) | <- (2 cols) | OK (2 cols)
|
||||
// Bottom row layout: SHIFT (2 cols) | SPACE (5 cols) | <- (2 cols) | OK (2 cols)
|
||||
// Total: 11 visual columns, but we use logical positions for selection
|
||||
|
||||
int currentX = startX;
|
||||
|
||||
// CAPS key (logical col 0, spans 2 key widths)
|
||||
const bool capsSelected = (selectedRow == 4 && selectedCol >= SHIFT_COL && selectedCol < SPACE_COL);
|
||||
renderItemWithSelector(currentX + 2, rowY, shiftActive ? "CAPS" : "caps", capsSelected);
|
||||
// SHIFT key (logical col 0, spans 2 key widths)
|
||||
const bool shiftSelected = (selectedRow == 4 && selectedCol >= SHIFT_COL && selectedCol < SPACE_COL);
|
||||
renderItemWithSelector(currentX + 2, rowY, shiftActive ? "SHIFT" : "shift", shiftSelected);
|
||||
currentX += 2 * (keyWidth + keySpacing);
|
||||
|
||||
// Space bar (logical cols 2-6, spans 5 key widths)
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
#include "Battery.h"
|
||||
#include "CrossPointSettings.h"
|
||||
#include "CrossPointState.h"
|
||||
#include "KOReaderCredentialStore.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "activities/boot_sleep/BootActivity.h"
|
||||
#include "activities/boot_sleep/SleepActivity.h"
|
||||
@ -289,6 +290,7 @@ void setup() {
|
||||
}
|
||||
|
||||
SETTINGS.loadFromFile();
|
||||
KOREADER_STORE.loadFromFile();
|
||||
|
||||
// verify power button press duration after we've read settings.
|
||||
verifyWakeupLongPress();
|
||||
|
||||
388
test/hyphenation_eval/HyphenationEvaluationTest.cpp
Normal file
388
test/hyphenation_eval/HyphenationEvaluationTest.cpp
Normal file
@ -0,0 +1,388 @@
|
||||
#include <Utf8.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/Epub/Epub/hyphenation/HyphenationCommon.h"
|
||||
#include "lib/Epub/Epub/hyphenation/LanguageHyphenator.h"
|
||||
#include "lib/Epub/Epub/hyphenation/LanguageRegistry.h"
|
||||
|
||||
struct TestCase {
|
||||
std::string word;
|
||||
std::string hyphenated;
|
||||
std::vector<size_t> expectedPositions;
|
||||
int frequency;
|
||||
};
|
||||
|
||||
struct EvaluationResult {
|
||||
int truePositives = 0;
|
||||
int falsePositives = 0;
|
||||
int falseNegatives = 0;
|
||||
double precision = 0.0;
|
||||
double recall = 0.0;
|
||||
double f1Score = 0.0;
|
||||
double weightedScore = 0.0;
|
||||
};
|
||||
|
||||
struct LanguageConfig {
|
||||
std::string cliName;
|
||||
std::string testDataFile;
|
||||
const char* primaryTag;
|
||||
};
|
||||
|
||||
const std::vector<LanguageConfig> kSupportedLanguages = {
|
||||
{"english", "test/hyphenation_eval/resources/english_hyphenation_tests.txt", "en"},
|
||||
{"french", "test/hyphenation_eval/resources/french_hyphenation_tests.txt", "fr"},
|
||||
{"german", "test/hyphenation_eval/resources/german_hyphenation_tests.txt", "de"},
|
||||
{"russian", "test/hyphenation_eval/resources/russian_hyphenation_tests.txt", "ru"},
|
||||
};
|
||||
|
||||
std::vector<size_t> expectedPositionsFromAnnotatedWord(const std::string& annotated) {
|
||||
std::vector<size_t> positions;
|
||||
const unsigned char* ptr = reinterpret_cast<const unsigned char*>(annotated.c_str());
|
||||
size_t codepointIndex = 0;
|
||||
|
||||
while (*ptr != 0) {
|
||||
if (*ptr == '=') {
|
||||
positions.push_back(codepointIndex);
|
||||
++ptr;
|
||||
continue;
|
||||
}
|
||||
|
||||
utf8NextCodepoint(&ptr);
|
||||
++codepointIndex;
|
||||
}
|
||||
|
||||
return positions;
|
||||
}
|
||||
|
||||
std::vector<TestCase> loadTestData(const std::string& filename) {
|
||||
std::vector<TestCase> testCases;
|
||||
std::ifstream file(filename);
|
||||
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "Error: Could not open file " << filename << std::endl;
|
||||
return testCases;
|
||||
}
|
||||
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
if (line.empty() || line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::istringstream iss(line);
|
||||
std::string word, hyphenated, freqStr;
|
||||
|
||||
if (std::getline(iss, word, '|') && std::getline(iss, hyphenated, '|') && std::getline(iss, freqStr, '|')) {
|
||||
TestCase testCase;
|
||||
testCase.word = word;
|
||||
testCase.hyphenated = hyphenated;
|
||||
testCase.frequency = std::stoi(freqStr);
|
||||
|
||||
testCase.expectedPositions = expectedPositionsFromAnnotatedWord(hyphenated);
|
||||
|
||||
testCases.push_back(testCase);
|
||||
}
|
||||
}
|
||||
|
||||
file.close();
|
||||
return testCases;
|
||||
}
|
||||
|
||||
std::string positionsToHyphenated(const std::string& word, const std::vector<size_t>& positions) {
|
||||
std::string result;
|
||||
std::vector<size_t> sortedPositions = positions;
|
||||
std::sort(sortedPositions.begin(), sortedPositions.end());
|
||||
|
||||
const unsigned char* ptr = reinterpret_cast<const unsigned char*>(word.c_str());
|
||||
size_t codepointIndex = 0;
|
||||
size_t posIdx = 0;
|
||||
|
||||
while (*ptr != 0) {
|
||||
while (posIdx < sortedPositions.size() && sortedPositions[posIdx] == codepointIndex) {
|
||||
result.push_back('=');
|
||||
++posIdx;
|
||||
}
|
||||
|
||||
const unsigned char* current = ptr;
|
||||
utf8NextCodepoint(&ptr);
|
||||
result.append(reinterpret_cast<const char*>(current), reinterpret_cast<const char*>(ptr));
|
||||
++codepointIndex;
|
||||
}
|
||||
|
||||
while (posIdx < sortedPositions.size() && sortedPositions[posIdx] == codepointIndex) {
|
||||
result.push_back('=');
|
||||
++posIdx;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<size_t> hyphenateWordWithHyphenator(const std::string& word, const LanguageHyphenator& hyphenator) {
|
||||
auto cps = collectCodepoints(word);
|
||||
trimSurroundingPunctuationAndFootnote(cps);
|
||||
|
||||
return hyphenator.breakIndexes(cps);
|
||||
}
|
||||
|
||||
std::vector<LanguageConfig> resolveLanguages(const std::string& selection) {
|
||||
if (selection == "all") {
|
||||
return kSupportedLanguages;
|
||||
}
|
||||
|
||||
for (const auto& config : kSupportedLanguages) {
|
||||
if (config.cliName == selection) {
|
||||
return {config};
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
EvaluationResult evaluateWord(const TestCase& testCase,
|
||||
std::function<std::vector<size_t>(const std::string&)> hyphenateFunc) {
|
||||
EvaluationResult result;
|
||||
|
||||
std::vector<size_t> actualPositions = hyphenateFunc(testCase.word);
|
||||
|
||||
std::vector<size_t> expected = testCase.expectedPositions;
|
||||
std::vector<size_t> actual = actualPositions;
|
||||
|
||||
std::sort(expected.begin(), expected.end());
|
||||
std::sort(actual.begin(), actual.end());
|
||||
|
||||
for (size_t pos : actual) {
|
||||
if (std::find(expected.begin(), expected.end(), pos) != expected.end()) {
|
||||
result.truePositives++;
|
||||
} else {
|
||||
result.falsePositives++;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t pos : expected) {
|
||||
if (std::find(actual.begin(), actual.end(), pos) == actual.end()) {
|
||||
result.falseNegatives++;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.truePositives + result.falsePositives > 0) {
|
||||
result.precision = static_cast<double>(result.truePositives) / (result.truePositives + result.falsePositives);
|
||||
}
|
||||
|
||||
if (result.truePositives + result.falseNegatives > 0) {
|
||||
result.recall = static_cast<double>(result.truePositives) / (result.truePositives + result.falseNegatives);
|
||||
}
|
||||
|
||||
if (result.precision + result.recall > 0) {
|
||||
result.f1Score = 2 * result.precision * result.recall / (result.precision + result.recall);
|
||||
}
|
||||
|
||||
// Treat words that contain no hyphenation marks in both the expected data and the
|
||||
// algorithmic output as perfect matches so they don't drag down the per-word averages.
|
||||
if (expected.empty() && actual.empty()) {
|
||||
result.precision = 1.0;
|
||||
result.recall = 1.0;
|
||||
result.f1Score = 1.0;
|
||||
}
|
||||
|
||||
double fpPenalty = 2.0;
|
||||
double fnPenalty = 1.0;
|
||||
|
||||
int totalErrors = result.falsePositives * fpPenalty + result.falseNegatives * fnPenalty;
|
||||
int totalPossible = expected.size() * fpPenalty;
|
||||
|
||||
if (totalPossible > 0) {
|
||||
result.weightedScore = 1.0 - (static_cast<double>(totalErrors) / totalPossible);
|
||||
result.weightedScore = std::max(0.0, result.weightedScore);
|
||||
} else if (result.falsePositives == 0) {
|
||||
result.weightedScore = 1.0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void printResults(const std::string& language, const std::vector<TestCase>& testCases,
|
||||
const std::vector<std::pair<TestCase, EvaluationResult>>& worstCases, int perfectMatches,
|
||||
int partialMatches, int completeMisses, double totalPrecision, double totalRecall, double totalF1,
|
||||
double totalWeighted, int totalTP, int totalFP, int totalFN,
|
||||
std::function<std::vector<size_t>(const std::string&)> hyphenateFunc) {
|
||||
std::string lang_upper = language;
|
||||
if (!lang_upper.empty()) {
|
||||
lang_upper[0] = std::toupper(lang_upper[0]);
|
||||
}
|
||||
|
||||
std::cout << "================================================================================" << std::endl;
|
||||
std::cout << lang_upper << " HYPHENATION EVALUATION RESULTS" << std::endl;
|
||||
std::cout << "================================================================================" << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
std::cout << "Total test cases: " << testCases.size() << std::endl;
|
||||
std::cout << "Perfect matches: " << perfectMatches << " (" << (perfectMatches * 100.0 / testCases.size()) << "%)"
|
||||
<< std::endl;
|
||||
std::cout << "Partial matches: " << partialMatches << std::endl;
|
||||
std::cout << "Complete misses: " << completeMisses << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
std::cout << "--- Overall Metrics (averaged per word) ---" << std::endl;
|
||||
std::cout << "Average Precision: " << (totalPrecision / testCases.size() * 100.0) << "%" << std::endl;
|
||||
std::cout << "Average Recall: " << (totalRecall / testCases.size() * 100.0) << "%" << std::endl;
|
||||
std::cout << "Average F1 Score: " << (totalF1 / testCases.size() * 100.0) << "%" << std::endl;
|
||||
std::cout << "Average Weighted Score: " << (totalWeighted / testCases.size() * 100.0) << "% (FP penalty: 2x)"
|
||||
<< std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
std::cout << "--- Overall Metrics (total counts) ---" << std::endl;
|
||||
std::cout << "True Positives: " << totalTP << std::endl;
|
||||
std::cout << "False Positives: " << totalFP << " (incorrect hyphenation points)" << std::endl;
|
||||
std::cout << "False Negatives: " << totalFN << " (missed hyphenation points)" << std::endl;
|
||||
|
||||
double overallPrecision = totalTP + totalFP > 0 ? static_cast<double>(totalTP) / (totalTP + totalFP) : 0.0;
|
||||
double overallRecall = totalTP + totalFN > 0 ? static_cast<double>(totalTP) / (totalTP + totalFN) : 0.0;
|
||||
double overallF1 = overallPrecision + overallRecall > 0
|
||||
? 2 * overallPrecision * overallRecall / (overallPrecision + overallRecall)
|
||||
: 0.0;
|
||||
|
||||
std::cout << "Overall Precision: " << (overallPrecision * 100.0) << "%" << std::endl;
|
||||
std::cout << "Overall Recall: " << (overallRecall * 100.0) << "%" << std::endl;
|
||||
std::cout << "Overall F1 Score: " << (overallF1 * 100.0) << "%" << std::endl;
|
||||
std::cout << std::endl;
|
||||
|
||||
// Filter out perfect matches from the “worst cases” section so that only actionable failures appear.
|
||||
auto hasImperfection = [](const EvaluationResult& r) { return r.weightedScore < 0.999999; };
|
||||
std::vector<std::pair<TestCase, EvaluationResult>> imperfectCases;
|
||||
imperfectCases.reserve(worstCases.size());
|
||||
for (const auto& entry : worstCases) {
|
||||
if (hasImperfection(entry.second)) {
|
||||
imperfectCases.push_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "--- Worst Cases (lowest weighted scores) ---" << std::endl;
|
||||
int showCount = std::min(10, static_cast<int>(imperfectCases.size()));
|
||||
for (int i = 0; i < showCount; i++) {
|
||||
const auto& testCase = imperfectCases[i].first;
|
||||
const auto& result = imperfectCases[i].second;
|
||||
|
||||
std::vector<size_t> actualPositions = hyphenateFunc(testCase.word);
|
||||
std::string actualHyphenated = positionsToHyphenated(testCase.word, actualPositions);
|
||||
|
||||
std::cout << "Word: " << testCase.word << " (freq: " << testCase.frequency << ")" << std::endl;
|
||||
std::cout << " Expected: " << testCase.hyphenated << std::endl;
|
||||
std::cout << " Got: " << actualHyphenated << std::endl;
|
||||
std::cout << " Precision: " << (result.precision * 100.0) << "%"
|
||||
<< " Recall: " << (result.recall * 100.0) << "%"
|
||||
<< " F1: " << (result.f1Score * 100.0) << "%"
|
||||
<< " Weighted: " << (result.weightedScore * 100.0) << "%" << std::endl;
|
||||
std::cout << " TP: " << result.truePositives << " FP: " << result.falsePositives
|
||||
<< " FN: " << result.falseNegatives << std::endl;
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
// Additional compact list of the worst ~100 words to aid iteration
|
||||
int compactCount = std::min(100, static_cast<int>(imperfectCases.size()));
|
||||
if (compactCount > 0) {
|
||||
std::cout << "--- Compact Worst Cases (" << compactCount << ") ---" << std::endl;
|
||||
for (int i = 0; i < compactCount; i++) {
|
||||
const auto& testCase = imperfectCases[i].first;
|
||||
std::vector<size_t> actualPositions = hyphenateFunc(testCase.word);
|
||||
std::string actualHyphenated = positionsToHyphenated(testCase.word, actualPositions);
|
||||
std::cout << testCase.word << " | exp:" << testCase.hyphenated << " | got:" << actualHyphenated << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
const bool summaryMode = argc <= 1;
|
||||
const std::string languageSelection = summaryMode ? "all" : argv[1];
|
||||
|
||||
std::vector<LanguageConfig> languages = resolveLanguages(languageSelection);
|
||||
if (languages.empty()) {
|
||||
std::cerr << "Unknown language: " << languageSelection << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (const auto& lang : languages) {
|
||||
const auto* hyphenator = getLanguageHyphenatorForPrimaryTag(lang.primaryTag);
|
||||
if (!hyphenator) {
|
||||
std::cerr << "No hyphenator registered for tag: " << lang.primaryTag << std::endl;
|
||||
continue;
|
||||
}
|
||||
const auto hyphenateFunc = [hyphenator](const std::string& word) {
|
||||
return hyphenateWordWithHyphenator(word, *hyphenator);
|
||||
};
|
||||
|
||||
if (!summaryMode) {
|
||||
std::cout << "Loading test data from: " << lang.testDataFile << std::endl;
|
||||
}
|
||||
std::vector<TestCase> testCases = loadTestData(lang.testDataFile);
|
||||
|
||||
if (testCases.empty()) {
|
||||
std::cerr << "No test cases loaded for " << lang.cliName << ". Skipping." << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!summaryMode) {
|
||||
std::cout << "Loaded " << testCases.size() << " test cases for " << lang.cliName << std::endl;
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
int perfectMatches = 0;
|
||||
int partialMatches = 0;
|
||||
int completeMisses = 0;
|
||||
|
||||
double totalPrecision = 0.0;
|
||||
double totalRecall = 0.0;
|
||||
double totalF1 = 0.0;
|
||||
double totalWeighted = 0.0;
|
||||
|
||||
int totalTP = 0, totalFP = 0, totalFN = 0;
|
||||
|
||||
std::vector<std::pair<TestCase, EvaluationResult>> worstCases;
|
||||
|
||||
for (const auto& testCase : testCases) {
|
||||
EvaluationResult result = evaluateWord(testCase, hyphenateFunc);
|
||||
|
||||
totalTP += result.truePositives;
|
||||
totalFP += result.falsePositives;
|
||||
totalFN += result.falseNegatives;
|
||||
|
||||
totalPrecision += result.precision;
|
||||
totalRecall += result.recall;
|
||||
totalF1 += result.f1Score;
|
||||
totalWeighted += result.weightedScore;
|
||||
|
||||
if (result.f1Score == 1.0) {
|
||||
perfectMatches++;
|
||||
} else if (result.f1Score > 0.0) {
|
||||
partialMatches++;
|
||||
} else {
|
||||
completeMisses++;
|
||||
}
|
||||
|
||||
worstCases.push_back({testCase, result});
|
||||
}
|
||||
|
||||
if (summaryMode) {
|
||||
const double averageF1Percent = testCases.empty() ? 0.0 : (totalF1 / testCases.size() * 100.0);
|
||||
std::cout << lang.cliName << ": " << averageF1Percent << "%" << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::sort(worstCases.begin(), worstCases.end(),
|
||||
[](const auto& a, const auto& b) { return a.second.weightedScore < b.second.weightedScore; });
|
||||
|
||||
printResults(lang.cliName, testCases, worstCases, perfectMatches, partialMatches, completeMisses, totalPrecision,
|
||||
totalRecall, totalF1, totalWeighted, totalTP, totalFP, totalFN, hyphenateFunc);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
5012
test/hyphenation_eval/resources/english_hyphenation_tests.txt
Normal file
5012
test/hyphenation_eval/resources/english_hyphenation_tests.txt
Normal file
File diff suppressed because it is too large
Load Diff
5012
test/hyphenation_eval/resources/french_hyphenation_tests.txt
Normal file
5012
test/hyphenation_eval/resources/french_hyphenation_tests.txt
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,232 @@
|
||||
"""
|
||||
Generate hyphenation test data from a text file.
|
||||
|
||||
This script extracts unique words from a book and generates ground truth
|
||||
hyphenations using the pyphen library, which can be used to test and validate
|
||||
the hyphenation implementations (e.g., German, English, Russian).
|
||||
|
||||
Usage:
|
||||
python generate_hyphenation_test_data.py <input_file> <output_file>
|
||||
[--language de_DE] [--max-words 5000] [--min-prefix 2] [--min-suffix 2]
|
||||
|
||||
Requirements:
|
||||
pip install pyphen
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import re
|
||||
from collections import Counter
|
||||
import pyphen
|
||||
from pathlib import Path
|
||||
import zipfile
|
||||
|
||||
|
||||
def extract_text_from_epub(epub_path):
|
||||
"""Extract textual content from an .epub archive by concatenating HTML/XHTML files."""
|
||||
texts = []
|
||||
with zipfile.ZipFile(epub_path, "r") as z:
|
||||
for name in z.namelist():
|
||||
lower = name.lower()
|
||||
if (
|
||||
lower.endswith(".xhtml")
|
||||
or lower.endswith(".html")
|
||||
or lower.endswith(".htm")
|
||||
):
|
||||
try:
|
||||
data = z.read(name).decode("utf-8", errors="ignore")
|
||||
except Exception:
|
||||
continue
|
||||
# Remove tags
|
||||
text = re.sub(r"<[^>]+>", " ", data)
|
||||
texts.append(text)
|
||||
return "\n".join(texts)
|
||||
|
||||
|
||||
def extract_words(text):
|
||||
"""Extract all words from text, preserving original case."""
|
||||
# Match runs of Unicode letters (any script) while excluding digits/underscores
|
||||
return re.findall(r"[^\W\d_]+", text, flags=re.UNICODE)
|
||||
|
||||
|
||||
def clean_word(word):
|
||||
"""Normalize word for hyphenation testing."""
|
||||
# Keep original case but strip any non-letter characters
|
||||
return word.strip()
|
||||
|
||||
|
||||
def generate_hyphenation_data(
|
||||
input_file,
|
||||
output_file,
|
||||
language="de_DE",
|
||||
min_length=6,
|
||||
max_words=5000,
|
||||
min_prefix=2,
|
||||
min_suffix=2,
|
||||
):
|
||||
"""
|
||||
Generate hyphenation test data from a text file.
|
||||
|
||||
Args:
|
||||
input_file: Path to input text file
|
||||
output_file: Path to output file with hyphenation data
|
||||
language: Language code for pyphen (e.g., 'de_DE', 'en_US')
|
||||
min_length: Minimum word length to include
|
||||
max_words: Maximum number of words to include (default: 5000)
|
||||
min_prefix: Minimum characters allowed before the first hyphen (default: 2)
|
||||
min_suffix: Minimum characters allowed after the last hyphen (default: 2)
|
||||
"""
|
||||
print(f"Reading from: {input_file}")
|
||||
|
||||
# Read the input file
|
||||
if str(input_file).lower().endswith(".epub"):
|
||||
print("Detected .epub input; extracting HTML content")
|
||||
text = extract_text_from_epub(input_file)
|
||||
else:
|
||||
with open(input_file, "r", encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
|
||||
# Extract words
|
||||
print("Extracting words...")
|
||||
words = extract_words(text)
|
||||
print(f"Found {len(words)} total words")
|
||||
|
||||
# Count word frequencies
|
||||
word_counts = Counter(words)
|
||||
print(f"Found {len(word_counts)} unique words")
|
||||
|
||||
# Initialize pyphen hyphenator
|
||||
print(
|
||||
f"Initializing hyphenator for language: {language} (min_prefix={min_prefix}, min_suffix={min_suffix})"
|
||||
)
|
||||
try:
|
||||
hyphenator = pyphen.Pyphen(lang=language, left=min_prefix, right=min_suffix)
|
||||
except KeyError:
|
||||
print(f"Error: Language '{language}' not found in pyphen.")
|
||||
print("Available languages include: de_DE, en_US, en_GB, fr_FR, etc.")
|
||||
return
|
||||
|
||||
# Generate hyphenations
|
||||
print("Generating hyphenations...")
|
||||
hyphenation_data = []
|
||||
|
||||
# Sort by frequency (most common first) then alphabetically
|
||||
sorted_words = sorted(word_counts.items(), key=lambda x: (-x[1], x[0].lower()))
|
||||
|
||||
for word, count in sorted_words:
|
||||
# Filter by minimum length
|
||||
if len(word) < min_length:
|
||||
continue
|
||||
|
||||
# Get hyphenation (may produce no '=' characters)
|
||||
hyphenated = hyphenator.inserted(word, hyphen="=")
|
||||
|
||||
# Include all words (so we can take the top N most common words even if
|
||||
# they don't have hyphenation points). This replaces the previous filter
|
||||
# which dropped words without '='.
|
||||
hyphenation_data.append(
|
||||
{"word": word, "hyphenated": hyphenated, "count": count}
|
||||
)
|
||||
|
||||
# Stop if we've reached max_words
|
||||
if max_words and len(hyphenation_data) >= max_words:
|
||||
break
|
||||
|
||||
print(f"Generated {len(hyphenation_data)} hyphenated words")
|
||||
|
||||
# Write output file
|
||||
print(f"Writing to: {output_file}")
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
# Write header with metadata
|
||||
f.write(f"# Hyphenation Test Data\n")
|
||||
f.write(f"# Source: {Path(input_file).name}\n")
|
||||
f.write(f"# Language: {language}\n")
|
||||
f.write(f"# Min prefix: {min_prefix}\n")
|
||||
f.write(f"# Min suffix: {min_suffix}\n")
|
||||
f.write(f"# Total words: {len(hyphenation_data)}\n")
|
||||
f.write(f"# Format: word | hyphenated_form | frequency_in_source\n")
|
||||
f.write(f"#\n")
|
||||
f.write(f"# Hyphenation points are marked with '='\n")
|
||||
f.write(f"# Example: Silbentrennung -> Sil=ben=tren=nung\n")
|
||||
f.write(f"#\n\n")
|
||||
|
||||
# Write data
|
||||
for item in hyphenation_data:
|
||||
f.write(f"{item['word']}|{item['hyphenated']}|{item['count']}\n")
|
||||
|
||||
print("Done!")
|
||||
|
||||
# Print some statistics
|
||||
print("\n=== Statistics ===")
|
||||
print(f"Total unique words extracted: {len(word_counts)}")
|
||||
print(f"Words with hyphenation points: {len(hyphenation_data)}")
|
||||
print(
|
||||
f"Average hyphenation points per word: {sum(h['hyphenated'].count('=') for h in hyphenation_data) / len(hyphenation_data):.2f}"
|
||||
)
|
||||
|
||||
# Print some examples
|
||||
print("\n=== Examples (first 10) ===")
|
||||
for item in hyphenation_data[:10]:
|
||||
print(
|
||||
f" {item['word']:20} -> {item['hyphenated']:30} (appears {item['count']}x)"
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate hyphenation test data from a text file",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# Generate test data from a German book
|
||||
python generate_hyphenation_test_data.py ../data/books/bobiverse_1.txt hyphenation_test_data.txt
|
||||
|
||||
# Limit to 500 most common words
|
||||
python generate_hyphenation_test_data.py ../data/books/bobiverse_1.txt hyphenation_test_data.txt --max-words 500
|
||||
|
||||
# Use English hyphenation (when available)
|
||||
python generate_hyphenation_test_data.py book.txt test_en.txt --language en_US
|
||||
""",
|
||||
)
|
||||
|
||||
parser.add_argument("input_file", help="Input text file to extract words from")
|
||||
parser.add_argument("output_file", help="Output file for hyphenation test data")
|
||||
parser.add_argument(
|
||||
"--language", default="de_DE", help="Language code (default: de_DE)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--min-length", type=int, default=6, help="Minimum word length (default: 6)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--max-words",
|
||||
type=int,
|
||||
default=5000,
|
||||
help="Maximum number of words to include (default: 5000)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--min-prefix",
|
||||
type=int,
|
||||
default=2,
|
||||
help="Minimum characters permitted before the first hyphen (default: 2)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--min-suffix",
|
||||
type=int,
|
||||
default=2,
|
||||
help="Minimum characters permitted after the last hyphen (default: 2)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
generate_hyphenation_data(
|
||||
args.input_file,
|
||||
args.output_file,
|
||||
language=args.language,
|
||||
min_length=args.min_length,
|
||||
max_words=args.max_words,
|
||||
min_prefix=args.min_prefix,
|
||||
min_suffix=args.min_suffix,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
5012
test/hyphenation_eval/resources/german_hyphenation_tests.txt
Normal file
5012
test/hyphenation_eval/resources/german_hyphenation_tests.txt
Normal file
File diff suppressed because it is too large
Load Diff
5012
test/hyphenation_eval/resources/russian_hyphenation_tests.txt
Normal file
5012
test/hyphenation_eval/resources/russian_hyphenation_tests.txt
Normal file
File diff suppressed because it is too large
Load Diff
32
test/run_hyphenation_eval.sh
Executable file
32
test/run_hyphenation_eval.sh
Executable file
@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
BUILD_DIR="$ROOT_DIR/build/hyphenation_eval"
|
||||
BINARY="$BUILD_DIR/HyphenationEvaluationTest"
|
||||
|
||||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
SOURCES=(
|
||||
"$ROOT_DIR/test/hyphenation_eval/HyphenationEvaluationTest.cpp"
|
||||
"$ROOT_DIR/lib/Epub/Epub/hyphenation/Hyphenator.cpp"
|
||||
"$ROOT_DIR/lib/Epub/Epub/hyphenation/LanguageRegistry.cpp"
|
||||
"$ROOT_DIR/lib/Epub/Epub/hyphenation/LiangHyphenation.cpp"
|
||||
"$ROOT_DIR/lib/Epub/Epub/hyphenation/HyphenationCommon.cpp"
|
||||
"$ROOT_DIR/lib/Utf8/Utf8.cpp"
|
||||
)
|
||||
|
||||
CXXFLAGS=(
|
||||
-std=c++20
|
||||
-O2
|
||||
-Wall
|
||||
-Wextra
|
||||
-pedantic
|
||||
-I"$ROOT_DIR"
|
||||
-I"$ROOT_DIR/lib"
|
||||
-I"$ROOT_DIR/lib/Utf8"
|
||||
)
|
||||
|
||||
c++ "${CXXFLAGS[@]}" "${SOURCES[@]}" -o "$BINARY"
|
||||
|
||||
"$BINARY" "$@"
|
||||
Loading…
x
Reference in New Issue
Block a user