Merge branch 'master' into feature/extend-image
This commit is contained in:
commit
077526a126
26
.github/workflows/pr-formatting-check.yml
vendored
Normal file
26
.github/workflows/pr-formatting-check.yml
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
name: "PR Formatting"
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- edited
|
||||
|
||||
permissions:
|
||||
statuses: write
|
||||
|
||||
jobs:
|
||||
title-check:
|
||||
name: Title Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden Runner
|
||||
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Check PR Title
|
||||
uses: amannn/action-semantic-pull-request@v6
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -4,3 +4,5 @@
|
||||
.vscode
|
||||
lib/EpdFont/fontsrc
|
||||
*.generated.h
|
||||
build
|
||||
**/__pycache__/
|
||||
@ -20,9 +20,10 @@ Button layout can be customized in **[Settings](#35-settings)**.
|
||||
|
||||
### Power On / Off
|
||||
|
||||
To turn the device on or off, **press and hold the Power button for half a second**. In **[Settings](#35-settings)** you can configure the power button to trigger on a short press instead of a long one.
|
||||
To turn the device on or off, **press and hold the Power button for approximately half a second**.
|
||||
In **[Settings](#35-settings)** you can configure the power button to turn the device off with a short press instead of a long one.
|
||||
|
||||
To reboot the device (for example if it's frozen, or after a firmware update), press and release the Reset button, and then hold the Power button for a few seconds.
|
||||
To reboot the device (for example if it's frozen, or after a firmware update), press and release the Reset button, and then quickly press and hold the Power button for a few seconds.
|
||||
|
||||
### First Launch
|
||||
|
||||
@ -63,18 +64,29 @@ See the [webserver docs](./docs/webserver.md) for more information on how to con
|
||||
|
||||
The Settings screen allows you to configure the device's behavior. There are a few settings you can adjust:
|
||||
- **Sleep Screen**: Which sleep screen to display when the device sleeps:
|
||||
- "Dark" (default) - The default dark sleep screen
|
||||
- "Dark" (default) - The default dark Crosspoint logo sleep screen
|
||||
- "Light" - The same default sleep screen, on a white background
|
||||
- "Custom" - Custom images from the SD card, see [Sleep Screen](#36-sleep-screen) below for more information
|
||||
- "Custom" - Custom images from the SD card; see [Sleep Screen](#36-sleep-screen) below for more information
|
||||
- "Cover" - The book cover image (Note: this is experimental and may not work as expected)
|
||||
- "Blank" - A blank screen
|
||||
- "None" - A blank screen
|
||||
- **Sleep Screen Cover Mode**: How to display the book cover when "Cover" sleep screen is selected:
|
||||
- "Fit" (default) - Scale the image down to fit centered on the screen, padding with white borders as necessary
|
||||
- "Crop" - Scale the image down and crop as necessary to try to to fill the screen (Note: this is experimental and may not work as expected)
|
||||
- **Status Bar**: Configure the status bar displayed while reading:
|
||||
- "None" - No status bar
|
||||
- "No Progress" - Show status bar without reading progress
|
||||
- "Full" - Show status bar with reading progress
|
||||
- **Hide Battery %**: Configure where to suppress the battery pecentage display in the status bar; the battery icon will still be shown:
|
||||
- "Never" - Always show battery percentage (default)
|
||||
- "In Reader" - Show battery percentage everywhere except in reading mode
|
||||
- "Always" - Always hide battery percentage
|
||||
- **Extra Paragraph Spacing**: If enabled, vertical space will be added between paragraphs in the book. If disabled, paragraphs will not have vertical space between them, but will have first-line indentation.
|
||||
- **Short Power Button Click**: Whether to trigger the power button on a short press or a long press.
|
||||
- **Reading Orientation**: Set the screen orientation for reading:
|
||||
- **Text Anti-Aliasing**: Whether to show smooth grey edges (anti-aliasing) on text in reading mode. Note this slows down page turns slightly.
|
||||
- **Short Power Button Click**: Controls the effect of a short click of the power button:
|
||||
- "Ignore" - Require a long press to turn off the device
|
||||
- "Sleep" - A short press powers the device off
|
||||
- "Page Turn" - A short press in reading mode turns to the next page; a long press turns the device off
|
||||
- **Reading Orientation**: Set the screen orientation for reading EPUB files:
|
||||
- "Portrait" (default) - Standard portrait orientation
|
||||
- "Landscape CW" - Landscape, rotated clockwise
|
||||
- "Inverted" - Portrait, upside down
|
||||
@ -83,16 +95,22 @@ The Settings screen allows you to configure the device's behavior. There are a f
|
||||
- Back, Confirm, Left, Right (default)
|
||||
- Left, Right, Back, Confirm
|
||||
- Left, Back, Confirm, Right
|
||||
- **Side Button Layout**: Swap the order of the up and down volume buttons from Previous/Next to Next/Previous. This change is only in effect when reading.
|
||||
- **Side Button Layout (reader)**: Swap the order of the up and down volume buttons from Previous/Next to Next/Previous. This change is only in effect when reading.
|
||||
- **Long-press Chapter Skip**: Set whether long-pressing page turn buttons skip to the next/previous chapter.
|
||||
- "Chapter Skip" (default) - Long-pressing skips to next/previous chapter
|
||||
- "Page Scroll" - Long-pressing scrolls a page up/down
|
||||
- Swap the order of the up and down volume buttons from Previous/Next to Next/Previous. This change is only in effect when reading.
|
||||
- **Reader Font Family**: Choose the font used for reading:
|
||||
- "Bookerly" (default) - Amazon's reading font
|
||||
- "Noto Sans" - Google's sans-serif font
|
||||
- "Open Dyslexic" - Font designed for readers with dyslexia
|
||||
- **Reader Font Size**: Adjust the text size for reading; options are "Small", "Medium", "Large", or "X Large".
|
||||
- **Reader Line Spacing**: Adjust the spacing between lines; options are "Tight", "Normal", or "Wide".
|
||||
- **Reader Screen Margin**: Controls the screen margins in reader mode between 5 and 40 pixels in 5 pixel increments.
|
||||
- **Reader Paragraph Alignment**: Set the alignment of paragraphs; options are "Justified" (default), "Left", "Center", or "Right".
|
||||
- **Time to Sleep**: Set the duration of inactivity before the device automatically goes to sleep.
|
||||
- **Refresh Frequency**: Set how often the screen does a full refresh while reading to reduce ghosting.
|
||||
- **Calibre Settings**: Set up integration for accessing a Calibre web library or connecting to Calibre as a wireless device.
|
||||
- **Check for updates**: Check for firmware updates over WiFi.
|
||||
|
||||
### 3.6 Sleep Screen
|
||||
@ -124,10 +142,15 @@ Once you have opened a book, the button layout changes to facilitate reading.
|
||||
|
||||
The role of the volume (side) buttons can be swapped in **[Settings](#35-settings)**.
|
||||
|
||||
If the **Short Power Button Click** setting is set to "Page Turn", you can also turn to the next page by briefly pressing the Power button.
|
||||
|
||||
### Chapter Navigation
|
||||
* **Next Chapter:** Press and **hold** the **Right** (or **Volume Down**) button briefly, then release.
|
||||
* **Previous Chapter:** Press and **hold** the **Left** (or **Volume Up**) button briefly, then release.
|
||||
|
||||
This feature can be disabled in **[Settings](#35-settings)** to help avoid changing chapters by mistake.
|
||||
|
||||
|
||||
### System Navigation
|
||||
* **Return to Book Selection:** Press **Back** to close the book and return to the **[Book Selection](#32-book-selection)** screen.
|
||||
* **Return to Home:** Press and **hold** the **Back** button to close the book and return to the **[Home](#31-home-screen)** screen.
|
||||
|
||||
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;
|
||||
|
||||
@ -348,6 +349,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 = std::string("cover") + (cropped ? "_crop" : "");
|
||||
return cachePath + "/" + coverFileName + ".bmp";
|
||||
@ -409,6 +419,70 @@ bool Epub::generateCoverBmp(bool cropped) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string Epub::getThumbBmpPath() const { return cachePath + "/thumb.bmp"; }
|
||||
|
||||
bool Epub::generateThumbBmp() const {
|
||||
// Already generated, return true
|
||||
if (SdMan.exists(getThumbBmpPath().c_str())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!bookMetadataCache || !bookMetadataCache->isLoaded()) {
|
||||
Serial.printf("[%lu] [EBP] Cannot generate thumb BMP, cache not loaded\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto coverImageHref = bookMetadataCache->coreMetadata.coverItemHref;
|
||||
if (coverImageHref.empty()) {
|
||||
Serial.printf("[%lu] [EBP] No known cover image for thumbnail\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (coverImageHref.substr(coverImageHref.length() - 4) == ".jpg" ||
|
||||
coverImageHref.substr(coverImageHref.length() - 5) == ".jpeg") {
|
||||
Serial.printf("[%lu] [EBP] Generating thumb BMP from JPG cover image\n", millis());
|
||||
const auto coverJpgTempPath = getCachePath() + "/.cover.jpg";
|
||||
|
||||
FsFile coverJpg;
|
||||
if (!SdMan.openFileForWrite("EBP", coverJpgTempPath, coverJpg)) {
|
||||
return false;
|
||||
}
|
||||
readItemContentsToStream(coverImageHref, coverJpg, 1024);
|
||||
coverJpg.close();
|
||||
|
||||
if (!SdMan.openFileForRead("EBP", coverJpgTempPath, coverJpg)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FsFile thumbBmp;
|
||||
if (!SdMan.openFileForWrite("EBP", getThumbBmpPath(), thumbBmp)) {
|
||||
coverJpg.close();
|
||||
return false;
|
||||
}
|
||||
// Use smaller target size for Continue Reading card (half of screen: 240x400)
|
||||
// Generate 1-bit BMP for fast home screen rendering (no gray passes needed)
|
||||
constexpr int THUMB_TARGET_WIDTH = 240;
|
||||
constexpr int THUMB_TARGET_HEIGHT = 400;
|
||||
const bool success = JpegToBmpConverter::jpegFileTo1BitBmpStreamWithSize(coverJpg, thumbBmp, THUMB_TARGET_WIDTH,
|
||||
THUMB_TARGET_HEIGHT);
|
||||
coverJpg.close();
|
||||
thumbBmp.close();
|
||||
SdMan.remove(coverJpgTempPath.c_str());
|
||||
|
||||
if (!success) {
|
||||
Serial.printf("[%lu] [EBP] Failed to generate thumb BMP from JPG cover image\n", millis());
|
||||
SdMan.remove(getThumbBmpPath().c_str());
|
||||
}
|
||||
Serial.printf("[%lu] [EBP] Generated thumb BMP from JPG cover image, success: %s\n", millis(),
|
||||
success ? "yes" : "no");
|
||||
return success;
|
||||
} else {
|
||||
Serial.printf("[%lu] [EBP] Cover image is not a JPG, skipping thumbnail\n", millis());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t* Epub::readItemContentsToBytes(const std::string& itemHref, size_t* size, const bool trailingNullByte) const {
|
||||
if (itemHref.empty()) {
|
||||
Serial.printf("[%lu] [EBP] Failed to read item, empty href\n", millis());
|
||||
@ -545,14 +619,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);
|
||||
}
|
||||
|
||||
@ -44,8 +44,11 @@ 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;
|
||||
bool generateThumbBmp() const;
|
||||
uint8_t* readItemContentsToBytes(const std::string& itemHref, size_t* size = nullptr,
|
||||
bool trailingNullByte = false) const;
|
||||
bool readItemContentsToStream(const std::string& itemHref, Print& out, size_t chunkSize) const;
|
||||
@ -60,5 +63,5 @@ class Epub {
|
||||
int getSpineIndexForTextReference() const;
|
||||
|
||||
size_t getBookSize() const;
|
||||
uint8_t calculateProgress(int currentSpineIndex, float currentSpineRead) const;
|
||||
float calculateProgress(int currentSpineIndex, float currentSpineRead) const;
|
||||
};
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
#include "FsHelpers.h"
|
||||
|
||||
namespace {
|
||||
constexpr uint8_t BOOK_CACHE_VERSION = 4;
|
||||
constexpr uint8_t BOOK_CACHE_VERSION = 5;
|
||||
constexpr char bookBinFile[] = "/book.bin";
|
||||
constexpr char tmpSpineBinFile[] = "/spine.bin.tmp";
|
||||
constexpr char tmpTocBinFile[] = "/toc.bin.tmp";
|
||||
@ -87,8 +87,9 @@ bool BookMetadataCache::buildBookBin(const std::string& epubPath, const BookMeta
|
||||
|
||||
constexpr uint32_t headerASize =
|
||||
sizeof(BOOK_CACHE_VERSION) + /* LUT Offset */ sizeof(uint32_t) + sizeof(spineCount) + sizeof(tocCount);
|
||||
const uint32_t metadataSize = metadata.title.size() + metadata.author.size() + metadata.coverItemHref.size() +
|
||||
metadata.textReferenceHref.size() + sizeof(uint32_t) * 4;
|
||||
const uint32_t metadataSize = metadata.title.size() + metadata.author.size() + metadata.language.size() +
|
||||
metadata.coverItemHref.size() + metadata.textReferenceHref.size() +
|
||||
sizeof(uint32_t) * 5;
|
||||
const uint32_t lutSize = sizeof(uint32_t) * spineCount + sizeof(uint32_t) * tocCount;
|
||||
const uint32_t lutOffset = headerASize + metadataSize;
|
||||
|
||||
@ -100,6 +101,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);
|
||||
|
||||
@ -289,6 +291,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);
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ class BookMetadataCache {
|
||||
struct BookMetadata {
|
||||
std::string title;
|
||||
std::string author;
|
||||
std::string language;
|
||||
std::string coverItemHref;
|
||||
std::string textReferenceHref;
|
||||
};
|
||||
|
||||
@ -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) {
|
||||
if (word.empty()) return;
|
||||
|
||||
@ -25,10 +64,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) {
|
||||
@ -42,17 +90,11 @@ std::vector<uint16_t> ParsedText::calculateWordWidths(const GfxRenderer& rendere
|
||||
std::vector<uint16_t> wordWidths;
|
||||
wordWidths.reserve(totalWordCount);
|
||||
|
||||
// add em-space at the beginning of first word in paragraph to indent
|
||||
if (!extraParagraphSpacing) {
|
||||
std::string& first_word = words.front();
|
||||
first_word.insert(0, "\xe2\x80\x83");
|
||||
}
|
||||
|
||||
auto wordsIt = words.begin();
|
||||
auto wordStylesIt = wordStyles.begin();
|
||||
|
||||
while (wordsIt != words.end()) {
|
||||
wordWidths.push_back(renderer.getTextWidth(fontId, wordsIt->c_str(), *wordStylesIt));
|
||||
wordWidths.push_back(measureWordWidth(renderer, fontId, *wordsIt, *wordStylesIt));
|
||||
|
||||
std::advance(wordsIt, 1);
|
||||
std::advance(wordStylesIt, 1);
|
||||
@ -61,8 +103,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
|
||||
@ -140,6 +195,138 @@ std::vector<size_t> ParsedText::computeLineBreaks(const int pageWidth, const int
|
||||
return lineBreakIndices;
|
||||
}
|
||||
|
||||
void ParsedText::applyParagraphIndent() {
|
||||
if (extraParagraphSpacing || words.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -191,5 +378,11 @@ void ParsedText::extractLine(const size_t breakIndex, const int pageWidth, const
|
||||
std::list<EpdFontFamily::Style> lineWordStyles;
|
||||
lineWordStyles.splice(lineWordStyles.begin(), wordStyles, wordStyles.begin(), wordStyleEndIt);
|
||||
|
||||
for (auto& word : lineWords) {
|
||||
if (containsSoftHyphen(word)) {
|
||||
stripSoftHyphensInPlace(word);
|
||||
}
|
||||
}
|
||||
|
||||
processLine(std::make_shared<TextBlock>(std::move(lineWords), std::move(lineXPos), std::move(lineWordStyles), style));
|
||||
}
|
||||
}
|
||||
@ -17,16 +17,24 @@ class ParsedText {
|
||||
std::list<EpdFontFamily::Style> wordStyles;
|
||||
TextBlock::Style style;
|
||||
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);
|
||||
std::vector<uint16_t> calculateWordWidths(const GfxRenderer& renderer, int fontId);
|
||||
|
||||
public:
|
||||
explicit ParsedText(const TextBlock::Style style, const bool extraParagraphSpacing)
|
||||
: style(style), extraParagraphSpacing(extraParagraphSpacing) {}
|
||||
explicit ParsedText(const TextBlock::Style style, const bool extraParagraphSpacing,
|
||||
const bool hyphenationEnabled = false)
|
||||
: style(style), extraParagraphSpacing(extraParagraphSpacing), hyphenationEnabled(hyphenationEnabled) {}
|
||||
~ParsedText() = default;
|
||||
|
||||
void addWord(std::string word, EpdFontFamily::Style fontStyle);
|
||||
@ -37,4 +45,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 = 9;
|
||||
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,
|
||||
viewportHeight, hyphenationEnabled,
|
||||
[this, &lut](std::unique_ptr<Page> page) { lut.emplace_back(this->onPageComplete(std::move(page))); },
|
||||
progressFn);
|
||||
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
@ -25,7 +25,7 @@ constexpr int NUM_ITALIC_TAGS = sizeof(ITALIC_TAGS) / sizeof(ITALIC_TAGS[0]);
|
||||
const char* IMAGE_TAGS[] = {"img"};
|
||||
constexpr int NUM_IMAGE_TAGS = sizeof(IMAGE_TAGS) / sizeof(IMAGE_TAGS[0]);
|
||||
|
||||
const char* SKIP_TAGS[] = {"head", "table"};
|
||||
const char* SKIP_TAGS[] = {"head"};
|
||||
constexpr int NUM_SKIP_TAGS = sizeof(SKIP_TAGS) / sizeof(SKIP_TAGS[0]);
|
||||
|
||||
bool isWhitespace(const char c) { return c == ' ' || c == '\r' || c == '\n' || c == '\t'; }
|
||||
@ -51,7 +51,7 @@ void ChapterHtmlSlimParser::startNewTextBlock(const TextBlock::Style style) {
|
||||
|
||||
makePages();
|
||||
}
|
||||
currentTextBlock.reset(new ParsedText(style, extraParagraphSpacing));
|
||||
currentTextBlock.reset(new ParsedText(style, extraParagraphSpacing, hyphenationEnabled));
|
||||
}
|
||||
|
||||
void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char* name, const XML_Char** atts) {
|
||||
@ -63,13 +63,44 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
||||
return;
|
||||
}
|
||||
|
||||
if (matches(name, IMAGE_TAGS, NUM_IMAGE_TAGS)) {
|
||||
// TODO: Start processing image tags
|
||||
// Special handling for tables - show placeholder text instead of dropping silently
|
||||
if (strcmp(name, "table") == 0) {
|
||||
// Add placeholder text
|
||||
self->startNewTextBlock(TextBlock::CENTER_ALIGN);
|
||||
if (self->currentTextBlock) {
|
||||
self->currentTextBlock->addWord("[Table omitted]", EpdFontFamily::ITALIC);
|
||||
}
|
||||
|
||||
// Skip table contents
|
||||
self->skipUntilDepth = self->depth;
|
||||
self->depth += 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (matches(name, IMAGE_TAGS, NUM_IMAGE_TAGS)) {
|
||||
// TODO: Start processing image tags
|
||||
std::string alt;
|
||||
if (atts != nullptr) {
|
||||
for (int i = 0; atts[i]; i += 2) {
|
||||
if (strcmp(atts[i], "alt") == 0) {
|
||||
alt = "[Image: " + std::string(atts[i + 1]) + "]";
|
||||
}
|
||||
}
|
||||
Serial.printf("[%lu] [EHP] Image alt: %s\n", millis(), alt.c_str());
|
||||
|
||||
self->startNewTextBlock(TextBlock::CENTER_ALIGN);
|
||||
self->italicUntilDepth = min(self->italicUntilDepth, self->depth);
|
||||
self->depth += 1;
|
||||
self->characterData(userData, alt.c_str(), alt.length());
|
||||
|
||||
} else {
|
||||
// Skip for now
|
||||
self->skipUntilDepth = self->depth;
|
||||
self->depth += 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (matches(name, SKIP_TAGS, NUM_SKIP_TAGS)) {
|
||||
// start skip
|
||||
self->skipUntilDepth = self->depth;
|
||||
@ -97,6 +128,9 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
||||
self->startNewTextBlock(self->currentTextBlock->getStyle());
|
||||
} else {
|
||||
self->startNewTextBlock((TextBlock::Style)self->paragraphAlignment);
|
||||
if (strcmp(name, "li") == 0) {
|
||||
self->currentTextBlock->addWord("\xe2\x80\xa2", EpdFontFamily::REGULAR);
|
||||
}
|
||||
}
|
||||
} else if (matches(name, BOLD_TAGS, NUM_BOLD_TAGS)) {
|
||||
self->boldUntilDepth = std::min(self->boldUntilDepth, self->depth);
|
||||
@ -136,18 +170,17 @@ 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);
|
||||
const XML_Char FEFF_BYTE_3 = static_cast<XML_Char>(0xBF);
|
||||
|
||||
if (s[i] == FEFF_BYTE_1) {
|
||||
// Check if the next two bytes complete the 3-byte sequence
|
||||
if ((i + 2 < len) && (s[i + 1] == FEFF_BYTE_2) && (s[i + 2] == FEFF_BYTE_3)) {
|
||||
// Sequence 0xEF 0xBB 0xBF found!
|
||||
i += 2; // Skip the next two bytes
|
||||
continue; // Move to the next iteration
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -36,6 +36,7 @@ class ChapterHtmlSlimParser {
|
||||
uint8_t paragraphAlignment;
|
||||
uint16_t viewportWidth;
|
||||
uint16_t viewportHeight;
|
||||
bool hyphenationEnabled;
|
||||
|
||||
void startNewTextBlock(TextBlock::Style style);
|
||||
void makePages();
|
||||
@ -48,7 +49,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)
|
||||
: filepath(filepath),
|
||||
@ -59,6 +60,7 @@ class ChapterHtmlSlimParser {
|
||||
paragraphAlignment(paragraphAlignment),
|
||||
viewportWidth(viewportWidth),
|
||||
viewportHeight(viewportHeight),
|
||||
hyphenationEnabled(hyphenationEnabled),
|
||||
completePageFn(completePageFn),
|
||||
progressFn(progressFn) {}
|
||||
~ChapterHtmlSlimParser() = default;
|
||||
|
||||
@ -107,6 +107,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)) {
|
||||
@ -266,6 +271,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) {
|
||||
@ -300,6 +310,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;
|
||||
|
||||
@ -13,6 +13,7 @@ class ContentOpfParser final : public Print {
|
||||
IN_METADATA,
|
||||
IN_BOOK_TITLE,
|
||||
IN_BOOK_AUTHOR,
|
||||
IN_BOOK_LANGUAGE,
|
||||
IN_MANIFEST,
|
||||
IN_SPINE,
|
||||
IN_GUIDE,
|
||||
@ -34,6 +35,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;
|
||||
|
||||
@ -228,7 +228,10 @@ BmpReaderError Bitmap::readNextRow(uint8_t* data, uint8_t* rowBuffer) const {
|
||||
}
|
||||
case 1: {
|
||||
for (int x = 0; x < width; x++) {
|
||||
lum = (rowBuffer[x >> 3] & (0x80 >> (x & 7))) ? 0xFF : 0x00;
|
||||
// Get palette index (0 or 1) from bit at position x
|
||||
const uint8_t palIndex = (rowBuffer[x >> 3] & (0x80 >> (x & 7))) ? 1 : 0;
|
||||
// Use palette lookup for proper black/white mapping
|
||||
lum = paletteLum[palIndex];
|
||||
packPixel(lum);
|
||||
}
|
||||
break;
|
||||
|
||||
@ -42,6 +42,8 @@ class Bitmap {
|
||||
bool isTopDown() const { return topDown; }
|
||||
bool hasGreyscale() const { return bpp > 1; }
|
||||
int getRowBytes() const { return rowBytes; }
|
||||
bool is1Bit() const { return bpp == 1; }
|
||||
uint16_t getBpp() const { return bpp; }
|
||||
|
||||
private:
|
||||
static uint16_t readLE16(FsFile& f);
|
||||
|
||||
@ -88,3 +88,19 @@ uint8_t quantize(int gray, int x, int y) {
|
||||
return quantizeSimple(gray);
|
||||
}
|
||||
}
|
||||
|
||||
// 1-bit noise dithering for fast home screen rendering
|
||||
// Uses hash-based noise for consistent dithering that works well at small sizes
|
||||
uint8_t quantize1bit(int gray, int x, int y) {
|
||||
gray = adjustPixel(gray);
|
||||
|
||||
// Generate noise threshold using integer hash (no regular pattern to alias)
|
||||
uint32_t hash = static_cast<uint32_t>(x) * 374761393u + static_cast<uint32_t>(y) * 668265263u;
|
||||
hash = (hash ^ (hash >> 13)) * 1274126177u;
|
||||
const int threshold = static_cast<int>(hash >> 24); // 0-255
|
||||
|
||||
// Simple threshold with noise: gray >= (128 + noise offset) -> white
|
||||
// The noise adds variation around the 128 midpoint
|
||||
const int adjustedThreshold = 128 + ((threshold - 128) / 2); // Range: 64-192
|
||||
return (gray >= adjustedThreshold) ? 1 : 0;
|
||||
}
|
||||
|
||||
@ -5,8 +5,89 @@
|
||||
// Helper functions
|
||||
uint8_t quantize(int gray, int x, int y);
|
||||
uint8_t quantizeSimple(int gray);
|
||||
uint8_t quantize1bit(int gray, int x, int y);
|
||||
int adjustPixel(int gray);
|
||||
|
||||
// 1-bit Atkinson dithering - better quality than noise dithering for thumbnails
|
||||
// Error distribution pattern (same as 2-bit but quantizes to 2 levels):
|
||||
// X 1/8 1/8
|
||||
// 1/8 1/8 1/8
|
||||
// 1/8
|
||||
class Atkinson1BitDitherer {
|
||||
public:
|
||||
explicit Atkinson1BitDitherer(int width) : width(width) {
|
||||
errorRow0 = new int16_t[width + 4](); // Current row
|
||||
errorRow1 = new int16_t[width + 4](); // Next row
|
||||
errorRow2 = new int16_t[width + 4](); // Row after next
|
||||
}
|
||||
|
||||
~Atkinson1BitDitherer() {
|
||||
delete[] errorRow0;
|
||||
delete[] errorRow1;
|
||||
delete[] errorRow2;
|
||||
}
|
||||
|
||||
// EXPLICITLY DELETE THE COPY CONSTRUCTOR
|
||||
Atkinson1BitDitherer(const Atkinson1BitDitherer& other) = delete;
|
||||
|
||||
// EXPLICITLY DELETE THE COPY ASSIGNMENT OPERATOR
|
||||
Atkinson1BitDitherer& operator=(const Atkinson1BitDitherer& other) = delete;
|
||||
|
||||
uint8_t processPixel(int gray, int x) {
|
||||
// Apply brightness/contrast/gamma adjustments
|
||||
gray = adjustPixel(gray);
|
||||
|
||||
// Add accumulated error
|
||||
int adjusted = gray + errorRow0[x + 2];
|
||||
if (adjusted < 0) adjusted = 0;
|
||||
if (adjusted > 255) adjusted = 255;
|
||||
|
||||
// Quantize to 2 levels (1-bit): 0 = black, 1 = white
|
||||
uint8_t quantized;
|
||||
int quantizedValue;
|
||||
if (adjusted < 128) {
|
||||
quantized = 0;
|
||||
quantizedValue = 0;
|
||||
} else {
|
||||
quantized = 1;
|
||||
quantizedValue = 255;
|
||||
}
|
||||
|
||||
// Calculate error (only distribute 6/8 = 75%)
|
||||
int error = (adjusted - quantizedValue) >> 3; // error/8
|
||||
|
||||
// Distribute 1/8 to each of 6 neighbors
|
||||
errorRow0[x + 3] += error; // Right
|
||||
errorRow0[x + 4] += error; // Right+1
|
||||
errorRow1[x + 1] += error; // Bottom-left
|
||||
errorRow1[x + 2] += error; // Bottom
|
||||
errorRow1[x + 3] += error; // Bottom-right
|
||||
errorRow2[x + 2] += error; // Two rows down
|
||||
|
||||
return quantized;
|
||||
}
|
||||
|
||||
void nextRow() {
|
||||
int16_t* temp = errorRow0;
|
||||
errorRow0 = errorRow1;
|
||||
errorRow1 = errorRow2;
|
||||
errorRow2 = temp;
|
||||
memset(errorRow2, 0, (width + 4) * sizeof(int16_t));
|
||||
}
|
||||
|
||||
void reset() {
|
||||
memset(errorRow0, 0, (width + 4) * sizeof(int16_t));
|
||||
memset(errorRow1, 0, (width + 4) * sizeof(int16_t));
|
||||
memset(errorRow2, 0, (width + 4) * sizeof(int16_t));
|
||||
}
|
||||
|
||||
private:
|
||||
int width;
|
||||
int16_t* errorRow0;
|
||||
int16_t* errorRow1;
|
||||
int16_t* errorRow2;
|
||||
};
|
||||
|
||||
// Atkinson dithering - distributes only 6/8 (75%) of error for cleaner results
|
||||
// Error distribution pattern:
|
||||
// X 1/8 1/8
|
||||
|
||||
@ -163,6 +163,12 @@ void GfxRenderer::drawVal(const int x, const int y, const uint8_t val) const {
|
||||
}
|
||||
void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, const int maxWidth, const int maxHeight,
|
||||
const float cropX, const float cropY, bool extend) const {
|
||||
// For 1-bit bitmaps, use optimized 1-bit rendering path (no crop support for 1-bit)
|
||||
if (bitmap.is1Bit() && cropX == 0.0f && cropY == 0.0f) {
|
||||
drawBitmap1Bit(bitmap, x, y, maxWidth, maxHeight);
|
||||
return;
|
||||
}
|
||||
|
||||
float scale = 1.0f;
|
||||
bool isScaled = false;
|
||||
int cropPixX = std::floor(bitmap.getWidth() * cropX / 2.0f);
|
||||
@ -204,6 +210,9 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con
|
||||
if (screenY >= getScreenHeight()) {
|
||||
break;
|
||||
}
|
||||
if (screenY < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bitmap.readNextRow(outputRow, rowBytes) != BmpReaderError::Ok) {
|
||||
Serial.printf("[%lu] [GFX] Failed to read row %d from bitmap\n", millis(), bmpY);
|
||||
@ -226,6 +235,9 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con
|
||||
if (screenX >= getScreenWidth()) {
|
||||
break;
|
||||
}
|
||||
if (screenX < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint8_t val = outputRow[bmpX / 4] >> (6 - ((bmpX * 2) % 8)) & 0x3;
|
||||
|
||||
@ -316,6 +328,143 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con
|
||||
free(rowBytes);
|
||||
}
|
||||
|
||||
void GfxRenderer::drawBitmap1Bit(const Bitmap& bitmap, const int x, const int y, const int maxWidth,
|
||||
const int maxHeight) const {
|
||||
float scale = 1.0f;
|
||||
bool isScaled = false;
|
||||
if (maxWidth > 0 && bitmap.getWidth() > maxWidth) {
|
||||
scale = static_cast<float>(maxWidth) / static_cast<float>(bitmap.getWidth());
|
||||
isScaled = true;
|
||||
}
|
||||
if (maxHeight > 0 && bitmap.getHeight() > maxHeight) {
|
||||
scale = std::min(scale, static_cast<float>(maxHeight) / static_cast<float>(bitmap.getHeight()));
|
||||
isScaled = true;
|
||||
}
|
||||
|
||||
// For 1-bit BMP, output is still 2-bit packed (for consistency with readNextRow)
|
||||
const int outputRowSize = (bitmap.getWidth() + 3) / 4;
|
||||
auto* outputRow = static_cast<uint8_t*>(malloc(outputRowSize));
|
||||
auto* rowBytes = static_cast<uint8_t*>(malloc(bitmap.getRowBytes()));
|
||||
|
||||
if (!outputRow || !rowBytes) {
|
||||
Serial.printf("[%lu] [GFX] !! Failed to allocate 1-bit BMP row buffers\n", millis());
|
||||
free(outputRow);
|
||||
free(rowBytes);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int bmpY = 0; bmpY < bitmap.getHeight(); bmpY++) {
|
||||
// Read rows sequentially using readNextRow
|
||||
if (bitmap.readNextRow(outputRow, rowBytes) != BmpReaderError::Ok) {
|
||||
Serial.printf("[%lu] [GFX] Failed to read row %d from 1-bit bitmap\n", millis(), bmpY);
|
||||
free(outputRow);
|
||||
free(rowBytes);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate screen Y based on whether BMP is top-down or bottom-up
|
||||
const int bmpYOffset = bitmap.isTopDown() ? bmpY : bitmap.getHeight() - 1 - bmpY;
|
||||
int screenY = y + (isScaled ? static_cast<int>(std::floor(bmpYOffset * scale)) : bmpYOffset);
|
||||
if (screenY >= getScreenHeight()) {
|
||||
continue; // Continue reading to keep row counter in sync
|
||||
}
|
||||
if (screenY < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int bmpX = 0; bmpX < bitmap.getWidth(); bmpX++) {
|
||||
int screenX = x + (isScaled ? static_cast<int>(std::floor(bmpX * scale)) : bmpX);
|
||||
if (screenX >= getScreenWidth()) {
|
||||
break;
|
||||
}
|
||||
if (screenX < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get 2-bit value (result of readNextRow quantization)
|
||||
const uint8_t val = outputRow[bmpX / 4] >> (6 - ((bmpX * 2) % 8)) & 0x3;
|
||||
|
||||
// For 1-bit source: 0 or 1 -> map to black (0,1,2) or white (3)
|
||||
// val < 3 means black pixel (draw it)
|
||||
if (val < 3) {
|
||||
drawPixel(screenX, screenY, true);
|
||||
}
|
||||
// White pixels (val == 3) are not drawn (leave background)
|
||||
}
|
||||
}
|
||||
|
||||
free(outputRow);
|
||||
free(rowBytes);
|
||||
}
|
||||
|
||||
void GfxRenderer::fillPolygon(const int* xPoints, const int* yPoints, int numPoints, bool state) const {
|
||||
if (numPoints < 3) return;
|
||||
|
||||
// Find bounding box
|
||||
int minY = yPoints[0], maxY = yPoints[0];
|
||||
for (int i = 1; i < numPoints; i++) {
|
||||
if (yPoints[i] < minY) minY = yPoints[i];
|
||||
if (yPoints[i] > maxY) maxY = yPoints[i];
|
||||
}
|
||||
|
||||
// Clip to screen
|
||||
if (minY < 0) minY = 0;
|
||||
if (maxY >= getScreenHeight()) maxY = getScreenHeight() - 1;
|
||||
|
||||
// Allocate node buffer for scanline algorithm
|
||||
auto* nodeX = static_cast<int*>(malloc(numPoints * sizeof(int)));
|
||||
if (!nodeX) {
|
||||
Serial.printf("[%lu] [GFX] !! Failed to allocate polygon node buffer\n", millis());
|
||||
return;
|
||||
}
|
||||
|
||||
// Scanline fill algorithm
|
||||
for (int scanY = minY; scanY <= maxY; scanY++) {
|
||||
int nodes = 0;
|
||||
|
||||
// Find all intersection points with edges
|
||||
int j = numPoints - 1;
|
||||
for (int i = 0; i < numPoints; i++) {
|
||||
if ((yPoints[i] < scanY && yPoints[j] >= scanY) || (yPoints[j] < scanY && yPoints[i] >= scanY)) {
|
||||
// Calculate X intersection using fixed-point to avoid float
|
||||
int dy = yPoints[j] - yPoints[i];
|
||||
if (dy != 0) {
|
||||
nodeX[nodes++] = xPoints[i] + (scanY - yPoints[i]) * (xPoints[j] - xPoints[i]) / dy;
|
||||
}
|
||||
}
|
||||
j = i;
|
||||
}
|
||||
|
||||
// Sort nodes by X (simple bubble sort, numPoints is small)
|
||||
for (int i = 0; i < nodes - 1; i++) {
|
||||
for (int k = i + 1; k < nodes; k++) {
|
||||
if (nodeX[i] > nodeX[k]) {
|
||||
int temp = nodeX[i];
|
||||
nodeX[i] = nodeX[k];
|
||||
nodeX[k] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fill between pairs of nodes
|
||||
for (int i = 0; i < nodes - 1; i += 2) {
|
||||
int startX = nodeX[i];
|
||||
int endX = nodeX[i + 1];
|
||||
|
||||
// Clip to screen
|
||||
if (startX < 0) startX = 0;
|
||||
if (endX >= getScreenWidth()) endX = getScreenWidth() - 1;
|
||||
|
||||
// Draw horizontal line
|
||||
for (int x = startX; x <= endX; x++) {
|
||||
drawPixel(x, scanY, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(nodeX);
|
||||
}
|
||||
|
||||
void GfxRenderer::clearScreen(const uint8_t color) const { einkDisplay.clearScreen(color); }
|
||||
|
||||
void GfxRenderer::invertScreen() const {
|
||||
@ -401,7 +550,10 @@ int GfxRenderer::getLineHeight(const int fontId) const {
|
||||
}
|
||||
|
||||
void GfxRenderer::drawButtonHints(const int fontId, const char* btn1, const char* btn2, const char* btn3,
|
||||
const char* btn4) const {
|
||||
const char* btn4) {
|
||||
const Orientation orig_orientation = getOrientation();
|
||||
setOrientation(Orientation::Portrait);
|
||||
|
||||
const int pageHeight = getScreenHeight();
|
||||
constexpr int buttonWidth = 106;
|
||||
constexpr int buttonHeight = 40;
|
||||
@ -414,12 +566,15 @@ void GfxRenderer::drawButtonHints(const int fontId, const char* btn1, const char
|
||||
// Only draw if the label is non-empty
|
||||
if (labels[i] != nullptr && labels[i][0] != '\0') {
|
||||
const int x = buttonPositions[i];
|
||||
fillRect(x, pageHeight - buttonY, buttonWidth, buttonHeight, false);
|
||||
drawRect(x, pageHeight - buttonY, buttonWidth, buttonHeight);
|
||||
const int textWidth = getTextWidth(fontId, labels[i]);
|
||||
const int textX = x + (buttonWidth - 1 - textWidth) / 2;
|
||||
drawText(fontId, textX, pageHeight - buttonY + textYOffset, labels[i]);
|
||||
}
|
||||
}
|
||||
|
||||
setOrientation(orig_orientation);
|
||||
}
|
||||
|
||||
void GfxRenderer::drawSideButtonHints(const int fontId, const char* topBtn, const char* bottomBtn) const {
|
||||
@ -509,7 +664,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;
|
||||
@ -687,8 +842,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?
|
||||
|
||||
@ -69,6 +69,8 @@ class GfxRenderer {
|
||||
void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const;
|
||||
void drawBitmap(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight, float cropX = 0, float cropY = 0,
|
||||
bool extend = false) const;
|
||||
void drawBitmap1Bit(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight) const;
|
||||
void fillPolygon(const int* xPoints, const int* yPoints, int numPoints, bool state = true) const;
|
||||
|
||||
// Text
|
||||
int getTextWidth(int fontId, const char* text, EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
||||
@ -83,7 +85,7 @@ class GfxRenderer {
|
||||
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
|
||||
|
||||
// UI Components
|
||||
void drawButtonHints(int fontId, const char* btn1, const char* btn2, const char* btn3, const char* btn4) const;
|
||||
void drawButtonHints(int fontId, const char* btn1, const char* btn2, const char* btn3, const char* btn4);
|
||||
void drawSideButtonHints(int fontId, const char* topBtn, const char* bottomBtn) const;
|
||||
|
||||
private:
|
||||
@ -98,8 +100,8 @@ class GfxRenderer {
|
||||
void copyGrayscaleLsbBuffers() const;
|
||||
void copyGrayscaleMsbBuffers() const;
|
||||
void displayGrayBuffer() const;
|
||||
bool storeBwBuffer(); // Returns true if buffer was stored successfully
|
||||
void restoreBwBuffer();
|
||||
bool storeBwBuffer(); // Returns true if buffer was stored successfully
|
||||
void restoreBwBuffer(); // Restore and free the stored buffer
|
||||
void cleanupGrayscaleWithFrameBuffer() const;
|
||||
|
||||
// Low level functions
|
||||
|
||||
@ -87,8 +87,47 @@ void writeBmpHeader8bit(Print& bmpOut, const int width, const int height) {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function: Write BMP header with 1-bit color depth (black and white)
|
||||
static void writeBmpHeader1bit(Print& bmpOut, const int width, const int height) {
|
||||
// Calculate row padding (each row must be multiple of 4 bytes)
|
||||
const int bytesPerRow = (width + 31) / 32 * 4; // 1 bit per pixel, round up to 4-byte boundary
|
||||
const int imageSize = bytesPerRow * height;
|
||||
const uint32_t fileSize = 62 + imageSize; // 14 (file header) + 40 (DIB header) + 8 (palette) + image
|
||||
|
||||
// BMP File Header (14 bytes)
|
||||
bmpOut.write('B');
|
||||
bmpOut.write('M');
|
||||
write32(bmpOut, fileSize); // File size
|
||||
write32(bmpOut, 0); // Reserved
|
||||
write32(bmpOut, 62); // Offset to pixel data (14 + 40 + 8)
|
||||
|
||||
// DIB Header (BITMAPINFOHEADER - 40 bytes)
|
||||
write32(bmpOut, 40);
|
||||
write32Signed(bmpOut, width);
|
||||
write32Signed(bmpOut, -height); // Negative height = top-down bitmap
|
||||
write16(bmpOut, 1); // Color planes
|
||||
write16(bmpOut, 1); // Bits per pixel (1 bit)
|
||||
write32(bmpOut, 0); // BI_RGB (no compression)
|
||||
write32(bmpOut, imageSize);
|
||||
write32(bmpOut, 2835); // xPixelsPerMeter (72 DPI)
|
||||
write32(bmpOut, 2835); // yPixelsPerMeter (72 DPI)
|
||||
write32(bmpOut, 2); // colorsUsed
|
||||
write32(bmpOut, 2); // colorsImportant
|
||||
|
||||
// Color Palette (2 colors x 4 bytes = 8 bytes)
|
||||
// Format: Blue, Green, Red, Reserved (BGRA)
|
||||
// Note: In 1-bit BMP, palette index 0 = black, 1 = white
|
||||
uint8_t palette[8] = {
|
||||
0x00, 0x00, 0x00, 0x00, // Color 0: Black
|
||||
0xFF, 0xFF, 0xFF, 0x00 // Color 1: White
|
||||
};
|
||||
for (const uint8_t i : palette) {
|
||||
bmpOut.write(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function: Write BMP header with 2-bit color depth
|
||||
void JpegToBmpConverter::writeBmpHeader(Print& bmpOut, const int width, const int height) {
|
||||
static void writeBmpHeader2bit(Print& bmpOut, const int width, const int height) {
|
||||
// Calculate row padding (each row must be multiple of 4 bytes)
|
||||
const int bytesPerRow = (width * 2 + 31) / 32 * 4; // 2 bits per pixel, round up
|
||||
const int imageSize = bytesPerRow * height;
|
||||
@ -159,9 +198,11 @@ unsigned char JpegToBmpConverter::jpegReadCallback(unsigned char* pBuf, const un
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
// Core function: Convert JPEG file to 2-bit BMP
|
||||
bool JpegToBmpConverter::jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut) {
|
||||
Serial.printf("[%lu] [JPG] Converting JPEG to BMP\n", millis());
|
||||
// Internal implementation with configurable target size and bit depth
|
||||
bool JpegToBmpConverter::jpegFileToBmpStreamInternal(FsFile& jpegFile, Print& bmpOut, int targetWidth, int targetHeight,
|
||||
bool oneBit) {
|
||||
Serial.printf("[%lu] [JPG] Converting JPEG to %s BMP (target: %dx%d)\n", millis(), oneBit ? "1-bit" : "2-bit",
|
||||
targetWidth, targetHeight);
|
||||
|
||||
// Setup context for picojpeg callback
|
||||
JpegReadContext context = {.file = jpegFile, .bufferPos = 0, .bufferFilled = 0};
|
||||
@ -196,10 +237,10 @@ bool JpegToBmpConverter::jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut) {
|
||||
uint32_t scaleY_fp = 65536;
|
||||
bool needsScaling = false;
|
||||
|
||||
if (USE_PRESCALE && (imageInfo.m_width > TARGET_MAX_WIDTH || imageInfo.m_height > TARGET_MAX_HEIGHT)) {
|
||||
if (targetWidth > 0 && targetHeight > 0 && (imageInfo.m_width > targetWidth || imageInfo.m_height > targetHeight)) {
|
||||
// Calculate scale to fit within target dimensions while maintaining aspect ratio
|
||||
const float scaleToFitWidth = static_cast<float>(TARGET_MAX_WIDTH) / imageInfo.m_width;
|
||||
const float scaleToFitHeight = static_cast<float>(TARGET_MAX_HEIGHT) / imageInfo.m_height;
|
||||
const float scaleToFitWidth = static_cast<float>(targetWidth) / imageInfo.m_width;
|
||||
const float scaleToFitHeight = static_cast<float>(targetHeight) / imageInfo.m_height;
|
||||
// We scale to the smaller dimension, so we can potentially crop later.
|
||||
// TODO: ideally, we already crop here.
|
||||
const float scale = (scaleToFitWidth > scaleToFitHeight) ? scaleToFitWidth : scaleToFitHeight;
|
||||
@ -218,16 +259,19 @@ bool JpegToBmpConverter::jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut) {
|
||||
needsScaling = true;
|
||||
|
||||
Serial.printf("[%lu] [JPG] Pre-scaling %dx%d -> %dx%d (fit to %dx%d)\n", millis(), imageInfo.m_width,
|
||||
imageInfo.m_height, outWidth, outHeight, TARGET_MAX_WIDTH, TARGET_MAX_HEIGHT);
|
||||
imageInfo.m_height, outWidth, outHeight, targetWidth, targetHeight);
|
||||
}
|
||||
|
||||
// Write BMP header with output dimensions
|
||||
int bytesPerRow;
|
||||
if (USE_8BIT_OUTPUT) {
|
||||
if (USE_8BIT_OUTPUT && !oneBit) {
|
||||
writeBmpHeader8bit(bmpOut, outWidth, outHeight);
|
||||
bytesPerRow = (outWidth + 3) / 4 * 4;
|
||||
} else if (oneBit) {
|
||||
writeBmpHeader1bit(bmpOut, outWidth, outHeight);
|
||||
bytesPerRow = (outWidth + 31) / 32 * 4; // 1 bit per pixel
|
||||
} else {
|
||||
writeBmpHeader(bmpOut, outWidth, outHeight);
|
||||
writeBmpHeader2bit(bmpOut, outWidth, outHeight);
|
||||
bytesPerRow = (outWidth * 2 + 31) / 32 * 4;
|
||||
}
|
||||
|
||||
@ -258,11 +302,16 @@ bool JpegToBmpConverter::jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create ditherer if enabled (only for 2-bit output)
|
||||
// Create ditherer if enabled
|
||||
// Use OUTPUT dimensions for dithering (after prescaling)
|
||||
AtkinsonDitherer* atkinsonDitherer = nullptr;
|
||||
FloydSteinbergDitherer* fsDitherer = nullptr;
|
||||
if (!USE_8BIT_OUTPUT) {
|
||||
Atkinson1BitDitherer* atkinson1BitDitherer = nullptr;
|
||||
|
||||
if (oneBit) {
|
||||
// For 1-bit output, use Atkinson dithering for better quality
|
||||
atkinson1BitDitherer = new Atkinson1BitDitherer(outWidth);
|
||||
} else if (!USE_8BIT_OUTPUT) {
|
||||
if (USE_ATKINSON) {
|
||||
atkinsonDitherer = new AtkinsonDitherer(outWidth);
|
||||
} else if (USE_FLOYD_STEINBERG) {
|
||||
@ -348,12 +397,25 @@ bool JpegToBmpConverter::jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut) {
|
||||
// No scaling - direct output (1:1 mapping)
|
||||
memset(rowBuffer, 0, bytesPerRow);
|
||||
|
||||
if (USE_8BIT_OUTPUT) {
|
||||
if (USE_8BIT_OUTPUT && !oneBit) {
|
||||
for (int x = 0; x < outWidth; x++) {
|
||||
const uint8_t gray = mcuRowBuffer[bufferY * imageInfo.m_width + x];
|
||||
rowBuffer[x] = adjustPixel(gray);
|
||||
}
|
||||
} else if (oneBit) {
|
||||
// 1-bit output with Atkinson dithering for better quality
|
||||
for (int x = 0; x < outWidth; x++) {
|
||||
const uint8_t gray = mcuRowBuffer[bufferY * imageInfo.m_width + x];
|
||||
const uint8_t bit =
|
||||
atkinson1BitDitherer ? atkinson1BitDitherer->processPixel(gray, x) : quantize1bit(gray, x, y);
|
||||
// Pack 1-bit value: MSB first, 8 pixels per byte
|
||||
const int byteIndex = x / 8;
|
||||
const int bitOffset = 7 - (x % 8);
|
||||
rowBuffer[byteIndex] |= (bit << bitOffset);
|
||||
}
|
||||
if (atkinson1BitDitherer) atkinson1BitDitherer->nextRow();
|
||||
} else {
|
||||
// 2-bit output
|
||||
for (int x = 0; x < outWidth; x++) {
|
||||
const uint8_t gray = adjustPixel(mcuRowBuffer[bufferY * imageInfo.m_width + x]);
|
||||
uint8_t twoBit;
|
||||
@ -411,12 +473,25 @@ bool JpegToBmpConverter::jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut) {
|
||||
if (srcY_fp >= nextOutY_srcStart && currentOutY < outHeight) {
|
||||
memset(rowBuffer, 0, bytesPerRow);
|
||||
|
||||
if (USE_8BIT_OUTPUT) {
|
||||
if (USE_8BIT_OUTPUT && !oneBit) {
|
||||
for (int x = 0; x < outWidth; x++) {
|
||||
const uint8_t gray = (rowCount[x] > 0) ? (rowAccum[x] / rowCount[x]) : 0;
|
||||
rowBuffer[x] = adjustPixel(gray);
|
||||
}
|
||||
} else if (oneBit) {
|
||||
// 1-bit output with Atkinson dithering for better quality
|
||||
for (int x = 0; x < outWidth; x++) {
|
||||
const uint8_t gray = (rowCount[x] > 0) ? (rowAccum[x] / rowCount[x]) : 0;
|
||||
const uint8_t bit = atkinson1BitDitherer ? atkinson1BitDitherer->processPixel(gray, x)
|
||||
: quantize1bit(gray, x, currentOutY);
|
||||
// Pack 1-bit value: MSB first, 8 pixels per byte
|
||||
const int byteIndex = x / 8;
|
||||
const int bitOffset = 7 - (x % 8);
|
||||
rowBuffer[byteIndex] |= (bit << bitOffset);
|
||||
}
|
||||
if (atkinson1BitDitherer) atkinson1BitDitherer->nextRow();
|
||||
} else {
|
||||
// 2-bit output
|
||||
for (int x = 0; x < outWidth; x++) {
|
||||
const uint8_t gray = adjustPixel((rowCount[x] > 0) ? (rowAccum[x] / rowCount[x]) : 0);
|
||||
uint8_t twoBit;
|
||||
@ -464,9 +539,29 @@ bool JpegToBmpConverter::jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut) {
|
||||
if (fsDitherer) {
|
||||
delete fsDitherer;
|
||||
}
|
||||
if (atkinson1BitDitherer) {
|
||||
delete atkinson1BitDitherer;
|
||||
}
|
||||
free(mcuRowBuffer);
|
||||
free(rowBuffer);
|
||||
|
||||
Serial.printf("[%lu] [JPG] Successfully converted JPEG to BMP\n", millis());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Core function: Convert JPEG file to 2-bit BMP (uses default target size)
|
||||
bool JpegToBmpConverter::jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut) {
|
||||
return jpegFileToBmpStreamInternal(jpegFile, bmpOut, TARGET_MAX_WIDTH, TARGET_MAX_HEIGHT, false);
|
||||
}
|
||||
|
||||
// Convert with custom target size (for thumbnails, 2-bit)
|
||||
bool JpegToBmpConverter::jpegFileToBmpStreamWithSize(FsFile& jpegFile, Print& bmpOut, int targetMaxWidth,
|
||||
int targetMaxHeight) {
|
||||
return jpegFileToBmpStreamInternal(jpegFile, bmpOut, targetMaxWidth, targetMaxHeight, false);
|
||||
}
|
||||
|
||||
// Convert to 1-bit BMP (black and white only, no grays) for fast home screen rendering
|
||||
bool JpegToBmpConverter::jpegFileTo1BitBmpStreamWithSize(FsFile& jpegFile, Print& bmpOut, int targetMaxWidth,
|
||||
int targetMaxHeight) {
|
||||
return jpegFileToBmpStreamInternal(jpegFile, bmpOut, targetMaxWidth, targetMaxHeight, true);
|
||||
}
|
||||
|
||||
@ -5,11 +5,15 @@ class Print;
|
||||
class ZipFile;
|
||||
|
||||
class JpegToBmpConverter {
|
||||
static void writeBmpHeader(Print& bmpOut, int width, int height);
|
||||
// [COMMENTED OUT] static uint8_t grayscaleTo2Bit(uint8_t grayscale, int x, int y);
|
||||
static unsigned char jpegReadCallback(unsigned char* pBuf, unsigned char buf_size,
|
||||
unsigned char* pBytes_actually_read, void* pCallback_data);
|
||||
static bool jpegFileToBmpStreamInternal(class FsFile& jpegFile, Print& bmpOut, int targetWidth, int targetHeight,
|
||||
bool oneBit);
|
||||
|
||||
public:
|
||||
static bool jpegFileToBmpStream(FsFile& jpegFile, Print& bmpOut);
|
||||
// Convert with custom target size (for thumbnails)
|
||||
static bool jpegFileToBmpStreamWithSize(FsFile& jpegFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight);
|
||||
// Convert to 1-bit BMP (black and white only, no grays) for fast home screen rendering
|
||||
static bool jpegFileTo1BitBmpStreamWithSize(FsFile& jpegFile, Print& bmpOut, int targetMaxWidth, int targetMaxHeight);
|
||||
};
|
||||
|
||||
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);
|
||||
};
|
||||
191
lib/Txt/Txt.cpp
Normal file
191
lib/Txt/Txt.cpp
Normal file
@ -0,0 +1,191 @@
|
||||
#include "Txt.h"
|
||||
|
||||
#include <FsHelpers.h>
|
||||
#include <JpegToBmpConverter.h>
|
||||
|
||||
Txt::Txt(std::string path, std::string cacheBasePath)
|
||||
: filepath(std::move(path)), cacheBasePath(std::move(cacheBasePath)) {
|
||||
// Generate cache path from file path hash
|
||||
const size_t hash = std::hash<std::string>{}(filepath);
|
||||
cachePath = this->cacheBasePath + "/txt_" + std::to_string(hash);
|
||||
}
|
||||
|
||||
bool Txt::load() {
|
||||
if (loaded) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!SdMan.exists(filepath.c_str())) {
|
||||
Serial.printf("[%lu] [TXT] File does not exist: %s\n", millis(), filepath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
FsFile file;
|
||||
if (!SdMan.openFileForRead("TXT", filepath, file)) {
|
||||
Serial.printf("[%lu] [TXT] Failed to open file: %s\n", millis(), filepath.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
fileSize = file.size();
|
||||
file.close();
|
||||
|
||||
loaded = true;
|
||||
Serial.printf("[%lu] [TXT] Loaded TXT file: %s (%zu bytes)\n", millis(), filepath.c_str(), fileSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Txt::getTitle() const {
|
||||
// Extract filename without path and extension
|
||||
size_t lastSlash = filepath.find_last_of('/');
|
||||
std::string filename = (lastSlash != std::string::npos) ? filepath.substr(lastSlash + 1) : filepath;
|
||||
|
||||
// Remove .txt extension
|
||||
if (filename.length() >= 4 && filename.substr(filename.length() - 4) == ".txt") {
|
||||
filename = filename.substr(0, filename.length() - 4);
|
||||
}
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
void Txt::setupCacheDir() const {
|
||||
if (!SdMan.exists(cacheBasePath.c_str())) {
|
||||
SdMan.mkdir(cacheBasePath.c_str());
|
||||
}
|
||||
if (!SdMan.exists(cachePath.c_str())) {
|
||||
SdMan.mkdir(cachePath.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
std::string Txt::findCoverImage() const {
|
||||
// Get the folder containing the txt file
|
||||
size_t lastSlash = filepath.find_last_of('/');
|
||||
std::string folder = (lastSlash != std::string::npos) ? filepath.substr(0, lastSlash) : "";
|
||||
if (folder.empty()) {
|
||||
folder = "/";
|
||||
}
|
||||
|
||||
// Get the base filename without extension (e.g., "mybook" from "/books/mybook.txt")
|
||||
std::string baseName = getTitle();
|
||||
|
||||
// Image extensions to try
|
||||
const char* extensions[] = {".bmp", ".jpg", ".jpeg", ".png", ".BMP", ".JPG", ".JPEG", ".PNG"};
|
||||
|
||||
// First priority: look for image with same name as txt file (e.g., mybook.jpg)
|
||||
for (const auto& ext : extensions) {
|
||||
std::string coverPath = folder + "/" + baseName + ext;
|
||||
if (SdMan.exists(coverPath.c_str())) {
|
||||
Serial.printf("[%lu] [TXT] Found matching cover image: %s\n", millis(), coverPath.c_str());
|
||||
return coverPath;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: look for cover image files
|
||||
const char* coverNames[] = {"cover", "Cover", "COVER"};
|
||||
for (const auto& name : coverNames) {
|
||||
for (const auto& ext : extensions) {
|
||||
std::string coverPath = folder + "/" + std::string(name) + ext;
|
||||
if (SdMan.exists(coverPath.c_str())) {
|
||||
Serial.printf("[%lu] [TXT] Found fallback cover image: %s\n", millis(), coverPath.c_str());
|
||||
return coverPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string Txt::getCoverBmpPath() const { return cachePath + "/cover.bmp"; }
|
||||
|
||||
bool Txt::generateCoverBmp() const {
|
||||
// Already generated, return true
|
||||
if (SdMan.exists(getCoverBmpPath().c_str())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string coverImagePath = findCoverImage();
|
||||
if (coverImagePath.empty()) {
|
||||
Serial.printf("[%lu] [TXT] No cover image found for TXT file\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setup cache directory
|
||||
setupCacheDir();
|
||||
|
||||
// Get file extension
|
||||
const size_t len = coverImagePath.length();
|
||||
const bool isJpg =
|
||||
(len >= 4 && (coverImagePath.substr(len - 4) == ".jpg" || coverImagePath.substr(len - 4) == ".JPG")) ||
|
||||
(len >= 5 && (coverImagePath.substr(len - 5) == ".jpeg" || coverImagePath.substr(len - 5) == ".JPEG"));
|
||||
const bool isBmp = len >= 4 && (coverImagePath.substr(len - 4) == ".bmp" || coverImagePath.substr(len - 4) == ".BMP");
|
||||
|
||||
if (isBmp) {
|
||||
// Copy BMP file to cache
|
||||
Serial.printf("[%lu] [TXT] Copying BMP cover image to cache\n", millis());
|
||||
FsFile src, dst;
|
||||
if (!SdMan.openFileForRead("TXT", coverImagePath, src)) {
|
||||
return false;
|
||||
}
|
||||
if (!SdMan.openFileForWrite("TXT", getCoverBmpPath(), dst)) {
|
||||
src.close();
|
||||
return false;
|
||||
}
|
||||
uint8_t buffer[1024];
|
||||
while (src.available()) {
|
||||
size_t bytesRead = src.read(buffer, sizeof(buffer));
|
||||
dst.write(buffer, bytesRead);
|
||||
}
|
||||
src.close();
|
||||
dst.close();
|
||||
Serial.printf("[%lu] [TXT] Copied BMP cover to cache\n", millis());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isJpg) {
|
||||
// Convert JPG/JPEG to BMP (same approach as Epub)
|
||||
Serial.printf("[%lu] [TXT] Generating BMP from JPG cover image\n", millis());
|
||||
FsFile coverJpg, coverBmp;
|
||||
if (!SdMan.openFileForRead("TXT", coverImagePath, coverJpg)) {
|
||||
return false;
|
||||
}
|
||||
if (!SdMan.openFileForWrite("TXT", getCoverBmpPath(), coverBmp)) {
|
||||
coverJpg.close();
|
||||
return false;
|
||||
}
|
||||
const bool success = JpegToBmpConverter::jpegFileToBmpStream(coverJpg, coverBmp);
|
||||
coverJpg.close();
|
||||
coverBmp.close();
|
||||
|
||||
if (!success) {
|
||||
Serial.printf("[%lu] [TXT] Failed to generate BMP from JPG cover image\n", millis());
|
||||
SdMan.remove(getCoverBmpPath().c_str());
|
||||
} else {
|
||||
Serial.printf("[%lu] [TXT] Generated BMP from JPG cover image\n", millis());
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
// PNG files are not supported (would need a PNG decoder)
|
||||
Serial.printf("[%lu] [TXT] Cover image format not supported (only BMP/JPG/JPEG)\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Txt::readContent(uint8_t* buffer, size_t offset, size_t length) const {
|
||||
if (!loaded) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FsFile file;
|
||||
if (!SdMan.openFileForRead("TXT", filepath, file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!file.seek(offset)) {
|
||||
file.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t bytesRead = file.read(buffer, length);
|
||||
file.close();
|
||||
|
||||
return bytesRead > 0;
|
||||
}
|
||||
33
lib/Txt/Txt.h
Normal file
33
lib/Txt/Txt.h
Normal file
@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDCardManager.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
class Txt {
|
||||
std::string filepath;
|
||||
std::string cacheBasePath;
|
||||
std::string cachePath;
|
||||
bool loaded = false;
|
||||
size_t fileSize = 0;
|
||||
|
||||
public:
|
||||
explicit Txt(std::string path, std::string cacheBasePath);
|
||||
|
||||
bool load();
|
||||
[[nodiscard]] const std::string& getPath() const { return filepath; }
|
||||
[[nodiscard]] const std::string& getCachePath() const { return cachePath; }
|
||||
[[nodiscard]] std::string getTitle() const;
|
||||
[[nodiscard]] size_t getFileSize() const { return fileSize; }
|
||||
|
||||
void setupCacheDir() const;
|
||||
|
||||
// Cover image support - looks for cover.bmp/jpg/jpeg/png in same folder as txt file
|
||||
[[nodiscard]] std::string getCoverBmpPath() const;
|
||||
[[nodiscard]] bool generateCoverBmp() const;
|
||||
[[nodiscard]] std::string findCoverImage() const;
|
||||
|
||||
// Read content from file
|
||||
[[nodiscard]] bool readContent(uint8_t* buffer, size_t offset, size_t length) const;
|
||||
};
|
||||
@ -2,4 +2,6 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#define REPLACEMENT_GLYPH 0xFFFD
|
||||
|
||||
uint32_t utf8NextCodepoint(const unsigned char** string);
|
||||
|
||||
263
lib/Xtc/Xtc.cpp
263
lib/Xtc/Xtc.cpp
@ -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);
|
||||
@ -293,6 +293,267 @@ bool Xtc::generateCoverBmp() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Xtc::getThumbBmpPath() const { return cachePath + "/thumb.bmp"; }
|
||||
|
||||
bool Xtc::generateThumbBmp() const {
|
||||
// Already generated
|
||||
if (SdMan.exists(getThumbBmpPath().c_str())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!loaded || !parser) {
|
||||
Serial.printf("[%lu] [XTC] Cannot generate thumb BMP, file not loaded\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parser->getPageCount() == 0) {
|
||||
Serial.printf("[%lu] [XTC] No pages in XTC file\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setup cache directory
|
||||
setupCacheDir();
|
||||
|
||||
// Get first page info for cover
|
||||
xtc::PageInfo pageInfo;
|
||||
if (!parser->getPageInfo(0, pageInfo)) {
|
||||
Serial.printf("[%lu] [XTC] Failed to get first page info\n", millis());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get bit depth
|
||||
const uint8_t bitDepth = parser->getBitDepth();
|
||||
|
||||
// Calculate target dimensions for thumbnail (fit within 240x400 Continue Reading card)
|
||||
constexpr int THUMB_TARGET_WIDTH = 240;
|
||||
constexpr int THUMB_TARGET_HEIGHT = 400;
|
||||
|
||||
// Calculate scale factor
|
||||
float scaleX = static_cast<float>(THUMB_TARGET_WIDTH) / pageInfo.width;
|
||||
float scaleY = static_cast<float>(THUMB_TARGET_HEIGHT) / pageInfo.height;
|
||||
float scale = (scaleX < scaleY) ? scaleX : scaleY;
|
||||
|
||||
// Only scale down, never up
|
||||
if (scale >= 1.0f) {
|
||||
// Page is already small enough, just use cover.bmp
|
||||
// Copy cover.bmp to thumb.bmp
|
||||
if (generateCoverBmp()) {
|
||||
FsFile src, dst;
|
||||
if (SdMan.openFileForRead("XTC", getCoverBmpPath(), src)) {
|
||||
if (SdMan.openFileForWrite("XTC", getThumbBmpPath(), dst)) {
|
||||
uint8_t buffer[512];
|
||||
while (src.available()) {
|
||||
size_t bytesRead = src.read(buffer, sizeof(buffer));
|
||||
dst.write(buffer, bytesRead);
|
||||
}
|
||||
dst.close();
|
||||
}
|
||||
src.close();
|
||||
}
|
||||
Serial.printf("[%lu] [XTC] Copied cover to thumb (no scaling needed)\n", millis());
|
||||
return SdMan.exists(getThumbBmpPath().c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t thumbWidth = static_cast<uint16_t>(pageInfo.width * scale);
|
||||
uint16_t thumbHeight = static_cast<uint16_t>(pageInfo.height * scale);
|
||||
|
||||
Serial.printf("[%lu] [XTC] Generating thumb BMP: %dx%d -> %dx%d (scale: %.3f)\n", millis(), pageInfo.width,
|
||||
pageInfo.height, thumbWidth, thumbHeight, scale);
|
||||
|
||||
// Allocate buffer for page data
|
||||
size_t bitmapSize;
|
||||
if (bitDepth == 2) {
|
||||
bitmapSize = ((static_cast<size_t>(pageInfo.width) * pageInfo.height + 7) / 8) * 2;
|
||||
} else {
|
||||
bitmapSize = ((pageInfo.width + 7) / 8) * pageInfo.height;
|
||||
}
|
||||
uint8_t* pageBuffer = static_cast<uint8_t*>(malloc(bitmapSize));
|
||||
if (!pageBuffer) {
|
||||
Serial.printf("[%lu] [XTC] Failed to allocate page buffer (%lu bytes)\n", millis(), bitmapSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load first page (cover)
|
||||
size_t bytesRead = const_cast<xtc::XtcParser*>(parser.get())->loadPage(0, pageBuffer, bitmapSize);
|
||||
if (bytesRead == 0) {
|
||||
Serial.printf("[%lu] [XTC] Failed to load cover page for thumb\n", millis());
|
||||
free(pageBuffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create thumbnail BMP file - use 1-bit format for fast home screen rendering (no gray passes)
|
||||
FsFile thumbBmp;
|
||||
if (!SdMan.openFileForWrite("XTC", getThumbBmpPath(), thumbBmp)) {
|
||||
Serial.printf("[%lu] [XTC] Failed to create thumb BMP file\n", millis());
|
||||
free(pageBuffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write 1-bit BMP header for fast home screen rendering
|
||||
const uint32_t rowSize = (thumbWidth + 31) / 32 * 4; // 1 bit per pixel, aligned to 4 bytes
|
||||
const uint32_t imageSize = rowSize * thumbHeight;
|
||||
const uint32_t fileSize = 14 + 40 + 8 + imageSize; // 8 bytes for 2-color palette
|
||||
|
||||
// File header
|
||||
thumbBmp.write('B');
|
||||
thumbBmp.write('M');
|
||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&fileSize), 4);
|
||||
uint32_t reserved = 0;
|
||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&reserved), 4);
|
||||
uint32_t dataOffset = 14 + 40 + 8; // 1-bit palette has 2 colors (8 bytes)
|
||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&dataOffset), 4);
|
||||
|
||||
// DIB header
|
||||
uint32_t dibHeaderSize = 40;
|
||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&dibHeaderSize), 4);
|
||||
int32_t widthVal = thumbWidth;
|
||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&widthVal), 4);
|
||||
int32_t heightVal = -static_cast<int32_t>(thumbHeight); // Negative for top-down
|
||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&heightVal), 4);
|
||||
uint16_t planes = 1;
|
||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&planes), 2);
|
||||
uint16_t bitsPerPixel = 1; // 1-bit for black and white
|
||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&bitsPerPixel), 2);
|
||||
uint32_t compression = 0;
|
||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&compression), 4);
|
||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&imageSize), 4);
|
||||
int32_t ppmX = 2835;
|
||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&ppmX), 4);
|
||||
int32_t ppmY = 2835;
|
||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&ppmY), 4);
|
||||
uint32_t colorsUsed = 2;
|
||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&colorsUsed), 4);
|
||||
uint32_t colorsImportant = 2;
|
||||
thumbBmp.write(reinterpret_cast<const uint8_t*>(&colorsImportant), 4);
|
||||
|
||||
// Color palette (2 colors for 1-bit: black and white)
|
||||
uint8_t palette[8] = {
|
||||
0x00, 0x00, 0x00, 0x00, // Color 0: Black
|
||||
0xFF, 0xFF, 0xFF, 0x00 // Color 1: White
|
||||
};
|
||||
thumbBmp.write(palette, 8);
|
||||
|
||||
// Allocate row buffer for 1-bit output
|
||||
uint8_t* rowBuffer = static_cast<uint8_t*>(malloc(rowSize));
|
||||
if (!rowBuffer) {
|
||||
free(pageBuffer);
|
||||
thumbBmp.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fixed-point scale factor (16.16)
|
||||
uint32_t scaleInv_fp = static_cast<uint32_t>(65536.0f / scale);
|
||||
|
||||
// Pre-calculate plane info for 2-bit mode
|
||||
const size_t planeSize = (bitDepth == 2) ? ((static_cast<size_t>(pageInfo.width) * pageInfo.height + 7) / 8) : 0;
|
||||
const uint8_t* plane1 = (bitDepth == 2) ? pageBuffer : nullptr;
|
||||
const uint8_t* plane2 = (bitDepth == 2) ? pageBuffer + planeSize : nullptr;
|
||||
const size_t colBytes = (bitDepth == 2) ? ((pageInfo.height + 7) / 8) : 0;
|
||||
const size_t srcRowBytes = (bitDepth == 1) ? ((pageInfo.width + 7) / 8) : 0;
|
||||
|
||||
for (uint16_t dstY = 0; dstY < thumbHeight; dstY++) {
|
||||
memset(rowBuffer, 0xFF, rowSize); // Start with all white (bit 1)
|
||||
|
||||
// Calculate source Y range with bounds checking
|
||||
uint32_t srcYStart = (static_cast<uint32_t>(dstY) * scaleInv_fp) >> 16;
|
||||
uint32_t srcYEnd = (static_cast<uint32_t>(dstY + 1) * scaleInv_fp) >> 16;
|
||||
if (srcYStart >= pageInfo.height) srcYStart = pageInfo.height - 1;
|
||||
if (srcYEnd > pageInfo.height) srcYEnd = pageInfo.height;
|
||||
if (srcYEnd <= srcYStart) srcYEnd = srcYStart + 1;
|
||||
if (srcYEnd > pageInfo.height) srcYEnd = pageInfo.height;
|
||||
|
||||
for (uint16_t dstX = 0; dstX < thumbWidth; dstX++) {
|
||||
// Calculate source X range with bounds checking
|
||||
uint32_t srcXStart = (static_cast<uint32_t>(dstX) * scaleInv_fp) >> 16;
|
||||
uint32_t srcXEnd = (static_cast<uint32_t>(dstX + 1) * scaleInv_fp) >> 16;
|
||||
if (srcXStart >= pageInfo.width) srcXStart = pageInfo.width - 1;
|
||||
if (srcXEnd > pageInfo.width) srcXEnd = pageInfo.width;
|
||||
if (srcXEnd <= srcXStart) srcXEnd = srcXStart + 1;
|
||||
if (srcXEnd > pageInfo.width) srcXEnd = pageInfo.width;
|
||||
|
||||
// Area averaging: sum grayscale values (0-255 range)
|
||||
uint32_t graySum = 0;
|
||||
uint32_t totalCount = 0;
|
||||
|
||||
for (uint32_t srcY = srcYStart; srcY < srcYEnd && srcY < pageInfo.height; srcY++) {
|
||||
for (uint32_t srcX = srcXStart; srcX < srcXEnd && srcX < pageInfo.width; srcX++) {
|
||||
uint8_t grayValue = 255; // Default: white
|
||||
|
||||
if (bitDepth == 2) {
|
||||
// XTH 2-bit mode: pixel value 0-3
|
||||
// Bounds check for column index
|
||||
if (srcX < pageInfo.width) {
|
||||
const size_t colIndex = pageInfo.width - 1 - srcX;
|
||||
const size_t byteInCol = srcY / 8;
|
||||
const size_t bitInByte = 7 - (srcY % 8);
|
||||
const size_t byteOffset = colIndex * colBytes + byteInCol;
|
||||
// Bounds check for buffer access
|
||||
if (byteOffset < planeSize) {
|
||||
const uint8_t bit1 = (plane1[byteOffset] >> bitInByte) & 1;
|
||||
const uint8_t bit2 = (plane2[byteOffset] >> bitInByte) & 1;
|
||||
const uint8_t pixelValue = (bit1 << 1) | bit2;
|
||||
// Convert 2-bit (0-3) to grayscale: 0=black, 3=white
|
||||
// pixelValue: 0=white, 1=light gray, 2=dark gray, 3=black (XTC polarity)
|
||||
grayValue = (3 - pixelValue) * 85; // 0->255, 1->170, 2->85, 3->0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 1-bit mode
|
||||
const size_t byteIdx = srcY * srcRowBytes + srcX / 8;
|
||||
const size_t bitIdx = 7 - (srcX % 8);
|
||||
// Bounds check for buffer access
|
||||
if (byteIdx < bitmapSize) {
|
||||
const uint8_t pixelBit = (pageBuffer[byteIdx] >> bitIdx) & 1;
|
||||
// XTC 1-bit polarity: 0=black, 1=white (same as BMP palette)
|
||||
grayValue = pixelBit ? 255 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
graySum += grayValue;
|
||||
totalCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate average grayscale and quantize to 1-bit with noise dithering
|
||||
uint8_t avgGray = (totalCount > 0) ? static_cast<uint8_t>(graySum / totalCount) : 255;
|
||||
|
||||
// Hash-based noise dithering for 1-bit output
|
||||
uint32_t hash = static_cast<uint32_t>(dstX) * 374761393u + static_cast<uint32_t>(dstY) * 668265263u;
|
||||
hash = (hash ^ (hash >> 13)) * 1274126177u;
|
||||
const int threshold = static_cast<int>(hash >> 24); // 0-255
|
||||
const int adjustedThreshold = 128 + ((threshold - 128) / 2); // Range: 64-192
|
||||
|
||||
// Quantize to 1-bit: 0=black, 1=white
|
||||
uint8_t oneBit = (avgGray >= adjustedThreshold) ? 1 : 0;
|
||||
|
||||
// Pack 1-bit value into row buffer (MSB first, 8 pixels per byte)
|
||||
const size_t byteIndex = dstX / 8;
|
||||
const size_t bitOffset = 7 - (dstX % 8);
|
||||
// Bounds check for row buffer access
|
||||
if (byteIndex < rowSize) {
|
||||
if (oneBit) {
|
||||
rowBuffer[byteIndex] |= (1 << bitOffset); // Set bit for white
|
||||
} else {
|
||||
rowBuffer[byteIndex] &= ~(1 << bitOffset); // Clear bit for black
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write row (already padded to 4-byte boundary by rowSize)
|
||||
thumbBmp.write(rowBuffer, rowSize);
|
||||
}
|
||||
|
||||
free(rowBuffer);
|
||||
thumbBmp.close();
|
||||
free(pageBuffer);
|
||||
|
||||
Serial.printf("[%lu] [XTC] Generated thumb BMP (%dx%d): %s\n", millis(), thumbWidth, thumbHeight,
|
||||
getThumbBmpPath().c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t Xtc::getPageCount() const {
|
||||
if (!loaded || !parser) {
|
||||
return 0;
|
||||
|
||||
@ -62,6 +62,9 @@ class Xtc {
|
||||
// Cover image support (for sleep screen)
|
||||
std::string getCoverBmpPath() const;
|
||||
bool generateCoverBmp() const;
|
||||
// Thumbnail support (for Continue Reading card)
|
||||
std::string getThumbBmpPath() const;
|
||||
bool generateThumbBmp() const;
|
||||
|
||||
// Page access
|
||||
uint32_t getPageCount() const;
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
default_envs = default
|
||||
|
||||
[crosspoint]
|
||||
version = 0.13.1
|
||||
version = 0.14.0
|
||||
|
||||
[base]
|
||||
platform = espressif32 @ 6.12.0
|
||||
@ -45,8 +45,9 @@ lib_deps =
|
||||
InputManager=symlink://open-x4-sdk/libs/hardware/InputManager
|
||||
EInkDisplay=symlink://open-x4-sdk/libs/display/EInkDisplay
|
||||
SDCardManager=symlink://open-x4-sdk/libs/hardware/SDCardManager
|
||||
ArduinoJson @ 7.4.2
|
||||
QRCode @ 0.0.1
|
||||
bblanchon/ArduinoJson @ 7.4.2
|
||||
ricmoo/QRCode @ 0.0.1
|
||||
links2004/WebSockets @ 2.7.3
|
||||
|
||||
[env:default]
|
||||
extends = base
|
||||
|
||||
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 = 17;
|
||||
constexpr uint8_t SETTINGS_COUNT = 20;
|
||||
constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
|
||||
} // namespace
|
||||
|
||||
@ -47,6 +47,8 @@ bool CrossPointSettings::saveToFile() const {
|
||||
serialization::writeString(outputFile, std::string(opdsServerUrl));
|
||||
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());
|
||||
@ -109,10 +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,12 +84,16 @@ 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
|
||||
char opdsServerUrl[128] = "";
|
||||
// Hide battery percentage
|
||||
uint8_t hideBatteryPercentage = HIDE_NEVER;
|
||||
// Long-press chapter skip on side buttons
|
||||
uint8_t longPressChapterSkip = 1;
|
||||
|
||||
~CrossPointSettings() = default;
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
#include <Serialization.h>
|
||||
|
||||
namespace {
|
||||
constexpr uint8_t STATE_FILE_VERSION = 1;
|
||||
constexpr uint8_t STATE_FILE_VERSION = 2;
|
||||
constexpr char STATE_FILE[] = "/.crosspoint/state.bin";
|
||||
} // namespace
|
||||
|
||||
@ -19,6 +19,7 @@ bool CrossPointState::saveToFile() const {
|
||||
|
||||
serialization::writePod(outputFile, STATE_FILE_VERSION);
|
||||
serialization::writeString(outputFile, openEpubPath);
|
||||
serialization::writePod(outputFile, lastSleepImage);
|
||||
outputFile.close();
|
||||
return true;
|
||||
}
|
||||
@ -31,13 +32,18 @@ bool CrossPointState::loadFromFile() {
|
||||
|
||||
uint8_t version;
|
||||
serialization::readPod(inputFile, version);
|
||||
if (version != STATE_FILE_VERSION) {
|
||||
if (version > STATE_FILE_VERSION) {
|
||||
Serial.printf("[%lu] [CPS] Deserialization failed: Unknown version %u\n", millis(), version);
|
||||
inputFile.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
serialization::readString(inputFile, openEpubPath);
|
||||
if (version >= 2) {
|
||||
serialization::readPod(inputFile, lastSleepImage);
|
||||
} else {
|
||||
lastSleepImage = 0;
|
||||
}
|
||||
|
||||
inputFile.close();
|
||||
return true;
|
||||
|
||||
@ -8,6 +8,7 @@ class CrossPointState {
|
||||
|
||||
public:
|
||||
std::string openEpubPath;
|
||||
uint8_t lastSleepImage;
|
||||
~CrossPointState() = default;
|
||||
|
||||
// Get singleton instance
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include <Epub.h>
|
||||
#include <GfxRenderer.h>
|
||||
#include <SDCardManager.h>
|
||||
#include <Txt.h>
|
||||
#include <Xtc.h>
|
||||
|
||||
#include "CrossPointSettings.h"
|
||||
@ -80,7 +81,13 @@ void SleepActivity::renderCustomSleepScreen() const {
|
||||
const auto numFiles = files.size();
|
||||
if (numFiles > 0) {
|
||||
// Generate a random number between 1 and numFiles
|
||||
const auto randomFileIndex = random(numFiles);
|
||||
auto randomFileIndex = random(numFiles);
|
||||
// If we picked the same image as last time, reroll
|
||||
while (numFiles > 1 && randomFileIndex == APP_STATE.lastSleepImage) {
|
||||
randomFileIndex = random(numFiles);
|
||||
}
|
||||
APP_STATE.lastSleepImage = randomFileIndex;
|
||||
APP_STATE.saveToFile();
|
||||
const auto filename = "/sleep/" + files[randomFileIndex];
|
||||
FsFile file;
|
||||
if (SdMan.openFileForRead("SLP", filename, file)) {
|
||||
@ -202,6 +209,7 @@ void SleepActivity::renderCoverSleepScreen() const {
|
||||
std::string coverBmpPath;
|
||||
bool cropped = SETTINGS.sleepScreenCoverMode == CrossPointSettings::SLEEP_SCREEN_COVER_MODE::CROP;
|
||||
|
||||
// Check if the current book is XTC, TXT, or EPUB
|
||||
if (StringUtils::checkFileExtension(APP_STATE.openEpubPath, ".xtc") ||
|
||||
StringUtils::checkFileExtension(APP_STATE.openEpubPath, ".xtch")) {
|
||||
// Handle XTC file
|
||||
@ -217,6 +225,20 @@ void SleepActivity::renderCoverSleepScreen() const {
|
||||
}
|
||||
|
||||
coverBmpPath = lastXtc.getCoverBmpPath();
|
||||
} else if (StringUtils::checkFileExtension(APP_STATE.openEpubPath, ".txt")) {
|
||||
// Handle TXT file - looks for cover image in the same folder
|
||||
Txt lastTxt(APP_STATE.openEpubPath, "/.crosspoint");
|
||||
if (!lastTxt.load()) {
|
||||
Serial.println("[SLP] Failed to load last TXT");
|
||||
return renderDefaultSleepScreen();
|
||||
}
|
||||
|
||||
if (!lastTxt.generateCoverBmp()) {
|
||||
Serial.println("[SLP] No cover image found for TXT file");
|
||||
return renderDefaultSleepScreen();
|
||||
}
|
||||
|
||||
coverBmpPath = lastTxt.getCoverBmpPath();
|
||||
} else if (StringUtils::checkFileExtension(APP_STATE.openEpubPath, ".epub")) {
|
||||
// Handle EPUB file
|
||||
Epub lastEpub(APP_STATE.openEpubPath, "/.crosspoint");
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
#include "HomeActivity.h"
|
||||
|
||||
#include <Bitmap.h>
|
||||
#include <Epub.h>
|
||||
#include <GfxRenderer.h>
|
||||
#include <SDCardManager.h>
|
||||
#include <Xtc.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
@ -46,7 +48,7 @@ void HomeActivity::onEnter() {
|
||||
lastBookTitle = lastBookTitle.substr(lastSlash + 1);
|
||||
}
|
||||
|
||||
// If epub, try to load the metadata for title/author
|
||||
// If epub, try to load the metadata for title/author and cover
|
||||
if (StringUtils::checkFileExtension(lastBookTitle, ".epub")) {
|
||||
Epub epub(APP_STATE.openEpubPath, "/.crosspoint");
|
||||
epub.load(false);
|
||||
@ -56,10 +58,31 @@ void HomeActivity::onEnter() {
|
||||
if (!epub.getAuthor().empty()) {
|
||||
lastBookAuthor = std::string(epub.getAuthor());
|
||||
}
|
||||
} else if (StringUtils::checkFileExtension(lastBookTitle, ".xtch")) {
|
||||
lastBookTitle.resize(lastBookTitle.length() - 5);
|
||||
} else if (StringUtils::checkFileExtension(lastBookTitle, ".xtc")) {
|
||||
lastBookTitle.resize(lastBookTitle.length() - 4);
|
||||
// Try to generate thumbnail image for Continue Reading card
|
||||
if (epub.generateThumbBmp()) {
|
||||
coverBmpPath = epub.getThumbBmpPath();
|
||||
hasCoverImage = true;
|
||||
}
|
||||
} else if (StringUtils::checkFileExtension(lastBookTitle, ".xtch") ||
|
||||
StringUtils::checkFileExtension(lastBookTitle, ".xtc")) {
|
||||
// Handle XTC file
|
||||
Xtc xtc(APP_STATE.openEpubPath, "/.crosspoint");
|
||||
if (xtc.load()) {
|
||||
if (!xtc.getTitle().empty()) {
|
||||
lastBookTitle = std::string(xtc.getTitle());
|
||||
}
|
||||
// Try to generate thumbnail image for Continue Reading card
|
||||
if (xtc.generateThumbBmp()) {
|
||||
coverBmpPath = xtc.getThumbBmpPath();
|
||||
hasCoverImage = true;
|
||||
}
|
||||
}
|
||||
// Remove extension from title if we don't have metadata
|
||||
if (StringUtils::checkFileExtension(lastBookTitle, ".xtch")) {
|
||||
lastBookTitle.resize(lastBookTitle.length() - 5);
|
||||
} else if (StringUtils::checkFileExtension(lastBookTitle, ".xtc")) {
|
||||
lastBookTitle.resize(lastBookTitle.length() - 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,7 +92,7 @@ void HomeActivity::onEnter() {
|
||||
updateRequired = true;
|
||||
|
||||
xTaskCreate(&HomeActivity::taskTrampoline, "HomeActivityTask",
|
||||
4096, // Stack size
|
||||
4096, // Stack size (increased for cover image rendering)
|
||||
this, // Parameters
|
||||
1, // Priority
|
||||
&displayTaskHandle // Task handle
|
||||
@ -87,6 +110,51 @@ void HomeActivity::onExit() {
|
||||
}
|
||||
vSemaphoreDelete(renderingMutex);
|
||||
renderingMutex = nullptr;
|
||||
|
||||
// Free the stored cover buffer if any
|
||||
freeCoverBuffer();
|
||||
}
|
||||
|
||||
bool HomeActivity::storeCoverBuffer() {
|
||||
uint8_t* frameBuffer = renderer.getFrameBuffer();
|
||||
if (!frameBuffer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Free any existing buffer first
|
||||
freeCoverBuffer();
|
||||
|
||||
const size_t bufferSize = GfxRenderer::getBufferSize();
|
||||
coverBuffer = static_cast<uint8_t*>(malloc(bufferSize));
|
||||
if (!coverBuffer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(coverBuffer, frameBuffer, bufferSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HomeActivity::restoreCoverBuffer() {
|
||||
if (!coverBuffer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t* frameBuffer = renderer.getFrameBuffer();
|
||||
if (!frameBuffer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t bufferSize = GfxRenderer::getBufferSize();
|
||||
memcpy(frameBuffer, coverBuffer, bufferSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
void HomeActivity::freeCoverBuffer() {
|
||||
if (coverBuffer) {
|
||||
free(coverBuffer);
|
||||
coverBuffer = nullptr;
|
||||
}
|
||||
coverBufferStored = false;
|
||||
}
|
||||
|
||||
void HomeActivity::loop() {
|
||||
@ -138,8 +206,12 @@ void HomeActivity::displayTaskLoop() {
|
||||
}
|
||||
}
|
||||
|
||||
void HomeActivity::render() const {
|
||||
renderer.clearScreen();
|
||||
void HomeActivity::render() {
|
||||
// If we have a stored cover buffer, restore it instead of clearing
|
||||
const bool bufferRestored = coverBufferStored && restoreCoverBuffer();
|
||||
if (!bufferRestored) {
|
||||
renderer.clearScreen();
|
||||
}
|
||||
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
@ -154,38 +226,109 @@ void HomeActivity::render() const {
|
||||
constexpr int bookY = 30;
|
||||
const bool bookSelected = hasContinueReading && selectorIndex == 0;
|
||||
|
||||
// Bookmark dimensions (used in multiple places)
|
||||
const int bookmarkWidth = bookWidth / 8;
|
||||
const int bookmarkHeight = bookHeight / 5;
|
||||
const int bookmarkX = bookX + bookWidth - bookmarkWidth - 10;
|
||||
const int bookmarkY = bookY + 5;
|
||||
|
||||
// Draw book card regardless, fill with message based on `hasContinueReading`
|
||||
{
|
||||
if (bookSelected) {
|
||||
renderer.fillRect(bookX, bookY, bookWidth, bookHeight);
|
||||
} else {
|
||||
renderer.drawRect(bookX, bookY, bookWidth, bookHeight);
|
||||
// Draw cover image as background if available (inside the box)
|
||||
// Only load from SD on first render, then use stored buffer
|
||||
if (hasContinueReading && hasCoverImage && !coverBmpPath.empty() && !coverRendered) {
|
||||
// First time: load cover from SD and render
|
||||
FsFile file;
|
||||
if (SdMan.openFileForRead("HOME", coverBmpPath, file)) {
|
||||
Bitmap bitmap(file);
|
||||
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
||||
// Calculate position to center image within the book card
|
||||
int coverX, coverY;
|
||||
|
||||
if (bitmap.getWidth() > bookWidth || bitmap.getHeight() > bookHeight) {
|
||||
const float imgRatio = static_cast<float>(bitmap.getWidth()) / static_cast<float>(bitmap.getHeight());
|
||||
const float boxRatio = static_cast<float>(bookWidth) / static_cast<float>(bookHeight);
|
||||
|
||||
if (imgRatio > boxRatio) {
|
||||
coverX = bookX;
|
||||
coverY = bookY + (bookHeight - static_cast<int>(bookWidth / imgRatio)) / 2;
|
||||
} else {
|
||||
coverX = bookX + (bookWidth - static_cast<int>(bookHeight * imgRatio)) / 2;
|
||||
coverY = bookY;
|
||||
}
|
||||
} else {
|
||||
coverX = bookX + (bookWidth - bitmap.getWidth()) / 2;
|
||||
coverY = bookY + (bookHeight - bitmap.getHeight()) / 2;
|
||||
}
|
||||
|
||||
// Draw the cover image centered within the book card
|
||||
renderer.drawBitmap(bitmap, coverX, coverY, bookWidth, bookHeight);
|
||||
|
||||
// Draw border around the card
|
||||
renderer.drawRect(bookX, bookY, bookWidth, bookHeight);
|
||||
|
||||
// No bookmark ribbon when cover is shown - it would just cover the art
|
||||
|
||||
// Store the buffer with cover image for fast navigation
|
||||
coverBufferStored = storeCoverBuffer();
|
||||
coverRendered = true;
|
||||
|
||||
// First render: if selected, draw selection indicators now
|
||||
if (bookSelected) {
|
||||
renderer.drawRect(bookX + 1, bookY + 1, bookWidth - 2, bookHeight - 2);
|
||||
renderer.drawRect(bookX + 2, bookY + 2, bookWidth - 4, bookHeight - 4);
|
||||
}
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
} else if (!bufferRestored && !coverRendered) {
|
||||
// No cover image: draw border or fill, plus bookmark as visual flair
|
||||
if (bookSelected) {
|
||||
renderer.fillRect(bookX, bookY, bookWidth, bookHeight);
|
||||
} else {
|
||||
renderer.drawRect(bookX, bookY, bookWidth, bookHeight);
|
||||
}
|
||||
|
||||
// Draw bookmark ribbon when no cover image (visual decoration)
|
||||
if (hasContinueReading) {
|
||||
const int notchDepth = bookmarkHeight / 3;
|
||||
const int centerX = bookmarkX + bookmarkWidth / 2;
|
||||
|
||||
const int xPoints[5] = {
|
||||
bookmarkX, // top-left
|
||||
bookmarkX + bookmarkWidth, // top-right
|
||||
bookmarkX + bookmarkWidth, // bottom-right
|
||||
centerX, // center notch point
|
||||
bookmarkX // bottom-left
|
||||
};
|
||||
const int yPoints[5] = {
|
||||
bookmarkY, // top-left
|
||||
bookmarkY, // top-right
|
||||
bookmarkY + bookmarkHeight, // bottom-right
|
||||
bookmarkY + bookmarkHeight - notchDepth, // center notch point
|
||||
bookmarkY + bookmarkHeight // bottom-left
|
||||
};
|
||||
|
||||
// Draw bookmark ribbon (inverted if selected)
|
||||
renderer.fillPolygon(xPoints, yPoints, 5, !bookSelected);
|
||||
}
|
||||
}
|
||||
|
||||
// Bookmark icon in the top-right corner of the card
|
||||
const int bookmarkWidth = bookWidth / 8;
|
||||
const int bookmarkHeight = bookHeight / 5;
|
||||
const int bookmarkX = bookX + bookWidth - bookmarkWidth - 8;
|
||||
constexpr int bookmarkY = bookY + 1;
|
||||
|
||||
// Main bookmark body (solid)
|
||||
renderer.fillRect(bookmarkX, bookmarkY, bookmarkWidth, bookmarkHeight, !bookSelected);
|
||||
|
||||
// Carve out an inverted triangle notch at the bottom center to create angled points
|
||||
const int notchHeight = bookmarkHeight / 2; // depth of the notch
|
||||
for (int i = 0; i < notchHeight; ++i) {
|
||||
const int y = bookmarkY + bookmarkHeight - 1 - i;
|
||||
const int xStart = bookmarkX + i;
|
||||
const int width = bookmarkWidth - 2 * i;
|
||||
if (width <= 0) {
|
||||
break;
|
||||
}
|
||||
// Draw a horizontal strip in the opposite color to "cut" the notch
|
||||
renderer.fillRect(xStart, y, width, 1, bookSelected);
|
||||
// If buffer was restored, draw selection indicators if needed
|
||||
if (bufferRestored && bookSelected && coverRendered) {
|
||||
// Draw selection border (no bookmark inversion needed since cover has no bookmark)
|
||||
renderer.drawRect(bookX + 1, bookY + 1, bookWidth - 2, bookHeight - 2);
|
||||
renderer.drawRect(bookX + 2, bookY + 2, bookWidth - 4, bookHeight - 4);
|
||||
} else if (!coverRendered && !bufferRestored) {
|
||||
// Selection border already handled above in the no-cover case
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@ -218,18 +361,25 @@ void HomeActivity::render() const {
|
||||
lines.back().append("...");
|
||||
|
||||
while (!lines.back().empty() && renderer.getTextWidth(UI_12_FONT_ID, lines.back().c_str()) > maxLineWidth) {
|
||||
lines.back().resize(lines.back().size() - 5);
|
||||
// Remove "..." first, then remove one UTF-8 char, then add "..." back
|
||||
lines.back().resize(lines.back().size() - 3); // Remove "..."
|
||||
StringUtils::utf8RemoveLastChar(lines.back());
|
||||
lines.back().append("...");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
int wordWidth = renderer.getTextWidth(UI_12_FONT_ID, i.c_str());
|
||||
while (wordWidth > maxLineWidth && i.size() > 5) {
|
||||
// Word itself is too long, trim it
|
||||
i.resize(i.size() - 5);
|
||||
i.append("...");
|
||||
wordWidth = renderer.getTextWidth(UI_12_FONT_ID, i.c_str());
|
||||
while (wordWidth > maxLineWidth && !i.empty()) {
|
||||
// Word itself is too long, trim it (UTF-8 safe)
|
||||
StringUtils::utf8RemoveLastChar(i);
|
||||
// Check if we have room for ellipsis
|
||||
std::string withEllipsis = i + "...";
|
||||
wordWidth = renderer.getTextWidth(UI_12_FONT_ID, withEllipsis.c_str());
|
||||
if (wordWidth <= maxLineWidth) {
|
||||
i = withEllipsis;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int newLineWidth = renderer.getTextWidth(UI_12_FONT_ID, currentLine.c_str());
|
||||
@ -261,6 +411,43 @@ void HomeActivity::render() const {
|
||||
// Vertically center the title block within the card
|
||||
int titleYStart = bookY + (bookHeight - totalTextHeight) / 2;
|
||||
|
||||
// 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
|
||||
int maxTextWidth = 0;
|
||||
for (const auto& line : lines) {
|
||||
const int lineWidth = renderer.getTextWidth(UI_12_FONT_ID, line.c_str());
|
||||
if (lineWidth > maxTextWidth) {
|
||||
maxTextWidth = lineWidth;
|
||||
}
|
||||
}
|
||||
if (!lastBookAuthor.empty()) {
|
||||
std::string trimmedAuthor = lastBookAuthor;
|
||||
while (renderer.getTextWidth(UI_10_FONT_ID, trimmedAuthor.c_str()) > maxLineWidth && !trimmedAuthor.empty()) {
|
||||
StringUtils::utf8RemoveLastChar(trimmedAuthor);
|
||||
}
|
||||
if (renderer.getTextWidth(UI_10_FONT_ID, trimmedAuthor.c_str()) <
|
||||
renderer.getTextWidth(UI_10_FONT_ID, lastBookAuthor.c_str())) {
|
||||
trimmedAuthor.append("...");
|
||||
}
|
||||
const int authorWidth = renderer.getTextWidth(UI_10_FONT_ID, trimmedAuthor.c_str());
|
||||
if (authorWidth > maxTextWidth) {
|
||||
maxTextWidth = authorWidth;
|
||||
}
|
||||
}
|
||||
|
||||
const int boxWidth = maxTextWidth + boxPadding * 2;
|
||||
const int boxHeight = totalTextHeight + boxPadding * 2;
|
||||
const int boxX = (pageWidth - boxWidth) / 2;
|
||||
const int boxY = titleYStart - boxPadding;
|
||||
|
||||
// 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);
|
||||
titleYStart += renderer.getLineHeight(UI_12_FONT_ID);
|
||||
@ -269,16 +456,40 @@ void HomeActivity::render() const {
|
||||
if (!lastBookAuthor.empty()) {
|
||||
titleYStart += renderer.getLineHeight(UI_10_FONT_ID) / 2;
|
||||
std::string trimmedAuthor = lastBookAuthor;
|
||||
// Trim author if too long
|
||||
// Trim author if too long (UTF-8 safe)
|
||||
bool wasTrimmed = false;
|
||||
while (renderer.getTextWidth(UI_10_FONT_ID, trimmedAuthor.c_str()) > maxLineWidth && !trimmedAuthor.empty()) {
|
||||
trimmedAuthor.resize(trimmedAuthor.size() - 5);
|
||||
StringUtils::utf8RemoveLastChar(trimmedAuthor);
|
||||
wasTrimmed = true;
|
||||
}
|
||||
if (wasTrimmed && !trimmedAuthor.empty()) {
|
||||
// Make room for ellipsis
|
||||
while (renderer.getTextWidth(UI_10_FONT_ID, (trimmedAuthor + "...").c_str()) > maxLineWidth &&
|
||||
!trimmedAuthor.empty()) {
|
||||
StringUtils::utf8RemoveLastChar(trimmedAuthor);
|
||||
}
|
||||
trimmedAuthor.append("...");
|
||||
}
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, titleYStart, trimmedAuthor.c_str(), !bookSelected);
|
||||
}
|
||||
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, bookY + bookHeight - renderer.getLineHeight(UI_10_FONT_ID) * 3 / 2,
|
||||
"Continue Reading", !bookSelected);
|
||||
// "Continue Reading" label at the bottom
|
||||
const int continueY = bookY + bookHeight - renderer.getLineHeight(UI_10_FONT_ID) * 3 / 2;
|
||||
if (coverRendered) {
|
||||
// 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;
|
||||
const int continueBoxWidth = continueTextWidth + continuePadding * 2;
|
||||
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, 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);
|
||||
}
|
||||
} else {
|
||||
// No book to continue reading
|
||||
const int y =
|
||||
|
||||
@ -14,8 +14,13 @@ class HomeActivity final : public Activity {
|
||||
bool updateRequired = false;
|
||||
bool hasContinueReading = false;
|
||||
bool hasOpdsUrl = false;
|
||||
bool hasCoverImage = false;
|
||||
bool coverRendered = false; // Track if cover has been rendered once
|
||||
bool coverBufferStored = false; // Track if cover buffer is stored
|
||||
uint8_t* coverBuffer = nullptr; // HomeActivity's own buffer for cover image
|
||||
std::string lastBookTitle;
|
||||
std::string lastBookAuthor;
|
||||
std::string coverBmpPath;
|
||||
const std::function<void()> onContinueReading;
|
||||
const std::function<void()> onReaderOpen;
|
||||
const std::function<void()> onSettingsOpen;
|
||||
@ -24,8 +29,11 @@ class HomeActivity final : public Activity {
|
||||
|
||||
static void taskTrampoline(void* param);
|
||||
[[noreturn]] void displayTaskLoop();
|
||||
void render() const;
|
||||
void render();
|
||||
int getMenuItemCount() const;
|
||||
bool storeCoverBuffer(); // Store frame buffer for cover image
|
||||
bool restoreCoverBuffer(); // Restore frame buffer from stored cover
|
||||
void freeCoverBuffer(); // Free the stored cover buffer
|
||||
|
||||
public:
|
||||
explicit HomeActivity(GfxRenderer& renderer, MappedInputManager& mappedInput,
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include <ESPmDNS.h>
|
||||
#include <GfxRenderer.h>
|
||||
#include <WiFi.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include <qrcode.h>
|
||||
|
||||
#include <cstddef>
|
||||
@ -83,9 +84,8 @@ void CrossPointWebServerActivity::onExit() {
|
||||
dnsServer = nullptr;
|
||||
}
|
||||
|
||||
// CRITICAL: Wait for LWIP stack to flush any pending packets
|
||||
Serial.printf("[%lu] [WEBACT] Waiting 500ms for network stack to flush pending packets...\n", millis());
|
||||
delay(500);
|
||||
// Brief wait for LWIP stack to flush pending packets
|
||||
delay(50);
|
||||
|
||||
// Disconnect WiFi gracefully
|
||||
if (isApMode) {
|
||||
@ -95,11 +95,11 @@ void CrossPointWebServerActivity::onExit() {
|
||||
Serial.printf("[%lu] [WEBACT] Disconnecting WiFi (graceful)...\n", millis());
|
||||
WiFi.disconnect(false); // false = don't erase credentials, send disconnect frame
|
||||
}
|
||||
delay(100); // Allow disconnect frame to be sent
|
||||
delay(30); // Allow disconnect frame to be sent
|
||||
|
||||
Serial.printf("[%lu] [WEBACT] Setting WiFi mode OFF...\n", millis());
|
||||
WiFi.mode(WIFI_OFF);
|
||||
delay(100); // Allow WiFi hardware to fully power down
|
||||
delay(30); // Allow WiFi hardware to power down
|
||||
|
||||
Serial.printf("[%lu] [WEBACT] [MEM] Free heap after WiFi disconnect: %d bytes\n", millis(), ESP.getFreeHeap());
|
||||
|
||||
@ -283,8 +283,28 @@ void CrossPointWebServerActivity::loop() {
|
||||
dnsServer->processNextRequest();
|
||||
}
|
||||
|
||||
// Handle web server requests - call handleClient multiple times per loop
|
||||
// to improve responsiveness and upload throughput
|
||||
// STA mode: Monitor WiFi connection health
|
||||
if (!isApMode && webServer && webServer->isRunning()) {
|
||||
static unsigned long lastWifiCheck = 0;
|
||||
if (millis() - lastWifiCheck > 2000) { // Check every 2 seconds
|
||||
lastWifiCheck = millis();
|
||||
const wl_status_t wifiStatus = WiFi.status();
|
||||
if (wifiStatus != WL_CONNECTED) {
|
||||
Serial.printf("[%lu] [WEBACT] WiFi disconnected! Status: %d\n", millis(), wifiStatus);
|
||||
// Show error and exit gracefully
|
||||
state = WebServerActivityState::SHUTTING_DOWN;
|
||||
updateRequired = true;
|
||||
return;
|
||||
}
|
||||
// Log weak signal warnings
|
||||
const int rssi = WiFi.RSSI();
|
||||
if (rssi < -75) {
|
||||
Serial.printf("[%lu] [WEBACT] Warning: Weak WiFi signal: %d dBm\n", millis(), rssi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle web server requests - maximize throughput with watchdog safety
|
||||
if (webServer && webServer->isRunning()) {
|
||||
const unsigned long timeSinceLastHandleClient = millis() - lastHandleClientTime;
|
||||
|
||||
@ -294,17 +314,32 @@ void CrossPointWebServerActivity::loop() {
|
||||
timeSinceLastHandleClient);
|
||||
}
|
||||
|
||||
// Call handleClient multiple times to process pending requests faster
|
||||
// This is critical for upload performance - HTTP file uploads send data
|
||||
// in chunks and each handleClient() call processes incoming data
|
||||
constexpr int HANDLE_CLIENT_ITERATIONS = 10;
|
||||
for (int i = 0; i < HANDLE_CLIENT_ITERATIONS && webServer->isRunning(); i++) {
|
||||
// Reset watchdog BEFORE processing - HTTP header parsing can be slow
|
||||
esp_task_wdt_reset();
|
||||
|
||||
// Process HTTP requests in tight loop for maximum throughput
|
||||
// More iterations = more data processed per main loop cycle
|
||||
constexpr int MAX_ITERATIONS = 500;
|
||||
for (int i = 0; i < MAX_ITERATIONS && webServer->isRunning(); i++) {
|
||||
webServer->handleClient();
|
||||
// Reset watchdog every 32 iterations
|
||||
if ((i & 0x1F) == 0x1F) {
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
// Yield and check for exit button every 64 iterations
|
||||
if ((i & 0x3F) == 0x3F) {
|
||||
yield();
|
||||
// Check for exit button inside loop for responsiveness
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
onGoBack();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
lastHandleClientTime = millis();
|
||||
}
|
||||
|
||||
// Handle exit on Back button
|
||||
// Handle exit on Back button (also check outside loop)
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
onGoBack();
|
||||
return;
|
||||
|
||||
@ -37,6 +37,14 @@ void WifiSelectionActivity::onEnter() {
|
||||
savePromptSelection = 0;
|
||||
forgetPromptSelection = 0;
|
||||
|
||||
// Cache MAC address for display
|
||||
uint8_t mac[6];
|
||||
WiFi.macAddress(mac);
|
||||
char macStr[32];
|
||||
snprintf(macStr, sizeof(macStr), "MAC address: %02x-%02x-%02x-%02x-%02x-%02x", mac[0], mac[1], mac[2], mac[3], mac[4],
|
||||
mac[5]);
|
||||
cachedMacAddress = std::string(macStr);
|
||||
|
||||
// Trigger first update to show scanning message
|
||||
updateRequired = true;
|
||||
|
||||
@ -572,6 +580,9 @@ void WifiSelectionActivity::renderNetworkList() const {
|
||||
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 90, countStr);
|
||||
}
|
||||
|
||||
// Show MAC address above the network count and legend
|
||||
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 105, cachedMacAddress.c_str());
|
||||
|
||||
// Draw help text
|
||||
renderer.drawText(SMALL_FONT_ID, 20, pageHeight - 75, "* = Encrypted | + = Saved");
|
||||
const auto labels = mappedInput.mapLabels("« Back", "Connect", "", "");
|
||||
|
||||
@ -62,6 +62,9 @@ class WifiSelectionActivity final : public ActivityWithSubactivity {
|
||||
// Password to potentially save (from keyboard or saved credentials)
|
||||
std::string enteredPassword;
|
||||
|
||||
// Cached MAC address string for display
|
||||
std::string cachedMacAddress;
|
||||
|
||||
// Whether network was connected using a saved password (skip save prompt)
|
||||
bool usedSavedPassword = false;
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
@ -168,7 +180,7 @@ void EpubReaderActivity::loop() {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool skipChapter = mappedInput.getHeldTime() > skipChapterMs;
|
||||
const bool skipChapter = SETTINGS.longPressChapterSkip && mappedInput.getHeldTime() > skipChapterMs;
|
||||
|
||||
if (skipChapter) {
|
||||
// We don't want to delete the section mid-render, so grab the semaphore
|
||||
@ -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");
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user