You've already forked crosspoint-reader
mirror of
https://github.com/crosspoint-reader/crosspoint-reader.git
synced 2026-04-29 10:26:52 -07:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 021f77eab3 | |||
| 6d3d25a288 | |||
| 9a33030623 | |||
| 6414f85257 | |||
| f0d92da8f2 | |||
| 8679c8f57c | |||
| 899caab70c | |||
| 98c8e7e77c | |||
| 7198d943b0 | |||
| 248af4b8fb | |||
| 05a027e2bf | |||
| fa0f27df6a | |||
| 2631613b8d | |||
| 72aa7ba3f6 | |||
| e08bac2e10 | |||
| 12d28e2148 | |||
| 85502b417e | |||
| ddec7f78dd | |||
| 2f9f86b3dd | |||
| 47eb1157ef | |||
| aee239a931 | |||
| 1ee8b728f9 | |||
| 2c80aca7b5 | |||
| 7704772ebe | |||
| 4186c7da9e | |||
| 802c9d0a30 |
@@ -31,7 +31,9 @@ This project is **not affiliated with Xteink**; it's built as a community projec
|
||||
- [x] EPUB parsing and rendering
|
||||
- [x] Saved reading position
|
||||
- [ ] File explorer with file picker
|
||||
- Currently CrossPoint will just open the first EPUB it finds at the root of the SD card
|
||||
- [x] Basic EPUB picker from root directory
|
||||
- [x] Support nested folders
|
||||
- [ ] EPUB picker with cover art
|
||||
- [ ] Image support within EPUB
|
||||
- [ ] Configurable font, layout, and display options
|
||||
- [ ] WiFi connectivity
|
||||
@@ -46,14 +48,37 @@ 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
|
||||
|
||||
Connect your Xteink X4 to your computer via USB-C and run the following command.
|
||||
|
||||
```sh
|
||||
pio run --target upload
|
||||
```
|
||||
|
||||
#### Web
|
||||
|
||||
1. Connect your Xteink X4 to your computer via USB-C
|
||||
2. Download the `firmware.bin` file from the latest release via the [releases page](https://github.com/daveallie/crosspoint-reader/releases)
|
||||
3. Go to https://xteink.dve.al/ and flash the firmware file using the "OTA fast flash controls" section
|
||||
4. Press the reset button on the Xteink X4 to restart the device
|
||||
|
||||
To revert back to the official firmware, you can flash the latest official firmware from https://xteink.dve.al/, or swap
|
||||
back to the other partition using the "Swap boot partition" button here https://xteink.dve.al/debug.
|
||||
|
||||
## Internals
|
||||
|
||||
CrossPoint Reader is pretty aggressive about caching data down to the SD card to minimise RAM usage. The ESP32-C3 only
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
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, GxEPD_BLACK);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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) 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;
|
||||
};
|
||||
|
||||
+19
-16
@@ -3,7 +3,6 @@
|
||||
#include <HardwareSerial.h>
|
||||
#include <SD.h>
|
||||
#include <ZipFile.h>
|
||||
#include <tinyxml2.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
@@ -162,14 +161,14 @@ bool Epub::parseContentOpf(ZipFile& zip, std::string& content_opf_file) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Epub::parseTocNcxFile(ZipFile& zip) {
|
||||
bool Epub::parseTocNcxFile(const ZipFile& zip) {
|
||||
// the ncx file should have been specified in the content.opf file
|
||||
if (tocNcxItem.empty()) {
|
||||
Serial.println("No ncx file specified");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto ncxData = zip.readTextFileToMemory(tocNcxItem.c_str());
|
||||
const auto ncxData = zip.readTextFileToMemory(tocNcxItem.c_str());
|
||||
if (!ncxData) {
|
||||
Serial.printf("Could not find %s\n", tocNcxItem.c_str());
|
||||
return false;
|
||||
@@ -177,7 +176,7 @@ bool Epub::parseTocNcxFile(ZipFile& zip) {
|
||||
|
||||
// Parse the Toc contents
|
||||
tinyxml2::XMLDocument doc;
|
||||
auto result = doc.Parse(ncxData);
|
||||
const auto result = doc.Parse(ncxData);
|
||||
free(ncxData);
|
||||
|
||||
if (result != tinyxml2::XML_SUCCESS) {
|
||||
@@ -185,27 +184,30 @@ bool Epub::parseTocNcxFile(ZipFile& zip) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto ncx = doc.FirstChildElement("ncx");
|
||||
const auto ncx = doc.FirstChildElement("ncx");
|
||||
if (!ncx) {
|
||||
Serial.println("Could not find first child ncx in toc");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto navMap = ncx->FirstChildElement("navMap");
|
||||
const auto navMap = ncx->FirstChildElement("navMap");
|
||||
if (!navMap) {
|
||||
Serial.println("Could not find navMap child in ncx");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto navPoint = navMap->FirstChildElement("navPoint");
|
||||
recursivelyParseNavMap(navMap->FirstChildElement("navPoint"));
|
||||
return true;
|
||||
}
|
||||
|
||||
void Epub::recursivelyParseNavMap(tinyxml2::XMLElement* element) {
|
||||
// Fills toc map
|
||||
while (navPoint) {
|
||||
std::string navTitle = navPoint->FirstChildElement("navLabel")->FirstChildElement("text")->FirstChild()->Value();
|
||||
auto content = navPoint->FirstChildElement("content");
|
||||
while (element) {
|
||||
std::string navTitle = element->FirstChildElement("navLabel")->FirstChildElement("text")->FirstChild()->Value();
|
||||
const auto content = element->FirstChildElement("content");
|
||||
std::string href = contentBasePath + content->Attribute("src");
|
||||
// split the href on the # to get the href and the anchor
|
||||
size_t pos = href.find('#');
|
||||
const size_t pos = href.find('#');
|
||||
std::string anchor;
|
||||
|
||||
if (pos != std::string::npos) {
|
||||
@@ -214,10 +216,13 @@ bool Epub::parseTocNcxFile(ZipFile& zip) {
|
||||
}
|
||||
|
||||
toc.emplace_back(navTitle, href, anchor, 0);
|
||||
navPoint = navPoint->NextSiblingElement("navPoint");
|
||||
}
|
||||
|
||||
return true;
|
||||
tinyxml2::XMLElement* nestedNavPoint = element->FirstChildElement("navPoint");
|
||||
if (nestedNavPoint) {
|
||||
recursivelyParseNavMap(nestedNavPoint);
|
||||
}
|
||||
element = element->NextSiblingElement("navPoint");
|
||||
}
|
||||
}
|
||||
|
||||
// load in the meta data for the epub file
|
||||
@@ -369,9 +374,7 @@ int Epub::getSpineIndexForTocIndex(const int tocIndex) const {
|
||||
int Epub::getTocIndexForSpineIndex(const int spineIndex) const {
|
||||
// the toc entry should have an href that matches the spine item
|
||||
// so we can find the toc index by looking for the href
|
||||
Serial.printf("Looking for %s\n", spine[spineIndex].second.c_str());
|
||||
for (int i = 0; i < toc.size(); i++) {
|
||||
Serial.printf("Looking at %s\n", toc[i].href.c_str());
|
||||
if (toc[i].href == spine[spineIndex].second) {
|
||||
return i;
|
||||
}
|
||||
|
||||
+3
-9
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include <HardwareSerial.h>
|
||||
#include <tinyxml2.h>
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
@@ -38,36 +39,29 @@ class Epub {
|
||||
// find the path for the content.opf file
|
||||
static bool findContentOpfFile(const ZipFile& zip, std::string& contentOpfFile);
|
||||
bool parseContentOpf(ZipFile& zip, std::string& content_opf_file);
|
||||
bool parseTocNcxFile(ZipFile& zip);
|
||||
bool parseTocNcxFile(const ZipFile& zip);
|
||||
void recursivelyParseNavMap(tinyxml2::XMLElement* element);
|
||||
|
||||
public:
|
||||
explicit Epub(std::string filepath, const std::string& cacheDir) : filepath(std::move(filepath)) {
|
||||
// create a cache key based on the filepath
|
||||
|
||||
cachePath = cacheDir + "/epub_" + std::to_string(std::hash<std::string>{}(this->filepath));
|
||||
}
|
||||
~Epub() = default;
|
||||
std::string& getBasePath() { return contentBasePath; }
|
||||
bool load();
|
||||
|
||||
void clearCache() const;
|
||||
|
||||
void setupCacheDir() const;
|
||||
|
||||
const std::string& getCachePath() const;
|
||||
const std::string& getPath() const;
|
||||
const std::string& getTitle() const;
|
||||
const std::string& getCoverImageItem() const;
|
||||
uint8_t* getItemContents(const std::string& itemHref, size_t* size = nullptr) const;
|
||||
char* getTextItemContents(const std::string& itemHref, size_t* size = nullptr) const;
|
||||
|
||||
std::string& getSpineItem(int spineIndex);
|
||||
int getSpineItemsCount() const;
|
||||
|
||||
EpubTocEntry& getTocItem(int tocTndex);
|
||||
int getTocItemsCount() const;
|
||||
// work out the section index for a toc index
|
||||
int getSpineIndexForTocIndex(int tocIndex) const;
|
||||
|
||||
int getTocIndexForSpineIndex(int spineIndex) const;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
-1
Submodule open-x4-sdk updated: be0cb2bb34...8224d278c5
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user