mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2025-12-19 15:47:40 +03:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b5a63d220 | ||
|
|
c1d5f5d562 | ||
|
|
adfeee063f | ||
|
|
2d3928ed81 | ||
|
|
48249fbd1e | ||
|
|
1a53dccebd | ||
|
|
3e28724b62 | ||
|
|
d86b3fe134 | ||
|
|
1a3d6b125d | ||
|
|
b2020f5512 | ||
|
|
70dc0f018e | ||
|
|
424594488f | ||
|
|
57fdb1c0fb | ||
|
|
5e1694748c | ||
|
|
063a1df851 | ||
|
|
d429966dd4 | ||
|
|
c78f2a9840 | ||
|
|
11f01d3a41 | ||
|
|
973d372521 | ||
|
|
67da8139b3 | ||
|
|
c287aa03a4 |
@ -1,505 +0,0 @@
|
|||||||
/**
|
|
||||||
* generated by fontconvert.py
|
|
||||||
* name: babyblue
|
|
||||||
* size: 8
|
|
||||||
* mode: 1-bit
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
#include "EpdFontData.h"
|
|
||||||
|
|
||||||
static const uint8_t babyblueBitmaps[3140] = {
|
|
||||||
0xFF, 0xFF, 0x30, 0xFF, 0xFF, 0xF0, 0x36, 0x1B, 0x0D, 0x9F, 0xFF, 0xF9, 0xB3, 0xFF, 0xFF, 0x36, 0x1B, 0x0D, 0x80,
|
|
||||||
0x18, 0x18, 0x7E, 0xFF, 0xDB, 0xD8, 0xFE, 0x7F, 0x9B, 0xDB, 0xFF, 0x7E, 0x18, 0x00, 0x71, 0x9F, 0x73, 0x6C, 0x6F,
|
|
||||||
0x8F, 0xE0, 0xFF, 0x83, 0xF8, 0xFB, 0x1B, 0x63, 0x7C, 0xC7, 0x00, 0x38, 0x3E, 0x1B, 0x0D, 0x87, 0xC3, 0xEF, 0xBF,
|
|
||||||
0x8E, 0xCF, 0x7F, 0x1E, 0xC0, 0xFF, 0x37, 0xEC, 0xCC, 0xCC, 0xCC, 0xCC, 0x67, 0x20, 0xCE, 0x73, 0x33, 0x33, 0x33,
|
|
||||||
0x33, 0x6C, 0x80, 0x32, 0xFF, 0xDE, 0xFE, 0x30, 0x18, 0x18, 0x18, 0xFF, 0xFF, 0x18, 0x18, 0x18, 0xBF, 0x00, 0xFF,
|
|
||||||
0xC0, 0x30, 0x0C, 0x31, 0xC6, 0x18, 0xE3, 0x1C, 0x63, 0x8C, 0x00, 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
|
|
||||||
0xC7, 0x7E, 0x3C, 0x19, 0xDF, 0xFD, 0x8C, 0x63, 0x18, 0xC6, 0x3C, 0x7E, 0xE7, 0x83, 0x07, 0x0E, 0x1C, 0x38, 0x70,
|
|
||||||
0xFE, 0xFF, 0x7E, 0xFF, 0xC3, 0x07, 0x3E, 0x3E, 0x07, 0x03, 0x83, 0xFF, 0x7E, 0x0E, 0x1E, 0x3E, 0x76, 0xE6, 0xFF,
|
|
||||||
0xFF, 0x06, 0x06, 0x06, 0x06, 0x7F, 0xFF, 0x06, 0x0F, 0xDF, 0xC1, 0x83, 0x87, 0xFD, 0xF0, 0x3E, 0x7F, 0xE3, 0xC0,
|
|
||||||
0xFC, 0xFE, 0xE7, 0xC3, 0xC7, 0x7E, 0x3C, 0xFF, 0xFF, 0xC0, 0xE0, 0xE0, 0x60, 0x70, 0x30, 0x18, 0x1C, 0x0C, 0x06,
|
|
||||||
0x00, 0x3C, 0x7E, 0x66, 0x66, 0x7E, 0x7E, 0xE7, 0xC3, 0xC7, 0x7E, 0x3C, 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC7, 0x7F,
|
|
||||||
0x3F, 0x07, 0x3E, 0x7C, 0xB0, 0x03, 0xB0, 0x03, 0xF8, 0x06, 0x3D, 0xF7, 0x8F, 0x0F, 0x87, 0x03, 0xFF, 0xFF, 0x00,
|
|
||||||
0xFF, 0xFF, 0x81, 0xE1, 0xF0, 0xF1, 0xEF, 0xBC, 0x40, 0x38, 0xFB, 0xBE, 0x30, 0xE3, 0x8E, 0x18, 0x30, 0x00, 0xC0,
|
|
||||||
0x0F, 0xE0, 0x7F, 0xC3, 0x83, 0x9D, 0xE7, 0xEF, 0xCF, 0x73, 0x3D, 0x8C, 0xF6, 0x33, 0xD8, 0xDB, 0x3F, 0xC6, 0x7E,
|
|
||||||
0x0C, 0x03, 0x18, 0x18, 0x7F, 0xE0, 0xFF, 0x00, 0x38, 0xFB, 0xBE, 0x3C, 0x7F, 0xFF, 0xE3, 0xC7, 0x8C, 0xFC, 0xFE,
|
|
||||||
0xC7, 0xC7, 0xFE, 0xFE, 0xC7, 0xC7, 0xFE, 0xFC, 0x3F, 0x3F, 0xF8, 0x78, 0x0C, 0x06, 0x03, 0x01, 0x83, 0x7F, 0x9F,
|
|
||||||
0x80, 0xFC, 0xFE, 0xC7, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0xFE, 0xFC, 0xFF, 0xFF, 0x06, 0x0F, 0xDF, 0xB0, 0x60, 0xFD,
|
|
||||||
0xFC, 0xFF, 0xFF, 0x06, 0x0F, 0xDF, 0xB0, 0x60, 0xC1, 0x80, 0x3F, 0x3F, 0xF8, 0x78, 0x0C, 0x7E, 0x3F, 0x07, 0x83,
|
|
||||||
0x7F, 0x9F, 0x80, 0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF, 0xF0, 0x0C, 0x30, 0xC3,
|
|
||||||
0x0C, 0x38, 0xF3, 0xFD, 0xE0, 0xC3, 0xC7, 0xCE, 0xDC, 0xF8, 0xF8, 0xDC, 0xCC, 0xC6, 0xC3, 0xC1, 0x83, 0x06, 0x0C,
|
|
||||||
0x18, 0x30, 0x60, 0xFD, 0xFC, 0xE1, 0xF8, 0x7F, 0x3F, 0xCF, 0xFF, 0xF7, 0xBD, 0xEF, 0x7B, 0xCC, 0xF3, 0x30, 0xC3,
|
|
||||||
0xE3, 0xF3, 0xF3, 0xFB, 0xDF, 0xCF, 0xCF, 0xC7, 0xC3, 0x3E, 0x3F, 0xB8, 0xF8, 0x3C, 0x1E, 0x0F, 0x07, 0x87, 0x7F,
|
|
||||||
0x1F, 0x00, 0xFE, 0xFF, 0xC3, 0xC3, 0xFF, 0xFE, 0xC0, 0xC0, 0xC0, 0xC0, 0x3E, 0x3F, 0xB8, 0xF8, 0x3C, 0x1E, 0x0F,
|
|
||||||
0x37, 0x9F, 0x7F, 0x1F, 0xC0, 0xFE, 0xFF, 0xC3, 0xC7, 0xFE, 0xFE, 0xC7, 0xC3, 0xC3, 0xC3, 0x7D, 0xFF, 0x1F, 0x87,
|
|
||||||
0xC3, 0xC1, 0xC3, 0xFE, 0xF8, 0xFF, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xC3, 0xC3, 0xC3, 0xC3,
|
|
||||||
0xC3, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0xC3, 0xC3, 0xC3, 0xE7, 0x66, 0x7E, 0x3C, 0x3C, 0x3C, 0x18, 0x83, 0x0F, 0x0C,
|
|
||||||
0x3C, 0x30, 0xF1, 0xE7, 0x67, 0x99, 0xBF, 0xE3, 0xCF, 0x0F, 0x3C, 0x3C, 0xF0, 0x61, 0x80, 0x80, 0xF8, 0x77, 0x38,
|
|
||||||
0xFC, 0x1E, 0x07, 0x83, 0xF1, 0xCE, 0xE1, 0xB0, 0x30, 0xC3, 0xC3, 0xE7, 0x7E, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18,
|
|
||||||
0xFF, 0xFF, 0x07, 0x0E, 0x1C, 0x38, 0x70, 0xE0, 0xFE, 0xFF, 0xFF, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xFE, 0x81, 0xC1,
|
|
||||||
0x83, 0x83, 0x83, 0x06, 0x06, 0x0C, 0x0C, 0xFF, 0x33, 0x33, 0x33, 0x33, 0x33, 0xFE, 0x18, 0x3C, 0x7E, 0x66, 0xE7,
|
|
||||||
0xC3, 0x83, 0xFF, 0xFF, 0x9D, 0x80, 0x7D, 0xFE, 0x1B, 0xFF, 0xF8, 0xFF, 0xBF, 0xC1, 0x83, 0xE7, 0xEC, 0xF8, 0xF1,
|
|
||||||
0xE7, 0xFD, 0xF0, 0x3C, 0xFF, 0x9E, 0x0C, 0x18, 0x9F, 0x9E, 0x06, 0x0C, 0xFB, 0xFE, 0x78, 0xF1, 0xE3, 0x7E, 0x7C,
|
|
||||||
0x3C, 0x7E, 0xE7, 0xFF, 0xFF, 0xC0, 0x7E, 0x3F, 0x3B, 0xD9, 0xFF, 0xB1, 0x8C, 0x63, 0x18, 0x3E, 0xFF, 0x9E, 0x3C,
|
|
||||||
0x78, 0xDF, 0x9F, 0x06, 0x0D, 0xF3, 0xC0, 0xC3, 0x0F, 0xBF, 0xEF, 0x3C, 0xF3, 0xCF, 0x30, 0xFB, 0xFF, 0xF0, 0x6D,
|
|
||||||
0x36, 0xDB, 0x6D, 0xBD, 0x00, 0xC3, 0x0C, 0xF7, 0xFB, 0xCE, 0x3C, 0xDB, 0x30, 0xFF, 0xFF, 0xF0, 0x7F, 0xBF, 0xFC,
|
|
||||||
0xCF, 0x33, 0xCC, 0xF3, 0x3C, 0xCF, 0x33, 0x7B, 0xFC, 0xF3, 0xCF, 0x3C, 0xF3, 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC7,
|
|
||||||
0x7E, 0x3C, 0x79, 0xFB, 0x3E, 0x3C, 0x79, 0xFF, 0x7C, 0xC1, 0x82, 0x00, 0x3C, 0xFF, 0x9E, 0x3C, 0x78, 0xDF, 0x9F,
|
|
||||||
0x06, 0x0C, 0x10, 0x77, 0xF7, 0x8C, 0x63, 0x18, 0x7D, 0xFF, 0x1F, 0xE7, 0xF0, 0xFF, 0xBE, 0x63, 0x3D, 0xE6, 0x31,
|
|
||||||
0x8C, 0x71, 0xC0, 0x8F, 0x3C, 0xF3, 0xCF, 0x3F, 0xDE, 0x83, 0xC3, 0xC7, 0x66, 0x66, 0x6E, 0x3C, 0x18, 0x80, 0xF3,
|
|
||||||
0x3D, 0xFD, 0xFE, 0x7F, 0x9F, 0xE3, 0x30, 0xCC, 0x83, 0xC7, 0x6E, 0x3C, 0x38, 0x7C, 0xE6, 0xC3, 0x83, 0xC7, 0x66,
|
|
||||||
0x6E, 0x3C, 0x3C, 0x18, 0x18, 0x18, 0x70, 0x60, 0xFF, 0xFC, 0x71, 0xC7, 0x1C, 0x3F, 0x7F, 0x19, 0xCC, 0x63, 0x3B,
|
|
||||||
0x98, 0x61, 0x8C, 0x63, 0x1C, 0x40, 0xFF, 0xFF, 0xFF, 0xF8, 0x83, 0x87, 0x0C, 0x30, 0xE1, 0xC7, 0x38, 0xC3, 0x0C,
|
|
||||||
0x63, 0x08, 0x00, 0x79, 0xFF, 0xE3, 0xC0, 0xF2, 0xFF, 0xFE, 0x18, 0x30, 0xF3, 0xFF, 0xFB, 0x36, 0x6E, 0x7E, 0x78,
|
|
||||||
0x60, 0xC1, 0x00, 0x3C, 0x7E, 0x66, 0x60, 0xFC, 0xFC, 0x30, 0x72, 0xFF, 0xFE, 0x83, 0xFF, 0x7E, 0x66, 0x66, 0x7E,
|
|
||||||
0xFE, 0x83, 0x83, 0xE7, 0x7E, 0x3C, 0xFF, 0xFF, 0xFF, 0xFF, 0x18, 0x18, 0xFF, 0xFC, 0xBF, 0xF8, 0x3C, 0x7E, 0x66,
|
|
||||||
0x7E, 0x7C, 0xEE, 0xC7, 0xC3, 0x77, 0x3E, 0x0C, 0x66, 0x66, 0x7C, 0x38, 0x9E, 0xE6, 0x3F, 0x1F, 0xEF, 0xFF, 0xFF,
|
|
||||||
0xF3, 0xFC, 0x3F, 0x3F, 0xFF, 0xDF, 0xDF, 0xE3, 0xF0, 0x77, 0xFF, 0xFF, 0xBC, 0x36, 0xFF, 0xF6, 0xCD, 0xCD, 0x8D,
|
|
||||||
0x80, 0xFF, 0xFF, 0x03, 0x03, 0x03, 0x3F, 0x1F, 0xEF, 0xFF, 0xFB, 0xF6, 0xFF, 0xBF, 0xEF, 0xDB, 0xF7, 0xDF, 0xE3,
|
|
||||||
0xF0, 0xFF, 0xFF, 0x6F, 0xFF, 0x60, 0x18, 0x18, 0x18, 0xFF, 0xFF, 0x18, 0x18, 0xFE, 0xFF, 0x77, 0xE6, 0x77, 0x73,
|
|
||||||
0xFF, 0x77, 0xEF, 0x7F, 0xB8, 0x7F, 0x00, 0xCF, 0x3C, 0xF3, 0xCF, 0x3F, 0xFE, 0xC3, 0x0C, 0x20, 0x7F, 0xFF, 0xFE,
|
|
||||||
0xFE, 0xFE, 0x7E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x1E, 0x16, 0xB0, 0x63, 0xEC, 0x37, 0xFB, 0x33, 0x30, 0x7B,
|
|
||||||
0xFC, 0xF3, 0xFD, 0xE0, 0x99, 0xF9, 0xF9, 0xB7, 0xFF, 0xB6, 0x00, 0x30, 0x07, 0x03, 0xF0, 0x7B, 0x0E, 0x31, 0xC3,
|
|
||||||
0x38, 0x37, 0x60, 0xEE, 0x1D, 0xE3, 0xBF, 0x33, 0xF6, 0x06, 0x30, 0x67, 0x0E, 0xF1, 0xCB, 0x38, 0x37, 0x03, 0xEF,
|
|
||||||
0x1D, 0xF3, 0x9F, 0x70, 0xEE, 0x0E, 0xC1, 0xF0, 0x70, 0x6F, 0x8E, 0xB9, 0xCB, 0xB8, 0xFF, 0x07, 0xE6, 0x1C, 0xE3,
|
|
||||||
0x9E, 0x73, 0xFE, 0x3F, 0xC0, 0x60, 0x30, 0x60, 0xC1, 0x87, 0x1C, 0x30, 0x60, 0xC6, 0xD9, 0xE1, 0x80, 0x30, 0x70,
|
|
||||||
0x61, 0xC7, 0xDD, 0xF1, 0xE3, 0xFF, 0xFF, 0x1E, 0x3C, 0x60, 0x18, 0x70, 0xC1, 0xC7, 0xDD, 0xF1, 0xE3, 0xFF, 0xFF,
|
|
||||||
0x1E, 0x3C, 0x60, 0x38, 0xF9, 0xB1, 0xC7, 0xDD, 0xF1, 0xE3, 0xFF, 0xFF, 0x1E, 0x3C, 0x60, 0x3C, 0xF9, 0xE1, 0xC7,
|
|
||||||
0xDD, 0xF1, 0xE3, 0xFF, 0xFF, 0x1E, 0x3C, 0x60, 0x6C, 0xD8, 0xE3, 0xEE, 0xF8, 0xF1, 0xFF, 0xFF, 0x8F, 0x1E, 0x30,
|
|
||||||
0x38, 0xF9, 0xF1, 0xC7, 0xDD, 0xF1, 0xE3, 0xFF, 0xFF, 0x1E, 0x3C, 0x60, 0x0F, 0xF8, 0x7F, 0xC7, 0xC0, 0x36, 0x03,
|
|
||||||
0xB0, 0x19, 0xF9, 0xFF, 0xCF, 0xE0, 0xE3, 0x06, 0x1F, 0xB0, 0xFE, 0x3F, 0x3F, 0xF8, 0x78, 0x0C, 0x06, 0x03, 0x01,
|
|
||||||
0x83, 0x7F, 0x9F, 0x86, 0x01, 0x83, 0x81, 0x80, 0x30, 0x70, 0x67, 0xFF, 0xF8, 0x30, 0x7E, 0xFD, 0x83, 0x07, 0xEF,
|
|
||||||
0xE0, 0x0C, 0x38, 0x67, 0xFF, 0xF8, 0x30, 0x7E, 0xFD, 0x83, 0x07, 0xEF, 0xE0, 0x18, 0x78, 0xF7, 0xFF, 0xF8, 0x30,
|
|
||||||
0x7E, 0xFD, 0x83, 0x07, 0xEF, 0xE0, 0x3C, 0x7B, 0xFF, 0xFC, 0x18, 0x3F, 0x7E, 0xC1, 0x83, 0xF7, 0xF0, 0x9D, 0x36,
|
|
||||||
0xDB, 0x6D, 0xB6, 0x7A, 0x6D, 0xB6, 0xDB, 0x6C, 0x6F, 0xB6, 0x66, 0x66, 0x66, 0x66, 0x60, 0xBB, 0x66, 0x66, 0x66,
|
|
||||||
0x66, 0x66, 0x7E, 0x3F, 0x98, 0xEC, 0x3F, 0xDF, 0xED, 0x86, 0xC7, 0x7F, 0x3F, 0x00, 0x1E, 0x3E, 0x3C, 0xC3, 0xE3,
|
|
||||||
0xF3, 0xF3, 0xFB, 0xDF, 0xCF, 0xCF, 0xC7, 0xC3, 0x18, 0x0E, 0x03, 0x07, 0xC7, 0xF7, 0x1F, 0x07, 0x83, 0xC1, 0xE0,
|
|
||||||
0xF0, 0xEF, 0xE3, 0xE0, 0x0C, 0x0E, 0x06, 0x07, 0xC7, 0xF7, 0x1F, 0x07, 0x83, 0xC1, 0xE0, 0xF0, 0xEF, 0xE3, 0xE0,
|
|
||||||
0x1C, 0x1F, 0x0D, 0x87, 0xC7, 0xF7, 0x1F, 0x07, 0x83, 0xC1, 0xE0, 0xF0, 0xEF, 0xE3, 0xE0, 0x1E, 0x1F, 0x0F, 0x07,
|
|
||||||
0xC7, 0xF7, 0x1F, 0x07, 0x83, 0xC1, 0xE0, 0xF0, 0xEF, 0xE3, 0xE0, 0x36, 0x1B, 0x0F, 0x8F, 0xEE, 0x3E, 0x0F, 0x07,
|
|
||||||
0x83, 0xC1, 0xE1, 0xDF, 0xC7, 0xC0, 0x8F, 0xF7, 0x9E, 0xFF, 0x30, 0x1F, 0xCF, 0xF7, 0x3D, 0x9F, 0x6E, 0xDF, 0x37,
|
|
||||||
0x8D, 0xC7, 0xFF, 0xB7, 0xC0, 0x30, 0x38, 0x18, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x0C,
|
|
||||||
0x1C, 0x18, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x18, 0x3C, 0x3C, 0xC3, 0xC3, 0xC3, 0xC3,
|
|
||||||
0xC3, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x3C, 0x3C, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x0C,
|
|
||||||
0x1C, 0x18, 0xC3, 0xC3, 0xE7, 0x7E, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0xC0, 0xFC, 0xFE, 0xC7, 0xC3, 0xC7, 0xFE,
|
|
||||||
0xFC, 0xC0, 0xC0, 0x38, 0xFB, 0xB6, 0x6D, 0xDB, 0x37, 0x67, 0xE7, 0xFF, 0x70, 0x30, 0x70, 0x63, 0xEF, 0xF0, 0xDF,
|
|
||||||
0xFF, 0xC7, 0xFD, 0xF8, 0x0C, 0x38, 0x63, 0xEF, 0xF0, 0xDF, 0xFF, 0xC7, 0xFD, 0xF8, 0x18, 0x78, 0xF3, 0xEF, 0xF0,
|
|
||||||
0xDF, 0xFF, 0xC7, 0xFD, 0xF8, 0x3C, 0xF9, 0xE3, 0xEF, 0xF0, 0xDF, 0xFF, 0xC7, 0xFD, 0xF8, 0x3C, 0x79, 0xF7, 0xF8,
|
|
||||||
0x6F, 0xFF, 0xE3, 0xFE, 0xFC, 0x18, 0x78, 0xF0, 0xC7, 0xDF, 0xE1, 0xBF, 0xFF, 0x8F, 0xFB, 0xF0, 0x7F, 0xEF, 0xFF,
|
|
||||||
0x86, 0x37, 0xFF, 0xFF, 0xFC, 0x61, 0xFF, 0xF7, 0xFE, 0x3C, 0xFF, 0x9E, 0x0C, 0x18, 0x9F, 0x9E, 0x18, 0x18, 0xE1,
|
|
||||||
0x80, 0x30, 0x38, 0x18, 0x3C, 0x7E, 0xE7, 0xFF, 0xFF, 0xC0, 0x7E, 0x3F, 0x0C, 0x1C, 0x18, 0x3C, 0x7E, 0xE7, 0xFF,
|
|
||||||
0xFF, 0xC0, 0x7E, 0x3F, 0x18, 0x3C, 0x3C, 0x3C, 0x7E, 0xE7, 0xFF, 0xFF, 0xC0, 0x7E, 0x3F, 0x3C, 0x3C, 0x3C, 0x7E,
|
|
||||||
0xE7, 0xFF, 0xFF, 0xC0, 0x7E, 0x3F, 0x9D, 0xA6, 0xDB, 0x6D, 0x80, 0x7F, 0x4D, 0xB6, 0xDB, 0x00, 0x6F, 0xB4, 0x66,
|
|
||||||
0x66, 0x66, 0x60, 0xBB, 0x46, 0x66, 0x66, 0x66, 0x3E, 0x3E, 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C,
|
|
||||||
0x3D, 0xF7, 0x9E, 0xFF, 0x3C, 0xF3, 0xCF, 0x3C, 0xC0, 0x30, 0x38, 0x18, 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC7, 0x7E,
|
|
||||||
0x3C, 0x0C, 0x1C, 0x18, 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x18, 0x3C, 0x3C, 0x3C, 0x7E, 0xE7, 0xC3,
|
|
||||||
0xC3, 0xC7, 0x7E, 0x3C, 0x1E, 0x3E, 0x3C, 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x3C, 0x3C, 0x3C, 0x7E,
|
|
||||||
0xE7, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x18, 0x18, 0xFF, 0xFF, 0x10, 0x18, 0x3F, 0x7F, 0xEF, 0xDF, 0xFB, 0xF7, 0xFE,
|
|
||||||
0xFC, 0x61, 0xC3, 0x23, 0xCF, 0x3C, 0xF3, 0xCF, 0xF7, 0x80, 0x18, 0xE3, 0x23, 0xCF, 0x3C, 0xF3, 0xCF, 0xF7, 0x80,
|
|
||||||
0x31, 0xE7, 0xA3, 0xCF, 0x3C, 0xF3, 0xCF, 0xF7, 0x80, 0x79, 0xE8, 0xF3, 0xCF, 0x3C, 0xF3, 0xFD, 0xE0, 0x0C, 0x1C,
|
|
||||||
0x18, 0x83, 0xC7, 0x66, 0x6E, 0x3C, 0x3C, 0x18, 0x18, 0x18, 0x70, 0x60, 0xC1, 0x83, 0xE7, 0xEE, 0xF8, 0xF1, 0xE7,
|
|
||||||
0xFD, 0xF3, 0x06, 0x08, 0x00, 0x3C, 0x3C, 0x83, 0xC7, 0x66, 0x6E, 0x3C, 0x3C, 0x18, 0x18, 0x18, 0x70, 0x60, 0x7C,
|
|
||||||
0xF8, 0xE3, 0xEE, 0xF8, 0xF1, 0xFF, 0xFF, 0x8F, 0x1E, 0x30, 0x7C, 0xF9, 0xF7, 0xF8, 0x6F, 0xFF, 0xE3, 0xFE, 0xFC,
|
|
||||||
0x6C, 0xF8, 0xE1, 0xC7, 0xDD, 0xF1, 0xE3, 0xFF, 0xFF, 0x1E, 0x3C, 0x60, 0x6C, 0xF8, 0xE3, 0xEF, 0xF0, 0xDF, 0xFF,
|
|
||||||
0xC7, 0xFD, 0xF8, 0x38, 0x7C, 0xEE, 0xC6, 0xC6, 0xFE, 0xFE, 0xC6, 0xC6, 0xC6, 0x0C, 0x0C, 0x0F, 0x07, 0x7C, 0xFE,
|
|
||||||
0x86, 0x7E, 0xFE, 0xC6, 0xFE, 0x7E, 0x06, 0x0C, 0x0F, 0x0F, 0x0C, 0x0E, 0x06, 0x07, 0xE7, 0xFF, 0x0F, 0x01, 0x80,
|
|
||||||
0xC0, 0x60, 0x30, 0x6F, 0xF3, 0xF0, 0x0C, 0x38, 0x61, 0xE7, 0xFC, 0xF0, 0x60, 0xC4, 0xFC, 0xF0, 0x0C, 0x0F, 0x07,
|
|
||||||
0x87, 0xE7, 0xFF, 0x0F, 0x01, 0x80, 0xC0, 0x60, 0x30, 0x6F, 0xF3, 0xF0, 0x18, 0x78, 0xF1, 0xE7, 0xFC, 0xF0, 0x60,
|
|
||||||
0xC4, 0xFC, 0xF0, 0x0C, 0x06, 0x0F, 0xCF, 0xFE, 0x1E, 0x03, 0x01, 0x80, 0xC0, 0x60, 0xDF, 0xE7, 0xE0, 0x18, 0x30,
|
|
||||||
0xF3, 0xFE, 0x78, 0x30, 0x62, 0x7E, 0x78, 0x1E, 0x0F, 0x03, 0x07, 0xE7, 0xFF, 0x0F, 0x01, 0x80, 0xC0, 0x60, 0x30,
|
|
||||||
0x6F, 0xF3, 0xF0, 0x3C, 0x78, 0x61, 0xE7, 0xFC, 0xF0, 0x60, 0xC4, 0xFC, 0xF0, 0x78, 0x78, 0x30, 0xFC, 0xFE, 0xC7,
|
|
||||||
0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0xFE, 0xFC, 0x01, 0x83, 0xC1, 0xE7, 0xC7, 0xE7, 0x33, 0x19, 0x8C, 0xC6, 0x3F, 0x0F,
|
|
||||||
0x80, 0x7E, 0x3F, 0x98, 0xEC, 0x3F, 0xDF, 0xED, 0x86, 0xC7, 0x7F, 0x3F, 0x00, 0x06, 0x1F, 0x1F, 0x3E, 0x7E, 0xE6,
|
|
||||||
0xC6, 0xC6, 0xC6, 0x7E, 0x3E, 0x3C, 0x7B, 0xFF, 0xFC, 0x18, 0x3F, 0x7E, 0xC1, 0x83, 0xF7, 0xF0, 0x3C, 0x3C, 0x3C,
|
|
||||||
0x7E, 0xE7, 0xFF, 0xFF, 0xC0, 0x7E, 0x3F, 0x66, 0xFC, 0xF7, 0xFF, 0xF8, 0x30, 0x7E, 0xFD, 0x83, 0x07, 0xEF, 0xE0,
|
|
||||||
0x66, 0x7E, 0x3C, 0x3C, 0x7E, 0xE7, 0xFF, 0xFF, 0xC0, 0x7E, 0x3F, 0x18, 0x33, 0xFF, 0xFC, 0x18, 0x3F, 0x7E, 0xC1,
|
|
||||||
0x83, 0xF7, 0xF0, 0x18, 0x18, 0x3C, 0x7E, 0xE7, 0xFF, 0xFF, 0xC0, 0x7E, 0x3F, 0xFE, 0xFE, 0xC0, 0xC0, 0xFC, 0xFC,
|
|
||||||
0xC0, 0xC0, 0xFC, 0xFE, 0x06, 0x0C, 0x0F, 0x0F, 0x3C, 0x7E, 0xE7, 0xFF, 0xFF, 0xC0, 0x7E, 0x3F, 0x06, 0x0C, 0x0F,
|
|
||||||
0x07, 0x3C, 0x78, 0x67, 0xFF, 0xF8, 0x30, 0x7E, 0xFD, 0x83, 0x07, 0xEF, 0xE0, 0x3C, 0x3C, 0x18, 0x3C, 0x7E, 0xE7,
|
|
||||||
0xFF, 0xFF, 0xC0, 0x7E, 0x3F, 0x0C, 0x0F, 0x07, 0x87, 0xE7, 0xFF, 0x0F, 0x01, 0x8F, 0xC7, 0xE0, 0xF0, 0x6F, 0xF3,
|
|
||||||
0xF0, 0x18, 0x78, 0xF1, 0xF7, 0xFC, 0xF1, 0xE3, 0xC6, 0xFC, 0xF8, 0x30, 0x6F, 0x9E, 0x00, 0x36, 0x1F, 0x07, 0x07,
|
|
||||||
0xE7, 0xFF, 0x0F, 0x01, 0x8F, 0xC7, 0xE0, 0xF0, 0x6F, 0xF3, 0xF0, 0x36, 0x7C, 0x71, 0xF7, 0xFC, 0xF1, 0xE3, 0xC6,
|
|
||||||
0xFC, 0xF8, 0x30, 0x6F, 0x9E, 0x00, 0x0C, 0x06, 0x0F, 0xCF, 0xFE, 0x1E, 0x03, 0x1F, 0x8F, 0xC1, 0xE0, 0xDF, 0xE7,
|
|
||||||
0xE0, 0x18, 0x30, 0xFB, 0xFE, 0x78, 0xF1, 0xE3, 0x7E, 0x7C, 0x18, 0x37, 0xCF, 0x00, 0x3F, 0x3F, 0xF8, 0x78, 0x0C,
|
|
||||||
0x7E, 0x3F, 0x07, 0x83, 0x7F, 0x9F, 0x83, 0x00, 0xC1, 0xC0, 0xE0, 0x18, 0x30, 0x61, 0xF7, 0xFC, 0xF1, 0xE3, 0xC6,
|
|
||||||
0xFC, 0xF8, 0x30, 0x6F, 0x9E, 0x00, 0x18, 0x3C, 0x3C, 0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3,
|
|
||||||
0x31, 0xE7, 0xB0, 0xC3, 0xEF, 0xFB, 0xCF, 0x3C, 0xF3, 0xCC, 0x61, 0xBF, 0xFF, 0xFD, 0x86, 0x7F, 0x9F, 0xE6, 0x19,
|
|
||||||
0x86, 0x61, 0x98, 0x60, 0x61, 0xF3, 0xE3, 0xE7, 0xEE, 0xD9, 0xB3, 0x66, 0xCD, 0x98, 0x7F, 0xEC, 0xC6, 0x31, 0x8C,
|
|
||||||
0x63, 0x18, 0xC6, 0x00, 0x7F, 0xEC, 0x86, 0x31, 0x8C, 0x63, 0x18, 0xFF, 0x66, 0x66, 0x66, 0x66, 0x66, 0xFF, 0x46,
|
|
||||||
0x66, 0x66, 0x66, 0x9F, 0xDC, 0xC6, 0x31, 0x8C, 0x63, 0x18, 0xC6, 0x00, 0x9F, 0xDC, 0x86, 0x31, 0x8C, 0x63, 0x18,
|
|
||||||
0x66, 0x66, 0x66, 0x66, 0x66, 0x6C, 0xF6, 0x66, 0x46, 0x66, 0x66, 0x66, 0x6C, 0xF6, 0xEF, 0xFF, 0xFF, 0xBF, 0xFF,
|
|
||||||
0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xE3, 0xF3, 0xFF, 0xDE, 0xDE, 0xE7, 0xBD, 0xEF, 0x7B, 0xDE, 0xC6, 0x33, 0x10,
|
|
||||||
0x0C, 0x3C, 0x78, 0x60, 0xC1, 0x83, 0x06, 0x0D, 0x1B, 0x37, 0xE7, 0x80, 0x6F, 0xB4, 0x66, 0x66, 0x66, 0x66, 0x6C,
|
|
||||||
0x80, 0xC3, 0xC7, 0xCE, 0xDC, 0xF8, 0xF8, 0xDC, 0xCC, 0xC6, 0xC3, 0x38, 0x1C, 0x38, 0x30, 0xC3, 0x0C, 0xF7, 0xFB,
|
|
||||||
0xCE, 0x3C, 0xDB, 0x37, 0x0E, 0x71, 0x80, 0x8F, 0x7F, 0xBC, 0xE3, 0xCD, 0xB3, 0x30, 0xE1, 0x86, 0x0C, 0x18, 0x30,
|
|
||||||
0x60, 0xC1, 0x83, 0x07, 0xEF, 0xE0, 0x7A, 0x6D, 0xB6, 0xDB, 0x6C, 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xFD,
|
|
||||||
0xFD, 0xC1, 0xC7, 0x0C, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0xEF, 0xEC, 0x0D, 0x9B, 0x36, 0x6C, 0x18, 0x30, 0x60,
|
|
||||||
0xC1, 0xFB, 0xF8, 0x3F, 0xFF, 0xCC, 0xCC, 0xCC, 0xC0, 0xC1, 0x83, 0x06, 0x0C, 0xD9, 0xB0, 0x60, 0xFD, 0xFC, 0xCC,
|
|
||||||
0xCC, 0xFF, 0xCC, 0xCC, 0x60, 0x60, 0x78, 0x78, 0xF0, 0xE0, 0x60, 0x60, 0x7E, 0x7F, 0x66, 0x67, 0xFE, 0x66, 0x66,
|
|
||||||
0x0C, 0x1C, 0x18, 0xC3, 0xE3, 0xF3, 0xF3, 0xFB, 0xDF, 0xCF, 0xCF, 0xC7, 0xC3, 0x18, 0x63, 0x8C, 0x7B, 0xFC, 0xF3,
|
|
||||||
0xCF, 0x3C, 0xF3, 0xC3, 0xE3, 0xF3, 0xF3, 0xFB, 0xDF, 0xCF, 0xCF, 0xC7, 0xC3, 0x38, 0x1C, 0x38, 0x30, 0x7B, 0xFC,
|
|
||||||
0xF3, 0xCF, 0x3C, 0xF3, 0x70, 0xE7, 0x18, 0x3C, 0x3C, 0x18, 0xC3, 0xE3, 0xF3, 0xF3, 0xFB, 0xDF, 0xCF, 0xCF, 0xC7,
|
|
||||||
0xC3, 0x79, 0xE3, 0x1E, 0xFF, 0x3C, 0xF3, 0xCF, 0x3C, 0xC0, 0x81, 0x83, 0x05, 0xE7, 0xEC, 0xD9, 0xB3, 0x66, 0xCD,
|
|
||||||
0x98, 0xC3, 0xE3, 0xF3, 0xF3, 0xFB, 0xDF, 0xCF, 0xCF, 0xC7, 0xC3, 0x03, 0x03, 0x03, 0x7B, 0xFC, 0xF3, 0xCF, 0x3C,
|
|
||||||
0xF3, 0x0C, 0x31, 0x84, 0x3E, 0x1F, 0x0F, 0x8F, 0xEE, 0x3E, 0x0F, 0x07, 0x83, 0xC1, 0xE1, 0xDF, 0xC7, 0xC0, 0x3C,
|
|
||||||
0x3C, 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x36, 0x1F, 0x07, 0x07, 0xC7, 0xF7, 0x1F, 0x07, 0x83, 0xC1,
|
|
||||||
0xE0, 0xF0, 0xEF, 0xE3, 0xE0, 0x36, 0x3E, 0x1C, 0x3C, 0x7E, 0xE7, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x1B, 0x1F, 0x8D,
|
|
||||||
0x87, 0xC7, 0xF7, 0x1F, 0x07, 0x83, 0xC1, 0xE0, 0xF0, 0xEF, 0xE3, 0xE0, 0x1E, 0x3E, 0x3C, 0x3C, 0x7E, 0xE7, 0xC3,
|
|
||||||
0xC3, 0xC7, 0x7E, 0x3C, 0x3F, 0xFB, 0xFF, 0xF9, 0xC1, 0x86, 0x0C, 0x3F, 0x61, 0xFB, 0x0C, 0x18, 0x60, 0xC7, 0x03,
|
|
||||||
0xFF, 0x0F, 0xFC, 0x3D, 0xE3, 0xFF, 0xB9, 0xCF, 0x87, 0xFC, 0x3F, 0xE3, 0x85, 0xFF, 0xE7, 0xBE, 0x18, 0x38, 0x30,
|
|
||||||
0xFE, 0xFF, 0xC3, 0xC7, 0xFE, 0xFE, 0xC7, 0xC3, 0xC3, 0xC3, 0x33, 0x98, 0xEF, 0xEF, 0x18, 0xC6, 0x30, 0xFE, 0xFF,
|
|
||||||
0xC3, 0xC7, 0xFE, 0xFE, 0xC7, 0xC3, 0xC3, 0xC3, 0x38, 0x1C, 0x38, 0x30, 0x77, 0xF7, 0x8C, 0x63, 0x18, 0xE3, 0xB9,
|
|
||||||
0x80, 0x3C, 0x3C, 0x18, 0xFE, 0xFF, 0xC3, 0xC7, 0xFE, 0xFE, 0xC7, 0xC3, 0xC3, 0xC3, 0xF7, 0x98, 0xEF, 0xEF, 0x18,
|
|
||||||
0xC6, 0x30, 0x18, 0x70, 0xC3, 0xEF, 0xF8, 0xFC, 0x3E, 0x1E, 0x0E, 0x1F, 0xF7, 0xC0, 0x18, 0x70, 0xC3, 0xEF, 0xF8,
|
|
||||||
0xFF, 0x3F, 0x87, 0xFD, 0xF0, 0x18, 0x78, 0xF3, 0xEF, 0xF8, 0xFC, 0x3E, 0x1E, 0x0E, 0x1F, 0xF7, 0xC0, 0x18, 0x78,
|
|
||||||
0xF3, 0xEF, 0xF8, 0xFF, 0x3F, 0x87, 0xFD, 0xF0, 0x7D, 0xFF, 0x1F, 0x87, 0xC3, 0xC1, 0xC3, 0xFE, 0xF8, 0xC0, 0xC7,
|
|
||||||
0x0C, 0x00, 0x7D, 0xFF, 0x1F, 0xE7, 0xF0, 0xFF, 0xBE, 0x18, 0x18, 0xE1, 0x80, 0x3C, 0x78, 0x63, 0xEF, 0xF8, 0xFC,
|
|
||||||
0x3E, 0x1E, 0x0E, 0x1F, 0xF7, 0xC0, 0x3C, 0x78, 0x63, 0xEF, 0xF8, 0xFF, 0x3F, 0x87, 0xFD, 0xF0, 0xFF, 0xFF, 0xC6,
|
|
||||||
0x03, 0x01, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x01, 0x83, 0x81, 0x80, 0x63, 0x3D, 0xE6, 0x31, 0x8C, 0x71,
|
|
||||||
0xCC, 0x37, 0x30, 0x3C, 0x3C, 0x18, 0xFF, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x0D, 0xB6, 0xFF,
|
|
||||||
0xF1, 0x86, 0x18, 0x61, 0xC3, 0x80, 0xFF, 0xFF, 0x18, 0x18, 0x7E, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x63, 0x3D, 0xE6,
|
|
||||||
0x73, 0xCC, 0x71, 0xC0, 0x1E, 0x3E, 0x3C, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x3D, 0xF7,
|
|
||||||
0xA3, 0xCF, 0x3C, 0xF3, 0xCF, 0xF7, 0x80, 0x3C, 0x3C, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C,
|
|
||||||
0x79, 0xE8, 0xF3, 0xCF, 0x3C, 0xF3, 0xFD, 0xE0, 0x36, 0x3E, 0x1C, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7,
|
|
||||||
0x7E, 0x3C, 0x6D, 0xF3, 0xA3, 0xCF, 0x3C, 0xF3, 0xCF, 0xF7, 0x80, 0x18, 0x3C, 0x3C, 0xDB, 0xC3, 0xC3, 0xC3, 0xC3,
|
|
||||||
0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x31, 0xE7, 0xAF, 0xCF, 0x3C, 0xF3, 0xCF, 0xF7, 0x80, 0x1E, 0x3E, 0x3C, 0xC3, 0xC3,
|
|
||||||
0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x3D, 0xF7, 0xA3, 0xCF, 0x3C, 0xF3, 0xCF, 0xF7, 0x80, 0xC3, 0xC3,
|
|
||||||
0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0x7E, 0x3C, 0x18, 0x30, 0x3C, 0x1C, 0x8D, 0x9B, 0x36, 0x6C, 0xD9, 0xBF, 0x3E,
|
|
||||||
0x0C, 0x30, 0x78, 0x70, 0x03, 0x00, 0x1E, 0x00, 0x78, 0x20, 0xC3, 0xC3, 0x0F, 0x0C, 0x3C, 0x79, 0xD9, 0xE6, 0x6F,
|
|
||||||
0xF8, 0xF3, 0xC3, 0xCF, 0x0F, 0x3C, 0x18, 0x60, 0x0C, 0x07, 0x81, 0xE2, 0x03, 0xCC, 0xF7, 0xF7, 0xF9, 0xFE, 0x7F,
|
|
||||||
0x8C, 0xC3, 0x30, 0x18, 0x3C, 0x3C, 0xC3, 0xC3, 0xE7, 0x7E, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x3C,
|
|
||||||
0x83, 0xC7, 0x66, 0x6E, 0x3C, 0x3C, 0x18, 0x18, 0x18, 0x70, 0x60, 0x3C, 0x3C, 0xC3, 0xC3, 0xE7, 0x7E, 0x3C, 0x18,
|
|
||||||
0x18, 0x18, 0x18, 0x18, 0x0C, 0x1C, 0x18, 0xFF, 0xFF, 0x07, 0x0E, 0x1C, 0x38, 0x70, 0xE0, 0xFE, 0xFF, 0x0C, 0x38,
|
|
||||||
0x67, 0xFF, 0xE3, 0x8E, 0x38, 0xE1, 0xFB, 0xF8, 0x0C, 0x0C, 0xFF, 0xFF, 0x07, 0x0E, 0x1C, 0x38, 0x70, 0xE0, 0xFE,
|
|
||||||
0xFF, 0x18, 0x33, 0xFF, 0xF1, 0xC7, 0x1C, 0x70, 0xFD, 0xFC, 0x3C, 0x3C, 0x18, 0xFF, 0xFF, 0x07, 0x0E, 0x1C, 0x38,
|
|
||||||
0x70, 0xE0, 0xFE, 0xFF, 0x3C, 0x78, 0x67, 0xFF, 0xE3, 0x8E, 0x38, 0xE1, 0xFB, 0xF8, 0x1E, 0x3F, 0x73, 0xFE, 0xFE,
|
|
||||||
0xFE, 0xFE, 0x62, 0x3F, 0x1E,
|
|
||||||
};
|
|
||||||
|
|
||||||
static const EpdGlyph babyblueGlyphs[] = {
|
|
||||||
{0, 0, 5, 0, 0, 0, 0}, //
|
|
||||||
{2, 10, 3, 1, 10, 3, 0}, // !
|
|
||||||
{4, 5, 5, 1, 11, 3, 3}, // "
|
|
||||||
{9, 11, 10, 0, 11, 13, 6}, // #
|
|
||||||
{8, 14, 9, 0, 12, 14, 19}, // $
|
|
||||||
{11, 11, 13, 1, 11, 16, 33}, // %
|
|
||||||
{9, 11, 11, 1, 11, 13, 49}, // &
|
|
||||||
{2, 4, 3, 1, 11, 1, 62}, // '
|
|
||||||
{4, 15, 5, 1, 11, 8, 63}, // (
|
|
||||||
{4, 15, 5, 1, 11, 8, 71}, // )
|
|
||||||
{6, 6, 6, 0, 11, 5, 79}, // *
|
|
||||||
{8, 8, 9, 0, 9, 8, 84}, // +
|
|
||||||
{2, 5, 3, 1, 3, 2, 92}, // ,
|
|
||||||
{5, 2, 5, 0, 5, 2, 94}, // -
|
|
||||||
{2, 2, 3, 1, 2, 1, 96}, // .
|
|
||||||
{6, 11, 6, 0, 11, 9, 97}, // /
|
|
||||||
{8, 11, 9, 0, 11, 11, 106}, // 0
|
|
||||||
{5, 11, 6, 1, 11, 7, 117}, // 1
|
|
||||||
{8, 11, 9, 0, 11, 11, 124}, // 2
|
|
||||||
{8, 11, 10, 1, 11, 11, 135}, // 3
|
|
||||||
{8, 11, 9, 0, 11, 11, 146}, // 4
|
|
||||||
{7, 11, 9, 1, 11, 10, 157}, // 5
|
|
||||||
{8, 11, 9, 0, 11, 11, 167}, // 6
|
|
||||||
{9, 11, 10, 0, 11, 13, 178}, // 7
|
|
||||||
{8, 11, 9, 0, 11, 11, 191}, // 8
|
|
||||||
{8, 11, 9, 0, 11, 11, 202}, // 9
|
|
||||||
{2, 8, 3, 1, 8, 2, 213}, // :
|
|
||||||
{2, 11, 3, 1, 8, 3, 215}, // ;
|
|
||||||
{7, 8, 9, 1, 9, 7, 218}, // <
|
|
||||||
{8, 5, 9, 0, 8, 5, 225}, // =
|
|
||||||
{7, 8, 7, 0, 9, 7, 230}, // >
|
|
||||||
{7, 11, 9, 1, 11, 10, 237}, // ?
|
|
||||||
{14, 15, 15, 0, 11, 27, 247}, // @
|
|
||||||
{7, 10, 9, 1, 10, 9, 274}, // A
|
|
||||||
{8, 10, 10, 1, 10, 10, 283}, // B
|
|
||||||
{9, 10, 11, 1, 10, 12, 293}, // C
|
|
||||||
{8, 10, 10, 1, 10, 10, 305}, // D
|
|
||||||
{7, 10, 9, 1, 10, 9, 315}, // E
|
|
||||||
{7, 10, 9, 1, 10, 9, 324}, // F
|
|
||||||
{9, 10, 11, 1, 10, 12, 333}, // G
|
|
||||||
{8, 10, 10, 1, 10, 10, 345}, // H
|
|
||||||
{2, 10, 3, 1, 10, 3, 355}, // I
|
|
||||||
{6, 10, 6, 0, 10, 8, 358}, // J
|
|
||||||
{8, 10, 10, 1, 10, 10, 366}, // K
|
|
||||||
{7, 10, 9, 1, 10, 9, 376}, // L
|
|
||||||
{10, 10, 12, 1, 10, 13, 385}, // M
|
|
||||||
{8, 10, 10, 1, 10, 10, 398}, // N
|
|
||||||
{9, 10, 11, 1, 10, 12, 408}, // O
|
|
||||||
{8, 10, 10, 1, 10, 10, 420}, // P
|
|
||||||
{9, 10, 11, 1, 10, 12, 430}, // Q
|
|
||||||
{8, 10, 10, 1, 10, 10, 442}, // R
|
|
||||||
{7, 10, 9, 1, 10, 9, 452}, // S
|
|
||||||
{8, 10, 9, 0, 10, 10, 461}, // T
|
|
||||||
{8, 10, 10, 1, 10, 10, 471}, // U
|
|
||||||
{8, 10, 10, 1, 10, 10, 481}, // V
|
|
||||||
{14, 10, 15, 0, 10, 18, 491}, // W
|
|
||||||
{10, 10, 11, 0, 10, 13, 509}, // X
|
|
||||||
{8, 10, 10, 1, 10, 10, 522}, // Y
|
|
||||||
{8, 10, 9, 0, 10, 10, 532}, // Z
|
|
||||||
{4, 14, 5, 1, 10, 7, 542}, // [
|
|
||||||
{7, 10, 7, 0, 10, 9, 549}, // <backslash>
|
|
||||||
{4, 14, 4, 0, 10, 7, 558}, // ]
|
|
||||||
{8, 7, 9, 0, 11, 7, 565}, // ^
|
|
||||||
{8, 2, 9, 0, -2, 2, 572}, // _
|
|
||||||
{3, 3, 3, 0, 11, 2, 574}, // `
|
|
||||||
{7, 8, 7, 0, 8, 7, 576}, // a
|
|
||||||
{7, 10, 9, 1, 10, 9, 583}, // b
|
|
||||||
{7, 8, 7, 0, 8, 7, 592}, // c
|
|
||||||
{7, 10, 7, 0, 10, 9, 599}, // d
|
|
||||||
{8, 8, 9, 0, 8, 8, 608}, // e
|
|
||||||
{5, 11, 5, 0, 11, 7, 616}, // f
|
|
||||||
{7, 12, 7, 0, 8, 11, 623}, // g
|
|
||||||
{6, 10, 7, 1, 10, 8, 634}, // h
|
|
||||||
{2, 10, 3, 1, 10, 3, 642}, // i
|
|
||||||
{3, 14, 3, 0, 10, 6, 645}, // j
|
|
||||||
{6, 10, 7, 1, 10, 8, 651}, // k
|
|
||||||
{2, 10, 3, 1, 10, 3, 659}, // l
|
|
||||||
{10, 8, 12, 1, 8, 10, 662}, // m
|
|
||||||
{6, 8, 7, 1, 8, 6, 672}, // n
|
|
||||||
{8, 8, 9, 0, 8, 8, 678}, // o
|
|
||||||
{7, 11, 9, 1, 8, 10, 686}, // p
|
|
||||||
{7, 11, 7, 0, 8, 10, 696}, // q
|
|
||||||
{5, 8, 6, 1, 8, 5, 706}, // r
|
|
||||||
{7, 8, 7, 0, 8, 7, 711}, // s
|
|
||||||
{5, 10, 5, 0, 10, 7, 718}, // t
|
|
||||||
{6, 8, 7, 1, 8, 6, 725}, // u
|
|
||||||
{8, 8, 9, 0, 8, 8, 731}, // v
|
|
||||||
{10, 8, 11, 0, 8, 10, 739}, // w
|
|
||||||
{8, 8, 9, 0, 8, 8, 749}, // x
|
|
||||||
{8, 11, 9, 0, 8, 11, 757}, // y
|
|
||||||
{7, 8, 7, 0, 8, 7, 768}, // z
|
|
||||||
{5, 15, 5, 0, 11, 10, 775}, // {
|
|
||||||
{2, 15, 3, 1, 11, 4, 785}, // |
|
|
||||||
{6, 15, 6, 0, 11, 12, 789}, // }
|
|
||||||
{9, 3, 10, 0, 7, 4, 801}, // ~
|
|
||||||
{2, 12, 4, 2, 8, 3, 805}, // ¡
|
|
||||||
{7, 13, 7, 0, 10, 12, 808}, // ¢
|
|
||||||
{8, 10, 9, 0, 10, 10, 820}, // £
|
|
||||||
{8, 8, 9, 0, 9, 8, 830}, // ¤
|
|
||||||
{8, 10, 9, 0, 10, 10, 838}, // ¥
|
|
||||||
{2, 15, 3, 1, 11, 4, 848}, // ¦
|
|
||||||
{8, 15, 9, 0, 11, 15, 852}, // §
|
|
||||||
{5, 3, 5, 0, 11, 2, 867}, // ¨
|
|
||||||
{10, 11, 11, 0, 11, 14, 869}, // ©
|
|
||||||
{5, 6, 5, 0, 11, 4, 883}, // ª
|
|
||||||
{7, 7, 9, 1, 8, 7, 887}, // «
|
|
||||||
{8, 5, 9, 0, 8, 5, 894}, // ¬
|
|
||||||
{10, 11, 11, 0, 11, 14, 899}, // ®
|
|
||||||
{8, 2, 9, 0, 12, 2, 913}, // ¯
|
|
||||||
{4, 5, 5, 1, 11, 3, 915}, // °
|
|
||||||
{8, 9, 9, 0, 9, 9, 918}, // ±
|
|
||||||
{5, 8, 5, 0, 11, 5, 927}, // ²
|
|
||||||
{5, 6, 5, 0, 11, 4, 932}, // ³
|
|
||||||
{3, 3, 4, 1, 11, 2, 936}, // ´
|
|
||||||
{6, 12, 9, 2, 8, 9, 938}, // µ
|
|
||||||
{8, 14, 9, 0, 10, 14, 947}, // ¶
|
|
||||||
{2, 2, 3, 1, 6, 1, 961}, // ·
|
|
||||||
{4, 4, 4, 0, 0, 2, 962}, // ¸
|
|
||||||
{4, 7, 4, 0, 12, 4, 964}, // ¹
|
|
||||||
{6, 6, 6, 0, 11, 5, 968}, // º
|
|
||||||
{7, 7, 9, 1, 8, 7, 973}, // »
|
|
||||||
{12, 12, 13, 0, 12, 18, 980}, // ¼
|
|
||||||
{12, 11, 13, 0, 11, 17, 998}, // ½
|
|
||||||
{12, 11, 13, 0, 11, 17, 1015}, // ¾
|
|
||||||
{7, 12, 9, 1, 8, 11, 1032}, // ¿
|
|
||||||
{7, 13, 9, 1, 13, 12, 1043}, // À
|
|
||||||
{7, 13, 9, 1, 13, 12, 1055}, // Á
|
|
||||||
{7, 13, 9, 1, 13, 12, 1067}, // Â
|
|
||||||
{7, 13, 9, 1, 13, 12, 1079}, // Ã
|
|
||||||
{7, 12, 9, 1, 12, 11, 1091}, // Ä
|
|
||||||
{7, 13, 9, 1, 13, 12, 1102}, // Å
|
|
||||||
{13, 11, 14, 0, 11, 18, 1114}, // Æ
|
|
||||||
{9, 14, 11, 1, 10, 16, 1132}, // Ç
|
|
||||||
{7, 13, 9, 1, 13, 12, 1148}, // È
|
|
||||||
{7, 13, 9, 1, 13, 12, 1160}, // É
|
|
||||||
{7, 13, 9, 1, 13, 12, 1172}, // Ê
|
|
||||||
{7, 12, 9, 1, 12, 11, 1184}, // Ë
|
|
||||||
{3, 13, 3, 0, 13, 5, 1195}, // Ì
|
|
||||||
{3, 13, 4, 1, 13, 5, 1200}, // Í
|
|
||||||
{4, 13, 4, 0, 13, 7, 1205}, // Î
|
|
||||||
{4, 12, 4, 0, 12, 6, 1212}, // Ï
|
|
||||||
{9, 10, 10, 0, 10, 12, 1218}, // Ð
|
|
||||||
{8, 13, 10, 1, 13, 13, 1230}, // Ñ
|
|
||||||
{9, 13, 11, 1, 13, 15, 1243}, // Ò
|
|
||||||
{9, 13, 11, 1, 13, 15, 1258}, // Ó
|
|
||||||
{9, 13, 11, 1, 13, 15, 1273}, // Ô
|
|
||||||
{9, 13, 11, 1, 13, 15, 1288}, // Õ
|
|
||||||
{9, 12, 11, 1, 12, 14, 1303}, // Ö
|
|
||||||
{6, 6, 7, 1, 8, 5, 1317}, // ×
|
|
||||||
{10, 10, 11, 0, 10, 13, 1322}, // Ø
|
|
||||||
{8, 13, 10, 1, 13, 13, 1335}, // Ù
|
|
||||||
{8, 13, 10, 1, 13, 13, 1348}, // Ú
|
|
||||||
{8, 13, 10, 1, 13, 13, 1361}, // Û
|
|
||||||
{8, 12, 10, 1, 12, 12, 1374}, // Ü
|
|
||||||
{8, 13, 10, 1, 13, 13, 1386}, // Ý
|
|
||||||
{8, 10, 10, 1, 10, 10, 1399}, // Þ
|
|
||||||
{7, 11, 9, 1, 11, 10, 1409}, // ß
|
|
||||||
{7, 11, 7, 0, 11, 10, 1419}, // à
|
|
||||||
{7, 11, 7, 0, 11, 10, 1429}, // á
|
|
||||||
{7, 11, 7, 0, 11, 10, 1439}, // â
|
|
||||||
{7, 11, 7, 0, 11, 10, 1449}, // ã
|
|
||||||
{7, 10, 7, 0, 10, 9, 1459}, // ä
|
|
||||||
{7, 12, 7, 0, 12, 11, 1468}, // å
|
|
||||||
{12, 8, 13, 0, 8, 12, 1479}, // æ
|
|
||||||
{7, 12, 7, 0, 8, 11, 1491}, // ç
|
|
||||||
{8, 11, 9, 0, 11, 11, 1502}, // è
|
|
||||||
{8, 11, 9, 0, 11, 11, 1513}, // é
|
|
||||||
{8, 11, 9, 0, 11, 11, 1524}, // ê
|
|
||||||
{8, 10, 9, 0, 10, 10, 1535}, // ë
|
|
||||||
{3, 11, 3, 0, 11, 5, 1545}, // ì
|
|
||||||
{3, 11, 4, 1, 11, 5, 1550}, // í
|
|
||||||
{4, 11, 4, 0, 11, 6, 1555}, // î
|
|
||||||
{4, 10, 4, 0, 10, 5, 1561}, // ï
|
|
||||||
{8, 11, 9, 0, 11, 11, 1566}, // ð
|
|
||||||
{6, 11, 7, 1, 11, 9, 1577}, // ñ
|
|
||||||
{8, 11, 9, 0, 11, 11, 1586}, // ò
|
|
||||||
{8, 11, 9, 0, 11, 11, 1597}, // ó
|
|
||||||
{8, 11, 9, 0, 11, 11, 1608}, // ô
|
|
||||||
{8, 11, 9, 0, 11, 11, 1619}, // õ
|
|
||||||
{8, 10, 9, 0, 10, 10, 1630}, // ö
|
|
||||||
{8, 6, 9, 0, 8, 6, 1640}, // ÷
|
|
||||||
{8, 8, 9, 0, 8, 8, 1646}, // ø
|
|
||||||
{6, 11, 7, 1, 11, 9, 1654}, // ù
|
|
||||||
{6, 11, 7, 1, 11, 9, 1663}, // ú
|
|
||||||
{6, 11, 7, 1, 11, 9, 1672}, // û
|
|
||||||
{6, 10, 7, 1, 10, 8, 1681}, // ü
|
|
||||||
{8, 14, 9, 0, 11, 14, 1689}, // ý
|
|
||||||
{7, 13, 9, 1, 10, 12, 1703}, // þ
|
|
||||||
{8, 13, 9, 0, 10, 13, 1715}, // ÿ
|
|
||||||
{7, 12, 9, 1, 12, 11, 1728}, // Ā
|
|
||||||
{7, 10, 7, 0, 10, 9, 1739}, // ā
|
|
||||||
{7, 13, 9, 1, 13, 12, 1748}, // Ă
|
|
||||||
{7, 11, 7, 0, 11, 10, 1760}, // ă
|
|
||||||
{8, 14, 10, 1, 10, 14, 1770}, // Ą
|
|
||||||
{8, 12, 9, 0, 8, 12, 1784}, // ą
|
|
||||||
{9, 13, 11, 1, 13, 15, 1796}, // Ć
|
|
||||||
{7, 11, 7, 0, 11, 10, 1811}, // ć
|
|
||||||
{9, 13, 11, 1, 13, 15, 1821}, // Ĉ
|
|
||||||
{7, 11, 7, 0, 11, 10, 1836}, // ĉ
|
|
||||||
{9, 12, 11, 1, 12, 14, 1846}, // Ċ
|
|
||||||
{7, 10, 7, 0, 10, 9, 1860}, // ċ
|
|
||||||
{9, 13, 11, 1, 13, 15, 1869}, // Č
|
|
||||||
{7, 11, 7, 0, 11, 10, 1884}, // č
|
|
||||||
{8, 13, 10, 1, 13, 13, 1894}, // Ď
|
|
||||||
{9, 11, 10, 0, 11, 13, 1907}, // ď
|
|
||||||
{9, 10, 10, 0, 10, 12, 1920}, // Đ
|
|
||||||
{8, 11, 9, 0, 11, 11, 1932}, // đ
|
|
||||||
{7, 12, 9, 1, 12, 11, 1943}, // Ē
|
|
||||||
{8, 10, 9, 0, 10, 10, 1954}, // ē
|
|
||||||
{7, 13, 9, 1, 13, 12, 1964}, // Ĕ
|
|
||||||
{8, 11, 9, 0, 11, 11, 1976}, // ĕ
|
|
||||||
{7, 12, 9, 1, 12, 11, 1987}, // Ė
|
|
||||||
{8, 10, 9, 0, 10, 10, 1998}, // ė
|
|
||||||
{8, 14, 10, 1, 10, 14, 2008}, // Ę
|
|
||||||
{8, 12, 9, 0, 8, 12, 2022}, // ę
|
|
||||||
{7, 13, 9, 1, 13, 12, 2034}, // Ě
|
|
||||||
{8, 11, 9, 0, 11, 11, 2046}, // ě
|
|
||||||
{9, 13, 11, 1, 13, 15, 2057}, // Ĝ
|
|
||||||
{7, 15, 7, 0, 11, 14, 2072}, // ĝ
|
|
||||||
{9, 13, 11, 1, 13, 15, 2086}, // Ğ
|
|
||||||
{7, 15, 7, 0, 11, 14, 2101}, // ğ
|
|
||||||
{9, 12, 11, 1, 12, 14, 2115}, // Ġ
|
|
||||||
{7, 14, 7, 0, 10, 13, 2129}, // ġ
|
|
||||||
{9, 14, 11, 1, 10, 16, 2142}, // Ģ
|
|
||||||
{7, 15, 7, 0, 11, 14, 2158}, // ģ
|
|
||||||
{8, 13, 10, 1, 13, 13, 2172}, // Ĥ
|
|
||||||
{6, 13, 7, 1, 13, 10, 2185}, // ĥ
|
|
||||||
{10, 10, 11, 0, 10, 13, 2195}, // Ħ
|
|
||||||
{7, 11, 7, 0, 11, 10, 2208}, // ħ
|
|
||||||
{5, 13, 5, 0, 13, 9, 2218}, // Ĩ
|
|
||||||
{5, 11, 5, 0, 11, 7, 2227}, // ĩ
|
|
||||||
{4, 12, 4, 0, 12, 6, 2234}, // Ī
|
|
||||||
{4, 10, 4, 0, 10, 5, 2240}, // ī
|
|
||||||
{5, 13, 5, 0, 13, 9, 2245}, // Ĭ
|
|
||||||
{5, 11, 5, 0, 11, 7, 2254}, // ĭ
|
|
||||||
{4, 14, 4, 0, 10, 7, 2261}, // Į
|
|
||||||
{4, 14, 4, 0, 10, 7, 2268}, // į
|
|
||||||
{2, 12, 3, 1, 12, 3, 2275}, // İ
|
|
||||||
{2, 8, 3, 1, 8, 2, 2278}, // ı
|
|
||||||
{8, 10, 10, 1, 10, 10, 2280}, // IJ
|
|
||||||
{5, 14, 6, 1, 10, 9, 2290}, // ij
|
|
||||||
{7, 13, 7, 0, 13, 12, 2299}, // Ĵ
|
|
||||||
{4, 15, 4, 0, 11, 8, 2311}, // ĵ
|
|
||||||
{8, 14, 10, 1, 10, 14, 2319}, // Ķ
|
|
||||||
{6, 14, 7, 1, 10, 11, 2333}, // ķ
|
|
||||||
{6, 8, 7, 1, 8, 6, 2344}, // ĸ
|
|
||||||
{7, 13, 9, 1, 13, 12, 2350}, // Ĺ
|
|
||||||
{3, 13, 4, 1, 13, 5, 2362}, // ĺ
|
|
||||||
{7, 14, 9, 1, 10, 13, 2367}, // Ļ
|
|
||||||
{4, 14, 4, 0, 10, 7, 2380}, // ļ
|
|
||||||
{7, 11, 9, 1, 11, 10, 2387}, // Ľ
|
|
||||||
{4, 11, 5, 1, 11, 6, 2397}, // ľ
|
|
||||||
{7, 10, 9, 1, 10, 9, 2403}, // Ŀ
|
|
||||||
{4, 10, 5, 1, 10, 5, 2412}, // ŀ
|
|
||||||
{8, 10, 9, 0, 10, 10, 2417}, // Ł
|
|
||||||
{4, 10, 4, 0, 10, 5, 2427}, // ł
|
|
||||||
{8, 13, 10, 1, 13, 13, 2432}, // Ń
|
|
||||||
{6, 12, 7, 1, 12, 9, 2445}, // ń
|
|
||||||
{8, 14, 10, 1, 10, 14, 2454}, // Ņ
|
|
||||||
{6, 12, 7, 1, 8, 9, 2468}, // ņ
|
|
||||||
{8, 13, 10, 1, 13, 13, 2477}, // Ň
|
|
||||||
{6, 11, 7, 1, 11, 9, 2490}, // ň
|
|
||||||
{7, 11, 7, 0, 11, 10, 2499}, // ʼn
|
|
||||||
{8, 13, 10, 1, 10, 13, 2509}, // Ŋ
|
|
||||||
{6, 12, 7, 1, 8, 9, 2522}, // ŋ
|
|
||||||
{9, 12, 11, 1, 12, 14, 2531}, // Ō
|
|
||||||
{8, 10, 9, 0, 10, 10, 2545}, // ō
|
|
||||||
{9, 13, 11, 1, 13, 15, 2555}, // Ŏ
|
|
||||||
{8, 11, 9, 0, 11, 11, 2570}, // ŏ
|
|
||||||
{9, 13, 11, 1, 13, 15, 2581}, // Ő
|
|
||||||
{8, 11, 9, 0, 11, 11, 2596}, // ő
|
|
||||||
{13, 11, 15, 1, 11, 18, 2607}, // Œ
|
|
||||||
{13, 8, 14, 0, 8, 13, 2625}, // œ
|
|
||||||
{8, 13, 10, 1, 13, 13, 2638}, // Ŕ
|
|
||||||
{5, 11, 6, 1, 11, 7, 2651}, // ŕ
|
|
||||||
{8, 14, 10, 1, 10, 14, 2658}, // Ŗ
|
|
||||||
{5, 12, 6, 1, 8, 8, 2672}, // ŗ
|
|
||||||
{8, 13, 10, 1, 13, 13, 2680}, // Ř
|
|
||||||
{5, 11, 6, 1, 11, 7, 2693}, // ř
|
|
||||||
{7, 13, 9, 1, 13, 12, 2700}, // Ś
|
|
||||||
{7, 11, 7, 0, 11, 10, 2712}, // ś
|
|
||||||
{7, 13, 9, 1, 13, 12, 2722}, // Ŝ
|
|
||||||
{7, 11, 7, 0, 11, 10, 2734}, // ŝ
|
|
||||||
{7, 14, 9, 1, 10, 13, 2744}, // Ş
|
|
||||||
{7, 12, 7, 0, 8, 11, 2757}, // ş
|
|
||||||
{7, 13, 9, 1, 13, 12, 2768}, // Š
|
|
||||||
{7, 11, 7, 0, 11, 10, 2780}, // š
|
|
||||||
{9, 14, 10, 0, 10, 16, 2790}, // Ţ
|
|
||||||
{5, 14, 5, 0, 10, 9, 2806}, // ţ
|
|
||||||
{8, 13, 9, 0, 13, 13, 2815}, // Ť
|
|
||||||
{6, 11, 6, 0, 11, 9, 2828}, // ť
|
|
||||||
{8, 10, 9, 0, 10, 10, 2837}, // Ŧ
|
|
||||||
{5, 10, 5, 0, 10, 7, 2847}, // ŧ
|
|
||||||
{8, 13, 10, 1, 13, 13, 2854}, // Ũ
|
|
||||||
{6, 11, 7, 1, 11, 9, 2867}, // ũ
|
|
||||||
{8, 12, 10, 1, 12, 12, 2876}, // Ū
|
|
||||||
{6, 10, 7, 1, 10, 8, 2888}, // ū
|
|
||||||
{8, 13, 10, 1, 13, 13, 2896}, // Ŭ
|
|
||||||
{6, 11, 7, 1, 11, 9, 2909}, // ŭ
|
|
||||||
{8, 13, 10, 1, 13, 13, 2918}, // Ů
|
|
||||||
{6, 11, 7, 1, 11, 9, 2931}, // ů
|
|
||||||
{8, 13, 10, 1, 13, 13, 2940}, // Ű
|
|
||||||
{6, 11, 7, 1, 11, 9, 2953}, // ű
|
|
||||||
{8, 14, 10, 1, 10, 14, 2962}, // Ų
|
|
||||||
{7, 12, 9, 1, 8, 11, 2976}, // ų
|
|
||||||
{14, 13, 15, 0, 13, 23, 2987}, // Ŵ
|
|
||||||
{10, 11, 11, 0, 11, 14, 3010}, // ŵ
|
|
||||||
{8, 13, 10, 1, 13, 13, 3024}, // Ŷ
|
|
||||||
{8, 14, 9, 0, 11, 14, 3037}, // ŷ
|
|
||||||
{8, 12, 10, 1, 12, 12, 3051}, // Ÿ
|
|
||||||
{8, 13, 9, 0, 13, 13, 3063}, // Ź
|
|
||||||
{7, 11, 7, 0, 11, 10, 3076}, // ź
|
|
||||||
{8, 12, 9, 0, 12, 12, 3086}, // Ż
|
|
||||||
{7, 10, 7, 0, 10, 9, 3098}, // ż
|
|
||||||
{8, 13, 9, 0, 13, 13, 3107}, // Ž
|
|
||||||
{7, 11, 7, 0, 11, 10, 3120}, // ž
|
|
||||||
{8, 10, 9, 0, 10, 10, 3130}, // €
|
|
||||||
};
|
|
||||||
|
|
||||||
static const EpdUnicodeInterval babyblueIntervals[] = {
|
|
||||||
{0x20, 0x7E, 0x0}, {0xA1, 0xAC, 0x5F}, {0xAE, 0xFF, 0x6B}, {0x100, 0x17E, 0xBD}, {0x20AC, 0x20AC, 0x13C},
|
|
||||||
};
|
|
||||||
|
|
||||||
static const EpdFontData babyblue = {
|
|
||||||
babyblueBitmaps, babyblueGlyphs, babyblueIntervals, 5, 17, 13, -4, false,
|
|
||||||
};
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -176,7 +176,7 @@ for i_start, i_end in intervals:
|
|||||||
px = 0
|
px = 0
|
||||||
|
|
||||||
if is2Bit:
|
if is2Bit:
|
||||||
# 0 = white, 15 black, 8+ dark grey, 7- light grey
|
# 0-3 white, 4-7 light grey, 8-11 dark grey, 12-15 black
|
||||||
# Downsample to 2-bit bitmap
|
# Downsample to 2-bit bitmap
|
||||||
pixels2b = []
|
pixels2b = []
|
||||||
px = 0
|
px = 0
|
||||||
@ -187,11 +187,11 @@ for i_start, i_end in intervals:
|
|||||||
bm = pixels4g[y * pitch + (x // 2)]
|
bm = pixels4g[y * pitch + (x // 2)]
|
||||||
bm = (bm >> ((x % 2) * 4)) & 0xF
|
bm = (bm >> ((x % 2) * 4)) & 0xF
|
||||||
|
|
||||||
if bm == 15:
|
if bm >= 12:
|
||||||
px += 3
|
px += 3
|
||||||
elif bm >= 8:
|
elif bm >= 8:
|
||||||
px += 2
|
px += 2
|
||||||
elif bm > 0:
|
elif bm >= 4:
|
||||||
px += 1
|
px += 1
|
||||||
|
|
||||||
if (y * bitmap.width + x) % 4 == 3:
|
if (y * bitmap.width + x) % 4 == 3:
|
||||||
@ -211,7 +211,7 @@ for i_start, i_end in intervals:
|
|||||||
# print(line)
|
# print(line)
|
||||||
# print('')
|
# print('')
|
||||||
else:
|
else:
|
||||||
# Downsample to 1-bit bitmap - treat any non-zero as black
|
# Downsample to 1-bit bitmap - treat any 2+ as black
|
||||||
pixelsbw = []
|
pixelsbw = []
|
||||||
px = 0
|
px = 0
|
||||||
pitch = (bitmap.width // 2) + (bitmap.width % 2)
|
pitch = (bitmap.width // 2) + (bitmap.width % 2)
|
||||||
@ -219,7 +219,7 @@ for i_start, i_end in intervals:
|
|||||||
for x in range(bitmap.width):
|
for x in range(bitmap.width):
|
||||||
px = px << 1
|
px = px << 1
|
||||||
bm = pixels4g[y * pitch + (x // 2)]
|
bm = pixels4g[y * pitch + (x // 2)]
|
||||||
px += 1 if ((x & 1) == 0 and bm & 0xF > 0) or ((x & 1) == 1 and bm & 0xF0 > 0) else 0
|
px += 1 if ((x & 1) == 0 and bm & 0xE > 0) or ((x & 1) == 1 and bm & 0xE0 > 0) else 0
|
||||||
|
|
||||||
if (y * bitmap.width + x) % 8 == 7:
|
if (y * bitmap.width + x) % 8 == 7:
|
||||||
pixelsbw.append(px)
|
pixelsbw.append(px)
|
||||||
|
|||||||
@ -73,10 +73,8 @@ bool Epub::parseContentOpf(const std::string& contentOpfFilePath) {
|
|||||||
coverImageItem = opfParser.items.at(opfParser.coverItemId);
|
coverImageItem = opfParser.items.at(opfParser.coverItemId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opfParser.items.count("ncx")) {
|
if (!opfParser.tocNcxPath.empty()) {
|
||||||
tocNcxItem = opfParser.items.at("ncx");
|
tocNcxItem = opfParser.tocNcxPath;
|
||||||
} else if (opfParser.items.count("ncxtoc")) {
|
|
||||||
tocNcxItem = opfParser.items.at("ncxtoc");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& spineRef : opfParser.spineRefs) {
|
for (auto& spineRef : opfParser.spineRefs) {
|
||||||
@ -150,11 +148,54 @@ bool Epub::load() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initializeSpineItemSizes();
|
||||||
Serial.printf("[%lu] [EBP] Loaded ePub: %s\n", millis(), filepath.c_str());
|
Serial.printf("[%lu] [EBP] Loaded ePub: %s\n", millis(), filepath.c_str());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Epub::initializeSpineItemSizes() {
|
||||||
|
setupCacheDir();
|
||||||
|
|
||||||
|
size_t spineItemsCount = getSpineItemsCount();
|
||||||
|
size_t cumSpineItemSize = 0;
|
||||||
|
if (SD.exists((getCachePath() + "/spine_size.bin").c_str())) {
|
||||||
|
File f = SD.open((getCachePath() + "/spine_size.bin").c_str());
|
||||||
|
uint8_t data[4];
|
||||||
|
for (size_t i = 0; i < spineItemsCount; i++) {
|
||||||
|
f.read(data, 4);
|
||||||
|
cumSpineItemSize = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
|
||||||
|
cumulativeSpineItemSize.emplace_back(cumSpineItemSize);
|
||||||
|
// Serial.printf("[%lu] [EBP] Loading item %d size %u to %u %u\n", millis(),
|
||||||
|
// i, cumSpineItemSize, data[1], data[0]);
|
||||||
|
}
|
||||||
|
f.close();
|
||||||
|
} else {
|
||||||
|
File f = SD.open((getCachePath() + "/spine_size.bin").c_str(), FILE_WRITE);
|
||||||
|
uint8_t data[4];
|
||||||
|
// determine size of spine items
|
||||||
|
for (size_t i = 0; i < spineItemsCount; i++) {
|
||||||
|
std::string spineItem = getSpineItem(i);
|
||||||
|
size_t s = 0;
|
||||||
|
getItemSize(spineItem, &s);
|
||||||
|
cumSpineItemSize += s;
|
||||||
|
cumulativeSpineItemSize.emplace_back(cumSpineItemSize);
|
||||||
|
|
||||||
|
// and persist to cache
|
||||||
|
data[0] = cumSpineItemSize & 0xFF;
|
||||||
|
data[1] = (cumSpineItemSize >> 8) & 0xFF;
|
||||||
|
data[2] = (cumSpineItemSize >> 16) & 0xFF;
|
||||||
|
data[3] = (cumSpineItemSize >> 24) & 0xFF;
|
||||||
|
// Serial.printf("[%lu] [EBP] Persisting item %d size %u to %u %u\n", millis(),
|
||||||
|
// i, cumSpineItemSize, data[1], data[0]);
|
||||||
|
f.write(data, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
f.close();
|
||||||
|
}
|
||||||
|
Serial.printf("[%lu] [EBP] Book size: %lu\n", millis(), cumSpineItemSize);
|
||||||
|
}
|
||||||
|
|
||||||
bool Epub::clearCache() const {
|
bool Epub::clearCache() const {
|
||||||
if (!SD.exists(cachePath.c_str())) {
|
if (!SD.exists(cachePath.c_str())) {
|
||||||
Serial.printf("[%lu] [EPB] Cache does not exist, no action needed\n", millis());
|
Serial.printf("[%lu] [EPB] Cache does not exist, no action needed\n", millis());
|
||||||
@ -257,6 +298,8 @@ bool Epub::getItemSize(const std::string& itemHref, size_t* size) const {
|
|||||||
|
|
||||||
int Epub::getSpineItemsCount() const { return spine.size(); }
|
int Epub::getSpineItemsCount() const { return spine.size(); }
|
||||||
|
|
||||||
|
size_t Epub::getCumulativeSpineItemSize(const int spineIndex) const { return cumulativeSpineItemSize.at(spineIndex); }
|
||||||
|
|
||||||
std::string& Epub::getSpineItem(const int spineIndex) {
|
std::string& Epub::getSpineItem(const int spineIndex) {
|
||||||
if (spineIndex < 0 || spineIndex >= spine.size()) {
|
if (spineIndex < 0 || spineIndex >= spine.size()) {
|
||||||
Serial.printf("[%lu] [EBP] getSpineItem index:%d is out of range\n", millis(), spineIndex);
|
Serial.printf("[%lu] [EBP] getSpineItem index:%d is out of range\n", millis(), spineIndex);
|
||||||
@ -279,6 +322,11 @@ int Epub::getTocItemsCount() const { return toc.size(); }
|
|||||||
|
|
||||||
// work out the section index for a toc index
|
// work out the section index for a toc index
|
||||||
int Epub::getSpineIndexForTocIndex(const int tocIndex) const {
|
int Epub::getSpineIndexForTocIndex(const int tocIndex) const {
|
||||||
|
if (tocIndex < 0 || tocIndex >= toc.size()) {
|
||||||
|
Serial.printf("[%lu] [EBP] getSpineIndexForTocIndex: tocIndex %d out of range\n", millis(), tocIndex);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// the toc entry should have an href that matches the spine item
|
// the toc entry should have an href that matches the spine item
|
||||||
// so we can find the spine index by looking for the href
|
// so we can find the spine index by looking for the href
|
||||||
for (int i = 0; i < spine.size(); i++) {
|
for (int i = 0; i < spine.size(); i++) {
|
||||||
@ -293,6 +341,11 @@ int Epub::getSpineIndexForTocIndex(const int tocIndex) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int Epub::getTocIndexForSpineIndex(const int spineIndex) const {
|
int Epub::getTocIndexForSpineIndex(const int spineIndex) const {
|
||||||
|
if (spineIndex < 0 || spineIndex >= spine.size()) {
|
||||||
|
Serial.printf("[%lu] [EBP] getTocIndexForSpineIndex: spineIndex %d out of range\n", millis(), spineIndex);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
// the toc entry should have an href that matches the spine item
|
// the toc entry should have an href that matches the spine item
|
||||||
// so we can find the toc index by looking for the href
|
// so we can find the toc index by looking for the href
|
||||||
for (int i = 0; i < toc.size(); i++) {
|
for (int i = 0; i < toc.size(); i++) {
|
||||||
@ -304,3 +357,22 @@ int Epub::getTocIndexForSpineIndex(const int spineIndex) const {
|
|||||||
Serial.printf("[%lu] [EBP] TOC item not found\n", millis());
|
Serial.printf("[%lu] [EBP] TOC item not found\n", millis());
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t Epub::getBookSize() const {
|
||||||
|
if (spine.empty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return getCumulativeSpineItemSize(getSpineItemsCount() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate progress in book
|
||||||
|
uint8_t Epub::calculateProgress(const int currentSpineIndex, const float currentSpineRead) {
|
||||||
|
size_t bookSize = getBookSize();
|
||||||
|
if (bookSize == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t prevChapterSize = (currentSpineIndex >= 1) ? getCumulativeSpineItemSize(currentSpineIndex - 1) : 0;
|
||||||
|
size_t curChapterSize = getCumulativeSpineItemSize(currentSpineIndex) - prevChapterSize;
|
||||||
|
size_t sectionProgSize = currentSpineRead * curChapterSize;
|
||||||
|
return round(static_cast<float>(prevChapterSize + sectionProgSize) / bookSize * 100.0);
|
||||||
|
}
|
||||||
|
|||||||
@ -20,6 +20,8 @@ class Epub {
|
|||||||
std::string filepath;
|
std::string filepath;
|
||||||
// the spine of the EPUB file
|
// the spine of the EPUB file
|
||||||
std::vector<std::pair<std::string, std::string>> spine;
|
std::vector<std::pair<std::string, std::string>> spine;
|
||||||
|
// the file size of the spine items (proxy to book progress)
|
||||||
|
std::vector<size_t> cumulativeSpineItemSize;
|
||||||
// the toc of the EPUB file
|
// the toc of the EPUB file
|
||||||
std::vector<EpubTocEntry> toc;
|
std::vector<EpubTocEntry> toc;
|
||||||
// the base path for items in the EPUB file
|
// the base path for items in the EPUB file
|
||||||
@ -30,6 +32,7 @@ class Epub {
|
|||||||
bool findContentOpfFile(std::string* contentOpfFile) const;
|
bool findContentOpfFile(std::string* contentOpfFile) const;
|
||||||
bool parseContentOpf(const std::string& contentOpfFilePath);
|
bool parseContentOpf(const std::string& contentOpfFilePath);
|
||||||
bool parseTocNcxFile();
|
bool parseTocNcxFile();
|
||||||
|
void initializeSpineItemSizes();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Epub(std::string filepath, const std::string& cacheDir) : filepath(std::move(filepath)) {
|
explicit Epub(std::string filepath, const std::string& cacheDir) : filepath(std::move(filepath)) {
|
||||||
@ -51,8 +54,12 @@ class Epub {
|
|||||||
bool getItemSize(const std::string& itemHref, size_t* size) const;
|
bool getItemSize(const std::string& itemHref, size_t* size) const;
|
||||||
std::string& getSpineItem(int spineIndex);
|
std::string& getSpineItem(int spineIndex);
|
||||||
int getSpineItemsCount() const;
|
int getSpineItemsCount() const;
|
||||||
EpubTocEntry& getTocItem(int tocTndex);
|
size_t getCumulativeSpineItemSize(const int spineIndex) const;
|
||||||
|
EpubTocEntry& getTocItem(int tocIndex);
|
||||||
int getTocItemsCount() const;
|
int getTocItemsCount() const;
|
||||||
int getSpineIndexForTocIndex(int tocIndex) const;
|
int getSpineIndexForTocIndex(int tocIndex) const;
|
||||||
int getTocIndexForSpineIndex(int spineIndex) const;
|
int getTocIndexForSpineIndex(int spineIndex) const;
|
||||||
|
|
||||||
|
size_t getBookSize() const;
|
||||||
|
uint8_t calculateProgress(const int currentSpineIndex, const float currentSpineRead);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,7 +3,9 @@
|
|||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
constexpr uint8_t PAGE_FILE_VERSION = 3;
|
constexpr uint8_t PAGE_FILE_VERSION = 3;
|
||||||
|
}
|
||||||
|
|
||||||
void PageLine::render(GfxRenderer& renderer, const int fontId) { block->render(renderer, fontId, xPos, yPos); }
|
void PageLine::render(GfxRenderer& renderer, const int fontId) { block->render(renderer, fontId, xPos, yPos); }
|
||||||
|
|
||||||
|
|||||||
@ -27,12 +27,16 @@ void ParsedText::layoutAndExtractLines(const GfxRenderer& renderer, const int fo
|
|||||||
const size_t totalWordCount = words.size();
|
const size_t totalWordCount = words.size();
|
||||||
const int pageWidth = renderer.getScreenWidth() - horizontalMargin;
|
const int pageWidth = renderer.getScreenWidth() - horizontalMargin;
|
||||||
const int spaceWidth = renderer.getSpaceWidth(fontId);
|
const int spaceWidth = renderer.getSpaceWidth(fontId);
|
||||||
// width of 1em to indent first line of paragraph if Extra Spacing is enabled
|
|
||||||
const int indentWidth = (!extraParagraphSpacing) ? 1 * renderer.getTextWidth(fontId, "m", REGULAR) : 0;
|
|
||||||
|
|
||||||
std::vector<uint16_t> wordWidths;
|
std::vector<uint16_t> wordWidths;
|
||||||
wordWidths.reserve(totalWordCount);
|
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 wordsIt = words.begin();
|
||||||
auto wordStylesIt = wordStyles.begin();
|
auto wordStylesIt = wordStyles.begin();
|
||||||
|
|
||||||
@ -53,7 +57,7 @@ void ParsedText::layoutAndExtractLines(const GfxRenderer& renderer, const int fo
|
|||||||
ans[totalWordCount - 1] = totalWordCount - 1;
|
ans[totalWordCount - 1] = totalWordCount - 1;
|
||||||
|
|
||||||
for (int i = totalWordCount - 2; i >= 0; --i) {
|
for (int i = totalWordCount - 2; i >= 0; --i) {
|
||||||
int currlen = -spaceWidth + indentWidth;
|
int currlen = -spaceWidth;
|
||||||
dp[i] = MAX_COST;
|
dp[i] = MAX_COST;
|
||||||
|
|
||||||
for (size_t j = i; j < totalWordCount; ++j) {
|
for (size_t j = i; j < totalWordCount; ++j) {
|
||||||
@ -125,9 +129,6 @@ void ParsedText::layoutAndExtractLines(const GfxRenderer& renderer, const int fo
|
|||||||
|
|
||||||
// Calculate spacing
|
// Calculate spacing
|
||||||
int spareSpace = pageWidth - lineWordWidthSum;
|
int spareSpace = pageWidth - lineWordWidthSum;
|
||||||
if (wordWidthIndex == 0) {
|
|
||||||
spareSpace -= indentWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
int spacing = spaceWidth;
|
int spacing = spaceWidth;
|
||||||
const bool isLastLine = lineBreak == totalWordCount;
|
const bool isLastLine = lineBreak == totalWordCount;
|
||||||
@ -137,8 +138,7 @@ void ParsedText::layoutAndExtractLines(const GfxRenderer& renderer, const int fo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate initial x position
|
// Calculate initial x position
|
||||||
uint16_t xpos = (wordWidthIndex == 0) ? indentWidth : 0;
|
uint16_t xpos = 0;
|
||||||
|
|
||||||
if (style == TextBlock::RIGHT_ALIGN) {
|
if (style == TextBlock::RIGHT_ALIGN) {
|
||||||
xpos = spareSpace - (lineWordCount - 1) * spaceWidth;
|
xpos = spareSpace - (lineWordCount - 1) * spaceWidth;
|
||||||
} else if (style == TextBlock::CENTER_ALIGN) {
|
} else if (style == TextBlock::CENTER_ALIGN) {
|
||||||
|
|||||||
@ -9,7 +9,9 @@
|
|||||||
#include "Page.h"
|
#include "Page.h"
|
||||||
#include "parsers/ChapterHtmlSlimParser.h"
|
#include "parsers/ChapterHtmlSlimParser.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
constexpr uint8_t SECTION_FILE_VERSION = 5;
|
constexpr uint8_t SECTION_FILE_VERSION = 5;
|
||||||
|
}
|
||||||
|
|
||||||
void Section::onPageComplete(std::unique_ptr<Page> page) {
|
void Section::onPageComplete(std::unique_ptr<Page> page) {
|
||||||
const auto filePath = cachePath + "/page_" + std::to_string(pageCount) + ".bin";
|
const auto filePath = cachePath + "/page_" + std::to_string(pageCount) + ".bin";
|
||||||
|
|||||||
@ -75,6 +75,18 @@ void XMLCALL ChapterHtmlSlimParser::startElement(void* userData, const XML_Char*
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip blocks with role="doc-pagebreak" and epub:type="pagebreak"
|
||||||
|
if (atts != nullptr) {
|
||||||
|
for (int i = 0; atts[i]; i += 2) {
|
||||||
|
if (strcmp(atts[i], "role") == 0 && strcmp(atts[i + 1], "doc-pagebreak") == 0 ||
|
||||||
|
strcmp(atts[i], "epub:type") == 0 && strcmp(atts[i + 1], "pagebreak") == 0) {
|
||||||
|
self->skipUntilDepth = self->depth;
|
||||||
|
self->depth += 1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (matches(name, HEADER_TAGS, NUM_HEADER_TAGS)) {
|
if (matches(name, HEADER_TAGS, NUM_HEADER_TAGS)) {
|
||||||
self->startNewTextBlock(TextBlock::CENTER_ALIGN);
|
self->startNewTextBlock(TextBlock::CENTER_ALIGN);
|
||||||
self->boldUntilDepth = min(self->boldUntilDepth, self->depth);
|
self->boldUntilDepth = min(self->boldUntilDepth, self->depth);
|
||||||
|
|||||||
@ -3,6 +3,10 @@
|
|||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
#include <ZipFile.h>
|
#include <ZipFile.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr const char MEDIA_TYPE_NCX[] = "application/x-dtbncx+xml";
|
||||||
|
}
|
||||||
|
|
||||||
bool ContentOpfParser::setup() {
|
bool ContentOpfParser::setup() {
|
||||||
parser = XML_ParserCreate(nullptr);
|
parser = XML_ParserCreate(nullptr);
|
||||||
if (!parser) {
|
if (!parser) {
|
||||||
@ -111,16 +115,28 @@ void XMLCALL ContentOpfParser::startElement(void* userData, const XML_Char* name
|
|||||||
if (self->state == IN_MANIFEST && (strcmp(name, "item") == 0 || strcmp(name, "opf:item") == 0)) {
|
if (self->state == IN_MANIFEST && (strcmp(name, "item") == 0 || strcmp(name, "opf:item") == 0)) {
|
||||||
std::string itemId;
|
std::string itemId;
|
||||||
std::string href;
|
std::string href;
|
||||||
|
std::string mediaType;
|
||||||
|
|
||||||
for (int i = 0; atts[i]; i += 2) {
|
for (int i = 0; atts[i]; i += 2) {
|
||||||
if (strcmp(atts[i], "id") == 0) {
|
if (strcmp(atts[i], "id") == 0) {
|
||||||
itemId = atts[i + 1];
|
itemId = atts[i + 1];
|
||||||
} else if (strcmp(atts[i], "href") == 0) {
|
} else if (strcmp(atts[i], "href") == 0) {
|
||||||
href = self->baseContentPath + atts[i + 1];
|
href = self->baseContentPath + atts[i + 1];
|
||||||
|
} else if (strcmp(atts[i], "media-type") == 0) {
|
||||||
|
mediaType = atts[i + 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self->items[itemId] = href;
|
self->items[itemId] = href;
|
||||||
|
|
||||||
|
if (mediaType == MEDIA_TYPE_NCX) {
|
||||||
|
if (self->tocNcxPath.empty()) {
|
||||||
|
self->tocNcxPath = href;
|
||||||
|
} else {
|
||||||
|
Serial.printf("[%lu] [COF] Warning: Multiple NCX files found in manifest. Ignoring duplicate: %s\n", millis(),
|
||||||
|
href.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
189
lib/GfxRenderer/Bitmap.cpp
Normal file
189
lib/GfxRenderer/Bitmap.cpp
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
#include "Bitmap.h"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
uint16_t Bitmap::readLE16(File& f) {
|
||||||
|
const int c0 = f.read();
|
||||||
|
const int c1 = f.read();
|
||||||
|
const auto b0 = static_cast<uint8_t>(c0 < 0 ? 0 : c0);
|
||||||
|
const auto b1 = static_cast<uint8_t>(c1 < 0 ? 0 : c1);
|
||||||
|
return static_cast<uint16_t>(b0) | (static_cast<uint16_t>(b1) << 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t Bitmap::readLE32(File& f) {
|
||||||
|
const int c0 = f.read();
|
||||||
|
const int c1 = f.read();
|
||||||
|
const int c2 = f.read();
|
||||||
|
const int c3 = f.read();
|
||||||
|
|
||||||
|
const auto b0 = static_cast<uint8_t>(c0 < 0 ? 0 : c0);
|
||||||
|
const auto b1 = static_cast<uint8_t>(c1 < 0 ? 0 : c1);
|
||||||
|
const auto b2 = static_cast<uint8_t>(c2 < 0 ? 0 : c2);
|
||||||
|
const auto b3 = static_cast<uint8_t>(c3 < 0 ? 0 : c3);
|
||||||
|
|
||||||
|
return static_cast<uint32_t>(b0) | (static_cast<uint32_t>(b1) << 8) | (static_cast<uint32_t>(b2) << 16) |
|
||||||
|
(static_cast<uint32_t>(b3) << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* Bitmap::errorToString(BmpReaderError err) {
|
||||||
|
switch (err) {
|
||||||
|
case BmpReaderError::Ok:
|
||||||
|
return "Ok";
|
||||||
|
case BmpReaderError::FileInvalid:
|
||||||
|
return "FileInvalid";
|
||||||
|
case BmpReaderError::SeekStartFailed:
|
||||||
|
return "SeekStartFailed";
|
||||||
|
case BmpReaderError::NotBMP:
|
||||||
|
return "NotBMP (missing 'BM')";
|
||||||
|
case BmpReaderError::DIBTooSmall:
|
||||||
|
return "DIBTooSmall (<40 bytes)";
|
||||||
|
case BmpReaderError::BadPlanes:
|
||||||
|
return "BadPlanes (!= 1)";
|
||||||
|
case BmpReaderError::UnsupportedBpp:
|
||||||
|
return "UnsupportedBpp (expected 1, 2, 8, 24, or 32)";
|
||||||
|
case BmpReaderError::UnsupportedCompression:
|
||||||
|
return "UnsupportedCompression (expected BI_RGB or BI_BITFIELDS for 32bpp)";
|
||||||
|
case BmpReaderError::BadDimensions:
|
||||||
|
return "BadDimensions";
|
||||||
|
case BmpReaderError::PaletteTooLarge:
|
||||||
|
return "PaletteTooLarge";
|
||||||
|
|
||||||
|
case BmpReaderError::SeekPixelDataFailed:
|
||||||
|
return "SeekPixelDataFailed";
|
||||||
|
case BmpReaderError::BufferTooSmall:
|
||||||
|
return "BufferTooSmall";
|
||||||
|
|
||||||
|
case BmpReaderError::OomRowBuffer:
|
||||||
|
return "OomRowBuffer";
|
||||||
|
case BmpReaderError::ShortReadRow:
|
||||||
|
return "ShortReadRow";
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
BmpReaderError Bitmap::parseHeaders() {
|
||||||
|
if (!file) return BmpReaderError::FileInvalid;
|
||||||
|
if (!file.seek(0)) return BmpReaderError::SeekStartFailed;
|
||||||
|
|
||||||
|
// --- BMP FILE HEADER ---
|
||||||
|
const uint16_t bfType = readLE16(file);
|
||||||
|
if (bfType != 0x4D42) return BmpReaderError::NotBMP;
|
||||||
|
|
||||||
|
file.seek(8, SeekCur);
|
||||||
|
bfOffBits = readLE32(file);
|
||||||
|
|
||||||
|
// --- DIB HEADER ---
|
||||||
|
const uint32_t biSize = readLE32(file);
|
||||||
|
if (biSize < 40) return BmpReaderError::DIBTooSmall;
|
||||||
|
|
||||||
|
width = static_cast<int32_t>(readLE32(file));
|
||||||
|
const auto rawHeight = static_cast<int32_t>(readLE32(file));
|
||||||
|
topDown = rawHeight < 0;
|
||||||
|
height = topDown ? -rawHeight : rawHeight;
|
||||||
|
|
||||||
|
const uint16_t planes = readLE16(file);
|
||||||
|
bpp = readLE16(file);
|
||||||
|
const uint32_t comp = readLE32(file);
|
||||||
|
const bool validBpp = bpp == 1 || bpp == 2 || bpp == 8 || bpp == 24 || bpp == 32;
|
||||||
|
|
||||||
|
if (planes != 1) return BmpReaderError::BadPlanes;
|
||||||
|
if (!validBpp) return BmpReaderError::UnsupportedBpp;
|
||||||
|
// Allow BI_RGB (0) for all, and BI_BITFIELDS (3) for 32bpp which is common for BGRA masks.
|
||||||
|
if (!(comp == 0 || (bpp == 32 && comp == 3))) return BmpReaderError::UnsupportedCompression;
|
||||||
|
|
||||||
|
file.seek(12, SeekCur); // biSizeImage, biXPelsPerMeter, biYPelsPerMeter
|
||||||
|
const uint32_t colorsUsed = readLE32(file);
|
||||||
|
if (colorsUsed > 256u) return BmpReaderError::PaletteTooLarge;
|
||||||
|
file.seek(4, SeekCur); // biClrImportant
|
||||||
|
|
||||||
|
if (width <= 0 || height <= 0) return BmpReaderError::BadDimensions;
|
||||||
|
|
||||||
|
// Pre-calculate Row Bytes to avoid doing this every row
|
||||||
|
rowBytes = (width * bpp + 31) / 32 * 4;
|
||||||
|
|
||||||
|
for (int i = 0; i < 256; i++) paletteLum[i] = static_cast<uint8_t>(i);
|
||||||
|
if (colorsUsed > 0) {
|
||||||
|
for (uint32_t i = 0; i < colorsUsed; i++) {
|
||||||
|
uint8_t rgb[4];
|
||||||
|
file.read(rgb, 4); // Read B, G, R, Reserved in one go
|
||||||
|
paletteLum[i] = (77u * rgb[2] + 150u * rgb[1] + 29u * rgb[0]) >> 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file.seek(bfOffBits)) {
|
||||||
|
return BmpReaderError::SeekPixelDataFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return BmpReaderError::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
// packed 2bpp output, 0 = black, 1 = dark gray, 2 = light gray, 3 = white
|
||||||
|
BmpReaderError Bitmap::readRow(uint8_t* data, uint8_t* rowBuffer) const {
|
||||||
|
// Note: rowBuffer should be pre-allocated by the caller to size 'rowBytes'
|
||||||
|
if (file.read(rowBuffer, rowBytes) != rowBytes) return BmpReaderError::ShortReadRow;
|
||||||
|
|
||||||
|
uint8_t* outPtr = data;
|
||||||
|
uint8_t currentOutByte = 0;
|
||||||
|
int bitShift = 6;
|
||||||
|
|
||||||
|
// Helper lambda to pack 2bpp color into the output stream
|
||||||
|
auto packPixel = [&](uint8_t lum) {
|
||||||
|
uint8_t color = (lum >> 6); // Simple 2-bit reduction: 0-255 -> 0-3
|
||||||
|
currentOutByte |= (color << bitShift);
|
||||||
|
if (bitShift == 0) {
|
||||||
|
*outPtr++ = currentOutByte;
|
||||||
|
currentOutByte = 0;
|
||||||
|
bitShift = 6;
|
||||||
|
} else {
|
||||||
|
bitShift -= 2;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (bpp) {
|
||||||
|
case 8: {
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
packPixel(paletteLum[rowBuffer[x]]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 24: {
|
||||||
|
const uint8_t* p = rowBuffer;
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
uint8_t lum = (77u * p[2] + 150u * p[1] + 29u * p[0]) >> 8;
|
||||||
|
packPixel(lum);
|
||||||
|
p += 3;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 1: {
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
uint8_t lum = (rowBuffer[x >> 3] & (0x80 >> (x & 7))) ? 0xFF : 0x00;
|
||||||
|
packPixel(lum);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 32: {
|
||||||
|
const uint8_t* p = rowBuffer;
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
uint8_t lum = (77u * p[2] + 150u * p[1] + 29u * p[0]) >> 8;
|
||||||
|
packPixel(lum);
|
||||||
|
p += 4;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush remaining bits if width is not a multiple of 4
|
||||||
|
if (bitShift != 6) *outPtr = currentOutByte;
|
||||||
|
|
||||||
|
return BmpReaderError::Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
BmpReaderError Bitmap::rewindToData() const {
|
||||||
|
if (!file.seek(bfOffBits)) {
|
||||||
|
return BmpReaderError::SeekPixelDataFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return BmpReaderError::Ok;
|
||||||
|
}
|
||||||
52
lib/GfxRenderer/Bitmap.h
Normal file
52
lib/GfxRenderer/Bitmap.h
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <FS.h>
|
||||||
|
|
||||||
|
enum class BmpReaderError : uint8_t {
|
||||||
|
Ok = 0,
|
||||||
|
FileInvalid,
|
||||||
|
SeekStartFailed,
|
||||||
|
|
||||||
|
NotBMP,
|
||||||
|
DIBTooSmall,
|
||||||
|
|
||||||
|
BadPlanes,
|
||||||
|
UnsupportedBpp,
|
||||||
|
UnsupportedCompression,
|
||||||
|
|
||||||
|
BadDimensions,
|
||||||
|
PaletteTooLarge,
|
||||||
|
|
||||||
|
SeekPixelDataFailed,
|
||||||
|
BufferTooSmall,
|
||||||
|
OomRowBuffer,
|
||||||
|
ShortReadRow,
|
||||||
|
};
|
||||||
|
|
||||||
|
class Bitmap {
|
||||||
|
public:
|
||||||
|
static const char* errorToString(BmpReaderError err);
|
||||||
|
|
||||||
|
explicit Bitmap(File& file) : file(file) {}
|
||||||
|
BmpReaderError parseHeaders();
|
||||||
|
BmpReaderError readRow(uint8_t* data, uint8_t* rowBuffer) const;
|
||||||
|
BmpReaderError rewindToData() const;
|
||||||
|
int getWidth() const { return width; }
|
||||||
|
int getHeight() const { return height; }
|
||||||
|
bool isTopDown() const { return topDown; }
|
||||||
|
bool hasGreyscale() const { return bpp > 1; }
|
||||||
|
int getRowBytes() const { return rowBytes; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
static uint16_t readLE16(File& f);
|
||||||
|
static uint32_t readLE32(File& f);
|
||||||
|
|
||||||
|
File& file;
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
bool topDown = false;
|
||||||
|
uint32_t bfOffBits = 0;
|
||||||
|
uint16_t bpp = 0;
|
||||||
|
int rowBytes = 0;
|
||||||
|
uint8_t paletteLum[256] = {};
|
||||||
|
};
|
||||||
@ -119,6 +119,66 @@ void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, co
|
|||||||
einkDisplay.drawImage(bitmap, y, x, height, width);
|
einkDisplay.drawImage(bitmap, y, x, height, width);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GfxRenderer::drawBitmap(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t outputRowSize = (bitmap.getWidth() + 3) / 4;
|
||||||
|
auto* outputRow = static_cast<uint8_t*>(malloc(outputRowSize));
|
||||||
|
auto* rowBytes = static_cast<uint8_t*>(malloc(bitmap.getRowBytes()));
|
||||||
|
|
||||||
|
for (int bmpY = 0; bmpY < bitmap.getHeight(); bmpY++) {
|
||||||
|
// The BMP's (0, 0) is the bottom-left corner (if the height is positive, top-left if negative).
|
||||||
|
// Screen's (0, 0) is the top-left corner.
|
||||||
|
int screenY = y + (bitmap.isTopDown() ? bmpY : bitmap.getHeight() - 1 - bmpY);
|
||||||
|
if (isScaled) {
|
||||||
|
screenY = std::floor(screenY * scale);
|
||||||
|
}
|
||||||
|
if (screenY >= getScreenHeight()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bitmap.readRow(outputRow, rowBytes) != BmpReaderError::Ok) {
|
||||||
|
Serial.printf("[%lu] [GFX] Failed to read row %d from bitmap\n", millis(), bmpY);
|
||||||
|
free(outputRow);
|
||||||
|
free(rowBytes);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int bmpX = 0; bmpX < bitmap.getWidth(); bmpX++) {
|
||||||
|
int screenX = x + bmpX;
|
||||||
|
if (isScaled) {
|
||||||
|
screenX = std::floor(screenX * scale);
|
||||||
|
}
|
||||||
|
if (screenX >= getScreenWidth()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t val = outputRow[bmpX / 4] >> (6 - ((bmpX * 2) % 8)) & 0x3;
|
||||||
|
|
||||||
|
if (renderMode == BW && val < 3) {
|
||||||
|
drawPixel(screenX, screenY);
|
||||||
|
} else if (renderMode == GRAYSCALE_MSB && (val == 1 || val == 2)) {
|
||||||
|
drawPixel(screenX, screenY, false);
|
||||||
|
} else if (renderMode == GRAYSCALE_LSB && val == 1) {
|
||||||
|
drawPixel(screenX, screenY, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(outputRow);
|
||||||
|
free(rowBytes);
|
||||||
|
}
|
||||||
|
|
||||||
void GfxRenderer::clearScreen(const uint8_t color) const { einkDisplay.clearScreen(color); }
|
void GfxRenderer::clearScreen(const uint8_t color) const { einkDisplay.clearScreen(color); }
|
||||||
|
|
||||||
void GfxRenderer::invertScreen() const {
|
void GfxRenderer::invertScreen() const {
|
||||||
@ -132,13 +192,19 @@ void GfxRenderer::displayBuffer(const EInkDisplay::RefreshMode refreshMode) cons
|
|||||||
einkDisplay.displayBuffer(refreshMode);
|
einkDisplay.displayBuffer(refreshMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Support partial window update
|
void GfxRenderer::displayWindow(const int x, const int y, const int width, const int height) const {
|
||||||
// void GfxRenderer::flushArea(const int x, const int y, const int width, const int height) const {
|
// Rotate coordinates from portrait (480x800) to landscape (800x480)
|
||||||
// const int rotatedX = y;
|
// Rotation: 90 degrees clockwise
|
||||||
// const int rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x;
|
// Portrait coordinates: (x, y) with dimensions (width, height)
|
||||||
//
|
// Landscape coordinates: (rotatedX, rotatedY) with dimensions (rotatedWidth, rotatedHeight)
|
||||||
// einkDisplay.displayBuffer(EInkDisplay::FAST_REFRESH, rotatedX, rotatedY, height, width);
|
|
||||||
// }
|
const int rotatedX = y;
|
||||||
|
const int rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x - width + 1;
|
||||||
|
const int rotatedWidth = height;
|
||||||
|
const int rotatedHeight = width;
|
||||||
|
|
||||||
|
einkDisplay.displayWindow(rotatedX, rotatedY, rotatedWidth, rotatedHeight);
|
||||||
|
}
|
||||||
|
|
||||||
// Note: Internal driver treats screen in command orientation, this library treats in portrait orientation
|
// Note: Internal driver treats screen in command orientation, this library treats in portrait orientation
|
||||||
int GfxRenderer::getScreenWidth() { return EInkDisplay::DISPLAY_HEIGHT; }
|
int GfxRenderer::getScreenWidth() { return EInkDisplay::DISPLAY_HEIGHT; }
|
||||||
@ -164,7 +230,7 @@ int GfxRenderer::getLineHeight(const int fontId) const {
|
|||||||
|
|
||||||
uint8_t* GfxRenderer::getFrameBuffer() const { return einkDisplay.getFrameBuffer(); }
|
uint8_t* GfxRenderer::getFrameBuffer() const { return einkDisplay.getFrameBuffer(); }
|
||||||
|
|
||||||
void GfxRenderer::swapBuffers() const { einkDisplay.swapBuffers(); }
|
size_t GfxRenderer::getBufferSize() { return EInkDisplay::BUFFER_SIZE; }
|
||||||
|
|
||||||
void GfxRenderer::grayscaleRevert() const { einkDisplay.grayscaleRevert(); }
|
void GfxRenderer::grayscaleRevert() const { einkDisplay.grayscaleRevert(); }
|
||||||
|
|
||||||
@ -174,6 +240,90 @@ void GfxRenderer::copyGrayscaleMsbBuffers() const { einkDisplay.copyGrayscaleMsb
|
|||||||
|
|
||||||
void GfxRenderer::displayGrayBuffer() const { einkDisplay.displayGrayBuffer(); }
|
void GfxRenderer::displayGrayBuffer() const { einkDisplay.displayGrayBuffer(); }
|
||||||
|
|
||||||
|
void GfxRenderer::freeBwBufferChunks() {
|
||||||
|
for (auto& bwBufferChunk : bwBufferChunks) {
|
||||||
|
if (bwBufferChunk) {
|
||||||
|
free(bwBufferChunk);
|
||||||
|
bwBufferChunk = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This should be called before grayscale buffers are populated.
|
||||||
|
* A `restoreBwBuffer` call should always follow the grayscale render if this method was called.
|
||||||
|
* Uses chunked allocation to avoid needing 48KB of contiguous memory.
|
||||||
|
*/
|
||||||
|
void GfxRenderer::storeBwBuffer() {
|
||||||
|
const uint8_t* frameBuffer = einkDisplay.getFrameBuffer();
|
||||||
|
|
||||||
|
// Allocate and copy each chunk
|
||||||
|
for (size_t i = 0; i < BW_BUFFER_NUM_CHUNKS; i++) {
|
||||||
|
// Check if any chunks are already allocated
|
||||||
|
if (bwBufferChunks[i]) {
|
||||||
|
Serial.printf("[%lu] [GFX] !! BW buffer chunk %zu already stored - this is likely a bug, freeing chunk\n",
|
||||||
|
millis(), i);
|
||||||
|
free(bwBufferChunks[i]);
|
||||||
|
bwBufferChunks[i] = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t offset = i * BW_BUFFER_CHUNK_SIZE;
|
||||||
|
bwBufferChunks[i] = static_cast<uint8_t*>(malloc(BW_BUFFER_CHUNK_SIZE));
|
||||||
|
|
||||||
|
if (!bwBufferChunks[i]) {
|
||||||
|
Serial.printf("[%lu] [GFX] !! Failed to allocate BW buffer chunk %zu (%zu bytes)\n", millis(), i,
|
||||||
|
BW_BUFFER_CHUNK_SIZE);
|
||||||
|
// Free previously allocated chunks
|
||||||
|
freeBwBufferChunks();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(bwBufferChunks[i], frameBuffer + offset, BW_BUFFER_CHUNK_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf("[%lu] [GFX] Stored BW buffer in %zu chunks (%zu bytes each)\n", millis(), BW_BUFFER_NUM_CHUNKS,
|
||||||
|
BW_BUFFER_CHUNK_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This can only be called if `storeBwBuffer` was called prior to the grayscale render.
|
||||||
|
* It should be called to restore the BW buffer state after grayscale rendering is complete.
|
||||||
|
* Uses chunked restoration to match chunked storage.
|
||||||
|
*/
|
||||||
|
void GfxRenderer::restoreBwBuffer() {
|
||||||
|
// Check if any all chunks are allocated
|
||||||
|
bool missingChunks = false;
|
||||||
|
for (const auto& bwBufferChunk : bwBufferChunks) {
|
||||||
|
if (!bwBufferChunk) {
|
||||||
|
missingChunks = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missingChunks) {
|
||||||
|
freeBwBufferChunks();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t* frameBuffer = einkDisplay.getFrameBuffer();
|
||||||
|
for (size_t i = 0; i < BW_BUFFER_NUM_CHUNKS; i++) {
|
||||||
|
// Check if chunk is missing
|
||||||
|
if (!bwBufferChunks[i]) {
|
||||||
|
Serial.printf("[%lu] [GFX] !! BW buffer chunks not stored - this is likely a bug\n", millis());
|
||||||
|
freeBwBufferChunks();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t offset = i * BW_BUFFER_CHUNK_SIZE;
|
||||||
|
memcpy(frameBuffer + offset, bwBufferChunks[i], BW_BUFFER_CHUNK_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
einkDisplay.cleanupGrayscaleBuffers(frameBuffer);
|
||||||
|
|
||||||
|
freeBwBufferChunks();
|
||||||
|
Serial.printf("[%lu] [GFX] Restored and freed BW buffer chunks\n", millis());
|
||||||
|
}
|
||||||
|
|
||||||
void GfxRenderer::renderChar(const EpdFontFamily& fontFamily, const uint32_t cp, int* x, const int* y,
|
void GfxRenderer::renderChar(const EpdFontFamily& fontFamily, const uint32_t cp, int* x, const int* y,
|
||||||
const bool pixelState, const EpdFontStyle style) const {
|
const bool pixelState, const EpdFontStyle style) const {
|
||||||
const EpdGlyph* glyph = fontFamily.getGlyph(cp, style);
|
const EpdGlyph* glyph = fontFamily.getGlyph(cp, style);
|
||||||
|
|||||||
@ -2,19 +2,29 @@
|
|||||||
|
|
||||||
#include <EInkDisplay.h>
|
#include <EInkDisplay.h>
|
||||||
#include <EpdFontFamily.h>
|
#include <EpdFontFamily.h>
|
||||||
|
#include <FS.h>
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
|
#include "Bitmap.h"
|
||||||
|
|
||||||
class GfxRenderer {
|
class GfxRenderer {
|
||||||
public:
|
public:
|
||||||
enum RenderMode { BW, GRAYSCALE_LSB, GRAYSCALE_MSB };
|
enum RenderMode { BW, GRAYSCALE_LSB, GRAYSCALE_MSB };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static constexpr size_t BW_BUFFER_CHUNK_SIZE = 8000; // 8KB chunks to allow for non-contiguous memory
|
||||||
|
static constexpr size_t BW_BUFFER_NUM_CHUNKS = EInkDisplay::BUFFER_SIZE / BW_BUFFER_CHUNK_SIZE;
|
||||||
|
static_assert(BW_BUFFER_CHUNK_SIZE * BW_BUFFER_NUM_CHUNKS == EInkDisplay::BUFFER_SIZE,
|
||||||
|
"BW buffer chunking does not line up with display buffer size");
|
||||||
|
|
||||||
EInkDisplay& einkDisplay;
|
EInkDisplay& einkDisplay;
|
||||||
RenderMode renderMode;
|
RenderMode renderMode;
|
||||||
|
uint8_t* bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr};
|
||||||
std::map<int, EpdFontFamily> fontMap;
|
std::map<int, EpdFontFamily> fontMap;
|
||||||
void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, const int* y, bool pixelState,
|
void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, const int* y, bool pixelState,
|
||||||
EpdFontStyle style) const;
|
EpdFontStyle style) const;
|
||||||
|
void freeBwBufferChunks();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit GfxRenderer(EInkDisplay& einkDisplay) : einkDisplay(einkDisplay), renderMode(BW) {}
|
explicit GfxRenderer(EInkDisplay& einkDisplay) : einkDisplay(einkDisplay), renderMode(BW) {}
|
||||||
@ -27,6 +37,8 @@ class GfxRenderer {
|
|||||||
static int getScreenWidth();
|
static int getScreenWidth();
|
||||||
static int getScreenHeight();
|
static int getScreenHeight();
|
||||||
void displayBuffer(EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH) const;
|
void displayBuffer(EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH) const;
|
||||||
|
// EXPERIMENTAL: Windowed update - display only a rectangular region (portrait coordinates)
|
||||||
|
void displayWindow(int x, int y, int width, int height) const;
|
||||||
void invertScreen() const;
|
void invertScreen() const;
|
||||||
void clearScreen(uint8_t color = 0xFF) const;
|
void clearScreen(uint8_t color = 0xFF) const;
|
||||||
|
|
||||||
@ -36,6 +48,7 @@ class GfxRenderer {
|
|||||||
void drawRect(int x, int y, int width, int height, bool state = true) const;
|
void drawRect(int x, int y, int width, int height, bool state = true) const;
|
||||||
void fillRect(int x, int y, int width, int height, bool state = true) const;
|
void fillRect(int x, int y, int width, int height, bool state = true) const;
|
||||||
void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const;
|
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) const;
|
||||||
|
|
||||||
// Text
|
// Text
|
||||||
int getTextWidth(int fontId, const char* text, EpdFontStyle style = REGULAR) const;
|
int getTextWidth(int fontId, const char* text, EpdFontStyle style = REGULAR) const;
|
||||||
@ -49,9 +62,11 @@ class GfxRenderer {
|
|||||||
void copyGrayscaleLsbBuffers() const;
|
void copyGrayscaleLsbBuffers() const;
|
||||||
void copyGrayscaleMsbBuffers() const;
|
void copyGrayscaleMsbBuffers() const;
|
||||||
void displayGrayBuffer() const;
|
void displayGrayBuffer() const;
|
||||||
|
void storeBwBuffer();
|
||||||
|
void restoreBwBuffer();
|
||||||
|
|
||||||
// Low level functions
|
// Low level functions
|
||||||
uint8_t* getFrameBuffer() const;
|
uint8_t* getFrameBuffer() const;
|
||||||
void swapBuffers() const;
|
static size_t getBufferSize();
|
||||||
void grayscaleRevert() const;
|
void grayscaleRevert() const;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -62,6 +62,10 @@ long ZipFile::getDataOffset(const mz_zip_archive_file_stat& fileStat) const {
|
|||||||
const uint64_t fileOffset = fileStat.m_local_header_ofs;
|
const uint64_t fileOffset = fileStat.m_local_header_ofs;
|
||||||
|
|
||||||
FILE* file = fopen(filePath.c_str(), "r");
|
FILE* file = fopen(filePath.c_str(), "r");
|
||||||
|
if (!file) {
|
||||||
|
Serial.printf("[%lu] [ZIP] Failed to open file for reading local header\n", millis());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
fseek(file, fileOffset, SEEK_SET);
|
fseek(file, fileOffset, SEEK_SET);
|
||||||
const size_t read = fread(pLocalHeader, 1, localHeaderSize, file);
|
const size_t read = fread(pLocalHeader, 1, localHeaderSize, file);
|
||||||
fclose(file);
|
fclose(file);
|
||||||
@ -104,6 +108,10 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo
|
|||||||
}
|
}
|
||||||
|
|
||||||
FILE* file = fopen(filePath.c_str(), "rb");
|
FILE* file = fopen(filePath.c_str(), "rb");
|
||||||
|
if (!file) {
|
||||||
|
Serial.printf("[%lu] [ZIP] Failed to open file for reading\n", millis());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
fseek(file, fileOffset, SEEK_SET);
|
fseek(file, fileOffset, SEEK_SET);
|
||||||
|
|
||||||
const auto deflatedDataSize = static_cast<size_t>(fileStat.m_comp_size);
|
const auto deflatedDataSize = static_cast<size_t>(fileStat.m_comp_size);
|
||||||
@ -175,6 +183,10 @@ bool ZipFile::readFileToStream(const char* filename, Print& out, const size_t ch
|
|||||||
}
|
}
|
||||||
|
|
||||||
FILE* file = fopen(filePath.c_str(), "rb");
|
FILE* file = fopen(filePath.c_str(), "rb");
|
||||||
|
if (!file) {
|
||||||
|
Serial.printf("[%lu] [ZIP] Failed to open file for streaming\n", millis());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
fseek(file, fileOffset, SEEK_SET);
|
fseek(file, fileOffset, SEEK_SET);
|
||||||
|
|
||||||
const auto deflatedDataSize = static_cast<size_t>(fileStat.m_comp_size);
|
const auto deflatedDataSize = static_cast<size_t>(fileStat.m_comp_size);
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
Subproject commit 4d0dcd5ff87fcd86eb2966a123e85b03284a03db
|
Subproject commit 98a5aa1f8969ccd317c9b45bf0fa84b6c82e167f
|
||||||
@ -1,5 +1,5 @@
|
|||||||
[platformio]
|
[platformio]
|
||||||
crosspoint_version = 0.6.0
|
crosspoint_version = 0.7.0
|
||||||
default_envs = default
|
default_envs = default
|
||||||
|
|
||||||
[base]
|
[base]
|
||||||
@ -20,6 +20,7 @@ build_flags =
|
|||||||
-DARDUINO_USB_MODE=1
|
-DARDUINO_USB_MODE=1
|
||||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||||
-DMINIZ_NO_ZLIB_COMPATIBLE_NAMES=1
|
-DMINIZ_NO_ZLIB_COMPATIBLE_NAMES=1
|
||||||
|
-DEINK_DISPLAY_SINGLE_BUFFER_MODE=1
|
||||||
# https://libexpat.github.io/doc/api/latest/#XML_GE
|
# https://libexpat.github.io/doc/api/latest/#XML_GE
|
||||||
-DXML_GE=0
|
-DXML_GE=0
|
||||||
-DXML_CONTEXT_BYTES=1024
|
-DXML_CONTEXT_BYTES=1024
|
||||||
|
|||||||
@ -10,9 +10,11 @@
|
|||||||
// Initialize the static instance
|
// Initialize the static instance
|
||||||
CrossPointSettings CrossPointSettings::instance;
|
CrossPointSettings CrossPointSettings::instance;
|
||||||
|
|
||||||
|
namespace {
|
||||||
constexpr uint8_t SETTINGS_FILE_VERSION = 1;
|
constexpr uint8_t SETTINGS_FILE_VERSION = 1;
|
||||||
constexpr uint8_t SETTINGS_COUNT = 2;
|
constexpr uint8_t SETTINGS_COUNT = 3;
|
||||||
constexpr char SETTINGS_FILE[] = "/sd/.crosspoint/settings.bin";
|
constexpr char SETTINGS_FILE[] = "/sd/.crosspoint/settings.bin";
|
||||||
|
} // namespace
|
||||||
|
|
||||||
bool CrossPointSettings::saveToFile() const {
|
bool CrossPointSettings::saveToFile() const {
|
||||||
// Make sure the directory exists
|
// Make sure the directory exists
|
||||||
@ -23,6 +25,7 @@ bool CrossPointSettings::saveToFile() const {
|
|||||||
serialization::writePod(outputFile, SETTINGS_COUNT);
|
serialization::writePod(outputFile, SETTINGS_COUNT);
|
||||||
serialization::writePod(outputFile, whiteSleepScreen);
|
serialization::writePod(outputFile, whiteSleepScreen);
|
||||||
serialization::writePod(outputFile, extraParagraphSpacing);
|
serialization::writePod(outputFile, extraParagraphSpacing);
|
||||||
|
serialization::writePod(outputFile, shortPwrBtn);
|
||||||
outputFile.close();
|
outputFile.close();
|
||||||
|
|
||||||
Serial.printf("[%lu] [CPS] Settings saved to file\n", millis());
|
Serial.printf("[%lu] [CPS] Settings saved to file\n", millis());
|
||||||
@ -49,15 +52,15 @@ bool CrossPointSettings::loadFromFile() {
|
|||||||
serialization::readPod(inputFile, fileSettingsCount);
|
serialization::readPod(inputFile, fileSettingsCount);
|
||||||
|
|
||||||
// load settings that exist
|
// load settings that exist
|
||||||
switch (fileSettingsCount) {
|
uint8_t settingsRead = 0;
|
||||||
case 1:
|
do {
|
||||||
serialization::readPod(inputFile, whiteSleepScreen);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
serialization::readPod(inputFile, whiteSleepScreen);
|
serialization::readPod(inputFile, whiteSleepScreen);
|
||||||
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
serialization::readPod(inputFile, extraParagraphSpacing);
|
serialization::readPod(inputFile, extraParagraphSpacing);
|
||||||
break;
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
}
|
serialization::readPod(inputFile, shortPwrBtn);
|
||||||
|
if (++settingsRead >= fileSettingsCount) break;
|
||||||
|
} while (false);
|
||||||
|
|
||||||
inputFile.close();
|
inputFile.close();
|
||||||
Serial.printf("[%lu] [CPS] Settings loaded from file\n", millis());
|
Serial.printf("[%lu] [CPS] Settings loaded from file\n", millis());
|
||||||
|
|||||||
@ -17,15 +17,18 @@ class CrossPointSettings {
|
|||||||
|
|
||||||
// Sleep screen settings
|
// Sleep screen settings
|
||||||
uint8_t whiteSleepScreen = 0;
|
uint8_t whiteSleepScreen = 0;
|
||||||
|
|
||||||
// Text rendering settings
|
// Text rendering settings
|
||||||
uint8_t extraParagraphSpacing = 1;
|
uint8_t extraParagraphSpacing = 1;
|
||||||
|
// Duration of the power button press
|
||||||
|
uint8_t shortPwrBtn = 0;
|
||||||
|
|
||||||
~CrossPointSettings() = default;
|
~CrossPointSettings() = default;
|
||||||
|
|
||||||
// Get singleton instance
|
// Get singleton instance
|
||||||
static CrossPointSettings& getInstance() { return instance; }
|
static CrossPointSettings& getInstance() { return instance; }
|
||||||
|
|
||||||
|
uint16_t getPowerButtonDuration() const { return shortPwrBtn ? 10 : 500; }
|
||||||
|
|
||||||
bool saveToFile() const;
|
bool saveToFile() const;
|
||||||
bool loadFromFile();
|
bool loadFromFile();
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,8 +6,12 @@
|
|||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
|
namespace {
|
||||||
constexpr uint8_t STATE_FILE_VERSION = 1;
|
constexpr uint8_t STATE_FILE_VERSION = 1;
|
||||||
constexpr char STATE_FILE[] = "/sd/.crosspoint/state.bin";
|
constexpr char STATE_FILE[] = "/sd/.crosspoint/state.bin";
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
CrossPointState CrossPointState::instance;
|
||||||
|
|
||||||
bool CrossPointState::saveToFile() const {
|
bool CrossPointState::saveToFile() const {
|
||||||
std::ofstream outputFile(STATE_FILE);
|
std::ofstream outputFile(STATE_FILE);
|
||||||
|
|||||||
@ -3,11 +3,20 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
class CrossPointState {
|
class CrossPointState {
|
||||||
|
// Static instance
|
||||||
|
static CrossPointState instance;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
std::string openEpubPath;
|
std::string openEpubPath;
|
||||||
~CrossPointState() = default;
|
~CrossPointState() = default;
|
||||||
|
|
||||||
|
// Get singleton instance
|
||||||
|
static CrossPointState& getInstance() { return instance; }
|
||||||
|
|
||||||
bool saveToFile() const;
|
bool saveToFile() const;
|
||||||
|
|
||||||
bool loadFromFile();
|
bool loadFromFile();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper macro to access settings
|
||||||
|
#define APP_STATE CrossPointState::getInstance()
|
||||||
|
|||||||
18
src/activities/Activity.h
Normal file
18
src/activities/Activity.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <InputManager.h>
|
||||||
|
|
||||||
|
class GfxRenderer;
|
||||||
|
|
||||||
|
class Activity {
|
||||||
|
protected:
|
||||||
|
GfxRenderer& renderer;
|
||||||
|
InputManager& inputManager;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Activity(GfxRenderer& renderer, InputManager& inputManager)
|
||||||
|
: renderer(renderer), inputManager(inputManager) {}
|
||||||
|
virtual ~Activity() = default;
|
||||||
|
virtual void onEnter() {}
|
||||||
|
virtual void onExit() {}
|
||||||
|
virtual void loop() {}
|
||||||
|
};
|
||||||
21
src/activities/ActivityWithSubactivity.cpp
Normal file
21
src/activities/ActivityWithSubactivity.cpp
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#include "ActivityWithSubactivity.h"
|
||||||
|
|
||||||
|
void ActivityWithSubactivity::exitActivity() {
|
||||||
|
if (subActivity) {
|
||||||
|
subActivity->onExit();
|
||||||
|
subActivity.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityWithSubactivity::enterNewActivity(Activity* activity) {
|
||||||
|
subActivity.reset(activity);
|
||||||
|
subActivity->onEnter();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityWithSubactivity::loop() {
|
||||||
|
if (subActivity) {
|
||||||
|
subActivity->loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ActivityWithSubactivity::onExit() { exitActivity(); }
|
||||||
17
src/activities/ActivityWithSubactivity.h
Normal file
17
src/activities/ActivityWithSubactivity.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "Activity.h"
|
||||||
|
|
||||||
|
class ActivityWithSubactivity : public Activity {
|
||||||
|
protected:
|
||||||
|
std::unique_ptr<Activity> subActivity = nullptr;
|
||||||
|
void exitActivity();
|
||||||
|
void enterNewActivity(Activity* activity);
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ActivityWithSubactivity(GfxRenderer& renderer, InputManager& inputManager)
|
||||||
|
: Activity(renderer, inputManager) {}
|
||||||
|
void loop() override;
|
||||||
|
void onExit() override;
|
||||||
|
};
|
||||||
@ -1,11 +1,11 @@
|
|||||||
#include "BootLogoScreen.h"
|
#include "BootActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "images/CrossLarge.h"
|
#include "images/CrossLarge.h"
|
||||||
|
|
||||||
void BootLogoScreen::onEnter() {
|
void BootActivity::onEnter() {
|
||||||
const auto pageWidth = GfxRenderer::getScreenWidth();
|
const auto pageWidth = GfxRenderer::getScreenWidth();
|
||||||
const auto pageHeight = GfxRenderer::getScreenHeight();
|
const auto pageHeight = GfxRenderer::getScreenHeight();
|
||||||
|
|
||||||
8
src/activities/boot_sleep/BootActivity.h
Normal file
8
src/activities/boot_sleep/BootActivity.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "../Activity.h"
|
||||||
|
|
||||||
|
class BootActivity final : public Activity {
|
||||||
|
public:
|
||||||
|
explicit BootActivity(GfxRenderer& renderer, InputManager& inputManager) : Activity(renderer, inputManager) {}
|
||||||
|
void onEnter() override;
|
||||||
|
};
|
||||||
87
src/activities/boot_sleep/SleepActivity.cpp
Normal file
87
src/activities/boot_sleep/SleepActivity.cpp
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
#include "SleepActivity.h"
|
||||||
|
|
||||||
|
#include <GfxRenderer.h>
|
||||||
|
|
||||||
|
#include "CrossPointSettings.h"
|
||||||
|
#include "SD.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "images/CrossLarge.h"
|
||||||
|
|
||||||
|
void SleepActivity::onEnter() {
|
||||||
|
// Look for sleep.bmp on the root of the sd card to determine if we should
|
||||||
|
// render a custom sleep screen instead of the default.
|
||||||
|
auto file = SD.open("/sleep.bmp");
|
||||||
|
if (file) {
|
||||||
|
Bitmap bitmap(file);
|
||||||
|
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
|
||||||
|
renderCustomSleepScreen(bitmap);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDefaultSleepScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SleepActivity::renderDefaultSleepScreen() const {
|
||||||
|
const auto pageWidth = GfxRenderer::getScreenWidth();
|
||||||
|
const auto pageHeight = GfxRenderer::getScreenHeight();
|
||||||
|
|
||||||
|
renderer.clearScreen();
|
||||||
|
renderer.drawImage(CrossLarge, (pageWidth - 128) / 2, (pageHeight - 128) / 2, 128, 128);
|
||||||
|
renderer.drawCenteredText(UI_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, BOLD);
|
||||||
|
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "SLEEPING");
|
||||||
|
|
||||||
|
// Apply white screen if enabled in settings
|
||||||
|
if (!SETTINGS.whiteSleepScreen) {
|
||||||
|
renderer.invertScreen();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SleepActivity::renderCustomSleepScreen(const Bitmap& bitmap) const {
|
||||||
|
int x, y;
|
||||||
|
const auto pageWidth = GfxRenderer::getScreenWidth();
|
||||||
|
const auto pageHeight = GfxRenderer::getScreenHeight();
|
||||||
|
|
||||||
|
if (bitmap.getWidth() > pageWidth || bitmap.getHeight() > pageHeight) {
|
||||||
|
// image will scale, make sure placement is right
|
||||||
|
const float ratio = static_cast<float>(bitmap.getWidth()) / static_cast<float>(bitmap.getHeight());
|
||||||
|
const float screenRatio = static_cast<float>(pageWidth) / static_cast<float>(pageHeight);
|
||||||
|
|
||||||
|
if (ratio > screenRatio) {
|
||||||
|
// image wider than viewport ratio, scaled down image needs to be centered vertically
|
||||||
|
x = 0;
|
||||||
|
y = (pageHeight - pageWidth / ratio) / 2;
|
||||||
|
} else {
|
||||||
|
// image taller than viewport ratio, scaled down image needs to be centered horizontally
|
||||||
|
x = (pageWidth - pageHeight * ratio) / 2;
|
||||||
|
y = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// center the image
|
||||||
|
x = (pageWidth - bitmap.getWidth()) / 2;
|
||||||
|
y = (pageHeight - bitmap.getHeight()) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.clearScreen();
|
||||||
|
renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight);
|
||||||
|
renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
|
||||||
|
|
||||||
|
if (bitmap.hasGreyscale()) {
|
||||||
|
bitmap.rewindToData();
|
||||||
|
renderer.clearScreen(0x00);
|
||||||
|
renderer.setRenderMode(GfxRenderer::GRAYSCALE_LSB);
|
||||||
|
renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight);
|
||||||
|
renderer.copyGrayscaleLsbBuffers();
|
||||||
|
|
||||||
|
bitmap.rewindToData();
|
||||||
|
renderer.clearScreen(0x00);
|
||||||
|
renderer.setRenderMode(GfxRenderer::GRAYSCALE_MSB);
|
||||||
|
renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight);
|
||||||
|
renderer.copyGrayscaleMsbBuffers();
|
||||||
|
|
||||||
|
renderer.displayGrayBuffer();
|
||||||
|
renderer.setRenderMode(GfxRenderer::BW);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/activities/boot_sleep/SleepActivity.h
Normal file
14
src/activities/boot_sleep/SleepActivity.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "../Activity.h"
|
||||||
|
|
||||||
|
class Bitmap;
|
||||||
|
|
||||||
|
class SleepActivity final : public Activity {
|
||||||
|
public:
|
||||||
|
explicit SleepActivity(GfxRenderer& renderer, InputManager& inputManager) : Activity(renderer, inputManager) {}
|
||||||
|
void onEnter() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void renderDefaultSleepScreen() const;
|
||||||
|
void renderCustomSleepScreen(const Bitmap& bitmap) const;
|
||||||
|
};
|
||||||
103
src/activities/home/HomeActivity.cpp
Normal file
103
src/activities/home/HomeActivity.cpp
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
#include "HomeActivity.h"
|
||||||
|
|
||||||
|
#include <GfxRenderer.h>
|
||||||
|
#include <SD.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr int menuItemCount = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HomeActivity::taskTrampoline(void* param) {
|
||||||
|
auto* self = static_cast<HomeActivity*>(param);
|
||||||
|
self->displayTaskLoop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HomeActivity::onEnter() {
|
||||||
|
renderingMutex = xSemaphoreCreateMutex();
|
||||||
|
|
||||||
|
selectorIndex = 0;
|
||||||
|
|
||||||
|
// Trigger first update
|
||||||
|
updateRequired = true;
|
||||||
|
|
||||||
|
xTaskCreate(&HomeActivity::taskTrampoline, "HomeActivityTask",
|
||||||
|
2048, // Stack size
|
||||||
|
this, // Parameters
|
||||||
|
1, // Priority
|
||||||
|
&displayTaskHandle // Task handle
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HomeActivity::onExit() {
|
||||||
|
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
||||||
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
|
if (displayTaskHandle) {
|
||||||
|
vTaskDelete(displayTaskHandle);
|
||||||
|
displayTaskHandle = nullptr;
|
||||||
|
}
|
||||||
|
vSemaphoreDelete(renderingMutex);
|
||||||
|
renderingMutex = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HomeActivity::loop() {
|
||||||
|
const bool prevPressed =
|
||||||
|
inputManager.wasPressed(InputManager::BTN_UP) || inputManager.wasPressed(InputManager::BTN_LEFT);
|
||||||
|
const bool nextPressed =
|
||||||
|
inputManager.wasPressed(InputManager::BTN_DOWN) || inputManager.wasPressed(InputManager::BTN_RIGHT);
|
||||||
|
|
||||||
|
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
||||||
|
if (selectorIndex == 0) {
|
||||||
|
onReaderOpen();
|
||||||
|
} else if (selectorIndex == 1) {
|
||||||
|
onSettingsOpen();
|
||||||
|
}
|
||||||
|
} else if (prevPressed) {
|
||||||
|
selectorIndex = (selectorIndex + menuItemCount - 1) % menuItemCount;
|
||||||
|
updateRequired = true;
|
||||||
|
} else if (nextPressed) {
|
||||||
|
selectorIndex = (selectorIndex + 1) % menuItemCount;
|
||||||
|
updateRequired = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HomeActivity::displayTaskLoop() {
|
||||||
|
while (true) {
|
||||||
|
if (updateRequired) {
|
||||||
|
updateRequired = false;
|
||||||
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
|
render();
|
||||||
|
xSemaphoreGive(renderingMutex);
|
||||||
|
}
|
||||||
|
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HomeActivity::render() const {
|
||||||
|
renderer.clearScreen();
|
||||||
|
|
||||||
|
const auto pageWidth = GfxRenderer::getScreenWidth();
|
||||||
|
const auto pageHeight = GfxRenderer::getScreenHeight();
|
||||||
|
renderer.drawCenteredText(READER_FONT_ID, 10, "CrossPoint Reader", true, BOLD);
|
||||||
|
|
||||||
|
// Draw selection
|
||||||
|
renderer.fillRect(0, 60 + selectorIndex * 30 + 2, pageWidth - 1, 30);
|
||||||
|
renderer.drawText(UI_FONT_ID, 20, 60, "Read", selectorIndex != 0);
|
||||||
|
renderer.drawText(UI_FONT_ID, 20, 90, "Settings", selectorIndex != 1);
|
||||||
|
|
||||||
|
renderer.drawRect(25, pageHeight - 40, 106, 40);
|
||||||
|
renderer.drawText(UI_FONT_ID, 25 + (105 - renderer.getTextWidth(UI_FONT_ID, "Back")) / 2, pageHeight - 35, "Back");
|
||||||
|
|
||||||
|
renderer.drawRect(130, pageHeight - 40, 106, 40);
|
||||||
|
renderer.drawText(UI_FONT_ID, 130 + (105 - renderer.getTextWidth(UI_FONT_ID, "Confirm")) / 2, pageHeight - 35,
|
||||||
|
"Confirm");
|
||||||
|
|
||||||
|
renderer.drawRect(245, pageHeight - 40, 106, 40);
|
||||||
|
renderer.drawText(UI_FONT_ID, 245 + (105 - renderer.getTextWidth(UI_FONT_ID, "Left")) / 2, pageHeight - 35, "Left");
|
||||||
|
|
||||||
|
renderer.drawRect(350, pageHeight - 40, 106, 40);
|
||||||
|
renderer.drawText(UI_FONT_ID, 350 + (105 - renderer.getTextWidth(UI_FONT_ID, "Right")) / 2, pageHeight - 35, "Right");
|
||||||
|
|
||||||
|
renderer.displayBuffer();
|
||||||
|
}
|
||||||
29
src/activities/home/HomeActivity.h
Normal file
29
src/activities/home/HomeActivity.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/semphr.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include "../Activity.h"
|
||||||
|
|
||||||
|
class HomeActivity final : public Activity {
|
||||||
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
|
int selectorIndex = 0;
|
||||||
|
bool updateRequired = false;
|
||||||
|
const std::function<void()> onReaderOpen;
|
||||||
|
const std::function<void()> onSettingsOpen;
|
||||||
|
|
||||||
|
static void taskTrampoline(void* param);
|
||||||
|
[[noreturn]] void displayTaskLoop();
|
||||||
|
void render() const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit HomeActivity(GfxRenderer& renderer, InputManager& inputManager, const std::function<void()>& onReaderOpen,
|
||||||
|
const std::function<void()>& onSettingsOpen)
|
||||||
|
: Activity(renderer, inputManager), onReaderOpen(onReaderOpen), onSettingsOpen(onSettingsOpen) {}
|
||||||
|
void onEnter() override;
|
||||||
|
void onExit() override;
|
||||||
|
void loop() override;
|
||||||
|
};
|
||||||
@ -1,4 +1,4 @@
|
|||||||
#include "EpubReaderScreen.h"
|
#include "EpubReaderActivity.h"
|
||||||
|
|
||||||
#include <Epub/Page.h>
|
#include <Epub/Page.h>
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
@ -6,23 +6,25 @@
|
|||||||
|
|
||||||
#include "Battery.h"
|
#include "Battery.h"
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
#include "EpubReaderChapterSelectionScreen.h"
|
#include "EpubReaderChapterSelectionActivity.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
constexpr int PAGES_PER_REFRESH = 15;
|
namespace {
|
||||||
constexpr unsigned long SKIP_CHAPTER_MS = 700;
|
constexpr int pagesPerRefresh = 15;
|
||||||
|
constexpr unsigned long skipChapterMs = 700;
|
||||||
constexpr float lineCompression = 0.95f;
|
constexpr float lineCompression = 0.95f;
|
||||||
constexpr int marginTop = 8;
|
constexpr int marginTop = 8;
|
||||||
constexpr int marginRight = 10;
|
constexpr int marginRight = 10;
|
||||||
constexpr int marginBottom = 22;
|
constexpr int marginBottom = 22;
|
||||||
constexpr int marginLeft = 10;
|
constexpr int marginLeft = 10;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void EpubReaderScreen::taskTrampoline(void* param) {
|
void EpubReaderActivity::taskTrampoline(void* param) {
|
||||||
auto* self = static_cast<EpubReaderScreen*>(param);
|
auto* self = static_cast<EpubReaderActivity*>(param);
|
||||||
self->displayTaskLoop();
|
self->displayTaskLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderScreen::onEnter() {
|
void EpubReaderActivity::onEnter() {
|
||||||
if (!epub) {
|
if (!epub) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -31,21 +33,21 @@ void EpubReaderScreen::onEnter() {
|
|||||||
|
|
||||||
epub->setupCacheDir();
|
epub->setupCacheDir();
|
||||||
|
|
||||||
// TODO: Move this to a state object
|
|
||||||
if (SD.exists((epub->getCachePath() + "/progress.bin").c_str())) {
|
|
||||||
File f = SD.open((epub->getCachePath() + "/progress.bin").c_str());
|
File f = SD.open((epub->getCachePath() + "/progress.bin").c_str());
|
||||||
|
if (f) {
|
||||||
uint8_t data[4];
|
uint8_t data[4];
|
||||||
f.read(data, 4);
|
if (f.read(data, 4) == 4) {
|
||||||
currentSpineIndex = data[0] + (data[1] << 8);
|
currentSpineIndex = data[0] + (data[1] << 8);
|
||||||
nextPageNumber = data[2] + (data[3] << 8);
|
nextPageNumber = data[2] + (data[3] << 8);
|
||||||
Serial.printf("[%lu] [ERS] Loaded cache: %d, %d\n", millis(), currentSpineIndex, nextPageNumber);
|
Serial.printf("[%lu] [ERS] Loaded cache: %d, %d\n", millis(), currentSpineIndex, nextPageNumber);
|
||||||
|
}
|
||||||
f.close();
|
f.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger first update
|
// Trigger first update
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
|
|
||||||
xTaskCreate(&EpubReaderScreen::taskTrampoline, "EpubReaderScreenTask",
|
xTaskCreate(&EpubReaderActivity::taskTrampoline, "EpubReaderActivityTask",
|
||||||
8192, // Stack size
|
8192, // Stack size
|
||||||
this, // Parameters
|
this, // Parameters
|
||||||
1, // Priority
|
1, // Priority
|
||||||
@ -53,7 +55,7 @@ void EpubReaderScreen::onEnter() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderScreen::onExit() {
|
void EpubReaderActivity::onExit() {
|
||||||
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
if (displayTaskHandle) {
|
if (displayTaskHandle) {
|
||||||
@ -66,22 +68,22 @@ void EpubReaderScreen::onExit() {
|
|||||||
epub.reset();
|
epub.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderScreen::handleInput() {
|
void EpubReaderActivity::loop() {
|
||||||
// Pass input responsibility to sub screen if exists
|
// Pass input responsibility to sub activity if exists
|
||||||
if (subScreen) {
|
if (subAcitivity) {
|
||||||
subScreen->handleInput();
|
subAcitivity->loop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enter chapter selection screen
|
// Enter chapter selection activity
|
||||||
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
||||||
// Don't start screen transition while rendering
|
// Don't start activity transition while rendering
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
subScreen.reset(new EpubReaderChapterSelectionScreen(
|
subAcitivity.reset(new EpubReaderChapterSelectionActivity(
|
||||||
this->renderer, this->inputManager, epub, currentSpineIndex,
|
this->renderer, this->inputManager, epub, currentSpineIndex,
|
||||||
[this] {
|
[this] {
|
||||||
subScreen->onExit();
|
subAcitivity->onExit();
|
||||||
subScreen.reset();
|
subAcitivity.reset();
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
},
|
},
|
||||||
[this](const int newSpineIndex) {
|
[this](const int newSpineIndex) {
|
||||||
@ -90,16 +92,16 @@ void EpubReaderScreen::handleInput() {
|
|||||||
nextPageNumber = 0;
|
nextPageNumber = 0;
|
||||||
section.reset();
|
section.reset();
|
||||||
}
|
}
|
||||||
subScreen->onExit();
|
subAcitivity->onExit();
|
||||||
subScreen.reset();
|
subAcitivity.reset();
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
}));
|
}));
|
||||||
subScreen->onEnter();
|
subAcitivity->onEnter();
|
||||||
xSemaphoreGive(renderingMutex);
|
xSemaphoreGive(renderingMutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputManager.wasPressed(InputManager::BTN_BACK)) {
|
if (inputManager.wasPressed(InputManager::BTN_BACK)) {
|
||||||
onGoHome();
|
onGoBack();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +122,7 @@ void EpubReaderScreen::handleInput() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool skipChapter = inputManager.getHeldTime() > SKIP_CHAPTER_MS;
|
const bool skipChapter = inputManager.getHeldTime() > skipChapterMs;
|
||||||
|
|
||||||
if (skipChapter) {
|
if (skipChapter) {
|
||||||
// We don't want to delete the section mid-render, so grab the semaphore
|
// We don't want to delete the section mid-render, so grab the semaphore
|
||||||
@ -166,7 +168,7 @@ void EpubReaderScreen::handleInput() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderScreen::displayTaskLoop() {
|
void EpubReaderActivity::displayTaskLoop() {
|
||||||
while (true) {
|
while (true) {
|
||||||
if (updateRequired) {
|
if (updateRequired) {
|
||||||
updateRequired = false;
|
updateRequired = false;
|
||||||
@ -179,7 +181,7 @@ void EpubReaderScreen::displayTaskLoop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Failure handling
|
// TODO: Failure handling
|
||||||
void EpubReaderScreen::renderScreen() {
|
void EpubReaderActivity::renderScreen() {
|
||||||
if (!epub) {
|
if (!epub) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -212,15 +214,12 @@ void EpubReaderScreen::renderScreen() {
|
|||||||
{
|
{
|
||||||
const int textWidth = renderer.getTextWidth(READER_FONT_ID, "Indexing...");
|
const int textWidth = renderer.getTextWidth(READER_FONT_ID, "Indexing...");
|
||||||
constexpr int margin = 20;
|
constexpr int margin = 20;
|
||||||
const int x = (GfxRenderer::getScreenWidth() - textWidth - margin * 2) / 2;
|
// Round all coordinates to 8 pixel boundaries
|
||||||
constexpr int y = 50;
|
const int x = ((GfxRenderer::getScreenWidth() - textWidth - margin * 2) / 2 + 7) / 8 * 8;
|
||||||
const int w = textWidth + margin * 2;
|
constexpr int y = 56;
|
||||||
const int h = renderer.getLineHeight(READER_FONT_ID) + margin * 2;
|
const int w = (textWidth + margin * 2 + 7) / 8 * 8;
|
||||||
renderer.grayscaleRevert();
|
const int h = (renderer.getLineHeight(READER_FONT_ID) + margin * 2 + 7) / 8 * 8;
|
||||||
uint8_t* fb1 = renderer.getFrameBuffer();
|
renderer.clearScreen();
|
||||||
renderer.swapBuffers();
|
|
||||||
memcpy(fb1, renderer.getFrameBuffer(), EInkDisplay::BUFFER_SIZE);
|
|
||||||
renderer.fillRect(x, y, w, h, 0);
|
|
||||||
renderer.drawText(READER_FONT_ID, x + margin, y + margin, "Indexing...");
|
renderer.drawText(READER_FONT_ID, x + margin, y + margin, "Indexing...");
|
||||||
renderer.drawRect(x + 5, y + 5, w - 10, h - 10);
|
renderer.drawRect(x + 5, y + 5, w - 10, h - 10);
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
@ -286,17 +285,20 @@ void EpubReaderScreen::renderScreen() {
|
|||||||
f.close();
|
f.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderScreen::renderContents(std::unique_ptr<Page> page) {
|
void EpubReaderActivity::renderContents(std::unique_ptr<Page> page) {
|
||||||
page->render(renderer, READER_FONT_ID);
|
page->render(renderer, READER_FONT_ID);
|
||||||
renderStatusBar();
|
renderStatusBar();
|
||||||
if (pagesUntilFullRefresh <= 1) {
|
if (pagesUntilFullRefresh <= 1) {
|
||||||
renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
|
renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
|
||||||
pagesUntilFullRefresh = PAGES_PER_REFRESH;
|
pagesUntilFullRefresh = pagesPerRefresh;
|
||||||
} else {
|
} else {
|
||||||
renderer.displayBuffer();
|
renderer.displayBuffer();
|
||||||
pagesUntilFullRefresh--;
|
pagesUntilFullRefresh--;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save bw buffer to reset buffer state after grayscale data sync
|
||||||
|
renderer.storeBwBuffer();
|
||||||
|
|
||||||
// grayscale rendering
|
// grayscale rendering
|
||||||
// TODO: Only do this if font supports it
|
// TODO: Only do this if font supports it
|
||||||
{
|
{
|
||||||
@ -315,12 +317,21 @@ void EpubReaderScreen::renderContents(std::unique_ptr<Page> page) {
|
|||||||
renderer.displayGrayBuffer();
|
renderer.displayGrayBuffer();
|
||||||
renderer.setRenderMode(GfxRenderer::BW);
|
renderer.setRenderMode(GfxRenderer::BW);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// restore the bw data
|
||||||
|
renderer.restoreBwBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderScreen::renderStatusBar() const {
|
void EpubReaderActivity::renderStatusBar() const {
|
||||||
constexpr auto textY = 776;
|
constexpr auto textY = 776;
|
||||||
|
|
||||||
|
// Calculate progress in book
|
||||||
|
float sectionChapterProg = static_cast<float>(section->currentPage) / section->pageCount;
|
||||||
|
uint8_t bookProgress = epub->calculateProgress(currentSpineIndex, sectionChapterProg);
|
||||||
|
|
||||||
// Right aligned text for progress counter
|
// Right aligned text for progress counter
|
||||||
const std::string progress = std::to_string(section->currentPage + 1) + " / " + std::to_string(section->pageCount);
|
const std::string progress = std::to_string(section->currentPage + 1) + "/" + std::to_string(section->pageCount) +
|
||||||
|
" " + std::to_string(bookProgress) + "%";
|
||||||
const auto progressTextWidth = renderer.getTextWidth(SMALL_FONT_ID, progress.c_str());
|
const auto progressTextWidth = renderer.getTextWidth(SMALL_FONT_ID, progress.c_str());
|
||||||
renderer.drawText(SMALL_FONT_ID, GfxRenderer::getScreenWidth() - marginRight - progressTextWidth, textY,
|
renderer.drawText(SMALL_FONT_ID, GfxRenderer::getScreenWidth() - marginRight - progressTextWidth, textY,
|
||||||
progress.c_str());
|
progress.c_str());
|
||||||
@ -372,7 +383,7 @@ void EpubReaderScreen::renderStatusBar() const {
|
|||||||
const auto tocItem = epub->getTocItem(tocIndex);
|
const auto tocItem = epub->getTocItem(tocIndex);
|
||||||
title = tocItem.title;
|
title = tocItem.title;
|
||||||
titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str());
|
titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str());
|
||||||
while (titleWidth > availableTextWidth) {
|
while (titleWidth > availableTextWidth && title.length() > 11) {
|
||||||
title = title.substr(0, title.length() - 8) + "...";
|
title = title.substr(0, title.length() - 8) + "...";
|
||||||
titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str());
|
titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str());
|
||||||
}
|
}
|
||||||
@ -5,19 +5,19 @@
|
|||||||
#include <freertos/semphr.h>
|
#include <freertos/semphr.h>
|
||||||
#include <freertos/task.h>
|
#include <freertos/task.h>
|
||||||
|
|
||||||
#include "Screen.h"
|
#include "../Activity.h"
|
||||||
|
|
||||||
class EpubReaderScreen final : public Screen {
|
class EpubReaderActivity final : public Activity {
|
||||||
std::shared_ptr<Epub> epub;
|
std::shared_ptr<Epub> epub;
|
||||||
std::unique_ptr<Section> section = nullptr;
|
std::unique_ptr<Section> section = nullptr;
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
std::unique_ptr<Screen> subScreen = nullptr;
|
std::unique_ptr<Activity> subAcitivity = nullptr;
|
||||||
int currentSpineIndex = 0;
|
int currentSpineIndex = 0;
|
||||||
int nextPageNumber = 0;
|
int nextPageNumber = 0;
|
||||||
int pagesUntilFullRefresh = 0;
|
int pagesUntilFullRefresh = 0;
|
||||||
bool updateRequired = false;
|
bool updateRequired = false;
|
||||||
const std::function<void()> onGoHome;
|
const std::function<void()> onGoBack;
|
||||||
|
|
||||||
static void taskTrampoline(void* param);
|
static void taskTrampoline(void* param);
|
||||||
[[noreturn]] void displayTaskLoop();
|
[[noreturn]] void displayTaskLoop();
|
||||||
@ -26,10 +26,10 @@ class EpubReaderScreen final : public Screen {
|
|||||||
void renderStatusBar() const;
|
void renderStatusBar() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit EpubReaderScreen(GfxRenderer& renderer, InputManager& inputManager, std::unique_ptr<Epub> epub,
|
explicit EpubReaderActivity(GfxRenderer& renderer, InputManager& inputManager, std::unique_ptr<Epub> epub,
|
||||||
const std::function<void()>& onGoHome)
|
const std::function<void()>& onGoBack)
|
||||||
: Screen(renderer, inputManager), epub(std::move(epub)), onGoHome(onGoHome) {}
|
: Activity(renderer, inputManager), epub(std::move(epub)), onGoBack(onGoBack) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
void handleInput() override;
|
void loop() override;
|
||||||
};
|
};
|
||||||
@ -1,4 +1,4 @@
|
|||||||
#include "EpubReaderChapterSelectionScreen.h"
|
#include "EpubReaderChapterSelectionActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <SD.h>
|
#include <SD.h>
|
||||||
@ -8,12 +8,12 @@
|
|||||||
constexpr int PAGE_ITEMS = 24;
|
constexpr int PAGE_ITEMS = 24;
|
||||||
constexpr int SKIP_PAGE_MS = 700;
|
constexpr int SKIP_PAGE_MS = 700;
|
||||||
|
|
||||||
void EpubReaderChapterSelectionScreen::taskTrampoline(void* param) {
|
void EpubReaderChapterSelectionActivity::taskTrampoline(void* param) {
|
||||||
auto* self = static_cast<EpubReaderChapterSelectionScreen*>(param);
|
auto* self = static_cast<EpubReaderChapterSelectionActivity*>(param);
|
||||||
self->displayTaskLoop();
|
self->displayTaskLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderChapterSelectionScreen::onEnter() {
|
void EpubReaderChapterSelectionActivity::onEnter() {
|
||||||
if (!epub) {
|
if (!epub) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -23,7 +23,7 @@ void EpubReaderChapterSelectionScreen::onEnter() {
|
|||||||
|
|
||||||
// Trigger first update
|
// Trigger first update
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
xTaskCreate(&EpubReaderChapterSelectionScreen::taskTrampoline, "EpubReaderChapterSelectionScreenTask",
|
xTaskCreate(&EpubReaderChapterSelectionActivity::taskTrampoline, "EpubReaderChapterSelectionActivityTask",
|
||||||
2048, // Stack size
|
2048, // Stack size
|
||||||
this, // Parameters
|
this, // Parameters
|
||||||
1, // Priority
|
1, // Priority
|
||||||
@ -31,7 +31,7 @@ void EpubReaderChapterSelectionScreen::onEnter() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderChapterSelectionScreen::onExit() {
|
void EpubReaderChapterSelectionActivity::onExit() {
|
||||||
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
if (displayTaskHandle) {
|
if (displayTaskHandle) {
|
||||||
@ -42,7 +42,7 @@ void EpubReaderChapterSelectionScreen::onExit() {
|
|||||||
renderingMutex = nullptr;
|
renderingMutex = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderChapterSelectionScreen::handleInput() {
|
void EpubReaderChapterSelectionActivity::loop() {
|
||||||
const bool prevReleased =
|
const bool prevReleased =
|
||||||
inputManager.wasReleased(InputManager::BTN_UP) || inputManager.wasReleased(InputManager::BTN_LEFT);
|
inputManager.wasReleased(InputManager::BTN_UP) || inputManager.wasReleased(InputManager::BTN_LEFT);
|
||||||
const bool nextReleased =
|
const bool nextReleased =
|
||||||
@ -72,7 +72,7 @@ void EpubReaderChapterSelectionScreen::handleInput() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderChapterSelectionScreen::displayTaskLoop() {
|
void EpubReaderChapterSelectionActivity::displayTaskLoop() {
|
||||||
while (true) {
|
while (true) {
|
||||||
if (updateRequired) {
|
if (updateRequired) {
|
||||||
updateRequired = false;
|
updateRequired = false;
|
||||||
@ -84,7 +84,7 @@ void EpubReaderChapterSelectionScreen::displayTaskLoop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderChapterSelectionScreen::renderScreen() {
|
void EpubReaderChapterSelectionActivity::renderScreen() {
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
|
|
||||||
const auto pageWidth = renderer.getScreenWidth();
|
const auto pageWidth = renderer.getScreenWidth();
|
||||||
@ -6,9 +6,9 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "Screen.h"
|
#include "../Activity.h"
|
||||||
|
|
||||||
class EpubReaderChapterSelectionScreen final : public Screen {
|
class EpubReaderChapterSelectionActivity final : public Activity {
|
||||||
std::shared_ptr<Epub> epub;
|
std::shared_ptr<Epub> epub;
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
@ -23,16 +23,16 @@ class EpubReaderChapterSelectionScreen final : public Screen {
|
|||||||
void renderScreen();
|
void renderScreen();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit EpubReaderChapterSelectionScreen(GfxRenderer& renderer, InputManager& inputManager,
|
explicit EpubReaderChapterSelectionActivity(GfxRenderer& renderer, InputManager& inputManager,
|
||||||
const std::shared_ptr<Epub>& epub, const int currentSpineIndex,
|
const std::shared_ptr<Epub>& epub, const int currentSpineIndex,
|
||||||
const std::function<void()>& onGoBack,
|
const std::function<void()>& onGoBack,
|
||||||
const std::function<void(int newSpineIndex)>& onSelectSpineIndex)
|
const std::function<void(int newSpineIndex)>& onSelectSpineIndex)
|
||||||
: Screen(renderer, inputManager),
|
: Activity(renderer, inputManager),
|
||||||
epub(epub),
|
epub(epub),
|
||||||
currentSpineIndex(currentSpineIndex),
|
currentSpineIndex(currentSpineIndex),
|
||||||
onGoBack(onGoBack),
|
onGoBack(onGoBack),
|
||||||
onSelectSpineIndex(onSelectSpineIndex) {}
|
onSelectSpineIndex(onSelectSpineIndex) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
void handleInput() override;
|
void loop() override;
|
||||||
};
|
};
|
||||||
@ -1,4 +1,4 @@
|
|||||||
#include "FileSelectionScreen.h"
|
#include "FileSelectionActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <SD.h>
|
#include <SD.h>
|
||||||
@ -15,12 +15,12 @@ void sortFileList(std::vector<std::string>& strs) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSelectionScreen::taskTrampoline(void* param) {
|
void FileSelectionActivity::taskTrampoline(void* param) {
|
||||||
auto* self = static_cast<FileSelectionScreen*>(param);
|
auto* self = static_cast<FileSelectionActivity*>(param);
|
||||||
self->displayTaskLoop();
|
self->displayTaskLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSelectionScreen::loadFiles() {
|
void FileSelectionActivity::loadFiles() {
|
||||||
files.clear();
|
files.clear();
|
||||||
selectorIndex = 0;
|
selectorIndex = 0;
|
||||||
auto root = SD.open(basepath.c_str());
|
auto root = SD.open(basepath.c_str());
|
||||||
@ -42,7 +42,7 @@ void FileSelectionScreen::loadFiles() {
|
|||||||
sortFileList(files);
|
sortFileList(files);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSelectionScreen::onEnter() {
|
void FileSelectionActivity::onEnter() {
|
||||||
renderingMutex = xSemaphoreCreateMutex();
|
renderingMutex = xSemaphoreCreateMutex();
|
||||||
|
|
||||||
basepath = "/";
|
basepath = "/";
|
||||||
@ -52,7 +52,7 @@ void FileSelectionScreen::onEnter() {
|
|||||||
// Trigger first update
|
// Trigger first update
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
|
|
||||||
xTaskCreate(&FileSelectionScreen::taskTrampoline, "FileSelectionScreenTask",
|
xTaskCreate(&FileSelectionActivity::taskTrampoline, "FileSelectionActivityTask",
|
||||||
2048, // Stack size
|
2048, // Stack size
|
||||||
this, // Parameters
|
this, // Parameters
|
||||||
1, // Priority
|
1, // Priority
|
||||||
@ -60,7 +60,7 @@ void FileSelectionScreen::onEnter() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSelectionScreen::onExit() {
|
void FileSelectionActivity::onExit() {
|
||||||
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
if (displayTaskHandle) {
|
if (displayTaskHandle) {
|
||||||
@ -72,7 +72,7 @@ void FileSelectionScreen::onExit() {
|
|||||||
files.clear();
|
files.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSelectionScreen::handleInput() {
|
void FileSelectionActivity::loop() {
|
||||||
const bool prevPressed =
|
const bool prevPressed =
|
||||||
inputManager.wasPressed(InputManager::BTN_UP) || inputManager.wasPressed(InputManager::BTN_LEFT);
|
inputManager.wasPressed(InputManager::BTN_UP) || inputManager.wasPressed(InputManager::BTN_LEFT);
|
||||||
const bool nextPressed =
|
const bool nextPressed =
|
||||||
@ -98,8 +98,8 @@ void FileSelectionScreen::handleInput() {
|
|||||||
loadFiles();
|
loadFiles();
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
} else {
|
} else {
|
||||||
// At root level, go to settings
|
// At root level, go back home
|
||||||
onSettingsOpen();
|
onGoHome();
|
||||||
}
|
}
|
||||||
} else if (prevPressed) {
|
} else if (prevPressed) {
|
||||||
selectorIndex = (selectorIndex + files.size() - 1) % files.size();
|
selectorIndex = (selectorIndex + files.size() - 1) % files.size();
|
||||||
@ -110,7 +110,7 @@ void FileSelectionScreen::handleInput() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSelectionScreen::displayTaskLoop() {
|
void FileSelectionActivity::displayTaskLoop() {
|
||||||
while (true) {
|
while (true) {
|
||||||
if (updateRequired) {
|
if (updateRequired) {
|
||||||
updateRequired = false;
|
updateRequired = false;
|
||||||
@ -122,14 +122,14 @@ void FileSelectionScreen::displayTaskLoop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileSelectionScreen::render() const {
|
void FileSelectionActivity::render() const {
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
|
|
||||||
const auto pageWidth = GfxRenderer::getScreenWidth();
|
const auto pageWidth = GfxRenderer::getScreenWidth();
|
||||||
renderer.drawCenteredText(READER_FONT_ID, 10, "CrossPoint Reader", true, BOLD);
|
renderer.drawCenteredText(READER_FONT_ID, 10, "CrossPoint Reader", true, BOLD);
|
||||||
|
|
||||||
// Help text
|
// Help text
|
||||||
renderer.drawText(SMALL_FONT_ID, 20, GfxRenderer::getScreenHeight() - 30, "Press BACK for Settings");
|
renderer.drawText(SMALL_FONT_ID, 20, GfxRenderer::getScreenHeight() - 30, "Press BACK for Home");
|
||||||
|
|
||||||
if (files.empty()) {
|
if (files.empty()) {
|
||||||
renderer.drawText(UI_FONT_ID, 20, 60, "No EPUBs found");
|
renderer.drawText(UI_FONT_ID, 20, 60, "No EPUBs found");
|
||||||
@ -7,9 +7,9 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "Screen.h"
|
#include "../Activity.h"
|
||||||
|
|
||||||
class FileSelectionScreen final : public Screen {
|
class FileSelectionActivity final : public Activity {
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
std::string basepath = "/";
|
std::string basepath = "/";
|
||||||
@ -17,7 +17,7 @@ class FileSelectionScreen final : public Screen {
|
|||||||
int selectorIndex = 0;
|
int selectorIndex = 0;
|
||||||
bool updateRequired = false;
|
bool updateRequired = false;
|
||||||
const std::function<void(const std::string&)> onSelect;
|
const std::function<void(const std::string&)> onSelect;
|
||||||
const std::function<void()> onSettingsOpen;
|
const std::function<void()> onGoHome;
|
||||||
|
|
||||||
static void taskTrampoline(void* param);
|
static void taskTrampoline(void* param);
|
||||||
[[noreturn]] void displayTaskLoop();
|
[[noreturn]] void displayTaskLoop();
|
||||||
@ -25,11 +25,11 @@ class FileSelectionScreen final : public Screen {
|
|||||||
void loadFiles();
|
void loadFiles();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit FileSelectionScreen(GfxRenderer& renderer, InputManager& inputManager,
|
explicit FileSelectionActivity(GfxRenderer& renderer, InputManager& inputManager,
|
||||||
const std::function<void(const std::string&)>& onSelect,
|
const std::function<void(const std::string&)>& onSelect,
|
||||||
const std::function<void()>& onSettingsOpen)
|
const std::function<void()>& onGoHome)
|
||||||
: Screen(renderer, inputManager), onSelect(onSelect), onSettingsOpen(onSettingsOpen) {}
|
: Activity(renderer, inputManager), onSelect(onSelect), onGoHome(onGoHome) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
void handleInput() override;
|
void loop() override;
|
||||||
};
|
};
|
||||||
68
src/activities/reader/ReaderActivity.cpp
Normal file
68
src/activities/reader/ReaderActivity.cpp
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#include "ReaderActivity.h"
|
||||||
|
|
||||||
|
#include <SD.h>
|
||||||
|
|
||||||
|
#include "CrossPointState.h"
|
||||||
|
#include "Epub.h"
|
||||||
|
#include "EpubReaderActivity.h"
|
||||||
|
#include "FileSelectionActivity.h"
|
||||||
|
#include "activities/util/FullScreenMessageActivity.h"
|
||||||
|
|
||||||
|
std::unique_ptr<Epub> ReaderActivity::loadEpub(const std::string& path) {
|
||||||
|
if (!SD.exists(path.c_str())) {
|
||||||
|
Serial.printf("[%lu] [ ] File does not exist: %s\n", millis(), path.c_str());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto epub = std::unique_ptr<Epub>(new Epub(path, "/.crosspoint"));
|
||||||
|
if (epub->load()) {
|
||||||
|
return epub;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf("[%lu] [ ] Failed to load epub\n", millis());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReaderActivity::onSelectEpubFile(const std::string& path) {
|
||||||
|
exitActivity();
|
||||||
|
enterNewActivity(new FullScreenMessageActivity(renderer, inputManager, "Loading..."));
|
||||||
|
|
||||||
|
auto epub = loadEpub(path);
|
||||||
|
if (epub) {
|
||||||
|
APP_STATE.openEpubPath = path;
|
||||||
|
APP_STATE.saveToFile();
|
||||||
|
onGoToEpubReader(std::move(epub));
|
||||||
|
} else {
|
||||||
|
exitActivity();
|
||||||
|
enterNewActivity(new FullScreenMessageActivity(renderer, inputManager, "Failed to load epub", REGULAR,
|
||||||
|
EInkDisplay::HALF_REFRESH));
|
||||||
|
delay(2000);
|
||||||
|
onGoToFileSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReaderActivity::onGoToFileSelection() {
|
||||||
|
exitActivity();
|
||||||
|
enterNewActivity(new FileSelectionActivity(
|
||||||
|
renderer, inputManager, [this](const std::string& path) { onSelectEpubFile(path); }, onGoBack));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReaderActivity::onGoToEpubReader(std::unique_ptr<Epub> epub) {
|
||||||
|
exitActivity();
|
||||||
|
enterNewActivity(new EpubReaderActivity(renderer, inputManager, std::move(epub), [this] { onGoToFileSelection(); }));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReaderActivity::onEnter() {
|
||||||
|
if (initialEpubPath.empty()) {
|
||||||
|
onGoToFileSelection();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto epub = loadEpub(initialEpubPath);
|
||||||
|
if (!epub) {
|
||||||
|
onGoBack();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onGoToEpubReader(std::move(epub));
|
||||||
|
}
|
||||||
24
src/activities/reader/ReaderActivity.h
Normal file
24
src/activities/reader/ReaderActivity.h
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "../ActivityWithSubactivity.h"
|
||||||
|
|
||||||
|
class Epub;
|
||||||
|
|
||||||
|
class ReaderActivity final : public ActivityWithSubactivity {
|
||||||
|
std::string initialEpubPath;
|
||||||
|
const std::function<void()> onGoBack;
|
||||||
|
static std::unique_ptr<Epub> loadEpub(const std::string& path);
|
||||||
|
|
||||||
|
void onSelectEpubFile(const std::string& path);
|
||||||
|
void onGoToFileSelection();
|
||||||
|
void onGoToEpubReader(std::unique_ptr<Epub> epub);
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit ReaderActivity(GfxRenderer& renderer, InputManager& inputManager, std::string initialEpubPath,
|
||||||
|
const std::function<void()>& onGoBack)
|
||||||
|
: ActivityWithSubactivity(renderer, inputManager),
|
||||||
|
initialEpubPath(std::move(initialEpubPath)),
|
||||||
|
onGoBack(onGoBack) {}
|
||||||
|
void onEnter() override;
|
||||||
|
};
|
||||||
@ -1,4 +1,4 @@
|
|||||||
#include "SettingsScreen.h"
|
#include "SettingsActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
|
||||||
@ -7,16 +7,17 @@
|
|||||||
|
|
||||||
// Define the static settings list
|
// Define the static settings list
|
||||||
|
|
||||||
const SettingInfo SettingsScreen::settingsList[SettingsScreen::settingsCount] = {
|
const SettingInfo SettingsActivity::settingsList[settingsCount] = {
|
||||||
{"White Sleep Screen", &CrossPointSettings::whiteSleepScreen},
|
{"White Sleep Screen", &CrossPointSettings::whiteSleepScreen},
|
||||||
{"Extra Paragraph Spacing", &CrossPointSettings::extraParagraphSpacing}};
|
{"Extra Paragraph Spacing", &CrossPointSettings::extraParagraphSpacing},
|
||||||
|
{"Short Power Button Click", &CrossPointSettings::shortPwrBtn}};
|
||||||
|
|
||||||
void SettingsScreen::taskTrampoline(void* param) {
|
void SettingsActivity::taskTrampoline(void* param) {
|
||||||
auto* self = static_cast<SettingsScreen*>(param);
|
auto* self = static_cast<SettingsActivity*>(param);
|
||||||
self->displayTaskLoop();
|
self->displayTaskLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsScreen::onEnter() {
|
void SettingsActivity::onEnter() {
|
||||||
renderingMutex = xSemaphoreCreateMutex();
|
renderingMutex = xSemaphoreCreateMutex();
|
||||||
|
|
||||||
// Reset selection to first item
|
// Reset selection to first item
|
||||||
@ -25,7 +26,7 @@ void SettingsScreen::onEnter() {
|
|||||||
// Trigger first update
|
// Trigger first update
|
||||||
updateRequired = true;
|
updateRequired = true;
|
||||||
|
|
||||||
xTaskCreate(&SettingsScreen::taskTrampoline, "SettingsScreenTask",
|
xTaskCreate(&SettingsActivity::taskTrampoline, "SettingsActivityTask",
|
||||||
2048, // Stack size
|
2048, // Stack size
|
||||||
this, // Parameters
|
this, // Parameters
|
||||||
1, // Priority
|
1, // Priority
|
||||||
@ -33,7 +34,7 @@ void SettingsScreen::onEnter() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsScreen::onExit() {
|
void SettingsActivity::onExit() {
|
||||||
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
if (displayTaskHandle) {
|
if (displayTaskHandle) {
|
||||||
@ -44,7 +45,7 @@ void SettingsScreen::onExit() {
|
|||||||
renderingMutex = nullptr;
|
renderingMutex = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsScreen::handleInput() {
|
void SettingsActivity::loop() {
|
||||||
// Handle actions with early return
|
// Handle actions with early return
|
||||||
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
||||||
toggleCurrentSetting();
|
toggleCurrentSetting();
|
||||||
@ -70,7 +71,7 @@ void SettingsScreen::handleInput() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsScreen::toggleCurrentSetting() {
|
void SettingsActivity::toggleCurrentSetting() {
|
||||||
// Validate index
|
// Validate index
|
||||||
if (selectedSettingIndex < 0 || selectedSettingIndex >= settingsCount) {
|
if (selectedSettingIndex < 0 || selectedSettingIndex >= settingsCount) {
|
||||||
return;
|
return;
|
||||||
@ -84,7 +85,7 @@ void SettingsScreen::toggleCurrentSetting() {
|
|||||||
SETTINGS.saveToFile();
|
SETTINGS.saveToFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsScreen::displayTaskLoop() {
|
void SettingsActivity::displayTaskLoop() {
|
||||||
while (true) {
|
while (true) {
|
||||||
if (updateRequired) {
|
if (updateRequired) {
|
||||||
updateRequired = false;
|
updateRequired = false;
|
||||||
@ -96,7 +97,7 @@ void SettingsScreen::displayTaskLoop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsScreen::render() const {
|
void SettingsActivity::render() const {
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
|
|
||||||
const auto pageWidth = GfxRenderer::getScreenWidth();
|
const auto pageWidth = GfxRenderer::getScreenWidth();
|
||||||
@ -7,7 +7,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "Screen.h"
|
#include "../Activity.h"
|
||||||
|
|
||||||
class CrossPointSettings;
|
class CrossPointSettings;
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ struct SettingInfo {
|
|||||||
uint8_t CrossPointSettings::* valuePtr; // Pointer to member in CrossPointSettings
|
uint8_t CrossPointSettings::* valuePtr; // Pointer to member in CrossPointSettings
|
||||||
};
|
};
|
||||||
|
|
||||||
class SettingsScreen final : public Screen {
|
class SettingsActivity final : public Activity {
|
||||||
TaskHandle_t displayTaskHandle = nullptr;
|
TaskHandle_t displayTaskHandle = nullptr;
|
||||||
SemaphoreHandle_t renderingMutex = nullptr;
|
SemaphoreHandle_t renderingMutex = nullptr;
|
||||||
bool updateRequired = false;
|
bool updateRequired = false;
|
||||||
@ -25,7 +25,7 @@ class SettingsScreen final : public Screen {
|
|||||||
const std::function<void()> onGoHome;
|
const std::function<void()> onGoHome;
|
||||||
|
|
||||||
// Static settings list
|
// Static settings list
|
||||||
static constexpr int settingsCount = 2; // Number of settings
|
static constexpr int settingsCount = 3; // Number of settings
|
||||||
static const SettingInfo settingsList[settingsCount];
|
static const SettingInfo settingsList[settingsCount];
|
||||||
|
|
||||||
static void taskTrampoline(void* param);
|
static void taskTrampoline(void* param);
|
||||||
@ -34,9 +34,9 @@ class SettingsScreen final : public Screen {
|
|||||||
void toggleCurrentSetting();
|
void toggleCurrentSetting();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit SettingsScreen(GfxRenderer& renderer, InputManager& inputManager, const std::function<void()>& onGoHome)
|
explicit SettingsActivity(GfxRenderer& renderer, InputManager& inputManager, const std::function<void()>& onGoHome)
|
||||||
: Screen(renderer, inputManager), onGoHome(onGoHome) {}
|
: Activity(renderer, inputManager), onGoHome(onGoHome) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
void onExit() override;
|
void onExit() override;
|
||||||
void handleInput() override;
|
void loop() override;
|
||||||
};
|
};
|
||||||
@ -1,10 +1,10 @@
|
|||||||
#include "FullScreenMessageScreen.h"
|
#include "FullScreenMessageActivity.h"
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
void FullScreenMessageScreen::onEnter() {
|
void FullScreenMessageActivity::onEnter() {
|
||||||
const auto height = renderer.getLineHeight(UI_FONT_ID);
|
const auto height = renderer.getLineHeight(UI_FONT_ID);
|
||||||
const auto top = (GfxRenderer::getScreenHeight() - height) / 2;
|
const auto top = (GfxRenderer::getScreenHeight() - height) / 2;
|
||||||
|
|
||||||
21
src/activities/util/FullScreenMessageActivity.h
Normal file
21
src/activities/util/FullScreenMessageActivity.h
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <EInkDisplay.h>
|
||||||
|
#include <EpdFontFamily.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "../Activity.h"
|
||||||
|
|
||||||
|
class FullScreenMessageActivity final : public Activity {
|
||||||
|
std::string text;
|
||||||
|
EpdFontStyle style;
|
||||||
|
EInkDisplay::RefreshMode refreshMode;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit FullScreenMessageActivity(GfxRenderer& renderer, InputManager& inputManager, std::string text,
|
||||||
|
const EpdFontStyle style = REGULAR,
|
||||||
|
const EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH)
|
||||||
|
: Activity(renderer, inputManager), text(std::move(text)), style(style), refreshMode(refreshMode) {}
|
||||||
|
void onEnter() override;
|
||||||
|
};
|
||||||
@ -9,7 +9,7 @@
|
|||||||
* "./lib/EpdFont/builtinFonts/bookerly_italic_2b.h",
|
* "./lib/EpdFont/builtinFonts/bookerly_italic_2b.h",
|
||||||
* ].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)'
|
* ].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)'
|
||||||
*/
|
*/
|
||||||
#define READER_FONT_ID 828106571
|
#define READER_FONT_ID 1818981670
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generated with:
|
* Generated with:
|
||||||
@ -18,7 +18,7 @@
|
|||||||
* "./lib/EpdFont/builtinFonts/ubuntu_bold_10.h",
|
* "./lib/EpdFont/builtinFonts/ubuntu_bold_10.h",
|
||||||
* ].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)'
|
* ].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)'
|
||||||
*/
|
*/
|
||||||
#define UI_FONT_ID -56235187
|
#define UI_FONT_ID (-1619831379)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generated with:
|
* Generated with:
|
||||||
@ -26,4 +26,4 @@
|
|||||||
* "./lib/EpdFont/builtinFonts/pixelarial14.h",
|
* "./lib/EpdFont/builtinFonts/pixelarial14.h",
|
||||||
* ].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)'
|
* ].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)'
|
||||||
*/
|
*/
|
||||||
#define SMALL_FONT_ID -1952330053
|
#define SMALL_FONT_ID (-139796914)
|
||||||
|
|||||||
145
src/main.cpp
145
src/main.cpp
@ -16,13 +16,13 @@
|
|||||||
#include "Battery.h"
|
#include "Battery.h"
|
||||||
#include "CrossPointSettings.h"
|
#include "CrossPointSettings.h"
|
||||||
#include "CrossPointState.h"
|
#include "CrossPointState.h"
|
||||||
|
#include "activities/boot_sleep/BootActivity.h"
|
||||||
|
#include "activities/boot_sleep/SleepActivity.h"
|
||||||
|
#include "activities/home/HomeActivity.h"
|
||||||
|
#include "activities/reader/ReaderActivity.h"
|
||||||
|
#include "activities/settings/SettingsActivity.h"
|
||||||
|
#include "activities/util/FullScreenMessageActivity.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "screens/BootLogoScreen.h"
|
|
||||||
#include "screens/EpubReaderScreen.h"
|
|
||||||
#include "screens/FileSelectionScreen.h"
|
|
||||||
#include "screens/FullScreenMessageScreen.h"
|
|
||||||
#include "screens/SettingsScreen.h"
|
|
||||||
#include "screens/SleepScreen.h"
|
|
||||||
|
|
||||||
#define SPI_FQ 40000000
|
#define SPI_FQ 40000000
|
||||||
// Display SPI pins (custom pins for XteinkX4, not hardware SPI defaults)
|
// Display SPI pins (custom pins for XteinkX4, not hardware SPI defaults)
|
||||||
@ -41,8 +41,7 @@
|
|||||||
EInkDisplay einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY);
|
EInkDisplay einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY);
|
||||||
InputManager inputManager;
|
InputManager inputManager;
|
||||||
GfxRenderer renderer(einkDisplay);
|
GfxRenderer renderer(einkDisplay);
|
||||||
Screen* currentScreen;
|
Activity* currentActivity;
|
||||||
CrossPointState appState;
|
|
||||||
|
|
||||||
// Fonts
|
// Fonts
|
||||||
EpdFont bookerlyFont(&bookerly_2b);
|
EpdFont bookerlyFont(&bookerly_2b);
|
||||||
@ -58,60 +57,41 @@ EpdFont ubuntu10Font(&ubuntu_10);
|
|||||||
EpdFont ubuntuBold10Font(&ubuntu_bold_10);
|
EpdFont ubuntuBold10Font(&ubuntu_bold_10);
|
||||||
EpdFontFamily ubuntuFontFamily(&ubuntu10Font, &ubuntuBold10Font);
|
EpdFontFamily ubuntuFontFamily(&ubuntu10Font, &ubuntuBold10Font);
|
||||||
|
|
||||||
// Power button timing
|
|
||||||
// Time required to confirm boot from sleep
|
|
||||||
constexpr unsigned long POWER_BUTTON_WAKEUP_MS = 500;
|
|
||||||
// Time required to enter sleep mode
|
|
||||||
constexpr unsigned long POWER_BUTTON_SLEEP_MS = 500;
|
|
||||||
// Auto-sleep timeout (10 minutes of inactivity)
|
// Auto-sleep timeout (10 minutes of inactivity)
|
||||||
constexpr unsigned long AUTO_SLEEP_TIMEOUT_MS = 10 * 60 * 1000;
|
constexpr unsigned long AUTO_SLEEP_TIMEOUT_MS = 10 * 60 * 1000;
|
||||||
|
|
||||||
std::unique_ptr<Epub> loadEpub(const std::string& path) {
|
void exitActivity() {
|
||||||
if (!SD.exists(path.c_str())) {
|
if (currentActivity) {
|
||||||
Serial.printf("[%lu] [ ] File does not exist: %s\n", millis(), path.c_str());
|
currentActivity->onExit();
|
||||||
return nullptr;
|
delete currentActivity;
|
||||||
}
|
|
||||||
|
|
||||||
auto epub = std::unique_ptr<Epub>(new Epub(path, "/.crosspoint"));
|
|
||||||
if (epub->load()) {
|
|
||||||
return epub;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.printf("[%lu] [ ] Failed to load epub\n", millis());
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void exitScreen() {
|
|
||||||
if (currentScreen) {
|
|
||||||
currentScreen->onExit();
|
|
||||||
delete currentScreen;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void enterNewScreen(Screen* screen) {
|
void enterNewActivity(Activity* activity) {
|
||||||
currentScreen = screen;
|
currentActivity = activity;
|
||||||
currentScreen->onEnter();
|
currentActivity->onEnter();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify long press on wake-up from deep sleep
|
// Verify long press on wake-up from deep sleep
|
||||||
void verifyWakeupLongPress() {
|
void verifyWakeupLongPress() {
|
||||||
// Give the user up to 1000ms to start holding the power button, and must hold for POWER_BUTTON_WAKEUP_MS
|
// Give the user up to 1000ms to start holding the power button, and must hold for SETTINGS.getPowerButtonDuration()
|
||||||
const auto start = millis();
|
const auto start = millis();
|
||||||
bool abort = false;
|
bool abort = false;
|
||||||
|
|
||||||
Serial.printf("[%lu] [ ] Verifying power button press\n", millis());
|
|
||||||
inputManager.update();
|
inputManager.update();
|
||||||
|
// Verify the user has actually pressed
|
||||||
while (!inputManager.isPressed(InputManager::BTN_POWER) && millis() - start < 1000) {
|
while (!inputManager.isPressed(InputManager::BTN_POWER) && millis() - start < 1000) {
|
||||||
delay(50);
|
delay(10); // only wait 10ms each iteration to not delay too much in case of short configured duration.
|
||||||
inputManager.update();
|
inputManager.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputManager.isPressed(InputManager::BTN_POWER)) {
|
if (inputManager.isPressed(InputManager::BTN_POWER)) {
|
||||||
do {
|
do {
|
||||||
delay(50);
|
delay(10);
|
||||||
inputManager.update();
|
inputManager.update();
|
||||||
} while (inputManager.isPressed(InputManager::BTN_POWER) && inputManager.getHeldTime() < POWER_BUTTON_WAKEUP_MS);
|
} while (inputManager.isPressed(InputManager::BTN_POWER) &&
|
||||||
abort = inputManager.getHeldTime() < POWER_BUTTON_WAKEUP_MS;
|
inputManager.getHeldTime() < SETTINGS.getPowerButtonDuration());
|
||||||
|
abort = inputManager.getHeldTime() < SETTINGS.getPowerButtonDuration();
|
||||||
} else {
|
} else {
|
||||||
abort = true;
|
abort = true;
|
||||||
}
|
}
|
||||||
@ -134,10 +114,10 @@ void waitForPowerRelease() {
|
|||||||
|
|
||||||
// Enter deep sleep mode
|
// Enter deep sleep mode
|
||||||
void enterDeepSleep() {
|
void enterDeepSleep() {
|
||||||
exitScreen();
|
exitActivity();
|
||||||
enterNewScreen(new SleepScreen(renderer, inputManager));
|
enterNewActivity(new SleepActivity(renderer, inputManager));
|
||||||
|
|
||||||
Serial.printf("[%lu] [ ] Power button released after a long press. Entering deep sleep.\n", millis());
|
Serial.printf("[%lu] [ ] Entering deep sleep.\n", millis());
|
||||||
delay(1000); // Allow Serial buffer to empty and display to update
|
delay(1000); // Allow Serial buffer to empty and display to update
|
||||||
|
|
||||||
// Enable Wakeup on LOW (button press)
|
// Enable Wakeup on LOW (button press)
|
||||||
@ -150,33 +130,20 @@ void enterDeepSleep() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onGoHome();
|
void onGoHome();
|
||||||
void onSelectEpubFile(const std::string& path) {
|
void onGoToReader(const std::string& initialEpubPath) {
|
||||||
exitScreen();
|
exitActivity();
|
||||||
enterNewScreen(new FullScreenMessageScreen(renderer, inputManager, "Loading..."));
|
enterNewActivity(new ReaderActivity(renderer, inputManager, initialEpubPath, onGoHome));
|
||||||
|
|
||||||
auto epub = loadEpub(path);
|
|
||||||
if (epub) {
|
|
||||||
appState.openEpubPath = path;
|
|
||||||
appState.saveToFile();
|
|
||||||
exitScreen();
|
|
||||||
enterNewScreen(new EpubReaderScreen(renderer, inputManager, std::move(epub), onGoHome));
|
|
||||||
} else {
|
|
||||||
exitScreen();
|
|
||||||
enterNewScreen(
|
|
||||||
new FullScreenMessageScreen(renderer, inputManager, "Failed to load epub", REGULAR, EInkDisplay::HALF_REFRESH));
|
|
||||||
delay(2000);
|
|
||||||
onGoHome();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
void onGoToReaderHome() { onGoToReader(std::string()); }
|
||||||
|
|
||||||
void onGoToSettings() {
|
void onGoToSettings() {
|
||||||
exitScreen();
|
exitActivity();
|
||||||
enterNewScreen(new SettingsScreen(renderer, inputManager, onGoHome));
|
enterNewActivity(new SettingsActivity(renderer, inputManager, onGoHome));
|
||||||
}
|
}
|
||||||
|
|
||||||
void onGoHome() {
|
void onGoHome() {
|
||||||
exitScreen();
|
exitActivity();
|
||||||
enterNewScreen(new FileSelectionScreen(renderer, inputManager, onSelectEpubFile, onGoToSettings));
|
enterNewActivity(new HomeActivity(renderer, inputManager, onGoToReaderHome, onGoToSettings));
|
||||||
}
|
}
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
@ -185,14 +152,25 @@ void setup() {
|
|||||||
Serial.printf("[%lu] [ ] Starting CrossPoint version " CROSSPOINT_VERSION "\n", millis());
|
Serial.printf("[%lu] [ ] Starting CrossPoint version " CROSSPOINT_VERSION "\n", millis());
|
||||||
|
|
||||||
inputManager.begin();
|
inputManager.begin();
|
||||||
verifyWakeupLongPress();
|
|
||||||
|
|
||||||
// Initialize pins
|
// Initialize pins
|
||||||
pinMode(BAT_GPIO0, INPUT);
|
pinMode(BAT_GPIO0, INPUT);
|
||||||
|
|
||||||
// Initialize SPI with custom pins
|
// Initialize SPI with custom pins
|
||||||
SPI.begin(EPD_SCLK, SD_SPI_MISO, EPD_MOSI, EPD_CS);
|
SPI.begin(EPD_SCLK, SD_SPI_MISO, EPD_MOSI, EPD_CS);
|
||||||
|
|
||||||
|
// SD Card Initialization
|
||||||
|
if (!SD.begin(SD_SPI_CS, SPI, SPI_FQ)) {
|
||||||
|
Serial.printf("[%lu] [ ] SD card initialization failed\n", millis());
|
||||||
|
exitActivity();
|
||||||
|
enterNewActivity(new FullScreenMessageActivity(renderer, inputManager, "SD card error", BOLD));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SETTINGS.loadFromFile();
|
||||||
|
|
||||||
|
// verify power button press duration after we've read settings.
|
||||||
|
verifyWakeupLongPress();
|
||||||
|
|
||||||
// Initialize display
|
// Initialize display
|
||||||
einkDisplay.begin();
|
einkDisplay.begin();
|
||||||
Serial.printf("[%lu] [ ] Display initialized\n", millis());
|
Serial.printf("[%lu] [ ] Display initialized\n", millis());
|
||||||
@ -202,27 +180,15 @@ void setup() {
|
|||||||
renderer.insertFont(SMALL_FONT_ID, smallFontFamily);
|
renderer.insertFont(SMALL_FONT_ID, smallFontFamily);
|
||||||
Serial.printf("[%lu] [ ] Fonts setup\n", millis());
|
Serial.printf("[%lu] [ ] Fonts setup\n", millis());
|
||||||
|
|
||||||
exitScreen();
|
exitActivity();
|
||||||
enterNewScreen(new BootLogoScreen(renderer, inputManager));
|
enterNewActivity(new BootActivity(renderer, inputManager));
|
||||||
|
|
||||||
// SD Card Initialization
|
APP_STATE.loadFromFile();
|
||||||
SD.begin(SD_SPI_CS, SPI, SPI_FQ);
|
if (APP_STATE.openEpubPath.empty()) {
|
||||||
|
onGoHome();
|
||||||
SETTINGS.loadFromFile();
|
} else {
|
||||||
appState.loadFromFile();
|
onGoToReader(APP_STATE.openEpubPath);
|
||||||
if (!appState.openEpubPath.empty()) {
|
|
||||||
auto epub = loadEpub(appState.openEpubPath);
|
|
||||||
if (epub) {
|
|
||||||
exitScreen();
|
|
||||||
enterNewScreen(new EpubReaderScreen(renderer, inputManager, std::move(epub), onGoHome));
|
|
||||||
// Ensure we're not still holding the power button before leaving setup
|
|
||||||
waitForPowerRelease();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
exitScreen();
|
|
||||||
enterNewScreen(new FileSelectionScreen(renderer, inputManager, onSelectEpubFile, onGoToSettings));
|
|
||||||
|
|
||||||
// Ensure we're not still holding the power button before leaving setup
|
// Ensure we're not still holding the power button before leaving setup
|
||||||
waitForPowerRelease();
|
waitForPowerRelease();
|
||||||
@ -253,13 +219,14 @@ void loop() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputManager.wasReleased(InputManager::BTN_POWER) && inputManager.getHeldTime() > POWER_BUTTON_WAKEUP_MS) {
|
if (inputManager.wasReleased(InputManager::BTN_POWER) &&
|
||||||
|
inputManager.getHeldTime() > SETTINGS.getPowerButtonDuration()) {
|
||||||
enterDeepSleep();
|
enterDeepSleep();
|
||||||
// This should never be hit as `enterDeepSleep` calls esp_deep_sleep_start
|
// This should never be hit as `enterDeepSleep` calls esp_deep_sleep_start
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentScreen) {
|
if (currentActivity) {
|
||||||
currentScreen->handleInput();
|
currentActivity->loop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "Screen.h"
|
|
||||||
|
|
||||||
class BootLogoScreen final : public Screen {
|
|
||||||
public:
|
|
||||||
explicit BootLogoScreen(GfxRenderer& renderer, InputManager& inputManager) : Screen(renderer, inputManager) {}
|
|
||||||
void onEnter() override;
|
|
||||||
};
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <EInkDisplay.h>
|
|
||||||
#include <EpdFontFamily.h>
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include "Screen.h"
|
|
||||||
|
|
||||||
class FullScreenMessageScreen final : public Screen {
|
|
||||||
std::string text;
|
|
||||||
EpdFontStyle style;
|
|
||||||
EInkDisplay::RefreshMode refreshMode;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit FullScreenMessageScreen(GfxRenderer& renderer, InputManager& inputManager, std::string text,
|
|
||||||
const EpdFontStyle style = REGULAR,
|
|
||||||
const EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH)
|
|
||||||
: Screen(renderer, inputManager), text(std::move(text)), style(style), refreshMode(refreshMode) {}
|
|
||||||
void onEnter() override;
|
|
||||||
};
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <InputManager.h>
|
|
||||||
|
|
||||||
class GfxRenderer;
|
|
||||||
|
|
||||||
class Screen {
|
|
||||||
protected:
|
|
||||||
GfxRenderer& renderer;
|
|
||||||
InputManager& inputManager;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit Screen(GfxRenderer& renderer, InputManager& inputManager) : renderer(renderer), inputManager(inputManager) {}
|
|
||||||
virtual ~Screen() = default;
|
|
||||||
virtual void onEnter() {}
|
|
||||||
virtual void onExit() {}
|
|
||||||
virtual void handleInput() {}
|
|
||||||
};
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
#include "SleepScreen.h"
|
|
||||||
|
|
||||||
#include <GfxRenderer.h>
|
|
||||||
|
|
||||||
#include "CrossPointSettings.h"
|
|
||||||
#include "config.h"
|
|
||||||
#include "images/CrossLarge.h"
|
|
||||||
|
|
||||||
void SleepScreen::onEnter() {
|
|
||||||
const auto pageWidth = GfxRenderer::getScreenWidth();
|
|
||||||
const auto pageHeight = GfxRenderer::getScreenHeight();
|
|
||||||
|
|
||||||
renderer.clearScreen();
|
|
||||||
renderer.drawImage(CrossLarge, (pageWidth - 128) / 2, (pageHeight - 128) / 2, 128, 128);
|
|
||||||
renderer.drawCenteredText(UI_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, BOLD);
|
|
||||||
renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "SLEEPING");
|
|
||||||
|
|
||||||
// Apply white screen if enabled in settings
|
|
||||||
if (!SETTINGS.whiteSleepScreen) {
|
|
||||||
renderer.invertScreen();
|
|
||||||
}
|
|
||||||
|
|
||||||
renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include "Screen.h"
|
|
||||||
|
|
||||||
class SleepScreen final : public Screen {
|
|
||||||
public:
|
|
||||||
explicit SleepScreen(GfxRenderer& renderer, InputManager& inputManager) : Screen(renderer, inputManager) {}
|
|
||||||
void onEnter() override;
|
|
||||||
};
|
|
||||||
Loading…
Reference in New Issue
Block a user