mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2025-12-18 07:07:41 +03:00
Remove EpdRenderer and create new GfxRenderer
This commit is contained in:
parent
2ed8017aa2
commit
b743a1ca8e
@ -1,139 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <EpdFontFamily.h>
|
|
||||||
#include <HardwareSerial.h>
|
|
||||||
#include <Utf8.h>
|
|
||||||
|
|
||||||
inline int min(const int a, const int b) { return a < b ? a : b; }
|
|
||||||
inline int max(const int a, const int b) { return a > b ? a : b; }
|
|
||||||
|
|
||||||
enum EpdFontRendererMode { BW, GRAYSCALE_LSB, GRAYSCALE_MSB };
|
|
||||||
|
|
||||||
template <typename Renderable>
|
|
||||||
class EpdFontRenderer {
|
|
||||||
Renderable& renderer;
|
|
||||||
void renderChar(uint32_t cp, int* x, const int* y, bool pixelState, EpdFontStyle style = REGULAR,
|
|
||||||
EpdFontRendererMode mode = BW);
|
|
||||||
|
|
||||||
public:
|
|
||||||
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, bool pixelState = true, EpdFontStyle style = REGULAR,
|
|
||||||
EpdFontRendererMode mode = BW);
|
|
||||||
void drawPixel(int x, int y, bool pixelState);
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename Renderable>
|
|
||||||
void EpdFontRenderer<Renderable>::renderString(const char* string, int* x, int* y, const bool pixelState,
|
|
||||||
const EpdFontStyle style, const EpdFontRendererMode mode) {
|
|
||||||
// cannot draw a NULL / empty string
|
|
||||||
if (string == nullptr || *string == '\0') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// no printable characters
|
|
||||||
if (!fontFamily->hasPrintableChars(string, style)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t cp;
|
|
||||||
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&string)))) {
|
|
||||||
renderChar(cp, x, y, pixelState, style, mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
*y += fontFamily->getData(style)->advanceY;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Consolidate this with EpdRenderer implementation
|
|
||||||
template <typename Renderable>
|
|
||||||
void EpdFontRenderer<Renderable>::drawPixel(const int x, const int y, const bool pixelState) {
|
|
||||||
uint8_t* frameBuffer = renderer.getFrameBuffer();
|
|
||||||
|
|
||||||
// Early return if no framebuffer is set
|
|
||||||
if (!frameBuffer) {
|
|
||||||
Serial.printf("!!No framebuffer\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bounds checking (portrait: 480x800)
|
|
||||||
if (x < 0 || x >= EInkDisplay::DISPLAY_HEIGHT || y < 0 || y >= EInkDisplay::DISPLAY_WIDTH) {
|
|
||||||
Serial.printf("!!Outside range (%d, %d)\n", x, y);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rotate coordinates: portrait (480x800) -> landscape (800x480)
|
|
||||||
// Rotation: 90 degrees clockwise
|
|
||||||
const int16_t rotatedX = y;
|
|
||||||
const int16_t rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x;
|
|
||||||
|
|
||||||
// Calculate byte position and bit position
|
|
||||||
const uint16_t byteIndex = rotatedY * EInkDisplay::DISPLAY_WIDTH_BYTES + (rotatedX / 8);
|
|
||||||
const uint8_t bitPosition = 7 - (rotatedX % 8); // MSB first
|
|
||||||
|
|
||||||
// Set or clear the bit
|
|
||||||
if (pixelState) {
|
|
||||||
frameBuffer[byteIndex] &= ~(1 << bitPosition); // Clear bit
|
|
||||||
} else {
|
|
||||||
frameBuffer[byteIndex] |= (1 << bitPosition); // Set bit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Renderable>
|
|
||||||
void EpdFontRenderer<Renderable>::renderChar(const uint32_t cp, int* x, const int* y, const bool pixelState,
|
|
||||||
const EpdFontStyle style, const EpdFontRendererMode mode) {
|
|
||||||
const EpdGlyph* glyph = fontFamily->getGlyph(cp, style);
|
|
||||||
if (!glyph) {
|
|
||||||
// TODO: Replace with fallback glyph property?
|
|
||||||
glyph = fontFamily->getGlyph('?', style);
|
|
||||||
}
|
|
||||||
|
|
||||||
// no glyph?
|
|
||||||
if (!glyph) {
|
|
||||||
Serial.printf("No glyph for codepoint %d\n", cp);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int is2Bit = fontFamily->getData(style)->is2Bit;
|
|
||||||
const uint32_t offset = glyph->dataOffset;
|
|
||||||
const uint8_t width = glyph->width;
|
|
||||||
const uint8_t height = glyph->height;
|
|
||||||
const int left = glyph->left;
|
|
||||||
|
|
||||||
const uint8_t* bitmap = nullptr;
|
|
||||||
bitmap = &fontFamily->getData(style)->bitmap[offset];
|
|
||||||
|
|
||||||
if (bitmap != nullptr) {
|
|
||||||
for (int glyphY = 0; glyphY < height; glyphY++) {
|
|
||||||
int screenY = *y - glyph->top + glyphY;
|
|
||||||
for (int glyphX = 0; glyphX < width; glyphX++) {
|
|
||||||
const int pixelPosition = glyphY * width + glyphX;
|
|
||||||
int screenX = *x + left + glyphX;
|
|
||||||
|
|
||||||
if (is2Bit) {
|
|
||||||
const uint8_t byte = bitmap[pixelPosition / 4];
|
|
||||||
const uint8_t bit_index = (3 - pixelPosition % 4) * 2;
|
|
||||||
|
|
||||||
const uint8_t val = (byte >> bit_index) & 0x3;
|
|
||||||
if (mode == BW && val > 0) {
|
|
||||||
drawPixel(screenX, screenY, pixelState);
|
|
||||||
} else if (mode == GRAYSCALE_MSB && val == 1) {
|
|
||||||
// TODO: Not sure how this anti-aliasing goes on black backgrounds
|
|
||||||
drawPixel(screenX, screenY, false);
|
|
||||||
} else if (mode == GRAYSCALE_LSB && val == 2) {
|
|
||||||
drawPixel(screenX, screenY, false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const uint8_t byte = bitmap[pixelPosition / 8];
|
|
||||||
const uint8_t bit_index = 7 - (pixelPosition % 8);
|
|
||||||
|
|
||||||
if ((byte >> bit_index) & 1) {
|
|
||||||
drawPixel(screenX, screenY, pixelState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*x += glyph->advanceX;
|
|
||||||
}
|
|
||||||
@ -1,242 +0,0 @@
|
|||||||
#include "EpdRenderer.h"
|
|
||||||
|
|
||||||
#include "builtinFonts/babyblue.h"
|
|
||||||
#include "builtinFonts/bookerly_2b.h"
|
|
||||||
#include "builtinFonts/bookerly_bold_2b.h"
|
|
||||||
#include "builtinFonts/bookerly_bold_italic_2b.h"
|
|
||||||
#include "builtinFonts/bookerly_italic_2b.h"
|
|
||||||
#include "builtinFonts/ubuntu_10.h"
|
|
||||||
#include "builtinFonts/ubuntu_bold_10.h"
|
|
||||||
|
|
||||||
EpdFont bookerlyFont(&bookerly_2b);
|
|
||||||
EpdFont bookerlyBoldFont(&bookerly_bold_2b);
|
|
||||||
EpdFont bookerlyItalicFont(&bookerly_italic_2b);
|
|
||||||
EpdFont bookerlyBoldItalicFont(&bookerly_bold_italic_2b);
|
|
||||||
EpdFontFamily bookerlyFontFamily(&bookerlyFont, &bookerlyBoldFont, &bookerlyItalicFont, &bookerlyBoldItalicFont);
|
|
||||||
|
|
||||||
EpdFont smallFont(&babyblue);
|
|
||||||
EpdFontFamily smallFontFamily(&smallFont);
|
|
||||||
|
|
||||||
EpdFont ubuntu10Font(&ubuntu_10);
|
|
||||||
EpdFont ununtuBold10Font(&ubuntu_bold_10);
|
|
||||||
EpdFontFamily ubuntuFontFamily(&ubuntu10Font, &ununtuBold10Font);
|
|
||||||
|
|
||||||
EpdRenderer::EpdRenderer(EInkDisplay& einkDisplay)
|
|
||||||
: einkDisplay(einkDisplay),
|
|
||||||
marginTop(11),
|
|
||||||
marginBottom(30),
|
|
||||||
marginLeft(10),
|
|
||||||
marginRight(10),
|
|
||||||
fontRendererMode(BW),
|
|
||||||
lineCompression(0.95f) {
|
|
||||||
this->regularFontRenderer = new EpdFontRenderer<EInkDisplay>(&bookerlyFontFamily, einkDisplay);
|
|
||||||
this->smallFontRenderer = new EpdFontRenderer<EInkDisplay>(&smallFontFamily, einkDisplay);
|
|
||||||
this->uiFontRenderer = new EpdFontRenderer<EInkDisplay>(&ubuntuFontFamily, einkDisplay);
|
|
||||||
}
|
|
||||||
|
|
||||||
EpdRenderer::~EpdRenderer() {
|
|
||||||
delete regularFontRenderer;
|
|
||||||
delete smallFontRenderer;
|
|
||||||
delete uiFontRenderer;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EpdRenderer::drawPixel(const int x, const int y, const bool state) const {
|
|
||||||
uint8_t* frameBuffer = einkDisplay.getFrameBuffer();
|
|
||||||
|
|
||||||
// Early return if no framebuffer is set
|
|
||||||
if (!frameBuffer) {
|
|
||||||
Serial.printf("!!No framebuffer\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int adjX = x + marginLeft;
|
|
||||||
const int adjY = y + marginTop;
|
|
||||||
|
|
||||||
// Bounds checking (portrait: 480x800)
|
|
||||||
if (adjX < 0 || adjX >= EInkDisplay::DISPLAY_HEIGHT || adjY < 0 || adjY >= EInkDisplay::DISPLAY_WIDTH) {
|
|
||||||
Serial.printf("!!Outside range (%d, %d)\n", adjX, adjY);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rotate coordinates: portrait (480x800) -> landscape (800x480)
|
|
||||||
// Rotation: 90 degrees clockwise
|
|
||||||
const int rotatedX = adjY;
|
|
||||||
const int rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - adjX;
|
|
||||||
|
|
||||||
// Calculate byte position and bit position
|
|
||||||
const uint16_t byteIndex = rotatedY * EInkDisplay::DISPLAY_WIDTH_BYTES + (rotatedX / 8);
|
|
||||||
const uint8_t bitPosition = 7 - (rotatedX % 8); // MSB first
|
|
||||||
|
|
||||||
if (state) {
|
|
||||||
frameBuffer[byteIndex] &= ~(1 << bitPosition); // Clear bit
|
|
||||||
} else {
|
|
||||||
frameBuffer[byteIndex] |= 1 << bitPosition; // Set bit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int EpdRenderer::getTextWidth(const char* text, const EpdFontStyle style) const {
|
|
||||||
int w = 0, h = 0;
|
|
||||||
|
|
||||||
regularFontRenderer->fontFamily->getTextDimensions(text, &w, &h, style);
|
|
||||||
|
|
||||||
return w;
|
|
||||||
}
|
|
||||||
|
|
||||||
int EpdRenderer::getUiTextWidth(const char* text, const EpdFontStyle style) const {
|
|
||||||
int w = 0, h = 0;
|
|
||||||
|
|
||||||
uiFontRenderer->fontFamily->getTextDimensions(text, &w, &h, style);
|
|
||||||
|
|
||||||
return w;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 bool state,
|
|
||||||
const EpdFontStyle style) const {
|
|
||||||
int ypos = y + getLineHeight() + marginTop;
|
|
||||||
int xpos = x + marginLeft;
|
|
||||||
regularFontRenderer->renderString(text, &xpos, &ypos, state, style, fontRendererMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EpdRenderer::drawUiText(const int x, const int y, const char* text, const bool state,
|
|
||||||
const EpdFontStyle style) const {
|
|
||||||
int ypos = y + uiFontRenderer->fontFamily->getData(style)->advanceY + marginTop;
|
|
||||||
int xpos = x + marginLeft;
|
|
||||||
uiFontRenderer->renderString(text, &xpos, &ypos, state, style, fontRendererMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EpdRenderer::drawSmallText(const int x, const int y, const char* text, const bool state,
|
|
||||||
const EpdFontStyle style) const {
|
|
||||||
int ypos = y + smallFontRenderer->fontFamily->getData(style)->advanceY + marginTop;
|
|
||||||
int xpos = x + marginLeft;
|
|
||||||
smallFontRenderer->renderString(text, &xpos, &ypos, state, style, fontRendererMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EpdRenderer::drawTextBox(const int x, const int y, const std::string& text, const int width, const int height,
|
|
||||||
const EpdFontStyle style) const {
|
|
||||||
const size_t length = text.length();
|
|
||||||
// fit the text into the box
|
|
||||||
int start = 0;
|
|
||||||
int end = 1;
|
|
||||||
int ypos = 0;
|
|
||||||
while (true) {
|
|
||||||
if (end >= length) {
|
|
||||||
drawText(x, y + ypos, text.substr(start, length - start).c_str(), true, style);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ypos + getLineHeight() >= height) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (text[end - 1] == '\n') {
|
|
||||||
drawText(x, y + ypos, text.substr(start, end - start).c_str(), true, style);
|
|
||||||
ypos += getLineHeight();
|
|
||||||
start = end;
|
|
||||||
end = start + 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getTextWidth(text.substr(start, end - start).c_str(), style) > width) {
|
|
||||||
drawText(x, y + ypos, text.substr(start, end - start - 1).c_str(), true, style);
|
|
||||||
ypos += getLineHeight();
|
|
||||||
start = end - 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
end++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EpdRenderer::drawLine(int x1, int y1, int x2, int y2, const bool state) const {
|
|
||||||
if (x1 == x2) {
|
|
||||||
if (y2 < y1) {
|
|
||||||
std::swap(y1, y2);
|
|
||||||
}
|
|
||||||
for (int y = y1; y <= y2; y++) {
|
|
||||||
drawPixel(x1, y, state);
|
|
||||||
}
|
|
||||||
} else if (y1 == y2) {
|
|
||||||
if (x2 < x1) {
|
|
||||||
std::swap(x1, x2);
|
|
||||||
}
|
|
||||||
for (int x = x1; x <= x2; x++) {
|
|
||||||
drawPixel(x, y1, state);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// TODO: Implement
|
|
||||||
Serial.println("Line drawing not supported");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EpdRenderer::drawRect(const int x, const int y, const int width, const int height, const bool state) const {
|
|
||||||
drawLine(x, y, x + width - 1, y, state);
|
|
||||||
drawLine(x + width - 1, y, x + width - 1, y + height - 1, state);
|
|
||||||
drawLine(x + width - 1, y + height - 1, x, y + height - 1, state);
|
|
||||||
drawLine(x, y, x, y + height - 1, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EpdRenderer::fillRect(const int x, const int y, const int width, const int height, const bool state) const {
|
|
||||||
for (int fillY = y; fillY < y + height; fillY++) {
|
|
||||||
drawLine(x, fillY, x + width - 1, fillY, state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EpdRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const {
|
|
||||||
drawImageNoMargin(bitmap, x + marginLeft, y + marginTop, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Support y-mirror?
|
|
||||||
void EpdRenderer::drawImageNoMargin(const uint8_t bitmap[], const int x, const int y, const int width,
|
|
||||||
const int height) const {
|
|
||||||
einkDisplay.drawImage(bitmap, x, y, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EpdRenderer::clearScreen(const uint8_t color) const {
|
|
||||||
Serial.println("Clearing screen");
|
|
||||||
einkDisplay.clearScreen(color);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EpdRenderer::invertScreen() const {
|
|
||||||
uint8_t *buffer = einkDisplay.getFrameBuffer();
|
|
||||||
for (int i = 0; i < EInkDisplay::BUFFER_SIZE; i++) {
|
|
||||||
buffer[i] = ~buffer[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EpdRenderer::flushDisplay(const EInkDisplay::RefreshMode refreshMode) const {
|
|
||||||
einkDisplay.displayBuffer(refreshMode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Support partial window update
|
|
||||||
// void EpdRenderer::flushArea(const int x, const int y, const int width, const int height) const {
|
|
||||||
// const int rotatedX = y;
|
|
||||||
// const int rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x;
|
|
||||||
//
|
|
||||||
// einkDisplay.displayBuffer(EInkDisplay::FAST_REFRESH, rotatedX, rotatedY, height, width);
|
|
||||||
// }
|
|
||||||
|
|
||||||
int EpdRenderer::getPageWidth() const { return EInkDisplay::DISPLAY_HEIGHT - marginLeft - marginRight; }
|
|
||||||
|
|
||||||
int EpdRenderer::getPageHeight() const { return EInkDisplay::DISPLAY_WIDTH - marginTop - marginBottom; }
|
|
||||||
|
|
||||||
int EpdRenderer::getSpaceWidth() const { return regularFontRenderer->fontFamily->getGlyph(' ', REGULAR)->advanceX; }
|
|
||||||
|
|
||||||
int EpdRenderer::getLineHeight() const {
|
|
||||||
return regularFontRenderer->fontFamily->getData(REGULAR)->advanceY * lineCompression;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EpdRenderer::swapBuffers() const { einkDisplay.swapBuffers(); }
|
|
||||||
|
|
||||||
void EpdRenderer::copyGrayscaleLsbBuffers() const { einkDisplay.copyGrayscaleLsbBuffers(einkDisplay.getFrameBuffer()); }
|
|
||||||
|
|
||||||
void EpdRenderer::copyGrayscaleMsbBuffers() const { einkDisplay.copyGrayscaleMsbBuffers(einkDisplay.getFrameBuffer()); }
|
|
||||||
|
|
||||||
void EpdRenderer::displayGrayBuffer() const { einkDisplay.displayGrayBuffer(); }
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <EInkDisplay.h>
|
|
||||||
|
|
||||||
#include <EpdFontRenderer.hpp>
|
|
||||||
|
|
||||||
class EpdRenderer {
|
|
||||||
EInkDisplay& einkDisplay;
|
|
||||||
EpdFontRenderer<EInkDisplay>* regularFontRenderer;
|
|
||||||
EpdFontRenderer<EInkDisplay>* smallFontRenderer;
|
|
||||||
EpdFontRenderer<EInkDisplay>* uiFontRenderer;
|
|
||||||
int marginTop;
|
|
||||||
int marginBottom;
|
|
||||||
int marginLeft;
|
|
||||||
int marginRight;
|
|
||||||
EpdFontRendererMode fontRendererMode;
|
|
||||||
float lineCompression;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit EpdRenderer(EInkDisplay& einkDisplay);
|
|
||||||
~EpdRenderer();
|
|
||||||
void drawPixel(int x, int y, bool state = true) 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, bool state = true, EpdFontStyle style = REGULAR) const;
|
|
||||||
void drawUiText(int x, int y, const char* text, bool state = true, EpdFontStyle style = REGULAR) const;
|
|
||||||
void drawSmallText(int x, int y, const char* text, bool state = true, 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, bool state = true) const;
|
|
||||||
void drawRect(int x, int y, int width, int height, bool state = true) const;
|
|
||||||
void fillRect(int x, int y, int width, int height, bool state = true) const;
|
|
||||||
void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const;
|
|
||||||
void drawImageNoMargin(const uint8_t bitmap[], int x, int y, int width, int height) const;
|
|
||||||
|
|
||||||
void swapBuffers() const;
|
|
||||||
void copyGrayscaleLsbBuffers() const;
|
|
||||||
void copyGrayscaleMsbBuffers() const;
|
|
||||||
void displayGrayBuffer() const;
|
|
||||||
void clearScreen(uint8_t color = 0xFF) const;
|
|
||||||
|
|
||||||
void invertScreen() const;
|
|
||||||
|
|
||||||
void flushDisplay(EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH) const;
|
|
||||||
// void flushArea(int x, int y, int width, int height) const;
|
|
||||||
|
|
||||||
int getPageWidth() const;
|
|
||||||
int getPageHeight() const;
|
|
||||||
int getSpaceWidth() const;
|
|
||||||
int getLineHeight() const;
|
|
||||||
|
|
||||||
// set margins
|
|
||||||
void setMarginTop(const int newMarginTop) { this->marginTop = newMarginTop; }
|
|
||||||
void setMarginBottom(const int newMarginBottom) { this->marginBottom = newMarginBottom; }
|
|
||||||
void setMarginLeft(const int newMarginLeft) { this->marginLeft = newMarginLeft; }
|
|
||||||
void setMarginRight(const int newMarginRight) { this->marginRight = newMarginRight; }
|
|
||||||
void setFontRendererMode(const EpdFontRendererMode mode) { this->fontRendererMode = mode; }
|
|
||||||
};
|
|
||||||
@ -1,6 +1,6 @@
|
|||||||
#include "EpubHtmlParserSlim.h"
|
#include "EpubHtmlParserSlim.h"
|
||||||
|
|
||||||
#include <EpdRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
#include <expat.h>
|
#include <expat.h>
|
||||||
|
|
||||||
@ -133,7 +133,7 @@ void XMLCALL EpubHtmlParserSlim::characterData(void* userData, const XML_Char* s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we're about to run out of space, then cut the word off and start a new one
|
// If we're about to run out of space, then cut the word off and start a new one
|
||||||
if (self->partWordBufferIndex >= PART_WORD_BUFFER_SIZE - 2) {
|
if (self->partWordBufferIndex >= MAX_WORD_SIZE) {
|
||||||
self->partWordBuffer[self->partWordBufferIndex] = '\0';
|
self->partWordBuffer[self->partWordBufferIndex] = '\0';
|
||||||
self->currentTextBlock->addWord(replaceHtmlEntities(self->partWordBuffer), self->boldUntilDepth < self->depth,
|
self->currentTextBlock->addWord(replaceHtmlEntities(self->partWordBuffer), self->boldUntilDepth < self->depth,
|
||||||
self->italicUntilDepth < self->depth);
|
self->italicUntilDepth < self->depth);
|
||||||
@ -257,28 +257,30 @@ void EpubHtmlParserSlim::makePages() {
|
|||||||
|
|
||||||
if (!currentPage) {
|
if (!currentPage) {
|
||||||
currentPage = new Page();
|
currentPage = new Page();
|
||||||
|
currentPageNextY = marginTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int lineHeight = renderer.getLineHeight();
|
const int lineHeight = renderer.getLineHeight(fontId) * lineCompression;
|
||||||
const int pageHeight = renderer.getPageHeight();
|
const int pageHeight = GfxRenderer::getScreenHeight() - marginTop - marginBottom;
|
||||||
|
|
||||||
// Long running task, make sure to let other things happen
|
// Long running task, make sure to let other things happen
|
||||||
vTaskDelay(1);
|
vTaskDelay(1);
|
||||||
|
|
||||||
if (currentTextBlock->getType() == TEXT_BLOCK) {
|
if (currentTextBlock->getType() == TEXT_BLOCK) {
|
||||||
const auto lines = currentTextBlock->splitIntoLines(renderer);
|
const auto lines = currentTextBlock->splitIntoLines(renderer, fontId, marginLeft + marginRight);
|
||||||
|
|
||||||
for (const auto line : lines) {
|
for (const auto line : lines) {
|
||||||
if (currentPage->nextY + lineHeight > pageHeight) {
|
if (currentPageNextY + lineHeight > pageHeight) {
|
||||||
completePageFn(currentPage);
|
completePageFn(currentPage);
|
||||||
currentPage = new Page();
|
currentPage = new Page();
|
||||||
|
currentPageNextY = marginTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPage->elements.push_back(new PageLine(line, currentPage->nextY));
|
currentPage->elements.push_back(new PageLine(line, marginLeft, currentPageNextY));
|
||||||
currentPage->nextY += lineHeight;
|
currentPageNextY += lineHeight;
|
||||||
}
|
}
|
||||||
// add some extra line between blocks
|
// add some extra line between blocks
|
||||||
currentPage->nextY += lineHeight / 2;
|
currentPageNextY += lineHeight / 2;
|
||||||
}
|
}
|
||||||
// TODO: Image block support
|
// TODO: Image block support
|
||||||
// if (block->getType() == BlockType::IMAGE_BLOCK) {
|
// if (block->getType() == BlockType::IMAGE_BLOCK) {
|
||||||
|
|||||||
@ -1,30 +1,38 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <expat.h>
|
#include <expat.h>
|
||||||
#include <limits.h>
|
|
||||||
|
|
||||||
|
#include <climits>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include "blocks/TextBlock.h"
|
#include "blocks/TextBlock.h"
|
||||||
|
|
||||||
class Page;
|
class Page;
|
||||||
class EpdRenderer;
|
class GfxRenderer;
|
||||||
|
|
||||||
#define PART_WORD_BUFFER_SIZE 200
|
#define MAX_WORD_SIZE 200
|
||||||
|
|
||||||
class EpubHtmlParserSlim {
|
class EpubHtmlParserSlim {
|
||||||
const char* filepath;
|
const char* filepath;
|
||||||
EpdRenderer& renderer;
|
GfxRenderer& renderer;
|
||||||
std::function<void(Page*)> completePageFn;
|
std::function<void(Page*)> completePageFn;
|
||||||
int depth = 0;
|
int depth = 0;
|
||||||
int skipUntilDepth = INT_MAX;
|
int skipUntilDepth = INT_MAX;
|
||||||
int boldUntilDepth = INT_MAX;
|
int boldUntilDepth = INT_MAX;
|
||||||
int italicUntilDepth = INT_MAX;
|
int italicUntilDepth = INT_MAX;
|
||||||
// If we encounter words longer than this, but this is pretty large
|
// buffer for building up words from characters, will auto break if longer than this
|
||||||
char partWordBuffer[PART_WORD_BUFFER_SIZE] = {};
|
// leave one char at end for null pointer
|
||||||
|
char partWordBuffer[MAX_WORD_SIZE + 1] = {};
|
||||||
int partWordBufferIndex = 0;
|
int partWordBufferIndex = 0;
|
||||||
TextBlock* currentTextBlock = nullptr;
|
TextBlock* currentTextBlock = nullptr;
|
||||||
Page* currentPage = nullptr;
|
Page* currentPage = nullptr;
|
||||||
|
int currentPageNextY = 0;
|
||||||
|
int fontId;
|
||||||
|
float lineCompression;
|
||||||
|
int marginTop;
|
||||||
|
int marginRight;
|
||||||
|
int marginBottom;
|
||||||
|
int marginLeft;
|
||||||
|
|
||||||
void startNewTextBlock(BLOCK_STYLE style);
|
void startNewTextBlock(BLOCK_STYLE style);
|
||||||
void makePages();
|
void makePages();
|
||||||
@ -34,9 +42,19 @@ class EpubHtmlParserSlim {
|
|||||||
static void XMLCALL endElement(void* userData, const XML_Char* name);
|
static void XMLCALL endElement(void* userData, const XML_Char* name);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit EpubHtmlParserSlim(const char* filepath, EpdRenderer& renderer,
|
explicit EpubHtmlParserSlim(const char* filepath, GfxRenderer& renderer, const int fontId,
|
||||||
|
const float lineCompression, const int marginTop, const int marginRight,
|
||||||
|
const int marginBottom, const int marginLeft,
|
||||||
const std::function<void(Page*)>& completePageFn)
|
const std::function<void(Page*)>& completePageFn)
|
||||||
: filepath(filepath), renderer(renderer), completePageFn(completePageFn) {}
|
: filepath(filepath),
|
||||||
|
renderer(renderer),
|
||||||
|
fontId(fontId),
|
||||||
|
lineCompression(lineCompression),
|
||||||
|
marginTop(marginTop),
|
||||||
|
marginRight(marginRight),
|
||||||
|
marginBottom(marginBottom),
|
||||||
|
marginLeft(marginLeft),
|
||||||
|
completePageFn(completePageFn) {}
|
||||||
~EpubHtmlParserSlim() = default;
|
~EpubHtmlParserSlim() = default;
|
||||||
bool parseAndBuildPages();
|
bool parseAndBuildPages();
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,9 +3,12 @@
|
|||||||
#include <HardwareSerial.h>
|
#include <HardwareSerial.h>
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
void PageLine::render(EpdRenderer& renderer) { block->render(renderer, 0, yPos); }
|
constexpr uint8_t PAGE_FILE_VERSION = 1;
|
||||||
|
|
||||||
|
void PageLine::render(GfxRenderer& renderer, const int fontId) { block->render(renderer, fontId, xPos, yPos); }
|
||||||
|
|
||||||
void PageLine::serialize(std::ostream& os) {
|
void PageLine::serialize(std::ostream& os) {
|
||||||
|
serialization::writePod(os, xPos);
|
||||||
serialization::writePod(os, yPos);
|
serialization::writePod(os, yPos);
|
||||||
|
|
||||||
// serialize TextBlock pointed to by PageLine
|
// serialize TextBlock pointed to by PageLine
|
||||||
@ -13,23 +16,25 @@ void PageLine::serialize(std::ostream& os) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PageLine* PageLine::deserialize(std::istream& is) {
|
PageLine* PageLine::deserialize(std::istream& is) {
|
||||||
|
int32_t xPos;
|
||||||
int32_t yPos;
|
int32_t yPos;
|
||||||
|
serialization::readPod(is, xPos);
|
||||||
serialization::readPod(is, yPos);
|
serialization::readPod(is, yPos);
|
||||||
|
|
||||||
const auto tb = TextBlock::deserialize(is);
|
const auto tb = TextBlock::deserialize(is);
|
||||||
return new PageLine(tb, yPos);
|
return new PageLine(tb, xPos, yPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Page::render(EpdRenderer& renderer) const {
|
void Page::render(GfxRenderer& renderer, const int fontId) const {
|
||||||
const auto start = millis();
|
const auto start = millis();
|
||||||
for (const auto element : elements) {
|
for (const auto element : elements) {
|
||||||
element->render(renderer);
|
element->render(renderer, fontId);
|
||||||
}
|
}
|
||||||
Serial.printf("Rendered page elements (%u) in %dms\n", elements.size(), millis() - start);
|
Serial.printf("Rendered page elements (%u) in %dms\n", elements.size(), millis() - start);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Page::serialize(std::ostream& os) const {
|
void Page::serialize(std::ostream& os) const {
|
||||||
serialization::writePod(os, nextY);
|
serialization::writePod(os, PAGE_FILE_VERSION);
|
||||||
|
|
||||||
const uint32_t count = elements.size();
|
const uint32_t count = elements.size();
|
||||||
serialization::writePod(os, count);
|
serialization::writePod(os, count);
|
||||||
@ -42,9 +47,14 @@ void Page::serialize(std::ostream& os) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Page* Page::deserialize(std::istream& is) {
|
Page* Page::deserialize(std::istream& is) {
|
||||||
auto* page = new Page();
|
uint8_t version;
|
||||||
|
serialization::readPod(is, version);
|
||||||
|
if (version != PAGE_FILE_VERSION) {
|
||||||
|
Serial.printf("Page: Unknown version %u\n", version);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
serialization::readPod(is, page->nextY);
|
auto* page = new Page();
|
||||||
|
|
||||||
uint32_t count;
|
uint32_t count;
|
||||||
serialization::readPod(is, count);
|
serialization::readPod(is, count);
|
||||||
|
|||||||
@ -8,10 +8,11 @@ enum PageElementTag : uint8_t {
|
|||||||
// represents something that has been added to a page
|
// represents something that has been added to a page
|
||||||
class PageElement {
|
class PageElement {
|
||||||
public:
|
public:
|
||||||
|
int xPos;
|
||||||
int yPos;
|
int yPos;
|
||||||
explicit PageElement(const int yPos) : yPos(yPos) {}
|
explicit PageElement(const int xPos, const int yPos) : xPos(xPos), yPos(yPos) {}
|
||||||
virtual ~PageElement() = default;
|
virtual ~PageElement() = default;
|
||||||
virtual void render(EpdRenderer& renderer) = 0;
|
virtual void render(GfxRenderer& renderer, int fontId) = 0;
|
||||||
virtual void serialize(std::ostream& os) = 0;
|
virtual void serialize(std::ostream& os) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -20,24 +21,24 @@ class PageLine final : public PageElement {
|
|||||||
const TextBlock* block;
|
const TextBlock* block;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PageLine(const TextBlock* block, const int yPos) : PageElement(yPos), block(block) {}
|
PageLine(const TextBlock* block, const int xPos, const int yPos) : PageElement(xPos, yPos), block(block) {}
|
||||||
~PageLine() override { delete block; }
|
~PageLine() override { delete block; }
|
||||||
void render(EpdRenderer& renderer) override;
|
void render(GfxRenderer& renderer, int fontId) override;
|
||||||
void serialize(std::ostream& os) override;
|
void serialize(std::ostream& os) override;
|
||||||
static PageLine* deserialize(std::istream& is);
|
static PageLine* deserialize(std::istream& is);
|
||||||
};
|
};
|
||||||
|
|
||||||
class Page {
|
class Page {
|
||||||
public:
|
public:
|
||||||
int nextY = 0;
|
|
||||||
// the list of block index and line numbers on this page
|
|
||||||
std::vector<PageElement*> elements;
|
|
||||||
void render(EpdRenderer& renderer) const;
|
|
||||||
~Page() {
|
~Page() {
|
||||||
for (const auto element : elements) {
|
for (const auto element : elements) {
|
||||||
delete element;
|
delete element;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the list of block index and line numbers on this page
|
||||||
|
std::vector<PageElement*> elements;
|
||||||
|
void render(GfxRenderer& renderer, int fontId) const;
|
||||||
void serialize(std::ostream& os) const;
|
void serialize(std::ostream& os) const;
|
||||||
static Page* deserialize(std::istream& is);
|
static Page* deserialize(std::istream& is);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
#include "Section.h"
|
#include "Section.h"
|
||||||
|
|
||||||
#include <EpdRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <SD.h>
|
#include <SD.h>
|
||||||
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
@ -9,7 +9,7 @@
|
|||||||
#include "Page.h"
|
#include "Page.h"
|
||||||
#include "Serialization.h"
|
#include "Serialization.h"
|
||||||
|
|
||||||
constexpr uint8_t SECTION_FILE_VERSION = 2;
|
constexpr uint8_t SECTION_FILE_VERSION = 3;
|
||||||
|
|
||||||
void Section::onPageComplete(const Page* page) {
|
void Section::onPageComplete(const Page* page) {
|
||||||
Serial.printf("Page %d complete - free mem: %lu\n", pageCount, ESP.getFreeHeap());
|
Serial.printf("Page %d complete - free mem: %lu\n", pageCount, ESP.getFreeHeap());
|
||||||
@ -24,14 +24,22 @@ void Section::onPageComplete(const Page* page) {
|
|||||||
delete page;
|
delete page;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Section::writeCacheMetadata() const {
|
void Section::writeCacheMetadata(const int fontId, const float lineCompression, const int marginTop,
|
||||||
|
const int marginRight, const int marginBottom, const int marginLeft) const {
|
||||||
std::ofstream outputFile(("/sd" + cachePath + "/section.bin").c_str());
|
std::ofstream outputFile(("/sd" + cachePath + "/section.bin").c_str());
|
||||||
serialization::writePod(outputFile, SECTION_FILE_VERSION);
|
serialization::writePod(outputFile, SECTION_FILE_VERSION);
|
||||||
|
serialization::writePod(outputFile, fontId);
|
||||||
|
serialization::writePod(outputFile, lineCompression);
|
||||||
|
serialization::writePod(outputFile, marginTop);
|
||||||
|
serialization::writePod(outputFile, marginRight);
|
||||||
|
serialization::writePod(outputFile, marginBottom);
|
||||||
|
serialization::writePod(outputFile, marginLeft);
|
||||||
serialization::writePod(outputFile, pageCount);
|
serialization::writePod(outputFile, pageCount);
|
||||||
outputFile.close();
|
outputFile.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Section::loadCacheMetadata() {
|
bool Section::loadCacheMetadata(const int fontId, const float lineCompression, const int marginTop,
|
||||||
|
const int marginRight, const int marginBottom, const int marginLeft) {
|
||||||
if (!SD.exists(cachePath.c_str())) {
|
if (!SD.exists(cachePath.c_str())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -42,14 +50,36 @@ bool Section::loadCacheMetadata() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::ifstream inputFile(("/sd" + sectionFilePath).c_str());
|
std::ifstream inputFile(("/sd" + sectionFilePath).c_str());
|
||||||
uint8_t version;
|
|
||||||
serialization::readPod(inputFile, version);
|
// Match parameters
|
||||||
if (version != SECTION_FILE_VERSION) {
|
{
|
||||||
inputFile.close();
|
uint8_t version;
|
||||||
SD.remove(sectionFilePath.c_str());
|
serialization::readPod(inputFile, version);
|
||||||
Serial.printf("Section state file: Unknown version %u\n", version);
|
if (version != SECTION_FILE_VERSION) {
|
||||||
return false;
|
inputFile.close();
|
||||||
|
clearCache();
|
||||||
|
Serial.printf("Section state file: Unknown version %u\n", version);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fileFontId, fileMarginTop, fileMarginRight, fileMarginBottom, fileMarginLeft;
|
||||||
|
float fileLineCompression;
|
||||||
|
serialization::readPod(inputFile, fileFontId);
|
||||||
|
serialization::readPod(inputFile, fileLineCompression);
|
||||||
|
serialization::readPod(inputFile, fileMarginTop);
|
||||||
|
serialization::readPod(inputFile, fileMarginRight);
|
||||||
|
serialization::readPod(inputFile, fileMarginBottom);
|
||||||
|
serialization::readPod(inputFile, fileMarginLeft);
|
||||||
|
|
||||||
|
if (fontId != fileFontId || lineCompression != fileLineCompression || marginTop != fileMarginTop ||
|
||||||
|
marginRight != fileMarginRight || marginBottom != fileMarginBottom || marginLeft != fileMarginLeft) {
|
||||||
|
inputFile.close();
|
||||||
|
clearCache();
|
||||||
|
Serial.println("Section state file: Parameters do not match, ignoring");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
serialization::readPod(inputFile, pageCount);
|
serialization::readPod(inputFile, pageCount);
|
||||||
inputFile.close();
|
inputFile.close();
|
||||||
Serial.printf("Loaded cache: %d pages\n", pageCount);
|
Serial.printf("Loaded cache: %d pages\n", pageCount);
|
||||||
@ -63,7 +93,8 @@ void Section::setupCacheDir() const {
|
|||||||
|
|
||||||
void Section::clearCache() const { SD.rmdir(cachePath.c_str()); }
|
void Section::clearCache() const { SD.rmdir(cachePath.c_str()); }
|
||||||
|
|
||||||
bool Section::persistPageDataToSD() {
|
bool Section::persistPageDataToSD(const int fontId, const float lineCompression, const int marginTop,
|
||||||
|
const int marginRight, const int marginBottom, const int marginLeft) {
|
||||||
const auto localPath = epub->getSpineItem(spineIndex);
|
const auto localPath = epub->getSpineItem(spineIndex);
|
||||||
|
|
||||||
// TODO: Should we get rid of this file all together?
|
// TODO: Should we get rid of this file all together?
|
||||||
@ -83,8 +114,8 @@ bool Section::persistPageDataToSD() {
|
|||||||
|
|
||||||
const auto sdTmpHtmlPath = "/sd" + tmpHtmlPath;
|
const auto sdTmpHtmlPath = "/sd" + tmpHtmlPath;
|
||||||
|
|
||||||
auto visitor =
|
auto visitor = EpubHtmlParserSlim(sdTmpHtmlPath.c_str(), renderer, fontId, lineCompression, marginTop, marginRight,
|
||||||
EpubHtmlParserSlim(sdTmpHtmlPath.c_str(), renderer, [this](const Page* page) { this->onPageComplete(page); });
|
marginBottom, marginLeft, [this](const Page* page) { this->onPageComplete(page); });
|
||||||
success = visitor.parseAndBuildPages();
|
success = visitor.parseAndBuildPages();
|
||||||
|
|
||||||
SD.remove(tmpHtmlPath.c_str());
|
SD.remove(tmpHtmlPath.c_str());
|
||||||
@ -93,7 +124,7 @@ bool Section::persistPageDataToSD() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
writeCacheMetadata();
|
writeCacheMetadata(fontId, lineCompression, marginTop, marginRight, marginBottom, marginLeft);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,29 +2,32 @@
|
|||||||
#include "Epub.h"
|
#include "Epub.h"
|
||||||
|
|
||||||
class Page;
|
class Page;
|
||||||
class EpdRenderer;
|
class GfxRenderer;
|
||||||
|
|
||||||
class Section {
|
class Section {
|
||||||
Epub* epub;
|
Epub* epub;
|
||||||
const int spineIndex;
|
const int spineIndex;
|
||||||
EpdRenderer& renderer;
|
GfxRenderer& renderer;
|
||||||
std::string cachePath;
|
std::string cachePath;
|
||||||
|
|
||||||
|
void writeCacheMetadata(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom,
|
||||||
|
int marginLeft) const;
|
||||||
void onPageComplete(const Page* page);
|
void onPageComplete(const Page* page);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
int pageCount = 0;
|
int pageCount = 0;
|
||||||
int currentPage = 0;
|
int currentPage = 0;
|
||||||
|
|
||||||
explicit Section(Epub* epub, const int spineIndex, EpdRenderer& renderer)
|
explicit Section(Epub* epub, const int spineIndex, GfxRenderer& renderer)
|
||||||
: epub(epub), spineIndex(spineIndex), renderer(renderer) {
|
: epub(epub), spineIndex(spineIndex), renderer(renderer) {
|
||||||
cachePath = epub->getCachePath() + "/" + std::to_string(spineIndex);
|
cachePath = epub->getCachePath() + "/" + std::to_string(spineIndex);
|
||||||
}
|
}
|
||||||
~Section() = default;
|
~Section() = default;
|
||||||
void writeCacheMetadata() const;
|
bool loadCacheMetadata(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom,
|
||||||
bool loadCacheMetadata();
|
int marginLeft);
|
||||||
void setupCacheDir() const;
|
void setupCacheDir() const;
|
||||||
void clearCache() const;
|
void clearCache() const;
|
||||||
bool persistPageDataToSD();
|
bool persistPageDataToSD(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom,
|
||||||
|
int marginLeft);
|
||||||
Page* loadPageFromSD() const;
|
Page* loadPageFromSD() const;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
class EpdRenderer;
|
class GfxRenderer;
|
||||||
|
|
||||||
typedef enum { TEXT_BLOCK, IMAGE_BLOCK } BlockType;
|
typedef enum { TEXT_BLOCK, IMAGE_BLOCK } BlockType;
|
||||||
|
|
||||||
@ -8,7 +8,7 @@ typedef enum { TEXT_BLOCK, IMAGE_BLOCK } BlockType;
|
|||||||
class Block {
|
class Block {
|
||||||
public:
|
public:
|
||||||
virtual ~Block() = default;
|
virtual ~Block() = default;
|
||||||
virtual void layout(EpdRenderer& renderer) = 0;
|
virtual void layout(GfxRenderer& renderer) = 0;
|
||||||
virtual BlockType getType() = 0;
|
virtual BlockType getType() = 0;
|
||||||
virtual bool isEmpty() = 0;
|
virtual bool isEmpty() = 0;
|
||||||
virtual void finish() {}
|
virtual void finish() {}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
#include "TextBlock.h"
|
#include "TextBlock.h"
|
||||||
|
|
||||||
#include <EpdRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <Serialization.h>
|
#include <Serialization.h>
|
||||||
|
|
||||||
void TextBlock::addWord(const std::string& word, const bool is_bold, const bool is_italic) {
|
void TextBlock::addWord(const std::string& word, const bool is_bold, const bool is_italic) {
|
||||||
@ -10,10 +10,11 @@ void TextBlock::addWord(const std::string& word, const bool is_bold, const bool
|
|||||||
wordStyles.push_back((is_bold ? BOLD_SPAN : 0) | (is_italic ? ITALIC_SPAN : 0));
|
wordStyles.push_back((is_bold ? BOLD_SPAN : 0) | (is_italic ? ITALIC_SPAN : 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::list<TextBlock*> TextBlock::splitIntoLines(const EpdRenderer& renderer) {
|
std::list<TextBlock*> TextBlock::splitIntoLines(const GfxRenderer& renderer, const int fontId,
|
||||||
|
const int horizontalMargin) {
|
||||||
const int totalWordCount = words.size();
|
const int totalWordCount = words.size();
|
||||||
const int pageWidth = renderer.getPageWidth();
|
const int pageWidth = GfxRenderer::getScreenWidth() - horizontalMargin;
|
||||||
const int spaceWidth = renderer.getSpaceWidth();
|
const int spaceWidth = renderer.getSpaceWidth(fontId);
|
||||||
|
|
||||||
words.shrink_to_fit();
|
words.shrink_to_fit();
|
||||||
wordStyles.shrink_to_fit();
|
wordStyles.shrink_to_fit();
|
||||||
@ -21,7 +22,7 @@ std::list<TextBlock*> TextBlock::splitIntoLines(const EpdRenderer& renderer) {
|
|||||||
|
|
||||||
// measure each word
|
// measure each word
|
||||||
uint16_t wordWidths[totalWordCount];
|
uint16_t wordWidths[totalWordCount];
|
||||||
for (int i = 0; i < words.size(); i++) {
|
for (int i = 0; i < totalWordCount; i++) {
|
||||||
// measure the word
|
// measure the word
|
||||||
EpdFontStyle fontStyle = REGULAR;
|
EpdFontStyle fontStyle = REGULAR;
|
||||||
if (wordStyles[i] & BOLD_SPAN) {
|
if (wordStyles[i] & BOLD_SPAN) {
|
||||||
@ -33,7 +34,7 @@ std::list<TextBlock*> TextBlock::splitIntoLines(const EpdRenderer& renderer) {
|
|||||||
} else if (wordStyles[i] & ITALIC_SPAN) {
|
} else if (wordStyles[i] & ITALIC_SPAN) {
|
||||||
fontStyle = ITALIC;
|
fontStyle = ITALIC;
|
||||||
}
|
}
|
||||||
const int width = renderer.getTextWidth(words[i].c_str(), fontStyle);
|
const int width = renderer.getTextWidth(fontId, words[i].c_str(), fontStyle);
|
||||||
wordWidths[i] = width;
|
wordWidths[i] = width;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +155,7 @@ std::list<TextBlock*> TextBlock::splitIntoLines(const EpdRenderer& renderer) {
|
|||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextBlock::render(const EpdRenderer& renderer, const int x, const int y) const {
|
void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int x, const int y) const {
|
||||||
for (int i = 0; i < words.size(); i++) {
|
for (int i = 0; i < words.size(); i++) {
|
||||||
// render the word
|
// render the word
|
||||||
EpdFontStyle fontStyle = REGULAR;
|
EpdFontStyle fontStyle = REGULAR;
|
||||||
@ -165,7 +166,7 @@ void TextBlock::render(const EpdRenderer& renderer, const int x, const int y) co
|
|||||||
} else if (wordStyles[i] & ITALIC_SPAN) {
|
} else if (wordStyles[i] & ITALIC_SPAN) {
|
||||||
fontStyle = ITALIC;
|
fontStyle = ITALIC;
|
||||||
}
|
}
|
||||||
renderer.drawText(x + wordXpos[i], y, words[i].c_str(), true, fontStyle);
|
renderer.drawText(fontId, x + wordXpos[i], y, words[i].c_str(), true, fontStyle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -40,10 +40,10 @@ class TextBlock final : public Block {
|
|||||||
void setStyle(const BLOCK_STYLE style) { this->style = style; }
|
void setStyle(const BLOCK_STYLE style) { this->style = style; }
|
||||||
BLOCK_STYLE getStyle() const { return style; }
|
BLOCK_STYLE getStyle() const { return style; }
|
||||||
bool isEmpty() override { return words.empty(); }
|
bool isEmpty() override { return words.empty(); }
|
||||||
void layout(EpdRenderer& renderer) override {};
|
void layout(GfxRenderer& renderer) override {};
|
||||||
// given a renderer works out where to break the words into lines
|
// given a renderer works out where to break the words into lines
|
||||||
std::list<TextBlock*> splitIntoLines(const EpdRenderer& renderer);
|
std::list<TextBlock*> splitIntoLines(const GfxRenderer& renderer, int fontId, int horizontalMargin);
|
||||||
void render(const EpdRenderer& renderer, int x, int y) const;
|
void render(const GfxRenderer& renderer, int fontId, int x, int y) const;
|
||||||
BlockType getType() override { return TEXT_BLOCK; }
|
BlockType getType() override { return TEXT_BLOCK; }
|
||||||
void serialize(std::ostream& os) const;
|
void serialize(std::ostream& os) const;
|
||||||
static TextBlock* deserialize(std::istream& is);
|
static TextBlock* deserialize(std::istream& is);
|
||||||
|
|||||||
222
lib/GfxRenderer/GfxRenderer.cpp
Normal file
222
lib/GfxRenderer/GfxRenderer.cpp
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
#include "GfxRenderer.h"
|
||||||
|
|
||||||
|
#include <Utf8.h>
|
||||||
|
|
||||||
|
void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { fontMap.insert({fontId, font}); }
|
||||||
|
|
||||||
|
void GfxRenderer::drawPixel(const int x, const int y, const bool state) const {
|
||||||
|
uint8_t* frameBuffer = einkDisplay.getFrameBuffer();
|
||||||
|
|
||||||
|
// Early return if no framebuffer is set
|
||||||
|
if (!frameBuffer) {
|
||||||
|
Serial.printf("!!No framebuffer\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rotate coordinates: portrait (480x800) -> landscape (800x480)
|
||||||
|
// Rotation: 90 degrees clockwise
|
||||||
|
const int rotatedX = y;
|
||||||
|
const int rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x;
|
||||||
|
|
||||||
|
// Bounds checking (portrait: 480x800)
|
||||||
|
if (rotatedX < 0 || rotatedX >= EInkDisplay::DISPLAY_WIDTH || rotatedY < 0 ||
|
||||||
|
rotatedY >= EInkDisplay::DISPLAY_HEIGHT) {
|
||||||
|
Serial.printf("!! Outside range (%d, %d)\n", x, y);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate byte position and bit position
|
||||||
|
const uint16_t byteIndex = rotatedY * EInkDisplay::DISPLAY_WIDTH_BYTES + (rotatedX / 8);
|
||||||
|
const uint8_t bitPosition = 7 - (rotatedX % 8); // MSB first
|
||||||
|
|
||||||
|
if (state) {
|
||||||
|
frameBuffer[byteIndex] &= ~(1 << bitPosition); // Clear bit
|
||||||
|
} else {
|
||||||
|
frameBuffer[byteIndex] |= 1 << bitPosition; // Set bit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int GfxRenderer::getTextWidth(const int fontId, const char* text, const EpdFontStyle style) const {
|
||||||
|
if (fontMap.count(fontId) == 0) {
|
||||||
|
Serial.printf("Font %d not found\n", fontId);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int w = 0, h = 0;
|
||||||
|
fontMap.at(fontId).getTextDimensions(text, &w, &h, style);
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxRenderer::drawText(const int fontId, const int x, const int y, const char* text, const bool black,
|
||||||
|
const EpdFontStyle style) const {
|
||||||
|
const int yPos = y + getLineHeight(fontId);
|
||||||
|
int xpos = x;
|
||||||
|
|
||||||
|
// cannot draw a NULL / empty string
|
||||||
|
if (text == nullptr || *text == '\0') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fontMap.count(fontId) == 0) {
|
||||||
|
Serial.printf("Font %d not found\n", fontId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto font = fontMap.at(fontId);
|
||||||
|
|
||||||
|
// no printable characters
|
||||||
|
if (!font.hasPrintableChars(text, style)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t cp;
|
||||||
|
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) {
|
||||||
|
renderChar(font, cp, &xpos, &yPos, black, style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxRenderer::drawLine(int x1, int y1, int x2, int y2, const bool state) const {
|
||||||
|
if (x1 == x2) {
|
||||||
|
if (y2 < y1) {
|
||||||
|
std::swap(y1, y2);
|
||||||
|
}
|
||||||
|
for (int y = y1; y <= y2; y++) {
|
||||||
|
drawPixel(x1, y, state);
|
||||||
|
}
|
||||||
|
} else if (y1 == y2) {
|
||||||
|
if (x2 < x1) {
|
||||||
|
std::swap(x1, x2);
|
||||||
|
}
|
||||||
|
for (int x = x1; x <= x2; x++) {
|
||||||
|
drawPixel(x, y1, state);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: Implement
|
||||||
|
Serial.println("Line drawing not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxRenderer::drawRect(const int x, const int y, const int width, const int height, const bool state) const {
|
||||||
|
drawLine(x, y, x + width - 1, y, state);
|
||||||
|
drawLine(x + width - 1, y, x + width - 1, y + height - 1, state);
|
||||||
|
drawLine(x + width - 1, y + height - 1, x, y + height - 1, state);
|
||||||
|
drawLine(x, y, x, y + height - 1, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxRenderer::fillRect(const int x, const int y, const int width, const int height, const bool state) const {
|
||||||
|
for (int fillY = y; fillY < y + height; fillY++) {
|
||||||
|
drawLine(x, fillY, x + width - 1, fillY, state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const {
|
||||||
|
einkDisplay.drawImage(bitmap, x, y, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxRenderer::clearScreen(const uint8_t color) const { einkDisplay.clearScreen(color); }
|
||||||
|
|
||||||
|
void GfxRenderer::invertScreen() const {
|
||||||
|
uint8_t* buffer = einkDisplay.getFrameBuffer();
|
||||||
|
for (int i = 0; i < EInkDisplay::BUFFER_SIZE; i++) {
|
||||||
|
buffer[i] = ~buffer[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxRenderer::displayBuffer(const EInkDisplay::RefreshMode refreshMode) const {
|
||||||
|
einkDisplay.displayBuffer(refreshMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Support partial window update
|
||||||
|
// void GfxRenderer::flushArea(const int x, const int y, const int width, const int height) const {
|
||||||
|
// const int rotatedX = y;
|
||||||
|
// const int rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x;
|
||||||
|
//
|
||||||
|
// einkDisplay.displayBuffer(EInkDisplay::FAST_REFRESH, rotatedX, rotatedY, height, width);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Note: Internal driver treats screen in command orientation, this library treats in portrait orientation
|
||||||
|
int GfxRenderer::getScreenWidth() { return EInkDisplay::DISPLAY_HEIGHT; }
|
||||||
|
int GfxRenderer::getScreenHeight() { return EInkDisplay::DISPLAY_WIDTH; }
|
||||||
|
|
||||||
|
int GfxRenderer::getSpaceWidth(const int fontId) const {
|
||||||
|
if (fontMap.count(fontId) == 0) {
|
||||||
|
Serial.printf("Font %d not found\n", fontId);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fontMap.at(fontId).getGlyph(' ', REGULAR)->advanceX;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GfxRenderer::getLineHeight(const int fontId) const {
|
||||||
|
if (fontMap.count(fontId) == 0) {
|
||||||
|
Serial.printf("Font %d not found\n", fontId);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fontMap.at(fontId).getData(REGULAR)->advanceY;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GfxRenderer::swapBuffers() const { einkDisplay.swapBuffers(); }
|
||||||
|
|
||||||
|
void GfxRenderer::copyGrayscaleLsbBuffers() const { einkDisplay.copyGrayscaleLsbBuffers(einkDisplay.getFrameBuffer()); }
|
||||||
|
|
||||||
|
void GfxRenderer::copyGrayscaleMsbBuffers() const { einkDisplay.copyGrayscaleMsbBuffers(einkDisplay.getFrameBuffer()); }
|
||||||
|
|
||||||
|
void GfxRenderer::displayGrayBuffer() const { einkDisplay.displayGrayBuffer(); }
|
||||||
|
|
||||||
|
void GfxRenderer::renderChar(const EpdFontFamily& fontFamily, const uint32_t cp, int* x, const int* y,
|
||||||
|
const bool pixelState, const EpdFontStyle style) const {
|
||||||
|
const EpdGlyph* glyph = fontFamily.getGlyph(cp, style);
|
||||||
|
if (!glyph) {
|
||||||
|
// TODO: Replace with fallback glyph property?
|
||||||
|
glyph = fontFamily.getGlyph('?', style);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no glyph?
|
||||||
|
if (!glyph) {
|
||||||
|
Serial.printf("No glyph for codepoint %d\n", cp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int is2Bit = fontFamily.getData(style)->is2Bit;
|
||||||
|
const uint32_t offset = glyph->dataOffset;
|
||||||
|
const uint8_t width = glyph->width;
|
||||||
|
const uint8_t height = glyph->height;
|
||||||
|
const int left = glyph->left;
|
||||||
|
|
||||||
|
const uint8_t* bitmap = nullptr;
|
||||||
|
bitmap = &fontFamily.getData(style)->bitmap[offset];
|
||||||
|
|
||||||
|
if (bitmap != nullptr) {
|
||||||
|
for (int glyphY = 0; glyphY < height; glyphY++) {
|
||||||
|
const int screenY = *y - glyph->top + glyphY;
|
||||||
|
for (int glyphX = 0; glyphX < width; glyphX++) {
|
||||||
|
const int pixelPosition = glyphY * width + glyphX;
|
||||||
|
const int screenX = *x + left + glyphX;
|
||||||
|
|
||||||
|
if (is2Bit) {
|
||||||
|
const uint8_t byte = bitmap[pixelPosition / 4];
|
||||||
|
const uint8_t bit_index = (3 - pixelPosition % 4) * 2;
|
||||||
|
|
||||||
|
const uint8_t val = (byte >> bit_index) & 0x3;
|
||||||
|
if (fontRenderMode == BW && val > 0) {
|
||||||
|
drawPixel(screenX, screenY, pixelState);
|
||||||
|
} else if (fontRenderMode == GRAYSCALE_MSB && val == 1) {
|
||||||
|
// TODO: Not sure how this anti-aliasing goes on black backgrounds
|
||||||
|
drawPixel(screenX, screenY, false);
|
||||||
|
} else if (fontRenderMode == GRAYSCALE_LSB && val == 2) {
|
||||||
|
drawPixel(screenX, screenY, false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const uint8_t byte = bitmap[pixelPosition / 8];
|
||||||
|
const uint8_t bit_index = 7 - (pixelPosition % 8);
|
||||||
|
|
||||||
|
if ((byte >> bit_index) & 1) {
|
||||||
|
drawPixel(screenX, screenY, pixelState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*x += glyph->advanceX;
|
||||||
|
}
|
||||||
53
lib/GfxRenderer/GfxRenderer.h
Normal file
53
lib/GfxRenderer/GfxRenderer.h
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <EInkDisplay.h>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include "EpdFontFamily.h"
|
||||||
|
|
||||||
|
class GfxRenderer {
|
||||||
|
public:
|
||||||
|
enum FontRenderMode { BW, GRAYSCALE_LSB, GRAYSCALE_MSB };
|
||||||
|
|
||||||
|
private:
|
||||||
|
EInkDisplay& einkDisplay;
|
||||||
|
FontRenderMode fontRenderMode;
|
||||||
|
std::map<int, EpdFontFamily> fontMap;
|
||||||
|
void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, const int* y, bool pixelState,
|
||||||
|
EpdFontStyle style) const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit GfxRenderer(EInkDisplay& einkDisplay) : einkDisplay(einkDisplay), fontRenderMode(BW) {}
|
||||||
|
~GfxRenderer() = default;
|
||||||
|
|
||||||
|
// Setup
|
||||||
|
void insertFont(int fontId, EpdFontFamily font);
|
||||||
|
|
||||||
|
// Screen ops
|
||||||
|
static int getScreenWidth();
|
||||||
|
static int getScreenHeight();
|
||||||
|
void displayBuffer(EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH) const;
|
||||||
|
void invertScreen() const;
|
||||||
|
void clearScreen(uint8_t color = 0xFF) const;
|
||||||
|
|
||||||
|
// Drawing
|
||||||
|
void drawPixel(int x, int y, bool state = true) const;
|
||||||
|
void drawLine(int x1, int y1, int x2, int y2, bool state = true) const;
|
||||||
|
void drawRect(int x, int y, int width, int height, bool state = true) const;
|
||||||
|
void fillRect(int x, int y, int width, int height, bool state = true) const;
|
||||||
|
void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const;
|
||||||
|
|
||||||
|
// Text
|
||||||
|
int getTextWidth(int fontId, const char* text, EpdFontStyle style = REGULAR) const;
|
||||||
|
void drawText(int fontId, int x, int y, const char* text, bool black = true, EpdFontStyle style = REGULAR) const;
|
||||||
|
void setFontRenderMode(const FontRenderMode mode) { this->fontRenderMode = mode; }
|
||||||
|
int getSpaceWidth(int fontId) const;
|
||||||
|
int getLineHeight(int fontId) const;
|
||||||
|
|
||||||
|
// Low level functions
|
||||||
|
void swapBuffers() const;
|
||||||
|
void copyGrayscaleLsbBuffers() const;
|
||||||
|
void copyGrayscaleMsbBuffers() const;
|
||||||
|
void displayGrayBuffer() const;
|
||||||
|
};
|
||||||
29
src/config.h
Normal file
29
src/config.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generated with:
|
||||||
|
* ruby -rdigest -e 'puts [
|
||||||
|
* "./lib/EpdFont/builtinFonts/bookerly_2b.h",
|
||||||
|
* "./lib/EpdFont/builtinFonts/bookerly_bold_2b.h",
|
||||||
|
* "./lib/EpdFont/builtinFonts/bookerly_bold_italic_2b.h",
|
||||||
|
* "./lib/EpdFont/builtinFonts/bookerly_italic_2b.h",
|
||||||
|
* ].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)'
|
||||||
|
*/
|
||||||
|
#define READER_FONT_ID 1747632454
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generated with:
|
||||||
|
* ruby -rdigest -e 'puts [
|
||||||
|
* "./lib/EpdFont/builtinFonts/ubuntu_10.h",
|
||||||
|
* "./lib/EpdFont/builtinFonts/ubuntu_bold_10.h",
|
||||||
|
* ].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)'
|
||||||
|
*/
|
||||||
|
#define UI_FONT_ID 225955604
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generated with:
|
||||||
|
* ruby -rdigest -e 'puts [
|
||||||
|
* "./lib/EpdFont/builtinFonts/babyblue.h",
|
||||||
|
* ].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)'
|
||||||
|
*/
|
||||||
|
#define SMALL_FONT_ID 141891058
|
||||||
34
src/main.cpp
34
src/main.cpp
@ -1,13 +1,21 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <EInkDisplay.h>
|
#include <EInkDisplay.h>
|
||||||
#include <EpdRenderer.h>
|
|
||||||
#include <Epub.h>
|
#include <Epub.h>
|
||||||
|
#include <GfxRenderer.h>
|
||||||
#include <InputManager.h>
|
#include <InputManager.h>
|
||||||
#include <SD.h>
|
#include <SD.h>
|
||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
|
|
||||||
#include "Battery.h"
|
#include "Battery.h"
|
||||||
#include "CrossPointState.h"
|
#include "CrossPointState.h"
|
||||||
|
#include "builtinFonts/babyblue.h"
|
||||||
|
#include "builtinFonts/bookerly_2b.h"
|
||||||
|
#include "builtinFonts/bookerly_bold_2b.h"
|
||||||
|
#include "builtinFonts/bookerly_bold_italic_2b.h"
|
||||||
|
#include "builtinFonts/bookerly_italic_2b.h"
|
||||||
|
#include "builtinFonts/ubuntu_10.h"
|
||||||
|
#include "builtinFonts/ubuntu_bold_10.h"
|
||||||
|
#include "config.h"
|
||||||
#include "screens/BootLogoScreen.h"
|
#include "screens/BootLogoScreen.h"
|
||||||
#include "screens/EpubReaderScreen.h"
|
#include "screens/EpubReaderScreen.h"
|
||||||
#include "screens/FileSelectionScreen.h"
|
#include "screens/FileSelectionScreen.h"
|
||||||
@ -30,10 +38,24 @@
|
|||||||
|
|
||||||
EInkDisplay einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY);
|
EInkDisplay einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY);
|
||||||
InputManager inputManager;
|
InputManager inputManager;
|
||||||
EpdRenderer renderer(einkDisplay);
|
GfxRenderer renderer(einkDisplay);
|
||||||
Screen* currentScreen;
|
Screen* currentScreen;
|
||||||
CrossPointState appState;
|
CrossPointState appState;
|
||||||
|
|
||||||
|
// Fonts
|
||||||
|
EpdFont bookerlyFont(&bookerly_2b);
|
||||||
|
EpdFont bookerlyBoldFont(&bookerly_bold_2b);
|
||||||
|
EpdFont bookerlyItalicFont(&bookerly_italic_2b);
|
||||||
|
EpdFont bookerlyBoldItalicFont(&bookerly_bold_italic_2b);
|
||||||
|
EpdFontFamily bookerlyFontFamily(&bookerlyFont, &bookerlyBoldFont, &bookerlyItalicFont, &bookerlyBoldItalicFont);
|
||||||
|
|
||||||
|
EpdFont smallFont(&babyblue);
|
||||||
|
EpdFontFamily smallFontFamily(&smallFont);
|
||||||
|
|
||||||
|
EpdFont ubuntu10Font(&ubuntu_10);
|
||||||
|
EpdFont ubuntuBold10Font(&ubuntu_bold_10);
|
||||||
|
EpdFontFamily ubuntuFontFamily(&ubuntu10Font, &ubuntuBold10Font);
|
||||||
|
|
||||||
// Power button timing
|
// Power button timing
|
||||||
// Time required to confirm boot from sleep
|
// Time required to confirm boot from sleep
|
||||||
constexpr unsigned long POWER_BUTTON_WAKEUP_MS = 1000;
|
constexpr unsigned long POWER_BUTTON_WAKEUP_MS = 1000;
|
||||||
@ -141,7 +163,8 @@ void onSelectEpubFile(const std::string& path) {
|
|||||||
enterNewScreen(new EpubReaderScreen(renderer, inputManager, epub, onGoHome));
|
enterNewScreen(new EpubReaderScreen(renderer, inputManager, epub, onGoHome));
|
||||||
} else {
|
} else {
|
||||||
exitScreen();
|
exitScreen();
|
||||||
enterNewScreen(new FullScreenMessageScreen(renderer, inputManager, "Failed to load epub", REGULAR, EInkDisplay::HALF_REFRESH));
|
enterNewScreen(
|
||||||
|
new FullScreenMessageScreen(renderer, inputManager, "Failed to load epub", REGULAR, EInkDisplay::HALF_REFRESH));
|
||||||
delay(2000);
|
delay(2000);
|
||||||
onGoHome();
|
onGoHome();
|
||||||
}
|
}
|
||||||
@ -172,6 +195,11 @@ void setup() {
|
|||||||
einkDisplay.begin();
|
einkDisplay.begin();
|
||||||
Serial.println("Display initialized");
|
Serial.println("Display initialized");
|
||||||
|
|
||||||
|
renderer.insertFont(READER_FONT_ID, bookerlyFontFamily);
|
||||||
|
renderer.insertFont(UI_FONT_ID, ubuntuFontFamily);
|
||||||
|
renderer.insertFont(SMALL_FONT_ID, smallFontFamily);
|
||||||
|
Serial.println("Fonts loaded");
|
||||||
|
|
||||||
exitScreen();
|
exitScreen();
|
||||||
enterNewScreen(new BootLogoScreen(renderer, inputManager));
|
enterNewScreen(new BootLogoScreen(renderer, inputManager));
|
||||||
|
|
||||||
|
|||||||
@ -1,19 +1,20 @@
|
|||||||
#include "BootLogoScreen.h"
|
#include "BootLogoScreen.h"
|
||||||
|
|
||||||
#include <EpdRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "images/CrossLarge.h"
|
#include "images/CrossLarge.h"
|
||||||
|
|
||||||
void BootLogoScreen::onEnter() {
|
void BootLogoScreen::onEnter() {
|
||||||
const auto pageWidth = renderer.getPageWidth();
|
const auto pageWidth = GfxRenderer::getScreenWidth();
|
||||||
const auto pageHeight = renderer.getPageHeight();
|
const auto pageHeight = GfxRenderer::getScreenHeight();
|
||||||
|
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
// Location for images is from top right in landscape orientation
|
// Location for images is from top right in landscape orientation
|
||||||
renderer.drawImage(CrossLarge, (pageHeight - 128) / 2, (pageWidth - 128) / 2, 128, 128);
|
renderer.drawImage(CrossLarge, (pageHeight - 128) / 2, (pageWidth - 128) / 2, 128, 128);
|
||||||
const int width = renderer.getUiTextWidth("CrossPoint", BOLD);
|
const int width = renderer.getTextWidth(UI_FONT_ID, "CrossPoint", BOLD);
|
||||||
renderer.drawUiText((pageWidth - width)/ 2, pageHeight / 2 + 70, "CrossPoint", true, BOLD);
|
renderer.drawText(UI_FONT_ID, (pageWidth - width) / 2, pageHeight / 2 + 70, "CrossPoint", true, BOLD);
|
||||||
const int bootingWidth = renderer.getSmallTextWidth("BOOTING");
|
const int bootingWidth = renderer.getTextWidth(SMALL_FONT_ID, "BOOTING");
|
||||||
renderer.drawSmallText((pageWidth - bootingWidth) / 2, pageHeight / 2 + 95, "BOOTING");
|
renderer.drawText(SMALL_FONT_ID, (pageWidth - bootingWidth) / 2, pageHeight / 2 + 95, "BOOTING");
|
||||||
renderer.flushDisplay();
|
renderer.displayBuffer();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,6 @@
|
|||||||
|
|
||||||
class BootLogoScreen final : public Screen {
|
class BootLogoScreen final : public Screen {
|
||||||
public:
|
public:
|
||||||
explicit BootLogoScreen(EpdRenderer& renderer, InputManager& inputManager) : Screen(renderer, inputManager) {}
|
explicit BootLogoScreen(GfxRenderer& renderer, InputManager& inputManager) : Screen(renderer, inputManager) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,13 +1,19 @@
|
|||||||
#include "EpubReaderScreen.h"
|
#include "EpubReaderScreen.h"
|
||||||
|
|
||||||
#include <EpdRenderer.h>
|
|
||||||
#include <Epub/Page.h>
|
#include <Epub/Page.h>
|
||||||
|
#include <GfxRenderer.h>
|
||||||
#include <SD.h>
|
#include <SD.h>
|
||||||
|
|
||||||
#include "Battery.h"
|
#include "Battery.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
constexpr int PAGES_PER_REFRESH = 15;
|
constexpr int PAGES_PER_REFRESH = 15;
|
||||||
constexpr unsigned long SKIP_CHAPTER_MS = 700;
|
constexpr unsigned long SKIP_CHAPTER_MS = 700;
|
||||||
|
constexpr float lineCompression = 0.95f;
|
||||||
|
constexpr int marginTop = 11;
|
||||||
|
constexpr int marginRight = 10;
|
||||||
|
constexpr int marginBottom = 30;
|
||||||
|
constexpr int marginLeft = 10;
|
||||||
|
|
||||||
void EpubReaderScreen::taskTrampoline(void* param) {
|
void EpubReaderScreen::taskTrampoline(void* param) {
|
||||||
auto* self = static_cast<EpubReaderScreen*>(param);
|
auto* self = static_cast<EpubReaderScreen*>(param);
|
||||||
@ -150,26 +156,28 @@ void EpubReaderScreen::renderScreen() {
|
|||||||
const auto filepath = epub->getSpineItem(currentSpineIndex);
|
const auto filepath = epub->getSpineItem(currentSpineIndex);
|
||||||
Serial.printf("Loading file: %s, index: %d\n", filepath.c_str(), currentSpineIndex);
|
Serial.printf("Loading file: %s, index: %d\n", filepath.c_str(), currentSpineIndex);
|
||||||
section = new Section(epub, currentSpineIndex, renderer);
|
section = new Section(epub, currentSpineIndex, renderer);
|
||||||
if (!section->loadCacheMetadata()) {
|
if (!section->loadCacheMetadata(READER_FONT_ID, lineCompression, marginTop, marginRight, marginBottom,
|
||||||
|
marginLeft)) {
|
||||||
Serial.println("Cache not found, building...");
|
Serial.println("Cache not found, building...");
|
||||||
|
|
||||||
{
|
{
|
||||||
const int textWidth = renderer.getTextWidth("Indexing...");
|
const int textWidth = renderer.getTextWidth(READER_FONT_ID, "Indexing...");
|
||||||
constexpr int margin = 20;
|
constexpr int margin = 20;
|
||||||
const int x = (renderer.getPageWidth() - textWidth - margin * 2) / 2;
|
const int x = (GfxRenderer::getScreenWidth() - textWidth - margin * 2) / 2;
|
||||||
constexpr int y = 50;
|
constexpr int y = 50;
|
||||||
const int w = textWidth + margin * 2;
|
const int w = textWidth + margin * 2;
|
||||||
const int h = renderer.getLineHeight() + margin * 2;
|
const int h = renderer.getLineHeight(READER_FONT_ID) + margin * 2;
|
||||||
renderer.swapBuffers();
|
renderer.swapBuffers();
|
||||||
renderer.fillRect(x, y, w, h, 0);
|
renderer.fillRect(x, y, w, h, 0);
|
||||||
renderer.drawText(x + margin, y + margin, "Indexing...");
|
renderer.drawText(READER_FONT_ID, x + margin, y + margin, "Indexing...");
|
||||||
renderer.drawRect(x + 5, y + 5, w - 10, h - 10);
|
renderer.drawRect(x + 5, y + 5, w - 10, h - 10);
|
||||||
renderer.flushDisplay(EInkDisplay::HALF_REFRESH);
|
renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
|
||||||
pagesUntilFullRefresh = 0;
|
pagesUntilFullRefresh = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
section->setupCacheDir();
|
section->setupCacheDir();
|
||||||
if (!section->persistPageDataToSD()) {
|
if (!section->persistPageDataToSD(READER_FONT_ID, lineCompression, marginTop, marginRight, marginBottom,
|
||||||
|
marginLeft)) {
|
||||||
Serial.println("Failed to persist page data to SD");
|
Serial.println("Failed to persist page data to SD");
|
||||||
delete section;
|
delete section;
|
||||||
section = nullptr;
|
section = nullptr;
|
||||||
@ -190,19 +198,19 @@ void EpubReaderScreen::renderScreen() {
|
|||||||
|
|
||||||
if (section->pageCount == 0) {
|
if (section->pageCount == 0) {
|
||||||
Serial.println("No pages to render");
|
Serial.println("No pages to render");
|
||||||
const int width = renderer.getTextWidth("Empty chapter", BOLD);
|
const int width = renderer.getTextWidth(READER_FONT_ID, "Empty chapter", BOLD);
|
||||||
renderer.drawText((renderer.getPageWidth() - width) / 2, 300, "Empty chapter", true, BOLD);
|
renderer.drawText(READER_FONT_ID, (GfxRenderer::getScreenWidth() - width) / 2, 300, "Empty chapter", true, BOLD);
|
||||||
renderStatusBar();
|
renderStatusBar();
|
||||||
renderer.flushDisplay();
|
renderer.displayBuffer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (section->currentPage < 0 || section->currentPage >= section->pageCount) {
|
if (section->currentPage < 0 || section->currentPage >= section->pageCount) {
|
||||||
Serial.printf("Page out of bounds: %d (max %d)\n", section->currentPage, section->pageCount);
|
Serial.printf("Page out of bounds: %d (max %d)\n", section->currentPage, section->pageCount);
|
||||||
const int width = renderer.getTextWidth("Out of bounds", BOLD);
|
const int width = renderer.getTextWidth(READER_FONT_ID, "Out of bounds", BOLD);
|
||||||
renderer.drawText((renderer.getPageWidth() - width) / 2, 300, "Out of bounds", true, BOLD);
|
renderer.drawText(READER_FONT_ID, (GfxRenderer::getScreenWidth() - width) / 2, 300, "Out of bounds", true, BOLD);
|
||||||
renderStatusBar();
|
renderStatusBar();
|
||||||
renderer.flushDisplay();
|
renderer.displayBuffer();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,52 +229,54 @@ void EpubReaderScreen::renderScreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderScreen::renderContents(const Page* p) {
|
void EpubReaderScreen::renderContents(const Page* p) {
|
||||||
p->render(renderer);
|
p->render(renderer, READER_FONT_ID);
|
||||||
renderStatusBar();
|
renderStatusBar();
|
||||||
if (pagesUntilFullRefresh <= 1) {
|
if (pagesUntilFullRefresh <= 1) {
|
||||||
renderer.flushDisplay(EInkDisplay::HALF_REFRESH);
|
renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
|
||||||
pagesUntilFullRefresh = PAGES_PER_REFRESH;
|
pagesUntilFullRefresh = PAGES_PER_REFRESH;
|
||||||
} else {
|
} else {
|
||||||
renderer.flushDisplay();
|
renderer.displayBuffer();
|
||||||
pagesUntilFullRefresh--;
|
pagesUntilFullRefresh--;
|
||||||
}
|
}
|
||||||
|
|
||||||
// grayscale rendering
|
// grayscale rendering
|
||||||
|
// TODO: Only do this if font supports it
|
||||||
{
|
{
|
||||||
renderer.clearScreen(0x00);
|
renderer.clearScreen(0x00);
|
||||||
renderer.setFontRendererMode(GRAYSCALE_LSB);
|
renderer.setFontRenderMode(GfxRenderer::GRAYSCALE_LSB);
|
||||||
p->render(renderer);
|
p->render(renderer, READER_FONT_ID);
|
||||||
renderer.copyGrayscaleLsbBuffers();
|
renderer.copyGrayscaleLsbBuffers();
|
||||||
|
|
||||||
// Render and copy to MSB buffer
|
// Render and copy to MSB buffer
|
||||||
renderer.clearScreen(0x00);
|
renderer.clearScreen(0x00);
|
||||||
renderer.setFontRendererMode(GRAYSCALE_MSB);
|
renderer.setFontRenderMode(GfxRenderer::GRAYSCALE_MSB);
|
||||||
p->render(renderer);
|
p->render(renderer, READER_FONT_ID);
|
||||||
renderer.copyGrayscaleMsbBuffers();
|
renderer.copyGrayscaleMsbBuffers();
|
||||||
|
|
||||||
// display grayscale part
|
// display grayscale part
|
||||||
renderer.displayGrayBuffer();
|
renderer.displayGrayBuffer();
|
||||||
renderer.setFontRendererMode(BW);
|
renderer.setFontRenderMode(GfxRenderer::BW);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EpubReaderScreen::renderStatusBar() const {
|
void EpubReaderScreen::renderStatusBar() const {
|
||||||
const auto pageWidth = renderer.getPageWidth();
|
// Right aligned text for progress counter
|
||||||
|
|
||||||
const std::string progress = std::to_string(section->currentPage + 1) + " / " + std::to_string(section->pageCount);
|
const std::string progress = std::to_string(section->currentPage + 1) + " / " + std::to_string(section->pageCount);
|
||||||
const auto progressTextWidth = renderer.getSmallTextWidth(progress.c_str());
|
const auto progressTextWidth = renderer.getTextWidth(SMALL_FONT_ID, progress.c_str());
|
||||||
renderer.drawSmallText(pageWidth - progressTextWidth, 765, progress.c_str());
|
renderer.drawText(SMALL_FONT_ID, GfxRenderer::getScreenWidth() - marginRight - progressTextWidth, 776,
|
||||||
|
progress.c_str());
|
||||||
|
|
||||||
|
// Left aligned battery icon and percentage
|
||||||
const uint16_t percentage = battery.readPercentage();
|
const uint16_t percentage = battery.readPercentage();
|
||||||
const auto percentageText = std::to_string(percentage) + "%";
|
const auto percentageText = std::to_string(percentage) + "%";
|
||||||
const auto percentageTextWidth = renderer.getSmallTextWidth(percentageText.c_str());
|
const auto percentageTextWidth = renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str());
|
||||||
renderer.drawSmallText(20, 765, percentageText.c_str());
|
renderer.drawText(SMALL_FONT_ID, 20 + marginLeft, 776, percentageText.c_str());
|
||||||
|
|
||||||
// 1 column on left, 2 columns on right, 5 columns of battery body
|
// 1 column on left, 2 columns on right, 5 columns of battery body
|
||||||
constexpr int batteryWidth = 15;
|
constexpr int batteryWidth = 15;
|
||||||
constexpr int batteryHeight = 10;
|
constexpr int batteryHeight = 10;
|
||||||
constexpr int x = 0;
|
constexpr int x = marginLeft;
|
||||||
constexpr int y = 772;
|
constexpr int y = 783;
|
||||||
|
|
||||||
// Top line
|
// Top line
|
||||||
renderer.drawLine(x, y, x + batteryWidth - 4, y);
|
renderer.drawLine(x, y, x + batteryWidth - 4, y);
|
||||||
@ -287,17 +297,18 @@ void EpubReaderScreen::renderStatusBar() const {
|
|||||||
}
|
}
|
||||||
renderer.fillRect(x + 1, y + 1, filledWidth, batteryHeight - 2);
|
renderer.fillRect(x + 1, y + 1, filledWidth, batteryHeight - 2);
|
||||||
|
|
||||||
|
// Centered chatper title text
|
||||||
// Page width minus existing content with 30px padding on each side
|
// Page width minus existing content with 30px padding on each side
|
||||||
const int leftMargin = 20 + percentageTextWidth + 30;
|
const int titleMarginLeft = 20 + percentageTextWidth + 30 + marginLeft;
|
||||||
const int rightMargin = progressTextWidth + 30;
|
const int titleMarginRight = progressTextWidth + 30 + marginRight;
|
||||||
const int availableTextWidth = pageWidth - leftMargin - rightMargin;
|
const int availableTextWidth = GfxRenderer::getScreenWidth() - titleMarginLeft - titleMarginRight;
|
||||||
const auto tocItem = epub->getTocItem(epub->getTocIndexForSpineIndex(currentSpineIndex));
|
const auto tocItem = epub->getTocItem(epub->getTocIndexForSpineIndex(currentSpineIndex));
|
||||||
auto title = tocItem.title;
|
auto title = tocItem.title;
|
||||||
int titleWidth = renderer.getSmallTextWidth(title.c_str());
|
int titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str());
|
||||||
while (titleWidth > availableTextWidth) {
|
while (titleWidth > availableTextWidth) {
|
||||||
title = title.substr(0, title.length() - 8) + "...";
|
title = title.substr(0, title.length() - 8) + "...";
|
||||||
titleWidth = renderer.getSmallTextWidth(title.c_str());
|
titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.drawSmallText(leftMargin + (availableTextWidth - titleWidth) / 2, 765, title.c_str());
|
renderer.drawText(SMALL_FONT_ID, titleMarginLeft + (availableTextWidth - titleWidth) / 2, 777, title.c_str());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,11 +21,11 @@ class EpubReaderScreen final : public Screen {
|
|||||||
static void taskTrampoline(void* param);
|
static void taskTrampoline(void* param);
|
||||||
[[noreturn]] void displayTaskLoop();
|
[[noreturn]] void displayTaskLoop();
|
||||||
void renderScreen();
|
void renderScreen();
|
||||||
void renderContents(const Page *p);
|
void renderContents(const Page* p);
|
||||||
void renderStatusBar() const;
|
void renderStatusBar() const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit EpubReaderScreen(EpdRenderer& renderer, InputManager& inputManager, Epub* epub,
|
explicit EpubReaderScreen(GfxRenderer& renderer, InputManager& inputManager, Epub* epub,
|
||||||
const std::function<void()>& onGoHome)
|
const std::function<void()>& onGoHome)
|
||||||
: Screen(renderer, inputManager), epub(epub), onGoHome(onGoHome) {}
|
: Screen(renderer, inputManager), epub(epub), onGoHome(onGoHome) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
#include "FileSelectionScreen.h"
|
#include "FileSelectionScreen.h"
|
||||||
|
|
||||||
#include <EpdRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <SD.h>
|
#include <SD.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
void sortFileList(std::vector<std::string>& strs) {
|
void sortFileList(std::vector<std::string>& strs) {
|
||||||
std::sort(begin(strs), end(strs), [](const std::string& str1, const std::string& str2) {
|
std::sort(begin(strs), end(strs), [](const std::string& str1, const std::string& str2) {
|
||||||
if (str1.back() == '/' && str2.back() != '/') return true;
|
if (str1.back() == '/' && str2.back() != '/') return true;
|
||||||
@ -118,21 +120,21 @@ void FileSelectionScreen::displayTaskLoop() {
|
|||||||
void FileSelectionScreen::render() const {
|
void FileSelectionScreen::render() const {
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
|
|
||||||
const auto pageWidth = renderer.getPageWidth();
|
const auto pageWidth = GfxRenderer::getScreenWidth();
|
||||||
const auto titleWidth = renderer.getTextWidth("CrossPoint Reader", BOLD);
|
const auto titleWidth = renderer.getTextWidth(READER_FONT_ID, "CrossPoint Reader", BOLD);
|
||||||
renderer.drawText((pageWidth - titleWidth) / 2, 0, "CrossPoint Reader", true, BOLD);
|
renderer.drawText(READER_FONT_ID, (pageWidth - titleWidth) / 2, 10, "CrossPoint Reader", true, BOLD);
|
||||||
|
|
||||||
if (files.empty()) {
|
if (files.empty()) {
|
||||||
renderer.drawUiText(10, 50, "No EPUBs found");
|
renderer.drawText(UI_FONT_ID, 20, 60, "No EPUBs found");
|
||||||
} else {
|
} else {
|
||||||
// Draw selection
|
// Draw selection
|
||||||
renderer.fillRect(0, 50 + selectorIndex * 30 + 2, pageWidth - 1, 30);
|
renderer.fillRect(0, 60 + selectorIndex * 30 + 2, pageWidth - 1, 30);
|
||||||
|
|
||||||
for (size_t i = 0; i < files.size(); i++) {
|
for (size_t i = 0; i < files.size(); i++) {
|
||||||
const auto file = files[i];
|
const auto file = files[i];
|
||||||
renderer.drawUiText(10, 50 + i * 30, file.c_str(), i != selectorIndex);
|
renderer.drawText(UI_FONT_ID, 20, 60 + i * 30, file.c_str(), i != selectorIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.flushDisplay();
|
renderer.displayBuffer();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,7 +24,7 @@ class FileSelectionScreen final : public Screen {
|
|||||||
void loadFiles();
|
void loadFiles();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit FileSelectionScreen(EpdRenderer& renderer, InputManager& inputManager,
|
explicit FileSelectionScreen(GfxRenderer& renderer, InputManager& inputManager,
|
||||||
const std::function<void(const std::string&)>& onSelect)
|
const std::function<void(const std::string&)>& onSelect)
|
||||||
: Screen(renderer, inputManager), onSelect(onSelect) {}
|
: Screen(renderer, inputManager), onSelect(onSelect) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
#include "FullScreenMessageScreen.h"
|
#include "FullScreenMessageScreen.h"
|
||||||
|
|
||||||
#include <EpdRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
void FullScreenMessageScreen::onEnter() {
|
void FullScreenMessageScreen::onEnter() {
|
||||||
const auto width = renderer.getUiTextWidth(text.c_str(), style);
|
const auto width = renderer.getTextWidth(UI_FONT_ID, text.c_str(), style);
|
||||||
const auto height = renderer.getLineHeight();
|
const auto height = renderer.getLineHeight(UI_FONT_ID);
|
||||||
const auto left = (renderer.getPageWidth() - width) / 2;
|
const auto left = (GfxRenderer::getScreenWidth() - width) / 2;
|
||||||
const auto top = (renderer.getPageHeight() - height) / 2;
|
const auto top = (GfxRenderer::getScreenHeight() - height) / 2;
|
||||||
|
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
renderer.drawUiText(left, top, text.c_str(), true, style);
|
renderer.drawText(UI_FONT_ID, left, top, text.c_str(), true, style);
|
||||||
renderer.flushDisplay(refreshMode);
|
renderer.displayBuffer(refreshMode);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <EInkDisplay.h>
|
||||||
|
#include <EpdFontFamily.h>
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include <EInkDisplay.h>
|
|
||||||
#include <EpdFontFamily.h>
|
|
||||||
#include "Screen.h"
|
#include "Screen.h"
|
||||||
|
|
||||||
class FullScreenMessageScreen final : public Screen {
|
class FullScreenMessageScreen final : public Screen {
|
||||||
@ -12,12 +13,9 @@ class FullScreenMessageScreen final : public Screen {
|
|||||||
EInkDisplay::RefreshMode refreshMode;
|
EInkDisplay::RefreshMode refreshMode;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit FullScreenMessageScreen(EpdRenderer& renderer, InputManager& inputManager, std::string text,
|
explicit FullScreenMessageScreen(GfxRenderer& renderer, InputManager& inputManager, std::string text,
|
||||||
const EpdFontStyle style = REGULAR,
|
const EpdFontStyle style = REGULAR,
|
||||||
const EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH)
|
const EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH)
|
||||||
: Screen(renderer, inputManager),
|
: Screen(renderer, inputManager), text(std::move(text)), style(style), refreshMode(refreshMode) {}
|
||||||
text(std::move(text)),
|
|
||||||
style(style),
|
|
||||||
refreshMode(refreshMode) {}
|
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <InputManager.h>
|
#include <InputManager.h>
|
||||||
|
|
||||||
class EpdRenderer;
|
class GfxRenderer;
|
||||||
|
|
||||||
class Screen {
|
class Screen {
|
||||||
protected:
|
protected:
|
||||||
EpdRenderer& renderer;
|
GfxRenderer& renderer;
|
||||||
InputManager& inputManager;
|
InputManager& inputManager;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Screen(EpdRenderer& renderer, InputManager& inputManager) : renderer(renderer), inputManager(inputManager) {}
|
explicit Screen(GfxRenderer& renderer, InputManager& inputManager) : renderer(renderer), inputManager(inputManager) {}
|
||||||
virtual ~Screen() = default;
|
virtual ~Screen() = default;
|
||||||
virtual void onEnter() {}
|
virtual void onEnter() {}
|
||||||
virtual void onExit() {}
|
virtual void onExit() {}
|
||||||
|
|||||||
@ -1,19 +1,20 @@
|
|||||||
#include "SleepScreen.h"
|
#include "SleepScreen.h"
|
||||||
|
|
||||||
#include <EpdRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "images/CrossLarge.h"
|
#include "images/CrossLarge.h"
|
||||||
|
|
||||||
void SleepScreen::onEnter() {
|
void SleepScreen::onEnter() {
|
||||||
const auto pageWidth = renderer.getPageWidth();
|
const auto pageWidth = GfxRenderer::getScreenWidth();
|
||||||
const auto pageHeight = renderer.getPageHeight();
|
const auto pageHeight = GfxRenderer::getScreenHeight();
|
||||||
|
|
||||||
renderer.clearScreen();
|
renderer.clearScreen();
|
||||||
renderer.drawImage(CrossLarge, (pageHeight - 128) / 2, (pageWidth - 128) / 2, 128, 128);
|
renderer.drawImage(CrossLarge, (pageHeight - 128) / 2, (pageWidth - 128) / 2, 128, 128);
|
||||||
const int width = renderer.getUiTextWidth("CrossPoint", BOLD);
|
const int width = renderer.getTextWidth(UI_FONT_ID, "CrossPoint", BOLD);
|
||||||
renderer.drawUiText((pageWidth - width)/ 2, pageHeight / 2 + 70, "CrossPoint", true, BOLD);
|
renderer.drawText(UI_FONT_ID, (pageWidth - width) / 2, pageHeight / 2 + 70, "CrossPoint", true, BOLD);
|
||||||
const int bootingWidth = renderer.getSmallTextWidth("SLEEPING");
|
const int bootingWidth = renderer.getTextWidth(SMALL_FONT_ID, "SLEEPING");
|
||||||
renderer.drawSmallText((pageWidth - bootingWidth) / 2, pageHeight / 2 + 95, "SLEEPING");
|
renderer.drawText(SMALL_FONT_ID, (pageWidth - bootingWidth) / 2, pageHeight / 2 + 95, "SLEEPING");
|
||||||
renderer.invertScreen();
|
renderer.invertScreen();
|
||||||
renderer.flushDisplay(EInkDisplay::FULL_REFRESH);
|
renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,6 +3,6 @@
|
|||||||
|
|
||||||
class SleepScreen final : public Screen {
|
class SleepScreen final : public Screen {
|
||||||
public:
|
public:
|
||||||
explicit SleepScreen(EpdRenderer& renderer, InputManager& inputManager) : Screen(renderer, inputManager) {}
|
explicit SleepScreen(GfxRenderer& renderer, InputManager& inputManager) : Screen(renderer, inputManager) {}
|
||||||
void onEnter() override;
|
void onEnter() override;
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user