From bf3f270067edace892677aef309177abb981491a Mon Sep 17 00:00:00 2001
From: IFAKA <99131130+IFAKA@users.noreply.github.com>
Date: Sun, 21 Dec 2025 03:34:58 +0100
Subject: [PATCH 01/33] fix: add NULL checks for frameBuffer in GfxRenderer
(#79)
## Problem
`invertScreen()`, `storeBwBuffer()`, and `restoreBwBuffer()` dereference
`frameBuffer` without NULL validation. If the display isn't initialized,
these functions will crash.
## Fix
Add NULL checks before using `frameBuffer` in all three functions.
Follows the existing pattern from `drawPixel()` (line 11) which already
validates the pointer.
Changed `lib/GfxRenderer/GfxRenderer.cpp`.
## Test
- Follows existing validated pattern from `drawPixel()`
- No logic changes - only adds early return on NULL
- Manual device testing appreciated
---
lib/GfxRenderer/GfxRenderer.cpp | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp
index 19c959f..b1a98c7 100644
--- a/lib/GfxRenderer/GfxRenderer.cpp
+++ b/lib/GfxRenderer/GfxRenderer.cpp
@@ -183,6 +183,10 @@ void GfxRenderer::clearScreen(const uint8_t color) const { einkDisplay.clearScre
void GfxRenderer::invertScreen() const {
uint8_t* buffer = einkDisplay.getFrameBuffer();
+ if (!buffer) {
+ Serial.printf("[%lu] [GFX] !! No framebuffer in invertScreen\n", millis());
+ return;
+ }
for (int i = 0; i < EInkDisplay::BUFFER_SIZE; i++) {
buffer[i] = ~buffer[i];
}
@@ -256,6 +260,10 @@ void GfxRenderer::freeBwBufferChunks() {
*/
void GfxRenderer::storeBwBuffer() {
const uint8_t* frameBuffer = einkDisplay.getFrameBuffer();
+ if (!frameBuffer) {
+ Serial.printf("[%lu] [GFX] !! No framebuffer in storeBwBuffer\n", millis());
+ return;
+ }
// Allocate and copy each chunk
for (size_t i = 0; i < BW_BUFFER_NUM_CHUNKS; i++) {
@@ -306,6 +314,12 @@ void GfxRenderer::restoreBwBuffer() {
}
uint8_t* frameBuffer = einkDisplay.getFrameBuffer();
+ if (!frameBuffer) {
+ Serial.printf("[%lu] [GFX] !! No framebuffer in restoreBwBuffer\n", millis());
+ freeBwBufferChunks();
+ return;
+ }
+
for (size_t i = 0; i < BW_BUFFER_NUM_CHUNKS; i++) {
// Check if chunk is missing
if (!bwBufferChunks[i]) {
From cc86533e8674f3a9dbd4d32f399d4d455542bfba Mon Sep 17 00:00:00 2001
From: IFAKA <99131130+IFAKA@users.noreply.github.com>
Date: Sun, 21 Dec 2025 03:35:37 +0100
Subject: [PATCH 02/33] fix: add NULL check after malloc in readFileToMemory()
(#81)
## Problem
`readFileToMemory()` allocates an output buffer via `malloc()` at line
120 but doesn't check if allocation succeeds. On low memory, the NULL
pointer is passed to `fread()` causing a crash.
## Fix
Add NULL check after `malloc()` for the output buffer. Follows the
existing pattern already used for `deflatedData` at line 141.
Changed `lib/ZipFile/ZipFile.cpp`.
## Test
- Follows existing validated pattern from same function
- Defensive addition only - no logic changes
---
lib/ZipFile/ZipFile.cpp | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/lib/ZipFile/ZipFile.cpp b/lib/ZipFile/ZipFile.cpp
index f55bb85..11ce211 100644
--- a/lib/ZipFile/ZipFile.cpp
+++ b/lib/ZipFile/ZipFile.cpp
@@ -118,6 +118,11 @@ uint8_t* ZipFile::readFileToMemory(const char* filename, size_t* size, const boo
const auto inflatedDataSize = static_cast(fileStat.m_uncomp_size);
const auto dataSize = trailingNullByte ? inflatedDataSize + 1 : inflatedDataSize;
const auto data = static_cast(malloc(dataSize));
+ if (data == nullptr) {
+ Serial.printf("[%lu] [ZIP] Failed to allocate memory for output buffer (%zu bytes)\n", millis(), dataSize);
+ fclose(file);
+ return nullptr;
+ }
if (fileStat.m_method == MZ_NO_COMPRESSION) {
// no deflation, just read content
From 73d1839ddddee63df97c3af1d012bb96b7c43bf3 Mon Sep 17 00:00:00 2001
From: IFAKA <99131130+IFAKA@users.noreply.github.com>
Date: Sun, 21 Dec 2025 03:36:30 +0100
Subject: [PATCH 03/33] fix: add bounds checks to Epub getter functions (#82)
## Problem
Three Epub getter functions can throw exceptions:
- `getCumulativeSpineItemSize()`: No bounds check before
`.at(spineIndex)`
- `getSpineItem()`: If spine is empty and index invalid, `.at(0)` throws
- `getTocItem()`: If toc is empty and index invalid, `.at(0)` throws
## Fix
- Add bounds check to `getCumulativeSpineItemSize()`, return 0 on error
- Add empty container checks to `getSpineItem()` and `getTocItem()`
- Use static fallback objects for safe reference returns on empty
containers
Changed `lib/Epub/Epub.cpp`.
## Test
- Defensive additions - follows existing bounds check patterns
- No logic changes for valid inputs
- Manual device testing appreciated
---
lib/Epub/Epub.cpp | 22 +++++++++++++++++++---
1 file changed, 19 insertions(+), 3 deletions(-)
diff --git a/lib/Epub/Epub.cpp b/lib/Epub/Epub.cpp
index a3edac8..8d3e4ad 100644
--- a/lib/Epub/Epub.cpp
+++ b/lib/Epub/Epub.cpp
@@ -298,10 +298,21 @@ bool Epub::getItemSize(const std::string& itemHref, size_t* size) const {
int Epub::getSpineItemsCount() const { return spine.size(); }
-size_t Epub::getCumulativeSpineItemSize(const int spineIndex) const { return cumulativeSpineItemSize.at(spineIndex); }
+size_t Epub::getCumulativeSpineItemSize(const int spineIndex) const {
+ if (spineIndex < 0 || spineIndex >= static_cast(cumulativeSpineItemSize.size())) {
+ Serial.printf("[%lu] [EBP] getCumulativeSpineItemSize index:%d is out of range\n", millis(), spineIndex);
+ return 0;
+ }
+ return cumulativeSpineItemSize.at(spineIndex);
+}
std::string& Epub::getSpineItem(const int spineIndex) {
- if (spineIndex < 0 || spineIndex >= spine.size()) {
+ static std::string emptyString;
+ if (spine.empty()) {
+ Serial.printf("[%lu] [EBP] getSpineItem called but spine is empty\n", millis());
+ return emptyString;
+ }
+ if (spineIndex < 0 || spineIndex >= static_cast(spine.size())) {
Serial.printf("[%lu] [EBP] getSpineItem index:%d is out of range\n", millis(), spineIndex);
return spine.at(0).second;
}
@@ -310,7 +321,12 @@ std::string& Epub::getSpineItem(const int spineIndex) {
}
EpubTocEntry& Epub::getTocItem(const int tocTndex) {
- if (tocTndex < 0 || tocTndex >= toc.size()) {
+ static EpubTocEntry emptyEntry("", "", "", 0);
+ if (toc.empty()) {
+ Serial.printf("[%lu] [EBP] getTocItem called but toc is empty\n", millis());
+ return emptyEntry;
+ }
+ if (tocTndex < 0 || tocTndex >= static_cast(toc.size())) {
Serial.printf("[%lu] [EBP] getTocItem index:%d is out of range\n", millis(), tocTndex);
return toc.at(0);
}
From 9a3bb81337becf05c32d6638769d0438aa8f07a8 Mon Sep 17 00:00:00 2001
From: IFAKA <99131130+IFAKA@users.noreply.github.com>
Date: Sun, 21 Dec 2025 03:36:59 +0100
Subject: [PATCH 04/33] fix: add NULL checks after malloc in drawBmp() (#80)
## Problem
`drawBmp()` allocates two row buffers via `malloc()` but doesn't check
if allocations succeed. On low memory, this causes a crash when the NULL
pointers are dereferenced.
## Fix
Add NULL check after both `malloc()` calls. If either fails, log error
and return early.
Changed `lib/GfxRenderer/GfxRenderer.cpp`.
## Test
- Defensive addition only - no logic changes
- Manual device testing appreciated
---
lib/GfxRenderer/GfxRenderer.cpp | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp
index b1a98c7..a4b9369 100644
--- a/lib/GfxRenderer/GfxRenderer.cpp
+++ b/lib/GfxRenderer/GfxRenderer.cpp
@@ -136,6 +136,13 @@ void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, con
auto* outputRow = static_cast(malloc(outputRowSize));
auto* rowBytes = static_cast(malloc(bitmap.getRowBytes()));
+ if (!outputRow || !rowBytes) {
+ Serial.printf("[%lu] [GFX] !! Failed to allocate BMP row buffers\n", millis());
+ free(outputRow);
+ free(rowBytes);
+ return;
+ }
+
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.
From 299623927ea9d67fe371a56f78142b17b7e70fd9 Mon Sep 17 00:00:00 2001
From: Dave Allie
Date: Sun, 21 Dec 2025 13:43:19 +1100
Subject: [PATCH 05/33] Build out lines when parsing html and holding >750
words in buffer (#73)
## Summary
* Build out lines for pages when holding over 750 buffered words
* Should fix issues with parsing long blocks of text causing memory
crashes
---
lib/Epub/Epub/ParsedText.cpp | 139 ++++++++++--------
lib/Epub/Epub/ParsedText.h | 12 +-
.../Epub/parsers/ChapterHtmlSlimParser.cpp | 11 ++
3 files changed, 96 insertions(+), 66 deletions(-)
diff --git a/lib/Epub/Epub/ParsedText.cpp b/lib/Epub/Epub/ParsedText.cpp
index 3747246..eff3fd6 100644
--- a/lib/Epub/Epub/ParsedText.cpp
+++ b/lib/Epub/Epub/ParsedText.cpp
@@ -19,14 +19,25 @@ void ParsedText::addWord(std::string word, const EpdFontStyle fontStyle) {
// Consumes data to minimize memory usage
void ParsedText::layoutAndExtractLines(const GfxRenderer& renderer, const int fontId, const int horizontalMargin,
- const std::function)>& processLine) {
+ const std::function)>& processLine,
+ const bool includeLastLine) {
if (words.empty()) {
return;
}
- const size_t totalWordCount = words.size();
const int pageWidth = renderer.getScreenWidth() - horizontalMargin;
const int spaceWidth = renderer.getSpaceWidth(fontId);
+ const auto wordWidths = calculateWordWidths(renderer, fontId);
+ const auto lineBreakIndices = computeLineBreaks(pageWidth, spaceWidth, wordWidths);
+ const size_t lineCount = includeLastLine ? lineBreakIndices.size() : lineBreakIndices.size() - 1;
+
+ for (size_t i = 0; i < lineCount; ++i) {
+ extractLine(i, pageWidth, spaceWidth, wordWidths, lineBreakIndices, processLine);
+ }
+}
+
+std::vector ParsedText::calculateWordWidths(const GfxRenderer& renderer, const int fontId) {
+ const size_t totalWordCount = words.size();
std::vector wordWidths;
wordWidths.reserve(totalWordCount);
@@ -47,6 +58,13 @@ void ParsedText::layoutAndExtractLines(const GfxRenderer& renderer, const int fo
std::advance(wordStylesIt, 1);
}
+ return wordWidths;
+}
+
+std::vector ParsedText::computeLineBreaks(const int pageWidth, const int spaceWidth,
+ const std::vector& wordWidths) const {
+ const size_t totalWordCount = words.size();
+
// DP table to store the minimum badness (cost) of lines starting at index i
std::vector dp(totalWordCount);
// 'ans[i]' stores the index 'j' of the *last word* in the optimal line starting at 'i'
@@ -106,66 +124,59 @@ void ParsedText::layoutAndExtractLines(const GfxRenderer& renderer, const int fo
currentWordIndex = nextBreakIndex;
}
- // Initialize iterators for consumption
- auto wordStartIt = words.begin();
- auto wordStyleStartIt = wordStyles.begin();
- size_t wordWidthIndex = 0;
-
- size_t lastBreakAt = 0;
- for (const size_t lineBreak : lineBreakIndices) {
- const size_t lineWordCount = lineBreak - lastBreakAt;
-
- // Calculate end iterators for the range to splice
- auto wordEndIt = wordStartIt;
- auto wordStyleEndIt = wordStyleStartIt;
- std::advance(wordEndIt, lineWordCount);
- std::advance(wordStyleEndIt, lineWordCount);
-
- // Calculate total word width for this line
- int lineWordWidthSum = 0;
- for (size_t i = 0; i < lineWordCount; ++i) {
- lineWordWidthSum += wordWidths[wordWidthIndex + i];
- }
-
- // Calculate spacing
- int spareSpace = pageWidth - lineWordWidthSum;
-
- int spacing = spaceWidth;
- const bool isLastLine = lineBreak == totalWordCount;
-
- if (style == TextBlock::JUSTIFIED && !isLastLine && lineWordCount >= 2) {
- spacing = spareSpace / (lineWordCount - 1);
- }
-
- // Calculate initial x position
- uint16_t xpos = 0;
- if (style == TextBlock::RIGHT_ALIGN) {
- xpos = spareSpace - (lineWordCount - 1) * spaceWidth;
- } else if (style == TextBlock::CENTER_ALIGN) {
- xpos = (spareSpace - (lineWordCount - 1) * spaceWidth) / 2;
- }
-
- // Pre-calculate X positions for words
- std::list lineXPos;
- for (size_t i = 0; i < lineWordCount; ++i) {
- const uint16_t currentWordWidth = wordWidths[wordWidthIndex + i];
- lineXPos.push_back(xpos);
- xpos += currentWordWidth + spacing;
- }
-
- // *** CRITICAL STEP: CONSUME DATA USING SPLICE ***
- std::list lineWords;
- lineWords.splice(lineWords.begin(), words, wordStartIt, wordEndIt);
- std::list lineWordStyles;
- lineWordStyles.splice(lineWordStyles.begin(), wordStyles, wordStyleStartIt, wordStyleEndIt);
-
- processLine(
- std::make_shared(std::move(lineWords), std::move(lineXPos), std::move(lineWordStyles), style));
-
- // Update pointers/indices for the next line
- wordStartIt = wordEndIt;
- wordStyleStartIt = wordStyleEndIt;
- wordWidthIndex += lineWordCount;
- lastBreakAt = lineBreak;
- }
+ return lineBreakIndices;
+}
+
+void ParsedText::extractLine(const size_t breakIndex, const int pageWidth, const int spaceWidth,
+ const std::vector& wordWidths, const std::vector& lineBreakIndices,
+ const std::function)>& processLine) {
+ const size_t lineBreak = lineBreakIndices[breakIndex];
+ const size_t lastBreakAt = breakIndex > 0 ? lineBreakIndices[breakIndex - 1] : 0;
+ const size_t lineWordCount = lineBreak - lastBreakAt;
+
+ // Calculate total word width for this line
+ int lineWordWidthSum = 0;
+ for (size_t i = lastBreakAt; i < lineBreak; i++) {
+ lineWordWidthSum += wordWidths[i];
+ }
+
+ // Calculate spacing
+ const int spareSpace = pageWidth - lineWordWidthSum;
+
+ int spacing = spaceWidth;
+ const bool isLastLine = lineBreak == words.size();
+
+ if (style == TextBlock::JUSTIFIED && !isLastLine && lineWordCount >= 2) {
+ spacing = spareSpace / (lineWordCount - 1);
+ }
+
+ // Calculate initial x position
+ uint16_t xpos = 0;
+ if (style == TextBlock::RIGHT_ALIGN) {
+ xpos = spareSpace - (lineWordCount - 1) * spaceWidth;
+ } else if (style == TextBlock::CENTER_ALIGN) {
+ xpos = (spareSpace - (lineWordCount - 1) * spaceWidth) / 2;
+ }
+
+ // Pre-calculate X positions for words
+ std::list lineXPos;
+ for (size_t i = lastBreakAt; i < lineBreak; i++) {
+ const uint16_t currentWordWidth = wordWidths[i];
+ lineXPos.push_back(xpos);
+ xpos += currentWordWidth + spacing;
+ }
+
+ // Iterators always start at the beginning as we are moving content with splice below
+ auto wordEndIt = words.begin();
+ auto wordStyleEndIt = wordStyles.begin();
+ std::advance(wordEndIt, lineWordCount);
+ std::advance(wordStyleEndIt, lineWordCount);
+
+ // *** CRITICAL STEP: CONSUME DATA USING SPLICE ***
+ std::list lineWords;
+ lineWords.splice(lineWords.begin(), words, words.begin(), wordEndIt);
+ std::list lineWordStyles;
+ lineWordStyles.splice(lineWordStyles.begin(), wordStyles, wordStyles.begin(), wordStyleEndIt);
+
+ processLine(std::make_shared(std::move(lineWords), std::move(lineXPos), std::move(lineWordStyles), style));
}
diff --git a/lib/Epub/Epub/ParsedText.h b/lib/Epub/Epub/ParsedText.h
index 0bd2544..7fdb128 100644
--- a/lib/Epub/Epub/ParsedText.h
+++ b/lib/Epub/Epub/ParsedText.h
@@ -2,11 +2,11 @@
#include
-#include
#include
#include
#include
#include
+#include
#include "blocks/TextBlock.h"
@@ -18,6 +18,12 @@ class ParsedText {
TextBlock::BLOCK_STYLE style;
bool extraParagraphSpacing;
+ std::vector computeLineBreaks(int pageWidth, int spaceWidth, const std::vector& wordWidths) const;
+ void extractLine(size_t breakIndex, int pageWidth, int spaceWidth, const std::vector& wordWidths,
+ const std::vector& lineBreakIndices,
+ const std::function)>& processLine);
+ std::vector calculateWordWidths(const GfxRenderer& renderer, int fontId);
+
public:
explicit ParsedText(const TextBlock::BLOCK_STYLE style, const bool extraParagraphSpacing)
: style(style), extraParagraphSpacing(extraParagraphSpacing) {}
@@ -26,7 +32,9 @@ class ParsedText {
void addWord(std::string word, EpdFontStyle fontStyle);
void setStyle(const TextBlock::BLOCK_STYLE style) { this->style = style; }
TextBlock::BLOCK_STYLE getStyle() const { return style; }
+ size_t size() const { return words.size(); }
bool isEmpty() const { return words.empty(); }
void layoutAndExtractLines(const GfxRenderer& renderer, int fontId, int horizontalMargin,
- const std::function)>& processLine);
+ const std::function)>& processLine,
+ bool includeLastLine = true);
};
diff --git a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
index d4edc33..718f4d7 100644
--- a/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
+++ b/lib/Epub/Epub/parsers/ChapterHtmlSlimParser.cpp
@@ -143,6 +143,17 @@ void XMLCALL ChapterHtmlSlimParser::characterData(void* userData, const XML_Char
self->partWordBuffer[self->partWordBufferIndex++] = s[i];
}
+
+ // If we have > 750 words buffered up, perform the layout and consume out all but the last line
+ // There should be enough here to build out 1-2 full pages and doing this will free up a lot of
+ // memory.
+ // Spotted when reading Intermezzo, there are some really long text blocks in there.
+ if (self->currentTextBlock->size() > 750) {
+ Serial.printf("[%lu] [EHP] Text block too long, splitting into multiple pages\n", millis());
+ self->currentTextBlock->layoutAndExtractLines(
+ self->renderer, self->fontId, self->marginLeft + self->marginRight,
+ [self](const std::shared_ptr& textBlock) { self->addLineToPage(textBlock); }, false);
+ }
}
void XMLCALL ChapterHtmlSlimParser::endElement(void* userData, const XML_Char* name) {
From 926c7867055c22ce70c9957a38e47da49ee838f1 Mon Sep 17 00:00:00 2001
From: Jonas Diemer
Date: Sun, 21 Dec 2025 04:38:51 +0100
Subject: [PATCH 06/33] Keep ZipFile open to speed up getting file stats. (#76)
Still a bit raw, but gets the time required to determine the size of
each chapter (for reading progress) down from ~25ms to 0-1ms.
This is done by keeping the zipArchive open (so simple ;)).
Probably we don't need to cache the spine sizes anymore then...
---------
Co-authored-by: Dave Allie
---
lib/Epub/Epub.cpp | 50 ++++++++++++-----------------------------
lib/Epub/Epub.h | 1 +
lib/ZipFile/ZipFile.cpp | 13 +++++------
lib/ZipFile/ZipFile.h | 6 ++---
4 files changed, 23 insertions(+), 47 deletions(-)
diff --git a/lib/Epub/Epub.cpp b/lib/Epub/Epub.cpp
index 8d3e4ad..0b55750 100644
--- a/lib/Epub/Epub.cpp
+++ b/lib/Epub/Epub.cpp
@@ -126,7 +126,6 @@ bool Epub::parseTocNcxFile() {
// load in the meta data for the epub file
bool Epub::load() {
Serial.printf("[%lu] [EBP] Loading ePub: %s\n", millis(), filepath.c_str());
- ZipFile zip("/sd" + filepath);
std::string contentOpfFilePath;
if (!findContentOpfFile(&contentOpfFilePath)) {
@@ -155,44 +154,20 @@ bool Epub::load() {
}
void Epub::initializeSpineItemSizes() {
- setupCacheDir();
+ Serial.printf("[%lu] [EBP] Calculating book size\n", millis());
- size_t spineItemsCount = getSpineItemsCount();
+ const 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);
+ const ZipFile zip("/sd" + filepath);
- // 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();
+ for (size_t i = 0; i < spineItemsCount; i++) {
+ std::string spineItem = getSpineItem(i);
+ size_t s = 0;
+ getItemSize(zip, spineItem, &s);
+ cumSpineItemSize += s;
+ cumulativeSpineItemSize.emplace_back(cumSpineItemSize);
}
+
Serial.printf("[%lu] [EBP] Book size: %lu\n", millis(), cumSpineItemSize);
}
@@ -291,8 +266,11 @@ bool Epub::readItemContentsToStream(const std::string& itemHref, Print& out, con
bool Epub::getItemSize(const std::string& itemHref, size_t* size) const {
const ZipFile zip("/sd" + filepath);
- const std::string path = normalisePath(itemHref);
+ return getItemSize(zip, itemHref, size);
+}
+bool Epub::getItemSize(const ZipFile& zip, const std::string& itemHref, size_t* size) {
+ const std::string path = normalisePath(itemHref);
return zip.getInflatedFileSize(path.c_str(), size);
}
diff --git a/lib/Epub/Epub.h b/lib/Epub/Epub.h
index f22b630..3115303 100644
--- a/lib/Epub/Epub.h
+++ b/lib/Epub/Epub.h
@@ -33,6 +33,7 @@ class Epub {
bool parseContentOpf(const std::string& contentOpfFilePath);
bool parseTocNcxFile();
void initializeSpineItemSizes();
+ static bool getItemSize(const ZipFile& zip, const std::string& itemHref, size_t* size);
public:
explicit Epub(std::string filepath, const std::string& cacheDir) : filepath(std::move(filepath)) {
diff --git a/lib/ZipFile/ZipFile.cpp b/lib/ZipFile/ZipFile.cpp
index 11ce211..83b1184 100644
--- a/lib/ZipFile/ZipFile.cpp
+++ b/lib/ZipFile/ZipFile.cpp
@@ -27,31 +27,28 @@ bool inflateOneShot(const uint8_t* inputBuf, const size_t deflatedSize, uint8_t*
return true;
}
-bool ZipFile::loadFileStat(const char* filename, mz_zip_archive_file_stat* fileStat) const {
- mz_zip_archive zipArchive = {};
- const bool status = mz_zip_reader_init_file(&zipArchive, filePath.c_str(), 0);
+ZipFile::ZipFile(std::string filePath) : filePath(std::move(filePath)) {
+ const bool status = mz_zip_reader_init_file(&zipArchive, this->filePath.c_str(), 0);
if (!status) {
- Serial.printf("[%lu] [ZIP] mz_zip_reader_init_file() failed! Error: %s\n", millis(),
+ Serial.printf("[%lu] [ZIP] mz_zip_reader_init_file() failed for %s! Error: %s\n", millis(), this->filePath.c_str(),
mz_zip_get_error_string(zipArchive.m_last_error));
- return false;
}
+}
+bool ZipFile::loadFileStat(const char* filename, mz_zip_archive_file_stat* fileStat) const {
// find the file
mz_uint32 fileIndex = 0;
if (!mz_zip_reader_locate_file_v2(&zipArchive, filename, nullptr, 0, &fileIndex)) {
Serial.printf("[%lu] [ZIP] Could not find file %s\n", millis(), filename);
- mz_zip_reader_end(&zipArchive);
return false;
}
if (!mz_zip_reader_file_stat(&zipArchive, fileIndex, fileStat)) {
Serial.printf("[%lu] [ZIP] mz_zip_reader_file_stat() failed! Error: %s\n", millis(),
mz_zip_get_error_string(zipArchive.m_last_error));
- mz_zip_reader_end(&zipArchive);
return false;
}
- mz_zip_reader_end(&zipArchive);
return true;
}
diff --git a/lib/ZipFile/ZipFile.h b/lib/ZipFile/ZipFile.h
index e452ec5..58e3ab9 100644
--- a/lib/ZipFile/ZipFile.h
+++ b/lib/ZipFile/ZipFile.h
@@ -1,19 +1,19 @@
#pragma once
#include
-#include
#include
#include "miniz.h"
class ZipFile {
std::string filePath;
+ mutable mz_zip_archive zipArchive = {};
bool loadFileStat(const char* filename, mz_zip_archive_file_stat* fileStat) const;
long getDataOffset(const mz_zip_archive_file_stat& fileStat) const;
public:
- explicit ZipFile(std::string filePath) : filePath(std::move(filePath)) {}
- ~ZipFile() = default;
+ explicit ZipFile(std::string filePath);
+ ~ZipFile() { mz_zip_reader_end(&zipArchive); }
bool getInflatedFileSize(const char* filename, size_t* size) const;
uint8_t* readFileToMemory(const char* filename, size_t* size = nullptr, bool trailingNullByte = false) const;
bool readFileToStream(const char* filename, Print& out, size_t chunkSize) const;
From 9b4dfbd1808ea723f1d60632ca332ff1f4c6d266 Mon Sep 17 00:00:00 2001
From: Dave Allie
Date: Sun, 21 Dec 2025 15:43:17 +1100
Subject: [PATCH 07/33] Allow any file to be uploaded (#84)
## Summary
- Allow any file to be uploaded
- Removes .epub restriction
## Additional Context
- Fixes #74
---
.../network/server/CrossPointWebServer.cpp | 7 --
src/html/FilesPageFooter.html | 96 ++++++++-----------
2 files changed, 39 insertions(+), 64 deletions(-)
diff --git a/src/activities/network/server/CrossPointWebServer.cpp b/src/activities/network/server/CrossPointWebServer.cpp
index 61a8813..90ec915 100644
--- a/src/activities/network/server/CrossPointWebServer.cpp
+++ b/src/activities/network/server/CrossPointWebServer.cpp
@@ -513,13 +513,6 @@ void CrossPointWebServer::handleUpload() {
Serial.printf("[%lu] [WEB] [UPLOAD] START: %s to path: %s\n", millis(), uploadFileName.c_str(), uploadPath.c_str());
Serial.printf("[%lu] [WEB] [UPLOAD] Free heap: %d bytes\n", millis(), ESP.getFreeHeap());
- // Validate file extension
- if (!isEpubFile(uploadFileName)) {
- uploadError = "Only .epub files are allowed";
- Serial.printf("[%lu] [WEB] [UPLOAD] REJECTED - not an epub file\n", millis());
- return;
- }
-
// Create file path
String filePath = uploadPath;
if (!filePath.endsWith("/")) filePath += "/";
diff --git a/src/html/FilesPageFooter.html b/src/html/FilesPageFooter.html
index 102430a..961753a 100644
--- a/src/html/FilesPageFooter.html
+++ b/src/html/FilesPageFooter.html
@@ -3,15 +3,15 @@
CrossPoint E-Reader • Open Source
-
+
-
📤 Upload eBook
+
📤 Upload file
-
+
-
+
-
+
From 0d32d21d756c5b45b5a7b3b3d0a29429763387ff Mon Sep 17 00:00:00 2001
From: Dave Allie
Date: Sun, 21 Dec 2025 15:43:53 +1100
Subject: [PATCH 08/33] Small code cleanup (#83)
## Summary
* Fix cppcheck low violations
* Remove teardown method on parsers, use destructor
* Code cleanup
---
.github/workflows/ci.yml | 2 +-
lib/Epub/Epub.cpp | 14 ++----
lib/Epub/Epub/Section.h | 7 +--
lib/Epub/Epub/parsers/ContainerParser.cpp | 3 +-
lib/Epub/Epub/parsers/ContainerParser.h | 2 +-
lib/Epub/Epub/parsers/ContentOpfParser.cpp | 5 +--
lib/Epub/Epub/parsers/ContentOpfParser.h | 2 +-
lib/Epub/Epub/parsers/TocNcxParser.cpp | 4 +-
lib/Epub/Epub/parsers/TocNcxParser.h | 2 +-
platformio.ini | 2 +-
src/WifiCredentialStore.cpp | 34 ++++++++-------
src/activities/boot_sleep/SleepActivity.cpp | 2 +-
.../network/CrossPointWebServerActivity.cpp | 5 +--
.../network/WifiSelectionActivity.cpp | 43 +++++++++----------
.../network/server/CrossPointWebServer.cpp | 4 +-
src/activities/reader/EpubReaderActivity.cpp | 2 +-
.../reader/FileSelectionActivity.cpp | 2 +-
src/activities/util/KeyboardEntryActivity.cpp | 2 +-
src/main.cpp | 19 +++-----
19 files changed, 70 insertions(+), 86 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c090791..b1d91ec 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -34,7 +34,7 @@ jobs:
sudo apt-get install -y clang-format-21
- name: Run cppcheck
- run: pio check --fail-on-defect medium --fail-on-defect high
+ run: pio check --fail-on-defect low --fail-on-defect medium --fail-on-defect high
- name: Run clang-format
run: PATH="/usr/lib/llvm-21/bin:$PATH" ./bin/clang-format-fix && git diff --exit-code || (echo "Please run 'bin/clang-format-fix' to fix formatting issues" && exit 1)
diff --git a/lib/Epub/Epub.cpp b/lib/Epub/Epub.cpp
index 0b55750..2df5a3f 100644
--- a/lib/Epub/Epub.cpp
+++ b/lib/Epub/Epub.cpp
@@ -30,24 +30,22 @@ bool Epub::findContentOpfFile(std::string* contentOpfFile) const {
// Stream read (reusing your existing stream logic)
if (!readItemContentsToStream(containerPath, containerParser, 512)) {
Serial.printf("[%lu] [EBP] Could not read META-INF/container.xml\n", millis());
- containerParser.teardown();
return false;
}
// Extract the result
if (containerParser.fullPath.empty()) {
Serial.printf("[%lu] [EBP] Could not find valid rootfile in container.xml\n", millis());
- containerParser.teardown();
return false;
}
*contentOpfFile = std::move(containerParser.fullPath);
-
- containerParser.teardown();
return true;
}
bool Epub::parseContentOpf(const std::string& contentOpfFilePath) {
+ Serial.printf("[%lu] [EBP] Parsing content.opf: %s\n", millis(), contentOpfFilePath.c_str());
+
size_t contentOpfSize;
if (!getItemSize(contentOpfFilePath, &contentOpfSize)) {
Serial.printf("[%lu] [EBP] Could not get size of content.opf\n", millis());
@@ -63,7 +61,6 @@ bool Epub::parseContentOpf(const std::string& contentOpfFilePath) {
if (!readItemContentsToStream(contentOpfFilePath, opfParser, 1024)) {
Serial.printf("[%lu] [EBP] Could not read content.opf\n", millis());
- opfParser.teardown();
return false;
}
@@ -84,8 +81,6 @@ bool Epub::parseContentOpf(const std::string& contentOpfFilePath) {
}
Serial.printf("[%lu] [EBP] Successfully parsed content.opf\n", millis());
-
- opfParser.teardown();
return true;
}
@@ -96,6 +91,8 @@ bool Epub::parseTocNcxFile() {
return false;
}
+ Serial.printf("[%lu] [EBP] Parsing toc ncx file: %s\n", millis(), tocNcxItem.c_str());
+
size_t tocSize;
if (!getItemSize(tocNcxItem, &tocSize)) {
Serial.printf("[%lu] [EBP] Could not get size of toc ncx\n", millis());
@@ -111,15 +108,12 @@ bool Epub::parseTocNcxFile() {
if (!readItemContentsToStream(tocNcxItem, ncxParser, 1024)) {
Serial.printf("[%lu] [EBP] Could not read toc ncx stream\n", millis());
- ncxParser.teardown();
return false;
}
this->toc = std::move(ncxParser.toc);
Serial.printf("[%lu] [EBP] Parsed %d TOC items\n", millis(), this->toc.size());
-
- ncxParser.teardown();
return true;
}
diff --git a/lib/Epub/Epub/Section.h b/lib/Epub/Epub/Section.h
index 35a17df..d7a2c72 100644
--- a/lib/Epub/Epub/Section.h
+++ b/lib/Epub/Epub/Section.h
@@ -21,9 +21,10 @@ class Section {
int currentPage = 0;
explicit Section(const std::shared_ptr& epub, const int spineIndex, GfxRenderer& renderer)
- : epub(epub), spineIndex(spineIndex), renderer(renderer) {
- cachePath = epub->getCachePath() + "/" + std::to_string(spineIndex);
- }
+ : epub(epub),
+ spineIndex(spineIndex),
+ renderer(renderer),
+ cachePath(epub->getCachePath() + "/" + std::to_string(spineIndex)) {}
~Section() = default;
bool loadCacheMetadata(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom,
int marginLeft, bool extraParagraphSpacing);
diff --git a/lib/Epub/Epub/parsers/ContainerParser.cpp b/lib/Epub/Epub/parsers/ContainerParser.cpp
index b7ff5d1..db126f2 100644
--- a/lib/Epub/Epub/parsers/ContainerParser.cpp
+++ b/lib/Epub/Epub/parsers/ContainerParser.cpp
@@ -14,12 +14,11 @@ bool ContainerParser::setup() {
return true;
}
-bool ContainerParser::teardown() {
+ContainerParser::~ContainerParser() {
if (parser) {
XML_ParserFree(parser);
parser = nullptr;
}
- return true;
}
size_t ContainerParser::write(const uint8_t data) { return write(&data, 1); }
diff --git a/lib/Epub/Epub/parsers/ContainerParser.h b/lib/Epub/Epub/parsers/ContainerParser.h
index 07e28ab..3951775 100644
--- a/lib/Epub/Epub/parsers/ContainerParser.h
+++ b/lib/Epub/Epub/parsers/ContainerParser.h
@@ -23,9 +23,9 @@ class ContainerParser final : public Print {
std::string fullPath;
explicit ContainerParser(const size_t xmlSize) : remainingSize(xmlSize) {}
+ ~ContainerParser() override;
bool setup();
- bool teardown();
size_t write(uint8_t) override;
size_t write(const uint8_t* buffer, size_t size) override;
diff --git a/lib/Epub/Epub/parsers/ContentOpfParser.cpp b/lib/Epub/Epub/parsers/ContentOpfParser.cpp
index 472d76c..5aa7303 100644
--- a/lib/Epub/Epub/parsers/ContentOpfParser.cpp
+++ b/lib/Epub/Epub/parsers/ContentOpfParser.cpp
@@ -4,7 +4,7 @@
#include
namespace {
-constexpr const char MEDIA_TYPE_NCX[] = "application/x-dtbncx+xml";
+constexpr char MEDIA_TYPE_NCX[] = "application/x-dtbncx+xml";
}
bool ContentOpfParser::setup() {
@@ -20,12 +20,11 @@ bool ContentOpfParser::setup() {
return true;
}
-bool ContentOpfParser::teardown() {
+ContentOpfParser::~ContentOpfParser() {
if (parser) {
XML_ParserFree(parser);
parser = nullptr;
}
- return true;
}
size_t ContentOpfParser::write(const uint8_t data) { return write(&data, 1); }
diff --git a/lib/Epub/Epub/parsers/ContentOpfParser.h b/lib/Epub/Epub/parsers/ContentOpfParser.h
index cba4551..a3070fc 100644
--- a/lib/Epub/Epub/parsers/ContentOpfParser.h
+++ b/lib/Epub/Epub/parsers/ContentOpfParser.h
@@ -34,9 +34,9 @@ class ContentOpfParser final : public Print {
explicit ContentOpfParser(const std::string& baseContentPath, const size_t xmlSize)
: baseContentPath(baseContentPath), remainingSize(xmlSize) {}
+ ~ContentOpfParser() override;
bool setup();
- bool teardown();
size_t write(uint8_t) override;
size_t write(const uint8_t* buffer, size_t size) override;
diff --git a/lib/Epub/Epub/parsers/TocNcxParser.cpp b/lib/Epub/Epub/parsers/TocNcxParser.cpp
index f02d7c4..4d541f5 100644
--- a/lib/Epub/Epub/parsers/TocNcxParser.cpp
+++ b/lib/Epub/Epub/parsers/TocNcxParser.cpp
@@ -1,5 +1,6 @@
#include "TocNcxParser.h"
+#include
#include
bool TocNcxParser::setup() {
@@ -15,12 +16,11 @@ bool TocNcxParser::setup() {
return true;
}
-bool TocNcxParser::teardown() {
+TocNcxParser::~TocNcxParser() {
if (parser) {
XML_ParserFree(parser);
parser = nullptr;
}
- return true;
}
size_t TocNcxParser::write(const uint8_t data) { return write(&data, 1); }
diff --git a/lib/Epub/Epub/parsers/TocNcxParser.h b/lib/Epub/Epub/parsers/TocNcxParser.h
index 6217f3f..5d5df0b 100644
--- a/lib/Epub/Epub/parsers/TocNcxParser.h
+++ b/lib/Epub/Epub/parsers/TocNcxParser.h
@@ -28,9 +28,9 @@ class TocNcxParser final : public Print {
explicit TocNcxParser(const std::string& baseContentPath, const size_t xmlSize)
: baseContentPath(baseContentPath), remainingSize(xmlSize) {}
+ ~TocNcxParser() override;
bool setup();
- bool teardown();
size_t write(uint8_t) override;
size_t write(const uint8_t* buffer, size_t size) override;
diff --git a/platformio.ini b/platformio.ini
index 4f71afe..0f92380 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -9,8 +9,8 @@ framework = arduino
monitor_speed = 115200
upload_speed = 921600
check_tool = cppcheck
+check_flags = --enable=all --suppress=missingIncludeSystem --suppress=unusedFunction --suppress=unmatchedSuppression --inline-suppr
check_skip_packages = yes
-check_severity = medium, high
board_upload.flash_size = 16MB
board_upload.maximum_size = 16777216
diff --git a/src/WifiCredentialStore.cpp b/src/WifiCredentialStore.cpp
index ec9d955..7df9e2f 100644
--- a/src/WifiCredentialStore.cpp
+++ b/src/WifiCredentialStore.cpp
@@ -111,12 +111,12 @@ bool WifiCredentialStore::loadFromFile() {
bool WifiCredentialStore::addCredential(const std::string& ssid, const std::string& password) {
// Check if this SSID already exists and update it
- for (auto& cred : credentials) {
- if (cred.ssid == ssid) {
- cred.password = password;
- Serial.printf("[%lu] [WCS] Updated credentials for: %s\n", millis(), ssid.c_str());
- return saveToFile();
- }
+ const auto cred = find_if(credentials.begin(), credentials.end(),
+ [&ssid](const WifiCredential& cred) { return cred.ssid == ssid; });
+ if (cred != credentials.end()) {
+ cred->password = password;
+ Serial.printf("[%lu] [WCS] Updated credentials for: %s\n", millis(), ssid.c_str());
+ return saveToFile();
}
// Check if we've reached the limit
@@ -132,22 +132,24 @@ bool WifiCredentialStore::addCredential(const std::string& ssid, const std::stri
}
bool WifiCredentialStore::removeCredential(const std::string& ssid) {
- for (auto it = credentials.begin(); it != credentials.end(); ++it) {
- if (it->ssid == ssid) {
- credentials.erase(it);
- Serial.printf("[%lu] [WCS] Removed credentials for: %s\n", millis(), ssid.c_str());
- return saveToFile();
- }
+ const auto cred = find_if(credentials.begin(), credentials.end(),
+ [&ssid](const WifiCredential& cred) { return cred.ssid == ssid; });
+ if (cred != credentials.end()) {
+ credentials.erase(cred);
+ Serial.printf("[%lu] [WCS] Removed credentials for: %s\n", millis(), ssid.c_str());
+ return saveToFile();
}
return false; // Not found
}
const WifiCredential* WifiCredentialStore::findCredential(const std::string& ssid) const {
- for (const auto& cred : credentials) {
- if (cred.ssid == ssid) {
- return &cred;
- }
+ const auto cred = find_if(credentials.begin(), credentials.end(),
+ [&ssid](const WifiCredential& cred) { return cred.ssid == ssid; });
+
+ if (cred != credentials.end()) {
+ return &*cred;
}
+
return nullptr;
}
diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp
index 3b36990..900a0db 100644
--- a/src/activities/boot_sleep/SleepActivity.cpp
+++ b/src/activities/boot_sleep/SleepActivity.cpp
@@ -1,11 +1,11 @@
#include "SleepActivity.h"
#include
+#include
#include
#include "CrossPointSettings.h"
-#include "SD.h"
#include "config.h"
#include "images/CrossLarge.h"
diff --git a/src/activities/network/CrossPointWebServerActivity.cpp b/src/activities/network/CrossPointWebServerActivity.cpp
index 82f6329..bb0f39a 100644
--- a/src/activities/network/CrossPointWebServerActivity.cpp
+++ b/src/activities/network/CrossPointWebServerActivity.cpp
@@ -217,8 +217,7 @@ void CrossPointWebServerActivity::render() const {
}
void CrossPointWebServerActivity::renderServerRunning() const {
- const auto pageWidth = GfxRenderer::getScreenWidth();
- const auto pageHeight = GfxRenderer::getScreenHeight();
+ const auto pageHeight = renderer.getScreenHeight();
const auto height = renderer.getLineHeight(UI_FONT_ID);
const auto top = (pageHeight - height * 5) / 2;
@@ -226,7 +225,7 @@ void CrossPointWebServerActivity::renderServerRunning() const {
std::string ssidInfo = "Network: " + connectedSSID;
if (ssidInfo.length() > 28) {
- ssidInfo = ssidInfo.substr(0, 25) + "...";
+ ssidInfo.replace(25, ssidInfo.length() - 25, "...");
}
renderer.drawCenteredText(UI_FONT_ID, top + 10, ssidInfo.c_str(), true, REGULAR);
diff --git a/src/activities/network/WifiSelectionActivity.cpp b/src/activities/network/WifiSelectionActivity.cpp
index a48891e..d6c3b1e 100644
--- a/src/activities/network/WifiSelectionActivity.cpp
+++ b/src/activities/network/WifiSelectionActivity.cpp
@@ -138,6 +138,7 @@ void WifiSelectionActivity::processWifiScanResults() {
// Convert map to vector
networks.clear();
for (const auto& pair : uniqueNetworks) {
+ // cppcheck-suppress useStlAlgorithm
networks.push_back(pair.second);
}
@@ -334,11 +335,10 @@ void WifiSelectionActivity::loop() {
// User chose "Yes" - forget the network
WIFI_STORE.removeCredential(selectedSSID);
// Update the network list to reflect the change
- for (auto& network : networks) {
- if (network.ssid == selectedSSID) {
- network.hasSavedPassword = false;
- break;
- }
+ const auto network = find_if(networks.begin(), networks.end(),
+ [this](const WifiNetworkInfo& net) { return net.ssid == selectedSSID; });
+ if (network != networks.end()) {
+ network->hasSavedPassword = false;
}
}
// Go back to network list
@@ -468,8 +468,8 @@ void WifiSelectionActivity::render() const {
}
void WifiSelectionActivity::renderNetworkList() const {
- const auto pageWidth = GfxRenderer::getScreenWidth();
- const auto pageHeight = GfxRenderer::getScreenHeight();
+ const auto pageWidth = renderer.getScreenWidth();
+ const auto pageHeight = renderer.getScreenHeight();
// Draw header
renderer.drawCenteredText(READER_FONT_ID, 10, "WiFi Networks", true, BOLD);
@@ -506,7 +506,7 @@ void WifiSelectionActivity::renderNetworkList() const {
// Draw network name (truncate if too long)
std::string displayName = network.ssid;
if (displayName.length() > 16) {
- displayName = displayName.substr(0, 13) + "...";
+ displayName.replace(13, displayName.length() - 13, "...");
}
renderer.drawText(UI_FONT_ID, 20, networkY, displayName.c_str());
@@ -544,15 +544,13 @@ void WifiSelectionActivity::renderNetworkList() const {
}
void WifiSelectionActivity::renderPasswordEntry() const {
- const auto pageHeight = GfxRenderer::getScreenHeight();
-
// Draw header
renderer.drawCenteredText(READER_FONT_ID, 5, "WiFi Password", true, BOLD);
// Draw network name with good spacing from header
std::string networkInfo = "Network: " + selectedSSID;
if (networkInfo.length() > 30) {
- networkInfo = networkInfo.substr(0, 27) + "...";
+ networkInfo.replace(27, networkInfo.length() - 27, "...");
}
renderer.drawCenteredText(UI_FONT_ID, 38, networkInfo.c_str(), true, REGULAR);
@@ -563,7 +561,7 @@ void WifiSelectionActivity::renderPasswordEntry() const {
}
void WifiSelectionActivity::renderConnecting() const {
- const auto pageHeight = GfxRenderer::getScreenHeight();
+ const auto pageHeight = renderer.getScreenHeight();
const auto height = renderer.getLineHeight(UI_FONT_ID);
const auto top = (pageHeight - height) / 2;
@@ -574,15 +572,14 @@ void WifiSelectionActivity::renderConnecting() const {
std::string ssidInfo = "to " + selectedSSID;
if (ssidInfo.length() > 25) {
- ssidInfo = ssidInfo.substr(0, 22) + "...";
+ ssidInfo.replace(22, ssidInfo.length() - 22, "...");
}
renderer.drawCenteredText(UI_FONT_ID, top, ssidInfo.c_str(), true, REGULAR);
}
}
void WifiSelectionActivity::renderConnected() const {
- const auto pageWidth = GfxRenderer::getScreenWidth();
- const auto pageHeight = GfxRenderer::getScreenHeight();
+ const auto pageHeight = renderer.getScreenHeight();
const auto height = renderer.getLineHeight(UI_FONT_ID);
const auto top = (pageHeight - height * 4) / 2;
@@ -590,7 +587,7 @@ void WifiSelectionActivity::renderConnected() const {
std::string ssidInfo = "Network: " + selectedSSID;
if (ssidInfo.length() > 28) {
- ssidInfo = ssidInfo.substr(0, 25) + "...";
+ ssidInfo.replace(25, ssidInfo.length() - 25, "...");
}
renderer.drawCenteredText(UI_FONT_ID, top + 10, ssidInfo.c_str(), true, REGULAR);
@@ -601,8 +598,8 @@ void WifiSelectionActivity::renderConnected() const {
}
void WifiSelectionActivity::renderSavePrompt() const {
- const auto pageWidth = GfxRenderer::getScreenWidth();
- const auto pageHeight = GfxRenderer::getScreenHeight();
+ const auto pageWidth = renderer.getScreenWidth();
+ const auto pageHeight = renderer.getScreenHeight();
const auto height = renderer.getLineHeight(UI_FONT_ID);
const auto top = (pageHeight - height * 3) / 2;
@@ -610,7 +607,7 @@ void WifiSelectionActivity::renderSavePrompt() const {
std::string ssidInfo = "Network: " + selectedSSID;
if (ssidInfo.length() > 28) {
- ssidInfo = ssidInfo.substr(0, 25) + "...";
+ ssidInfo.replace(25, ssidInfo.length() - 25, "...");
}
renderer.drawCenteredText(UI_FONT_ID, top, ssidInfo.c_str(), true, REGULAR);
@@ -641,7 +638,7 @@ void WifiSelectionActivity::renderSavePrompt() const {
}
void WifiSelectionActivity::renderConnectionFailed() const {
- const auto pageHeight = GfxRenderer::getScreenHeight();
+ const auto pageHeight = renderer.getScreenHeight();
const auto height = renderer.getLineHeight(UI_FONT_ID);
const auto top = (pageHeight - height * 2) / 2;
@@ -651,8 +648,8 @@ void WifiSelectionActivity::renderConnectionFailed() const {
}
void WifiSelectionActivity::renderForgetPrompt() const {
- const auto pageWidth = GfxRenderer::getScreenWidth();
- const auto pageHeight = GfxRenderer::getScreenHeight();
+ const auto pageWidth = renderer.getScreenWidth();
+ const auto pageHeight = renderer.getScreenHeight();
const auto height = renderer.getLineHeight(UI_FONT_ID);
const auto top = (pageHeight - height * 3) / 2;
@@ -660,7 +657,7 @@ void WifiSelectionActivity::renderForgetPrompt() const {
std::string ssidInfo = "Network: " + selectedSSID;
if (ssidInfo.length() > 28) {
- ssidInfo = ssidInfo.substr(0, 25) + "...";
+ ssidInfo.replace(25, ssidInfo.length() - 25, "...");
}
renderer.drawCenteredText(UI_FONT_ID, top, ssidInfo.c_str(), true, REGULAR);
diff --git a/src/activities/network/server/CrossPointWebServer.cpp b/src/activities/network/server/CrossPointWebServer.cpp
index 90ec915..6291627 100644
--- a/src/activities/network/server/CrossPointWebServer.cpp
+++ b/src/activities/network/server/CrossPointWebServer.cpp
@@ -383,9 +383,7 @@ void CrossPointWebServer::handleFileList() {
// Folders come first
if (a.isDirectory != b.isDirectory) return a.isDirectory > b.isDirectory;
// Then sort by epub status (epubs first among files)
- if (!a.isDirectory && !b.isDirectory) {
- if (a.isEpub != b.isEpub) return a.isEpub > b.isEpub;
- }
+ if (a.isEpub != b.isEpub) return a.isEpub > b.isEpub;
// Then alphabetically
return a.name < b.name;
});
diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp
index fc4504d..8827e99 100644
--- a/src/activities/reader/EpubReaderActivity.cpp
+++ b/src/activities/reader/EpubReaderActivity.cpp
@@ -383,7 +383,7 @@ void EpubReaderActivity::renderStatusBar() const {
title = tocItem.title;
titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str());
while (titleWidth > availableTextWidth && title.length() > 11) {
- title = title.substr(0, title.length() - 8) + "...";
+ title.replace(title.length() - 8, 8, "...");
titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str());
}
}
diff --git a/src/activities/reader/FileSelectionActivity.cpp b/src/activities/reader/FileSelectionActivity.cpp
index 9e665cb..4389461 100644
--- a/src/activities/reader/FileSelectionActivity.cpp
+++ b/src/activities/reader/FileSelectionActivity.cpp
@@ -93,7 +93,7 @@ void FileSelectionActivity::loop() {
}
} else if (inputManager.wasPressed(InputManager::BTN_BACK)) {
if (basepath != "/") {
- basepath = basepath.substr(0, basepath.rfind('/'));
+ basepath.replace(basepath.find_last_of('/'), std::string::npos, "");
if (basepath.empty()) basepath = "/";
loadFiles();
updateRequired = true;
diff --git a/src/activities/util/KeyboardEntryActivity.cpp b/src/activities/util/KeyboardEntryActivity.cpp
index 40d6fed..68fc079 100644
--- a/src/activities/util/KeyboardEntryActivity.cpp
+++ b/src/activities/util/KeyboardEntryActivity.cpp
@@ -20,7 +20,7 @@ KeyboardEntryActivity::KeyboardEntryActivity(GfxRenderer& renderer, InputManager
void KeyboardEntryActivity::setText(const std::string& newText) {
text = newText;
if (maxLength > 0 && text.length() > maxLength) {
- text = text.substr(0, maxLength);
+ text.resize(maxLength);
}
}
diff --git a/src/main.cpp b/src/main.cpp
index 86169a5..89d2af4 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -5,7 +5,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -203,20 +202,18 @@ void setup() {
}
void loop() {
- static unsigned long lastLoopTime = 0;
static unsigned long maxLoopDuration = 0;
-
- unsigned long loopStartTime = millis();
-
+ const unsigned long loopStartTime = millis();
static unsigned long lastMemPrint = 0;
+
+ inputManager.update();
+
if (Serial && millis() - lastMemPrint >= 10000) {
Serial.printf("[%lu] [MEM] Free: %d bytes, Total: %d bytes, Min Free: %d bytes\n", millis(), ESP.getFreeHeap(),
ESP.getHeapSize(), ESP.getMinFreeHeap());
lastMemPrint = millis();
}
- inputManager.update();
-
// Check for any user activity (button press or release)
static unsigned long lastActivityTime = millis();
if (inputManager.wasAnyPressed() || inputManager.wasAnyReleased()) {
@@ -237,13 +234,13 @@ void loop() {
return;
}
- unsigned long activityStartTime = millis();
+ const unsigned long activityStartTime = millis();
if (currentActivity) {
currentActivity->loop();
}
- unsigned long activityDuration = millis() - activityStartTime;
+ const unsigned long activityDuration = millis() - activityStartTime;
- unsigned long loopDuration = millis() - loopStartTime;
+ const unsigned long loopDuration = millis() - loopStartTime;
if (loopDuration > maxLoopDuration) {
maxLoopDuration = loopDuration;
if (maxLoopDuration > 50) {
@@ -252,8 +249,6 @@ void loop() {
}
}
- lastLoopTime = loopStartTime;
-
// Add delay at the end of the loop to prevent tight spinning
// When an activity requests skip loop delay (e.g., webserver running), use yield() for faster response
// Otherwise, use longer delay to save power
From f264efdb12a47c6f07c475f8ff734c71658f6833 Mon Sep 17 00:00:00 2001
From: Dave Allie
Date: Sun, 21 Dec 2025 17:08:34 +1100
Subject: [PATCH 09/33] Extract EPUB TOC into temp file before parsing (#85)
## Summary
* Extract EPUB TOC into temp file before parsing
* Streaming ZIP -> XML parser uses up a lot of memory as we're
allocating inflation buffers while also holding a few copies of the
buffer in different forms
* Instead, but streaming the inflated file down to the SD card (like we
do for HTML parsing, we can lower memory usage)
## Additional Context
* This should help with
https://github.com/daveallie/crosspoint-reader/issues/60 and
https://github.com/daveallie/crosspoint-reader/issues/10. It won't
remove those class of issues completely, but will allow for many more
books to be opened.
---
lib/Epub/Epub.cpp | 36 +++++++++++++++++++-------
lib/Epub/Epub/EpubTocEntry.h | 7 ++---
lib/Epub/Epub/parsers/TocNcxParser.cpp | 2 +-
lib/Epub/Epub/parsers/TocNcxParser.h | 2 +-
4 files changed, 31 insertions(+), 16 deletions(-)
diff --git a/lib/Epub/Epub.cpp b/lib/Epub/Epub.cpp
index 2df5a3f..cc3bc90 100644
--- a/lib/Epub/Epub.cpp
+++ b/lib/Epub/Epub.cpp
@@ -93,24 +93,42 @@ bool Epub::parseTocNcxFile() {
Serial.printf("[%lu] [EBP] Parsing toc ncx file: %s\n", millis(), tocNcxItem.c_str());
- size_t tocSize;
- if (!getItemSize(tocNcxItem, &tocSize)) {
- Serial.printf("[%lu] [EBP] Could not get size of toc ncx\n", millis());
- return false;
- }
+ const auto tmpNcxPath = getCachePath() + "/toc.ncx";
+ File tempNcxFile = SD.open(tmpNcxPath.c_str(), FILE_WRITE);
+ readItemContentsToStream(tocNcxItem, tempNcxFile, 1024);
+ tempNcxFile.close();
+ tempNcxFile = SD.open(tmpNcxPath.c_str(), FILE_READ);
+ const auto ncxSize = tempNcxFile.size();
- TocNcxParser ncxParser(contentBasePath, tocSize);
+ TocNcxParser ncxParser(contentBasePath, ncxSize);
if (!ncxParser.setup()) {
Serial.printf("[%lu] [EBP] Could not setup toc ncx parser\n", millis());
return false;
}
- if (!readItemContentsToStream(tocNcxItem, ncxParser, 1024)) {
- Serial.printf("[%lu] [EBP] Could not read toc ncx stream\n", millis());
+ const auto ncxBuffer = static_cast(malloc(1024));
+ if (!ncxBuffer) {
+ Serial.printf("[%lu] [EBP] Could not allocate memory for toc ncx parser\n", millis());
return false;
}
+ while (tempNcxFile.available()) {
+ const auto readSize = tempNcxFile.read(ncxBuffer, 1024);
+ const auto processedSize = ncxParser.write(ncxBuffer, readSize);
+
+ if (processedSize != readSize) {
+ Serial.printf("[%lu] [EBP] Could not process all toc ncx data\n", millis());
+ free(ncxBuffer);
+ tempNcxFile.close();
+ return false;
+ }
+ }
+
+ free(ncxBuffer);
+ tempNcxFile.close();
+ SD.remove(tmpNcxPath.c_str());
+
this->toc = std::move(ncxParser.toc);
Serial.printf("[%lu] [EBP] Parsed %d TOC items\n", millis(), this->toc.size());
@@ -293,7 +311,7 @@ std::string& Epub::getSpineItem(const int spineIndex) {
}
EpubTocEntry& Epub::getTocItem(const int tocTndex) {
- static EpubTocEntry emptyEntry("", "", "", 0);
+ static EpubTocEntry emptyEntry = {};
if (toc.empty()) {
Serial.printf("[%lu] [EBP] getTocItem called but toc is empty\n", millis());
return emptyEntry;
diff --git a/lib/Epub/Epub/EpubTocEntry.h b/lib/Epub/Epub/EpubTocEntry.h
index 715e4a4..94f0c90 100644
--- a/lib/Epub/Epub/EpubTocEntry.h
+++ b/lib/Epub/Epub/EpubTocEntry.h
@@ -2,12 +2,9 @@
#include
-class EpubTocEntry {
- public:
+struct EpubTocEntry {
std::string title;
std::string href;
std::string anchor;
- int level;
- EpubTocEntry(std::string title, std::string href, std::string anchor, const int level)
- : title(std::move(title)), href(std::move(href)), anchor(std::move(anchor)), level(level) {}
+ uint8_t level;
};
diff --git a/lib/Epub/Epub/parsers/TocNcxParser.cpp b/lib/Epub/Epub/parsers/TocNcxParser.cpp
index 4d541f5..0a613f3 100644
--- a/lib/Epub/Epub/parsers/TocNcxParser.cpp
+++ b/lib/Epub/Epub/parsers/TocNcxParser.cpp
@@ -155,7 +155,7 @@ void XMLCALL TocNcxParser::endElement(void* userData, const XML_Char* name) {
}
// Push to vector
- self->toc.emplace_back(self->currentLabel, href, anchor, self->currentDepth);
+ self->toc.push_back({std::move(self->currentLabel), std::move(href), std::move(anchor), self->currentDepth});
// Clear them so we don't re-add them if there are weird XML structures
self->currentLabel.clear();
diff --git a/lib/Epub/Epub/parsers/TocNcxParser.h b/lib/Epub/Epub/parsers/TocNcxParser.h
index 5d5df0b..2f3601a 100644
--- a/lib/Epub/Epub/parsers/TocNcxParser.h
+++ b/lib/Epub/Epub/parsers/TocNcxParser.h
@@ -17,7 +17,7 @@ class TocNcxParser final : public Print {
std::string currentLabel;
std::string currentSrc;
- size_t currentDepth = 0;
+ uint8_t currentDepth = 0;
static void startElement(void* userData, const XML_Char* name, const XML_Char** atts);
static void characterData(void* userData, const XML_Char* s, int len);
From b73ae7fe747c48dc46ae24768c2408b1202e29bf Mon Sep 17 00:00:00 2001
From: Dave Allie
Date: Sun, 21 Dec 2025 17:12:53 +1100
Subject: [PATCH 10/33] Paginate book list and avoid out of bounds rendering
(#86)
## Summary
* Paginate book list
* Avoid out of bounds rendering of long book titles, truncate with
ellipsis instead
## Additional Context
* Should partially help with
https://github.com/daveallie/crosspoint-reader/issues/75 as it was
previously rendering a lot of content off screen, will need to test with
a large directory
---
.../EpubReaderChapterSelectionActivity.cpp | 2 +
.../reader/FileSelectionActivity.cpp | 51 +++++++++++++------
2 files changed, 38 insertions(+), 15 deletions(-)
diff --git a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp
index f4dfc37..9af556b 100644
--- a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp
+++ b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp
@@ -5,8 +5,10 @@
#include "config.h"
+namespace {
constexpr int PAGE_ITEMS = 24;
constexpr int SKIP_PAGE_MS = 700;
+} // namespace
void EpubReaderChapterSelectionActivity::taskTrampoline(void* param) {
auto* self = static_cast(param);
diff --git a/src/activities/reader/FileSelectionActivity.cpp b/src/activities/reader/FileSelectionActivity.cpp
index 4389461..d8aef3c 100644
--- a/src/activities/reader/FileSelectionActivity.cpp
+++ b/src/activities/reader/FileSelectionActivity.cpp
@@ -5,6 +5,11 @@
#include "config.h"
+namespace {
+constexpr int PAGE_ITEMS = 23;
+constexpr int SKIP_PAGE_MS = 700;
+} // namespace
+
void sortFileList(std::vector& strs) {
std::sort(begin(strs), end(strs), [](const std::string& str1, const std::string& str2) {
if (str1.back() == '/' && str2.back() != '/') return true;
@@ -73,10 +78,12 @@ void FileSelectionActivity::onExit() {
}
void FileSelectionActivity::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);
+ const bool prevReleased =
+ inputManager.wasReleased(InputManager::BTN_UP) || inputManager.wasReleased(InputManager::BTN_LEFT);
+ const bool nextReleased =
+ inputManager.wasReleased(InputManager::BTN_DOWN) || inputManager.wasReleased(InputManager::BTN_RIGHT);
+
+ const bool skipPage = inputManager.getHeldTime() > SKIP_PAGE_MS;
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
if (files.empty()) {
@@ -101,11 +108,19 @@ void FileSelectionActivity::loop() {
// At root level, go back home
onGoHome();
}
- } else if (prevPressed) {
- selectorIndex = (selectorIndex + files.size() - 1) % files.size();
+ } else if (prevReleased) {
+ if (skipPage) {
+ selectorIndex = ((selectorIndex / PAGE_ITEMS - 1) * PAGE_ITEMS + files.size()) % files.size();
+ } else {
+ selectorIndex = (selectorIndex + files.size() - 1) % files.size();
+ }
updateRequired = true;
- } else if (nextPressed) {
- selectorIndex = (selectorIndex + 1) % files.size();
+ } else if (nextReleased) {
+ if (skipPage) {
+ selectorIndex = ((selectorIndex / PAGE_ITEMS + 1) * PAGE_ITEMS) % files.size();
+ } else {
+ selectorIndex = (selectorIndex + 1) % files.size();
+ }
updateRequired = true;
}
}
@@ -126,21 +141,27 @@ void FileSelectionActivity::render() const {
renderer.clearScreen();
const auto pageWidth = GfxRenderer::getScreenWidth();
- renderer.drawCenteredText(READER_FONT_ID, 10, "CrossPoint Reader", true, BOLD);
+ renderer.drawCenteredText(READER_FONT_ID, 10, "Books", true, BOLD);
// Help text
renderer.drawText(SMALL_FONT_ID, 20, GfxRenderer::getScreenHeight() - 30, "Press BACK for Home");
if (files.empty()) {
renderer.drawText(UI_FONT_ID, 20, 60, "No EPUBs found");
- } else {
- // Draw selection
- renderer.fillRect(0, 60 + selectorIndex * 30 + 2, pageWidth - 1, 30);
+ renderer.displayBuffer();
+ return;
+ }
- for (size_t i = 0; i < files.size(); i++) {
- const auto file = files[i];
- renderer.drawText(UI_FONT_ID, 20, 60 + i * 30, file.c_str(), i != selectorIndex);
+ const auto pageStartIndex = selectorIndex / PAGE_ITEMS * PAGE_ITEMS;
+ renderer.fillRect(0, 60 + (selectorIndex % PAGE_ITEMS) * 30 + 2, pageWidth - 1, 30);
+ for (int i = pageStartIndex; i < files.size() && i < pageStartIndex + PAGE_ITEMS; i++) {
+ auto item = files[i];
+ int itemWidth = renderer.getTextWidth(UI_FONT_ID, item.c_str());
+ while (itemWidth > renderer.getScreenWidth() - 40 && item.length() > 8) {
+ item.replace(item.length() - 5, 5, "...");
+ itemWidth = renderer.getTextWidth(UI_FONT_ID, item.c_str());
}
+ renderer.drawText(UI_FONT_ID, 20, 60 + (i % PAGE_ITEMS) * 30, item.c_str(), i != selectorIndex);
}
renderer.displayBuffer();
From 2a27c6d06890cc447aa772f3edc993c2d0e1982b Mon Sep 17 00:00:00 2001
From: Dave Allie
Date: Sun, 21 Dec 2025 17:15:17 +1100
Subject: [PATCH 11/33] Add JPG image support (#23)
## Summary
- Add basic JPG image support
- Map JPG back to 2-bit BMP output
- Can be used to later render the BMP file from disk or directly pass to
output if wanted
- Give the 3 passes over the data needed to render grayscale content,
putting it on disk is preferred to outputting it multiple times
## Additional Context
- WIP, looking forward to BMP support from
https://github.com/daveallie/crosspoint-reader/pull/16
- Addresses some of #11
---
lib/JpegToBmpConverter/JpegToBmpConverter.cpp | 244 ++
lib/JpegToBmpConverter/JpegToBmpConverter.h | 15 +
lib/picojpeg/picojpeg.c | 2087 +++++++++++++++++
lib/picojpeg/picojpeg.h | 124 +
4 files changed, 2470 insertions(+)
create mode 100644 lib/JpegToBmpConverter/JpegToBmpConverter.cpp
create mode 100644 lib/JpegToBmpConverter/JpegToBmpConverter.h
create mode 100644 lib/picojpeg/picojpeg.c
create mode 100644 lib/picojpeg/picojpeg.h
diff --git a/lib/JpegToBmpConverter/JpegToBmpConverter.cpp b/lib/JpegToBmpConverter/JpegToBmpConverter.cpp
new file mode 100644
index 0000000..4b48d70
--- /dev/null
+++ b/lib/JpegToBmpConverter/JpegToBmpConverter.cpp
@@ -0,0 +1,244 @@
+#include "JpegToBmpConverter.h"
+
+#include
+
+#include
+#include
+
+// Context structure for picojpeg callback
+struct JpegReadContext {
+ File& file;
+ uint8_t buffer[512];
+ size_t bufferPos;
+ size_t bufferFilled;
+};
+
+// Helper function: Convert 8-bit grayscale to 2-bit (0-3)
+uint8_t JpegToBmpConverter::grayscaleTo2Bit(const uint8_t grayscale) {
+ // Simple threshold mapping:
+ // 0-63 -> 0 (black)
+ // 64-127 -> 1 (dark gray)
+ // 128-191 -> 2 (light gray)
+ // 192-255 -> 3 (white)
+ return grayscale >> 6;
+}
+
+inline void write16(Print& out, const uint16_t value) {
+ // out.write(reinterpret_cast(&value), 2);
+ out.write(value & 0xFF);
+ out.write((value >> 8) & 0xFF);
+}
+
+inline void write32(Print& out, const uint32_t value) {
+ // out.write(reinterpret_cast(&value), 4);
+ out.write(value & 0xFF);
+ out.write((value >> 8) & 0xFF);
+ out.write((value >> 16) & 0xFF);
+ out.write((value >> 24) & 0xFF);
+}
+
+inline void write32Signed(Print& out, const int32_t value) {
+ // out.write(reinterpret_cast(&value), 4);
+ out.write(value & 0xFF);
+ out.write((value >> 8) & 0xFF);
+ out.write((value >> 16) & 0xFF);
+ out.write((value >> 24) & 0xFF);
+}
+
+// Helper function: Write BMP header with 2-bit color depth
+void JpegToBmpConverter::writeBmpHeader(Print& bmpOut, const int width, const int height) {
+ // Calculate row padding (each row must be multiple of 4 bytes)
+ const int bytesPerRow = (width * 2 + 31) / 32 * 4; // 2 bits per pixel, round up
+ const int imageSize = bytesPerRow * height;
+ const uint32_t fileSize = 70 + imageSize; // 14 (file header) + 40 (DIB header) + 16 (palette) + image
+
+ // BMP File Header (14 bytes)
+ bmpOut.write('B');
+ bmpOut.write('M');
+ write32(bmpOut, fileSize); // File size
+ write32(bmpOut, 0); // Reserved
+ write32(bmpOut, 70); // Offset to pixel data
+
+ // DIB Header (BITMAPINFOHEADER - 40 bytes)
+ write32(bmpOut, 40);
+ write32Signed(bmpOut, width);
+ write32Signed(bmpOut, -height); // Negative height = top-down bitmap
+ write16(bmpOut, 1); // Color planes
+ write16(bmpOut, 2); // Bits per pixel (2 bits)
+ write32(bmpOut, 0); // BI_RGB (no compression)
+ write32(bmpOut, imageSize);
+ write32(bmpOut, 2835); // xPixelsPerMeter (72 DPI)
+ write32(bmpOut, 2835); // yPixelsPerMeter (72 DPI)
+ write32(bmpOut, 4); // colorsUsed
+ write32(bmpOut, 4); // colorsImportant
+
+ // Color Palette (4 colors x 4 bytes = 16 bytes)
+ // Format: Blue, Green, Red, Reserved (BGRA)
+ uint8_t palette[16] = {
+ 0x00, 0x00, 0x00, 0x00, // Color 0: Black
+ 0x55, 0x55, 0x55, 0x00, // Color 1: Dark gray (85)
+ 0xAA, 0xAA, 0xAA, 0x00, // Color 2: Light gray (170)
+ 0xFF, 0xFF, 0xFF, 0x00 // Color 3: White
+ };
+ for (const uint8_t i : palette) {
+ bmpOut.write(i);
+ }
+}
+
+// Callback function for picojpeg to read JPEG data
+unsigned char JpegToBmpConverter::jpegReadCallback(unsigned char* pBuf, const unsigned char buf_size,
+ unsigned char* pBytes_actually_read, void* pCallback_data) {
+ auto* context = static_cast(pCallback_data);
+
+ if (!context || !context->file) {
+ return PJPG_STREAM_READ_ERROR;
+ }
+
+ // Check if we need to refill our context buffer
+ if (context->bufferPos >= context->bufferFilled) {
+ context->bufferFilled = context->file.read(context->buffer, sizeof(context->buffer));
+ context->bufferPos = 0;
+
+ if (context->bufferFilled == 0) {
+ // EOF or error
+ *pBytes_actually_read = 0;
+ return 0; // Success (EOF is normal)
+ }
+ }
+
+ // Copy available bytes to picojpeg's buffer
+ const size_t available = context->bufferFilled - context->bufferPos;
+ const size_t toRead = available < buf_size ? available : buf_size;
+
+ memcpy(pBuf, context->buffer + context->bufferPos, toRead);
+ context->bufferPos += toRead;
+ *pBytes_actually_read = static_cast(toRead);
+
+ return 0; // Success
+}
+
+// Core function: Convert JPEG file to 2-bit BMP
+bool JpegToBmpConverter::jpegFileToBmpStream(File& jpegFile, Print& bmpOut) {
+ Serial.printf("[%lu] [JPG] Converting JPEG to BMP\n", millis());
+
+ // Setup context for picojpeg callback
+ JpegReadContext context = {.file = jpegFile, .bufferPos = 0, .bufferFilled = 0};
+
+ // Initialize picojpeg decoder
+ pjpeg_image_info_t imageInfo;
+ const unsigned char status = pjpeg_decode_init(&imageInfo, jpegReadCallback, &context, 0);
+ if (status != 0) {
+ Serial.printf("[%lu] [JPG] JPEG decode init failed with error code: %d\n", millis(), status);
+ return false;
+ }
+
+ Serial.printf("[%lu] [JPG] JPEG dimensions: %dx%d, components: %d, MCUs: %dx%d\n", millis(), imageInfo.m_width,
+ imageInfo.m_height, imageInfo.m_comps, imageInfo.m_MCUSPerRow, imageInfo.m_MCUSPerCol);
+
+ // Write BMP header
+ writeBmpHeader(bmpOut, imageInfo.m_width, imageInfo.m_height);
+
+ // Calculate row parameters
+ const int bytesPerRow = (imageInfo.m_width * 2 + 31) / 32 * 4;
+
+ // Allocate row buffer for packed 2-bit pixels
+ auto* rowBuffer = static_cast(malloc(bytesPerRow));
+ if (!rowBuffer) {
+ Serial.printf("[%lu] [JPG] Failed to allocate row buffer\n", millis());
+ return false;
+ }
+
+ // Allocate a buffer for one MCU row worth of grayscale pixels
+ // This is the minimal memory needed for streaming conversion
+ const int mcuPixelHeight = imageInfo.m_MCUHeight;
+ const int mcuRowPixels = imageInfo.m_width * mcuPixelHeight;
+ auto* mcuRowBuffer = static_cast(malloc(mcuRowPixels));
+ if (!mcuRowBuffer) {
+ Serial.printf("[%lu] [JPG] Failed to allocate MCU row buffer\n", millis());
+ free(rowBuffer);
+ return false;
+ }
+
+ // Process MCUs row-by-row and write to BMP as we go (top-down)
+ const int mcuPixelWidth = imageInfo.m_MCUWidth;
+
+ for (int mcuY = 0; mcuY < imageInfo.m_MCUSPerCol; mcuY++) {
+ // Clear the MCU row buffer
+ memset(mcuRowBuffer, 0, mcuRowPixels);
+
+ // Decode one row of MCUs
+ for (int mcuX = 0; mcuX < imageInfo.m_MCUSPerRow; mcuX++) {
+ const unsigned char mcuStatus = pjpeg_decode_mcu();
+ if (mcuStatus != 0) {
+ if (mcuStatus == PJPG_NO_MORE_BLOCKS) {
+ Serial.printf("[%lu] [JPG] Unexpected end of blocks at MCU (%d, %d)\n", millis(), mcuX, mcuY);
+ } else {
+ Serial.printf("[%lu] [JPG] JPEG decode MCU failed at (%d, %d) with error code: %d\n", millis(), mcuX, mcuY,
+ mcuStatus);
+ }
+ free(mcuRowBuffer);
+ free(rowBuffer);
+ return false;
+ }
+
+ // Process MCU block into MCU row buffer
+ for (int blockY = 0; blockY < mcuPixelHeight; blockY++) {
+ for (int blockX = 0; blockX < mcuPixelWidth; blockX++) {
+ const int pixelX = mcuX * mcuPixelWidth + blockX;
+
+ // Skip pixels outside image width (can happen with MCU alignment)
+ if (pixelX >= imageInfo.m_width) {
+ continue;
+ }
+
+ // Get grayscale value
+ uint8_t gray;
+ if (imageInfo.m_comps == 1) {
+ // Grayscale image
+ gray = imageInfo.m_pMCUBufR[blockY * mcuPixelWidth + blockX];
+ } else {
+ // RGB image - convert to grayscale
+ const uint8_t r = imageInfo.m_pMCUBufR[blockY * mcuPixelWidth + blockX];
+ const uint8_t g = imageInfo.m_pMCUBufG[blockY * mcuPixelWidth + blockX];
+ const uint8_t b = imageInfo.m_pMCUBufB[blockY * mcuPixelWidth + blockX];
+ // Luminance formula: Y = 0.299*R + 0.587*G + 0.114*B
+ // Using integer approximation: (30*R + 59*G + 11*B) / 100
+ gray = (r * 30 + g * 59 + b * 11) / 100;
+ }
+
+ // Store grayscale value in MCU row buffer
+ mcuRowBuffer[blockY * imageInfo.m_width + pixelX] = gray;
+ }
+ }
+ }
+
+ // Write all pixel rows from this MCU row to BMP file
+ const int startRow = mcuY * mcuPixelHeight;
+ const int endRow = (mcuY + 1) * mcuPixelHeight;
+
+ for (int y = startRow; y < endRow && y < imageInfo.m_height; y++) {
+ memset(rowBuffer, 0, bytesPerRow);
+
+ // Pack 4 pixels per byte (2 bits each)
+ for (int x = 0; x < imageInfo.m_width; x++) {
+ const int bufferY = y - startRow;
+ const uint8_t gray = mcuRowBuffer[bufferY * imageInfo.m_width + x];
+ const uint8_t twoBit = grayscaleTo2Bit(gray);
+
+ const int byteIndex = (x * 2) / 8;
+ const int bitOffset = 6 - ((x * 2) % 8); // 6, 4, 2, 0
+ rowBuffer[byteIndex] |= (twoBit << bitOffset);
+ }
+
+ // Write row with padding
+ bmpOut.write(rowBuffer, bytesPerRow);
+ }
+ }
+
+ // Clean up
+ free(mcuRowBuffer);
+ free(rowBuffer);
+
+ Serial.printf("[%lu] [JPG] Successfully converted JPEG to BMP\n", millis());
+ return true;
+}
diff --git a/lib/JpegToBmpConverter/JpegToBmpConverter.h b/lib/JpegToBmpConverter/JpegToBmpConverter.h
new file mode 100644
index 0000000..fc881e2
--- /dev/null
+++ b/lib/JpegToBmpConverter/JpegToBmpConverter.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include
+
+class ZipFile;
+
+class JpegToBmpConverter {
+ static void writeBmpHeader(Print& bmpOut, int width, int height);
+ static uint8_t grayscaleTo2Bit(uint8_t grayscale);
+ static unsigned char jpegReadCallback(unsigned char* pBuf, unsigned char buf_size,
+ unsigned char* pBytes_actually_read, void* pCallback_data);
+
+ public:
+ static bool jpegFileToBmpStream(File& jpegFile, Print& bmpOut);
+};
diff --git a/lib/picojpeg/picojpeg.c b/lib/picojpeg/picojpeg.c
new file mode 100644
index 0000000..f612b73
--- /dev/null
+++ b/lib/picojpeg/picojpeg.c
@@ -0,0 +1,2087 @@
+//------------------------------------------------------------------------------
+// picojpeg.c v1.1 - Public domain, Rich Geldreich
+// Nov. 27, 2010 - Initial release
+// Feb. 9, 2013 - Added H1V2/H2V1 support, cleaned up macros, signed shift fixes
+// Also integrated and tested changes from Chris Phoenix .
+//------------------------------------------------------------------------------
+#include "picojpeg.h"
+//------------------------------------------------------------------------------
+// Set to 1 if right shifts on signed ints are always unsigned (logical) shifts
+// When 1, arithmetic right shifts will be emulated by using a logical shift
+// with special case code to ensure the sign bit is replicated.
+#define PJPG_RIGHT_SHIFT_IS_ALWAYS_UNSIGNED 0
+
+// Define PJPG_INLINE to "inline" if your C compiler supports explicit inlining
+#define PJPG_INLINE
+//------------------------------------------------------------------------------
+typedef unsigned char uint8;
+typedef unsigned short uint16;
+typedef signed char int8;
+typedef signed short int16;
+//------------------------------------------------------------------------------
+#if PJPG_RIGHT_SHIFT_IS_ALWAYS_UNSIGNED
+static int16 replicateSignBit16(int8 n) {
+ switch (n) {
+ case 0:
+ return 0x0000;
+ case 1:
+ return 0x8000;
+ case 2:
+ return 0xC000;
+ case 3:
+ return 0xE000;
+ case 4:
+ return 0xF000;
+ case 5:
+ return 0xF800;
+ case 6:
+ return 0xFC00;
+ case 7:
+ return 0xFE00;
+ case 8:
+ return 0xFF00;
+ case 9:
+ return 0xFF80;
+ case 10:
+ return 0xFFC0;
+ case 11:
+ return 0xFFE0;
+ case 12:
+ return 0xFFF0;
+ case 13:
+ return 0xFFF8;
+ case 14:
+ return 0xFFFC;
+ case 15:
+ return 0xFFFE;
+ default:
+ return 0xFFFF;
+ }
+}
+static PJPG_INLINE int16 arithmeticRightShiftN16(int16 x, int8 n) {
+ int16 r = (uint16)x >> (uint8)n;
+ if (x < 0) r |= replicateSignBit16(n);
+ return r;
+}
+static PJPG_INLINE long arithmeticRightShift8L(long x) {
+ long r = (unsigned long)x >> 8U;
+ if (x < 0) r |= ~(~(unsigned long)0U >> 8U);
+ return r;
+}
+#define PJPG_ARITH_SHIFT_RIGHT_N_16(x, n) arithmeticRightShiftN16(x, n)
+#define PJPG_ARITH_SHIFT_RIGHT_8_L(x) arithmeticRightShift8L(x)
+#else
+#define PJPG_ARITH_SHIFT_RIGHT_N_16(x, n) ((x) >> (n))
+#define PJPG_ARITH_SHIFT_RIGHT_8_L(x) ((x) >> 8)
+#endif
+//------------------------------------------------------------------------------
+// Change as needed - the PJPG_MAX_WIDTH/PJPG_MAX_HEIGHT checks are only present
+// to quickly detect bogus files.
+#define PJPG_MAX_WIDTH 16384
+#define PJPG_MAX_HEIGHT 16384
+#define PJPG_MAXCOMPSINSCAN 3
+//------------------------------------------------------------------------------
+typedef enum {
+ M_SOF0 = 0xC0,
+ M_SOF1 = 0xC1,
+ M_SOF2 = 0xC2,
+ M_SOF3 = 0xC3,
+
+ M_SOF5 = 0xC5,
+ M_SOF6 = 0xC6,
+ M_SOF7 = 0xC7,
+
+ M_JPG = 0xC8,
+ M_SOF9 = 0xC9,
+ M_SOF10 = 0xCA,
+ M_SOF11 = 0xCB,
+
+ M_SOF13 = 0xCD,
+ M_SOF14 = 0xCE,
+ M_SOF15 = 0xCF,
+
+ M_DHT = 0xC4,
+
+ M_DAC = 0xCC,
+
+ M_RST0 = 0xD0,
+ M_RST1 = 0xD1,
+ M_RST2 = 0xD2,
+ M_RST3 = 0xD3,
+ M_RST4 = 0xD4,
+ M_RST5 = 0xD5,
+ M_RST6 = 0xD6,
+ M_RST7 = 0xD7,
+
+ M_SOI = 0xD8,
+ M_EOI = 0xD9,
+ M_SOS = 0xDA,
+ M_DQT = 0xDB,
+ M_DNL = 0xDC,
+ M_DRI = 0xDD,
+ M_DHP = 0xDE,
+ M_EXP = 0xDF,
+
+ M_APP0 = 0xE0,
+ M_APP15 = 0xEF,
+
+ M_JPG0 = 0xF0,
+ M_JPG13 = 0xFD,
+ M_COM = 0xFE,
+
+ M_TEM = 0x01,
+
+ M_ERROR = 0x100,
+
+ RST0 = 0xD0
+} JPEG_MARKER;
+//------------------------------------------------------------------------------
+static const int8 ZAG[] = {
+ 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48,
+ 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23,
+ 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
+};
+//------------------------------------------------------------------------------
+// 128 bytes
+static int16 gCoeffBuf[8 * 8];
+
+// 8*8*4 bytes * 3 = 768
+static uint8 gMCUBufR[256];
+static uint8 gMCUBufG[256];
+static uint8 gMCUBufB[256];
+
+// 256 bytes
+static int16 gQuant0[8 * 8];
+static int16 gQuant1[8 * 8];
+
+// 6 bytes
+static int16 gLastDC[3];
+
+typedef struct HuffTableT {
+ uint16 mMinCode[16];
+ uint16 mMaxCode[16];
+ uint8 mValPtr[16];
+} HuffTable;
+
+// DC - 192
+static HuffTable gHuffTab0;
+
+static uint8 gHuffVal0[16];
+
+static HuffTable gHuffTab1;
+static uint8 gHuffVal1[16];
+
+// AC - 672
+static HuffTable gHuffTab2;
+static uint8 gHuffVal2[256];
+
+static HuffTable gHuffTab3;
+static uint8 gHuffVal3[256];
+
+static uint8 gValidHuffTables;
+static uint8 gValidQuantTables;
+
+static uint8 gTemFlag;
+#define PJPG_MAX_IN_BUF_SIZE 256
+static uint8 gInBuf[PJPG_MAX_IN_BUF_SIZE];
+static uint8 gInBufOfs;
+static uint8 gInBufLeft;
+
+static uint16 gBitBuf;
+static uint8 gBitsLeft;
+//------------------------------------------------------------------------------
+static uint16 gImageXSize;
+static uint16 gImageYSize;
+static uint8 gCompsInFrame;
+static uint8 gCompIdent[3];
+static uint8 gCompHSamp[3];
+static uint8 gCompVSamp[3];
+static uint8 gCompQuant[3];
+
+static uint16 gRestartInterval;
+static uint16 gNextRestartNum;
+static uint16 gRestartsLeft;
+
+static uint8 gCompsInScan;
+static uint8 gCompList[3];
+static uint8 gCompDCTab[3]; // 0,1
+static uint8 gCompACTab[3]; // 0,1
+
+static pjpeg_scan_type_t gScanType;
+
+static uint8 gMaxBlocksPerMCU;
+static uint8 gMaxMCUXSize;
+static uint8 gMaxMCUYSize;
+static uint16 gMaxMCUSPerRow;
+static uint16 gMaxMCUSPerCol;
+
+static uint16 gNumMCUSRemainingX, gNumMCUSRemainingY;
+
+static uint8 gMCUOrg[6];
+
+static pjpeg_need_bytes_callback_t g_pNeedBytesCallback;
+static void* g_pCallback_data;
+static uint8 gCallbackStatus;
+static uint8 gReduce;
+//------------------------------------------------------------------------------
+static void fillInBuf(void) {
+ unsigned char status;
+
+ // Reserve a few bytes at the beginning of the buffer for putting back ("stuffing") chars.
+ gInBufOfs = 4;
+ gInBufLeft = 0;
+
+ status = (*g_pNeedBytesCallback)(gInBuf + gInBufOfs, PJPG_MAX_IN_BUF_SIZE - gInBufOfs, &gInBufLeft, g_pCallback_data);
+ if (status) {
+ // The user provided need bytes callback has indicated an error, so record the error and continue trying to decode.
+ // The highest level pjpeg entrypoints will catch the error and return the non-zero status.
+ gCallbackStatus = status;
+ }
+}
+//------------------------------------------------------------------------------
+static PJPG_INLINE uint8 getChar(void) {
+ if (!gInBufLeft) {
+ fillInBuf();
+ if (!gInBufLeft) {
+ gTemFlag = ~gTemFlag;
+ return gTemFlag ? 0xFF : 0xD9;
+ }
+ }
+
+ gInBufLeft--;
+ return gInBuf[gInBufOfs++];
+}
+//------------------------------------------------------------------------------
+static PJPG_INLINE void stuffChar(uint8 i) {
+ gInBufOfs--;
+ gInBuf[gInBufOfs] = i;
+ gInBufLeft++;
+}
+//------------------------------------------------------------------------------
+static PJPG_INLINE uint8 getOctet(uint8 FFCheck) {
+ uint8 c = getChar();
+
+ if ((FFCheck) && (c == 0xFF)) {
+ uint8 n = getChar();
+
+ if (n) {
+ stuffChar(n);
+ stuffChar(0xFF);
+ }
+ }
+
+ return c;
+}
+//------------------------------------------------------------------------------
+static uint16 getBits(uint8 numBits, uint8 FFCheck) {
+ uint8 origBits = numBits;
+ uint16 ret = gBitBuf;
+
+ if (numBits > 8) {
+ numBits -= 8;
+
+ gBitBuf <<= gBitsLeft;
+
+ gBitBuf |= getOctet(FFCheck);
+
+ gBitBuf <<= (8 - gBitsLeft);
+
+ ret = (ret & 0xFF00) | (gBitBuf >> 8);
+ }
+
+ if (gBitsLeft < numBits) {
+ gBitBuf <<= gBitsLeft;
+
+ gBitBuf |= getOctet(FFCheck);
+
+ gBitBuf <<= (numBits - gBitsLeft);
+
+ gBitsLeft = 8 - (numBits - gBitsLeft);
+ } else {
+ gBitsLeft = (uint8)(gBitsLeft - numBits);
+ gBitBuf <<= numBits;
+ }
+
+ return ret >> (16 - origBits);
+}
+//------------------------------------------------------------------------------
+static PJPG_INLINE uint16 getBits1(uint8 numBits) { return getBits(numBits, 0); }
+//------------------------------------------------------------------------------
+static PJPG_INLINE uint16 getBits2(uint8 numBits) { return getBits(numBits, 1); }
+//------------------------------------------------------------------------------
+static PJPG_INLINE uint8 getBit(void) {
+ uint8 ret = 0;
+ if (gBitBuf & 0x8000) ret = 1;
+
+ if (!gBitsLeft) {
+ gBitBuf |= getOctet(1);
+
+ gBitsLeft += 8;
+ }
+
+ gBitsLeft--;
+ gBitBuf <<= 1;
+
+ return ret;
+}
+//------------------------------------------------------------------------------
+static uint16 getExtendTest(uint8 i) {
+ switch (i) {
+ case 0:
+ return 0;
+ case 1:
+ return 0x0001;
+ case 2:
+ return 0x0002;
+ case 3:
+ return 0x0004;
+ case 4:
+ return 0x0008;
+ case 5:
+ return 0x0010;
+ case 6:
+ return 0x0020;
+ case 7:
+ return 0x0040;
+ case 8:
+ return 0x0080;
+ case 9:
+ return 0x0100;
+ case 10:
+ return 0x0200;
+ case 11:
+ return 0x0400;
+ case 12:
+ return 0x0800;
+ case 13:
+ return 0x1000;
+ case 14:
+ return 0x2000;
+ case 15:
+ return 0x4000;
+ default:
+ return 0;
+ }
+}
+//------------------------------------------------------------------------------
+static int16 getExtendOffset(uint8 i) {
+ switch (i) {
+ case 0:
+ return 0;
+ case 1:
+ return ((-1) << 1) + 1;
+ case 2:
+ return ((-1) << 2) + 1;
+ case 3:
+ return ((-1) << 3) + 1;
+ case 4:
+ return ((-1) << 4) + 1;
+ case 5:
+ return ((-1) << 5) + 1;
+ case 6:
+ return ((-1) << 6) + 1;
+ case 7:
+ return ((-1) << 7) + 1;
+ case 8:
+ return ((-1) << 8) + 1;
+ case 9:
+ return ((-1) << 9) + 1;
+ case 10:
+ return ((-1) << 10) + 1;
+ case 11:
+ return ((-1) << 11) + 1;
+ case 12:
+ return ((-1) << 12) + 1;
+ case 13:
+ return ((-1) << 13) + 1;
+ case 14:
+ return ((-1) << 14) + 1;
+ case 15:
+ return ((-1) << 15) + 1;
+ default:
+ return 0;
+ }
+};
+//------------------------------------------------------------------------------
+static PJPG_INLINE int16 huffExtend(uint16 x, uint8 s) {
+ return ((x < getExtendTest(s)) ? ((int16)x + getExtendOffset(s)) : (int16)x);
+}
+//------------------------------------------------------------------------------
+static PJPG_INLINE uint8 huffDecode(const HuffTable* pHuffTable, const uint8* pHuffVal) {
+ uint8 i = 0;
+ uint8 j;
+ uint16 code = getBit();
+
+ // This func only reads a bit at a time, which on modern CPU's is not terribly efficient.
+ // But on microcontrollers without strong integer shifting support this seems like a
+ // more reasonable approach.
+ for (;;) {
+ uint16 maxCode;
+
+ if (i == 16) return 0;
+
+ maxCode = pHuffTable->mMaxCode[i];
+ if ((code <= maxCode) && (maxCode != 0xFFFF)) break;
+
+ i++;
+ code <<= 1;
+ code |= getBit();
+ }
+
+ j = pHuffTable->mValPtr[i];
+ j = (uint8)(j + (code - pHuffTable->mMinCode[i]));
+
+ return pHuffVal[j];
+}
+//------------------------------------------------------------------------------
+static void huffCreate(const uint8* pBits, HuffTable* pHuffTable) {
+ uint8 i = 0;
+ uint8 j = 0;
+
+ uint16 code = 0;
+
+ for (;;) {
+ uint8 num = pBits[i];
+
+ if (!num) {
+ pHuffTable->mMinCode[i] = 0x0000;
+ pHuffTable->mMaxCode[i] = 0xFFFF;
+ pHuffTable->mValPtr[i] = 0;
+ } else {
+ pHuffTable->mMinCode[i] = code;
+ pHuffTable->mMaxCode[i] = code + num - 1;
+ pHuffTable->mValPtr[i] = j;
+
+ j = (uint8)(j + num);
+
+ code = (uint16)(code + num);
+ }
+
+ code <<= 1;
+
+ i++;
+ if (i > 15) break;
+ }
+}
+//------------------------------------------------------------------------------
+static HuffTable* getHuffTable(uint8 index) {
+ // 0-1 = DC
+ // 2-3 = AC
+ switch (index) {
+ case 0:
+ return &gHuffTab0;
+ case 1:
+ return &gHuffTab1;
+ case 2:
+ return &gHuffTab2;
+ case 3:
+ return &gHuffTab3;
+ default:
+ return 0;
+ }
+}
+//------------------------------------------------------------------------------
+static uint8* getHuffVal(uint8 index) {
+ // 0-1 = DC
+ // 2-3 = AC
+ switch (index) {
+ case 0:
+ return gHuffVal0;
+ case 1:
+ return gHuffVal1;
+ case 2:
+ return gHuffVal2;
+ case 3:
+ return gHuffVal3;
+ default:
+ return 0;
+ }
+}
+//------------------------------------------------------------------------------
+static uint16 getMaxHuffCodes(uint8 index) { return (index < 2) ? 12 : 255; }
+//------------------------------------------------------------------------------
+static uint8 readDHTMarker(void) {
+ uint8 bits[16];
+ uint16 left = getBits1(16);
+
+ if (left < 2) return PJPG_BAD_DHT_MARKER;
+
+ left -= 2;
+
+ while (left) {
+ uint8 i, tableIndex, index;
+ uint8* pHuffVal;
+ HuffTable* pHuffTable;
+ uint16 count, totalRead;
+
+ index = (uint8)getBits1(8);
+
+ if (((index & 0xF) > 1) || ((index & 0xF0) > 0x10)) return PJPG_BAD_DHT_INDEX;
+
+ tableIndex = ((index >> 3) & 2) + (index & 1);
+
+ pHuffTable = getHuffTable(tableIndex);
+ pHuffVal = getHuffVal(tableIndex);
+
+ gValidHuffTables |= (1 << tableIndex);
+
+ count = 0;
+ for (i = 0; i <= 15; i++) {
+ uint8 n = (uint8)getBits1(8);
+ bits[i] = n;
+ count = (uint16)(count + n);
+ }
+
+ if (count > getMaxHuffCodes(tableIndex)) return PJPG_BAD_DHT_COUNTS;
+
+ for (i = 0; i < count; i++) pHuffVal[i] = (uint8)getBits1(8);
+
+ totalRead = 1 + 16 + count;
+
+ if (left < totalRead) return PJPG_BAD_DHT_MARKER;
+
+ left = (uint16)(left - totalRead);
+
+ huffCreate(bits, pHuffTable);
+ }
+
+ return 0;
+}
+//------------------------------------------------------------------------------
+static void createWinogradQuant(int16* pQuant);
+
+static uint8 readDQTMarker(void) {
+ uint16 left = getBits1(16);
+
+ if (left < 2) return PJPG_BAD_DQT_MARKER;
+
+ left -= 2;
+
+ while (left) {
+ uint8 i;
+ uint8 n = (uint8)getBits1(8);
+ uint8 prec = n >> 4;
+ uint16 totalRead;
+
+ n &= 0x0F;
+
+ if (n > 1) return PJPG_BAD_DQT_TABLE;
+
+ gValidQuantTables |= (n ? 2 : 1);
+
+ // read quantization entries, in zag order
+ for (i = 0; i < 64; i++) {
+ uint16 temp = getBits1(8);
+
+ if (prec) temp = (temp << 8) + getBits1(8);
+
+ if (n)
+ gQuant1[i] = (int16)temp;
+ else
+ gQuant0[i] = (int16)temp;
+ }
+
+ createWinogradQuant(n ? gQuant1 : gQuant0);
+
+ totalRead = 64 + 1;
+
+ if (prec) totalRead += 64;
+
+ if (left < totalRead) return PJPG_BAD_DQT_LENGTH;
+
+ left = (uint16)(left - totalRead);
+ }
+
+ return 0;
+}
+//------------------------------------------------------------------------------
+static uint8 readSOFMarker(void) {
+ uint8 i;
+ uint16 left = getBits1(16);
+
+ if (getBits1(8) != 8) return PJPG_BAD_PRECISION;
+
+ gImageYSize = getBits1(16);
+
+ if ((!gImageYSize) || (gImageYSize > PJPG_MAX_HEIGHT)) return PJPG_BAD_HEIGHT;
+
+ gImageXSize = getBits1(16);
+
+ if ((!gImageXSize) || (gImageXSize > PJPG_MAX_WIDTH)) return PJPG_BAD_WIDTH;
+
+ gCompsInFrame = (uint8)getBits1(8);
+
+ if (gCompsInFrame > 3) return PJPG_TOO_MANY_COMPONENTS;
+
+ if (left != (gCompsInFrame + gCompsInFrame + gCompsInFrame + 8)) return PJPG_BAD_SOF_LENGTH;
+
+ for (i = 0; i < gCompsInFrame; i++) {
+ gCompIdent[i] = (uint8)getBits1(8);
+ gCompHSamp[i] = (uint8)getBits1(4);
+ gCompVSamp[i] = (uint8)getBits1(4);
+ gCompQuant[i] = (uint8)getBits1(8);
+
+ if (gCompQuant[i] > 1) return PJPG_UNSUPPORTED_QUANT_TABLE;
+ }
+
+ return 0;
+}
+//------------------------------------------------------------------------------
+// Used to skip unrecognized markers.
+static uint8 skipVariableMarker(void) {
+ uint16 left = getBits1(16);
+
+ if (left < 2) return PJPG_BAD_VARIABLE_MARKER;
+
+ left -= 2;
+
+ while (left) {
+ getBits1(8);
+ left--;
+ }
+
+ return 0;
+}
+//------------------------------------------------------------------------------
+// Read a define restart interval (DRI) marker.
+static uint8 readDRIMarker(void) {
+ if (getBits1(16) != 4) return PJPG_BAD_DRI_LENGTH;
+
+ gRestartInterval = getBits1(16);
+
+ return 0;
+}
+//------------------------------------------------------------------------------
+// Read a start of scan (SOS) marker.
+static uint8 readSOSMarker(void) {
+ uint8 i;
+ uint16 left = getBits1(16);
+ uint8 spectral_start, spectral_end, successive_high, successive_low;
+
+ gCompsInScan = (uint8)getBits1(8);
+
+ left -= 3;
+
+ if ((left != (gCompsInScan + gCompsInScan + 3)) || (gCompsInScan < 1) || (gCompsInScan > PJPG_MAXCOMPSINSCAN))
+ return PJPG_BAD_SOS_LENGTH;
+
+ for (i = 0; i < gCompsInScan; i++) {
+ uint8 cc = (uint8)getBits1(8);
+ uint8 c = (uint8)getBits1(8);
+ uint8 ci;
+
+ left -= 2;
+
+ for (ci = 0; ci < gCompsInFrame; ci++)
+ if (cc == gCompIdent[ci]) break;
+
+ if (ci >= gCompsInFrame) return PJPG_BAD_SOS_COMP_ID;
+
+ gCompList[i] = ci;
+ gCompDCTab[ci] = (c >> 4) & 15;
+ gCompACTab[ci] = (c & 15);
+ }
+
+ spectral_start = (uint8)getBits1(8);
+ spectral_end = (uint8)getBits1(8);
+ successive_high = (uint8)getBits1(4);
+ successive_low = (uint8)getBits1(4);
+
+ left -= 3;
+
+ while (left) {
+ getBits1(8);
+ left--;
+ }
+
+ return 0;
+}
+//------------------------------------------------------------------------------
+static uint8 nextMarker(void) {
+ uint8 c;
+ uint8 bytes = 0;
+
+ do {
+ do {
+ bytes++;
+
+ c = (uint8)getBits1(8);
+
+ } while (c != 0xFF);
+
+ do {
+ c = (uint8)getBits1(8);
+
+ } while (c == 0xFF);
+
+ } while (c == 0);
+
+ // If bytes > 0 here, there where extra bytes before the marker (not good).
+
+ return c;
+}
+//------------------------------------------------------------------------------
+// Process markers. Returns when an SOFx, SOI, EOI, or SOS marker is
+// encountered.
+static uint8 processMarkers(uint8* pMarker) {
+ for (;;) {
+ uint8 c = nextMarker();
+
+ switch (c) {
+ case M_SOF0:
+ case M_SOF1:
+ case M_SOF2:
+ case M_SOF3:
+ case M_SOF5:
+ case M_SOF6:
+ case M_SOF7:
+ // case M_JPG:
+ case M_SOF9:
+ case M_SOF10:
+ case M_SOF11:
+ case M_SOF13:
+ case M_SOF14:
+ case M_SOF15:
+ case M_SOI:
+ case M_EOI:
+ case M_SOS: {
+ *pMarker = c;
+ return 0;
+ }
+ case M_DHT: {
+ readDHTMarker();
+ break;
+ }
+ // Sorry, no arithmetic support at this time. Dumb patents!
+ case M_DAC: {
+ return PJPG_NO_ARITHMITIC_SUPPORT;
+ }
+ case M_DQT: {
+ readDQTMarker();
+ break;
+ }
+ case M_DRI: {
+ readDRIMarker();
+ break;
+ }
+ // case M_APP0: /* no need to read the JFIF marker */
+
+ case M_JPG:
+ case M_RST0: /* no parameters */
+ case M_RST1:
+ case M_RST2:
+ case M_RST3:
+ case M_RST4:
+ case M_RST5:
+ case M_RST6:
+ case M_RST7:
+ case M_TEM: {
+ return PJPG_UNEXPECTED_MARKER;
+ }
+ default: /* must be DNL, DHP, EXP, APPn, JPGn, COM, or RESn or APP0 */
+ {
+ skipVariableMarker();
+ break;
+ }
+ }
+ }
+ // return 0;
+}
+//------------------------------------------------------------------------------
+// Finds the start of image (SOI) marker.
+static uint8 locateSOIMarker(void) {
+ uint16 bytesleft;
+
+ uint8 lastchar = (uint8)getBits1(8);
+
+ uint8 thischar = (uint8)getBits1(8);
+
+ /* ok if it's a normal JPEG file without a special header */
+
+ if ((lastchar == 0xFF) && (thischar == M_SOI)) return 0;
+
+ bytesleft = 4096; // 512;
+
+ for (;;) {
+ if (--bytesleft == 0) return PJPG_NOT_JPEG;
+
+ lastchar = thischar;
+
+ thischar = (uint8)getBits1(8);
+
+ if (lastchar == 0xFF) {
+ if (thischar == M_SOI)
+ break;
+ else if (thischar == M_EOI) // getBits1 will keep returning M_EOI if we read past the end
+ return PJPG_NOT_JPEG;
+ }
+ }
+
+ /* Check the next character after marker: if it's not 0xFF, it can't
+ be the start of the next marker, so the file is bad */
+
+ thischar = (uint8)((gBitBuf >> 8) & 0xFF);
+
+ if (thischar != 0xFF) return PJPG_NOT_JPEG;
+
+ return 0;
+}
+//------------------------------------------------------------------------------
+// Find a start of frame (SOF) marker.
+static uint8 locateSOFMarker(void) {
+ uint8 c;
+
+ uint8 status = locateSOIMarker();
+ if (status) return status;
+
+ status = processMarkers(&c);
+ if (status) return status;
+
+ switch (c) {
+ case M_SOF2: {
+ // Progressive JPEG - not supported by picojpeg (would require too
+ // much memory, or too many IDCT's for embedded systems).
+ return PJPG_UNSUPPORTED_MODE;
+ }
+ case M_SOF0: /* baseline DCT */
+ {
+ status = readSOFMarker();
+ if (status) return status;
+
+ break;
+ }
+ case M_SOF9: {
+ return PJPG_NO_ARITHMITIC_SUPPORT;
+ }
+ case M_SOF1: /* extended sequential DCT */
+ default: {
+ return PJPG_UNSUPPORTED_MARKER;
+ }
+ }
+
+ return 0;
+}
+//------------------------------------------------------------------------------
+// Find a start of scan (SOS) marker.
+static uint8 locateSOSMarker(uint8* pFoundEOI) {
+ uint8 c;
+ uint8 status;
+
+ *pFoundEOI = 0;
+
+ status = processMarkers(&c);
+ if (status) return status;
+
+ if (c == M_EOI) {
+ *pFoundEOI = 1;
+ return 0;
+ } else if (c != M_SOS)
+ return PJPG_UNEXPECTED_MARKER;
+
+ return readSOSMarker();
+}
+//------------------------------------------------------------------------------
+static uint8 init(void) {
+ gImageXSize = 0;
+ gImageYSize = 0;
+ gCompsInFrame = 0;
+ gRestartInterval = 0;
+ gCompsInScan = 0;
+ gValidHuffTables = 0;
+ gValidQuantTables = 0;
+ gTemFlag = 0;
+ gInBufOfs = 0;
+ gInBufLeft = 0;
+ gBitBuf = 0;
+ gBitsLeft = 8;
+
+ getBits1(8);
+ getBits1(8);
+
+ return 0;
+}
+//------------------------------------------------------------------------------
+// This method throws back into the stream any bytes that where read
+// into the bit buffer during initial marker scanning.
+static void fixInBuffer(void) {
+ /* In case any 0xFF's where pulled into the buffer during marker scanning */
+
+ if (gBitsLeft > 0) stuffChar((uint8)gBitBuf);
+
+ stuffChar((uint8)(gBitBuf >> 8));
+
+ gBitsLeft = 8;
+ getBits2(8);
+ getBits2(8);
+}
+//------------------------------------------------------------------------------
+// Restart interval processing.
+static uint8 processRestart(void) {
+ // Let's scan a little bit to find the marker, but not _too_ far.
+ // 1536 is a "fudge factor" that determines how much to scan.
+ uint16 i;
+ uint8 c = 0;
+
+ for (i = 1536; i > 0; i--)
+ if (getChar() == 0xFF) break;
+
+ if (i == 0) return PJPG_BAD_RESTART_MARKER;
+
+ for (; i > 0; i--)
+ if ((c = getChar()) != 0xFF) break;
+
+ if (i == 0) return PJPG_BAD_RESTART_MARKER;
+
+ // Is it the expected marker? If not, something bad happened.
+ if (c != (gNextRestartNum + M_RST0)) return PJPG_BAD_RESTART_MARKER;
+
+ // Reset each component's DC prediction values.
+ gLastDC[0] = 0;
+ gLastDC[1] = 0;
+ gLastDC[2] = 0;
+
+ gRestartsLeft = gRestartInterval;
+
+ gNextRestartNum = (gNextRestartNum + 1) & 7;
+
+ // Get the bit buffer going again
+
+ gBitsLeft = 8;
+ getBits2(8);
+ getBits2(8);
+
+ return 0;
+}
+//------------------------------------------------------------------------------
+// FIXME: findEOI() is not actually called at the end of the image
+// (it's optional, and probably not needed on embedded devices)
+static uint8 findEOI(void) {
+ uint8 c;
+ uint8 status;
+
+ // Prime the bit buffer
+ gBitsLeft = 8;
+ getBits1(8);
+ getBits1(8);
+
+ // The next marker _should_ be EOI
+ status = processMarkers(&c);
+ if (status)
+ return status;
+ else if (gCallbackStatus)
+ return gCallbackStatus;
+
+ // gTotalBytesRead -= in_buf_left;
+ if (c != M_EOI) return PJPG_UNEXPECTED_MARKER;
+
+ return 0;
+}
+//------------------------------------------------------------------------------
+static uint8 checkHuffTables(void) {
+ uint8 i;
+
+ for (i = 0; i < gCompsInScan; i++) {
+ uint8 compDCTab = gCompDCTab[gCompList[i]];
+ uint8 compACTab = gCompACTab[gCompList[i]] + 2;
+
+ if (((gValidHuffTables & (1 << compDCTab)) == 0) || ((gValidHuffTables & (1 << compACTab)) == 0))
+ return PJPG_UNDEFINED_HUFF_TABLE;
+ }
+
+ return 0;
+}
+//------------------------------------------------------------------------------
+static uint8 checkQuantTables(void) {
+ uint8 i;
+
+ for (i = 0; i < gCompsInScan; i++) {
+ uint8 compQuantMask = gCompQuant[gCompList[i]] ? 2 : 1;
+
+ if ((gValidQuantTables & compQuantMask) == 0) return PJPG_UNDEFINED_QUANT_TABLE;
+ }
+
+ return 0;
+}
+//------------------------------------------------------------------------------
+static uint8 initScan(void) {
+ uint8 foundEOI;
+ uint8 status = locateSOSMarker(&foundEOI);
+ if (status) return status;
+ if (foundEOI) return PJPG_UNEXPECTED_MARKER;
+
+ status = checkHuffTables();
+ if (status) return status;
+
+ status = checkQuantTables();
+ if (status) return status;
+
+ gLastDC[0] = 0;
+ gLastDC[1] = 0;
+ gLastDC[2] = 0;
+
+ if (gRestartInterval) {
+ gRestartsLeft = gRestartInterval;
+ gNextRestartNum = 0;
+ }
+
+ fixInBuffer();
+
+ return 0;
+}
+//------------------------------------------------------------------------------
+static uint8 initFrame(void) {
+ if (gCompsInFrame == 1) {
+ if ((gCompHSamp[0] != 1) || (gCompVSamp[0] != 1)) return PJPG_UNSUPPORTED_SAMP_FACTORS;
+
+ gScanType = PJPG_GRAYSCALE;
+
+ gMaxBlocksPerMCU = 1;
+ gMCUOrg[0] = 0;
+
+ gMaxMCUXSize = 8;
+ gMaxMCUYSize = 8;
+ } else if (gCompsInFrame == 3) {
+ if (((gCompHSamp[1] != 1) || (gCompVSamp[1] != 1)) || ((gCompHSamp[2] != 1) || (gCompVSamp[2] != 1)))
+ return PJPG_UNSUPPORTED_SAMP_FACTORS;
+
+ if ((gCompHSamp[0] == 1) && (gCompVSamp[0] == 1)) {
+ gScanType = PJPG_YH1V1;
+
+ gMaxBlocksPerMCU = 3;
+ gMCUOrg[0] = 0;
+ gMCUOrg[1] = 1;
+ gMCUOrg[2] = 2;
+
+ gMaxMCUXSize = 8;
+ gMaxMCUYSize = 8;
+ } else if ((gCompHSamp[0] == 1) && (gCompVSamp[0] == 2)) {
+ gScanType = PJPG_YH1V2;
+
+ gMaxBlocksPerMCU = 4;
+ gMCUOrg[0] = 0;
+ gMCUOrg[1] = 0;
+ gMCUOrg[2] = 1;
+ gMCUOrg[3] = 2;
+
+ gMaxMCUXSize = 8;
+ gMaxMCUYSize = 16;
+ } else if ((gCompHSamp[0] == 2) && (gCompVSamp[0] == 1)) {
+ gScanType = PJPG_YH2V1;
+
+ gMaxBlocksPerMCU = 4;
+ gMCUOrg[0] = 0;
+ gMCUOrg[1] = 0;
+ gMCUOrg[2] = 1;
+ gMCUOrg[3] = 2;
+
+ gMaxMCUXSize = 16;
+ gMaxMCUYSize = 8;
+ } else if ((gCompHSamp[0] == 2) && (gCompVSamp[0] == 2)) {
+ gScanType = PJPG_YH2V2;
+
+ gMaxBlocksPerMCU = 6;
+ gMCUOrg[0] = 0;
+ gMCUOrg[1] = 0;
+ gMCUOrg[2] = 0;
+ gMCUOrg[3] = 0;
+ gMCUOrg[4] = 1;
+ gMCUOrg[5] = 2;
+
+ gMaxMCUXSize = 16;
+ gMaxMCUYSize = 16;
+ } else
+ return PJPG_UNSUPPORTED_SAMP_FACTORS;
+ } else
+ return PJPG_UNSUPPORTED_COLORSPACE;
+
+ gMaxMCUSPerRow = (gImageXSize + (gMaxMCUXSize - 1)) >> ((gMaxMCUXSize == 8) ? 3 : 4);
+ gMaxMCUSPerCol = (gImageYSize + (gMaxMCUYSize - 1)) >> ((gMaxMCUYSize == 8) ? 3 : 4);
+
+ // This can overflow on large JPEG's.
+ // gNumMCUSRemaining = gMaxMCUSPerRow * gMaxMCUSPerCol;
+ gNumMCUSRemainingX = gMaxMCUSPerRow;
+ gNumMCUSRemainingY = gMaxMCUSPerCol;
+
+ return 0;
+}
+//----------------------------------------------------------------------------
+// Winograd IDCT: 5 multiplies per row/col, up to 80 muls for the 2D IDCT
+
+#define PJPG_DCT_SCALE_BITS 7
+
+#define PJPG_DCT_SCALE (1U << PJPG_DCT_SCALE_BITS)
+
+#define PJPG_DESCALE(x) PJPG_ARITH_SHIFT_RIGHT_N_16(((x) + (1 << (PJPG_DCT_SCALE_BITS - 1))), PJPG_DCT_SCALE_BITS)
+
+#define PJPG_WFIX(x) ((x) * PJPG_DCT_SCALE + 0.5f)
+
+#define PJPG_WINOGRAD_QUANT_SCALE_BITS 10
+
+const uint8 gWinogradQuant[] = {
+ 128, 178, 178, 167, 246, 167, 151, 232, 232, 151, 128, 209, 219, 209, 128, 101, 178, 197, 197, 178, 101, 69,
+ 139, 167, 177, 167, 139, 69, 35, 96, 131, 151, 151, 131, 96, 35, 49, 91, 118, 128, 118, 91, 49, 46,
+ 81, 101, 101, 81, 46, 42, 69, 79, 69, 42, 35, 54, 54, 35, 28, 37, 28, 19, 19, 10,
+};
+
+// Multiply quantization matrix by the Winograd IDCT scale factors
+static void createWinogradQuant(int16* pQuant) {
+ uint8 i;
+
+ for (i = 0; i < 64; i++) {
+ long x = pQuant[i];
+ x *= gWinogradQuant[i];
+ pQuant[i] = (int16)((x + (1 << (PJPG_WINOGRAD_QUANT_SCALE_BITS - PJPG_DCT_SCALE_BITS - 1))) >>
+ (PJPG_WINOGRAD_QUANT_SCALE_BITS - PJPG_DCT_SCALE_BITS));
+ }
+}
+
+// These multiply helper functions are the 4 types of signed multiplies needed by the Winograd IDCT.
+// A smart C compiler will optimize them to use 16x8 = 24 bit muls, if not you may need to tweak
+// these functions or drop to CPU specific inline assembly.
+
+// 1/cos(4*pi/16)
+// 362, 256+106
+static PJPG_INLINE int16 imul_b1_b3(int16 w) {
+ long x = (w * 362L);
+ x += 128L;
+ return (int16)(PJPG_ARITH_SHIFT_RIGHT_8_L(x));
+}
+
+// 1/cos(6*pi/16)
+// 669, 256+256+157
+static PJPG_INLINE int16 imul_b2(int16 w) {
+ long x = (w * 669L);
+ x += 128L;
+ return (int16)(PJPG_ARITH_SHIFT_RIGHT_8_L(x));
+}
+
+// 1/cos(2*pi/16)
+// 277, 256+21
+static PJPG_INLINE int16 imul_b4(int16 w) {
+ long x = (w * 277L);
+ x += 128L;
+ return (int16)(PJPG_ARITH_SHIFT_RIGHT_8_L(x));
+}
+
+// 1/(cos(2*pi/16) + cos(6*pi/16))
+// 196, 196
+static PJPG_INLINE int16 imul_b5(int16 w) {
+ long x = (w * 196L);
+ x += 128L;
+ return (int16)(PJPG_ARITH_SHIFT_RIGHT_8_L(x));
+}
+
+static PJPG_INLINE uint8 clamp(int16 s) {
+ if ((uint16)s > 255U) {
+ if (s < 0)
+ return 0;
+ else if (s > 255)
+ return 255;
+ }
+
+ return (uint8)s;
+}
+
+static void idctRows(void) {
+ uint8 i;
+ int16* pSrc = gCoeffBuf;
+
+ for (i = 0; i < 8; i++) {
+ if ((pSrc[1] | pSrc[2] | pSrc[3] | pSrc[4] | pSrc[5] | pSrc[6] | pSrc[7]) == 0) {
+ // Short circuit the 1D IDCT if only the DC component is non-zero
+ int16 src0 = *pSrc;
+
+ *(pSrc + 1) = src0;
+ *(pSrc + 2) = src0;
+ *(pSrc + 3) = src0;
+ *(pSrc + 4) = src0;
+ *(pSrc + 5) = src0;
+ *(pSrc + 6) = src0;
+ *(pSrc + 7) = src0;
+ } else {
+ int16 src4 = *(pSrc + 5);
+ int16 src7 = *(pSrc + 3);
+ int16 x4 = src4 - src7;
+ int16 x7 = src4 + src7;
+
+ int16 src5 = *(pSrc + 1);
+ int16 src6 = *(pSrc + 7);
+ int16 x5 = src5 + src6;
+ int16 x6 = src5 - src6;
+
+ int16 tmp1 = imul_b5(x4 - x6);
+ int16 stg26 = imul_b4(x6) - tmp1;
+
+ int16 x24 = tmp1 - imul_b2(x4);
+
+ int16 x15 = x5 - x7;
+ int16 x17 = x5 + x7;
+
+ int16 tmp2 = stg26 - x17;
+ int16 tmp3 = imul_b1_b3(x15) - tmp2;
+ int16 x44 = tmp3 + x24;
+
+ int16 src0 = *(pSrc + 0);
+ int16 src1 = *(pSrc + 4);
+ int16 x30 = src0 + src1;
+ int16 x31 = src0 - src1;
+
+ int16 src2 = *(pSrc + 2);
+ int16 src3 = *(pSrc + 6);
+ int16 x12 = src2 - src3;
+ int16 x13 = src2 + src3;
+
+ int16 x32 = imul_b1_b3(x12) - x13;
+
+ int16 x40 = x30 + x13;
+ int16 x43 = x30 - x13;
+ int16 x41 = x31 + x32;
+ int16 x42 = x31 - x32;
+
+ *(pSrc + 0) = x40 + x17;
+ *(pSrc + 1) = x41 + tmp2;
+ *(pSrc + 2) = x42 + tmp3;
+ *(pSrc + 3) = x43 - x44;
+ *(pSrc + 4) = x43 + x44;
+ *(pSrc + 5) = x42 - tmp3;
+ *(pSrc + 6) = x41 - tmp2;
+ *(pSrc + 7) = x40 - x17;
+ }
+
+ pSrc += 8;
+ }
+}
+
+static void idctCols(void) {
+ uint8 i;
+
+ int16* pSrc = gCoeffBuf;
+
+ for (i = 0; i < 8; i++) {
+ if ((pSrc[1 * 8] | pSrc[2 * 8] | pSrc[3 * 8] | pSrc[4 * 8] | pSrc[5 * 8] | pSrc[6 * 8] | pSrc[7 * 8]) == 0) {
+ // Short circuit the 1D IDCT if only the DC component is non-zero
+ uint8 c = clamp(PJPG_DESCALE(*pSrc) + 128);
+ *(pSrc + 0 * 8) = c;
+ *(pSrc + 1 * 8) = c;
+ *(pSrc + 2 * 8) = c;
+ *(pSrc + 3 * 8) = c;
+ *(pSrc + 4 * 8) = c;
+ *(pSrc + 5 * 8) = c;
+ *(pSrc + 6 * 8) = c;
+ *(pSrc + 7 * 8) = c;
+ } else {
+ int16 src4 = *(pSrc + 5 * 8);
+ int16 src7 = *(pSrc + 3 * 8);
+ int16 x4 = src4 - src7;
+ int16 x7 = src4 + src7;
+
+ int16 src5 = *(pSrc + 1 * 8);
+ int16 src6 = *(pSrc + 7 * 8);
+ int16 x5 = src5 + src6;
+ int16 x6 = src5 - src6;
+
+ int16 tmp1 = imul_b5(x4 - x6);
+ int16 stg26 = imul_b4(x6) - tmp1;
+
+ int16 x24 = tmp1 - imul_b2(x4);
+
+ int16 x15 = x5 - x7;
+ int16 x17 = x5 + x7;
+
+ int16 tmp2 = stg26 - x17;
+ int16 tmp3 = imul_b1_b3(x15) - tmp2;
+ int16 x44 = tmp3 + x24;
+
+ int16 src0 = *(pSrc + 0 * 8);
+ int16 src1 = *(pSrc + 4 * 8);
+ int16 x30 = src0 + src1;
+ int16 x31 = src0 - src1;
+
+ int16 src2 = *(pSrc + 2 * 8);
+ int16 src3 = *(pSrc + 6 * 8);
+ int16 x12 = src2 - src3;
+ int16 x13 = src2 + src3;
+
+ int16 x32 = imul_b1_b3(x12) - x13;
+
+ int16 x40 = x30 + x13;
+ int16 x43 = x30 - x13;
+ int16 x41 = x31 + x32;
+ int16 x42 = x31 - x32;
+
+ // descale, convert to unsigned and clamp to 8-bit
+ *(pSrc + 0 * 8) = clamp(PJPG_DESCALE(x40 + x17) + 128);
+ *(pSrc + 1 * 8) = clamp(PJPG_DESCALE(x41 + tmp2) + 128);
+ *(pSrc + 2 * 8) = clamp(PJPG_DESCALE(x42 + tmp3) + 128);
+ *(pSrc + 3 * 8) = clamp(PJPG_DESCALE(x43 - x44) + 128);
+ *(pSrc + 4 * 8) = clamp(PJPG_DESCALE(x43 + x44) + 128);
+ *(pSrc + 5 * 8) = clamp(PJPG_DESCALE(x42 - tmp3) + 128);
+ *(pSrc + 6 * 8) = clamp(PJPG_DESCALE(x41 - tmp2) + 128);
+ *(pSrc + 7 * 8) = clamp(PJPG_DESCALE(x40 - x17) + 128);
+ }
+
+ pSrc++;
+ }
+}
+
+/*----------------------------------------------------------------------------*/
+static PJPG_INLINE uint8 addAndClamp(uint8 a, int16 b) {
+ b = a + b;
+
+ if ((uint16)b > 255U) {
+ if (b < 0)
+ return 0;
+ else if (b > 255)
+ return 255;
+ }
+
+ return (uint8)b;
+}
+/*----------------------------------------------------------------------------*/
+static PJPG_INLINE uint8 subAndClamp(uint8 a, int16 b) {
+ b = a - b;
+
+ if ((uint16)b > 255U) {
+ if (b < 0)
+ return 0;
+ else if (b > 255)
+ return 255;
+ }
+
+ return (uint8)b;
+}
+/*----------------------------------------------------------------------------*/
+// 103/256
+// R = Y + 1.402 (Cr-128)
+
+// 88/256, 183/256
+// G = Y - 0.34414 (Cb-128) - 0.71414 (Cr-128)
+
+// 198/256
+// B = Y + 1.772 (Cb-128)
+/*----------------------------------------------------------------------------*/
+// Cb upsample and accumulate, 4x4 to 8x8
+static void upsampleCb(uint8 srcOfs, uint8 dstOfs) {
+ // Cb - affects G and B
+ uint8 x, y;
+ int16* pSrc = gCoeffBuf + srcOfs;
+ uint8* pDstG = gMCUBufG + dstOfs;
+ uint8* pDstB = gMCUBufB + dstOfs;
+ for (y = 0; y < 4; y++) {
+ for (x = 0; x < 4; x++) {
+ uint8 cb = (uint8)*pSrc++;
+ int16 cbG, cbB;
+
+ cbG = ((cb * 88U) >> 8U) - 44U;
+ pDstG[0] = subAndClamp(pDstG[0], cbG);
+ pDstG[1] = subAndClamp(pDstG[1], cbG);
+ pDstG[8] = subAndClamp(pDstG[8], cbG);
+ pDstG[9] = subAndClamp(pDstG[9], cbG);
+
+ cbB = (cb + ((cb * 198U) >> 8U)) - 227U;
+ pDstB[0] = addAndClamp(pDstB[0], cbB);
+ pDstB[1] = addAndClamp(pDstB[1], cbB);
+ pDstB[8] = addAndClamp(pDstB[8], cbB);
+ pDstB[9] = addAndClamp(pDstB[9], cbB);
+
+ pDstG += 2;
+ pDstB += 2;
+ }
+
+ pSrc = pSrc - 4 + 8;
+ pDstG = pDstG - 8 + 16;
+ pDstB = pDstB - 8 + 16;
+ }
+}
+/*----------------------------------------------------------------------------*/
+// Cb upsample and accumulate, 4x8 to 8x8
+static void upsampleCbH(uint8 srcOfs, uint8 dstOfs) {
+ // Cb - affects G and B
+ uint8 x, y;
+ int16* pSrc = gCoeffBuf + srcOfs;
+ uint8* pDstG = gMCUBufG + dstOfs;
+ uint8* pDstB = gMCUBufB + dstOfs;
+ for (y = 0; y < 8; y++) {
+ for (x = 0; x < 4; x++) {
+ uint8 cb = (uint8)*pSrc++;
+ int16 cbG, cbB;
+
+ cbG = ((cb * 88U) >> 8U) - 44U;
+ pDstG[0] = subAndClamp(pDstG[0], cbG);
+ pDstG[1] = subAndClamp(pDstG[1], cbG);
+
+ cbB = (cb + ((cb * 198U) >> 8U)) - 227U;
+ pDstB[0] = addAndClamp(pDstB[0], cbB);
+ pDstB[1] = addAndClamp(pDstB[1], cbB);
+
+ pDstG += 2;
+ pDstB += 2;
+ }
+
+ pSrc = pSrc - 4 + 8;
+ }
+}
+/*----------------------------------------------------------------------------*/
+// Cb upsample and accumulate, 8x4 to 8x8
+static void upsampleCbV(uint8 srcOfs, uint8 dstOfs) {
+ // Cb - affects G and B
+ uint8 x, y;
+ int16* pSrc = gCoeffBuf + srcOfs;
+ uint8* pDstG = gMCUBufG + dstOfs;
+ uint8* pDstB = gMCUBufB + dstOfs;
+ for (y = 0; y < 4; y++) {
+ for (x = 0; x < 8; x++) {
+ uint8 cb = (uint8)*pSrc++;
+ int16 cbG, cbB;
+
+ cbG = ((cb * 88U) >> 8U) - 44U;
+ pDstG[0] = subAndClamp(pDstG[0], cbG);
+ pDstG[8] = subAndClamp(pDstG[8], cbG);
+
+ cbB = (cb + ((cb * 198U) >> 8U)) - 227U;
+ pDstB[0] = addAndClamp(pDstB[0], cbB);
+ pDstB[8] = addAndClamp(pDstB[8], cbB);
+
+ ++pDstG;
+ ++pDstB;
+ }
+
+ pDstG = pDstG - 8 + 16;
+ pDstB = pDstB - 8 + 16;
+ }
+}
+/*----------------------------------------------------------------------------*/
+// 103/256
+// R = Y + 1.402 (Cr-128)
+
+// 88/256, 183/256
+// G = Y - 0.34414 (Cb-128) - 0.71414 (Cr-128)
+
+// 198/256
+// B = Y + 1.772 (Cb-128)
+/*----------------------------------------------------------------------------*/
+// Cr upsample and accumulate, 4x4 to 8x8
+static void upsampleCr(uint8 srcOfs, uint8 dstOfs) {
+ // Cr - affects R and G
+ uint8 x, y;
+ int16* pSrc = gCoeffBuf + srcOfs;
+ uint8* pDstR = gMCUBufR + dstOfs;
+ uint8* pDstG = gMCUBufG + dstOfs;
+ for (y = 0; y < 4; y++) {
+ for (x = 0; x < 4; x++) {
+ uint8 cr = (uint8)*pSrc++;
+ int16 crR, crG;
+
+ crR = (cr + ((cr * 103U) >> 8U)) - 179;
+ pDstR[0] = addAndClamp(pDstR[0], crR);
+ pDstR[1] = addAndClamp(pDstR[1], crR);
+ pDstR[8] = addAndClamp(pDstR[8], crR);
+ pDstR[9] = addAndClamp(pDstR[9], crR);
+
+ crG = ((cr * 183U) >> 8U) - 91;
+ pDstG[0] = subAndClamp(pDstG[0], crG);
+ pDstG[1] = subAndClamp(pDstG[1], crG);
+ pDstG[8] = subAndClamp(pDstG[8], crG);
+ pDstG[9] = subAndClamp(pDstG[9], crG);
+
+ pDstR += 2;
+ pDstG += 2;
+ }
+
+ pSrc = pSrc - 4 + 8;
+ pDstR = pDstR - 8 + 16;
+ pDstG = pDstG - 8 + 16;
+ }
+}
+/*----------------------------------------------------------------------------*/
+// Cr upsample and accumulate, 4x8 to 8x8
+static void upsampleCrH(uint8 srcOfs, uint8 dstOfs) {
+ // Cr - affects R and G
+ uint8 x, y;
+ int16* pSrc = gCoeffBuf + srcOfs;
+ uint8* pDstR = gMCUBufR + dstOfs;
+ uint8* pDstG = gMCUBufG + dstOfs;
+ for (y = 0; y < 8; y++) {
+ for (x = 0; x < 4; x++) {
+ uint8 cr = (uint8)*pSrc++;
+ int16 crR, crG;
+
+ crR = (cr + ((cr * 103U) >> 8U)) - 179;
+ pDstR[0] = addAndClamp(pDstR[0], crR);
+ pDstR[1] = addAndClamp(pDstR[1], crR);
+
+ crG = ((cr * 183U) >> 8U) - 91;
+ pDstG[0] = subAndClamp(pDstG[0], crG);
+ pDstG[1] = subAndClamp(pDstG[1], crG);
+
+ pDstR += 2;
+ pDstG += 2;
+ }
+
+ pSrc = pSrc - 4 + 8;
+ }
+}
+/*----------------------------------------------------------------------------*/
+// Cr upsample and accumulate, 8x4 to 8x8
+static void upsampleCrV(uint8 srcOfs, uint8 dstOfs) {
+ // Cr - affects R and G
+ uint8 x, y;
+ int16* pSrc = gCoeffBuf + srcOfs;
+ uint8* pDstR = gMCUBufR + dstOfs;
+ uint8* pDstG = gMCUBufG + dstOfs;
+ for (y = 0; y < 4; y++) {
+ for (x = 0; x < 8; x++) {
+ uint8 cr = (uint8)*pSrc++;
+ int16 crR, crG;
+
+ crR = (cr + ((cr * 103U) >> 8U)) - 179;
+ pDstR[0] = addAndClamp(pDstR[0], crR);
+ pDstR[8] = addAndClamp(pDstR[8], crR);
+
+ crG = ((cr * 183U) >> 8U) - 91;
+ pDstG[0] = subAndClamp(pDstG[0], crG);
+ pDstG[8] = subAndClamp(pDstG[8], crG);
+
+ ++pDstR;
+ ++pDstG;
+ }
+
+ pDstR = pDstR - 8 + 16;
+ pDstG = pDstG - 8 + 16;
+ }
+}
+/*----------------------------------------------------------------------------*/
+// Convert Y to RGB
+static void copyY(uint8 dstOfs) {
+ uint8 i;
+ uint8* pRDst = gMCUBufR + dstOfs;
+ uint8* pGDst = gMCUBufG + dstOfs;
+ uint8* pBDst = gMCUBufB + dstOfs;
+ int16* pSrc = gCoeffBuf;
+
+ for (i = 64; i > 0; i--) {
+ uint8 c = (uint8)*pSrc++;
+
+ *pRDst++ = c;
+ *pGDst++ = c;
+ *pBDst++ = c;
+ }
+}
+/*----------------------------------------------------------------------------*/
+// Cb convert to RGB and accumulate
+static void convertCb(uint8 dstOfs) {
+ uint8 i;
+ uint8* pDstG = gMCUBufG + dstOfs;
+ uint8* pDstB = gMCUBufB + dstOfs;
+ int16* pSrc = gCoeffBuf;
+
+ for (i = 64; i > 0; i--) {
+ uint8 cb = (uint8)*pSrc++;
+ int16 cbG, cbB;
+
+ cbG = ((cb * 88U) >> 8U) - 44U;
+ *pDstG++ = subAndClamp(pDstG[0], cbG);
+
+ cbB = (cb + ((cb * 198U) >> 8U)) - 227U;
+ *pDstB++ = addAndClamp(pDstB[0], cbB);
+ }
+}
+/*----------------------------------------------------------------------------*/
+// Cr convert to RGB and accumulate
+static void convertCr(uint8 dstOfs) {
+ uint8 i;
+ uint8* pDstR = gMCUBufR + dstOfs;
+ uint8* pDstG = gMCUBufG + dstOfs;
+ int16* pSrc = gCoeffBuf;
+
+ for (i = 64; i > 0; i--) {
+ uint8 cr = (uint8)*pSrc++;
+ int16 crR, crG;
+
+ crR = (cr + ((cr * 103U) >> 8U)) - 179;
+ *pDstR++ = addAndClamp(pDstR[0], crR);
+
+ crG = ((cr * 183U) >> 8U) - 91;
+ *pDstG++ = subAndClamp(pDstG[0], crG);
+ }
+}
+/*----------------------------------------------------------------------------*/
+static void transformBlock(uint8 mcuBlock) {
+ idctRows();
+ idctCols();
+
+ switch (gScanType) {
+ case PJPG_GRAYSCALE: {
+ // MCU size: 1, 1 block per MCU
+ copyY(0);
+ break;
+ }
+ case PJPG_YH1V1: {
+ // MCU size: 8x8, 3 blocks per MCU
+ switch (mcuBlock) {
+ case 0: {
+ copyY(0);
+ break;
+ }
+ case 1: {
+ convertCb(0);
+ break;
+ }
+ case 2: {
+ convertCr(0);
+ break;
+ }
+ }
+
+ break;
+ }
+ case PJPG_YH1V2: {
+ // MCU size: 8x16, 4 blocks per MCU
+ switch (mcuBlock) {
+ case 0: {
+ copyY(0);
+ break;
+ }
+ case 1: {
+ copyY(128);
+ break;
+ }
+ case 2: {
+ upsampleCbV(0, 0);
+ upsampleCbV(4 * 8, 128);
+ break;
+ }
+ case 3: {
+ upsampleCrV(0, 0);
+ upsampleCrV(4 * 8, 128);
+ break;
+ }
+ }
+
+ break;
+ }
+ case PJPG_YH2V1: {
+ // MCU size: 16x8, 4 blocks per MCU
+ switch (mcuBlock) {
+ case 0: {
+ copyY(0);
+ break;
+ }
+ case 1: {
+ copyY(64);
+ break;
+ }
+ case 2: {
+ upsampleCbH(0, 0);
+ upsampleCbH(4, 64);
+ break;
+ }
+ case 3: {
+ upsampleCrH(0, 0);
+ upsampleCrH(4, 64);
+ break;
+ }
+ }
+
+ break;
+ }
+ case PJPG_YH2V2: {
+ // MCU size: 16x16, 6 blocks per MCU
+ switch (mcuBlock) {
+ case 0: {
+ copyY(0);
+ break;
+ }
+ case 1: {
+ copyY(64);
+ break;
+ }
+ case 2: {
+ copyY(128);
+ break;
+ }
+ case 3: {
+ copyY(192);
+ break;
+ }
+ case 4: {
+ upsampleCb(0, 0);
+ upsampleCb(4, 64);
+ upsampleCb(4 * 8, 128);
+ upsampleCb(4 + 4 * 8, 192);
+ break;
+ }
+ case 5: {
+ upsampleCr(0, 0);
+ upsampleCr(4, 64);
+ upsampleCr(4 * 8, 128);
+ upsampleCr(4 + 4 * 8, 192);
+ break;
+ }
+ }
+
+ break;
+ }
+ }
+}
+//------------------------------------------------------------------------------
+static void transformBlockReduce(uint8 mcuBlock) {
+ uint8 c = clamp(PJPG_DESCALE(gCoeffBuf[0]) + 128);
+ int16 cbG, cbB, crR, crG;
+
+ switch (gScanType) {
+ case PJPG_GRAYSCALE: {
+ // MCU size: 1, 1 block per MCU
+ gMCUBufR[0] = c;
+ break;
+ }
+ case PJPG_YH1V1: {
+ // MCU size: 8x8, 3 blocks per MCU
+ switch (mcuBlock) {
+ case 0: {
+ gMCUBufR[0] = c;
+ gMCUBufG[0] = c;
+ gMCUBufB[0] = c;
+ break;
+ }
+ case 1: {
+ cbG = ((c * 88U) >> 8U) - 44U;
+ gMCUBufG[0] = subAndClamp(gMCUBufG[0], cbG);
+
+ cbB = (c + ((c * 198U) >> 8U)) - 227U;
+ gMCUBufB[0] = addAndClamp(gMCUBufB[0], cbB);
+ break;
+ }
+ case 2: {
+ crR = (c + ((c * 103U) >> 8U)) - 179;
+ gMCUBufR[0] = addAndClamp(gMCUBufR[0], crR);
+
+ crG = ((c * 183U) >> 8U) - 91;
+ gMCUBufG[0] = subAndClamp(gMCUBufG[0], crG);
+ break;
+ }
+ }
+
+ break;
+ }
+ case PJPG_YH1V2: {
+ // MCU size: 8x16, 4 blocks per MCU
+ switch (mcuBlock) {
+ case 0: {
+ gMCUBufR[0] = c;
+ gMCUBufG[0] = c;
+ gMCUBufB[0] = c;
+ break;
+ }
+ case 1: {
+ gMCUBufR[128] = c;
+ gMCUBufG[128] = c;
+ gMCUBufB[128] = c;
+ break;
+ }
+ case 2: {
+ cbG = ((c * 88U) >> 8U) - 44U;
+ gMCUBufG[0] = subAndClamp(gMCUBufG[0], cbG);
+ gMCUBufG[128] = subAndClamp(gMCUBufG[128], cbG);
+
+ cbB = (c + ((c * 198U) >> 8U)) - 227U;
+ gMCUBufB[0] = addAndClamp(gMCUBufB[0], cbB);
+ gMCUBufB[128] = addAndClamp(gMCUBufB[128], cbB);
+
+ break;
+ }
+ case 3: {
+ crR = (c + ((c * 103U) >> 8U)) - 179;
+ gMCUBufR[0] = addAndClamp(gMCUBufR[0], crR);
+ gMCUBufR[128] = addAndClamp(gMCUBufR[128], crR);
+
+ crG = ((c * 183U) >> 8U) - 91;
+ gMCUBufG[0] = subAndClamp(gMCUBufG[0], crG);
+ gMCUBufG[128] = subAndClamp(gMCUBufG[128], crG);
+
+ break;
+ }
+ }
+ break;
+ }
+ case PJPG_YH2V1: {
+ // MCU size: 16x8, 4 blocks per MCU
+ switch (mcuBlock) {
+ case 0: {
+ gMCUBufR[0] = c;
+ gMCUBufG[0] = c;
+ gMCUBufB[0] = c;
+ break;
+ }
+ case 1: {
+ gMCUBufR[64] = c;
+ gMCUBufG[64] = c;
+ gMCUBufB[64] = c;
+ break;
+ }
+ case 2: {
+ cbG = ((c * 88U) >> 8U) - 44U;
+ gMCUBufG[0] = subAndClamp(gMCUBufG[0], cbG);
+ gMCUBufG[64] = subAndClamp(gMCUBufG[64], cbG);
+
+ cbB = (c + ((c * 198U) >> 8U)) - 227U;
+ gMCUBufB[0] = addAndClamp(gMCUBufB[0], cbB);
+ gMCUBufB[64] = addAndClamp(gMCUBufB[64], cbB);
+
+ break;
+ }
+ case 3: {
+ crR = (c + ((c * 103U) >> 8U)) - 179;
+ gMCUBufR[0] = addAndClamp(gMCUBufR[0], crR);
+ gMCUBufR[64] = addAndClamp(gMCUBufR[64], crR);
+
+ crG = ((c * 183U) >> 8U) - 91;
+ gMCUBufG[0] = subAndClamp(gMCUBufG[0], crG);
+ gMCUBufG[64] = subAndClamp(gMCUBufG[64], crG);
+
+ break;
+ }
+ }
+ break;
+ }
+ case PJPG_YH2V2: {
+ // MCU size: 16x16, 6 blocks per MCU
+ switch (mcuBlock) {
+ case 0: {
+ gMCUBufR[0] = c;
+ gMCUBufG[0] = c;
+ gMCUBufB[0] = c;
+ break;
+ }
+ case 1: {
+ gMCUBufR[64] = c;
+ gMCUBufG[64] = c;
+ gMCUBufB[64] = c;
+ break;
+ }
+ case 2: {
+ gMCUBufR[128] = c;
+ gMCUBufG[128] = c;
+ gMCUBufB[128] = c;
+ break;
+ }
+ case 3: {
+ gMCUBufR[192] = c;
+ gMCUBufG[192] = c;
+ gMCUBufB[192] = c;
+ break;
+ }
+ case 4: {
+ cbG = ((c * 88U) >> 8U) - 44U;
+ gMCUBufG[0] = subAndClamp(gMCUBufG[0], cbG);
+ gMCUBufG[64] = subAndClamp(gMCUBufG[64], cbG);
+ gMCUBufG[128] = subAndClamp(gMCUBufG[128], cbG);
+ gMCUBufG[192] = subAndClamp(gMCUBufG[192], cbG);
+
+ cbB = (c + ((c * 198U) >> 8U)) - 227U;
+ gMCUBufB[0] = addAndClamp(gMCUBufB[0], cbB);
+ gMCUBufB[64] = addAndClamp(gMCUBufB[64], cbB);
+ gMCUBufB[128] = addAndClamp(gMCUBufB[128], cbB);
+ gMCUBufB[192] = addAndClamp(gMCUBufB[192], cbB);
+
+ break;
+ }
+ case 5: {
+ crR = (c + ((c * 103U) >> 8U)) - 179;
+ gMCUBufR[0] = addAndClamp(gMCUBufR[0], crR);
+ gMCUBufR[64] = addAndClamp(gMCUBufR[64], crR);
+ gMCUBufR[128] = addAndClamp(gMCUBufR[128], crR);
+ gMCUBufR[192] = addAndClamp(gMCUBufR[192], crR);
+
+ crG = ((c * 183U) >> 8U) - 91;
+ gMCUBufG[0] = subAndClamp(gMCUBufG[0], crG);
+ gMCUBufG[64] = subAndClamp(gMCUBufG[64], crG);
+ gMCUBufG[128] = subAndClamp(gMCUBufG[128], crG);
+ gMCUBufG[192] = subAndClamp(gMCUBufG[192], crG);
+
+ break;
+ }
+ }
+ break;
+ }
+ }
+}
+//------------------------------------------------------------------------------
+static uint8 decodeNextMCU(void) {
+ uint8 status;
+ uint8 mcuBlock;
+
+ if (gRestartInterval) {
+ if (gRestartsLeft == 0) {
+ status = processRestart();
+ if (status) return status;
+ }
+ gRestartsLeft--;
+ }
+
+ for (mcuBlock = 0; mcuBlock < gMaxBlocksPerMCU; mcuBlock++) {
+ uint8 componentID = gMCUOrg[mcuBlock];
+ uint8 compQuant = gCompQuant[componentID];
+ uint8 compDCTab = gCompDCTab[componentID];
+ uint8 numExtraBits, compACTab, k;
+ const int16* pQ = compQuant ? gQuant1 : gQuant0;
+ uint16 r, dc;
+
+ uint8 s = huffDecode(compDCTab ? &gHuffTab1 : &gHuffTab0, compDCTab ? gHuffVal1 : gHuffVal0);
+
+ r = 0;
+ numExtraBits = s & 0xF;
+ if (numExtraBits) r = getBits2(numExtraBits);
+ dc = huffExtend(r, s);
+
+ dc = dc + gLastDC[componentID];
+ gLastDC[componentID] = dc;
+
+ gCoeffBuf[0] = dc * pQ[0];
+
+ compACTab = gCompACTab[componentID];
+
+ if (gReduce) {
+ // Decode, but throw out the AC coefficients in reduce mode.
+ for (k = 1; k < 64; k++) {
+ s = huffDecode(compACTab ? &gHuffTab3 : &gHuffTab2, compACTab ? gHuffVal3 : gHuffVal2);
+
+ numExtraBits = s & 0xF;
+ if (numExtraBits) getBits2(numExtraBits);
+
+ r = s >> 4;
+ s &= 15;
+
+ if (s) {
+ if (r) {
+ if ((k + r) > 63) return PJPG_DECODE_ERROR;
+
+ k = (uint8)(k + r);
+ }
+ } else {
+ if (r == 15) {
+ if ((k + 16) > 64) return PJPG_DECODE_ERROR;
+
+ k += (16 - 1); // - 1 because the loop counter is k
+ } else
+ break;
+ }
+ }
+
+ transformBlockReduce(mcuBlock);
+ } else {
+ // Decode and dequantize AC coefficients
+ for (k = 1; k < 64; k++) {
+ uint16 extraBits;
+
+ s = huffDecode(compACTab ? &gHuffTab3 : &gHuffTab2, compACTab ? gHuffVal3 : gHuffVal2);
+
+ extraBits = 0;
+ numExtraBits = s & 0xF;
+ if (numExtraBits) extraBits = getBits2(numExtraBits);
+
+ r = s >> 4;
+ s &= 15;
+
+ if (s) {
+ int16 ac;
+
+ if (r) {
+ if ((k + r) > 63) return PJPG_DECODE_ERROR;
+
+ while (r) {
+ gCoeffBuf[ZAG[k++]] = 0;
+ r--;
+ }
+ }
+
+ ac = huffExtend(extraBits, s);
+
+ gCoeffBuf[ZAG[k]] = ac * pQ[k];
+ } else {
+ if (r == 15) {
+ if ((k + 16) > 64) return PJPG_DECODE_ERROR;
+
+ for (r = 16; r > 0; r--) gCoeffBuf[ZAG[k++]] = 0;
+
+ k--; // - 1 because the loop counter is k
+ } else
+ break;
+ }
+ }
+
+ while (k < 64) gCoeffBuf[ZAG[k++]] = 0;
+
+ transformBlock(mcuBlock);
+ }
+ }
+
+ return 0;
+}
+//------------------------------------------------------------------------------
+unsigned char pjpeg_decode_mcu(void) {
+ uint8 status;
+
+ if (gCallbackStatus) return gCallbackStatus;
+
+ if ((!gNumMCUSRemainingX) && (!gNumMCUSRemainingY)) return PJPG_NO_MORE_BLOCKS;
+
+ status = decodeNextMCU();
+ if ((status) || (gCallbackStatus)) return gCallbackStatus ? gCallbackStatus : status;
+
+ gNumMCUSRemainingX--;
+ if (!gNumMCUSRemainingX) {
+ gNumMCUSRemainingY--;
+ if (gNumMCUSRemainingY > 0) gNumMCUSRemainingX = gMaxMCUSPerRow;
+ }
+
+ return 0;
+}
+//------------------------------------------------------------------------------
+unsigned char pjpeg_decode_init(pjpeg_image_info_t* pInfo, pjpeg_need_bytes_callback_t pNeed_bytes_callback,
+ void* pCallback_data, unsigned char reduce) {
+ uint8 status;
+
+ pInfo->m_width = 0;
+ pInfo->m_height = 0;
+ pInfo->m_comps = 0;
+ pInfo->m_MCUSPerRow = 0;
+ pInfo->m_MCUSPerCol = 0;
+ pInfo->m_scanType = PJPG_GRAYSCALE;
+ pInfo->m_MCUWidth = 0;
+ pInfo->m_MCUHeight = 0;
+ pInfo->m_pMCUBufR = (unsigned char*)0;
+ pInfo->m_pMCUBufG = (unsigned char*)0;
+ pInfo->m_pMCUBufB = (unsigned char*)0;
+
+ g_pNeedBytesCallback = pNeed_bytes_callback;
+ g_pCallback_data = pCallback_data;
+ gCallbackStatus = 0;
+ gReduce = reduce;
+
+ status = init();
+ if ((status) || (gCallbackStatus)) return gCallbackStatus ? gCallbackStatus : status;
+
+ status = locateSOFMarker();
+ if ((status) || (gCallbackStatus)) return gCallbackStatus ? gCallbackStatus : status;
+
+ status = initFrame();
+ if ((status) || (gCallbackStatus)) return gCallbackStatus ? gCallbackStatus : status;
+
+ status = initScan();
+ if ((status) || (gCallbackStatus)) return gCallbackStatus ? gCallbackStatus : status;
+
+ pInfo->m_width = gImageXSize;
+ pInfo->m_height = gImageYSize;
+ pInfo->m_comps = gCompsInFrame;
+ pInfo->m_scanType = gScanType;
+ pInfo->m_MCUSPerRow = gMaxMCUSPerRow;
+ pInfo->m_MCUSPerCol = gMaxMCUSPerCol;
+ pInfo->m_MCUWidth = gMaxMCUXSize;
+ pInfo->m_MCUHeight = gMaxMCUYSize;
+ pInfo->m_pMCUBufR = gMCUBufR;
+ pInfo->m_pMCUBufG = gMCUBufG;
+ pInfo->m_pMCUBufB = gMCUBufB;
+
+ return 0;
+}
diff --git a/lib/picojpeg/picojpeg.h b/lib/picojpeg/picojpeg.h
new file mode 100644
index 0000000..11345fb
--- /dev/null
+++ b/lib/picojpeg/picojpeg.h
@@ -0,0 +1,124 @@
+//------------------------------------------------------------------------------
+// picojpeg - Public domain, Rich Geldreich
+//------------------------------------------------------------------------------
+#ifndef PICOJPEG_H
+#define PICOJPEG_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Error codes
+enum {
+ PJPG_NO_MORE_BLOCKS = 1,
+ PJPG_BAD_DHT_COUNTS,
+ PJPG_BAD_DHT_INDEX,
+ PJPG_BAD_DHT_MARKER,
+ PJPG_BAD_DQT_MARKER,
+ PJPG_BAD_DQT_TABLE,
+ PJPG_BAD_PRECISION,
+ PJPG_BAD_HEIGHT,
+ PJPG_BAD_WIDTH,
+ PJPG_TOO_MANY_COMPONENTS,
+ PJPG_BAD_SOF_LENGTH,
+ PJPG_BAD_VARIABLE_MARKER,
+ PJPG_BAD_DRI_LENGTH,
+ PJPG_BAD_SOS_LENGTH,
+ PJPG_BAD_SOS_COMP_ID,
+ PJPG_W_EXTRA_BYTES_BEFORE_MARKER,
+ PJPG_NO_ARITHMITIC_SUPPORT,
+ PJPG_UNEXPECTED_MARKER,
+ PJPG_NOT_JPEG,
+ PJPG_UNSUPPORTED_MARKER,
+ PJPG_BAD_DQT_LENGTH,
+ PJPG_TOO_MANY_BLOCKS,
+ PJPG_UNDEFINED_QUANT_TABLE,
+ PJPG_UNDEFINED_HUFF_TABLE,
+ PJPG_NOT_SINGLE_SCAN,
+ PJPG_UNSUPPORTED_COLORSPACE,
+ PJPG_UNSUPPORTED_SAMP_FACTORS,
+ PJPG_DECODE_ERROR,
+ PJPG_BAD_RESTART_MARKER,
+ PJPG_ASSERTION_ERROR,
+ PJPG_BAD_SOS_SPECTRAL,
+ PJPG_BAD_SOS_SUCCESSIVE,
+ PJPG_STREAM_READ_ERROR,
+ PJPG_NOTENOUGHMEM,
+ PJPG_UNSUPPORTED_COMP_IDENT,
+ PJPG_UNSUPPORTED_QUANT_TABLE,
+ PJPG_UNSUPPORTED_MODE, // picojpeg doesn't support progressive JPEG's
+};
+
+// Scan types
+typedef enum { PJPG_GRAYSCALE, PJPG_YH1V1, PJPG_YH2V1, PJPG_YH1V2, PJPG_YH2V2 } pjpeg_scan_type_t;
+
+typedef struct {
+ // Image resolution
+ int m_width;
+ int m_height;
+
+ // Number of components (1 or 3)
+ int m_comps;
+
+ // Total number of minimum coded units (MCU's) per row/col.
+ int m_MCUSPerRow;
+ int m_MCUSPerCol;
+
+ // Scan type
+ pjpeg_scan_type_t m_scanType;
+
+ // MCU width/height in pixels (each is either 8 or 16 depending on the scan type)
+ int m_MCUWidth;
+ int m_MCUHeight;
+
+ // m_pMCUBufR, m_pMCUBufG, and m_pMCUBufB are pointers to internal MCU Y or RGB pixel component buffers.
+ // Each time pjpegDecodeMCU() is called successfully these buffers will be filled with 8x8 pixel blocks of Y or RGB
+ // pixels. Each MCU consists of (m_MCUWidth/8)*(m_MCUHeight/8) Y/RGB blocks: 1 for greyscale/no subsampling, 2 for
+ // H1V2/H2V1, or 4 blocks for H2V2 sampling factors. Each block is a contiguous array of 64 (8x8) bytes of a single
+ // component: either Y for grayscale images, or R, G or B components for color images.
+ //
+ // The 8x8 pixel blocks are organized in these byte arrays like this:
+ //
+ // PJPG_GRAYSCALE: Each MCU is decoded to a single block of 8x8 grayscale pixels.
+ // Only the values in m_pMCUBufR are valid. Each 8 bytes is a row of pixels (raster order: left to right, top to
+ // bottom) from the 8x8 block.
+ //
+ // PJPG_H1V1: Each MCU contains is decoded to a single block of 8x8 RGB pixels.
+ //
+ // PJPG_YH2V1: Each MCU is decoded to 2 blocks, or 16x8 pixels.
+ // The 2 RGB blocks are at byte offsets: 0, 64
+ //
+ // PJPG_YH1V2: Each MCU is decoded to 2 blocks, or 8x16 pixels.
+ // The 2 RGB blocks are at byte offsets: 0,
+ // 128
+ //
+ // PJPG_YH2V2: Each MCU is decoded to 4 blocks, or 16x16 pixels.
+ // The 2x2 block array is organized at byte offsets: 0, 64,
+ // 128, 192
+ //
+ // It is up to the caller to copy or blit these pixels from these buffers into the destination bitmap.
+ unsigned char* m_pMCUBufR;
+ unsigned char* m_pMCUBufG;
+ unsigned char* m_pMCUBufB;
+} pjpeg_image_info_t;
+
+typedef unsigned char (*pjpeg_need_bytes_callback_t)(unsigned char* pBuf, unsigned char buf_size,
+ unsigned char* pBytes_actually_read, void* pCallback_data);
+
+// Initializes the decompressor. Returns 0 on success, or one of the above error codes on failure.
+// pNeed_bytes_callback will be called to fill the decompressor's internal input buffer.
+// If reduce is 1, only the first pixel of each block will be decoded. This mode is much faster because it skips the AC
+// dequantization, IDCT and chroma upsampling of every image pixel. Not thread safe.
+unsigned char pjpeg_decode_init(pjpeg_image_info_t* pInfo, pjpeg_need_bytes_callback_t pNeed_bytes_callback,
+ void* pCallback_data, unsigned char reduce);
+
+// Decompresses the file's next MCU. Returns 0 on success, PJPG_NO_MORE_BLOCKS if no more blocks are available, or an
+// error code. Must be called a total of m_MCUSPerRow*m_MCUSPerCol times to completely decompress the image. Not thread
+// safe.
+unsigned char pjpeg_decode_mcu(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PICOJPEG_H
From 6aa5d41a429f9ba40c0c561d3cc81ffd4eb5a7aa Mon Sep 17 00:00:00 2001
From: Sam Davis
Date: Sun, 21 Dec 2025 18:32:50 +1100
Subject: [PATCH 12/33] Add info about sleep screen customisation to user guide
(#88)
## Summary
- Updates user guide with information about using custom sleep screens
## Additional Context
N/A
---
USER_GUIDE.md | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/USER_GUIDE.md b/USER_GUIDE.md
index f5a9bd0..333fa72 100644
--- a/USER_GUIDE.md
+++ b/USER_GUIDE.md
@@ -64,6 +64,18 @@ The Settings screen allows you to configure the device's behavior. There are a f
paragraphs will not have vertical space between them, but will have first word indentation.
- **Short Power Button Click**: Whether to trigger the power button on a short press or a long press.
+### 3.6 Sleep Screen
+
+You can customize the sleep screen by placing custom images in specific locations on the SD card:
+
+- **Single Image:** Place a file named `sleep.bmp` in the root directory.
+- **Multiple Images:** Create a `sleep` directory in the root of the SD card and place any number of `.bmp` images inside. If images are found in this directory, they will take priority over the `sleep.png` file, and one will be randomly selected each time the device sleeps.
+
+> [!TIP]
+> For best results:
+> - Use uncompressed BMP files with 24-bit color depth
+> - Use a resolution of 480x800 pixels to match the device's screen resolution.
+
---
## 4. Reading Mode
From 958508eb6bf53c0b2ec6443a08937dc188948e81 Mon Sep 17 00:00:00 2001
From: Dave Allie
Date: Sun, 21 Dec 2025 18:41:52 +1100
Subject: [PATCH 13/33] Prevent boot loop if last open epub crashes on load
(#87)
## Summary
* Unset openEpubPath on boot and set once epub fully loaded
## Additional Context
* If an epub was crashing when loading, it was possible to get the
device stuck into a loop. There was no way to get back to the home
screen as we'd always load you back into old epub
* Break this loop by clearing the stored value when we boot, still
jumping to the last open epub, but only resetting that value once the
epub has been fully loaded
---
src/activities/reader/EpubReaderActivity.cpp | 5 +++++
src/activities/reader/ReaderActivity.cpp | 3 ---
src/main.cpp | 6 +++++-
3 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp
index 8827e99..9635952 100644
--- a/src/activities/reader/EpubReaderActivity.cpp
+++ b/src/activities/reader/EpubReaderActivity.cpp
@@ -6,6 +6,7 @@
#include "Battery.h"
#include "CrossPointSettings.h"
+#include "CrossPointState.h"
#include "EpubReaderChapterSelectionActivity.h"
#include "config.h"
@@ -44,6 +45,10 @@ void EpubReaderActivity::onEnter() {
f.close();
}
+ // Save current epub as last opened epub
+ APP_STATE.openEpubPath = epub->getPath();
+ APP_STATE.saveToFile();
+
// Trigger first update
updateRequired = true;
diff --git a/src/activities/reader/ReaderActivity.cpp b/src/activities/reader/ReaderActivity.cpp
index 099d7e2..93389fe 100644
--- a/src/activities/reader/ReaderActivity.cpp
+++ b/src/activities/reader/ReaderActivity.cpp
@@ -2,7 +2,6 @@
#include
-#include "CrossPointState.h"
#include "Epub.h"
#include "EpubReaderActivity.h"
#include "FileSelectionActivity.h"
@@ -29,8 +28,6 @@ void ReaderActivity::onSelectEpubFile(const std::string& path) {
auto epub = loadEpub(path);
if (epub) {
- APP_STATE.openEpubPath = path;
- APP_STATE.saveToFile();
onGoToEpubReader(std::move(epub));
} else {
exitActivity();
diff --git a/src/main.cpp b/src/main.cpp
index 89d2af4..5dfc25b 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -194,7 +194,11 @@ void setup() {
if (APP_STATE.openEpubPath.empty()) {
onGoHome();
} else {
- onGoToReader(APP_STATE.openEpubPath);
+ // Clear app state to avoid getting into a boot loop if the epub doesn't load
+ const auto path = APP_STATE.openEpubPath;
+ APP_STATE.openEpubPath = "";
+ APP_STATE.saveToFile();
+ onGoToReader(path);
}
// Ensure we're not still holding the power button before leaving setup
From 955c78de649aaa39f2b8e357934dfe55c7ff9055 Mon Sep 17 00:00:00 2001
From: Dave Allie
Date: Sun, 21 Dec 2025 18:42:06 +1100
Subject: [PATCH 14/33] Book cover sleep screen (#89)
## Summary
* Fix issue with 2-bit bmp rendering
* Add support generate book cover BMP from JPG and use as sleep screen
## Additional Context
* It does not support other image formats beyond JPG at this point
* Something is cooked with my JpegToBmpConverter logic, it generates
weird interlaced looking images for some JPGs
| Book 1 | Book 2|
| --- | --- |
|

|

|
---
USER_GUIDE.md | 13 ++-
lib/Epub/Epub.cpp | 41 +++++++-
lib/Epub/Epub.h | 3 +-
lib/GfxRenderer/Bitmap.cpp | 39 +++++---
src/CrossPointSettings.cpp | 4 +-
src/CrossPointSettings.h | 5 +-
src/activities/boot_sleep/SleepActivity.cpp | 98 ++++++++++++++------
src/activities/boot_sleep/SleepActivity.h | 6 +-
src/activities/settings/SettingsActivity.cpp | 35 ++++---
src/activities/settings/SettingsActivity.h | 9 +-
10 files changed, 182 insertions(+), 71 deletions(-)
diff --git a/USER_GUIDE.md b/USER_GUIDE.md
index 333fa72..a19a1e8 100644
--- a/USER_GUIDE.md
+++ b/USER_GUIDE.md
@@ -59,7 +59,11 @@ See the [webserver docs](./docs/webserver.md) for more information on how to con
### 3.5 Settings
The Settings screen allows you to configure the device's behavior. There are a few settings you can adjust:
-- **White Sleep Screen**: Whether to use the white screen or black (inverted) default sleep screen
+- **Sleep Screen**: Which sleep screen to display when the device sleeps, options are:
+ - "Dark" (default) - The default dark sleep screen
+ - "Light" - The same default sleep screen, on a white background
+ - "Custom" - Custom images from the SD card, see [3.6 Sleep Screen](#36-sleep-screen) below for more information
+ - "Cover" - The book cover image (Note: this is experimental and may not work as expected)
- **Extra Paragraph Spacing**: If enabled, vertical space will be added between paragraphs in the book, if disabled,
paragraphs will not have vertical space between them, but will have first word indentation.
- **Short Power Button Click**: Whether to trigger the power button on a short press or a long press.
@@ -69,7 +73,12 @@ The Settings screen allows you to configure the device's behavior. There are a f
You can customize the sleep screen by placing custom images in specific locations on the SD card:
- **Single Image:** Place a file named `sleep.bmp` in the root directory.
-- **Multiple Images:** Create a `sleep` directory in the root of the SD card and place any number of `.bmp` images inside. If images are found in this directory, they will take priority over the `sleep.png` file, and one will be randomly selected each time the device sleeps.
+- **Multiple Images:** Create a `sleep` directory in the root of the SD card and place any number of `.bmp` images
+ inside. If images are found in this directory, they will take priority over the `sleep.png` file, and one will be
+ randomly selected each time the device sleeps.
+
+> [!NOTE]
+> You'll need to set the **Sleep Screen** setting to **Custom** in order to use these images.
> [!TIP]
> For best results:
diff --git a/lib/Epub/Epub.cpp b/lib/Epub/Epub.cpp
index cc3bc90..d959cb7 100644
--- a/lib/Epub/Epub.cpp
+++ b/lib/Epub/Epub.cpp
@@ -1,6 +1,7 @@
#include "Epub.h"
#include
+#include
#include
#include
@@ -218,7 +219,45 @@ const std::string& Epub::getPath() const { return filepath; }
const std::string& Epub::getTitle() const { return title; }
-const std::string& Epub::getCoverImageItem() const { return coverImageItem; }
+std::string Epub::getCoverBmpPath() const { return cachePath + "/cover.bmp"; }
+
+bool Epub::generateCoverBmp() const {
+ // Already generated, return true
+ if (SD.exists(getCoverBmpPath().c_str())) {
+ return true;
+ }
+
+ if (coverImageItem.empty()) {
+ Serial.printf("[%lu] [EBP] No known cover image\n", millis());
+ return false;
+ }
+
+ if (coverImageItem.substr(coverImageItem.length() - 4) == ".jpg" ||
+ coverImageItem.substr(coverImageItem.length() - 5) == ".jpeg") {
+ Serial.printf("[%lu] [EBP] Generating BMP from JPG cover image\n", millis());
+ File coverJpg = SD.open((getCachePath() + "/.cover.jpg").c_str(), FILE_WRITE, true);
+ readItemContentsToStream(coverImageItem, coverJpg, 1024);
+ coverJpg.close();
+
+ coverJpg = SD.open((getCachePath() + "/.cover.jpg").c_str(), FILE_READ);
+ File coverBmp = SD.open(getCoverBmpPath().c_str(), FILE_WRITE, true);
+ const bool success = JpegToBmpConverter::jpegFileToBmpStream(coverJpg, coverBmp);
+ coverJpg.close();
+ coverBmp.close();
+ SD.remove((getCachePath() + "/.cover.jpg").c_str());
+
+ if (!success) {
+ Serial.printf("[%lu] [EBP] Failed to generate BMP from JPG cover image\n", millis());
+ SD.remove(getCoverBmpPath().c_str());
+ }
+ Serial.printf("[%lu] [EBP] Generated BMP from JPG cover image, success: %s\n", millis(), success ? "yes" : "no");
+ return success;
+ } else {
+ Serial.printf("[%lu] [EBP] Cover image is not a JPG, skipping\n", millis());
+ }
+
+ return false;
+}
std::string normalisePath(const std::string& path) {
std::vector components;
diff --git a/lib/Epub/Epub.h b/lib/Epub/Epub.h
index 3115303..381379c 100644
--- a/lib/Epub/Epub.h
+++ b/lib/Epub/Epub.h
@@ -48,7 +48,8 @@ class Epub {
const std::string& getCachePath() const;
const std::string& getPath() const;
const std::string& getTitle() const;
- const std::string& getCoverImageItem() const;
+ std::string getCoverBmpPath() const;
+ bool generateCoverBmp() const;
uint8_t* readItemContentsToBytes(const std::string& itemHref, size_t* size = nullptr,
bool trailingNullByte = false) const;
bool readItemContentsToStream(const std::string& itemHref, Print& out, size_t chunkSize) const;
diff --git a/lib/GfxRenderer/Bitmap.cpp b/lib/GfxRenderer/Bitmap.cpp
index 0e0b0d6..c9ad6f8 100644
--- a/lib/GfxRenderer/Bitmap.cpp
+++ b/lib/GfxRenderer/Bitmap.cpp
@@ -128,7 +128,7 @@ BmpReaderError Bitmap::readRow(uint8_t* data, uint8_t* rowBuffer) const {
int bitShift = 6;
// Helper lambda to pack 2bpp color into the output stream
- auto packPixel = [&](uint8_t lum) {
+ auto packPixel = [&](const uint8_t lum) {
uint8_t color = (lum >> 6); // Simple 2-bit reduction: 0-255 -> 0-3
currentOutByte |= (color << bitShift);
if (bitShift == 0) {
@@ -140,38 +140,49 @@ BmpReaderError Bitmap::readRow(uint8_t* data, uint8_t* rowBuffer) const {
}
};
+ uint8_t lum;
+
switch (bpp) {
- case 8: {
+ case 32: {
+ const uint8_t* p = rowBuffer;
for (int x = 0; x < width; x++) {
- packPixel(paletteLum[rowBuffer[x]]);
+ lum = (77u * p[2] + 150u * p[1] + 29u * p[0]) >> 8;
+ packPixel(lum);
+ p += 4;
}
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;
+ lum = (77u * p[2] + 150u * p[1] + 29u * p[0]) >> 8;
packPixel(lum);
p += 3;
}
break;
}
+ case 8: {
+ for (int x = 0; x < width; x++) {
+ packPixel(paletteLum[rowBuffer[x]]);
+ }
+ break;
+ }
+ case 2: {
+ for (int x = 0; x < width; x++) {
+ lum = paletteLum[(rowBuffer[x >> 2] >> (6 - ((x & 3) * 2))) & 0x03];
+ packPixel(lum);
+ }
+ break;
+ }
case 1: {
for (int x = 0; x < width; x++) {
- uint8_t lum = (rowBuffer[x >> 3] & (0x80 >> (x & 7))) ? 0xFF : 0x00;
+ 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;
- }
+ default:
+ return BmpReaderError::UnsupportedBpp;
}
// Flush remaining bits if width is not a multiple of 4
diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp
index 8959072..fe5e2a0 100644
--- a/src/CrossPointSettings.cpp
+++ b/src/CrossPointSettings.cpp
@@ -23,7 +23,7 @@ bool CrossPointSettings::saveToFile() const {
std::ofstream outputFile(SETTINGS_FILE);
serialization::writePod(outputFile, SETTINGS_FILE_VERSION);
serialization::writePod(outputFile, SETTINGS_COUNT);
- serialization::writePod(outputFile, whiteSleepScreen);
+ serialization::writePod(outputFile, sleepScreen);
serialization::writePod(outputFile, extraParagraphSpacing);
serialization::writePod(outputFile, shortPwrBtn);
outputFile.close();
@@ -54,7 +54,7 @@ bool CrossPointSettings::loadFromFile() {
// load settings that exist
uint8_t settingsRead = 0;
do {
- serialization::readPod(inputFile, whiteSleepScreen);
+ serialization::readPod(inputFile, sleepScreen);
if (++settingsRead >= fileSettingsCount) break;
serialization::readPod(inputFile, extraParagraphSpacing);
if (++settingsRead >= fileSettingsCount) break;
diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h
index d6ad766..14c3332 100644
--- a/src/CrossPointSettings.h
+++ b/src/CrossPointSettings.h
@@ -15,8 +15,11 @@ class CrossPointSettings {
CrossPointSettings(const CrossPointSettings&) = delete;
CrossPointSettings& operator=(const CrossPointSettings&) = delete;
+ // Should match with SettingsActivity text
+ enum SLEEP_SCREEN_MODE { DARK = 0, LIGHT = 1, CUSTOM = 2, COVER = 3 };
+
// Sleep screen settings
- uint8_t whiteSleepScreen = 0;
+ uint8_t sleepScreen = DARK;
// Text rendering settings
uint8_t extraParagraphSpacing = 1;
// Duration of the power button press
diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp
index 900a0db..4200c4e 100644
--- a/src/activities/boot_sleep/SleepActivity.cpp
+++ b/src/activities/boot_sleep/SleepActivity.cpp
@@ -1,16 +1,45 @@
#include "SleepActivity.h"
+#include
#include
#include
#include
#include "CrossPointSettings.h"
+#include "CrossPointState.h"
#include "config.h"
#include "images/CrossLarge.h"
void SleepActivity::onEnter() {
renderPopup("Entering Sleep...");
+
+ if (SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::CUSTOM) {
+ return renderCustomSleepScreen();
+ }
+
+ if (SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::COVER) {
+ return renderCoverSleepScreen();
+ }
+
+ renderDefaultSleepScreen();
+}
+
+void SleepActivity::renderPopup(const char* message) const {
+ const int textWidth = renderer.getTextWidth(READER_FONT_ID, message);
+ constexpr int margin = 20;
+ const int x = (renderer.getScreenWidth() - textWidth - margin * 2) / 2;
+ constexpr int y = 117;
+ const int w = textWidth + margin * 2;
+ const int h = renderer.getLineHeight(READER_FONT_ID) + margin * 2;
+ // renderer.clearScreen();
+ renderer.fillRect(x + 5, y + 5, w - 10, h - 10, false);
+ renderer.drawText(READER_FONT_ID, x + margin, y + margin, message);
+ renderer.drawRect(x + 5, y + 5, w - 10, h - 10);
+ renderer.displayBuffer();
+}
+
+void SleepActivity::renderCustomSleepScreen() const {
// Check if we have a /sleep directory
auto dir = SD.open("/sleep");
if (dir && dir.isDirectory()) {
@@ -28,31 +57,31 @@ void SleepActivity::onEnter() {
}
if (filename.substr(filename.length() - 4) != ".bmp") {
- Serial.printf("[%lu] [Slp] Skipping non-.bmp file name: %s\n", millis(), file.name());
+ Serial.printf("[%lu] [SLP] Skipping non-.bmp file name: %s\n", millis(), file.name());
file.close();
continue;
}
Bitmap bitmap(file);
if (bitmap.parseHeaders() != BmpReaderError::Ok) {
- Serial.printf("[%lu] [Slp] Skipping invalid BMP file: %s\n", millis(), file.name());
+ Serial.printf("[%lu] [SLP] Skipping invalid BMP file: %s\n", millis(), file.name());
file.close();
continue;
}
files.emplace_back(filename);
file.close();
}
- int numFiles = files.size();
+ const auto numFiles = files.size();
if (numFiles > 0) {
// Generate a random number between 1 and numFiles
- int randomFileIndex = random(numFiles);
- auto filename = "/sleep/" + files[randomFileIndex];
+ const auto randomFileIndex = random(numFiles);
+ const auto filename = "/sleep/" + files[randomFileIndex];
auto file = SD.open(filename.c_str());
if (file) {
- Serial.printf("[%lu] [Slp] Randomly loading: /sleep/%s\n", millis(), files[randomFileIndex].c_str());
+ Serial.printf("[%lu] [SLP] Randomly loading: /sleep/%s\n", millis(), files[randomFileIndex].c_str());
delay(100);
Bitmap bitmap(file);
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
- renderCustomSleepScreen(bitmap);
+ renderBitmapSleepScreen(bitmap);
dir.close();
return;
}
@@ -67,8 +96,8 @@ void SleepActivity::onEnter() {
if (file) {
Bitmap bitmap(file);
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
- Serial.printf("[%lu] [Slp] Loading: /sleep.bmp\n", millis());
- renderCustomSleepScreen(bitmap);
+ Serial.printf("[%lu] [SLP] Loading: /sleep.bmp\n", millis());
+ renderBitmapSleepScreen(bitmap);
return;
}
}
@@ -76,41 +105,27 @@ void SleepActivity::onEnter() {
renderDefaultSleepScreen();
}
-void SleepActivity::renderPopup(const char* message) const {
- const int textWidth = renderer.getTextWidth(READER_FONT_ID, message);
- constexpr int margin = 20;
- const int x = (GfxRenderer::getScreenWidth() - textWidth - margin * 2) / 2;
- constexpr int y = 117;
- const int w = textWidth + margin * 2;
- const int h = renderer.getLineHeight(READER_FONT_ID) + margin * 2;
- // renderer.clearScreen();
- renderer.fillRect(x + 5, y + 5, w - 10, h - 10, false);
- renderer.drawText(READER_FONT_ID, x + margin, y + margin, message);
- renderer.drawRect(x + 5, y + 5, w - 10, h - 10);
- renderer.displayBuffer();
-}
-
void SleepActivity::renderDefaultSleepScreen() const {
- const auto pageWidth = GfxRenderer::getScreenWidth();
- const auto pageHeight = GfxRenderer::getScreenHeight();
+ const auto pageWidth = renderer.getScreenWidth();
+ const auto pageHeight = renderer.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) {
+ // Make sleep screen dark unless light is selected in settings
+ if (SETTINGS.sleepScreen != CrossPointSettings::SLEEP_SCREEN_MODE::LIGHT) {
renderer.invertScreen();
}
renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
}
-void SleepActivity::renderCustomSleepScreen(const Bitmap& bitmap) const {
+void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const {
int x, y;
- const auto pageWidth = GfxRenderer::getScreenWidth();
- const auto pageHeight = GfxRenderer::getScreenHeight();
+ const auto pageWidth = renderer.getScreenWidth();
+ const auto pageHeight = renderer.getScreenHeight();
if (bitmap.getWidth() > pageWidth || bitmap.getHeight() > pageHeight) {
// image will scale, make sure placement is right
@@ -153,3 +168,26 @@ void SleepActivity::renderCustomSleepScreen(const Bitmap& bitmap) const {
renderer.setRenderMode(GfxRenderer::BW);
}
}
+
+void SleepActivity::renderCoverSleepScreen() const {
+ Epub lastEpub(APP_STATE.openEpubPath, "/.crosspoint");
+ if (!lastEpub.load()) {
+ Serial.println("[SLP] Failed to load last epub");
+ return renderDefaultSleepScreen();
+ }
+ if (!lastEpub.generateCoverBmp()) {
+ Serial.println("[SLP] Failed to generate cover bmp");
+ return renderDefaultSleepScreen();
+ }
+
+ auto file = SD.open(lastEpub.getCoverBmpPath().c_str(), FILE_READ);
+ if (file) {
+ Bitmap bitmap(file);
+ if (bitmap.parseHeaders() == BmpReaderError::Ok) {
+ renderBitmapSleepScreen(bitmap);
+ return;
+ }
+ }
+
+ renderDefaultSleepScreen();
+}
diff --git a/src/activities/boot_sleep/SleepActivity.h b/src/activities/boot_sleep/SleepActivity.h
index defc1d5..2112199 100644
--- a/src/activities/boot_sleep/SleepActivity.h
+++ b/src/activities/boot_sleep/SleepActivity.h
@@ -9,7 +9,9 @@ class SleepActivity final : public Activity {
void onEnter() override;
private:
- void renderDefaultSleepScreen() const;
- void renderCustomSleepScreen(const Bitmap& bitmap) const;
void renderPopup(const char* message) const;
+ void renderDefaultSleepScreen() const;
+ void renderCustomSleepScreen() const;
+ void renderCoverSleepScreen() const;
+ void renderBitmapSleepScreen(const Bitmap& bitmap) const;
};
diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp
index b3acf3f..29f6876 100644
--- a/src/activities/settings/SettingsActivity.cpp
+++ b/src/activities/settings/SettingsActivity.cpp
@@ -6,11 +6,14 @@
#include "config.h"
// Define the static settings list
-
-const SettingInfo SettingsActivity::settingsList[settingsCount] = {
- {"White Sleep Screen", SettingType::TOGGLE, &CrossPointSettings::whiteSleepScreen},
- {"Extra Paragraph Spacing", SettingType::TOGGLE, &CrossPointSettings::extraParagraphSpacing},
- {"Short Power Button Click", SettingType::TOGGLE, &CrossPointSettings::shortPwrBtn}};
+namespace {
+constexpr int settingsCount = 3;
+const SettingInfo settingsList[settingsCount] = {
+ // Should match with SLEEP_SCREEN_MODE
+ {"Sleep Screen", SettingType::ENUM, &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover"}},
+ {"Extra Paragraph Spacing", SettingType::TOGGLE, &CrossPointSettings::extraParagraphSpacing, {}},
+ {"Short Power Button Click", SettingType::TOGGLE, &CrossPointSettings::shortPwrBtn, {}}};
+} // namespace
void SettingsActivity::taskTrampoline(void* param) {
auto* self = static_cast(param);
@@ -81,15 +84,18 @@ void SettingsActivity::toggleCurrentSetting() {
const auto& setting = settingsList[selectedSettingIndex];
- // Only toggle if it's a toggle type and has a value pointer
- if (setting.type != SettingType::TOGGLE || setting.valuePtr == nullptr) {
+ if (setting.type == SettingType::TOGGLE && setting.valuePtr != nullptr) {
+ // Toggle the boolean value using the member pointer
+ const bool currentValue = SETTINGS.*(setting.valuePtr);
+ SETTINGS.*(setting.valuePtr) = !currentValue;
+ } else if (setting.type == SettingType::ENUM && setting.valuePtr != nullptr) {
+ const uint8_t currentValue = SETTINGS.*(setting.valuePtr);
+ SETTINGS.*(setting.valuePtr) = (currentValue + 1) % static_cast(setting.enumValues.size());
+ } else {
+ // Only toggle if it's a toggle type and has a value pointer
return;
}
- // Toggle the boolean value using the member pointer
- bool currentValue = SETTINGS.*(setting.valuePtr);
- SETTINGS.*(setting.valuePtr) = !currentValue;
-
// Save settings when they change
SETTINGS.saveToFile();
}
@@ -129,8 +135,13 @@ void SettingsActivity::render() const {
// Draw value based on setting type
if (settingsList[i].type == SettingType::TOGGLE && settingsList[i].valuePtr != nullptr) {
- bool value = SETTINGS.*(settingsList[i].valuePtr);
+ const bool value = SETTINGS.*(settingsList[i].valuePtr);
renderer.drawText(UI_FONT_ID, pageWidth - 80, settingY, value ? "ON" : "OFF");
+ } else if (settingsList[i].type == SettingType::ENUM && settingsList[i].valuePtr != nullptr) {
+ const uint8_t value = SETTINGS.*(settingsList[i].valuePtr);
+ auto valueText = settingsList[i].enumValues[value];
+ const auto width = renderer.getTextWidth(UI_FONT_ID, valueText.c_str());
+ renderer.drawText(UI_FONT_ID, pageWidth - 50 - width, settingY, valueText.c_str());
}
}
diff --git a/src/activities/settings/SettingsActivity.h b/src/activities/settings/SettingsActivity.h
index 7843a5c..333f467 100644
--- a/src/activities/settings/SettingsActivity.h
+++ b/src/activities/settings/SettingsActivity.h
@@ -12,13 +12,14 @@
class CrossPointSettings;
-enum class SettingType { TOGGLE };
+enum class SettingType { TOGGLE, ENUM };
// Structure to hold setting information
struct SettingInfo {
const char* name; // Display name of the setting
SettingType type; // Type of setting
- uint8_t CrossPointSettings::* valuePtr; // Pointer to member in CrossPointSettings (for TOGGLE)
+ uint8_t CrossPointSettings::* valuePtr; // Pointer to member in CrossPointSettings (for TOGGLE/ENUM)
+ std::vector enumValues;
};
class SettingsActivity final : public Activity {
@@ -28,10 +29,6 @@ class SettingsActivity final : public Activity {
int selectedSettingIndex = 0; // Currently selected setting
const std::function onGoHome;
- // Static settings list
- static constexpr int settingsCount = 3; // Number of settings
- static const SettingInfo settingsList[settingsCount];
-
static void taskTrampoline(void* param);
[[noreturn]] void displayTaskLoop();
void render() const;
From 424104f8ff5f6c00fa7838079a615f346c1f8498 Mon Sep 17 00:00:00 2001
From: Dave Allie
Date: Sun, 21 Dec 2025 19:01:00 +1100
Subject: [PATCH 15/33] Fix incorrect justification of last line in paragraph
(#90)
## Summary
* Fix incorrect justification of last line in paragraph
* `words` is changing size due to the slice, so `isLastLine` would
rarely be right, either removing justification mid-paragraph, or
including it in the last line.
## Additional Context
* Introduced in #73
---
lib/Epub/Epub/ParsedText.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/Epub/Epub/ParsedText.cpp b/lib/Epub/Epub/ParsedText.cpp
index eff3fd6..d73f80a 100644
--- a/lib/Epub/Epub/ParsedText.cpp
+++ b/lib/Epub/Epub/ParsedText.cpp
@@ -144,7 +144,7 @@ void ParsedText::extractLine(const size_t breakIndex, const int pageWidth, const
const int spareSpace = pageWidth - lineWordWidthSum;
int spacing = spaceWidth;
- const bool isLastLine = lineBreak == words.size();
+ const bool isLastLine = breakIndex == lineBreakIndices.size() - 1;
if (style == TextBlock::JUSTIFIED && !isLastLine && lineWordCount >= 2) {
spacing = spareSpace / (lineWordCount - 1);
From febf79a98aafacae82909339b6ec79e53fc00eaa Mon Sep 17 00:00:00 2001
From: Arthur Tazhitdinov
Date: Sun, 21 Dec 2025 13:01:11 +0500
Subject: [PATCH 16/33] Fix: restores cyrillic glyphs to Pixel Arial font (#70)
## Summary
* adds cyrillic glyphs to pixel arial font, used as Small font in UI
## Additional Context
* with recent changes pixel arial font lost cyrillic glyphs
---
lib/EpdFont/builtinFonts/pixelarial14.h | 979 ++++++++++++++++++++++--
src/config.h | 2 +-
2 files changed, 919 insertions(+), 62 deletions(-)
diff --git a/lib/EpdFont/builtinFonts/pixelarial14.h b/lib/EpdFont/builtinFonts/pixelarial14.h
index c8e6a0e..70c9e5c 100644
--- a/lib/EpdFont/builtinFonts/pixelarial14.h
+++ b/lib/EpdFont/builtinFonts/pixelarial14.h
@@ -7,71 +7,452 @@
#pragma once
#include "EpdFontData.h"
-static const uint8_t pixelarial14Bitmaps[1145] = {
- 0xFF, 0xFF, 0xFA, 0xC0, 0xFF, 0xFF, 0xFB, 0x18, 0x63, 0x0C, 0x63, 0x98, 0xCF, 0xFF, 0xFF, 0x9C, 0xE3, 0x18, 0xFF,
- 0xFF, 0xFB, 0x9C, 0x63, 0x0C, 0x60, 0x30, 0xF3, 0xF7, 0xBF, 0x1F, 0x1F, 0x9B, 0x37, 0xEF, 0xFB, 0xC3, 0x00, 0x70,
- 0x67, 0xCE, 0x36, 0x61, 0xB3, 0x0D, 0xB0, 0x7D, 0x81, 0xDD, 0xC0, 0xDE, 0x07, 0x98, 0xEC, 0xC6, 0x66, 0x33, 0xF3,
- 0x07, 0x00, 0x3E, 0x0F, 0xE1, 0x8C, 0x31, 0x86, 0x60, 0xFC, 0x1E, 0x03, 0xE0, 0xC7, 0x98, 0xF3, 0x0E, 0x7F, 0xE7,
- 0xE6, 0xFF, 0xE0, 0x37, 0x66, 0xCC, 0xCC, 0xCC, 0xCC, 0xC6, 0x67, 0x30, 0xCE, 0x66, 0x33, 0x33, 0x33, 0x33, 0x36,
- 0x6E, 0xC0, 0x6F, 0xF6, 0xFB, 0x08, 0x0C, 0x06, 0x03, 0x0F, 0xFF, 0xFC, 0x60, 0x30, 0x18, 0x04, 0x00, 0xBF, 0xC0,
- 0x03, 0xEF, 0x80, 0xB0, 0x18, 0x61, 0x8C, 0x30, 0xC7, 0x18, 0x61, 0x8E, 0x30, 0xC0, 0x7E, 0xFF, 0xC3, 0xC3, 0xC3,
+static const uint8_t pixelarial14Bitmaps[8296] = {
+ 0xFF, 0xFF, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0x1C, 0x63, 0x8C, 0xF7, 0x98, 0xCF, 0xFF, 0xFF, 0xDC, 0xE7, 0xFE, 0xFF,
+ 0xFF, 0xFB, 0x9C, 0x63, 0x0C, 0x60, 0x30, 0xF3, 0xFF, 0xBF, 0x1F, 0x9F, 0x9B, 0x37, 0xEF, 0xFB, 0xE3, 0x00, 0x70,
+ 0x67, 0xCF, 0x37, 0x61, 0xBB, 0x0D, 0xF0, 0x7D, 0x81, 0xDD, 0xC0, 0xDF, 0x07, 0x98, 0xFC, 0xC7, 0x66, 0x7B, 0xF3,
+ 0x07, 0x00, 0x3E, 0x0F, 0xE1, 0x8C, 0x31, 0x86, 0x60, 0xFC, 0x1F, 0x07, 0xE4, 0xC7, 0x98, 0xF3, 0x0E, 0x7F, 0xF7,
+ 0xE6, 0xFF, 0xF0, 0x37, 0x66, 0xCC, 0xCC, 0xCC, 0xCC, 0xC6, 0x67, 0x30, 0xCE, 0x66, 0x33, 0x33, 0x33, 0x33, 0x36,
+ 0x6E, 0xC0, 0x6F, 0xF6, 0xFF, 0x08, 0x0E, 0x07, 0x03, 0x8F, 0xFF, 0xFC, 0x70, 0x38, 0x1C, 0x0E, 0x00, 0xFF, 0xC0,
+ 0xFB, 0xFF, 0x80, 0xF0, 0x1C, 0x73, 0xCC, 0x30, 0xC7, 0x18, 0x61, 0x8E, 0x30, 0xC0, 0x7E, 0xFF, 0xC3, 0xC3, 0xC3,
0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0x7E, 0x37, 0xFF, 0x33, 0x33, 0x33, 0x33, 0x30, 0x7E, 0xFF, 0xC3, 0xC3,
- 0x03, 0x03, 0x07, 0x06, 0x18, 0x38, 0x70, 0xFF, 0xFF, 0x7E, 0xFF, 0xC3, 0xC3, 0x03, 0x3F, 0x3F, 0x03, 0x03, 0xC3,
- 0xC3, 0xFF, 0x7E, 0x03, 0x03, 0x83, 0xC3, 0x63, 0x31, 0x99, 0xCC, 0xC6, 0xC3, 0x7F, 0xFF, 0xE0, 0x60, 0x30, 0x7F,
+ 0x03, 0x03, 0x07, 0x0E, 0x1C, 0x38, 0x70, 0xFF, 0xFF, 0x7E, 0xFF, 0xC3, 0xC3, 0x03, 0x3F, 0x3F, 0x03, 0x03, 0xC3,
+ 0xC3, 0xFF, 0x7E, 0x03, 0x03, 0x83, 0xC3, 0xE3, 0x31, 0x99, 0xCD, 0xC6, 0xC3, 0x7F, 0xFF, 0xE0, 0x60, 0x30, 0x7F,
0x7F, 0xE0, 0xC0, 0xFE, 0xFF, 0xC3, 0x03, 0x03, 0xC3, 0xC7, 0xFE, 0x7E, 0x7E, 0xFF, 0xC3, 0xC0, 0xC0, 0xFE, 0xFF,
- 0xE3, 0xC3, 0xC3, 0xC3, 0xFF, 0x7E, 0xFF, 0xFF, 0x07, 0x06, 0x06, 0x0E, 0x18, 0x18, 0x18, 0x38, 0x30, 0x30, 0x30,
+ 0xE3, 0xC3, 0xC3, 0xC3, 0xFF, 0x7E, 0xFF, 0xFF, 0x07, 0x06, 0x06, 0x1E, 0x1C, 0x1C, 0x1C, 0x38, 0x30, 0x30, 0x30,
0x7E, 0xFF, 0xC3, 0xC3, 0xC3, 0xFF, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0x7E, 0x7E, 0xFF, 0xC3, 0xC3, 0xC3, 0xC7,
- 0xFF, 0x7B, 0x03, 0x03, 0xC7, 0xFE, 0x78, 0xB0, 0x00, 0x2C, 0xB0, 0x00, 0x2F, 0xF0, 0x03, 0x03, 0x1E, 0x7E, 0xF0,
- 0xC0, 0x70, 0x3E, 0x0F, 0x03, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFE, 0x80, 0xC0, 0x70, 0x7E, 0x0F, 0x03, 0x1E, 0x7C,
- 0xF0, 0xC0, 0x7E, 0xFF, 0xC3, 0xC3, 0x03, 0x07, 0x0E, 0x18, 0x30, 0x30, 0x30, 0x10, 0x30, 0x3F, 0x87, 0xFE, 0xEF,
- 0x7D, 0xF3, 0xF1, 0xBF, 0x1B, 0xF3, 0xBF, 0xFF, 0xDF, 0xE6, 0x00, 0x7F, 0x03, 0xF0, 0x06, 0x00, 0xF0, 0x1B, 0x01,
- 0xB0, 0x1B, 0x03, 0xB8, 0x31, 0x83, 0x18, 0x7F, 0xE7, 0xFE, 0xE0, 0x7C, 0x03, 0xC0, 0x30, 0xFF, 0x7F, 0xF0, 0x78,
- 0x3C, 0x1F, 0xFF, 0xFF, 0x83, 0xC1, 0xE0, 0xF0, 0x7F, 0xFF, 0xF0, 0x3F, 0x0F, 0xF3, 0x87, 0x60, 0x3C, 0x01, 0x80,
- 0x30, 0x06, 0x00, 0xC0, 0x18, 0x0F, 0x87, 0x3F, 0xC3, 0xF0, 0xFF, 0x1F, 0xF3, 0x07, 0x60, 0x3C, 0x07, 0x80, 0xF0,
- 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x07, 0x7F, 0xCF, 0xF0, 0xFF, 0xFF, 0xF0, 0x18, 0x0C, 0x07, 0xFF, 0xFF, 0x80, 0xC0,
+ 0xFF, 0x7F, 0x03, 0x03, 0xC7, 0xFE, 0x7C, 0xF0, 0x00, 0x3C, 0xF0, 0x00, 0x3F, 0xF0, 0x03, 0x03, 0x1E, 0x7E, 0xF0,
+ 0xF0, 0x70, 0x7E, 0x1F, 0x03, 0xFF, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xC0, 0xC0, 0x70, 0x7E, 0x1F, 0x0F, 0x1E, 0x7E,
+ 0xF0, 0xC0, 0x7E, 0xFF, 0xC3, 0xC3, 0x03, 0x07, 0x1E, 0x1C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3F, 0x87, 0xFE, 0xFF,
+ 0x7D, 0xFB, 0xF1, 0xBF, 0x1B, 0xF3, 0xBF, 0xFF, 0xDF, 0xE6, 0x00, 0x7F, 0x03, 0xF0, 0x06, 0x01, 0xF0, 0x1F, 0x01,
+ 0xF0, 0x1F, 0x03, 0xB8, 0x31, 0x87, 0xFC, 0x7F, 0xE7, 0xFE, 0xE0, 0x7C, 0x03, 0xC0, 0x30, 0xFF, 0x7F, 0xF0, 0x78,
+ 0x3C, 0x1F, 0xFF, 0xFF, 0x83, 0xC1, 0xE0, 0xF0, 0x7F, 0xFF, 0xF0, 0x3F, 0x0F, 0xF3, 0x87, 0xE0, 0x3C, 0x01, 0x80,
+ 0x30, 0x06, 0x00, 0xC0, 0x18, 0x0F, 0x87, 0xBF, 0xC3, 0xF0, 0xFF, 0x1F, 0xF3, 0x07, 0xE0, 0x3C, 0x07, 0x80, 0xF0,
+ 0x1E, 0x03, 0xC0, 0x78, 0x0F, 0x07, 0xFF, 0xCF, 0xF0, 0xFF, 0xFF, 0xF0, 0x18, 0x0C, 0x07, 0xFF, 0xFF, 0x80, 0xC0,
0x60, 0x30, 0x1F, 0xFF, 0xF8, 0xFF, 0xFF, 0xF0, 0x18, 0x0C, 0x07, 0xFB, 0xFD, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C,
0x00, 0x3F, 0x87, 0xFE, 0xE0, 0x7C, 0x03, 0xC0, 0x0C, 0x00, 0xC1, 0xFC, 0x1F, 0xC0, 0x3C, 0x03, 0xE0, 0x77, 0xFE,
0x3F, 0x80, 0xC1, 0xE0, 0xF0, 0x78, 0x3C, 0x1F, 0xFF, 0xFF, 0x83, 0xC1, 0xE0, 0xF0, 0x78, 0x3C, 0x18, 0xFF, 0xFF,
- 0xFF, 0xC0, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x07, 0x8F, 0x1F, 0xF7, 0x80, 0xC0, 0x78, 0x3B, 0x0E, 0x61,
- 0x8C, 0x61, 0x9C, 0x3F, 0x87, 0xB0, 0xE3, 0x18, 0x63, 0x0E, 0x60, 0xEC, 0x06, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06,
+ 0xFF, 0xC0, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x07, 0x8F, 0x1F, 0xF7, 0xC0, 0xC0, 0x78, 0x3F, 0x0E, 0x61,
+ 0x8C, 0x61, 0xBC, 0x3F, 0x87, 0xB8, 0xE3, 0x1C, 0x63, 0x0E, 0x60, 0xFC, 0x06, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06,
0x03, 0x01, 0x80, 0xC0, 0x60, 0x30, 0x1F, 0xFF, 0xF8, 0xC0, 0x3E, 0x07, 0xE0, 0x7E, 0x07, 0xF1, 0xBF, 0x1B, 0xFB,
- 0xBD, 0xB3, 0xDB, 0x3D, 0xB3, 0xCF, 0x3C, 0x63, 0xC6, 0x30, 0xC1, 0xF0, 0xFC, 0x7E, 0x3F, 0x1F, 0xCF, 0x67, 0xBB,
+ 0xBD, 0xF3, 0xDF, 0x3D, 0xF3, 0xDF, 0x3C, 0x63, 0xC6, 0x30, 0xC1, 0xF0, 0xFC, 0x7E, 0x3F, 0x1F, 0xEF, 0x77, 0xBB,
0xC7, 0xE3, 0xF1, 0xF8, 0x7C, 0x18, 0x3F, 0x87, 0xFE, 0xE0, 0x7C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0,
0x3C, 0x03, 0xE0, 0x77, 0xFE, 0x3F, 0x80, 0xFF, 0x7F, 0xF0, 0x78, 0x3C, 0x1E, 0x0F, 0xFF, 0xFE, 0xC0, 0x60, 0x30,
- 0x18, 0x0C, 0x00, 0x3F, 0x87, 0xFE, 0xE0, 0x7C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x36, 0xE3,
+ 0x18, 0x0C, 0x00, 0x3F, 0x87, 0xFE, 0xE0, 0x7C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x37, 0xE3,
0xE7, 0xFF, 0x3F, 0x70, 0xFF, 0x9F, 0xFF, 0x01, 0xE0, 0x3C, 0x07, 0x80, 0xFF, 0xFF, 0xFC, 0xC6, 0x18, 0xE3, 0x0E,
- 0x60, 0xEC, 0x06, 0x7F, 0x7F, 0xF0, 0x78, 0x3C, 0x07, 0xC1, 0xFE, 0x0F, 0x01, 0xE0, 0xF0, 0x7F, 0xF7, 0xF0, 0xFF,
- 0xFF, 0xC6, 0x03, 0x01, 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xC1, 0xE0, 0xF0, 0x78, 0x3C,
- 0x1E, 0x0F, 0x07, 0x83, 0xC1, 0xE0, 0xF8, 0xEF, 0xE3, 0xE0, 0xC0, 0x3C, 0x03, 0xE0, 0x76, 0x06, 0x60, 0x67, 0x1C,
- 0x31, 0x83, 0x18, 0x1B, 0x01, 0xB0, 0x0F, 0x00, 0x60, 0x06, 0x00, 0xC1, 0x81, 0xE1, 0xF0, 0xF8, 0xD8, 0xEC, 0x6C,
- 0x66, 0x36, 0x33, 0x1B, 0x19, 0xDD, 0xDC, 0x6C, 0x78, 0x36, 0x3C, 0x1B, 0x1E, 0x0F, 0x8F, 0x03, 0x03, 0x01, 0x81,
- 0x80, 0xC0, 0x7C, 0x39, 0x86, 0x30, 0xC3, 0x30, 0x7E, 0x07, 0x80, 0xF0, 0x33, 0x0E, 0x31, 0x86, 0x70, 0xEC, 0x06,
- 0xC0, 0x3E, 0x07, 0x70, 0xE3, 0x18, 0x31, 0x83, 0xB8, 0x0F, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06,
- 0x00, 0xFF, 0xFF, 0xF8, 0x0E, 0x01, 0x80, 0x30, 0x0E, 0x03, 0x80, 0xC0, 0x30, 0x06, 0x01, 0xC0, 0x7F, 0xEF, 0xFE,
- 0xFF, 0x6D, 0xB6, 0xDB, 0x6D, 0xB7, 0xE0, 0xC3, 0x0E, 0x18, 0x61, 0x87, 0x0C, 0x30, 0xC3, 0x86, 0x18, 0xFD, 0xB6,
- 0xDB, 0x6D, 0xB6, 0xDF, 0xE0, 0x30, 0xF1, 0xE3, 0xC7, 0x9D, 0xF1, 0x80, 0xFF, 0xDF, 0xFC, 0xCE, 0x73, 0x00, 0x7C,
- 0x7E, 0xC3, 0x83, 0x3F, 0x3F, 0x63, 0xC3, 0xC7, 0xFF, 0x7B, 0xC0, 0xC0, 0xDC, 0xFE, 0xE3, 0xE3, 0xC3, 0xC3, 0xC3,
- 0xC3, 0xE3, 0xFF, 0xFE, 0x78, 0xF3, 0x1E, 0x3C, 0x18, 0x30, 0x60, 0xC7, 0xFD, 0xE0, 0x03, 0x03, 0x7B, 0x7B, 0xC7,
- 0xC7, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0xFF, 0x7B, 0x7C, 0x7E, 0xC3, 0xC3, 0xFF, 0xFF, 0xC0, 0xC0, 0xC3, 0xFF, 0x7E,
- 0x39, 0xEF, 0xBE, 0x61, 0x86, 0x18, 0x61, 0x86, 0x18, 0x60, 0x7B, 0x7B, 0xC7, 0xC7, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7,
- 0xFF, 0x7B, 0x03, 0xC3, 0xFF, 0x7E, 0xC0, 0xC0, 0xDC, 0xFE, 0xE3, 0xE3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
- 0xFB, 0xFF, 0xFF, 0xC0, 0x33, 0x13, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0xE0, 0xC0, 0xC0, 0xC3, 0xC3, 0xC6, 0xCE,
- 0xF8, 0xF0, 0xF8, 0xCE, 0xC6, 0xC7, 0xC3, 0xFF, 0xFF, 0xFF, 0xC0, 0x98, 0xCF, 0x9E, 0xE7, 0x3E, 0x73, 0xC6, 0x3C,
- 0x63, 0xC6, 0x3C, 0x63, 0xC6, 0x3C, 0x63, 0xC6, 0x30, 0x9C, 0xFE, 0xE3, 0xE3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
- 0xC3, 0x7C, 0x7E, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0x7E, 0x9C, 0xFE, 0xE3, 0xE3, 0xC3, 0xC3, 0xC3,
- 0xC3, 0xE3, 0xFF, 0xFE, 0xC0, 0xC0, 0xC0, 0xC0, 0x7B, 0x7B, 0xC7, 0xC7, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0xFF, 0x7B,
- 0x03, 0x03, 0x03, 0x03, 0x9B, 0xEE, 0x38, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x00, 0x78, 0xF3, 0x1E, 0x3F, 0x8F, 0x81,
- 0x83, 0xC7, 0xFD, 0xE0, 0x61, 0x8F, 0xBE, 0x61, 0x86, 0x18, 0x61, 0x86, 0x1E, 0x78, 0x83, 0xC3, 0xC3, 0xC3, 0xC3,
- 0xC3, 0xC3, 0xC3, 0xC7, 0xFF, 0x7B, 0x80, 0xE0, 0xF0, 0x7C, 0x76, 0x33, 0x18, 0xD8, 0x6C, 0x3E, 0x0C, 0x06, 0x00,
- 0x84, 0x3C, 0x63, 0xC6, 0x3E, 0xF7, 0x7B, 0x67, 0xB6, 0x7B, 0x67, 0xB6, 0x7B, 0xC3, 0x18, 0x31, 0x80, 0x83, 0xC3,
- 0x66, 0x66, 0x7E, 0x38, 0x38, 0x7E, 0x66, 0xE7, 0xC3, 0x80, 0xE0, 0xF0, 0x7C, 0x76, 0x33, 0x18, 0xD8, 0x6C, 0x36,
- 0x1F, 0x06, 0x03, 0x01, 0x83, 0xC1, 0xC0, 0xFF, 0xFF, 0x06, 0x0E, 0x18, 0x18, 0x30, 0x30, 0x70, 0xFF, 0xFF, 0x37,
- 0x66, 0x66, 0x66, 0xCE, 0x66, 0x66, 0x67, 0x30, 0xFF, 0xFF, 0xFF, 0xC0, 0xCE, 0x66, 0x66, 0x66, 0x37, 0x66, 0x66,
- 0x6E, 0xC0, 0xC3, 0x99, 0xFF, 0xF9, 0xB8, 0x30, 0xDB, 0x66, 0xC0, 0x6D, 0xBD, 0x00, 0x7B, 0xEF, 0x3C, 0xF2, 0xC0,
- 0x79, 0xE7, 0x9E, 0xF2, 0xC0,
+ 0x60, 0xFC, 0x06, 0x7F, 0x7F, 0xF0, 0x78, 0x3C, 0x07, 0xE1, 0xFE, 0x0F, 0x01, 0xE0, 0xF0, 0x7F, 0xF7, 0xF0, 0xFF,
+ 0xFF, 0xC7, 0x03, 0x81, 0xC0, 0xE0, 0x70, 0x38, 0x1C, 0x0E, 0x07, 0x03, 0x81, 0xC0, 0xC1, 0xE0, 0xF0, 0x78, 0x3C,
+ 0x1E, 0x0F, 0x07, 0x83, 0xC1, 0xE0, 0xF8, 0xEF, 0xE3, 0xE0, 0xC0, 0x3C, 0x03, 0xE0, 0x76, 0x06, 0x60, 0x67, 0x1E,
+ 0x31, 0x83, 0xB8, 0x1F, 0x01, 0xF0, 0x1F, 0x00, 0x60, 0x06, 0x00, 0xC1, 0x81, 0xE1, 0xF0, 0xF8, 0xD8, 0xEC, 0x6C,
+ 0x66, 0x36, 0x33, 0x1B, 0x19, 0xDD, 0xFC, 0x6C, 0x7C, 0x36, 0x3E, 0x1B, 0x1F, 0x0F, 0x8F, 0x03, 0x83, 0x01, 0xC1,
+ 0x80, 0xC0, 0x7C, 0x3D, 0x86, 0x30, 0xC3, 0x30, 0x7E, 0x07, 0x80, 0xF8, 0x33, 0x0E, 0x71, 0x86, 0x70, 0xFC, 0x06,
+ 0xC0, 0x3E, 0x07, 0x71, 0xE3, 0x18, 0x31, 0x83, 0xF8, 0x1F, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06,
+ 0x00, 0xFF, 0xFF, 0xFC, 0x0E, 0x01, 0x80, 0x30, 0x0E, 0x07, 0x80, 0xE0, 0x30, 0x06, 0x01, 0xC0, 0x7F, 0xFF, 0xFE,
+ 0xFF, 0x6D, 0xB6, 0xDB, 0x6D, 0xB7, 0xE0, 0xC3, 0x0E, 0x18, 0x61, 0x87, 0x0C, 0x30, 0xC3, 0x87, 0x1C, 0xFD, 0xB6,
+ 0xDB, 0x6D, 0xB6, 0xDF, 0xE0, 0x30, 0xF1, 0xF3, 0xE7, 0xDD, 0xF1, 0x80, 0xFF, 0xFF, 0xFC, 0xCE, 0x73, 0x00, 0x7E,
+ 0x7E, 0xC3, 0xC3, 0x3F, 0x7F, 0x63, 0xE3, 0xC7, 0xFF, 0x7F, 0xC0, 0xC0, 0xFE, 0xFE, 0xE3, 0xE3, 0xC3, 0xC3, 0xC3,
+ 0xC3, 0xE3, 0xFF, 0xFE, 0x78, 0xFB, 0x1E, 0x3C, 0x18, 0x30, 0x60, 0xC7, 0xFD, 0xF0, 0x03, 0x03, 0x7B, 0x7F, 0xC7,
+ 0xC7, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0xFF, 0x7F, 0x7E, 0x7E, 0xC3, 0xC3, 0xFF, 0xFF, 0xC0, 0xC0, 0xC3, 0xFF, 0x7E,
+ 0x3D, 0xEF, 0xBF, 0x61, 0x86, 0x18, 0x61, 0x86, 0x18, 0x60, 0x7B, 0x7F, 0xC7, 0xC7, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7,
+ 0xFF, 0x7F, 0x03, 0xC3, 0xFF, 0x7E, 0xC0, 0xC0, 0xFE, 0xFE, 0xE3, 0xE3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
+ 0xFF, 0xFF, 0xFF, 0xC0, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0xE0, 0xC0, 0xC0, 0xC3, 0xC3, 0xC6, 0xDE,
+ 0xFC, 0xF8, 0xFC, 0xEE, 0xC6, 0xC7, 0xC3, 0xFF, 0xFF, 0xFF, 0xC0, 0xF9, 0xEF, 0xDE, 0xE7, 0x3E, 0x73, 0xC6, 0x3C,
+ 0x63, 0xC6, 0x3C, 0x63, 0xC6, 0x3C, 0x63, 0xC6, 0x30, 0xFE, 0xFE, 0xE3, 0xE3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
+ 0xC3, 0x7E, 0x7E, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0x7E, 0xFE, 0xFE, 0xE3, 0xE3, 0xC3, 0xC3, 0xC3,
+ 0xC3, 0xE3, 0xFF, 0xFE, 0xC0, 0xC0, 0xC0, 0xC0, 0x7B, 0x7F, 0xC7, 0xC7, 0xC3, 0xC3, 0xC3, 0xC3, 0xC7, 0xFF, 0x7F,
+ 0x03, 0x03, 0x03, 0x03, 0xFB, 0xFE, 0x38, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x00, 0x78, 0xFB, 0x1E, 0x3F, 0x8F, 0x81,
+ 0x83, 0xC7, 0xFD, 0xF0, 0x61, 0x8F, 0xBF, 0x61, 0x86, 0x18, 0x61, 0x86, 0x1E, 0x7C, 0xC3, 0xC3, 0xC3, 0xC3, 0xC3,
+ 0xC3, 0xC3, 0xC3, 0xC7, 0xFF, 0x7F, 0xC1, 0xE0, 0xF0, 0x7C, 0x76, 0x33, 0xB8, 0xD8, 0x6C, 0x3E, 0x0E, 0x07, 0x00,
+ 0xC6, 0x3C, 0x63, 0xC6, 0x3F, 0xF7, 0x7F, 0x67, 0xF6, 0x7F, 0x67, 0xF6, 0x7B, 0xE3, 0x18, 0x31, 0x80, 0xC3, 0xC3,
+ 0x66, 0x66, 0x7E, 0x3C, 0x3C, 0x7E, 0x66, 0xE7, 0xC3, 0xC1, 0xE0, 0xF0, 0x7C, 0x76, 0x33, 0xB8, 0xD8, 0x6C, 0x36,
+ 0x1F, 0x07, 0x03, 0x81, 0xC3, 0xE1, 0xC0, 0xFF, 0xFF, 0x06, 0x1E, 0x1C, 0x1C, 0x30, 0x30, 0x70, 0xFF, 0xFF, 0x37,
+ 0x66, 0x66, 0x6E, 0xCE, 0x66, 0x66, 0x67, 0x30, 0xFF, 0xFF, 0xFF, 0xC0, 0xCE, 0x66, 0x66, 0x67, 0x37, 0x66, 0x66,
+ 0x6E, 0xC0, 0xC3, 0x9B, 0xFF, 0xF9, 0xB8, 0x30, 0xFC, 0x0D, 0xBF, 0xFF, 0xF0, 0x1C, 0x1C, 0x3E, 0x7E, 0xE0, 0xE0,
+ 0xE0, 0xE0, 0xE0, 0x7A, 0x3E, 0x1C, 0x1C, 0x3F, 0x3B, 0x70, 0x70, 0x70, 0x70, 0xFE, 0x70, 0x70, 0x70, 0x60, 0x7F,
+ 0x61, 0xBF, 0xDF, 0xCC, 0x66, 0x33, 0x19, 0xFC, 0xFF, 0x61, 0x80, 0xE1, 0xD8, 0xE7, 0x30, 0xDC, 0x3E, 0x07, 0x87,
+ 0xF8, 0x30, 0x0C, 0x1F, 0xE0, 0xC0, 0x30, 0xFF, 0xFF, 0xF0, 0x03, 0xFF, 0xFF, 0x3F, 0x70, 0x60, 0x70, 0x7C, 0x6F,
+ 0xE3, 0xE3, 0x7F, 0x3E, 0x07, 0x07, 0x6E, 0xFE, 0xEF, 0xB0, 0x1F, 0x87, 0x1C, 0x66, 0x6D, 0xFF, 0xD8, 0x3F, 0x83,
+ 0xD8, 0x3D, 0x83, 0xDF, 0xF6, 0x66, 0x71, 0xE1, 0xF8, 0x7D, 0x37, 0xFB, 0xED, 0xF0, 0x3B, 0x77, 0x7E, 0xEE, 0x7E,
+ 0x77, 0x3B, 0xFF, 0x80, 0xC0, 0x60, 0x30, 0x18, 0xF8, 0x1F, 0x87, 0x1C, 0x60, 0x6D, 0xF7, 0xDB, 0xBD, 0xBB, 0xDF,
+ 0x3D, 0xB3, 0xD9, 0xF6, 0x06, 0x71, 0xE1, 0xF8, 0xFC, 0x7B, 0xEC, 0xFE, 0x78, 0x0C, 0x06, 0x03, 0x1F, 0xF0, 0xC0,
+ 0x60, 0x30, 0x00, 0x00, 0x7F, 0xC0, 0xFB, 0x61, 0x8E, 0x71, 0x8F, 0xC0, 0xF9, 0x61, 0x9E, 0x1F, 0x7F, 0x80, 0x6E,
+ 0xC0, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xE7, 0xF7, 0xFF, 0xC0, 0xC0, 0xC0, 0x3F, 0xDF, 0x77, 0xDF, 0xF7, 0xFD,
+ 0xDF, 0x77, 0xDC, 0x77, 0x0D, 0xC3, 0x70, 0xDC, 0x37, 0x0D, 0xC3, 0x70, 0xDC, 0x77, 0x67, 0xF0, 0x73, 0x8C, 0x63,
+ 0x18, 0xC0, 0x7C, 0xDF, 0x9F, 0x36, 0xEF, 0x80, 0xEC, 0x6E, 0x77, 0x3F, 0x77, 0x6E, 0xEC, 0x70, 0x70, 0xE0, 0xC0,
+ 0xC3, 0x81, 0x86, 0x03, 0x18, 0x06, 0x73, 0x8C, 0xCF, 0x03, 0x9E, 0x06, 0x6C, 0x1C, 0xFC, 0x30, 0x30, 0xE0, 0x60,
+ 0x70, 0x60, 0xE1, 0xC0, 0xC3, 0x01, 0x8E, 0x03, 0x18, 0x06, 0x7F, 0x8C, 0xDB, 0x03, 0x06, 0x0E, 0x1C, 0x18, 0x70,
+ 0x71, 0xC0, 0xC3, 0xE0, 0xF8, 0x70, 0xB0, 0xC0, 0x63, 0x83, 0xC6, 0x01, 0xDC, 0x1B, 0xB3, 0xBE, 0xEF, 0x01, 0x9E,
+ 0x07, 0x6C, 0x1C, 0xFC, 0x30, 0x30, 0xE0, 0x60, 0x38, 0x70, 0x00, 0x01, 0x87, 0x0E, 0x38, 0xE1, 0xC3, 0xFB, 0xF0,
+ 0x1C, 0x01, 0xE0, 0x06, 0x00, 0x00, 0x0E, 0x00, 0xE0, 0x1F, 0x01, 0xB0, 0x1B, 0x83, 0xB8, 0x31, 0xC7, 0x1C, 0x7F,
+ 0xC6, 0x0E, 0xE0, 0xEE, 0x06, 0x03, 0x00, 0xF0, 0x0E, 0x00, 0x00, 0x0E, 0x00, 0xE0, 0x1F, 0x01, 0xB0, 0x1B, 0x83,
+ 0xB8, 0x31, 0xC7, 0x1C, 0x7F, 0xC6, 0x0E, 0xE0, 0xEE, 0x06, 0x0E, 0x01, 0xF0, 0x1B, 0x00, 0x00, 0x0E, 0x00, 0xE0,
+ 0x1F, 0x01, 0xB0, 0x1B, 0x83, 0xB8, 0x31, 0xC7, 0x1C, 0x7F, 0xC6, 0x0E, 0xE0, 0xEE, 0x06, 0x1F, 0x81, 0xF0, 0x00,
+ 0x00, 0x00, 0x0E, 0x00, 0xE0, 0x1F, 0x01, 0xB0, 0x1B, 0x83, 0xB8, 0x31, 0xC7, 0x1C, 0x7F, 0xC6, 0x0E, 0xE0, 0xEE,
+ 0x06, 0x1B, 0x81, 0xB8, 0x00, 0x00, 0xE0, 0x0E, 0x01, 0xF0, 0x1B, 0x01, 0xB8, 0x3B, 0x83, 0x1C, 0x71, 0xC7, 0xFC,
+ 0x60, 0xEE, 0x0E, 0xE0, 0x60, 0x0E, 0x01, 0xF0, 0x0F, 0x00, 0xE0, 0x0F, 0x01, 0xF0, 0x1B, 0x01, 0xB8, 0x3B, 0x83,
+ 0x1C, 0x71, 0xC7, 0xFC, 0x60, 0xEE, 0x0E, 0xE0, 0x60, 0x03, 0xFE, 0x03, 0xC0, 0x07, 0xC0, 0x06, 0xC0, 0x0E, 0xC0,
+ 0x1C, 0xFE, 0x18, 0xC0, 0x38, 0xC0, 0x3F, 0xC0, 0x70, 0xC0, 0xE0, 0xC0, 0xE0, 0xFF, 0x02, 0x03, 0xF8, 0xF3, 0x38,
+ 0x07, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60, 0x0E, 0x01, 0xC0, 0x1E, 0x61, 0xFC, 0x0E, 0x01, 0xC0, 0x78, 0x38, 0x1E,
+ 0x07, 0x00, 0x0F, 0xF7, 0x03, 0x81, 0xC0, 0xE0, 0x7F, 0x38, 0x1C, 0x0E, 0x07, 0x03, 0x81, 0xFF, 0x0E, 0x0F, 0x06,
+ 0x00, 0x0F, 0xF7, 0x03, 0x81, 0xC0, 0xE0, 0x7F, 0x38, 0x1C, 0x0E, 0x07, 0x03, 0x81, 0xFF, 0x18, 0x1E, 0x19, 0x80,
+ 0x0F, 0xF7, 0x03, 0x81, 0xC0, 0xE0, 0x7F, 0x38, 0x1C, 0x0E, 0x07, 0x03, 0x81, 0xFF, 0x7E, 0x3F, 0x00, 0x1F, 0xEE,
+ 0x07, 0x03, 0x81, 0xC0, 0xFE, 0x70, 0x38, 0x1C, 0x0E, 0x07, 0x03, 0xFE, 0xCF, 0x70, 0x77, 0x77, 0x77, 0x77, 0x77,
+ 0x77, 0x6E, 0xC0, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0x31, 0xE6, 0xC0, 0x38, 0xE3, 0x8E, 0x38, 0xE3, 0x8E, 0x38,
+ 0xE3, 0x8E, 0xEF, 0xB0, 0x0E, 0x38, 0xE3, 0x8E, 0x38, 0xE3, 0x8E, 0x38, 0xE3, 0x80, 0x7F, 0x87, 0x3C, 0x70, 0xE7,
+ 0x06, 0x70, 0x7F, 0xC7, 0x70, 0x77, 0x07, 0x70, 0x67, 0x0E, 0x73, 0xC7, 0xF8, 0x3F, 0x0F, 0xC0, 0x00, 0x00, 0xE0,
+ 0xF8, 0x3F, 0x0F, 0xE3, 0xEC, 0xFB, 0x3E, 0x6F, 0x9B, 0xE3, 0xF8, 0x7E, 0x1F, 0x83, 0x0E, 0x00, 0x78, 0x00, 0xC0,
+ 0x00, 0x01, 0xFC, 0x1E, 0xF1, 0xC1, 0xCE, 0x06, 0x60, 0x3B, 0x01, 0xD8, 0x0E, 0xC0, 0x77, 0x03, 0x38, 0x38, 0xF7,
+ 0x83, 0xF8, 0x03, 0x80, 0x3C, 0x01, 0xC0, 0x00, 0x01, 0xFC, 0x1E, 0xF1, 0xC1, 0xCE, 0x06, 0x60, 0x3B, 0x01, 0xD8,
+ 0x0E, 0xC0, 0x77, 0x03, 0x38, 0x38, 0xF7, 0x83, 0xF8, 0x07, 0x00, 0x7C, 0x03, 0x60, 0x00, 0x01, 0xFC, 0x1E, 0xF1,
+ 0xC1, 0xCE, 0x06, 0x60, 0x3B, 0x01, 0xD8, 0x0E, 0xC0, 0x77, 0x03, 0x38, 0x38, 0xF7, 0x83, 0xF8, 0x0F, 0xC0, 0x7C,
+ 0x00, 0x00, 0x00, 0x01, 0xFC, 0x1E, 0xF1, 0xC1, 0xCE, 0x06, 0x60, 0x3B, 0x01, 0xD8, 0x0E, 0xC0, 0x77, 0x03, 0x38,
+ 0x38, 0xF7, 0x83, 0xF8, 0x0D, 0xC0, 0x6E, 0x00, 0x00, 0x3F, 0x83, 0xDE, 0x38, 0x39, 0xC0, 0xCC, 0x07, 0x60, 0x3B,
+ 0x01, 0xD8, 0x0E, 0xE0, 0x67, 0x07, 0x1E, 0xF0, 0x7F, 0x00, 0x42, 0xEE, 0x7E, 0x3C, 0x7E, 0xEE, 0x42, 0x1F, 0xF1,
+ 0xEF, 0x9C, 0x3C, 0xE3, 0xE6, 0x3B, 0xB1, 0x9D, 0x9C, 0xED, 0xC7, 0x7C, 0x33, 0xE3, 0x8F, 0x78, 0xFF, 0x80, 0x18,
+ 0x07, 0x00, 0xC0, 0x00, 0xC1, 0xF0, 0x7C, 0x1F, 0x07, 0xC1, 0xF0, 0x7C, 0x1F, 0x07, 0xE1, 0xF8, 0x67, 0xF8, 0xFC,
+ 0x06, 0x03, 0x80, 0xC0, 0x00, 0xC1, 0xF0, 0x7C, 0x1F, 0x07, 0xC1, 0xF0, 0x7C, 0x1F, 0x07, 0xE1, 0xF8, 0x67, 0xF8,
+ 0xFC, 0x0C, 0x07, 0x83, 0x20, 0x00, 0xC1, 0xF0, 0x7C, 0x1F, 0x07, 0xC1, 0xF0, 0x7C, 0x1F, 0x07, 0xE1, 0xF8, 0x67,
+ 0xF8, 0xFC, 0x3F, 0x0F, 0xC0, 0x03, 0x07, 0xC1, 0xF0, 0x7C, 0x1F, 0x07, 0xC1, 0xF0, 0x7C, 0x1F, 0x87, 0xE1, 0x9F,
+ 0xE3, 0xF0, 0x06, 0x01, 0xC0, 0x30, 0x00, 0x0E, 0x1D, 0xC3, 0x9C, 0xE1, 0xDC, 0x3F, 0x03, 0xC0, 0x78, 0x06, 0x00,
+ 0xC0, 0x18, 0x03, 0x00, 0x60, 0xE0, 0x70, 0x3F, 0x9D, 0xEE, 0x3F, 0x0F, 0x87, 0xC7, 0xEF, 0x7F, 0x38, 0x1C, 0x00,
+ 0x7E, 0x7B, 0xB9, 0xD8, 0xEC, 0xE6, 0xE3, 0x71, 0x9E, 0xC7, 0xE0, 0xF0, 0x7B, 0x7D, 0xF0, 0x38, 0x3C, 0x0C, 0x00,
+ 0x7E, 0x2F, 0x03, 0x7F, 0x77, 0xE3, 0xE3, 0x73, 0x7F, 0x0E, 0x1E, 0x18, 0x00, 0x7E, 0x2F, 0x03, 0x7F, 0x77, 0xE3,
+ 0xE3, 0x73, 0x7F, 0x1C, 0x3E, 0x36, 0x00, 0x7E, 0x2F, 0x03, 0x7F, 0x77, 0xE3, 0xE3, 0x73, 0x7F, 0x3F, 0x7E, 0x00,
+ 0x00, 0x7E, 0x2F, 0x03, 0x7F, 0x77, 0xE3, 0xE3, 0x73, 0x7F, 0x36, 0x36, 0x00, 0x7E, 0x2F, 0x03, 0x7F, 0x77, 0xE3,
+ 0xE3, 0x73, 0x7F, 0x1C, 0x36, 0x36, 0x1C, 0x00, 0x7E, 0x2F, 0x03, 0x7F, 0x77, 0xE3, 0xE3, 0x73, 0x7F, 0x7F, 0xF8,
+ 0xBF, 0xF0, 0x79, 0xC0, 0xC3, 0x7F, 0xFF, 0xDC, 0x0E, 0x38, 0x1D, 0xF6, 0x7F, 0xF8, 0x3F, 0x7A, 0x60, 0x60, 0xE0,
+ 0x60, 0x60, 0x7B, 0x3F, 0x0C, 0x0E, 0x1C, 0x18, 0x0E, 0x03, 0x00, 0x03, 0xF3, 0xF9, 0x8E, 0xC7, 0xFF, 0xB0, 0x18,
+ 0x0F, 0x63, 0xF0, 0x06, 0x07, 0x03, 0x00, 0x03, 0xF3, 0xF9, 0x8E, 0xC7, 0xFF, 0xB0, 0x18, 0x0F, 0x63, 0xF0, 0x0C,
+ 0x0F, 0x0C, 0x80, 0x03, 0xF3, 0xF9, 0x8E, 0xC7, 0xFF, 0xB0, 0x18, 0x0F, 0x63, 0xF0, 0x3F, 0x1F, 0x80, 0x07, 0xE7,
+ 0xF3, 0x1D, 0x8F, 0xFF, 0x60, 0x30, 0x1E, 0xC7, 0xE0, 0xCE, 0x60, 0x66, 0x66, 0x66, 0x66, 0x60, 0x6E, 0xC0, 0xCC,
+ 0xCC, 0xCC, 0xCC, 0xC0, 0x31, 0xE6, 0xC0, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xFF, 0xF0, 0x0C, 0x30, 0xC3,
+ 0x0C, 0x30, 0xC3, 0x0C, 0x0F, 0xC3, 0xF0, 0xF8, 0x0E, 0x01, 0x8F, 0xE7, 0xF9, 0x86, 0xE1, 0x98, 0x66, 0x39, 0xFC,
+ 0x3F, 0x00, 0x7E, 0x7E, 0x00, 0x00, 0xFE, 0xEE, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0x18, 0x07, 0x00, 0xC0,
+ 0x00, 0x3F, 0x1F, 0xE6, 0x19, 0x86, 0xE1, 0xD8, 0x66, 0x19, 0xFE, 0x3F, 0x00, 0x06, 0x03, 0x80, 0xC0, 0x00, 0x3F,
+ 0x1F, 0xE6, 0x19, 0x86, 0xE1, 0xD8, 0x66, 0x19, 0xFE, 0x3F, 0x00, 0x0C, 0x07, 0x83, 0x30, 0x00, 0x3F, 0x1F, 0xE6,
+ 0x19, 0x86, 0xE1, 0xD8, 0x66, 0x19, 0xFE, 0x3F, 0x00, 0x3F, 0x0F, 0xC0, 0x00, 0x00, 0x3F, 0x1F, 0xE6, 0x19, 0x86,
+ 0xE1, 0xD8, 0x66, 0x19, 0xFE, 0x3F, 0x00, 0x3F, 0x0F, 0xC0, 0x00, 0xFC, 0x7F, 0x98, 0x66, 0x1B, 0x87, 0x61, 0x98,
+ 0x67, 0xF8, 0xFC, 0x1C, 0x0E, 0x00, 0x00, 0x0F, 0xF8, 0x00, 0x00, 0x38, 0x1C, 0x00, 0x3F, 0x9F, 0xE6, 0x79, 0xBE,
+ 0xED, 0xDF, 0x67, 0x99, 0xFE, 0x7F, 0x00, 0x30, 0x38, 0x18, 0x00, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xE7, 0xF7,
+ 0x7F, 0x0C, 0x1C, 0x18, 0x00, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xE7, 0xF7, 0x7F, 0x18, 0x3C, 0x6C, 0x00, 0xC7,
+ 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xE7, 0xF7, 0x7F, 0x7E, 0x7E, 0x00, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xE7, 0xF7,
+ 0x7F, 0x0E, 0x0F, 0x06, 0x00, 0x0E, 0x37, 0x19, 0x9C, 0xCE, 0x76, 0x1B, 0x0F, 0x83, 0x81, 0xC0, 0xE2, 0xE1, 0xE0,
+ 0xC0, 0x60, 0x30, 0x18, 0x0F, 0xE7, 0x7B, 0x0D, 0x86, 0xC3, 0xE1, 0xB0, 0xDF, 0xEF, 0xE6, 0x03, 0x01, 0x80, 0x36,
+ 0x1B, 0x00, 0x1C, 0x6E, 0x33, 0x39, 0x9C, 0xEC, 0x36, 0x1F, 0x07, 0x03, 0x81, 0xC5, 0xC3, 0xC0, 0x1F, 0x00, 0x00,
+ 0x00, 0x00, 0xE0, 0x0E, 0x01, 0xF0, 0x1B, 0x01, 0xB8, 0x3B, 0x83, 0x1C, 0x71, 0xC7, 0xFC, 0x60, 0xEE, 0x0E, 0xE0,
+ 0x60, 0x3E, 0x00, 0x00, 0x7E, 0x2F, 0x03, 0x7F, 0x77, 0xE3, 0xE3, 0x73, 0x7F, 0x1B, 0x01, 0xF0, 0x00, 0x00, 0xE0,
+ 0x0E, 0x01, 0xF0, 0x1B, 0x01, 0xB8, 0x3B, 0x83, 0x1C, 0x71, 0xC7, 0xFC, 0x60, 0xEE, 0x0E, 0xE0, 0x60, 0x36, 0x3E,
+ 0x00, 0x7E, 0x2F, 0x03, 0x7F, 0x77, 0xE3, 0xE3, 0x73, 0x7F, 0x0E, 0x00, 0xE0, 0x1F, 0x01, 0xB0, 0x1B, 0x83, 0xB8,
+ 0x31, 0xC7, 0x1C, 0x7F, 0xC6, 0x0E, 0xE0, 0xEE, 0x06, 0x00, 0xE0, 0x0C, 0x00, 0xF0, 0x7E, 0x2F, 0x03, 0x7F, 0x77,
+ 0xE3, 0xE3, 0x73, 0x7F, 0x06, 0x0E, 0x0F, 0x03, 0x00, 0xE0, 0x18, 0x01, 0x01, 0xFC, 0x79, 0x9C, 0x03, 0x80, 0x60,
+ 0x0C, 0x01, 0x80, 0x30, 0x07, 0x00, 0xE0, 0x0F, 0x30, 0xFE, 0x06, 0x0E, 0x0C, 0x00, 0x3F, 0x7A, 0x60, 0x60, 0xE0,
+ 0x60, 0x60, 0x7B, 0x3F, 0x06, 0x01, 0xE0, 0x36, 0x01, 0x01, 0xFC, 0x79, 0x9C, 0x03, 0x80, 0x60, 0x0C, 0x01, 0x80,
+ 0x30, 0x07, 0x00, 0xE0, 0x0F, 0x30, 0xFE, 0x0C, 0x1E, 0x32, 0x00, 0x3F, 0x7A, 0x60, 0x60, 0xE0, 0x60, 0x60, 0x7B,
+ 0x3F, 0x07, 0x00, 0xE0, 0x08, 0x0F, 0xE3, 0xCC, 0xE0, 0x1C, 0x03, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x38, 0x07, 0x00,
+ 0x79, 0x87, 0xF0, 0x0C, 0x0C, 0x00, 0x3F, 0x7A, 0x60, 0x60, 0xE0, 0x60, 0x60, 0x7B, 0x3F, 0x0D, 0x81, 0xE0, 0x18,
+ 0x01, 0x01, 0xFC, 0x79, 0x9C, 0x03, 0x80, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x07, 0x00, 0xE0, 0x0F, 0x30, 0xFE, 0x32,
+ 0x1E, 0x0C, 0x00, 0x3F, 0x7A, 0x60, 0x60, 0xE0, 0x60, 0x60, 0x7B, 0x3F, 0x36, 0x07, 0xC0, 0x70, 0x00, 0x0F, 0xF1,
+ 0xCF, 0x38, 0x77, 0x0E, 0xE0, 0xDC, 0x1F, 0x83, 0xF0, 0x6E, 0x1D, 0xC3, 0xB9, 0xE7, 0xF8, 0x01, 0xF0, 0x1F, 0x01,
+ 0xF0, 0x1E, 0x3F, 0x87, 0xF8, 0x61, 0x86, 0x18, 0xE1, 0x86, 0x18, 0x61, 0x87, 0xB8, 0x3F, 0x80, 0x7F, 0x87, 0x3C,
+ 0x70, 0xE7, 0x06, 0x70, 0x7F, 0xC7, 0x70, 0x77, 0x07, 0x70, 0x67, 0x0E, 0x73, 0xC7, 0xF8, 0x01, 0x80, 0x60, 0xFC,
+ 0x06, 0x3F, 0x9F, 0xE6, 0x19, 0x86, 0xE1, 0x98, 0x66, 0x19, 0xEE, 0x3F, 0x80, 0x7E, 0x00, 0x00, 0x1F, 0xEE, 0x07,
+ 0x03, 0x81, 0xC0, 0xFE, 0x70, 0x38, 0x1C, 0x0E, 0x07, 0x03, 0xFE, 0x3F, 0x00, 0x00, 0x07, 0xE7, 0xF3, 0x1D, 0x8F,
+ 0xFF, 0x60, 0x30, 0x1E, 0xC7, 0xE0, 0x7E, 0x1E, 0x00, 0x1F, 0xEE, 0x07, 0x03, 0x81, 0xC0, 0xFE, 0x70, 0x38, 0x1C,
+ 0x0E, 0x07, 0x03, 0xFE, 0x36, 0x0F, 0x00, 0x07, 0xE7, 0xF3, 0x1D, 0x8F, 0xFF, 0x60, 0x30, 0x1E, 0xC7, 0xE0, 0x18,
+ 0x0C, 0x00, 0x1F, 0xEE, 0x07, 0x03, 0x81, 0xC0, 0xFE, 0x70, 0x38, 0x1C, 0x0E, 0x07, 0x03, 0xFE, 0x0C, 0x06, 0x00,
+ 0x07, 0xE7, 0xF3, 0x1D, 0x8F, 0xFF, 0x60, 0x30, 0x1E, 0xC7, 0xE0, 0xFF, 0x70, 0x38, 0x1C, 0x0E, 0x07, 0xF3, 0x81,
+ 0xC0, 0xE0, 0x70, 0x38, 0x1F, 0xF0, 0x70, 0x30, 0x1E, 0x3F, 0x3F, 0x98, 0xEC, 0x7F, 0xFB, 0x01, 0x80, 0xF6, 0x3F,
+ 0x03, 0x83, 0x80, 0xE0, 0x64, 0x1E, 0x06, 0x00, 0x0F, 0xF7, 0x03, 0x81, 0xC0, 0xE0, 0x7F, 0x38, 0x1C, 0x0E, 0x07,
+ 0x03, 0x81, 0xFF, 0x32, 0x0F, 0x03, 0x00, 0x00, 0x01, 0xF9, 0xFC, 0xC7, 0x63, 0xFF, 0xD8, 0x0C, 0x07, 0xB1, 0xF8,
+ 0x07, 0x01, 0xF0, 0x36, 0x00, 0x01, 0xFC, 0x7B, 0x9C, 0x03, 0x80, 0x60, 0x0C, 0x01, 0x83, 0xB0, 0x77, 0x0E, 0xE1,
+ 0xCF, 0x38, 0xFF, 0x0C, 0x0F, 0x06, 0xC0, 0x03, 0xFB, 0xDD, 0x8E, 0xC7, 0xE3, 0xF1, 0xD8, 0xEF, 0xF3, 0xF8, 0x1D,
+ 0xDC, 0xFE, 0x0D, 0x81, 0xF0, 0x00, 0x0F, 0xE3, 0xDC, 0xE0, 0x1C, 0x03, 0x00, 0x60, 0x0C, 0x1D, 0x83, 0xB8, 0x77,
+ 0x0E, 0x79, 0xC7, 0xF8, 0x1F, 0x0F, 0x00, 0x07, 0xF7, 0xBB, 0x1D, 0x8F, 0xC7, 0xE3, 0xB1, 0xDF, 0xE7, 0xF0, 0x3B,
+ 0xB9, 0xFC, 0x07, 0x00, 0xE0, 0x00, 0x0F, 0xE3, 0xDC, 0xE0, 0x1C, 0x03, 0x00, 0x60, 0x0C, 0x1D, 0x83, 0xB8, 0x77,
+ 0x0E, 0x79, 0xC7, 0xF8, 0x0C, 0x06, 0x00, 0x07, 0xF7, 0xBB, 0x1D, 0x8F, 0xC7, 0xE3, 0xB1, 0xDF, 0xE7, 0xF0, 0x3B,
+ 0xB9, 0xFC, 0x1F, 0xC7, 0xB9, 0xC0, 0x38, 0x06, 0x00, 0xC0, 0x18, 0x3B, 0x07, 0x70, 0xEE, 0x1C, 0xF3, 0x8F, 0xF0,
+ 0x00, 0x0E, 0x03, 0x80, 0x06, 0x07, 0x03, 0x00, 0x03, 0xFB, 0xDD, 0x8E, 0xC7, 0xE3, 0xF1, 0xD8, 0xEF, 0xF3, 0xF8,
+ 0x1D, 0xDC, 0xFE, 0x0C, 0x07, 0x83, 0x30, 0x00, 0xE1, 0xF8, 0x7E, 0x1F, 0x87, 0xE1, 0xFF, 0xFE, 0x1F, 0x87, 0xE1,
+ 0xF8, 0x7E, 0x1F, 0x87, 0x78, 0x3C, 0x00, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xFE, 0x77, 0x31, 0xD8, 0xEC, 0x76, 0x3B,
+ 0x1D, 0x8E, 0xC7, 0x70, 0x63, 0x83, 0x3F, 0xFE, 0xE0, 0xC7, 0x06, 0x3F, 0xF1, 0xC1, 0x8E, 0x0C, 0x70, 0x63, 0x83,
+ 0x1C, 0x18, 0xE0, 0xC0, 0x60, 0x30, 0x3F, 0x0C, 0x07, 0xF3, 0xB9, 0x8E, 0xC7, 0x63, 0xB1, 0xD8, 0xEC, 0x76, 0x38,
+ 0x7D, 0xF8, 0x00, 0x03, 0x87, 0x0E, 0x1C, 0x38, 0x70, 0xE1, 0xC3, 0x87, 0x0E, 0x1C, 0xFF, 0xF0, 0x00, 0x30, 0xC3,
+ 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xFC, 0x00, 0x0E, 0x38, 0xE3, 0x8E, 0x38, 0xE3, 0x8E, 0x38, 0xE3, 0x80, 0xFC, 0x00,
+ 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0xDF, 0x80, 0xE7, 0x39, 0xCE, 0x73, 0x9C, 0xE7, 0x39, 0xC0, 0x7D, 0xE0,
+ 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0xEC, 0xF0, 0x66, 0x00, 0x66, 0x66,
+ 0x66, 0x66, 0x6E, 0xCF, 0xFC, 0x7F, 0xFF, 0xFF, 0xFF, 0xF8, 0xFF, 0xFF, 0xC0, 0xE0, 0x7C, 0x0F, 0x81, 0xF0, 0x3E,
+ 0x07, 0xC0, 0xF8, 0x1F, 0x03, 0xE0, 0x7C, 0x1F, 0xB7, 0xF7, 0xE0, 0xCF, 0x9C, 0x00, 0x0C, 0xF9, 0xF3, 0xE7, 0xCF,
+ 0x9F, 0x3E, 0x7C, 0xE1, 0xC3, 0x1E, 0x06, 0x07, 0x83, 0x60, 0x00, 0x70, 0x38, 0x1C, 0x0E, 0x07, 0x03, 0x81, 0xC0,
+ 0xE0, 0x60, 0x33, 0xB9, 0xF8, 0x18, 0x78, 0xD8, 0x01, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x0E, 0x38,
+ 0xE3, 0xB9, 0xCE, 0xF3, 0xF8, 0xFC, 0x3E, 0x0F, 0xC3, 0xF8, 0xEF, 0x39, 0xCE, 0x3B, 0x87, 0x00, 0x07, 0x03, 0xC0,
+ 0xC0, 0xC0, 0xC0, 0xC0, 0xCE, 0xCE, 0xDC, 0xF8, 0xF8, 0xFC, 0xDC, 0xCE, 0xC7, 0x00, 0x38, 0x78, 0xC7, 0xCE, 0xDC,
+ 0xF8, 0xF8, 0xFC, 0xDE, 0xCE, 0xC7, 0x30, 0xF0, 0xE0, 0x00, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0,
+ 0xE0, 0xE0, 0xFF, 0xEE, 0x0C, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xEF, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0,
+ 0xE0, 0xE0, 0xE0, 0xFF, 0x00, 0x18, 0x38, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x18, 0xE7, 0x81, 0xDC, 0xFC, 0xF8,
+ 0xF8, 0xF8, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xFF, 0xDE, 0xF7, 0xBC, 0x63, 0x18, 0xC6, 0x31, 0xCF, 0x00,
+ 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xEE, 0xEE, 0xE0, 0xE0, 0xE0, 0xE0, 0xFF, 0xC3, 0x0C, 0x30, 0xC3, 0x0D, 0xF7, 0xC3,
+ 0x0C, 0x38, 0xF0, 0x38, 0x0E, 0x03, 0x80, 0xE0, 0x3E, 0x0F, 0x07, 0x81, 0xE0, 0x38, 0x0E, 0x03, 0x80, 0xFF, 0x73,
+ 0x9C, 0xE7, 0xBF, 0xDE, 0x73, 0x9C, 0xE3, 0x80, 0x06, 0x03, 0x80, 0xC0, 0x00, 0xE0, 0xF8, 0x3F, 0x0F, 0xE3, 0xEC,
+ 0xFB, 0x3E, 0x6F, 0x9B, 0xE3, 0xF8, 0x7E, 0x1F, 0x83, 0x0C, 0x1C, 0x18, 0x00, 0xFE, 0xEE, 0xC7, 0xC7, 0xC7, 0xC7,
+ 0xC7, 0xC7, 0xC7, 0xE0, 0xF8, 0x3F, 0x0F, 0xE3, 0xEC, 0xFB, 0x3E, 0x6F, 0x9B, 0xE3, 0xF8, 0x7E, 0x1F, 0x83, 0x00,
+ 0x03, 0x01, 0xC0, 0xFE, 0xEE, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0x00, 0x18, 0x38, 0x1B, 0x07, 0x80, 0xC0,
+ 0x00, 0xE0, 0xF8, 0x3F, 0x0F, 0xE3, 0xEC, 0xFB, 0x3E, 0x6F, 0x9B, 0xE3, 0xF8, 0x7E, 0x1F, 0x83, 0x66, 0x3C, 0x18,
+ 0x00, 0xFE, 0xEE, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xE0, 0x70, 0x30, 0x18, 0x00, 0x03, 0xF9, 0xDC, 0xC7,
+ 0x63, 0xB1, 0xD8, 0xEC, 0x76, 0x3B, 0x1C, 0xE0, 0xF8, 0x3F, 0x0F, 0xE3, 0xEC, 0xFB, 0x3E, 0x6F, 0x9B, 0xE3, 0xF8,
+ 0x7E, 0x1F, 0x83, 0x00, 0xC0, 0x70, 0x3C, 0xFE, 0xEE, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0x07, 0x07, 0x0E,
+ 0x0F, 0x80, 0x00, 0x00, 0x00, 0x3F, 0x83, 0xDE, 0x38, 0x39, 0xC0, 0xCC, 0x07, 0x60, 0x3B, 0x01, 0xD8, 0x0E, 0xE0,
+ 0x67, 0x07, 0x1E, 0xF0, 0x7F, 0x00, 0x3F, 0x00, 0x00, 0x00, 0xFC, 0x7F, 0x98, 0x66, 0x1B, 0x87, 0x61, 0x98, 0x67,
+ 0xF8, 0xFC, 0x0D, 0x80, 0x7C, 0x00, 0x00, 0x3F, 0x83, 0xDE, 0x38, 0x39, 0xC0, 0xCC, 0x07, 0x60, 0x3B, 0x01, 0xD8,
+ 0x0E, 0xE0, 0x67, 0x07, 0x1E, 0xF0, 0x7F, 0x00, 0x3F, 0x07, 0x80, 0x00, 0xFC, 0x7F, 0x98, 0x66, 0x1B, 0x87, 0x61,
+ 0x98, 0x67, 0xF8, 0xFC, 0x07, 0xC0, 0x7E, 0x03, 0x60, 0x00, 0x01, 0xFC, 0x1E, 0xF1, 0xC1, 0xCE, 0x06, 0x60, 0x3B,
+ 0x01, 0xD8, 0x0E, 0xC0, 0x77, 0x03, 0x38, 0x38, 0xF7, 0x83, 0xF8, 0x0D, 0x87, 0xE1, 0xB0, 0x00, 0x3F, 0x1F, 0xE6,
+ 0x19, 0x86, 0xE1, 0xD8, 0x66, 0x19, 0xFE, 0x3F, 0x00, 0x1F, 0xFF, 0x1F, 0x70, 0x1C, 0x38, 0x0E, 0x1C, 0x06, 0x0E,
+ 0x03, 0x07, 0xF9, 0x83, 0x80, 0xC1, 0xC0, 0x70, 0xE0, 0x38, 0x70, 0x0F, 0xB8, 0x03, 0xFF, 0xF0, 0x3F, 0x7C, 0x7F,
+ 0xEE, 0x61, 0xC6, 0x61, 0xC7, 0xE1, 0xFF, 0x61, 0xC0, 0x61, 0xC0, 0x7F, 0xF6, 0x3F, 0x7E, 0x0C, 0x07, 0x01, 0x80,
+ 0x00, 0xFF, 0x3B, 0xEE, 0x3B, 0x8E, 0xE3, 0xBB, 0xEF, 0xF3, 0xB8, 0xE7, 0x38, 0xEE, 0x3B, 0x87, 0x18, 0xE3, 0x00,
+ 0xFF, 0xAC, 0x30, 0xC3, 0x0C, 0x30, 0xC0, 0xFF, 0x3B, 0xEE, 0x3B, 0x8E, 0xE3, 0xBB, 0xEF, 0xF3, 0xB8, 0xE7, 0x38,
+ 0xEE, 0x3B, 0x87, 0x00, 0x07, 0x03, 0x80, 0x7E, 0xE9, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x01, 0x87, 0x00, 0x66,
+ 0x0F, 0x01, 0x80, 0x00, 0xFF, 0x3B, 0xEE, 0x3B, 0x8E, 0xE3, 0xBB, 0xEF, 0xF3, 0xB8, 0xE7, 0x38, 0xEE, 0x3B, 0x87,
+ 0xC9, 0xE3, 0x00, 0xFF, 0xAC, 0x30, 0xC3, 0x0C, 0x30, 0xC0, 0x06, 0x0F, 0x07, 0x00, 0x03, 0xF3, 0xBB, 0x81, 0xC0,
+ 0x70, 0x1E, 0x03, 0xC0, 0x70, 0x38, 0x1F, 0x9F, 0xFE, 0x0C, 0x38, 0x60, 0x07, 0xEE, 0xF8, 0x38, 0x3C, 0x1C, 0x1B,
+ 0x7F, 0xE0, 0x0C, 0x0F, 0x0D, 0x80, 0x03, 0xF3, 0xBB, 0x81, 0xC0, 0x70, 0x1E, 0x03, 0xC0, 0x70, 0x38, 0x1F, 0x9F,
+ 0xFE, 0x18, 0x79, 0x90, 0x07, 0xEE, 0xF8, 0x38, 0x3C, 0x1C, 0x1B, 0x7F, 0xE0, 0x3F, 0x3B, 0xB8, 0x1C, 0x07, 0x01,
+ 0xE0, 0x3C, 0x07, 0x03, 0x81, 0xF9, 0xFF, 0xE1, 0xC0, 0xE0, 0xF0, 0x7E, 0xEF, 0x83, 0x83, 0xC1, 0xC1, 0xB7, 0xFE,
+ 0x70, 0xE1, 0xC0, 0x36, 0x0F, 0x03, 0x00, 0x03, 0xF3, 0xBB, 0x81, 0xC0, 0x70, 0x1E, 0x03, 0xC0, 0x70, 0x38, 0x1F,
+ 0x9F, 0xFE, 0x6C, 0x78, 0x60, 0x07, 0xEE, 0xF8, 0x38, 0x3C, 0x1C, 0x1B, 0x7F, 0xE0, 0xFF, 0xC3, 0x00, 0xC0, 0x30,
+ 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x1C, 0x07, 0x03, 0xC0, 0xC3, 0x0C, 0x3F, 0xC3, 0x0C,
+ 0x30, 0xC3, 0x0E, 0xDF, 0x30, 0xE7, 0x80, 0x36, 0x07, 0x80, 0xC0, 0x00, 0xFF, 0xC3, 0x00, 0xC0, 0x30, 0x0C, 0x03,
+ 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x1B, 0x6D, 0xB0, 0xFF, 0x0C, 0x30, 0xC3, 0x0C, 0x38, 0x7C, 0xFF,
+ 0xC3, 0x00, 0xC0, 0x30, 0x0C, 0x1F, 0xC0, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0xC3, 0x0C, 0x3F, 0xC3, 0x0C,
+ 0x3F, 0xC3, 0x0E, 0x1F, 0x3F, 0x0F, 0xC0, 0x00, 0x00, 0xC1, 0xF0, 0x7C, 0x1F, 0x07, 0xC1, 0xF0, 0x7C, 0x1F, 0x07,
+ 0xE1, 0xF8, 0x67, 0xF8, 0xFC, 0x7E, 0x7E, 0x00, 0x00, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xE7, 0xF7, 0x7F, 0x3F,
+ 0x00, 0x00, 0x03, 0x07, 0xC1, 0xF0, 0x7C, 0x1F, 0x07, 0xC1, 0xF0, 0x7C, 0x1F, 0x87, 0xE1, 0x9F, 0xE3, 0xF0, 0x7E,
+ 0x00, 0x00, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xE7, 0xF7, 0x7F, 0x3E, 0x07, 0x80, 0x03, 0x07, 0xC1, 0xF0, 0x7C,
+ 0x1F, 0x07, 0xC1, 0xF0, 0x7C, 0x1F, 0x87, 0xE1, 0x9F, 0xE3, 0xF0, 0x7C, 0x3C, 0x00, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7,
+ 0xC7, 0xE7, 0xF7, 0x7F, 0x1E, 0x07, 0x81, 0xE0, 0x78, 0x00, 0x30, 0x7C, 0x1F, 0x07, 0xC1, 0xF0, 0x7C, 0x1F, 0x07,
+ 0xC1, 0xF8, 0x7E, 0x19, 0xFE, 0x3F, 0x00, 0x3C, 0x3C, 0x3C, 0x3C, 0x00, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xE7,
+ 0xF7, 0x7F, 0x0F, 0x87, 0xE1, 0xF0, 0x00, 0xC1, 0xF0, 0x7C, 0x1F, 0x07, 0xC1, 0xF0, 0x7C, 0x1F, 0x07, 0xE1, 0xF8,
+ 0x67, 0xF8, 0xFC, 0x1F, 0x3F, 0x3C, 0x00, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xE7, 0xF7, 0x7F, 0xC1, 0xF0, 0x7C,
+ 0x1F, 0x07, 0xC1, 0xF0, 0x7C, 0x1F, 0x07, 0xE1, 0xF8, 0x67, 0xF8, 0xFC, 0x0E, 0x03, 0x00, 0xE0, 0xC7, 0xC7, 0xC7,
+ 0xC7, 0xC7, 0xC7, 0xE7, 0xF7, 0x7F, 0x0F, 0x0E, 0x0F, 0x01, 0x80, 0x03, 0xC0, 0x06, 0x40, 0x00, 0x00, 0xE0, 0x07,
+ 0xE1, 0x86, 0xE3, 0xC6, 0x63, 0xC6, 0x73, 0xCE, 0x77, 0xCE, 0x76, 0xEC, 0x36, 0x6C, 0x3E, 0x7C, 0x3C, 0x7C, 0x3C,
+ 0x38, 0x1C, 0x38, 0x07, 0x00, 0x7C, 0x03, 0x60, 0x00, 0x0E, 0x71, 0xF3, 0x9D, 0x9C, 0xEC, 0xF6, 0x7D, 0xB3, 0xEF,
+ 0x8F, 0x78, 0x79, 0xC1, 0x8E, 0x00, 0x0C, 0x03, 0xC0, 0x4C, 0x00, 0x0E, 0x1D, 0xC3, 0x9C, 0xE1, 0xDC, 0x3F, 0x03,
+ 0xC0, 0x78, 0x06, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60, 0x1C, 0x1F, 0x0D, 0x80, 0x0E, 0x37, 0x19, 0x9C, 0xCE, 0x76,
+ 0x1B, 0x0F, 0x83, 0x81, 0xC0, 0xE2, 0xE1, 0xE0, 0x3F, 0x07, 0xE0, 0x00, 0x70, 0xEE, 0x1C, 0xE7, 0x0E, 0xE1, 0xF8,
+ 0x1E, 0x03, 0xC0, 0x30, 0x06, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x06, 0x03, 0x80, 0xC0, 0x00, 0xFF, 0x80, 0xE0, 0x38,
+ 0x1C, 0x0E, 0x07, 0x01, 0xC0, 0xE0, 0x70, 0x1C, 0x0E, 0x03, 0xFF, 0x0C, 0x1C, 0x18, 0x00, 0xFF, 0x0E, 0x0E, 0x1C,
+ 0x38, 0x38, 0x70, 0xE0, 0xFF, 0x0C, 0x03, 0x00, 0x03, 0xFE, 0x03, 0x80, 0xE0, 0x70, 0x38, 0x1C, 0x07, 0x03, 0x81,
+ 0xC0, 0x70, 0x38, 0x0F, 0xFC, 0x18, 0x18, 0x00, 0xFF, 0x0E, 0x0E, 0x1C, 0x38, 0x38, 0x70, 0xE0, 0xFF, 0x33, 0x07,
+ 0x80, 0xC0, 0x00, 0xFF, 0x80, 0xE0, 0x38, 0x1C, 0x0E, 0x07, 0x01, 0xC0, 0xE0, 0x70, 0x1C, 0x0E, 0x03, 0xFF, 0x66,
+ 0x3C, 0x18, 0x00, 0xFF, 0x0E, 0x0E, 0x1C, 0x38, 0x38, 0x70, 0xE0, 0xFF, 0x1D, 0xFE, 0x30, 0xC3, 0x0C, 0x30, 0xC3,
+ 0x0C, 0x30, 0xC3, 0x00, 0x79, 0xF0, 0x38, 0x1E, 0x07, 0x00, 0x0F, 0xF7, 0x03, 0x81, 0xC0, 0xE0, 0x7F, 0x38, 0x1C,
+ 0x0E, 0x07, 0x03, 0x81, 0xFF, 0x7E, 0x3F, 0x00, 0x1F, 0xEE, 0x07, 0x03, 0x81, 0xC0, 0xFE, 0x70, 0x38, 0x1C, 0x0E,
+ 0x07, 0x03, 0xFE, 0xFF, 0xC0, 0xE0, 0x07, 0x00, 0x38, 0x01, 0xC0, 0x0F, 0xF0, 0x7B, 0xC3, 0x86, 0x1C, 0x38, 0xE1,
+ 0x87, 0x1C, 0x3B, 0xC0, 0x0C, 0x1C, 0x18, 0x00, 0xFF, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0,
+ 0xE0, 0x1F, 0xC7, 0x99, 0xC0, 0x38, 0x06, 0x00, 0xFE, 0x18, 0x03, 0x00, 0x70, 0x0E, 0x00, 0xF3, 0x0F, 0xE0, 0x3F,
+ 0x3B, 0xB8, 0x1C, 0x07, 0x01, 0xE0, 0x3C, 0x07, 0x03, 0x81, 0xF9, 0xFF, 0xE0, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xEF,
+ 0xB0, 0x0E, 0x38, 0xE3, 0x8E, 0x38, 0xE3, 0x8E, 0x38, 0xE3, 0x80, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+ 0x06, 0x06, 0xEE, 0xFC, 0x1F, 0xE0, 0x07, 0x38, 0x01, 0xCE, 0x00, 0x73, 0x80, 0x1C, 0xE0, 0x07, 0x3F, 0xC1, 0xCE,
+ 0x78, 0x63, 0x86, 0x18, 0xE1, 0xCE, 0x38, 0x67, 0x0E, 0x7B, 0x83, 0xFC, 0x00, 0x00, 0x00, 0xE1, 0xC0, 0xE1, 0xC0,
+ 0xE1, 0xC0, 0xE1, 0xC0, 0xE1, 0xC0, 0xFF, 0xFE, 0xE1, 0xDF, 0xE1, 0xC7, 0xE1, 0xC3, 0xE1, 0xC7, 0xE1, 0xCF, 0xE1,
+ 0xFE, 0xFF, 0xC1, 0xC0, 0x1C, 0x01, 0xC0, 0x1C, 0x01, 0xFE, 0x1F, 0xF1, 0xC7, 0x1C, 0x31, 0xC3, 0x1C, 0x31, 0xC3,
+ 0x0E, 0x07, 0x81, 0xC0, 0x00, 0xE3, 0xB9, 0xCE, 0xF3, 0xF8, 0xFC, 0x3E, 0x0F, 0xC3, 0xF8, 0xEF, 0x39, 0xCE, 0x3B,
+ 0x87, 0x18, 0x07, 0x00, 0xE0, 0x00, 0xE0, 0xF8, 0x7E, 0x3F, 0x9F, 0xE6, 0xFB, 0xBE, 0xCF, 0xE3, 0xF8, 0xFC, 0x3E,
+ 0x0F, 0x83, 0x3B, 0x07, 0xC0, 0x03, 0x87, 0xE1, 0xDC, 0x77, 0x18, 0xEE, 0x3B, 0x07, 0xC1, 0xF0, 0x38, 0x0E, 0x17,
+ 0x0F, 0x80, 0xE1, 0xF8, 0x7E, 0x1F, 0x87, 0xE1, 0xF8, 0x7E, 0x1F, 0x87, 0xE1, 0xF8, 0x7E, 0x1F, 0xFF, 0x0C, 0x03,
+ 0x00, 0xC0, 0x0E, 0x00, 0xE0, 0x1F, 0x01, 0xB0, 0x1B, 0x83, 0xB8, 0x31, 0xC7, 0x1C, 0x7F, 0xC6, 0x0E, 0xE0, 0xEE,
+ 0x06, 0xFF, 0x70, 0x38, 0x1C, 0x0E, 0x07, 0xFB, 0xBF, 0xC7, 0xE1, 0xF1, 0xF9, 0xFF, 0xE0, 0xFF, 0x3B, 0xEE, 0x3B,
+ 0x8E, 0xE7, 0xBF, 0xCE, 0x7B, 0x86, 0xE1, 0xB8, 0x6E, 0x7B, 0xFC, 0xFF, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0,
+ 0xE0, 0xE0, 0xE0, 0xE0, 0x1F, 0xE1, 0xCE, 0x1C, 0xE1, 0xCE, 0x1C, 0xE1, 0xCE, 0x18, 0xE1, 0x8E, 0x38, 0xE3, 0x8E,
+ 0x70, 0xEF, 0xFF, 0xC0, 0x3C, 0x03, 0xC0, 0x30, 0xFF, 0x70, 0x38, 0x1C, 0x0E, 0x07, 0xF3, 0x81, 0xC0, 0xE0, 0x70,
+ 0x38, 0x1F, 0xF0, 0xF3, 0x8E, 0xE7, 0x38, 0xEE, 0xE0, 0xFF, 0x80, 0xFF, 0x00, 0xFC, 0x03, 0xFC, 0x0D, 0xDC, 0x3B,
+ 0x98, 0xE7, 0x39, 0xCE, 0x3F, 0x1C, 0x70, 0x7F, 0x3B, 0x80, 0xE0, 0x70, 0xF1, 0xF8, 0x3E, 0x07, 0x01, 0x81, 0xF9,
+ 0xFF, 0xE0, 0xE0, 0xF8, 0x7E, 0x3F, 0x9F, 0xE6, 0xFB, 0xBE, 0xCF, 0xE3, 0xF8, 0xFC, 0x3E, 0x0F, 0x83, 0x3F, 0x07,
+ 0xC0, 0x03, 0x83, 0xE1, 0xF8, 0xFE, 0x7F, 0x9B, 0xEE, 0xFB, 0x3F, 0x8F, 0xE3, 0xF0, 0xF8, 0x3E, 0x0C, 0xE3, 0xB9,
+ 0xCE, 0xF3, 0xF8, 0xFC, 0x3E, 0x0F, 0xC3, 0xF8, 0xEF, 0x39, 0xCE, 0x3B, 0x87, 0x1F, 0xE3, 0x9C, 0x73, 0x8E, 0x71,
+ 0xCE, 0x39, 0xC7, 0x38, 0xC7, 0x18, 0xE7, 0x1D, 0xC3, 0xF0, 0x70, 0x60, 0x37, 0x83, 0xBC, 0x1D, 0xF1, 0xFD, 0x8F,
+ 0xE4, 0xDF, 0x36, 0xF9, 0xA7, 0xC7, 0x3E, 0x38, 0xF0, 0x07, 0x80, 0x30, 0xE1, 0xF8, 0x7E, 0x1F, 0x87, 0xE1, 0xFF,
+ 0xFE, 0x1F, 0x87, 0xE1, 0xF8, 0x7E, 0x1F, 0x87, 0x1F, 0xC1, 0xEF, 0x1C, 0x1C, 0xE0, 0x66, 0x03, 0xB0, 0x1D, 0x80,
+ 0xEC, 0x07, 0x70, 0x33, 0x83, 0x8F, 0x78, 0x3F, 0x80, 0xFF, 0xF8, 0x7E, 0x1F, 0x87, 0xE1, 0xF8, 0x7E, 0x1F, 0x87,
+ 0xE1, 0xF8, 0x7E, 0x1F, 0x87, 0xFE, 0x77, 0xB8, 0xFC, 0x3E, 0x1F, 0x1F, 0xBD, 0xFC, 0xE0, 0x70, 0x38, 0x1C, 0x00,
+ 0x02, 0x03, 0xF8, 0xF3, 0x38, 0x07, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60, 0x0E, 0x01, 0xC0, 0x1E, 0x61, 0xFC, 0xFF,
+ 0xC3, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0xE1, 0xF8, 0x77, 0x1D, 0xC6,
+ 0x3B, 0x8E, 0xC1, 0xF0, 0x7C, 0x0E, 0x03, 0x85, 0xC3, 0xE0, 0x03, 0x80, 0x7F, 0x83, 0xBF, 0x9C, 0xE6, 0x63, 0x9D,
+ 0x8E, 0x76, 0x38, 0xD8, 0xE7, 0x63, 0x9D, 0xCE, 0x63, 0xBF, 0x87, 0xFC, 0x03, 0x80, 0xE1, 0xCE, 0x38, 0xEE, 0x1F,
+ 0x81, 0xF0, 0x1C, 0x07, 0x80, 0xF8, 0x3B, 0x8E, 0x71, 0xC7, 0x70, 0x70, 0xE1, 0xDC, 0x3B, 0x87, 0x70, 0xEE, 0x1D,
+ 0xC3, 0xB8, 0x77, 0x0E, 0xE1, 0xDC, 0x3B, 0x87, 0x7F, 0xF0, 0x06, 0x00, 0xC0, 0x18, 0x61, 0xD8, 0x76, 0x1D, 0x87,
+ 0x61, 0xD8, 0x77, 0xBC, 0xFF, 0x01, 0xC0, 0x70, 0x1C, 0x07, 0xE3, 0x8F, 0xC7, 0x1F, 0x8E, 0x3F, 0x1C, 0x7E, 0x38,
+ 0xFC, 0x71, 0xF8, 0xE3, 0xF1, 0xC7, 0xE3, 0x8F, 0xC7, 0x1F, 0x8E, 0x3F, 0xFF, 0xF0, 0xE3, 0x8E, 0xE3, 0x8E, 0xE3,
+ 0x8E, 0xE3, 0x8E, 0xE3, 0x8E, 0xE3, 0x8E, 0xE3, 0x8E, 0xE3, 0x8E, 0xE3, 0x8E, 0xE3, 0x8E, 0xE3, 0x8E, 0xFF, 0xFF,
+ 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0xF8, 0x01, 0x80, 0x18, 0x01, 0x80, 0x18, 0x01, 0xFE, 0x19, 0xF1, 0x87, 0x18,
+ 0x31, 0x87, 0x19, 0xF1, 0xFE, 0xE0, 0x3F, 0x01, 0xF8, 0x0F, 0xC0, 0x7E, 0x03, 0xFF, 0x9F, 0xBE, 0xFC, 0x77, 0xE3,
+ 0xBF, 0x1D, 0xFB, 0xEF, 0xFE, 0x70, 0xE0, 0x70, 0x38, 0x1C, 0x0E, 0x07, 0xFB, 0xBF, 0xC7, 0xE1, 0xF1, 0xFB, 0xFF,
+ 0xE0, 0xFE, 0x3B, 0xE0, 0x38, 0x07, 0x01, 0xCF, 0xF0, 0x1C, 0x07, 0x01, 0xC0, 0xEC, 0x7B, 0xF8, 0xE1, 0xF8, 0xE3,
+ 0xDC, 0xE7, 0x0E, 0xE6, 0x07, 0xEE, 0x07, 0xFE, 0x07, 0xEE, 0x07, 0xE6, 0x07, 0xE6, 0x07, 0xE7, 0x0E, 0xE3, 0xDE,
+ 0xE1, 0xF8, 0x3F, 0xDE, 0x76, 0x1D, 0x87, 0x61, 0xDE, 0x73, 0xFC, 0xE7, 0x39, 0xDC, 0x77, 0x1F, 0x87, 0x7E, 0x2F,
+ 0x03, 0x7F, 0x77, 0xE3, 0xE3, 0x73, 0x7F, 0x1F, 0x3F, 0x18, 0x1C, 0x0F, 0xE7, 0xBB, 0x0D, 0x87, 0xC3, 0xE1, 0xF8,
+ 0xDE, 0xE7, 0xE0, 0xFE, 0xCE, 0xC7, 0xCE, 0xFE, 0xCF, 0xC3, 0xCF, 0xFE, 0xFF, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC0,
+ 0x3F, 0x8E, 0x63, 0x98, 0xE6, 0x31, 0x8C, 0x67, 0x19, 0xC6, 0xFF, 0xF0, 0x3C, 0x0C, 0x3F, 0x3F, 0x98, 0xEC, 0x7F,
+ 0xFB, 0x01, 0x80, 0xF6, 0x3F, 0x00, 0x67, 0x33, 0xBB, 0x8F, 0xF8, 0x3F, 0x81, 0xFC, 0x0F, 0xE0, 0xDD, 0x8C, 0xE6,
+ 0xE7, 0x38, 0x7E, 0x6E, 0x07, 0x0E, 0x3E, 0x0F, 0x07, 0x6F, 0xFE, 0xC7, 0xC7, 0xCF, 0xCF, 0xDB, 0xF3, 0xF3, 0xE3,
+ 0xE3, 0x7E, 0x3C, 0x00, 0xC7, 0xC7, 0xCF, 0xCF, 0xDB, 0xF3, 0xF3, 0xE3, 0xE3, 0xC7, 0xCE, 0xDC, 0xF8, 0xF8, 0xFC,
+ 0xDE, 0xCE, 0xC7, 0x3F, 0x9C, 0xCE, 0x67, 0x33, 0x99, 0x8C, 0xC6, 0xE3, 0xE1, 0x80, 0xE1, 0xDC, 0x3B, 0xC7, 0x69,
+ 0xED, 0xBD, 0xBD, 0xF3, 0xBE, 0x67, 0xC0, 0xE0, 0xC3, 0xC3, 0xC3, 0xC3, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3, 0x3F, 0x1F,
+ 0xE6, 0x19, 0x86, 0xE1, 0xD8, 0x66, 0x19, 0xFE, 0x3F, 0x00, 0xFF, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7,
+ 0xFE, 0x77, 0xB0, 0xD8, 0x6C, 0x3E, 0x1B, 0x0D, 0xFE, 0xFE, 0x60, 0x30, 0x18, 0x00, 0x3F, 0x7A, 0x60, 0x60, 0xE0,
+ 0x60, 0x60, 0x7B, 0x3F, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xE3, 0x71, 0x99, 0xCC, 0xE7, 0x61,
+ 0xB0, 0xF8, 0x38, 0x1C, 0x0E, 0x2E, 0x1E, 0x00, 0x07, 0x00, 0x38, 0x01, 0xC0, 0x1F, 0x03, 0xFE, 0x3F, 0xF9, 0xDC,
+ 0xCC, 0xE7, 0xE7, 0x3B, 0x39, 0x99, 0xCC, 0xFF, 0xE3, 0xFE, 0x03, 0x80, 0x1C, 0x00, 0xE0, 0xE3, 0x3B, 0x8F, 0x87,
+ 0x81, 0xC1, 0xF1, 0xF8, 0xCE, 0xE3, 0x80, 0xC7, 0x63, 0xB1, 0xD8, 0xEC, 0x76, 0x3B, 0x1D, 0x8E, 0xFF, 0x80, 0xC0,
+ 0x60, 0xE3, 0xE3, 0xE3, 0xE3, 0x7F, 0x3F, 0x03, 0x03, 0x03, 0xC6, 0x3C, 0x63, 0xC6, 0x3C, 0x63, 0xC6, 0x3C, 0x63,
+ 0xC6, 0x3C, 0x63, 0xFF, 0xF0, 0xC6, 0x36, 0x31, 0xB1, 0x8D, 0x8C, 0x6C, 0x63, 0x63, 0x1B, 0x18, 0xD8, 0xC6, 0xFF,
+ 0xF8, 0x00, 0xC0, 0x06, 0xF8, 0x0E, 0x03, 0x80, 0xFE, 0x3B, 0xCE, 0x33, 0x8C, 0xEF, 0x3F, 0x80, 0xC0, 0xF8, 0x1F,
+ 0x03, 0xFF, 0x7E, 0xEF, 0x8F, 0xF1, 0xFE, 0x77, 0xFE, 0xE0, 0xC0, 0xC0, 0xC0, 0xFE, 0xEF, 0xC7, 0xC7, 0xCF, 0xFE,
+ 0x7C, 0x6E, 0x07, 0x07, 0x3F, 0x07, 0x07, 0xDE, 0xFC, 0xC7, 0xEC, 0xFE, 0xCC, 0x7C, 0xC3, 0xFC, 0x3D, 0xC3, 0xCC,
+ 0x7C, 0xFE, 0xC7, 0xE0, 0x3F, 0x73, 0x63, 0x63, 0x73, 0x3F, 0x33, 0x73, 0xE3, 0x18, 0x0E, 0x03, 0x00, 0x03, 0xF3,
+ 0xF9, 0x8E, 0xC7, 0xFF, 0xB0, 0x18, 0x0F, 0x63, 0xF0, 0x3F, 0x1F, 0x80, 0x07, 0xE7, 0xF3, 0x1D, 0x8F, 0xFF, 0x60,
+ 0x30, 0x1E, 0xC7, 0xE0, 0x60, 0x30, 0x3F, 0x0C, 0x07, 0xF3, 0xB9, 0x8E, 0xC7, 0x63, 0xB1, 0xD8, 0xEC, 0x76, 0x38,
+ 0x1C, 0x0E, 0x0E, 0x1C, 0xF3, 0x80, 0xFF, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC0, 0x3F, 0x7B, 0x60, 0x60, 0xFE, 0x60,
+ 0x60, 0x7B, 0x3F, 0x7E, 0xEF, 0x83, 0x83, 0xC1, 0xC1, 0xB7, 0xFE, 0xD8, 0x0D, 0xB6, 0xDB, 0x6C, 0xFF, 0xF0, 0x0C,
+ 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x18, 0x60, 0x00, 0x18, 0x61, 0x86, 0x18, 0x61, 0x86, 0x18, 0x63, 0x9C, 0x3F,
+ 0x80, 0x73, 0x00, 0xE6, 0x01, 0xCF, 0xE3, 0x99, 0xC6, 0x31, 0xCC, 0x63, 0xB8, 0xCE, 0xE1, 0xFC, 0xC3, 0x06, 0x18,
+ 0x30, 0xC1, 0xFF, 0xEC, 0x33, 0xE1, 0x8F, 0x0C, 0x78, 0x67, 0xC3, 0xF0, 0x60, 0x30, 0x3F, 0x0C, 0x07, 0xF3, 0xB9,
+ 0x8E, 0xC7, 0x63, 0xB1, 0xD8, 0xEC, 0x76, 0x38, 0x0C, 0x1C, 0x18, 0x00, 0xC7, 0xCE, 0xDC, 0xF8, 0xF8, 0xFC, 0xDE,
+ 0xCE, 0xC7, 0x30, 0x38, 0x18, 0x00, 0xC7, 0xC7, 0xCF, 0xCF, 0xDB, 0xF3, 0xF3, 0xE3, 0xE3, 0x36, 0x1F, 0x00, 0x1C,
+ 0x6E, 0x33, 0x39, 0x9C, 0xEC, 0x36, 0x1F, 0x07, 0x03, 0x81, 0xC5, 0xC3, 0xC0, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0xC7,
+ 0xC7, 0xC7, 0xFF, 0x18, 0x18, 0x18, 0x01, 0x80, 0xFF, 0x81, 0x80, 0x18, 0x01, 0x80, 0x1F, 0xE1, 0x9F, 0x18, 0x71,
+ 0x83, 0x18, 0x71, 0x9F, 0x1F, 0xE0, 0x38, 0x0E, 0x03, 0x80, 0xE0, 0xFF, 0x0E, 0x03, 0x80, 0xFE, 0x3B, 0xCE, 0x33,
+ 0x8C, 0xEF, 0x3F, 0x80, 0x1F, 0xC1, 0xE7, 0x1C, 0x1C, 0xE0, 0x66, 0x03, 0xBF, 0xFD, 0xDF, 0xEC, 0x07, 0x70, 0x33,
+ 0x83, 0x8F, 0x38, 0x3F, 0x80, 0x3F, 0x1F, 0xE6, 0x19, 0xF6, 0xFF, 0xD8, 0x66, 0x19, 0xCE, 0x3F, 0x00, 0xE0, 0x7B,
+ 0x83, 0xE6, 0x0C, 0x1C, 0x70, 0x71, 0xC0, 0xC6, 0x03, 0xB8, 0x0E, 0xE0, 0x1F, 0x00, 0x7C, 0x00, 0xE0, 0x03, 0x80,
+ 0xE3, 0xF8, 0xC6, 0x71, 0x98, 0x76, 0x0F, 0x83, 0xC0, 0xF0, 0x1C, 0x00, 0x3F, 0x01, 0xE0, 0x00, 0x0E, 0x0C, 0xE1,
+ 0xCE, 0x3C, 0xE7, 0xCE, 0x6C, 0xEE, 0xCE, 0xCC, 0xF8, 0xCF, 0x8C, 0xF0, 0xCE, 0x0C, 0xE0, 0xF0, 0x07, 0x00, 0xE0,
+ 0x0E, 0x7E, 0x0F, 0x00, 0x03, 0x1C, 0xC7, 0x33, 0xCC, 0xF3, 0x6C, 0xF3, 0x3C, 0xCE, 0x33, 0x8F, 0x01, 0xC0, 0xE0,
+ 0x38, 0x70, 0x1F, 0x81, 0xC0, 0x38, 0x07, 0x00, 0xFF, 0x1D, 0xF3, 0x8E, 0x70, 0xCE, 0x19, 0xC7, 0x39, 0xE7, 0xF8,
+ 0x70, 0x38, 0x3F, 0x0E, 0x07, 0xF3, 0xBD, 0xCE, 0xEF, 0x7F, 0x00, 0xFE, 0x77, 0xB8, 0xFC, 0x3E, 0x9F, 0xFF, 0xBF,
+ 0xFE, 0xE7, 0x71, 0xB8, 0x1C, 0x00, 0xFE, 0x77, 0xB0, 0xD8, 0x6C, 0x3E, 0x1B, 0x7D, 0xFE, 0xFE, 0x63, 0x30, 0xD8,
+ 0x00, 0x07, 0x07, 0xFF, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0x0C, 0x3F, 0xF0, 0xC3,
+ 0x0C, 0x30, 0xC3, 0x0C, 0x00, 0x7F, 0xB8, 0x1C, 0x0E, 0x07, 0x07, 0xE1, 0xC0, 0xE0, 0x70, 0x38, 0x1C, 0x0E, 0x00,
+ 0x7E, 0xE1, 0xC3, 0x8F, 0xCE, 0x1C, 0x38, 0x70, 0xFF, 0x70, 0x38, 0x1C, 0x0F, 0xE7, 0x3B, 0x8F, 0xC3, 0xE1, 0xF0,
+ 0xF8, 0x7C, 0x30, 0x38, 0x38, 0x78, 0xFE, 0xC0, 0xC0, 0xFE, 0xEE, 0xC7, 0xC3, 0xC3, 0xC7, 0x07, 0x0E, 0x1E, 0xF3,
+ 0x8E, 0x73, 0x9C, 0x3B, 0xB8, 0x1F, 0xF0, 0x0F, 0xF0, 0x07, 0xE0, 0x0F, 0xF0, 0x1B, 0xB0, 0x3B, 0x98, 0x73, 0x9C,
+ 0x73, 0x8C, 0xE3, 0x8F, 0x00, 0x03, 0x00, 0x03, 0x00, 0x03, 0x67, 0x31, 0xDD, 0xC3, 0xFE, 0x07, 0xF0, 0x1F, 0x80,
+ 0x7F, 0x03, 0x7E, 0x19, 0xDC, 0xE7, 0x3C, 0x00, 0x70, 0x01, 0xC0, 0x7F, 0x3B, 0x80, 0xE0, 0x70, 0xF1, 0xF8, 0x3E,
+ 0x07, 0x01, 0x81, 0xF9, 0xFF, 0xE1, 0xC0, 0xE0, 0xF0, 0x7E, 0x6E, 0x07, 0x0E, 0x3E, 0x0F, 0x07, 0x6F, 0xFE, 0x18,
+ 0x1C, 0x38, 0xE3, 0x9C, 0xE3, 0x98, 0x76, 0x0F, 0xC1, 0xF0, 0x3E, 0x07, 0x60, 0xEE, 0x1C, 0xE3, 0x8E, 0x70, 0xE0,
+ 0x0C, 0x01, 0x80, 0x30, 0xC7, 0x67, 0x37, 0x1F, 0x0F, 0x06, 0xC3, 0x71, 0x9C, 0xC7, 0x81, 0xC0, 0xE0, 0x70, 0xE0,
+ 0xFC, 0x3B, 0xEE, 0x7F, 0x8F, 0xE1, 0xF8, 0x3F, 0x87, 0xF8, 0xFB, 0x9C, 0x33, 0x87, 0x70, 0x70, 0xC3, 0xB1, 0xCD,
+ 0xE3, 0x78, 0xFC, 0x37, 0x8D, 0xF3, 0x0E, 0xC1, 0x80, 0x71, 0xFF, 0x79, 0xDE, 0x3F, 0x87, 0xE0, 0xF8, 0x1F, 0x83,
+ 0xF8, 0x77, 0x8E, 0x79, 0xC7, 0x38, 0x70, 0x73, 0xBF, 0xC7, 0xE1, 0xF0, 0x7C, 0x1F, 0x87, 0x71, 0xCE, 0x73, 0x80,
+ 0xF8, 0x70, 0xC7, 0x06, 0x70, 0x37, 0x01, 0xF0, 0x0F, 0x80, 0x7E, 0x03, 0x78, 0x19, 0xE0, 0xC7, 0x06, 0x1C, 0x30,
+ 0x70, 0xF9, 0xC7, 0x70, 0xFC, 0x1F, 0x03, 0xE0, 0x7E, 0x0F, 0xE1, 0xDE, 0x39, 0xC0, 0xE1, 0xDC, 0x3B, 0x87, 0x70,
+ 0xEE, 0x1D, 0xFF, 0xB8, 0x77, 0x0E, 0xE1, 0xDC, 0x3B, 0x87, 0x70, 0xF0, 0x06, 0x00, 0xC0, 0x18, 0xC3, 0x61, 0xB0,
+ 0xD8, 0x6F, 0xF6, 0x1B, 0x0D, 0x86, 0xC3, 0x80, 0xC0, 0x60, 0xE1, 0xFF, 0x0E, 0x38, 0x71, 0xC3, 0x8E, 0x1C, 0x7F,
+ 0xE3, 0x87, 0x1C, 0x38, 0xE1, 0xC7, 0x0E, 0x38, 0x71, 0xC3, 0x80, 0xC3, 0xF0, 0xCC, 0x33, 0x0C, 0xFF, 0x30, 0xCC,
+ 0x33, 0x0C, 0xC3, 0x00, 0xFF, 0xC0, 0xE1, 0xC0, 0xE1, 0xC0, 0xE1, 0xC0, 0xE1, 0xFC, 0xE1, 0xFE, 0xE1, 0xC7, 0xE1,
+ 0xC3, 0xE1, 0xC3, 0xE1, 0xC3, 0xE1, 0xC3, 0xE1, 0xC7, 0x00, 0x07, 0x00, 0x0E, 0x00, 0x3C, 0xFF, 0x03, 0x1C, 0x0C,
+ 0x70, 0x31, 0xFC, 0xC7, 0x7B, 0x1C, 0x6C, 0x71, 0xB1, 0xC7, 0xC7, 0x18, 0x00, 0x60, 0x03, 0x80, 0x3C, 0x1F, 0xC1,
+ 0xE6, 0x1C, 0x00, 0xE0, 0x06, 0x1E, 0x31, 0xF9, 0x9C, 0xEC, 0xE7, 0x77, 0x3B, 0xB9, 0x8F, 0xDC, 0x3F, 0xC0, 0x38,
+ 0x00, 0xF0, 0x03, 0xC0, 0x3E, 0x1E, 0x06, 0x79, 0xBF, 0xEC, 0xDB, 0x36, 0xDD, 0xFE, 0x3F, 0x01, 0xE0, 0x3C, 0x02,
+ 0x03, 0xF8, 0xF3, 0x38, 0x07, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x60, 0x0E, 0x01, 0xC0, 0x1E, 0x61, 0xFC, 0x0E, 0x01,
+ 0xC0, 0x78, 0x3F, 0x7A, 0x60, 0x60, 0xE0, 0x60, 0x60, 0x7B, 0x3F, 0x0C, 0x0E, 0x1C, 0xFF, 0xC3, 0x00, 0xC0, 0x30,
+ 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x3C, 0x07, 0x01, 0xC0, 0x70, 0xFF, 0x18, 0x18, 0x18, 0x18,
+ 0x18, 0x18, 0x18, 0x1C, 0x0C, 0x0C, 0xE1, 0xDC, 0x39, 0xCE, 0x1D, 0xC3, 0xF0, 0x3C, 0x07, 0x80, 0x60, 0x0C, 0x01,
+ 0x80, 0x30, 0x06, 0x00, 0xE3, 0xF1, 0x99, 0xCC, 0xE7, 0x61, 0xF0, 0xF8, 0x78, 0x1C, 0x0C, 0x06, 0x03, 0x00, 0xE1,
+ 0xDC, 0x39, 0xCE, 0x19, 0xC3, 0xF0, 0x3C, 0x07, 0x83, 0xFC, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0xE3, 0xF1, 0x99,
+ 0xCE, 0xE7, 0x61, 0xF0, 0xF8, 0x78, 0x7F, 0x0C, 0x06, 0x03, 0x00, 0xE1, 0xCE, 0x38, 0xEE, 0x1F, 0x81, 0xF0, 0x1C,
+ 0x07, 0x80, 0xF8, 0x3B, 0x8E, 0x71, 0xC7, 0x70, 0x70, 0x06, 0x00, 0xC0, 0x18, 0xE3, 0x3B, 0x8F, 0x87, 0x81, 0xC1,
+ 0xF1, 0xF8, 0xCE, 0xE3, 0x80, 0xC0, 0x60, 0xFF, 0x98, 0x38, 0x30, 0x70, 0x60, 0xE0, 0xC1, 0xC1, 0x83, 0x83, 0x07,
+ 0x06, 0x0E, 0x0C, 0x1C, 0x18, 0x38, 0x30, 0x70, 0x60, 0xFF, 0xF0, 0x00, 0xE0, 0x01, 0xC0, 0x03, 0x80, 0xFE, 0xC7,
+ 0x18, 0xE3, 0x1C, 0x63, 0x8C, 0x71, 0x8E, 0x31, 0xC6, 0x3F, 0xE0, 0x0C, 0x01, 0x80, 0x61, 0xCC, 0x39, 0x87, 0x30,
+ 0xE6, 0x1C, 0xC3, 0x9E, 0xF1, 0xFE, 0x01, 0xC0, 0x38, 0x07, 0x00, 0xF0, 0x06, 0x00, 0xC0, 0x18, 0x03, 0xE3, 0x71,
+ 0xB8, 0xDC, 0x67, 0xF1, 0xF8, 0x0C, 0x06, 0x03, 0x80, 0xC0, 0x60, 0x61, 0xD8, 0x76, 0x1D, 0x87, 0x61, 0xDB, 0x77,
+ 0xFC, 0xFF, 0x0D, 0xC3, 0x70, 0x1C, 0x07, 0xE3, 0xE3, 0xFB, 0xFB, 0x7F, 0x7F, 0x1B, 0x1B, 0x03, 0xE0, 0x70, 0x38,
+ 0x1C, 0x0F, 0xF7, 0xBF, 0x8F, 0xC3, 0xE1, 0xF0, 0xF8, 0x7C, 0x30, 0xC0, 0xC0, 0xC0, 0xC0, 0xFE, 0xEE, 0xC7, 0xC7,
+ 0xC7, 0xC7, 0xC7, 0xC7, 0xC7, 0x07, 0xF0, 0x3F, 0xE0, 0xC3, 0xB7, 0x06, 0xFC, 0x1D, 0xFF, 0xF1, 0x80, 0x07, 0x00,
+ 0x1C, 0x00, 0x30, 0x00, 0xF3, 0x81, 0xFE, 0x0F, 0xC3, 0xBB, 0x63, 0xFC, 0x7F, 0xFE, 0x70, 0x06, 0x00, 0xF6, 0x0F,
+ 0xC0, 0x07, 0xF0, 0x3F, 0xE0, 0xC3, 0xB7, 0x06, 0xFC, 0x1D, 0xFF, 0xF1, 0x80, 0x07, 0x00, 0x1C, 0x00, 0x30, 0x00,
+ 0xF3, 0x81, 0xFE, 0x01, 0xC0, 0x07, 0x00, 0x1C, 0x00, 0x0F, 0xC3, 0xBB, 0x63, 0xFC, 0x7F, 0xFE, 0x70, 0x06, 0x00,
+ 0xF6, 0x0F, 0xC0, 0x60, 0x0C, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x06, 0xE0, 0x0F, 0x80, 0x00, 0x07, 0x9C, 0x77,
+ 0x39, 0xC7, 0x77, 0x07, 0xFC, 0x07, 0xF8, 0x07, 0xE0, 0x1F, 0xE0, 0x6E, 0xE1, 0xDC, 0xC7, 0x39, 0xCE, 0x71, 0xF8,
+ 0xE3, 0x80, 0x0D, 0x80, 0x7C, 0x00, 0x00, 0xCE, 0x67, 0x77, 0x1F, 0xF0, 0x7F, 0x03, 0xF8, 0x1F, 0xC1, 0xBB, 0x19,
+ 0xCD, 0xCE, 0x70, 0xE3, 0xF3, 0xBB, 0x9F, 0x8F, 0xC7, 0xF3, 0xBD, 0xC7, 0xE1, 0xF0, 0xF8, 0x7C, 0x30, 0x38, 0xF8,
+ 0x78, 0xC7, 0xCE, 0xDC, 0xF8, 0xFE, 0xDE, 0xC7, 0xC3, 0xC3, 0x07, 0x0E, 0x1E, 0x1F, 0xE0, 0xE7, 0x07, 0x38, 0x39,
+ 0xC1, 0xCE, 0x0E, 0x70, 0x73, 0x83, 0x1C, 0x18, 0xE1, 0xC7, 0x1C, 0x39, 0xC1, 0xE0, 0x03, 0x00, 0x38, 0x01, 0x80,
+ 0x3F, 0x87, 0x30, 0xE6, 0x1C, 0xC3, 0x98, 0x63, 0x0C, 0x63, 0x8C, 0xE1, 0xE0, 0x1C, 0x07, 0x00, 0xE0, 0xE1, 0xF8,
+ 0x7E, 0x1F, 0x87, 0xE1, 0xFF, 0xFE, 0x1F, 0x87, 0xE1, 0xF8, 0x7E, 0x1F, 0x87, 0x01, 0xC0, 0x70, 0x38, 0xC3, 0xC3,
+ 0xC3, 0xC3, 0xFF, 0xC3, 0xC3, 0xC3, 0xC3, 0x03, 0x07, 0x0E, 0xE1, 0xCE, 0x1C, 0xE1, 0xCE, 0x1C, 0xE1, 0xCF, 0xFC,
+ 0xE1, 0xCE, 0x1C, 0xE1, 0xCE, 0x1C, 0xE1, 0xCE, 0x1E, 0x00, 0x60, 0x0E, 0x00, 0xC0, 0xC3, 0x30, 0xCC, 0x33, 0x0C,
+ 0xFF, 0x30, 0xCC, 0x33, 0x0C, 0xC3, 0x80, 0x60, 0x38, 0x0C, 0xC3, 0xE1, 0xF0, 0xF8, 0x7C, 0x3E, 0x1F, 0xDE, 0xFF,
+ 0x03, 0x81, 0xC0, 0xE0, 0xF0, 0x60, 0x30, 0x18, 0xE3, 0xE3, 0xE3, 0xE3, 0x7F, 0x3F, 0x03, 0x03, 0x0F, 0x0E, 0x0E,
+ 0x60, 0x31, 0xE0, 0xE3, 0xC1, 0xC7, 0xC7, 0xCD, 0x8F, 0x99, 0x37, 0x33, 0x6E, 0x66, 0x9C, 0xC7, 0x39, 0x8E, 0x33,
+ 0x00, 0x66, 0x00, 0xF0, 0x00, 0xC0, 0x03, 0x80, 0x06, 0x00, 0xE1, 0xCE, 0x1C, 0xF1, 0xCD, 0x3C, 0xDB, 0xCD, 0xEE,
+ 0xCE, 0xEC, 0xCE, 0xC0, 0xF0, 0x07, 0x00, 0x70, 0x06, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x1B, 0x01, 0xF0, 0x00, 0x00,
+ 0xE0, 0x0E, 0x01, 0xF0, 0x1B, 0x01, 0xB8, 0x3B, 0x83, 0x1C, 0x71, 0xC7, 0xFC, 0x60, 0xEE, 0x0E, 0xE0, 0x60, 0x36,
+ 0x3E, 0x00, 0x7E, 0x2F, 0x03, 0x7F, 0x77, 0xE3, 0xE3, 0x73, 0x7F, 0x1B, 0x81, 0xB8, 0x00, 0x00, 0xE0, 0x0E, 0x01,
+ 0xF0, 0x1B, 0x01, 0xB8, 0x3B, 0x83, 0x1C, 0x71, 0xC7, 0xFC, 0x60, 0xEE, 0x0E, 0xE0, 0x60, 0x36, 0x36, 0x00, 0x7E,
+ 0x2F, 0x03, 0x7F, 0x77, 0xE3, 0xE3, 0x73, 0x7F, 0x03, 0xFE, 0x03, 0xC0, 0x07, 0xC0, 0x06, 0xC0, 0x0E, 0xC0, 0x1C,
+ 0xFE, 0x18, 0xC0, 0x38, 0xC0, 0x3F, 0xC0, 0x70, 0xC0, 0xE0, 0xC0, 0xE0, 0xFF, 0x7F, 0xF8, 0xBF, 0xF0, 0x79, 0xC0,
+ 0xC3, 0x7F, 0xFF, 0xDC, 0x0E, 0x38, 0x1D, 0xF6, 0x7F, 0xF8, 0x7E, 0x1E, 0x00, 0x1F, 0xEE, 0x07, 0x03, 0x81, 0xC0,
+ 0xFE, 0x70, 0x38, 0x1C, 0x0E, 0x07, 0x03, 0xFE, 0x3F, 0x0F, 0x00, 0x07, 0xE7, 0xF3, 0x1D, 0x8F, 0xFF, 0x60, 0x30,
+ 0x1E, 0xC7, 0xE0, 0x3F, 0x86, 0x78, 0x03, 0x80, 0x70, 0x06, 0x00, 0xDF, 0xFB, 0x03, 0x60, 0xEE, 0x1C, 0xEF, 0x0F,
+ 0xC0, 0x7E, 0x37, 0x80, 0xE0, 0x7F, 0xFB, 0x1D, 0x8E, 0xFE, 0x3E, 0x00, 0x1B, 0x83, 0x70, 0x00, 0x1F, 0xC3, 0x3C,
+ 0x01, 0xC0, 0x38, 0x03, 0x00, 0x6F, 0xFD, 0x81, 0xB0, 0x77, 0x0E, 0x77, 0x87, 0xE0, 0x37, 0x1B, 0x80, 0x0F, 0xC6,
+ 0xF0, 0x1C, 0x0F, 0xFF, 0x63, 0xB1, 0xDF, 0xC7, 0xC0, 0x06, 0xE0, 0x0D, 0xC0, 0x00, 0x07, 0x9C, 0x77, 0x39, 0xC7,
+ 0x77, 0x07, 0xFC, 0x07, 0xF8, 0x07, 0xE0, 0x1F, 0xE0, 0x6E, 0xE1, 0xDC, 0xC7, 0x39, 0xCE, 0x71, 0xF8, 0xE3, 0x80,
+ 0x0D, 0x80, 0x6C, 0x00, 0x00, 0xCE, 0x67, 0x77, 0x1F, 0xF0, 0x7F, 0x03, 0xF8, 0x1F, 0xC1, 0xBB, 0x19, 0xCD, 0xCE,
+ 0x70, 0x76, 0x3B, 0x00, 0x0F, 0xE7, 0x70, 0x1C, 0x0E, 0x1E, 0x3F, 0x07, 0xC0, 0xE0, 0x30, 0x3F, 0x3F, 0xFC, 0x7E,
+ 0x7E, 0x00, 0x7E, 0x6E, 0x07, 0x0E, 0x3E, 0x0F, 0x07, 0x6F, 0xFE, 0x7F, 0x83, 0x81, 0xC1, 0xC1, 0xC0, 0xF8, 0x3E,
+ 0x07, 0x01, 0x81, 0xF9, 0xFF, 0xE0, 0x7F, 0x03, 0x81, 0x81, 0xC0, 0xC0, 0xF0, 0x3C, 0x07, 0x03, 0x81, 0xDB, 0xCF,
+ 0xC0, 0x3F, 0x00, 0x00, 0x03, 0x83, 0xE1, 0xF8, 0xFE, 0x7F, 0x9B, 0xEE, 0xFB, 0x3F, 0x8F, 0xE3, 0xF0, 0xF8, 0x3E,
+ 0x0C, 0x7E, 0x00, 0x00, 0xC7, 0xC7, 0xCF, 0xCF, 0xDB, 0xF3, 0xF3, 0xE3, 0xE3, 0x3F, 0x0F, 0xC0, 0x03, 0x83, 0xE1,
+ 0xF8, 0xFE, 0x7F, 0x9B, 0xEE, 0xFB, 0x3F, 0x8F, 0xE3, 0xF0, 0xF8, 0x3E, 0x0C, 0x7E, 0x7E, 0x00, 0xC7, 0xC7, 0xCF,
+ 0xCF, 0xDB, 0xF3, 0xF3, 0xE3, 0xE3, 0x0D, 0xC0, 0x6E, 0x00, 0x00, 0x3F, 0x83, 0xDE, 0x38, 0x39, 0xC0, 0xCC, 0x07,
+ 0x60, 0x3B, 0x01, 0xD8, 0x0E, 0xE0, 0x67, 0x07, 0x1E, 0xF0, 0x7F, 0x00, 0x3F, 0x0F, 0xC0, 0x00, 0xFC, 0x7F, 0x98,
+ 0x66, 0x1B, 0x87, 0x61, 0x98, 0x67, 0xF8, 0xFC, 0x1F, 0xC1, 0xEF, 0x1C, 0x1C, 0xE0, 0x66, 0x03, 0xBF, 0xFD, 0x80,
+ 0xEC, 0x07, 0x70, 0x73, 0x83, 0x8F, 0x78, 0x3F, 0x80, 0x3F, 0x1F, 0xE6, 0x19, 0x86, 0xFF, 0xD8, 0x66, 0x19, 0xFE,
+ 0x3F, 0x00, 0x0D, 0xC0, 0x6E, 0x00, 0x00, 0x3F, 0x83, 0xDE, 0x38, 0x39, 0xC0, 0xCC, 0x07, 0x7F, 0xFB, 0x01, 0xD8,
+ 0x0E, 0xE0, 0xE7, 0x07, 0x1E, 0xF0, 0x7F, 0x00, 0x3F, 0x0F, 0xC0, 0x00, 0xFC, 0x7F, 0x98, 0x66, 0x1B, 0xFF, 0x61,
+ 0x98, 0x67, 0xF8, 0xFC, 0x76, 0x1D, 0x80, 0x03, 0xF8, 0xEF, 0x80, 0xE0, 0x1C, 0x07, 0x3F, 0xC0, 0x70, 0x1C, 0x07,
+ 0x03, 0xB1, 0xEF, 0xE0, 0x6C, 0x6C, 0x00, 0x7C, 0x6E, 0x07, 0x07, 0x3F, 0x07, 0x07, 0xDE, 0xFC, 0x3F, 0x00, 0x00,
+ 0x03, 0x87, 0xE1, 0xDC, 0x77, 0x18, 0xEE, 0x3B, 0x07, 0xC1, 0xF0, 0x38, 0x0E, 0x17, 0x0F, 0x80, 0x3E, 0x00, 0x00,
+ 0x1C, 0x6E, 0x33, 0x39, 0x9C, 0xEC, 0x36, 0x1F, 0x07, 0x03, 0x81, 0xC5, 0xC3, 0xC0, 0x3B, 0x0E, 0xC0, 0x03, 0x87,
+ 0xE1, 0xDC, 0x77, 0x18, 0xEE, 0x3B, 0x07, 0xC1, 0xF0, 0x38, 0x0E, 0x17, 0x0F, 0x80, 0x36, 0x1B, 0x00, 0x1C, 0x6E,
+ 0x33, 0x39, 0x9C, 0xEC, 0x36, 0x1F, 0x07, 0x03, 0x81, 0xC5, 0xC3, 0xC0, 0x0D, 0x87, 0xE1, 0xB0, 0x00, 0xE1, 0xF8,
+ 0x77, 0x1D, 0xC6, 0x3B, 0x8E, 0xC1, 0xF0, 0x7C, 0x0E, 0x03, 0x85, 0xC3, 0xE0, 0x1B, 0x1F, 0x8D, 0x80, 0x0E, 0x37,
+ 0x19, 0x9C, 0xCE, 0x76, 0x1B, 0x0F, 0x83, 0x81, 0xC0, 0xE2, 0xE1, 0xE0, 0x3B, 0x0E, 0xC0, 0x01, 0x87, 0x61, 0xD8,
+ 0x76, 0x1D, 0x87, 0x61, 0xDE, 0xF3, 0xFC, 0x07, 0x01, 0xC0, 0x70, 0x1C, 0x76, 0x76, 0x00, 0xE3, 0xE3, 0xE3, 0xE3,
+ 0x7F, 0x3F, 0x03, 0x03, 0x03, 0xFF, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xF0, 0x30, 0x30,
+ 0x30, 0xFF, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xF1, 0xC7, 0x00, 0x1D, 0x80, 0xEC, 0x00, 0x01, 0xC0, 0x7E, 0x03, 0xF0,
+ 0x1F, 0x80, 0xFC, 0x07, 0xFF, 0x3F, 0x7D, 0xF8, 0xEF, 0xC7, 0x7E, 0x3B, 0xF7, 0xDF, 0xFC, 0xE0, 0x1B, 0x03, 0x60,
+ 0x00, 0x60, 0x7C, 0x0F, 0x81, 0xFF, 0xBF, 0x77, 0xC7, 0xF8, 0xFF, 0x3B, 0xFF, 0x70, 0x7F, 0xC0, 0x7F, 0xFF, 0xC0,
+ 0x7F, 0xFF, 0xC0, 0xDB, 0x66, 0xC0, 0x6D, 0xBD, 0x80, 0x6F, 0xF0, 0x7F, 0xEF, 0x3C, 0xF3, 0xC0, 0x7D, 0xF7, 0xDF,
+ 0xF3, 0xC0, 0x6C, 0xDB, 0xB7, 0xE0, 0x18, 0x18, 0x18, 0x18, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+ 0x18, 0x18, 0x18, 0x18, 0x18, 0xFF, 0x18, 0x18, 0x18, 0x18, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x79, 0xF7, 0xDE, 0xE3,
+ 0x8F, 0xC7, 0x1C, 0x7C, 0x70, 0x03, 0x67, 0x00, 0x3B, 0xB8, 0x01, 0xDF, 0x80, 0x06, 0xD8, 0x00, 0x3F, 0xC0, 0x00,
+ 0x0D, 0xE7, 0xC0, 0xFF, 0xFE, 0x06, 0xCF, 0x30, 0x76, 0x79, 0x83, 0x3F, 0xFC, 0x38, 0xF3, 0xE0, 0x3B, 0x9D, 0xC7,
+ 0x38, 0xE0, 0xE3, 0x1C, 0x77, 0x33, 0x80, 0x03, 0x83, 0x81, 0x81, 0xC0, 0xC0, 0xE0, 0x60, 0x70, 0x30, 0x38, 0x38,
+ 0x18, 0x00, 0x0F, 0x87, 0xE3, 0x80, 0xE0, 0xFF, 0x0C, 0x03, 0x03, 0xFC, 0x38, 0x0E, 0x01, 0xDC, 0x3E, 0xFF, 0x86,
+ 0x03, 0x01, 0xE1, 0xF3, 0xE1, 0xBC, 0x3E, 0x7C, 0x36, 0x03, 0x01, 0x80, 0x08, 0x1F, 0x89, 0xC0, 0x70, 0x3F, 0xFC,
+ 0x38, 0x78, 0xFF, 0xB0, 0x18, 0x0E, 0x33, 0xF8, 0x60, 0xFF, 0x8F, 0x03, 0x9F, 0xE0, 0xE1, 0xE3, 0xE1, 0xC0, 0x70,
+ 0x1C, 0x07, 0x03, 0x80, 0x7E, 0x1B, 0xC0, 0x38, 0x06, 0x01, 0x8F, 0xE7, 0xB9, 0x86, 0xE1, 0xB8, 0x6E, 0x39, 0xDC,
+ 0x3E, 0x00, 0x0E, 0x01, 0xC0, 0x7C, 0x0D, 0x83, 0xB8, 0x63, 0x0C, 0x73, 0x8E, 0x60, 0xDC, 0x1F, 0x83, 0xFF, 0xF0,
+ 0xFF, 0xF8, 0x7E, 0x1F, 0x87, 0xE1, 0xF8, 0x7E, 0x1F, 0x87, 0xE1, 0xF8, 0x7E, 0x1F, 0x87, 0xE1, 0xC0, 0xFF, 0xF0,
+ 0x1C, 0x07, 0x03, 0x80, 0xE0, 0x30, 0x38, 0x38, 0x1C, 0x1C, 0x0E, 0x0E, 0x07, 0xFC, 0xFF, 0x80, 0x03, 0x83, 0x81,
+ 0x81, 0xC0, 0xC0, 0xE0, 0x60, 0x70, 0x30, 0x38, 0x38, 0x18, 0x00, 0x77, 0x00, 0xC0, 0x70, 0x18, 0x06, 0x01, 0x80,
+ 0xEF, 0x33, 0xCC, 0x3F, 0x07, 0x81, 0xE0, 0x78, 0x0C, 0x00, 0x7F, 0xBF, 0xFC, 0xCF, 0xFF, 0x7F, 0x80, 0x1E, 0x70,
+ 0xC1, 0x83, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC1, 0x83, 0x06, 0x1C, 0x70, 0x7D, 0xFF, 0xC0, 0x00, 0x07, 0xDF, 0xFC,
+ 0x03, 0x03, 0xBF, 0xE1, 0x81, 0xC7, 0xFC, 0xC0, 0x60, 0x03, 0x87, 0x9F, 0x0E, 0x03, 0xC0, 0x78, 0x0E, 0x00, 0xFF,
+ 0x80, 0x70, 0x3E, 0x03, 0xC0, 0x70, 0xF3, 0xE1, 0xC0, 0x00, 0xFF, 0x80,
};
static const EpdGlyph pixelarial14Glyphs[] = {
+ {0, 0, 0, 0, 0, 0, 0}, //
+ {0, 0, 0, 0, 0, 0, 0}, //
+ {0, 0, 4, 0, 0, 0, 0}, //
+ {0, 0, 4, 0, 0, 0, 0}, //
+ {0, 0, 0, 0, 0, 0, 0}, //
{0, 0, 4, 0, 0, 0, 0}, //
{2, 13, 3, 0, 13, 4, 0}, // !
{4, 6, 5, 0, 13, 3, 4}, // "
@@ -167,18 +548,494 @@ static const EpdGlyph pixelarial14Glyphs[] = {
{2, 13, 3, 0, 13, 4, 1110}, // |
{4, 17, 5, 0, 13, 9, 1114}, // }
{11, 4, 12, 0, 9, 6, 1123}, // ~
- {3, 6, 4, 0, 13, 3, 1129}, // ‘
- {3, 6, 4, 0, 13, 3, 1132}, // ’
- {6, 6, 6, 0, 13, 5, 1135}, // “
- {6, 6, 6, 0, 13, 5, 1140}, // ”
+ {0, 0, 4, 0, 0, 0, 1129}, //
+ {3, 12, 5, 1, 9, 5, 1129}, // ¡
+ {8, 13, 10, 1, 11, 13, 1134}, // ¢
+ {8, 12, 10, 1, 12, 12, 1147}, // £
+ {9, 9, 10, 0, 11, 11, 1159}, // ¤
+ {10, 12, 10, 0, 12, 15, 1170}, // ¥
+ {3, 16, 5, 1, 13, 6, 1185}, // ¦
+ {8, 14, 8, 0, 13, 14, 1191}, // §
+ {6, 2, 6, 0, 12, 2, 1205}, // ¨
+ {12, 12, 14, 1, 12, 18, 1207}, // ©
+ {6, 6, 7, 0, 12, 5, 1225}, // ª
+ {8, 7, 8, 0, 8, 7, 1230}, // «
+ {9, 5, 10, 0, 6, 6, 1237}, // ¬
+ {5, 1, 5, 0, 5, 1, 1243}, //
+ {12, 12, 14, 1, 12, 18, 1244}, // ®
+ {6, 1, 6, 0, 12, 1, 1262}, // ¯
+ {6, 5, 6, 0, 13, 4, 1263}, // °
+ {9, 10, 10, 0, 10, 12, 1267}, // ±
+ {6, 7, 6, 0, 12, 6, 1279}, // ²
+ {6, 7, 6, 0, 12, 6, 1285}, // ³
+ {4, 3, 6, 2, 13, 2, 1291}, // ´
+ {8, 12, 10, 1, 9, 12, 1293}, // µ
+ {10, 15, 11, 0, 12, 19, 1305}, // ¶
+ {4, 2, 4, 0, 6, 1, 1324}, // ·
+ {4, 3, 6, 1, 0, 2, 1325}, // ¸
+ {5, 7, 6, 0, 12, 5, 1327}, // ¹
+ {7, 6, 8, 0, 12, 6, 1332}, // º
+ {8, 7, 8, 0, 8, 7, 1338}, // »
+ {15, 12, 15, 0, 12, 23, 1345}, // ¼
+ {15, 12, 15, 0, 12, 23, 1368}, // ½
+ {15, 12, 15, 0, 12, 23, 1391}, // ¾
+ {7, 12, 7, 0, 9, 11, 1414}, // ¿
+ {12, 16, 11, 0, 16, 24, 1425}, // À
+ {12, 16, 11, 0, 16, 24, 1449}, // Á
+ {12, 16, 11, 0, 16, 24, 1473}, // Â
+ {12, 16, 11, 0, 16, 24, 1497}, // Ã
+ {12, 15, 11, 0, 15, 23, 1521}, // Ä
+ {12, 15, 11, 0, 15, 23, 1544}, // Å
+ {16, 12, 16, 0, 12, 24, 1567}, // Æ
+ {11, 16, 11, 0, 13, 22, 1591}, // Ç
+ {9, 16, 10, 1, 16, 18, 1613}, // È
+ {9, 16, 10, 1, 16, 18, 1631}, // É
+ {9, 16, 10, 1, 16, 18, 1649}, // Ê
+ {9, 15, 10, 1, 15, 17, 1667}, // Ë
+ {4, 16, 5, 0, 16, 8, 1684}, // Ì
+ {4, 16, 5, 1, 16, 8, 1692}, // Í
+ {6, 16, 5, -1, 16, 12, 1700}, // Î
+ {6, 15, 5, -1, 15, 12, 1712}, // Ï
+ {12, 12, 12, 0, 12, 18, 1724}, // Ð
+ {10, 16, 12, 1, 16, 20, 1742}, // Ñ
+ {13, 16, 13, 0, 16, 26, 1762}, // Ò
+ {13, 16, 13, 0, 16, 26, 1788}, // Ó
+ {13, 16, 13, 0, 16, 26, 1814}, // Ô
+ {13, 16, 13, 0, 16, 26, 1840}, // Õ
+ {13, 15, 13, 0, 15, 25, 1866}, // Ö
+ {8, 7, 10, 1, 9, 7, 1891}, // ×
+ {13, 12, 13, 0, 12, 20, 1898}, // Ø
+ {10, 16, 12, 1, 16, 20, 1918}, // Ù
+ {10, 16, 12, 1, 16, 20, 1938}, // Ú
+ {10, 16, 12, 1, 16, 20, 1958}, // Û
+ {10, 15, 12, 1, 15, 19, 1978}, // Ü
+ {11, 16, 10, 0, 16, 22, 1997}, // Ý
+ {9, 12, 10, 1, 12, 14, 2019}, // Þ
+ {9, 13, 11, 1, 13, 15, 2033}, // ß
+ {8, 13, 9, 0, 13, 13, 2048}, // à
+ {8, 13, 9, 0, 13, 13, 2061}, // á
+ {8, 13, 9, 0, 13, 13, 2074}, // â
+ {8, 13, 9, 0, 13, 13, 2087}, // ã
+ {8, 12, 9, 0, 12, 12, 2100}, // ä
+ {8, 14, 9, 0, 14, 14, 2112}, // å
+ {14, 9, 15, 0, 9, 16, 2126}, // æ
+ {8, 12, 8, 0, 9, 12, 2142}, // ç
+ {9, 13, 10, 0, 13, 15, 2154}, // è
+ {9, 13, 10, 0, 13, 15, 2169}, // é
+ {9, 13, 10, 0, 13, 15, 2184}, // ê
+ {9, 12, 10, 0, 12, 14, 2199}, // ë
+ {4, 13, 4, 0, 13, 7, 2213}, // ì
+ {4, 13, 4, 1, 13, 7, 2220}, // í
+ {6, 13, 4, -1, 13, 10, 2227}, // î
+ {6, 12, 4, -1, 12, 9, 2237}, // ï
+ {10, 13, 10, 0, 13, 17, 2246}, // ð
+ {8, 13, 10, 1, 13, 13, 2263}, // ñ
+ {10, 13, 10, 0, 13, 17, 2276}, // ò
+ {10, 13, 10, 0, 13, 17, 2293}, // ó
+ {10, 13, 10, 0, 13, 17, 2310}, // ô
+ {10, 13, 10, 0, 13, 17, 2327}, // õ
+ {10, 12, 10, 0, 12, 15, 2344}, // ö
+ {9, 9, 10, 0, 9, 11, 2359}, // ÷
+ {10, 9, 10, 0, 9, 12, 2370}, // ø
+ {8, 13, 10, 1, 13, 13, 2382}, // ù
+ {8, 13, 10, 1, 13, 13, 2395}, // ú
+ {8, 13, 10, 1, 13, 13, 2408}, // û
+ {8, 12, 10, 1, 12, 12, 2421}, // ü
+ {9, 16, 8, 0, 13, 18, 2433}, // ý
+ {9, 16, 10, 1, 13, 18, 2451}, // þ
+ {9, 15, 8, 0, 12, 17, 2469}, // ÿ
+ {12, 15, 11, 0, 15, 23, 2486}, // Ā
+ {8, 12, 9, 0, 12, 12, 2509}, // ā
+ {12, 15, 11, 0, 15, 23, 2521}, // Ă
+ {8, 12, 9, 0, 12, 12, 2544}, // ă
+ {12, 15, 11, 0, 12, 23, 2556}, // Ą
+ {8, 12, 9, 0, 9, 12, 2579}, // ą
+ {11, 16, 11, 0, 16, 22, 2591}, // Ć
+ {8, 13, 8, 0, 13, 13, 2613}, // ć
+ {11, 16, 11, 0, 16, 22, 2626}, // Ĉ
+ {8, 13, 8, 0, 13, 13, 2648}, // ĉ
+ {11, 15, 11, 0, 15, 21, 2661}, // Ċ
+ {8, 12, 8, 0, 12, 12, 2682}, // ċ
+ {11, 16, 11, 0, 16, 22, 2694}, // Č
+ {8, 13, 8, 0, 13, 13, 2716}, // č
+ {11, 16, 12, 1, 16, 22, 2729}, // Ď
+ {12, 13, 11, 0, 13, 20, 2751}, // ď
+ {12, 12, 12, 0, 12, 18, 2771}, // Đ
+ {10, 13, 10, 0, 13, 17, 2789}, // đ
+ {9, 15, 10, 1, 15, 17, 2806}, // Ē
+ {9, 12, 10, 0, 12, 14, 2823}, // ē
+ {9, 15, 10, 1, 15, 17, 2837}, // Ĕ
+ {9, 12, 10, 0, 12, 14, 2854}, // ĕ
+ {9, 15, 10, 1, 15, 17, 2868}, // Ė
+ {9, 12, 10, 0, 12, 14, 2885}, // ė
+ {9, 15, 10, 1, 12, 17, 2899}, // Ę
+ {9, 12, 10, 0, 9, 14, 2916}, // ę
+ {9, 16, 10, 1, 16, 18, 2930}, // Ě
+ {9, 14, 10, 0, 14, 16, 2948}, // ě
+ {11, 16, 11, 0, 16, 22, 2964}, // Ĝ
+ {9, 16, 10, 0, 13, 18, 2986}, // ĝ
+ {11, 15, 11, 0, 15, 21, 3004}, // Ğ
+ {9, 15, 10, 0, 12, 17, 3025}, // ğ
+ {11, 15, 11, 0, 15, 21, 3042}, // Ġ
+ {9, 15, 10, 0, 12, 17, 3063}, // ġ
+ {11, 15, 11, 0, 12, 21, 3080}, // Ģ
+ {9, 16, 10, 0, 13, 18, 3101}, // ģ
+ {10, 16, 12, 1, 16, 20, 3119}, // Ĥ
+ {9, 16, 10, 0, 16, 18, 3139}, // ĥ
+ {13, 12, 12, 0, 12, 20, 3157}, // Ħ
+ {9, 13, 10, 0, 13, 15, 3177}, // ħ
+ {7, 16, 5, -1, 16, 14, 3192}, // Ĩ
+ {6, 13, 4, -1, 13, 10, 3206}, // ĩ
+ {6, 15, 5, -1, 15, 12, 3216}, // Ī
+ {6, 12, 4, -1, 12, 9, 3228}, // ī
+ {5, 15, 5, 0, 15, 10, 3237}, // Ĭ
+ {6, 12, 4, -1, 12, 9, 3247}, // ĭ
+ {4, 15, 5, 0, 12, 8, 3256}, // Į
+ {4, 16, 4, 0, 13, 8, 3264}, // į
+ {3, 15, 5, 1, 15, 6, 3272}, // İ
+ {2, 9, 4, 1, 9, 3, 3278}, // ı
+ {11, 12, 13, 1, 12, 17, 3281}, // IJ
+ {7, 16, 9, 1, 13, 14, 3298}, // ij
+ {9, 16, 9, 0, 16, 18, 3312}, // Ĵ
+ {7, 16, 4, -2, 13, 14, 3330}, // ĵ
+ {10, 15, 11, 1, 12, 19, 3344}, // Ķ
+ {8, 16, 9, 1, 13, 16, 3363}, // ķ
+ {8, 9, 9, 1, 9, 9, 3379}, // ĸ
+ {8, 16, 9, 1, 16, 16, 3388}, // Ĺ
+ {4, 16, 5, 1, 16, 8, 3404}, // ĺ
+ {8, 15, 9, 1, 12, 15, 3412}, // Ļ
+ {5, 16, 5, 0, 13, 10, 3427}, // ļ
+ {8, 12, 9, 1, 12, 12, 3437}, // Ľ
+ {5, 13, 5, 1, 13, 9, 3449}, // ľ
+ {8, 12, 9, 1, 12, 12, 3458}, // Ŀ
+ {6, 13, 6, 1, 13, 10, 3470}, // ŀ
+ {10, 12, 9, -1, 12, 15, 3480}, // Ł
+ {5, 13, 5, 0, 13, 9, 3495}, // ł
+ {10, 16, 12, 1, 16, 20, 3504}, // Ń
+ {8, 13, 10, 1, 13, 13, 3524}, // ń
+ {10, 15, 12, 1, 12, 19, 3537}, // Ņ
+ {8, 12, 10, 1, 9, 12, 3556}, // ņ
+ {10, 16, 12, 1, 16, 20, 3568}, // Ň
+ {8, 13, 10, 1, 13, 13, 3588}, // ň
+ {9, 14, 10, 0, 14, 16, 3601}, // ʼn
+ {10, 15, 12, 1, 12, 19, 3617}, // Ŋ
+ {8, 12, 10, 1, 9, 12, 3636}, // ŋ
+ {13, 15, 13, 0, 15, 25, 3648}, // Ō
+ {10, 12, 10, 0, 12, 15, 3673}, // ō
+ {13, 15, 13, 0, 15, 25, 3688}, // Ŏ
+ {10, 12, 10, 0, 12, 15, 3713}, // ŏ
+ {13, 16, 13, 0, 16, 26, 3728}, // Ő
+ {10, 13, 10, 0, 13, 17, 3754}, // ő
+ {17, 12, 17, 0, 12, 26, 3771}, // Œ
+ {16, 9, 16, 0, 9, 18, 3797}, // œ
+ {10, 16, 11, 1, 16, 20, 3815}, // Ŕ
+ {6, 13, 7, 1, 13, 10, 3835}, // ŕ
+ {10, 15, 11, 1, 12, 19, 3845}, // Ŗ
+ {7, 12, 7, 0, 9, 11, 3864}, // ŗ
+ {10, 16, 11, 1, 16, 20, 3875}, // Ř
+ {6, 13, 7, 1, 13, 10, 3895}, // ř
+ {9, 16, 9, 0, 16, 18, 3905}, // Ś
+ {7, 13, 8, 0, 13, 12, 3923}, // ś
+ {9, 16, 9, 0, 16, 18, 3935}, // Ŝ
+ {7, 13, 8, 0, 13, 12, 3953}, // ŝ
+ {9, 15, 9, 0, 12, 17, 3965}, // Ş
+ {7, 12, 8, 0, 9, 11, 3982}, // ş
+ {9, 16, 9, 0, 16, 18, 3993}, // Š
+ {7, 13, 8, 0, 13, 12, 4011}, // š
+ {10, 15, 10, 0, 12, 19, 4023}, // Ţ
+ {6, 15, 7, 1, 12, 12, 4042}, // ţ
+ {10, 16, 10, 0, 16, 20, 4054}, // Ť
+ {6, 13, 7, 1, 13, 10, 4074}, // ť
+ {10, 12, 10, 0, 12, 15, 4084}, // Ŧ
+ {6, 12, 7, 1, 12, 9, 4099}, // ŧ
+ {10, 16, 12, 1, 16, 20, 4108}, // Ũ
+ {8, 13, 10, 1, 13, 13, 4128}, // ũ
+ {10, 15, 12, 1, 15, 19, 4141}, // Ū
+ {8, 12, 10, 1, 12, 12, 4160}, // ū
+ {10, 15, 12, 1, 15, 19, 4172}, // Ŭ
+ {8, 12, 10, 1, 12, 12, 4191}, // ŭ
+ {10, 17, 12, 1, 17, 22, 4203}, // Ů
+ {8, 14, 10, 1, 14, 14, 4225}, // ů
+ {10, 16, 12, 1, 16, 20, 4239}, // Ű
+ {8, 13, 10, 1, 13, 13, 4259}, // ű
+ {10, 15, 12, 1, 12, 19, 4272}, // Ų
+ {8, 12, 10, 1, 9, 12, 4291}, // ų
+ {16, 16, 16, 0, 16, 32, 4303}, // Ŵ
+ {13, 13, 13, 0, 13, 22, 4335}, // ŵ
+ {11, 16, 10, 0, 16, 22, 4357}, // Ŷ
+ {9, 16, 8, 0, 13, 18, 4379}, // ŷ
+ {11, 15, 10, 0, 15, 21, 4397}, // Ÿ
+ {10, 16, 10, 0, 16, 20, 4418}, // Ź
+ {8, 13, 8, 0, 13, 13, 4438}, // ź
+ {10, 15, 10, 0, 15, 19, 4451}, // Ż
+ {8, 12, 8, 0, 12, 12, 4470}, // ż
+ {10, 16, 10, 0, 16, 20, 4482}, // Ž
+ {8, 13, 8, 0, 13, 13, 4502}, // ž
+ {6, 14, 5, 1, 14, 11, 4515}, // ſ
+ {6, 2, 6, 0, 12, 2, 4526}, // ̑
+ {9, 16, 10, 1, 16, 18, 4528}, // Ѐ
+ {9, 15, 10, 1, 15, 17, 4546}, // Ё
+ {13, 12, 13, 0, 12, 20, 4563}, // Ђ
+ {8, 16, 9, 1, 16, 16, 4583}, // Ѓ
+ {11, 12, 11, 0, 12, 17, 4599}, // Є
+ {9, 12, 9, 0, 12, 14, 4616}, // Ѕ
+ {3, 12, 5, 1, 12, 5, 4630}, // І
+ {6, 15, 5, -1, 15, 12, 4635}, // Ї
+ {8, 12, 9, 0, 12, 12, 4647}, // Ј
+ {18, 13, 18, 0, 12, 30, 4659}, // Љ
+ {16, 12, 17, 1, 12, 24, 4689}, // Њ
+ {12, 12, 13, 0, 12, 18, 4713}, // Ћ
+ {10, 16, 11, 1, 16, 20, 4731}, // Ќ
+ {10, 16, 12, 1, 16, 20, 4751}, // Ѝ
+ {10, 15, 10, 0, 15, 19, 4771}, // Ў
+ {10, 15, 12, 1, 12, 19, 4790}, // Џ
+ {12, 12, 11, 0, 12, 18, 4809}, // А
+ {9, 12, 11, 1, 12, 14, 4827}, // Б
+ {10, 12, 11, 1, 12, 15, 4841}, // В
+ {8, 12, 9, 1, 12, 12, 4856}, // Г
+ {12, 15, 12, 0, 12, 23, 4868}, // Д
+ {9, 12, 10, 1, 12, 14, 4891}, // Е
+ {15, 12, 15, 0, 12, 23, 4905}, // Ж
+ {9, 12, 10, 0, 12, 14, 4928}, // З
+ {10, 12, 12, 1, 12, 15, 4942}, // И
+ {10, 15, 12, 1, 15, 19, 4957}, // Й
+ {10, 12, 11, 1, 12, 15, 4976}, // К
+ {11, 12, 12, 0, 12, 17, 4991}, // Л
+ {13, 12, 15, 1, 12, 20, 5008}, // М
+ {10, 12, 12, 1, 12, 15, 5028}, // Н
+ {13, 12, 13, 0, 12, 20, 5043}, // О
+ {10, 12, 12, 1, 12, 15, 5063}, // П
+ {9, 12, 10, 1, 12, 14, 5078}, // Р
+ {11, 13, 11, 0, 13, 18, 5092}, // С
+ {10, 12, 10, 0, 12, 15, 5110}, // Т
+ {10, 12, 10, 0, 12, 15, 5125}, // У
+ {14, 13, 15, 0, 13, 23, 5140}, // Ф
+ {11, 12, 11, 0, 12, 17, 5163}, // Х
+ {11, 15, 12, 1, 12, 21, 5180}, // Ц
+ {10, 12, 11, 0, 12, 15, 5201}, // Ч
+ {15, 12, 17, 1, 12, 23, 5216}, // Ш
+ {16, 15, 17, 1, 12, 30, 5239}, // Щ
+ {12, 12, 12, 0, 12, 18, 5269}, // Ъ
+ {13, 12, 15, 1, 12, 20, 5287}, // Ы
+ {9, 12, 10, 1, 12, 14, 5307}, // Ь
+ {10, 12, 11, 0, 12, 15, 5321}, // Э
+ {16, 12, 18, 1, 12, 24, 5336}, // Ю
+ {10, 12, 11, 0, 12, 15, 5360}, // Я
+ {8, 9, 9, 0, 9, 9, 5375}, // а
+ {9, 13, 10, 1, 13, 15, 5384}, // б
+ {8, 9, 9, 1, 9, 9, 5399}, // в
+ {6, 9, 7, 1, 9, 7, 5408}, // г
+ {10, 11, 10, 0, 9, 14, 5415}, // д
+ {9, 9, 10, 0, 9, 11, 5429}, // е
+ {13, 9, 13, 0, 9, 15, 5440}, // ж
+ {8, 9, 8, 0, 9, 9, 5455}, // з
+ {8, 9, 10, 1, 9, 9, 5464}, // и
+ {8, 12, 10, 1, 12, 12, 5473}, // й
+ {8, 9, 9, 1, 9, 9, 5485}, // к
+ {9, 9, 10, 0, 9, 11, 5494}, // л
+ {11, 9, 12, 1, 9, 13, 5505}, // м
+ {8, 9, 10, 1, 9, 9, 5518}, // н
+ {10, 9, 10, 0, 9, 12, 5527}, // о
+ {8, 9, 10, 1, 9, 9, 5539}, // п
+ {9, 12, 10, 1, 9, 14, 5548}, // р
+ {8, 9, 8, 0, 9, 9, 5562}, // с
+ {8, 9, 8, 0, 9, 9, 5571}, // т
+ {9, 12, 8, 0, 9, 14, 5580}, // у
+ {13, 16, 13, 0, 13, 26, 5594}, // ф
+ {9, 9, 9, 0, 9, 11, 5620}, // х
+ {9, 11, 10, 1, 9, 13, 5631}, // ц
+ {8, 9, 9, 0, 9, 9, 5644}, // ч
+ {12, 9, 14, 1, 9, 14, 5653}, // ш
+ {13, 11, 14, 1, 9, 18, 5667}, // щ
+ {10, 9, 10, 0, 9, 12, 5685}, // ъ
+ {11, 9, 13, 1, 9, 13, 5697}, // ы
+ {8, 9, 9, 1, 9, 9, 5710}, // ь
+ {8, 9, 8, 0, 9, 9, 5719}, // э
+ {12, 9, 14, 1, 9, 14, 5728}, // ю
+ {8, 9, 9, 0, 9, 9, 5742}, // я
+ {9, 13, 10, 0, 13, 15, 5751}, // ѐ
+ {9, 12, 10, 0, 12, 14, 5766}, // ё
+ {9, 16, 10, 0, 13, 18, 5780}, // ђ
+ {6, 13, 7, 1, 13, 10, 5798}, // ѓ
+ {8, 9, 8, 0, 9, 9, 5808}, // є
+ {7, 9, 8, 0, 9, 8, 5817}, // ѕ
+ {3, 13, 4, 1, 13, 5, 5825}, // і
+ {6, 12, 4, -1, 12, 9, 5830}, // ї
+ {6, 16, 4, -2, 13, 12, 5839}, // ј
+ {15, 9, 15, 0, 9, 17, 5851}, // љ
+ {13, 9, 14, 1, 9, 15, 5868}, // њ
+ {9, 13, 10, 0, 13, 15, 5883}, // ћ
+ {8, 13, 9, 1, 13, 13, 5898}, // ќ
+ {8, 13, 10, 1, 13, 13, 5911}, // ѝ
+ {9, 15, 8, 0, 12, 17, 5924}, // ў
+ {8, 11, 10, 1, 9, 11, 5941}, // џ
+ {12, 13, 12, 0, 13, 20, 5952}, // Ѣ
+ {10, 13, 10, 0, 13, 17, 5972}, // ѣ
+ {13, 12, 13, 0, 12, 20, 5989}, // Ѳ
+ {10, 9, 10, 0, 9, 12, 6009}, // ѳ
+ {14, 12, 13, 0, 12, 21, 6021}, // Ѵ
+ {10, 9, 10, 0, 9, 12, 6042}, // ѵ
+ {12, 18, 13, 1, 15, 27, 6054}, // Ҋ
+ {10, 15, 10, 1, 12, 19, 6081}, // ҋ
+ {11, 13, 11, 0, 13, 18, 6100}, // Ҍ
+ {9, 9, 9, 0, 9, 11, 6118}, // ҍ
+ {9, 12, 10, 1, 12, 14, 6129}, // Ҏ
+ {9, 12, 10, 1, 9, 14, 6143}, // ҏ
+ {8, 14, 9, 1, 14, 14, 6157}, // Ґ
+ {6, 11, 7, 1, 11, 9, 6171}, // ґ
+ {9, 12, 9, 0, 12, 14, 6180}, // Ғ
+ {7, 9, 7, 0, 9, 8, 6194}, // ғ
+ {9, 15, 11, 1, 12, 17, 6202}, // Ҕ
+ {8, 12, 9, 1, 9, 12, 6219}, // ҕ
+ {16, 15, 16, 0, 12, 30, 6231}, // Җ
+ {14, 11, 13, 0, 9, 20, 6261}, // җ
+ {9, 15, 10, 0, 12, 17, 6281}, // Ҙ
+ {8, 12, 8, 0, 9, 12, 6298}, // ҙ
+ {11, 15, 11, 1, 12, 21, 6310}, // Қ
+ {9, 12, 9, 1, 9, 14, 6331}, // қ
+ {11, 12, 12, 1, 12, 17, 6345}, // Ҝ
+ {10, 9, 11, 1, 9, 12, 6362}, // ҝ
+ {11, 12, 11, 0, 12, 17, 6374}, // Ҟ
+ {10, 9, 9, 0, 9, 12, 6391}, // ҟ
+ {13, 12, 13, 0, 12, 20, 6403}, // Ҡ
+ {11, 9, 10, 0, 9, 13, 6423}, // ҡ
+ {11, 15, 12, 1, 12, 21, 6436}, // Ң
+ {9, 11, 10, 1, 9, 13, 6457}, // ң
+ {13, 12, 14, 1, 12, 20, 6470}, // Ҥ
+ {10, 9, 11, 1, 9, 12, 6490}, // ҥ
+ {16, 15, 18, 1, 12, 30, 6502}, // Ҧ
+ {14, 12, 15, 1, 9, 21, 6532}, // ҧ
+ {13, 15, 13, 0, 12, 25, 6553}, // Ҩ
+ {10, 11, 10, 0, 9, 14, 6578}, // ҩ
+ {11, 16, 11, 0, 13, 22, 6592}, // Ҫ
+ {8, 12, 8, 0, 9, 12, 6614}, // ҫ
+ {10, 15, 10, 0, 12, 19, 6626}, // Ҭ
+ {8, 11, 8, 0, 9, 11, 6645}, // ҭ
+ {11, 12, 10, 0, 12, 17, 6656}, // Ү
+ {9, 12, 9, 0, 9, 14, 6673}, // ү
+ {11, 12, 10, 0, 12, 17, 6687}, // Ұ
+ {9, 12, 9, 0, 9, 14, 6704}, // ұ
+ {11, 15, 11, 0, 12, 21, 6718}, // Ҳ
+ {9, 11, 9, 0, 9, 13, 6739}, // ҳ
+ {15, 15, 14, 0, 12, 29, 6752}, // Ҵ
+ {11, 11, 11, 0, 9, 16, 6781}, // ҵ
+ {11, 16, 11, 0, 12, 22, 6797}, // Ҷ
+ {9, 11, 9, 0, 9, 13, 6819}, // ҷ
+ {10, 12, 11, 0, 12, 15, 6832}, // Ҹ
+ {8, 9, 9, 0, 9, 9, 6847}, // ҹ
+ {9, 12, 11, 1, 12, 14, 6856}, // Һ
+ {8, 13, 10, 1, 13, 13, 6870}, // һ
+ {14, 12, 14, 0, 12, 21, 6883}, // Ҽ
+ {11, 9, 11, 0, 9, 13, 6904}, // ҽ
+ {14, 15, 14, 0, 12, 27, 6917}, // Ҿ
+ {11, 11, 11, 0, 9, 16, 6944}, // ҿ
+ {3, 12, 5, 1, 12, 5, 6960}, // Ӏ
+ {15, 15, 15, 0, 15, 29, 6965}, // Ӂ
+ {13, 12, 13, 0, 12, 20, 6994}, // ӂ
+ {9, 15, 11, 1, 12, 17, 7014}, // Ӄ
+ {8, 12, 9, 1, 9, 12, 7031}, // ӄ
+ {13, 15, 12, 0, 12, 25, 7043}, // Ӆ
+ {11, 12, 10, 0, 9, 17, 7068}, // ӆ
+ {10, 15, 12, 1, 12, 19, 7085}, // Ӈ
+ {8, 12, 10, 1, 9, 12, 7104}, // ӈ
+ {12, 15, 12, 1, 12, 23, 7116}, // Ӊ
+ {10, 12, 10, 1, 9, 15, 7139}, // ӊ
+ {9, 15, 11, 1, 12, 17, 7154}, // Ӌ
+ {8, 11, 9, 0, 9, 11, 7171}, // ӌ
+ {15, 15, 15, 1, 12, 29, 7182}, // Ӎ
+ {12, 12, 13, 1, 9, 18, 7211}, // ӎ
+ {3, 12, 5, 1, 12, 5, 7229}, // ӏ
+ {12, 15, 11, 0, 15, 23, 7234}, // Ӑ
+ {8, 12, 9, 0, 12, 12, 7257}, // ӑ
+ {12, 15, 11, 0, 15, 23, 7269}, // Ӓ
+ {8, 12, 9, 0, 12, 12, 7292}, // ӓ
+ {16, 12, 16, 0, 12, 24, 7304}, // Ӕ
+ {14, 9, 15, 0, 9, 16, 7328}, // ӕ
+ {9, 15, 10, 1, 15, 17, 7344}, // Ӗ
+ {9, 12, 10, 0, 12, 14, 7361}, // ӗ
+ {11, 12, 12, 0, 12, 17, 7375}, // Ә
+ {9, 9, 10, 0, 9, 11, 7392}, // ә
+ {11, 15, 12, 0, 15, 21, 7403}, // Ӛ
+ {9, 12, 10, 0, 12, 14, 7424}, // ӛ
+ {15, 15, 15, 0, 15, 29, 7438}, // Ӝ
+ {13, 12, 13, 0, 12, 20, 7467}, // ӝ
+ {9, 15, 10, 0, 15, 17, 7487}, // Ӟ
+ {8, 12, 8, 0, 12, 12, 7504}, // ӟ
+ {9, 12, 10, 0, 12, 14, 7516}, // Ӡ
+ {9, 12, 8, -1, 9, 14, 7530}, // ӡ
+ {10, 15, 12, 1, 15, 19, 7544}, // Ӣ
+ {8, 12, 10, 1, 12, 12, 7563}, // ӣ
+ {10, 15, 12, 1, 15, 19, 7575}, // Ӥ
+ {8, 12, 10, 1, 12, 12, 7594}, // ӥ
+ {13, 15, 13, 0, 15, 25, 7606}, // Ӧ
+ {10, 12, 10, 0, 12, 15, 7631}, // ӧ
+ {13, 12, 13, 0, 12, 20, 7646}, // Ө
+ {10, 9, 10, 0, 9, 12, 7666}, // ө
+ {13, 15, 13, 0, 15, 25, 7678}, // Ӫ
+ {10, 12, 10, 0, 12, 15, 7703}, // ӫ
+ {10, 15, 11, 0, 15, 19, 7718}, // Ӭ
+ {8, 12, 8, 0, 12, 12, 7737}, // ӭ
+ {10, 15, 10, 0, 15, 19, 7749}, // Ӯ
+ {9, 15, 8, 0, 12, 17, 7768}, // ӯ
+ {10, 15, 10, 0, 15, 19, 7785}, // Ӱ
+ {9, 15, 8, 0, 12, 17, 7804}, // ӱ
+ {10, 16, 10, 0, 16, 20, 7821}, // Ӳ
+ {9, 16, 8, 0, 13, 18, 7841}, // ӳ
+ {10, 15, 11, 0, 15, 19, 7859}, // Ӵ
+ {8, 12, 9, 0, 12, 12, 7878}, // ӵ
+ {8, 15, 9, 1, 12, 15, 7890}, // Ӷ
+ {6, 11, 7, 1, 9, 9, 7905}, // ӷ
+ {13, 15, 15, 1, 15, 25, 7914}, // Ӹ
+ {11, 12, 13, 1, 12, 17, 7939}, // ӹ
+ {10, 1, 8, -1, 5, 2, 7956}, // –
+ {18, 1, 17, -1, 5, 3, 7958}, // —
+ {18, 1, 17, -1, 5, 3, 7961}, // ―
+ {3, 6, 4, 0, 13, 3, 7964}, // ‘
+ {3, 6, 4, 0, 13, 3, 7967}, // ’
+ {3, 4, 4, 0, 2, 2, 7970}, // ‚
+ {6, 6, 6, 0, 13, 5, 7972}, // “
+ {6, 6, 6, 0, 13, 5, 7977}, // ”
+ {7, 4, 7, 0, 2, 4, 7982}, // „
+ {8, 14, 8, 0, 12, 14, 7986}, // †
+ {8, 14, 8, 0, 12, 14, 8000}, // ‡
+ {6, 4, 6, 0, 8, 3, 8014}, // •
+ {15, 2, 17, 1, 2, 4, 8017}, // …
+ {21, 12, 21, 0, 12, 32, 8021}, // ‰
+ {5, 7, 5, 0, 8, 5, 8053}, // ‹
+ {5, 7, 5, 0, 8, 5, 8058}, // ›
+ {9, 12, 3, -3, 12, 14, 8063}, // ⁄
+ {10, 12, 10, 0, 12, 15, 8077}, // €
+ {9, 12, 10, 0, 12, 14, 8092}, // ₮
+ {9, 14, 10, 0, 13, 16, 8106}, // ₴
+ {9, 12, 10, 1, 12, 14, 8122}, // ₹
+ {10, 13, 10, 0, 13, 17, 8136}, // ∂
+ {11, 12, 11, 0, 12, 17, 8153}, // ∆
+ {10, 13, 12, 1, 11, 17, 8170}, // ∏
+ {9, 14, 9, 0, 12, 16, 8187}, // ∑
+ {9, 1, 10, 0, 5, 2, 8203}, // −
+ {9, 12, 3, -3, 12, 14, 8205}, // ∕
+ {4, 2, 4, 0, 6, 1, 8219}, // ∙
+ {10, 13, 10, 0, 13, 17, 8220}, // √
+ {10, 5, 10, 0, 7, 7, 8237}, // ∞
+ {7, 16, 6, 0, 13, 14, 8244}, // ∫
+ {9, 6, 10, 0, 8, 7, 8258}, // ≈
+ {9, 8, 10, 0, 9, 9, 8265}, // ≠
+ {9, 9, 10, 0, 9, 11, 8274}, // ≤
+ {9, 9, 10, 0, 9, 11, 8285}, // ≥
};
static const EpdUnicodeInterval pixelarial14Intervals[] = {
- {0x20, 0x7E, 0x0},
- {0x2018, 0x2019, 0x5F},
- {0x201C, 0x201D, 0x61},
+ {0x0, 0x0, 0x0}, {0x8, 0x9, 0x1}, {0xD, 0xD, 0x3}, {0x1D, 0x1D, 0x4},
+ {0x20, 0x7E, 0x5}, {0xA0, 0xFF, 0x64}, {0x100, 0x17F, 0xC4}, {0x311, 0x311, 0x144},
+ {0x400, 0x45F, 0x145}, {0x462, 0x463, 0x1A5}, {0x472, 0x475, 0x1A7}, {0x48A, 0x4F9, 0x1AB},
+ {0x2013, 0x2015, 0x21B}, {0x2018, 0x201A, 0x21E}, {0x201C, 0x201E, 0x221}, {0x2020, 0x2022, 0x224},
+ {0x2026, 0x2026, 0x227}, {0x2030, 0x2030, 0x228}, {0x2039, 0x203A, 0x229}, {0x2044, 0x2044, 0x22B},
+ {0x20AC, 0x20AC, 0x22C}, {0x20AE, 0x20AE, 0x22D}, {0x20B4, 0x20B4, 0x22E}, {0x20B9, 0x20B9, 0x22F},
+ {0x2202, 0x2202, 0x230}, {0x2206, 0x2206, 0x231}, {0x220F, 0x220F, 0x232}, {0x2211, 0x2212, 0x233},
+ {0x2215, 0x2215, 0x235}, {0x2219, 0x221A, 0x236}, {0x221E, 0x221E, 0x238}, {0x222B, 0x222B, 0x239},
+ {0x2248, 0x2248, 0x23A}, {0x2260, 0x2260, 0x23B}, {0x2264, 0x2265, 0x23C},
};
static const EpdFontData pixelarial14 = {
- pixelarial14Bitmaps, pixelarial14Glyphs, pixelarial14Intervals, 3, 17, 13, -4, false,
-};
+ pixelarial14Bitmaps, pixelarial14Glyphs, pixelarial14Intervals, 35, 17, 13, -4, false,
+};
\ No newline at end of file
diff --git a/src/config.h b/src/config.h
index a378446..58a7305 100644
--- a/src/config.h
+++ b/src/config.h
@@ -26,4 +26,4 @@
* "./lib/EpdFont/builtinFonts/pixelarial14.h",
* ].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)'
*/
-#define SMALL_FONT_ID (-139796914)
+#define SMALL_FONT_ID 1482513144
From fcfa10bb1f41bf6efafc8e85c0de457ba4b8f80c Mon Sep 17 00:00:00 2001
From: Dave Allie
Date: Sun, 21 Dec 2025 19:02:21 +1100
Subject: [PATCH 17/33] Cut release 0.8.0
---
platformio.ini | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/platformio.ini b/platformio.ini
index 0f92380..a4bdcd1 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -1,5 +1,5 @@
[platformio]
-crosspoint_version = 0.7.0
+crosspoint_version = 0.8.0
default_envs = default
[base]
From 246afae6efef96287debf595505f57d67cb47d93 Mon Sep 17 00:00:00 2001
From: Dave Allie
Date: Sun, 21 Dec 2025 21:16:41 +1100
Subject: [PATCH 18/33] Start power off sequence as soon as hold duration for
the power button is reached (#93)
## Summary
* Swap from `wasReleased` to `isPressed` when checking power button
duration
* In practice it makes the power down experience feel a lot snappier
* Remove the unnecessary 1000ms delay when powering off
## Additional Context
* A little discussion in here:
https://github.com/daveallie/crosspoint-reader/discussions/53#discussioncomment-15309707
---
src/main.cpp | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/src/main.cpp b/src/main.cpp
index 5dfc25b..0d21781 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -119,14 +119,12 @@ void enterDeepSleep() {
exitActivity();
enterNewActivity(new SleepActivity(renderer, inputManager));
- Serial.printf("[%lu] [ ] Entering deep sleep.\n", millis());
- delay(1000); // Allow Serial buffer to empty and display to update
-
- // Enable Wakeup on LOW (button press)
- esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW);
-
einkDisplay.deepSleep();
+ Serial.printf("[%lu] [ ] Entering deep sleep.\n", millis());
+ esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW);
+ // Ensure that the power button has been released to avoid immediately turning back on if you're holding it
+ waitForPowerRelease();
// Enter Deep Sleep
esp_deep_sleep_start();
}
@@ -231,7 +229,7 @@ void loop() {
return;
}
- if (inputManager.wasReleased(InputManager::BTN_POWER) &&
+ if (inputManager.isPressed(InputManager::BTN_POWER) &&
inputManager.getHeldTime() > SETTINGS.getPowerButtonDuration()) {
enterDeepSleep();
// This should never be hit as `enterDeepSleep` calls esp_deep_sleep_start
From 77c655fcf5f3af9969f76718ed04f60dfd106474 Mon Sep 17 00:00:00 2001
From: Dave Allie
Date: Sun, 21 Dec 2025 21:17:00 +1100
Subject: [PATCH 19/33] Give activities names and log when entering and exiting
them (#92)
## Summary
* Give activities name and log when entering and exiting them
* Clearer logs when attempting to debug, knowing where users are coming
from/going to helps
---
src/activities/Activity.h | 11 ++-
src/activities/ActivityWithSubactivity.cpp | 5 +-
src/activities/ActivityWithSubactivity.h | 4 +-
src/activities/boot_sleep/BootActivity.cpp | 2 +
src/activities/boot_sleep/BootActivity.h | 2 +-
src/activities/boot_sleep/SleepActivity.cpp | 6 ++
src/activities/boot_sleep/SleepActivity.h | 3 +-
src/activities/home/HomeActivity.cpp | 4 +
src/activities/home/HomeActivity.h | 2 +-
.../network/CrossPointWebServerActivity.cpp | 91 ++++++++-----------
.../network/CrossPointWebServerActivity.h | 8 +-
.../network/WifiSelectionActivity.cpp | 6 +-
.../network/WifiSelectionActivity.h | 2 +-
src/activities/reader/EpubReaderActivity.cpp | 22 +++--
src/activities/reader/EpubReaderActivity.h | 7 +-
.../EpubReaderChapterSelectionActivity.cpp | 4 +
.../EpubReaderChapterSelectionActivity.h | 2 +-
.../reader/FileSelectionActivity.cpp | 4 +
src/activities/reader/FileSelectionActivity.h | 2 +-
src/activities/reader/ReaderActivity.cpp | 2 +
src/activities/reader/ReaderActivity.h | 2 +-
src/activities/settings/SettingsActivity.cpp | 6 +-
src/activities/settings/SettingsActivity.h | 4 +-
.../util/FullScreenMessageActivity.cpp | 2 +
.../util/FullScreenMessageActivity.h | 5 +-
src/activities/util/KeyboardEntryActivity.cpp | 11 +--
src/activities/util/KeyboardEntryActivity.h | 8 +-
27 files changed, 123 insertions(+), 104 deletions(-)
diff --git a/src/activities/Activity.h b/src/activities/Activity.h
index dfe6714..90a2eb1 100644
--- a/src/activities/Activity.h
+++ b/src/activities/Activity.h
@@ -1,19 +1,22 @@
#pragma once
#include
+#include
+
class GfxRenderer;
class Activity {
protected:
+ std::string name;
GfxRenderer& renderer;
InputManager& inputManager;
public:
- explicit Activity(GfxRenderer& renderer, InputManager& inputManager)
- : renderer(renderer), inputManager(inputManager) {}
+ explicit Activity(std::string name, GfxRenderer& renderer, InputManager& inputManager)
+ : name(std::move(name)), renderer(renderer), inputManager(inputManager) {}
virtual ~Activity() = default;
- virtual void onEnter() {}
- virtual void onExit() {}
+ virtual void onEnter() { Serial.printf("[%lu] [ACT] Entering activity: %s\n", millis(), name.c_str()); }
+ virtual void onExit() { Serial.printf("[%lu] [ACT] Exiting activity: %s\n", millis(), name.c_str()); }
virtual void loop() {}
virtual bool skipLoopDelay() { return false; }
};
diff --git a/src/activities/ActivityWithSubactivity.cpp b/src/activities/ActivityWithSubactivity.cpp
index 56dccd9..61b1fc1 100644
--- a/src/activities/ActivityWithSubactivity.cpp
+++ b/src/activities/ActivityWithSubactivity.cpp
@@ -18,4 +18,7 @@ void ActivityWithSubactivity::loop() {
}
}
-void ActivityWithSubactivity::onExit() { exitActivity(); }
+void ActivityWithSubactivity::onExit() {
+ Activity::onExit();
+ exitActivity();
+}
diff --git a/src/activities/ActivityWithSubactivity.h b/src/activities/ActivityWithSubactivity.h
index b3a6873..af55987 100644
--- a/src/activities/ActivityWithSubactivity.h
+++ b/src/activities/ActivityWithSubactivity.h
@@ -10,8 +10,8 @@ class ActivityWithSubactivity : public Activity {
void enterNewActivity(Activity* activity);
public:
- explicit ActivityWithSubactivity(GfxRenderer& renderer, InputManager& inputManager)
- : Activity(renderer, inputManager) {}
+ explicit ActivityWithSubactivity(std::string name, GfxRenderer& renderer, InputManager& inputManager)
+ : Activity(std::move(name), renderer, inputManager) {}
void loop() override;
void onExit() override;
};
diff --git a/src/activities/boot_sleep/BootActivity.cpp b/src/activities/boot_sleep/BootActivity.cpp
index a76cb7c..78a1248 100644
--- a/src/activities/boot_sleep/BootActivity.cpp
+++ b/src/activities/boot_sleep/BootActivity.cpp
@@ -6,6 +6,8 @@
#include "images/CrossLarge.h"
void BootActivity::onEnter() {
+ Activity::onEnter();
+
const auto pageWidth = GfxRenderer::getScreenWidth();
const auto pageHeight = GfxRenderer::getScreenHeight();
diff --git a/src/activities/boot_sleep/BootActivity.h b/src/activities/boot_sleep/BootActivity.h
index dd647ce..a14d0c7 100644
--- a/src/activities/boot_sleep/BootActivity.h
+++ b/src/activities/boot_sleep/BootActivity.h
@@ -3,6 +3,6 @@
class BootActivity final : public Activity {
public:
- explicit BootActivity(GfxRenderer& renderer, InputManager& inputManager) : Activity(renderer, inputManager) {}
+ explicit BootActivity(GfxRenderer& renderer, InputManager& inputManager) : Activity("Boot", renderer, inputManager) {}
void onEnter() override;
};
diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp
index 4200c4e..ca72aeb 100644
--- a/src/activities/boot_sleep/SleepActivity.cpp
+++ b/src/activities/boot_sleep/SleepActivity.cpp
@@ -12,6 +12,7 @@
#include "images/CrossLarge.h"
void SleepActivity::onEnter() {
+ Activity::onEnter();
renderPopup("Entering Sleep...");
if (SETTINGS.sleepScreen == CrossPointSettings::SLEEP_SCREEN_MODE::CUSTOM) {
@@ -170,11 +171,16 @@ void SleepActivity::renderBitmapSleepScreen(const Bitmap& bitmap) const {
}
void SleepActivity::renderCoverSleepScreen() const {
+ if (APP_STATE.openEpubPath.empty()) {
+ return renderDefaultSleepScreen();
+ }
+
Epub lastEpub(APP_STATE.openEpubPath, "/.crosspoint");
if (!lastEpub.load()) {
Serial.println("[SLP] Failed to load last epub");
return renderDefaultSleepScreen();
}
+
if (!lastEpub.generateCoverBmp()) {
Serial.println("[SLP] Failed to generate cover bmp");
return renderDefaultSleepScreen();
diff --git a/src/activities/boot_sleep/SleepActivity.h b/src/activities/boot_sleep/SleepActivity.h
index 2112199..774fd25 100644
--- a/src/activities/boot_sleep/SleepActivity.h
+++ b/src/activities/boot_sleep/SleepActivity.h
@@ -5,7 +5,8 @@ class Bitmap;
class SleepActivity final : public Activity {
public:
- explicit SleepActivity(GfxRenderer& renderer, InputManager& inputManager) : Activity(renderer, inputManager) {}
+ explicit SleepActivity(GfxRenderer& renderer, InputManager& inputManager)
+ : Activity("Sleep", renderer, inputManager) {}
void onEnter() override;
private:
diff --git a/src/activities/home/HomeActivity.cpp b/src/activities/home/HomeActivity.cpp
index 82d57cc..9cfa185 100644
--- a/src/activities/home/HomeActivity.cpp
+++ b/src/activities/home/HomeActivity.cpp
@@ -15,6 +15,8 @@ void HomeActivity::taskTrampoline(void* param) {
}
void HomeActivity::onEnter() {
+ Activity::onEnter();
+
renderingMutex = xSemaphoreCreateMutex();
selectorIndex = 0;
@@ -31,6 +33,8 @@ void HomeActivity::onEnter() {
}
void HomeActivity::onExit() {
+ Activity::onExit();
+
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
xSemaphoreTake(renderingMutex, portMAX_DELAY);
if (displayTaskHandle) {
diff --git a/src/activities/home/HomeActivity.h b/src/activities/home/HomeActivity.h
index 5dd26ec..943a466 100644
--- a/src/activities/home/HomeActivity.h
+++ b/src/activities/home/HomeActivity.h
@@ -23,7 +23,7 @@ class HomeActivity final : public Activity {
public:
explicit HomeActivity(GfxRenderer& renderer, InputManager& inputManager, const std::function& onReaderOpen,
const std::function& onSettingsOpen, const std::function& onFileTransferOpen)
- : Activity(renderer, inputManager),
+ : Activity("Home", renderer, inputManager),
onReaderOpen(onReaderOpen),
onSettingsOpen(onSettingsOpen),
onFileTransferOpen(onFileTransferOpen) {}
diff --git a/src/activities/network/CrossPointWebServerActivity.cpp b/src/activities/network/CrossPointWebServerActivity.cpp
index bb0f39a..d02411f 100644
--- a/src/activities/network/CrossPointWebServerActivity.cpp
+++ b/src/activities/network/CrossPointWebServerActivity.cpp
@@ -11,7 +11,8 @@ void CrossPointWebServerActivity::taskTrampoline(void* param) {
}
void CrossPointWebServerActivity::onEnter() {
- Serial.printf("[%lu] [WEBACT] ========== CrossPointWebServerActivity onEnter ==========\n", millis());
+ ActivityWithSubactivity::onEnter();
+
Serial.printf("[%lu] [WEBACT] [MEM] Free heap at onEnter: %d bytes\n", millis(), ESP.getFreeHeap());
renderingMutex = xSemaphoreCreateMutex();
@@ -36,13 +37,13 @@ void CrossPointWebServerActivity::onEnter() {
// Launch WiFi selection subactivity
Serial.printf("[%lu] [WEBACT] Launching WifiSelectionActivity...\n", millis());
- wifiSelection.reset(new WifiSelectionActivity(renderer, inputManager,
- [this](bool connected) { onWifiSelectionComplete(connected); }));
- wifiSelection->onEnter();
+ enterNewActivity(new WifiSelectionActivity(renderer, inputManager,
+ [this](const bool connected) { onWifiSelectionComplete(connected); }));
}
void CrossPointWebServerActivity::onExit() {
- Serial.printf("[%lu] [WEBACT] ========== CrossPointWebServerActivity onExit START ==========\n", millis());
+ ActivityWithSubactivity::onExit();
+
Serial.printf("[%lu] [WEBACT] [MEM] Free heap at onExit start: %d bytes\n", millis(), ESP.getFreeHeap());
state = WebServerActivityState::SHUTTING_DOWN;
@@ -50,14 +51,6 @@ void CrossPointWebServerActivity::onExit() {
// Stop the web server first (before disconnecting WiFi)
stopWebServer();
- // Exit WiFi selection subactivity if still active
- if (wifiSelection) {
- Serial.printf("[%lu] [WEBACT] Exiting WifiSelectionActivity...\n", millis());
- wifiSelection->onExit();
- wifiSelection.reset();
- Serial.printf("[%lu] [WEBACT] WifiSelectionActivity exited\n", millis());
- }
-
// CRITICAL: Wait for LWIP stack to flush any pending packets
Serial.printf("[%lu] [WEBACT] Waiting 500ms for network stack to flush pending packets...\n", millis());
delay(500);
@@ -92,20 +85,17 @@ void CrossPointWebServerActivity::onExit() {
Serial.printf("[%lu] [WEBACT] Mutex deleted\n", millis());
Serial.printf("[%lu] [WEBACT] [MEM] Free heap at onExit end: %d bytes\n", millis(), ESP.getFreeHeap());
- Serial.printf("[%lu] [WEBACT] ========== CrossPointWebServerActivity onExit COMPLETE ==========\n", millis());
}
-void CrossPointWebServerActivity::onWifiSelectionComplete(bool connected) {
+void CrossPointWebServerActivity::onWifiSelectionComplete(const bool connected) {
Serial.printf("[%lu] [WEBACT] WifiSelectionActivity completed, connected=%d\n", millis(), connected);
if (connected) {
// Get connection info before exiting subactivity
- connectedIP = wifiSelection->getConnectedIP();
+ connectedIP = static_cast(subActivity.get())->getConnectedIP();
connectedSSID = WiFi.SSID().c_str();
- // Exit the wifi selection subactivity
- wifiSelection->onExit();
- wifiSelection.reset();
+ exitActivity();
// Start the web server
startWebServer();
@@ -150,47 +140,40 @@ void CrossPointWebServerActivity::stopWebServer() {
}
void CrossPointWebServerActivity::loop() {
+ if (subActivity) {
+ // Forward loop to subactivity
+ subActivity->loop();
+ return;
+ }
+
// Handle different states
- switch (state) {
- case WebServerActivityState::WIFI_SELECTION:
- // Forward loop to WiFi selection subactivity
- if (wifiSelection) {
- wifiSelection->loop();
- }
- break;
+ if (state == WebServerActivityState::SERVER_RUNNING) {
+ // Handle web server requests - call handleClient multiple times per loop
+ // to improve responsiveness and upload throughput
+ if (webServer && webServer->isRunning()) {
+ const unsigned long timeSinceLastHandleClient = millis() - lastHandleClientTime;
- case WebServerActivityState::SERVER_RUNNING:
- // Handle web server requests - call handleClient multiple times per loop
- // to improve responsiveness and upload throughput
- if (webServer && webServer->isRunning()) {
- unsigned long timeSinceLastHandleClient = millis() - lastHandleClientTime;
-
- // Log if there's a significant gap between handleClient calls (>100ms)
- if (lastHandleClientTime > 0 && timeSinceLastHandleClient > 100) {
- Serial.printf("[%lu] [WEBACT] WARNING: %lu ms gap since last handleClient\n", millis(),
- timeSinceLastHandleClient);
- }
-
- // Call handleClient multiple times to process pending requests faster
- // This is critical for upload performance - HTTP file uploads send data
- // in chunks and each handleClient() call processes incoming data
- constexpr int HANDLE_CLIENT_ITERATIONS = 10;
- for (int i = 0; i < HANDLE_CLIENT_ITERATIONS && webServer->isRunning(); i++) {
- webServer->handleClient();
- }
- lastHandleClientTime = millis();
+ // Log if there's a significant gap between handleClient calls (>100ms)
+ if (lastHandleClientTime > 0 && timeSinceLastHandleClient > 100) {
+ Serial.printf("[%lu] [WEBACT] WARNING: %lu ms gap since last handleClient\n", millis(),
+ timeSinceLastHandleClient);
}
- // Handle exit on Back button
- if (inputManager.wasPressed(InputManager::BTN_BACK)) {
- onGoBack();
- return;
+ // Call handleClient multiple times to process pending requests faster
+ // This is critical for upload performance - HTTP file uploads send data
+ // in chunks and each handleClient() call processes incoming data
+ constexpr int HANDLE_CLIENT_ITERATIONS = 10;
+ for (int i = 0; i < HANDLE_CLIENT_ITERATIONS && webServer->isRunning(); i++) {
+ webServer->handleClient();
}
- break;
+ lastHandleClientTime = millis();
+ }
- case WebServerActivityState::SHUTTING_DOWN:
- // Do nothing - waiting for cleanup
- break;
+ // Handle exit on Back button
+ if (inputManager.wasPressed(InputManager::BTN_BACK)) {
+ onGoBack();
+ return;
+ }
}
}
diff --git a/src/activities/network/CrossPointWebServerActivity.h b/src/activities/network/CrossPointWebServerActivity.h
index ad41dcd..76d19f1 100644
--- a/src/activities/network/CrossPointWebServerActivity.h
+++ b/src/activities/network/CrossPointWebServerActivity.h
@@ -9,6 +9,7 @@
#include "../Activity.h"
#include "WifiSelectionActivity.h"
+#include "activities/ActivityWithSubactivity.h"
#include "server/CrossPointWebServer.h"
// Web server activity states
@@ -26,16 +27,13 @@ enum class WebServerActivityState {
* - Handles client requests in its loop() function
* - Cleans up the server and shuts down WiFi on exit
*/
-class CrossPointWebServerActivity final : public Activity {
+class CrossPointWebServerActivity final : public ActivityWithSubactivity {
TaskHandle_t displayTaskHandle = nullptr;
SemaphoreHandle_t renderingMutex = nullptr;
bool updateRequired = false;
WebServerActivityState state = WebServerActivityState::WIFI_SELECTION;
const std::function onGoBack;
- // WiFi selection subactivity
- std::unique_ptr wifiSelection;
-
// Web server - owned by this activity
std::unique_ptr webServer;
@@ -58,7 +56,7 @@ class CrossPointWebServerActivity final : public Activity {
public:
explicit CrossPointWebServerActivity(GfxRenderer& renderer, InputManager& inputManager,
const std::function& onGoBack)
- : Activity(renderer, inputManager), onGoBack(onGoBack) {}
+ : ActivityWithSubactivity("CrossPointWebServer", renderer, inputManager), onGoBack(onGoBack) {}
void onEnter() override;
void onExit() override;
void loop() override;
diff --git a/src/activities/network/WifiSelectionActivity.cpp b/src/activities/network/WifiSelectionActivity.cpp
index d6c3b1e..7f202fe 100644
--- a/src/activities/network/WifiSelectionActivity.cpp
+++ b/src/activities/network/WifiSelectionActivity.cpp
@@ -14,6 +14,8 @@ void WifiSelectionActivity::taskTrampoline(void* param) {
}
void WifiSelectionActivity::onEnter() {
+ Activity::onEnter();
+
renderingMutex = xSemaphoreCreateMutex();
// Load saved WiFi credentials
@@ -47,7 +49,8 @@ void WifiSelectionActivity::onEnter() {
}
void WifiSelectionActivity::onExit() {
- Serial.printf("[%lu] [WIFI] ========== WifiSelectionActivity onExit START ==========\n", millis());
+ Activity::onExit();
+
Serial.printf("[%lu] [WIFI] [MEM] Free heap at onExit start: %d bytes\n", millis(), ESP.getFreeHeap());
// Stop any ongoing WiFi scan
@@ -78,7 +81,6 @@ void WifiSelectionActivity::onExit() {
Serial.printf("[%lu] [WIFI] Mutex deleted\n", millis());
Serial.printf("[%lu] [WIFI] [MEM] Free heap at onExit end: %d bytes\n", millis(), ESP.getFreeHeap());
- Serial.printf("[%lu] [WIFI] ========== WifiSelectionActivity onExit COMPLETE ==========\n", millis());
}
void WifiSelectionActivity::startWifiScan() {
diff --git a/src/activities/network/WifiSelectionActivity.h b/src/activities/network/WifiSelectionActivity.h
index e7b12ae..a009de1 100644
--- a/src/activities/network/WifiSelectionActivity.h
+++ b/src/activities/network/WifiSelectionActivity.h
@@ -98,7 +98,7 @@ class WifiSelectionActivity final : public Activity {
public:
explicit WifiSelectionActivity(GfxRenderer& renderer, InputManager& inputManager,
const std::function& onComplete)
- : Activity(renderer, inputManager), onComplete(onComplete) {}
+ : Activity("WifiSelection", renderer, inputManager), onComplete(onComplete) {}
void onEnter() override;
void onExit() override;
void loop() override;
diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp
index 9635952..84abc49 100644
--- a/src/activities/reader/EpubReaderActivity.cpp
+++ b/src/activities/reader/EpubReaderActivity.cpp
@@ -26,6 +26,8 @@ void EpubReaderActivity::taskTrampoline(void* param) {
}
void EpubReaderActivity::onEnter() {
+ ActivityWithSubactivity::onEnter();
+
if (!epub) {
return;
}
@@ -61,6 +63,8 @@ void EpubReaderActivity::onEnter() {
}
void EpubReaderActivity::onExit() {
+ ActivityWithSubactivity::onExit();
+
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
xSemaphoreTake(renderingMutex, portMAX_DELAY);
if (displayTaskHandle) {
@@ -75,8 +79,8 @@ void EpubReaderActivity::onExit() {
void EpubReaderActivity::loop() {
// Pass input responsibility to sub activity if exists
- if (subAcitivity) {
- subAcitivity->loop();
+ if (subActivity) {
+ subActivity->loop();
return;
}
@@ -84,11 +88,11 @@ void EpubReaderActivity::loop() {
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
// Don't start activity transition while rendering
xSemaphoreTake(renderingMutex, portMAX_DELAY);
- subAcitivity.reset(new EpubReaderChapterSelectionActivity(
+ exitActivity();
+ enterNewActivity(new EpubReaderChapterSelectionActivity(
this->renderer, this->inputManager, epub, currentSpineIndex,
[this] {
- subAcitivity->onExit();
- subAcitivity.reset();
+ exitActivity();
updateRequired = true;
},
[this](const int newSpineIndex) {
@@ -97,11 +101,9 @@ void EpubReaderActivity::loop() {
nextPageNumber = 0;
section.reset();
}
- subAcitivity->onExit();
- subAcitivity.reset();
+ exitActivity();
updateRequired = true;
}));
- subAcitivity->onEnter();
xSemaphoreGive(renderingMutex);
}
@@ -330,8 +332,8 @@ void EpubReaderActivity::renderStatusBar() const {
constexpr auto textY = 776;
// Calculate progress in book
- float sectionChapterProg = static_cast(section->currentPage) / section->pageCount;
- uint8_t bookProgress = epub->calculateProgress(currentSpineIndex, sectionChapterProg);
+ const float sectionChapterProg = static_cast(section->currentPage) / section->pageCount;
+ const uint8_t bookProgress = epub->calculateProgress(currentSpineIndex, sectionChapterProg);
// Right aligned text for progress counter
const std::string progress = std::to_string(section->currentPage + 1) + "/" + std::to_string(section->pageCount) +
diff --git a/src/activities/reader/EpubReaderActivity.h b/src/activities/reader/EpubReaderActivity.h
index 8eeddc1..4edbabc 100644
--- a/src/activities/reader/EpubReaderActivity.h
+++ b/src/activities/reader/EpubReaderActivity.h
@@ -5,14 +5,13 @@
#include
#include
-#include "../Activity.h"
+#include "activities/ActivityWithSubactivity.h"
-class EpubReaderActivity final : public Activity {
+class EpubReaderActivity final : public ActivityWithSubactivity {
std::shared_ptr epub;
std::unique_ptr section = nullptr;
TaskHandle_t displayTaskHandle = nullptr;
SemaphoreHandle_t renderingMutex = nullptr;
- std::unique_ptr subAcitivity = nullptr;
int currentSpineIndex = 0;
int nextPageNumber = 0;
int pagesUntilFullRefresh = 0;
@@ -28,7 +27,7 @@ class EpubReaderActivity final : public Activity {
public:
explicit EpubReaderActivity(GfxRenderer& renderer, InputManager& inputManager, std::unique_ptr epub,
const std::function& onGoBack)
- : Activity(renderer, inputManager), epub(std::move(epub)), onGoBack(onGoBack) {}
+ : ActivityWithSubactivity("EpubReader", renderer, inputManager), epub(std::move(epub)), onGoBack(onGoBack) {}
void onEnter() override;
void onExit() override;
void loop() override;
diff --git a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp
index 9af556b..07fd456 100644
--- a/src/activities/reader/EpubReaderChapterSelectionActivity.cpp
+++ b/src/activities/reader/EpubReaderChapterSelectionActivity.cpp
@@ -16,6 +16,8 @@ void EpubReaderChapterSelectionActivity::taskTrampoline(void* param) {
}
void EpubReaderChapterSelectionActivity::onEnter() {
+ Activity::onEnter();
+
if (!epub) {
return;
}
@@ -34,6 +36,8 @@ void EpubReaderChapterSelectionActivity::onEnter() {
}
void EpubReaderChapterSelectionActivity::onExit() {
+ Activity::onExit();
+
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
xSemaphoreTake(renderingMutex, portMAX_DELAY);
if (displayTaskHandle) {
diff --git a/src/activities/reader/EpubReaderChapterSelectionActivity.h b/src/activities/reader/EpubReaderChapterSelectionActivity.h
index b25ef6f..8c1adef 100644
--- a/src/activities/reader/EpubReaderChapterSelectionActivity.h
+++ b/src/activities/reader/EpubReaderChapterSelectionActivity.h
@@ -27,7 +27,7 @@ class EpubReaderChapterSelectionActivity final : public Activity {
const std::shared_ptr& epub, const int currentSpineIndex,
const std::function& onGoBack,
const std::function& onSelectSpineIndex)
- : Activity(renderer, inputManager),
+ : Activity("EpubReaderChapterSelection", renderer, inputManager),
epub(epub),
currentSpineIndex(currentSpineIndex),
onGoBack(onGoBack),
diff --git a/src/activities/reader/FileSelectionActivity.cpp b/src/activities/reader/FileSelectionActivity.cpp
index d8aef3c..fc39a8c 100644
--- a/src/activities/reader/FileSelectionActivity.cpp
+++ b/src/activities/reader/FileSelectionActivity.cpp
@@ -48,6 +48,8 @@ void FileSelectionActivity::loadFiles() {
}
void FileSelectionActivity::onEnter() {
+ Activity::onEnter();
+
renderingMutex = xSemaphoreCreateMutex();
basepath = "/";
@@ -66,6 +68,8 @@ void FileSelectionActivity::onEnter() {
}
void FileSelectionActivity::onExit() {
+ Activity::onExit();
+
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
xSemaphoreTake(renderingMutex, portMAX_DELAY);
if (displayTaskHandle) {
diff --git a/src/activities/reader/FileSelectionActivity.h b/src/activities/reader/FileSelectionActivity.h
index ff1a922..2a8f8ae 100644
--- a/src/activities/reader/FileSelectionActivity.h
+++ b/src/activities/reader/FileSelectionActivity.h
@@ -28,7 +28,7 @@ class FileSelectionActivity final : public Activity {
explicit FileSelectionActivity(GfxRenderer& renderer, InputManager& inputManager,
const std::function& onSelect,
const std::function& onGoHome)
- : Activity(renderer, inputManager), onSelect(onSelect), onGoHome(onGoHome) {}
+ : Activity("FileSelection", renderer, inputManager), onSelect(onSelect), onGoHome(onGoHome) {}
void onEnter() override;
void onExit() override;
void loop() override;
diff --git a/src/activities/reader/ReaderActivity.cpp b/src/activities/reader/ReaderActivity.cpp
index 93389fe..d888fb6 100644
--- a/src/activities/reader/ReaderActivity.cpp
+++ b/src/activities/reader/ReaderActivity.cpp
@@ -50,6 +50,8 @@ void ReaderActivity::onGoToEpubReader(std::unique_ptr epub) {
}
void ReaderActivity::onEnter() {
+ ActivityWithSubactivity::onEnter();
+
if (initialEpubPath.empty()) {
onGoToFileSelection();
return;
diff --git a/src/activities/reader/ReaderActivity.h b/src/activities/reader/ReaderActivity.h
index a68cd89..e566d6d 100644
--- a/src/activities/reader/ReaderActivity.h
+++ b/src/activities/reader/ReaderActivity.h
@@ -17,7 +17,7 @@ class ReaderActivity final : public ActivityWithSubactivity {
public:
explicit ReaderActivity(GfxRenderer& renderer, InputManager& inputManager, std::string initialEpubPath,
const std::function& onGoBack)
- : ActivityWithSubactivity(renderer, inputManager),
+ : ActivityWithSubactivity("Reader", renderer, inputManager),
initialEpubPath(std::move(initialEpubPath)),
onGoBack(onGoBack) {}
void onEnter() override;
diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp
index 29f6876..d38d85c 100644
--- a/src/activities/settings/SettingsActivity.cpp
+++ b/src/activities/settings/SettingsActivity.cpp
@@ -21,6 +21,8 @@ void SettingsActivity::taskTrampoline(void* param) {
}
void SettingsActivity::onEnter() {
+ Activity::onEnter();
+
renderingMutex = xSemaphoreCreateMutex();
// Reset selection to first item
@@ -38,6 +40,8 @@ void SettingsActivity::onEnter() {
}
void SettingsActivity::onExit() {
+ Activity::onExit();
+
// Wait until not rendering to delete task to avoid killing mid-instruction to EPD
xSemaphoreTake(renderingMutex, portMAX_DELAY);
if (displayTaskHandle) {
@@ -76,7 +80,7 @@ void SettingsActivity::loop() {
}
}
-void SettingsActivity::toggleCurrentSetting() {
+void SettingsActivity::toggleCurrentSetting() const {
// Validate index
if (selectedSettingIndex < 0 || selectedSettingIndex >= settingsCount) {
return;
diff --git a/src/activities/settings/SettingsActivity.h b/src/activities/settings/SettingsActivity.h
index 333f467..6fe5db1 100644
--- a/src/activities/settings/SettingsActivity.h
+++ b/src/activities/settings/SettingsActivity.h
@@ -32,11 +32,11 @@ class SettingsActivity final : public Activity {
static void taskTrampoline(void* param);
[[noreturn]] void displayTaskLoop();
void render() const;
- void toggleCurrentSetting();
+ void toggleCurrentSetting() const;
public:
explicit SettingsActivity(GfxRenderer& renderer, InputManager& inputManager, const std::function& onGoHome)
- : Activity(renderer, inputManager), onGoHome(onGoHome) {}
+ : Activity("Settings", renderer, inputManager), onGoHome(onGoHome) {}
void onEnter() override;
void onExit() override;
void loop() override;
diff --git a/src/activities/util/FullScreenMessageActivity.cpp b/src/activities/util/FullScreenMessageActivity.cpp
index a56952f..cf84cc5 100644
--- a/src/activities/util/FullScreenMessageActivity.cpp
+++ b/src/activities/util/FullScreenMessageActivity.cpp
@@ -5,6 +5,8 @@
#include "config.h"
void FullScreenMessageActivity::onEnter() {
+ Activity::onEnter();
+
const auto height = renderer.getLineHeight(UI_FONT_ID);
const auto top = (GfxRenderer::getScreenHeight() - height) / 2;
diff --git a/src/activities/util/FullScreenMessageActivity.h b/src/activities/util/FullScreenMessageActivity.h
index 3180ddb..8c6e30e 100644
--- a/src/activities/util/FullScreenMessageActivity.h
+++ b/src/activities/util/FullScreenMessageActivity.h
@@ -16,6 +16,9 @@ class FullScreenMessageActivity final : public Activity {
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) {}
+ : Activity("FullScreenMessage", renderer, inputManager),
+ text(std::move(text)),
+ style(style),
+ refreshMode(refreshMode) {}
void onEnter() override;
};
diff --git a/src/activities/util/KeyboardEntryActivity.cpp b/src/activities/util/KeyboardEntryActivity.cpp
index 68fc079..b4ed01c 100644
--- a/src/activities/util/KeyboardEntryActivity.cpp
+++ b/src/activities/util/KeyboardEntryActivity.cpp
@@ -12,11 +12,6 @@ const char* const KeyboardEntryActivity::keyboard[NUM_ROWS] = {
const char* const KeyboardEntryActivity::keyboardShift[NUM_ROWS] = {"~!@#$%^&*()_+", "QWERTYUIOP{}|", "ASDFGHJKL:\"",
"ZXCVBNM<>?", "^ _____ 0 && text.length() > maxLength) {
@@ -37,15 +32,13 @@ void KeyboardEntryActivity::reset(const std::string& newTitle, const std::string
}
void KeyboardEntryActivity::onEnter() {
+ Activity::onEnter();
+
// Reset state when entering the activity
complete = false;
cancelled = false;
}
-void KeyboardEntryActivity::onExit() {
- // Clean up if needed
-}
-
void KeyboardEntryActivity::loop() {
handleInput();
render(10);
diff --git a/src/activities/util/KeyboardEntryActivity.h b/src/activities/util/KeyboardEntryActivity.h
index af6d9b4..3b5b806 100644
--- a/src/activities/util/KeyboardEntryActivity.h
+++ b/src/activities/util/KeyboardEntryActivity.h
@@ -34,7 +34,12 @@ class KeyboardEntryActivity : public Activity {
* @param isPassword If true, display asterisks instead of actual characters
*/
KeyboardEntryActivity(GfxRenderer& renderer, InputManager& inputManager, const std::string& title = "Enter Text",
- const std::string& initialText = "", size_t maxLength = 0, bool isPassword = false);
+ const std::string& initialText = "", const size_t maxLength = 0, const bool isPassword = false)
+ : Activity("KeyboardEntry", renderer, inputManager),
+ title(title),
+ text(initialText),
+ maxLength(maxLength),
+ isPassword(isPassword) {}
/**
* Handle button input. Call this in your main loop.
@@ -85,7 +90,6 @@ class KeyboardEntryActivity : public Activity {
// Activity overrides
void onEnter() override;
- void onExit() override;
void loop() override;
private:
From b39ce22e5427a528459398babc2a7ef280e456e0 Mon Sep 17 00:00:00 2001
From: Dave Allie
Date: Mon, 22 Dec 2025 00:31:25 +1100
Subject: [PATCH 20/33] Cleanup of activities
---
src/activities/Activity.h | 5 +-
src/activities/home/HomeActivity.cpp | 5 +-
.../network/CrossPointWebServerActivity.cpp | 2 +
.../network/CrossPointWebServerActivity.h | 4 +-
.../network/WifiSelectionActivity.cpp | 70 +++++++++++--------
.../network/WifiSelectionActivity.h | 10 +--
src/activities/reader/EpubReaderActivity.cpp | 1 +
.../EpubReaderChapterSelectionActivity.cpp | 1 +
.../reader/FileSelectionActivity.cpp | 1 +
src/activities/settings/SettingsActivity.cpp | 1 +
.../CrossPointWebServer.cpp | 0
.../server => network}/CrossPointWebServer.h | 2 -
src/{ => network}/html/FilesPageFooter.html | 0
src/{ => network}/html/FilesPageHeader.html | 0
src/{ => network}/html/HomePage.html | 0
15 files changed, 57 insertions(+), 45 deletions(-)
rename src/{activities/network/server => network}/CrossPointWebServer.cpp (100%)
rename src/{activities/network/server => network}/CrossPointWebServer.h (96%)
rename src/{ => network}/html/FilesPageFooter.html (100%)
rename src/{ => network}/html/FilesPageHeader.html (100%)
rename src/{ => network}/html/HomePage.html (100%)
diff --git a/src/activities/Activity.h b/src/activities/Activity.h
index 90a2eb1..3a61db6 100644
--- a/src/activities/Activity.h
+++ b/src/activities/Activity.h
@@ -1,8 +1,11 @@
#pragma once
-#include
+#include
+
+#include
#include
+class InputManager;
class GfxRenderer;
class Activity {
diff --git a/src/activities/home/HomeActivity.cpp b/src/activities/home/HomeActivity.cpp
index 9cfa185..bbda130 100644
--- a/src/activities/home/HomeActivity.cpp
+++ b/src/activities/home/HomeActivity.cpp
@@ -1,6 +1,7 @@
#include "HomeActivity.h"
#include
+#include
#include
#include "config.h"
@@ -83,8 +84,8 @@ void HomeActivity::displayTaskLoop() {
void HomeActivity::render() const {
renderer.clearScreen();
- const auto pageWidth = GfxRenderer::getScreenWidth();
- const auto pageHeight = GfxRenderer::getScreenHeight();
+ const auto pageWidth = renderer.getScreenWidth();
+ const auto pageHeight = renderer.getScreenHeight();
renderer.drawCenteredText(READER_FONT_ID, 10, "CrossPoint Reader", true, BOLD);
// Draw selection
diff --git a/src/activities/network/CrossPointWebServerActivity.cpp b/src/activities/network/CrossPointWebServerActivity.cpp
index d02411f..5bf571a 100644
--- a/src/activities/network/CrossPointWebServerActivity.cpp
+++ b/src/activities/network/CrossPointWebServerActivity.cpp
@@ -1,8 +1,10 @@
#include "CrossPointWebServerActivity.h"
#include
+#include
#include
+#include "WifiSelectionActivity.h"
#include "config.h"
void CrossPointWebServerActivity::taskTrampoline(void* param) {
diff --git a/src/activities/network/CrossPointWebServerActivity.h b/src/activities/network/CrossPointWebServerActivity.h
index 76d19f1..6889f6e 100644
--- a/src/activities/network/CrossPointWebServerActivity.h
+++ b/src/activities/network/CrossPointWebServerActivity.h
@@ -7,10 +7,8 @@
#include
#include
-#include "../Activity.h"
-#include "WifiSelectionActivity.h"
#include "activities/ActivityWithSubactivity.h"
-#include "server/CrossPointWebServer.h"
+#include "network/CrossPointWebServer.h"
// Web server activity states
enum class WebServerActivityState {
diff --git a/src/activities/network/WifiSelectionActivity.cpp b/src/activities/network/WifiSelectionActivity.cpp
index 7f202fe..c18e0f5 100644
--- a/src/activities/network/WifiSelectionActivity.cpp
+++ b/src/activities/network/WifiSelectionActivity.cpp
@@ -6,6 +6,7 @@
#include