Compare commits

...

18 Commits

Author SHA1 Message Date
Dave Allie 021f77eab3 Sort items on FileSelectionScreen 2025-12-06 13:01:16 +11:00
Dave Allie 6d3d25a288 Fix bug with selectin epubs inside of folders 2025-12-06 12:57:17 +11:00
Dave Allie 9a33030623 Use reference passing for EpdRenderer 2025-12-06 12:56:39 +11:00
Dave Allie 6414f85257 Use InputManager from community-sdk 2025-12-06 12:35:41 +11:00
Dave Allie f0d92da8f2 Update README.md with checkout instructions
Fixes https://github.com/daveallie/crosspoint-reader/issues/1
2025-12-06 04:53:58 +11:00
Dave Allie 8679c8f57c Update sleep screen 2025-12-06 04:20:41 +11:00
Dave Allie 899caab70c Avoid leaving screens mid-display update
Was leave the EPD in a bad state, blocking further actions
2025-12-06 03:02:52 +11:00
Dave Allie 98c8e7e77c Fix memory leak with Epub object getting orphaned 2025-12-06 02:49:10 +11:00
Dave Allie 7198d943b0 Add UI font 2025-12-06 01:44:14 +11:00
Dave Allie 248af4b8fb Add new boot logo screen 2025-12-06 01:44:14 +11:00
Dave Allie 05a027e2bf Wrap up multiple font styles into EpdFontFamily 2025-12-06 01:44:14 +11:00
Dave Allie fa0f27df6a Full screen refresh of EpubReaderScreen every 10 renders
This is done in an effort to minimise ghosting
2025-12-05 22:19:44 +11:00
Dave Allie 2631613b8d Add directory picking to home screen 2025-12-05 22:12:28 +11:00
Dave Allie 72aa7ba3f6 Upgrade open-x4-sdk 2025-12-05 21:14:08 +11:00
Dave Allie e08bac2e10 Show indexing text when indexing 2025-12-05 21:12:15 +11:00
Dave Allie 12d28e2148 Avoid ghosting on sleep screen by doing a full screen update 2025-12-05 17:55:17 +11:00
Dave Allie 85502b417e Speedup boot by not waiting for Serial 2025-12-05 17:47:23 +11:00
Dave Allie ddec7f78dd Fix hold to wake logic
esp_sleep_get_wakeup_cause does not seem to be set when not connected to USB power
2025-12-04 00:57:32 +11:00
37 changed files with 7306 additions and 410 deletions
+12 -1
View File
@@ -32,7 +32,7 @@ This project is **not affiliated with Xteink**; it's built as a community projec
- [x] Saved reading position
- [ ] File explorer with file picker
- [x] Basic EPUB picker from root directory
- [ ] Support nested folders
- [x] Support nested folders
- [ ] EPUB picker with cover art
- [ ] Image support within EPUB
- [ ] Configurable font, layout, and display options
@@ -48,6 +48,17 @@ This project is **not affiliated with Xteink**; it's built as a community projec
* USB-C cable for flashing the ESP32-C3
* Xteink X4
### Checking out the code
CrossPoint uses PlatformIO for building and flashing the firmware. To get started, clone the repository:
```
git clone --recursive https://github.com/daveallie/crosspoint-reader
# Or, if you've already cloned without --recursive:
git submodule update --init --recursive
```
### Flashing your device
#### Command line
+37
View File
@@ -0,0 +1,37 @@
#include "EpdFontFamily.h"
const EpdFont* EpdFontFamily::getFont(const EpdFontStyle style) const {
if (style == BOLD && bold) {
return bold;
}
if (style == ITALIC && italic) {
return italic;
}
if (style == BOLD_ITALIC) {
if (boldItalic) {
return boldItalic;
}
if (bold) {
return bold;
}
if (italic) {
return italic;
}
}
return regular;
}
void EpdFontFamily::getTextDimensions(const char* string, int* w, int* h, const EpdFontStyle style) const {
getFont(style)->getTextDimensions(string, w, h);
}
bool EpdFontFamily::hasPrintableChars(const char* string, const EpdFontStyle style) const {
return getFont(style)->hasPrintableChars(string);
}
const EpdFontData* EpdFontFamily::getData(const EpdFontStyle style) const { return getFont(style)->data; }
const EpdGlyph* EpdFontFamily::getGlyph(const uint32_t cp, const EpdFontStyle style) const {
return getFont(style)->getGlyph(cp);
};
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include "EpdFont.h"
enum EpdFontStyle { REGULAR, BOLD, ITALIC, BOLD_ITALIC };
class EpdFontFamily {
const EpdFont* regular;
const EpdFont* bold;
const EpdFont* italic;
const EpdFont* boldItalic;
const EpdFont* getFont(EpdFontStyle style) const;
public:
explicit EpdFontFamily(const EpdFont* regular, const EpdFont* bold = nullptr, const EpdFont* italic = nullptr,
const EpdFont* boldItalic = nullptr)
: regular(regular), bold(bold), italic(italic), boldItalic(boldItalic) {}
~EpdFontFamily() = default;
void getTextDimensions(const char* string, int* w, int* h, EpdFontStyle style = REGULAR) const;
bool hasPrintableChars(const char* string, EpdFontStyle style = REGULAR) const;
const EpdFontData* getData(EpdFontStyle style = REGULAR) const;
const EpdGlyph* getGlyph(uint32_t cp, EpdFontStyle style = REGULAR) const;
};
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+20 -23
View File
@@ -1,5 +1,5 @@
#pragma once
#include <EpdFont.h>
#include <EpdFontFamily.h>
#include <HardwareSerial.h>
#include <Utf8.h>
#include <miniz.h>
@@ -12,13 +12,14 @@ static tinfl_decompressor decomp;
template <typename Renderable>
class EpdFontRenderer {
Renderable* renderer;
void renderChar(uint32_t cp, int* x, const int* y, uint16_t color);
void renderChar(uint32_t cp, int* x, const int* y, uint16_t color, EpdFontStyle style = REGULAR);
public:
const EpdFont* font;
explicit EpdFontRenderer(const EpdFont* font, Renderable* renderer);
const EpdFontFamily* fontFamily;
explicit EpdFontRenderer(const EpdFontFamily* fontFamily, Renderable* renderer)
: fontFamily(fontFamily), renderer(renderer) {}
~EpdFontRenderer() = default;
void renderString(const char* string, int* x, int* y, uint16_t color);
void renderString(const char* string, int* x, int* y, uint16_t color, EpdFontStyle style = REGULAR);
};
inline int uncompress(uint8_t* dest, size_t uncompressedSize, const uint8_t* source, size_t sourceSize) {
@@ -38,37 +39,33 @@ inline int uncompress(uint8_t* dest, size_t uncompressedSize, const uint8_t* sou
}
template <typename Renderable>
EpdFontRenderer<Renderable>::EpdFontRenderer(const EpdFont* font, Renderable* renderer) {
this->font = font;
this->renderer = renderer;
}
template <typename Renderable>
void EpdFontRenderer<Renderable>::renderString(const char* string, int* x, int* y, const uint16_t color) {
void EpdFontRenderer<Renderable>::renderString(const char* string, int* x, int* y, const uint16_t color,
const EpdFontStyle style) {
// cannot draw a NULL / empty string
if (string == nullptr || *string == '\0') {
return;
}
// no printable characters
if (!font->hasPrintableChars(string)) {
if (!fontFamily->hasPrintableChars(string, style)) {
return;
}
uint32_t cp;
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&string)))) {
renderChar(cp, x, y, color);
renderChar(cp, x, y, color, style);
}
*y += font->data->advanceY;
*y += fontFamily->getData(style)->advanceY;
}
template <typename Renderable>
void EpdFontRenderer<Renderable>::renderChar(const uint32_t cp, int* x, const int* y, uint16_t color) {
const EpdGlyph* glyph = font->getGlyph(cp);
void EpdFontRenderer<Renderable>::renderChar(const uint32_t cp, int* x, const int* y, uint16_t color,
const EpdFontStyle style) {
const EpdGlyph* glyph = fontFamily->getGlyph(cp, style);
if (!glyph) {
// TODO: Replace with fallback glyph property?
glyph = font->getGlyph('?');
glyph = fontFamily->getGlyph('?', style);
}
// no glyph?
@@ -86,17 +83,17 @@ void EpdFontRenderer<Renderable>::renderChar(const uint32_t cp, int* x, const in
const unsigned long bitmapSize = byteWidth * height;
const uint8_t* bitmap = nullptr;
if (font->data->compressed) {
if (fontFamily->getData(style)->compressed) {
auto* tmpBitmap = static_cast<uint8_t*>(malloc(bitmapSize));
if (tmpBitmap == nullptr && bitmapSize) {
// ESP_LOGE("font", "malloc failed.");
Serial.println("Failed to allocate memory for decompression buffer");
return;
}
uncompress(tmpBitmap, bitmapSize, &font->data->bitmap[offset], glyph->compressedSize);
uncompress(tmpBitmap, bitmapSize, &fontFamily->getData(style)->bitmap[offset], glyph->compressedSize);
bitmap = tmpBitmap;
} else {
bitmap = &font->data->bitmap[offset];
bitmap = &fontFamily->getData(style)->bitmap[offset];
}
if (bitmap != nullptr) {
@@ -123,7 +120,7 @@ void EpdFontRenderer<Renderable>::renderChar(const uint32_t cp, int* x, const in
}
}
if (font->data->compressed) {
if (fontFamily->getData(style)->compressed) {
free(const_cast<uint8_t*>(bitmap));
}
}
+66 -58
View File
@@ -5,14 +5,18 @@
#include "builtinFonts/bookerly_bold.h"
#include "builtinFonts/bookerly_bold_italic.h"
#include "builtinFonts/bookerly_italic.h"
#include "builtinFonts/ubuntu_10.h"
#include "builtinFonts/ubuntu_bold_10.h"
EpdRenderer::EpdRenderer(XteinkDisplay* display) {
const auto bookerlyFontFamily = new EpdFontFamily(new EpdFont(&bookerly), new EpdFont(&bookerly_bold),
new EpdFont(&bookerly_italic), new EpdFont(&bookerly_bold_italic));
const auto ubuntuFontFamily = new EpdFontFamily(new EpdFont(&ubuntu_10), new EpdFont(&ubuntu_bold_10));
this->display = display;
this->regularFont = new EpdFontRenderer<XteinkDisplay>(new EpdFont(&bookerly), display);
this->boldFont = new EpdFontRenderer<XteinkDisplay>(new EpdFont(&bookerly_bold), display);
this->italicFont = new EpdFontRenderer<XteinkDisplay>(new EpdFont(&bookerly_italic), display);
this->bold_italicFont = new EpdFontRenderer<XteinkDisplay>(new EpdFont(&bookerly_bold_italic), display);
this->smallFont = new EpdFontRenderer<XteinkDisplay>(new EpdFont(&babyblue), display);
this->regularFontRenderer = new EpdFontRenderer<XteinkDisplay>(bookerlyFontFamily, display);
this->smallFontRenderer = new EpdFontRenderer<XteinkDisplay>(new EpdFontFamily(new EpdFont(&babyblue)), display);
this->uiFontRenderer = new EpdFontRenderer<XteinkDisplay>(ubuntuFontFamily, display);
this->marginTop = 11;
this->marginBottom = 30;
@@ -21,50 +25,53 @@ EpdRenderer::EpdRenderer(XteinkDisplay* display) {
this->lineCompression = 0.95f;
}
EpdFontRenderer<XteinkDisplay>* EpdRenderer::getFontRenderer(const bool bold, const bool italic) const {
if (bold && italic) {
return bold_italicFont;
}
if (bold) {
return boldFont;
}
if (italic) {
return italicFont;
}
return regularFont;
}
int EpdRenderer::getTextWidth(const char* text, const bool bold, const bool italic) const {
int EpdRenderer::getTextWidth(const char* text, const EpdFontStyle style) const {
int w = 0, h = 0;
getFontRenderer(bold, italic)->font->getTextDimensions(text, &w, &h);
regularFontRenderer->fontFamily->getTextDimensions(text, &w, &h, style);
return w;
}
int EpdRenderer::getSmallTextWidth(const char* text) const {
int EpdRenderer::getUiTextWidth(const char* text, const EpdFontStyle style) const {
int w = 0, h = 0;
smallFont->font->getTextDimensions(text, &w, &h);
uiFontRenderer->fontFamily->getTextDimensions(text, &w, &h, style);
return w;
}
void EpdRenderer::drawText(const int x, const int y, const char* text, const bool bold, const bool italic,
const uint16_t color) const {
int EpdRenderer::getSmallTextWidth(const char* text, const EpdFontStyle style) const {
int w = 0, h = 0;
smallFontRenderer->fontFamily->getTextDimensions(text, &w, &h, style);
return w;
}
void EpdRenderer::drawText(const int x, const int y, const char* text, const uint16_t color,
const EpdFontStyle style) const {
int ypos = y + getLineHeight() + marginTop;
int xpos = x + marginLeft;
getFontRenderer(bold, italic)->renderString(text, &xpos, &ypos, color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
regularFontRenderer->renderString(text, &xpos, &ypos, color > 0 ? GxEPD_BLACK : GxEPD_WHITE, style);
}
void EpdRenderer::drawSmallText(const int x, const int y, const char* text, const uint16_t color) const {
int ypos = y + smallFont->font->data->advanceY + marginTop;
void EpdRenderer::drawUiText(const int x, const int y, const char* text, const uint16_t color,
const EpdFontStyle style) const {
int ypos = y + uiFontRenderer->fontFamily->getData(style)->advanceY + marginTop;
int xpos = x + marginLeft;
smallFont->renderString(text, &xpos, &ypos, color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
uiFontRenderer->renderString(text, &xpos, &ypos, color > 0 ? GxEPD_BLACK : GxEPD_WHITE, style);
}
void EpdRenderer::drawSmallText(const int x, const int y, const char* text, const uint16_t color,
const EpdFontStyle style) const {
int ypos = y + smallFontRenderer->fontFamily->getData(style)->advanceY + marginTop;
int xpos = x + marginLeft;
smallFontRenderer->renderString(text, &xpos, &ypos, color > 0 ? GxEPD_BLACK : GxEPD_WHITE, style);
}
void EpdRenderer::drawTextBox(const int x, const int y, const std::string& text, const int width, const int height,
const bool bold, const bool italic) const {
const EpdFontStyle style) const {
const size_t length = text.length();
// fit the text into the box
int start = 0;
@@ -72,7 +79,7 @@ void EpdRenderer::drawTextBox(const int x, const int y, const std::string& text,
int ypos = 0;
while (true) {
if (end >= length) {
drawText(x, y + ypos, text.substr(start, length - start).c_str(), bold, italic);
drawText(x, y + ypos, text.substr(start, length - start).c_str(), 1, style);
break;
}
@@ -81,15 +88,15 @@ void EpdRenderer::drawTextBox(const int x, const int y, const std::string& text,
}
if (text[end - 1] == '\n') {
drawText(x, y + ypos, text.substr(start, end - start).c_str(), bold, italic);
drawText(x, y + ypos, text.substr(start, end - start).c_str(), 1, style);
ypos += getLineHeight();
start = end;
end = start + 1;
continue;
}
if (getTextWidth(text.substr(start, end - start).c_str(), bold, italic) > width) {
drawText(x, y + ypos, text.substr(start, end - start - 1).c_str(), bold, italic);
if (getTextWidth(text.substr(start, end - start).c_str(), style) > width) {
drawText(x, y + ypos, text.substr(start, end - start - 1).c_str(), 1, style);
ypos += getLineHeight();
start = end - 1;
continue;
@@ -108,44 +115,45 @@ void EpdRenderer::drawRect(const int x, const int y, const int width, const int
display->drawRect(x + marginLeft, y + marginTop, width, height, color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
}
void EpdRenderer::fillRect(const int x, const int y, const int width, const int height,
const uint16_t color = 0) const {
void EpdRenderer::fillRect(const int x, const int y, const int width, const int height, const uint16_t color) const {
display->fillRect(x + marginLeft, y + marginTop, width, height, color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
}
void EpdRenderer::drawCircle(const int x, const int y, const int radius, const uint16_t color) const {
display->drawCircle(x + marginLeft, y + marginTop, radius, color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
}
void EpdRenderer::fillCircle(const int x, const int y, const int radius, const uint16_t color) const {
display->fillCircle(x + marginLeft, y + marginTop, radius, color > 0 ? GxEPD_BLACK : GxEPD_WHITE);
}
void EpdRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height,
const bool invert, const bool mirrorY) const {
drawImageNoMargin(bitmap, x + marginLeft, y + marginTop, width, height, invert, mirrorY);
}
void EpdRenderer::drawImageNoMargin(const uint8_t bitmap[], const int x, const int y, const int width, const int height,
const bool invert, const bool mirrorY) const {
display->drawImage(bitmap, x, y, width, height, invert, mirrorY);
}
void EpdRenderer::clearScreen(const bool black) const {
Serial.println("Clearing screen");
display->fillScreen(black ? GxEPD_BLACK : GxEPD_WHITE);
}
void EpdRenderer::flushDisplay() const { display->display(true); }
void EpdRenderer::flushDisplay(const bool partialUpdate) const { display->display(partialUpdate); }
void EpdRenderer::flushArea(int x, int y, int width, int height) const {
// TODO: Fix
display->display(true);
void EpdRenderer::flushArea(const int x, const int y, const int width, const int height) const {
display->displayWindow(x + marginLeft, y + marginTop, width, height);
}
int EpdRenderer::getPageWidth() const { return display->width() - marginLeft - marginRight; }
int EpdRenderer::getPageHeight() const { return display->height() - marginTop - marginBottom; }
int EpdRenderer::getSpaceWidth() const { return regularFont->font->getGlyph(' ')->advanceX; }
int EpdRenderer::getSpaceWidth() const { return regularFontRenderer->fontFamily->getGlyph(' ', REGULAR)->advanceX; }
int EpdRenderer::getLineHeight() const { return regularFont->font->data->advanceY * lineCompression; }
// deep sleep helper - persist any state to disk that may be needed on wake
bool EpdRenderer::dehydrate() {
// TODO: Implement
return false;
};
// deep sleep helper - retrieve any state from disk after wake
bool EpdRenderer::hydrate() {
// TODO: Implement
return false;
};
// really really clear the screen
void EpdRenderer::reset() {
// TODO: Implement
};
int EpdRenderer::getLineHeight() const {
return regularFontRenderer->fontFamily->getData(REGULAR)->advanceY * lineCompression;
}
+20 -24
View File
@@ -8,32 +8,36 @@
class EpdRenderer {
XteinkDisplay* display;
EpdFontRenderer<XteinkDisplay>* regularFont;
EpdFontRenderer<XteinkDisplay>* boldFont;
EpdFontRenderer<XteinkDisplay>* italicFont;
EpdFontRenderer<XteinkDisplay>* bold_italicFont;
EpdFontRenderer<XteinkDisplay>* smallFont;
EpdFontRenderer<XteinkDisplay>* regularFontRenderer;
EpdFontRenderer<XteinkDisplay>* smallFontRenderer;
EpdFontRenderer<XteinkDisplay>* uiFontRenderer;
int marginTop;
int marginBottom;
int marginLeft;
int marginRight;
float lineCompression;
EpdFontRenderer<XteinkDisplay>* getFontRenderer(bool bold, bool italic) const;
public:
explicit EpdRenderer(XteinkDisplay* display);
~EpdRenderer() = default;
int getTextWidth(const char* text, bool bold = false, bool italic = false) const;
int getSmallTextWidth(const char* text) const;
void drawText(int x, int y, const char* text, bool bold = false, bool italic = false, uint16_t color = 1) const;
void drawSmallText(int x, int y, const char* text, uint16_t color = 1) const;
void drawTextBox(int x, int y, const std::string& text, int width, int height, bool bold = false,
bool italic = false) const;
void drawLine(int x1, int y1, int x2, int y2, uint16_t color) const;
void drawRect(int x, int y, int width, int height, uint16_t color) const;
void fillRect(int x, int y, int width, int height, uint16_t color) const;
int getTextWidth(const char* text, EpdFontStyle style = REGULAR) const;
int getUiTextWidth(const char* text, EpdFontStyle style = REGULAR) const;
int getSmallTextWidth(const char* text, EpdFontStyle style = REGULAR) const;
void drawText(int x, int y, const char* text, uint16_t color = 1, EpdFontStyle style = REGULAR) const;
void drawUiText(int x, int y, const char* text, uint16_t color = 1, EpdFontStyle style = REGULAR) const;
void drawSmallText(int x, int y, const char* text, uint16_t color = 1, EpdFontStyle style = REGULAR) const;
void drawTextBox(int x, int y, const std::string& text, int width, int height, EpdFontStyle style = REGULAR) const;
void drawLine(int x1, int y1, int x2, int y2, uint16_t color = 1) const;
void drawRect(int x, int y, int width, int height, uint16_t color = 1) const;
void fillRect(int x, int y, int width, int height, uint16_t color = 1) const;
void drawCircle(int x, int y, int radius, uint16_t color = 1) const;
void fillCircle(int x, int y, int radius, uint16_t color = 1) const;
void drawImage(const uint8_t bitmap[], int x, int y, int width, int height, bool invert = false,
bool mirrorY = false) const;
void drawImageNoMargin(const uint8_t bitmap[], int x, int y, int width, int height, bool invert = false,
bool mirrorY = false) const;
void clearScreen(bool black = false) const;
void flushDisplay() const;
void flushDisplay(bool partialUpdate = true) const;
void flushArea(int x, int y, int width, int height) const;
int getPageWidth() const;
@@ -45,12 +49,4 @@ class EpdRenderer {
void setMarginBottom(const int newMarginBottom) { this->marginBottom = newMarginBottom; }
void setMarginLeft(const int newMarginLeft) { this->marginLeft = newMarginLeft; }
void setMarginRight(const int newMarginRight) { this->marginRight = newMarginRight; }
// deep sleep helper - persist any state to disk that may be needed on wake
bool dehydrate();
// deep sleep helper - retrieve any state from disk after wake
bool hydrate();
// really really clear the screen
void reset();
uint8_t temperature = 20;
};
+2 -2
View File
@@ -146,8 +146,8 @@ void EpubHtmlParser::makePages() {
currentPage = new Page();
}
const int lineHeight = renderer->getLineHeight();
const int pageHeight = renderer->getPageHeight();
const int lineHeight = renderer.getLineHeight();
const int pageHeight = renderer.getPageHeight();
// Long running task, make sure to let other things happen
vTaskDelay(1);
+2 -2
View File
@@ -10,7 +10,7 @@ class EpdRenderer;
class EpubHtmlParser final : public tinyxml2::XMLVisitor {
const char* filepath;
EpdRenderer* renderer;
EpdRenderer& renderer;
std::function<void(Page*)> completePageFn;
bool insideBoldTag = false;
@@ -27,7 +27,7 @@ class EpubHtmlParser final : public tinyxml2::XMLVisitor {
bool VisitExit(const tinyxml2::XMLElement& element) override;
// xml parser callbacks
public:
explicit EpubHtmlParser(const char* filepath, EpdRenderer* renderer, const std::function<void(Page*)>& completePageFn)
explicit EpubHtmlParser(const char* filepath, EpdRenderer& renderer, const std::function<void(Page*)>& completePageFn)
: filepath(filepath), renderer(renderer), completePageFn(completePageFn) {}
~EpubHtmlParser() override = default;
bool parseAndBuildPages();
+2 -2
View File
@@ -3,7 +3,7 @@
#include <HardwareSerial.h>
#include <Serialization.h>
void PageLine::render(EpdRenderer* renderer) { block->render(renderer, 0, yPos); }
void PageLine::render(EpdRenderer& renderer) { block->render(renderer, 0, yPos); }
void PageLine::serialize(std::ostream& os) {
serialization::writePod(os, yPos);
@@ -20,7 +20,7 @@ PageLine* PageLine::deserialize(std::istream& is) {
return new PageLine(tb, yPos);
}
void Page::render(EpdRenderer* renderer) const {
void Page::render(EpdRenderer& renderer) const {
const auto start = millis();
for (const auto element : elements) {
element->render(renderer);
+3 -3
View File
@@ -11,7 +11,7 @@ class PageElement {
int yPos;
explicit PageElement(const int yPos) : yPos(yPos) {}
virtual ~PageElement() = default;
virtual void render(EpdRenderer* renderer) = 0;
virtual void render(EpdRenderer& renderer) = 0;
virtual void serialize(std::ostream& os) = 0;
};
@@ -22,7 +22,7 @@ class PageLine final : public PageElement {
public:
PageLine(const TextBlock* block, const int yPos) : PageElement(yPos), block(block) {}
~PageLine() override { delete block; }
void render(EpdRenderer* renderer) override;
void render(EpdRenderer& renderer) override;
void serialize(std::ostream& os) override;
static PageLine* deserialize(std::istream& is);
};
@@ -32,7 +32,7 @@ class Page {
int nextY = 0;
// the list of block index and line numbers on this page
std::vector<PageElement*> elements;
void render(EpdRenderer* renderer) const;
void render(EpdRenderer& renderer) const;
~Page() {
for (const auto element : elements) {
delete element;
+4 -4
View File
@@ -107,11 +107,11 @@ void Section::renderPage() {
delete p;
} else if (pageCount == 0) {
Serial.println("No pages to render");
const int width = renderer->getTextWidth("Empty chapter", true);
renderer->drawText((renderer->getPageWidth() - width) / 2, 300, "Empty chapter", true);
const int width = renderer.getTextWidth("Empty chapter", BOLD);
renderer.drawText((renderer.getPageWidth() - width) / 2, 300, "Empty chapter", 1, BOLD);
} else {
Serial.printf("Page out of bounds: %d (max %d)\n", currentPage, pageCount);
const int width = renderer->getTextWidth("Out of bounds", true);
renderer->drawText((renderer->getPageWidth() - width) / 2, 300, "Out of bounds", true);
const int width = renderer.getTextWidth("Out of bounds", BOLD);
renderer.drawText((renderer.getPageWidth() - width) / 2, 300, "Out of bounds", 1, BOLD);
}
}
+2 -2
View File
@@ -7,7 +7,7 @@ class EpdRenderer;
class Section {
Epub* epub;
const int spineIndex;
EpdRenderer* renderer;
EpdRenderer& renderer;
std::string cachePath;
void onPageComplete(const Page* page);
@@ -16,7 +16,7 @@ class Section {
int pageCount = 0;
int currentPage = 0;
explicit Section(Epub* epub, const int spineIndex, EpdRenderer* renderer)
explicit Section(Epub* epub, const int spineIndex, EpdRenderer& renderer)
: epub(epub), spineIndex(spineIndex), renderer(renderer) {
cachePath = epub->getCachePath() + "/" + std::to_string(spineIndex);
}
+1 -1
View File
@@ -8,7 +8,7 @@ typedef enum { TEXT_BLOCK, IMAGE_BLOCK } BlockType;
class Block {
public:
virtual ~Block() = default;
virtual void layout(EpdRenderer* renderer) = 0;
virtual void layout(EpdRenderer& renderer) = 0;
virtual BlockType getType() = 0;
virtual bool isEmpty() = 0;
virtual void finish() {}
+27 -6
View File
@@ -43,10 +43,10 @@ void TextBlock::addSpan(const std::string& span, const bool is_bold, const bool
}
}
std::list<TextBlock*> TextBlock::splitIntoLines(const EpdRenderer* renderer) {
std::list<TextBlock*> TextBlock::splitIntoLines(const EpdRenderer& renderer) {
const int totalWordCount = words.size();
const int pageWidth = renderer->getPageWidth();
const int spaceWidth = renderer->getSpaceWidth();
const int pageWidth = renderer.getPageWidth();
const int spaceWidth = renderer.getSpaceWidth();
words.shrink_to_fit();
wordStyles.shrink_to_fit();
@@ -56,7 +56,17 @@ std::list<TextBlock*> TextBlock::splitIntoLines(const EpdRenderer* renderer) {
uint16_t wordWidths[totalWordCount];
for (int i = 0; i < words.size(); i++) {
// measure the word
const int width = renderer->getTextWidth(words[i].c_str(), wordStyles[i] & BOLD_SPAN, wordStyles[i] & ITALIC_SPAN);
EpdFontStyle fontStyle = REGULAR;
if (wordStyles[i] & BOLD_SPAN) {
if (wordStyles[i] & ITALIC_SPAN) {
fontStyle = BOLD_ITALIC;
} else {
fontStyle = BOLD;
}
} else if (wordStyles[i] & ITALIC_SPAN) {
fontStyle = ITALIC;
}
const int width = renderer.getTextWidth(words[i].c_str(), fontStyle);
wordWidths[i] = width;
}
@@ -177,12 +187,23 @@ std::list<TextBlock*> TextBlock::splitIntoLines(const EpdRenderer* renderer) {
return lines;
}
void TextBlock::render(const EpdRenderer* renderer, const int x, const int y) const {
void TextBlock::render(const EpdRenderer& renderer, const int x, const int y) const {
for (int i = 0; i < words.size(); i++) {
// get the style
const uint8_t wordStyle = wordStyles[i];
// render the word
renderer->drawText(x + wordXpos[i], y, words[i].c_str(), wordStyle & BOLD_SPAN, wordStyle & ITALIC_SPAN);
EpdFontStyle fontStyle = REGULAR;
if (wordStyles[i] & BOLD_SPAN) {
if (wordStyles[i] & ITALIC_SPAN) {
fontStyle = BOLD_ITALIC;
} else {
fontStyle = BOLD;
}
} else if (wordStyles[i] & ITALIC_SPAN) {
fontStyle = ITALIC;
}
renderer.drawText(x + wordXpos[i], y, words[i].c_str(), 1, fontStyle);
}
}
+3 -3
View File
@@ -40,10 +40,10 @@ class TextBlock final : public Block {
void set_style(const BLOCK_STYLE style) { this->style = style; }
BLOCK_STYLE get_style() const { return style; }
bool isEmpty() override { return words.empty(); }
void layout(EpdRenderer* renderer) override {};
void layout(EpdRenderer& renderer) override {};
// given a renderer works out where to break the words into lines
std::list<TextBlock*> splitIntoLines(const EpdRenderer* renderer);
void render(const EpdRenderer* renderer, int x, int y) const;
std::list<TextBlock*> splitIntoLines(const EpdRenderer& renderer);
void render(const EpdRenderer& renderer, int x, int y) const;
BlockType getType() override { return TEXT_BLOCK; }
void serialize(std::ostream& os) const;
static TextBlock* deserialize(std::istream& is);
+1
View File
@@ -34,3 +34,4 @@ lib_deps =
zinggjm/GxEPD2@^1.6.5
https://github.com/leethomason/tinyxml2.git#11.0.0
BatteryMonitor=symlink://open-x4-sdk/libs/hardware/BatteryMonitor
InputManager=symlink://open-x4-sdk/libs/hardware/InputManager
+13 -24
View File
@@ -9,38 +9,27 @@
constexpr uint8_t STATE_VERSION = 1;
constexpr char STATE_FILE[] = "/sd/.crosspoint/state.bin";
void CrossPointState::serialize(std::ostream& os) const {
serialization::writePod(os, STATE_VERSION);
serialization::writeString(os, openEpubPath);
bool CrossPointState::saveToFile() const {
std::ofstream outputFile(STATE_FILE);
serialization::writePod(outputFile, STATE_VERSION);
serialization::writeString(outputFile, openEpubPath);
outputFile.close();
return true;
}
CrossPointState* CrossPointState::deserialize(std::istream& is) {
const auto state = new CrossPointState();
bool CrossPointState::loadFromFile() {
std::ifstream inputFile(STATE_FILE);
uint8_t version;
serialization::readPod(is, version);
serialization::readPod(inputFile, version);
if (version != STATE_VERSION) {
Serial.printf("CrossPointState: Unknown version %u\n", version);
return state;
inputFile.close();
return false;
}
serialization::readString(is, state->openEpubPath);
return state;
}
serialization::readString(inputFile, openEpubPath);
void CrossPointState::saveToFile() const {
std::ofstream outputFile(STATE_FILE);
serialize(outputFile);
outputFile.close();
}
CrossPointState* CrossPointState::loadFromFile() {
if (!SD.exists(&STATE_FILE[3])) {
return new CrossPointState();
}
std::ifstream inputFile(STATE_FILE);
CrossPointState* state = deserialize(inputFile);
inputFile.close();
return state;
return true;
}

Some files were not shown because too many files have changed in this diff Show More