Move to SDK EInkDisplay and enable anti-aliased 2-bit text (#5)

* First pass at moving to SDK EInkDisplay library

* Add 2-bit grayscale text and anti-aliased rendering of text

* Render status bar for empty chapters

* Refresh screen every 15 pages to avoid ghosting

* Simplify boot and sleep screens

* Give FileSelectionScreen task more stack memory

* Move text around slightly on Boot and Sleep screens

* Re-use existing buffer and write to whole screen for 'partial update'
This commit is contained in:
Dave Allie 2025-12-08 19:48:49 +11:00 committed by GitHub
parent de453fed1d
commit 2ed8017aa2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 11300 additions and 9243 deletions

View File

@ -31,4 +31,5 @@ typedef struct {
uint8_t advanceY; ///< Newline distance (y axis) uint8_t advanceY; ///< Newline distance (y axis)
int ascender; ///< Maximal height of a glyph above the base line int ascender; ///< Maximal height of a glyph above the base line
int descender; ///< Maximal height of a glyph below the base line int descender; ///< Maximal height of a glyph below the base line
bool is2Bit;
} EpdFontData; } EpdFontData;

View File

@ -2,6 +2,7 @@
* generated by fontconvert.py * generated by fontconvert.py
* name: babyblue * name: babyblue
* size: 8 * size: 8
* mode: 1-bit
*/ */
#pragma once #pragma once
#include "EpdFontData.h" #include "EpdFontData.h"
@ -500,5 +501,5 @@ static const EpdUnicodeInterval babyblueIntervals[] = {
}; };
static const EpdFontData babyblue = { static const EpdFontData babyblue = {
babyblueBitmaps, babyblueGlyphs, babyblueIntervals, 5, 17, 13, -4, babyblueBitmaps, babyblueGlyphs, babyblueIntervals, 5, 17, 13, -4, false,
}; };

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@
* generated by fontconvert.py * generated by fontconvert.py
* name: ubuntu_10 * name: ubuntu_10
* size: 10 * size: 10
* mode: 1-bit
*/ */
#pragma once #pragma once
#include "EpdFontData.h" #include "EpdFontData.h"
@ -762,5 +763,5 @@ static const EpdUnicodeInterval ubuntu_10Intervals[] = {
}; };
static const EpdFontData ubuntu_10 = { static const EpdFontData ubuntu_10 = {
ubuntu_10Bitmaps, ubuntu_10Glyphs, ubuntu_10Intervals, 31, 24, 20, -4, ubuntu_10Bitmaps, ubuntu_10Glyphs, ubuntu_10Intervals, 31, 24, 20, -4, false,
}; };

View File

@ -2,6 +2,7 @@
* generated by fontconvert.py * generated by fontconvert.py
* name: ubuntu_bold_10 * name: ubuntu_bold_10
* size: 10 * size: 10
* mode: 1-bit
*/ */
#pragma once #pragma once
#include "EpdFontData.h" #include "EpdFontData.h"
@ -806,5 +807,5 @@ static const EpdUnicodeInterval ubuntu_bold_10Intervals[] = {
}; };
static const EpdFontData ubuntu_bold_10 = { static const EpdFontData ubuntu_bold_10 = {
ubuntu_bold_10Bitmaps, ubuntu_bold_10Glyphs, ubuntu_bold_10Intervals, 31, 24, 20, -4, ubuntu_bold_10Bitmaps, ubuntu_bold_10Glyphs, ubuntu_bold_10Intervals, 31, 24, 20, -4, false,
}; };

View File

@ -13,12 +13,14 @@ parser = argparse.ArgumentParser(description="Generate a header file from a font
parser.add_argument("name", action="store", help="name of the font.") parser.add_argument("name", action="store", help="name of the font.")
parser.add_argument("size", type=int, help="font size to use.") parser.add_argument("size", type=int, help="font size to use.")
parser.add_argument("fontstack", action="store", nargs='+', help="list of font files, ordered by descending priority.") parser.add_argument("fontstack", action="store", nargs='+', help="list of font files, ordered by descending priority.")
parser.add_argument("--2bit", dest="is2Bit", action="store_true", help="generate 2-bit greyscale bitmap instead of 1-bit black and white.")
parser.add_argument("--additional-intervals", dest="additional_intervals", action="append", help="Additional code point intervals to export as min,max. This argument can be repeated.") parser.add_argument("--additional-intervals", dest="additional_intervals", action="append", help="Additional code point intervals to export as min,max. This argument can be repeated.")
args = parser.parse_args() args = parser.parse_args()
GlyphProps = namedtuple("GlyphProps", ["width", "height", "advance_x", "left", "top", "data_length", "data_offset", "code_point"]) GlyphProps = namedtuple("GlyphProps", ["width", "height", "advance_x", "left", "top", "data_length", "data_offset", "code_point"])
font_stack = [freetype.Face(f) for f in args.fontstack] font_stack = [freetype.Face(f) for f in args.fontstack]
is2Bit = args.is2Bit
size = args.size size = args.size
font_name = args.name font_name = args.name
@ -173,6 +175,42 @@ for i_start, i_end in intervals:
pixels4g.append(px) pixels4g.append(px)
px = 0 px = 0
if is2Bit:
# 0 = white, 15 black, 8+ dark grey, 7- light grey
# Downsample to 2-bit bitmap
pixels2b = []
px = 0
pitch = (bitmap.width // 2) + (bitmap.width % 2)
for y in range(bitmap.rows):
for x in range(bitmap.width):
px = px << 2
bm = pixels4g[y * pitch + (x // 2)]
bm = (bm >> ((x % 2) * 4)) & 0xF
if bm == 15:
px += 3
elif bm >= 8:
px += 2
elif bm > 0:
px += 1
if (y * bitmap.width + x) % 4 == 3:
pixels2b.append(px)
px = 0
if (bitmap.width * bitmap.rows) % 4 != 0:
px = px << (4 - (bitmap.width * bitmap.rows) % 4) * 2
pixels2b.append(px)
# for y in range(bitmap.rows):
# line = ''
# for x in range(bitmap.width):
# pixelPosition = y * bitmap.width + x
# byte = pixels2b[pixelPosition // 4]
# bit_index = (3 - (pixelPosition % 4)) * 2
# line += '#' if ((byte >> bit_index) & 3) > 0 else '.'
# print(line)
# print('')
else:
# Downsample to 1-bit bitmap - treat any non-zero as black # Downsample to 1-bit bitmap - treat any non-zero as black
pixelsbw = [] pixelsbw = []
px = 0 px = 0
@ -190,9 +228,20 @@ for i_start, i_end in intervals:
px = px << (8 - (bitmap.width * bitmap.rows) % 8) px = px << (8 - (bitmap.width * bitmap.rows) % 8)
pixelsbw.append(px) pixelsbw.append(px)
# for y in range(bitmap.rows):
# line = ''
# for x in range(bitmap.width):
# pixelPosition = y * bitmap.width + x
# byte = pixelsbw[pixelPosition // 8]
# bit_index = 7 - (pixelPosition % 8)
# line += '#' if (byte >> bit_index) & 1 else '.'
# print(line)
# print('')
pixels = pixels2b if is2Bit else pixelsbw
# Build output data # Build output data
packed = bytes(pixelsbw) packed = bytes(pixels)
glyph = GlyphProps( glyph = GlyphProps(
width = bitmap.width, width = bitmap.width,
height = bitmap.rows, height = bitmap.rows,
@ -216,7 +265,7 @@ for index, glyph in enumerate(all_glyphs):
glyph_data.extend([b for b in packed]) glyph_data.extend([b for b in packed])
glyph_props.append(props) glyph_props.append(props)
print(f"/**\n * generated by fontconvert.py\n * name: {font_name}\n * size: {size}\n */") print(f"/**\n * generated by fontconvert.py\n * name: {font_name}\n * size: {size}\n * mode: {'2-bit' if is2Bit else '1-bit'}\n */")
print("#pragma once") print("#pragma once")
print("#include \"EpdFontData.h\"\n") print("#include \"EpdFontData.h\"\n")
print(f"static const uint8_t {font_name}Bitmaps[{len(glyph_data)}] = {{") print(f"static const uint8_t {font_name}Bitmaps[{len(glyph_data)}] = {{")
@ -244,4 +293,5 @@ print(f" {len(intervals)},")
print(f" {norm_ceil(face.size.height)},") print(f" {norm_ceil(face.size.height)},")
print(f" {norm_ceil(face.size.ascender)},") print(f" {norm_ceil(face.size.ascender)},")
print(f" {norm_floor(face.size.descender)},") print(f" {norm_floor(face.size.descender)},")
print(f" {'true' if is2Bit else 'false'},")
print("};") print("};")

View File

@ -6,22 +6,27 @@
inline int min(const int a, const int b) { return a < b ? a : b; } 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; } inline int max(const int a, const int b) { return a > b ? a : b; }
enum EpdFontRendererMode { BW, GRAYSCALE_LSB, GRAYSCALE_MSB };
template <typename Renderable> template <typename Renderable>
class EpdFontRenderer { class EpdFontRenderer {
Renderable& renderer; Renderable& renderer;
void renderChar(uint32_t cp, int* x, const int* y, uint16_t color, EpdFontStyle style = REGULAR); void renderChar(uint32_t cp, int* x, const int* y, bool pixelState, EpdFontStyle style = REGULAR,
EpdFontRendererMode mode = BW);
public: public:
const EpdFontFamily* fontFamily; const EpdFontFamily* fontFamily;
explicit EpdFontRenderer(const EpdFontFamily* fontFamily, Renderable& renderer) explicit EpdFontRenderer(const EpdFontFamily* fontFamily, Renderable& renderer)
: fontFamily(fontFamily), renderer(renderer) {} : fontFamily(fontFamily), renderer(renderer) {}
~EpdFontRenderer() = default; ~EpdFontRenderer() = default;
void renderString(const char* string, int* x, int* y, uint16_t color, EpdFontStyle style = REGULAR); 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> 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 bool pixelState,
const EpdFontStyle style) { const EpdFontStyle style, const EpdFontRendererMode mode) {
// cannot draw a NULL / empty string // cannot draw a NULL / empty string
if (string == nullptr || *string == '\0') { if (string == nullptr || *string == '\0') {
return; return;
@ -34,15 +39,49 @@ void EpdFontRenderer<Renderable>::renderString(const char* string, int* x, int*
uint32_t cp; uint32_t cp;
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&string)))) { while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&string)))) {
renderChar(cp, x, y, color, style); renderChar(cp, x, y, pixelState, style, mode);
} }
*y += fontFamily->getData(style)->advanceY; *y += fontFamily->getData(style)->advanceY;
} }
// TODO: Consolidate this with EpdRenderer implementation
template <typename Renderable> template <typename Renderable>
void EpdFontRenderer<Renderable>::renderChar(const uint32_t cp, int* x, const int* y, uint16_t color, void EpdFontRenderer<Renderable>::drawPixel(const int x, const int y, const bool pixelState) {
const EpdFontStyle style) { 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); const EpdGlyph* glyph = fontFamily->getGlyph(cp, style);
if (!glyph) { if (!glyph) {
// TODO: Replace with fallback glyph property? // TODO: Replace with fallback glyph property?
@ -55,6 +94,7 @@ void EpdFontRenderer<Renderable>::renderChar(const uint32_t cp, int* x, const in
return; return;
} }
const int is2Bit = fontFamily->getData(style)->is2Bit;
const uint32_t offset = glyph->dataOffset; const uint32_t offset = glyph->dataOffset;
const uint8_t width = glyph->width; const uint8_t width = glyph->width;
const uint8_t height = glyph->height; const uint8_t height = glyph->height;
@ -70,11 +110,26 @@ void EpdFontRenderer<Renderable>::renderChar(const uint32_t cp, int* x, const in
const int pixelPosition = glyphY * width + glyphX; const int pixelPosition = glyphY * width + glyphX;
int screenX = *x + left + 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 byte = bitmap[pixelPosition / 8];
const uint8_t bit_index = 7 - (pixelPosition % 8); const uint8_t bit_index = 7 - (pixelPosition % 8);
if ((byte >> bit_index) & 1) { if ((byte >> bit_index) & 1) {
renderer.drawPixel(screenX, screenY, color); drawPixel(screenX, screenY, pixelState);
}
} }
} }
} }

View File

@ -1,17 +1,17 @@
#include "EpdRenderer.h" #include "EpdRenderer.h"
#include "builtinFonts/babyblue.h" #include "builtinFonts/babyblue.h"
#include "builtinFonts/bookerly.h" #include "builtinFonts/bookerly_2b.h"
#include "builtinFonts/bookerly_bold.h" #include "builtinFonts/bookerly_bold_2b.h"
#include "builtinFonts/bookerly_bold_italic.h" #include "builtinFonts/bookerly_bold_italic_2b.h"
#include "builtinFonts/bookerly_italic.h" #include "builtinFonts/bookerly_italic_2b.h"
#include "builtinFonts/ubuntu_10.h" #include "builtinFonts/ubuntu_10.h"
#include "builtinFonts/ubuntu_bold_10.h" #include "builtinFonts/ubuntu_bold_10.h"
EpdFont bookerlyFont(&bookerly); EpdFont bookerlyFont(&bookerly_2b);
EpdFont bookerlyBoldFont(&bookerly_bold); EpdFont bookerlyBoldFont(&bookerly_bold_2b);
EpdFont bookerlyItalicFont(&bookerly_italic); EpdFont bookerlyItalicFont(&bookerly_italic_2b);
EpdFont bookerlyBoldItalicFont(&bookerly_bold_italic); EpdFont bookerlyBoldItalicFont(&bookerly_bold_italic_2b);
EpdFontFamily bookerlyFontFamily(&bookerlyFont, &bookerlyBoldFont, &bookerlyItalicFont, &bookerlyBoldItalicFont); EpdFontFamily bookerlyFontFamily(&bookerlyFont, &bookerlyBoldFont, &bookerlyItalicFont, &bookerlyBoldItalicFont);
EpdFont smallFont(&babyblue); EpdFont smallFont(&babyblue);
@ -21,11 +21,17 @@ EpdFont ubuntu10Font(&ubuntu_10);
EpdFont ununtuBold10Font(&ubuntu_bold_10); EpdFont ununtuBold10Font(&ubuntu_bold_10);
EpdFontFamily ubuntuFontFamily(&ubuntu10Font, &ununtuBold10Font); EpdFontFamily ubuntuFontFamily(&ubuntu10Font, &ununtuBold10Font);
EpdRenderer::EpdRenderer(XteinkDisplay& display) EpdRenderer::EpdRenderer(EInkDisplay& einkDisplay)
: display(display), marginTop(11), marginBottom(30), marginLeft(10), marginRight(10), lineCompression(0.95f) { : einkDisplay(einkDisplay),
this->regularFontRenderer = new EpdFontRenderer<XteinkDisplay>(&bookerlyFontFamily, display); marginTop(11),
this->smallFontRenderer = new EpdFontRenderer<XteinkDisplay>(&smallFontFamily, display); marginBottom(30),
this->uiFontRenderer = new EpdFontRenderer<XteinkDisplay>(&ubuntuFontFamily, display); 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() { EpdRenderer::~EpdRenderer() {
@ -34,6 +40,40 @@ EpdRenderer::~EpdRenderer() {
delete uiFontRenderer; 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 EpdRenderer::getTextWidth(const char* text, const EpdFontStyle style) const {
int w = 0, h = 0; int w = 0, h = 0;
@ -58,25 +98,25 @@ int EpdRenderer::getSmallTextWidth(const char* text, const EpdFontStyle style) c
return w; return w;
} }
void EpdRenderer::drawText(const int x, const int y, const char* text, const uint16_t color, void EpdRenderer::drawText(const int x, const int y, const char* text, const bool state,
const EpdFontStyle style) const { const EpdFontStyle style) const {
int ypos = y + getLineHeight() + marginTop; int ypos = y + getLineHeight() + marginTop;
int xpos = x + marginLeft; int xpos = x + marginLeft;
regularFontRenderer->renderString(text, &xpos, &ypos, color > 0 ? GxEPD_BLACK : GxEPD_WHITE, style); regularFontRenderer->renderString(text, &xpos, &ypos, state, style, fontRendererMode);
} }
void EpdRenderer::drawUiText(const int x, const int y, const char* text, const uint16_t color, void EpdRenderer::drawUiText(const int x, const int y, const char* text, const bool state,
const EpdFontStyle style) const { const EpdFontStyle style) const {
int ypos = y + uiFontRenderer->fontFamily->getData(style)->advanceY + marginTop; int ypos = y + uiFontRenderer->fontFamily->getData(style)->advanceY + marginTop;
int xpos = x + marginLeft; int xpos = x + marginLeft;
uiFontRenderer->renderString(text, &xpos, &ypos, color > 0 ? GxEPD_BLACK : GxEPD_WHITE, style); uiFontRenderer->renderString(text, &xpos, &ypos, state, style, fontRendererMode);
} }
void EpdRenderer::drawSmallText(const int x, const int y, const char* text, const uint16_t color, void EpdRenderer::drawSmallText(const int x, const int y, const char* text, const bool state,
const EpdFontStyle style) const { const EpdFontStyle style) const {
int ypos = y + smallFontRenderer->fontFamily->getData(style)->advanceY + marginTop; int ypos = y + smallFontRenderer->fontFamily->getData(style)->advanceY + marginTop;
int xpos = x + marginLeft; int xpos = x + marginLeft;
smallFontRenderer->renderString(text, &xpos, &ypos, color > 0 ? GxEPD_BLACK : GxEPD_WHITE, style); 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, void EpdRenderer::drawTextBox(const int x, const int y, const std::string& text, const int width, const int height,
@ -88,7 +128,7 @@ void EpdRenderer::drawTextBox(const int x, const int y, const std::string& text,
int ypos = 0; int ypos = 0;
while (true) { while (true) {
if (end >= length) { if (end >= length) {
drawText(x, y + ypos, text.substr(start, length - start).c_str(), 1, style); drawText(x, y + ypos, text.substr(start, length - start).c_str(), true, style);
break; break;
} }
@ -97,7 +137,7 @@ void EpdRenderer::drawTextBox(const int x, const int y, const std::string& text,
} }
if (text[end - 1] == '\n') { if (text[end - 1] == '\n') {
drawText(x, y + ypos, text.substr(start, end - start).c_str(), 1, style); drawText(x, y + ypos, text.substr(start, end - start).c_str(), true, style);
ypos += getLineHeight(); ypos += getLineHeight();
start = end; start = end;
end = start + 1; end = start + 1;
@ -105,7 +145,7 @@ void EpdRenderer::drawTextBox(const int x, const int y, const std::string& text,
} }
if (getTextWidth(text.substr(start, end - start).c_str(), style) > width) { 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); drawText(x, y + ypos, text.substr(start, end - start - 1).c_str(), true, style);
ypos += getLineHeight(); ypos += getLineHeight();
start = end - 1; start = end - 1;
continue; continue;
@ -115,54 +155,88 @@ void EpdRenderer::drawTextBox(const int x, const int y, const std::string& text,
} }
} }
void EpdRenderer::drawLine(int x1, int y1, int x2, int y2, uint16_t color) const { void EpdRenderer::drawLine(int x1, int y1, int x2, int y2, const bool state) const {
display.drawLine(x1 + marginLeft, y1 + marginTop, x2 + marginLeft, y2 + marginTop, if (x1 == x2) {
color > 0 ? GxEPD_BLACK : GxEPD_WHITE); 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 uint16_t color) const { void EpdRenderer::drawRect(const int x, const int y, const int width, const int height, const bool state) const {
display.drawRect(x + marginLeft, y + marginTop, width, height, color > 0 ? GxEPD_BLACK : GxEPD_WHITE); 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 uint16_t color) const { void EpdRenderer::fillRect(const int x, const int y, const int width, const int height, const bool state) const {
display.fillRect(x + marginLeft, y + marginTop, width, height, color > 0 ? GxEPD_BLACK : GxEPD_WHITE); for (int fillY = y; fillY < y + height; fillY++) {
drawLine(x, fillY, x + width - 1, fillY, state);
}
} }
void EpdRenderer::drawCircle(const int x, const int y, const int radius, const uint16_t color) const { void EpdRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const {
display.drawCircle(x + marginLeft, y + marginTop, radius, color > 0 ? GxEPD_BLACK : GxEPD_WHITE); drawImageNoMargin(bitmap, x + marginLeft, y + marginTop, width, height);
} }
void EpdRenderer::fillCircle(const int x, const int y, const int radius, const uint16_t color) const { // TODO: Support y-mirror?
display.fillCircle(x + marginLeft, y + marginTop, radius, color > 0 ? GxEPD_BLACK : GxEPD_WHITE); 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::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height, void EpdRenderer::clearScreen(const uint8_t color) const {
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"); Serial.println("Clearing screen");
display.fillScreen(black ? GxEPD_BLACK : GxEPD_WHITE); einkDisplay.clearScreen(color);
} }
void EpdRenderer::flushDisplay(const bool partialUpdate) const { display.display(partialUpdate); } void EpdRenderer::invertScreen() const {
uint8_t *buffer = einkDisplay.getFrameBuffer();
void EpdRenderer::flushArea(const int x, const int y, const int width, const int height) const { for (int i = 0; i < EInkDisplay::BUFFER_SIZE; i++) {
display.displayWindow(x + marginLeft, y + marginTop, width, height); buffer[i] = ~buffer[i];
}
} }
int EpdRenderer::getPageWidth() const { return display.width() - marginLeft - marginRight; } void EpdRenderer::flushDisplay(const EInkDisplay::RefreshMode refreshMode) const {
einkDisplay.displayBuffer(refreshMode);
}
int EpdRenderer::getPageHeight() const { return display.height() - marginTop - marginBottom; } // 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::getSpaceWidth() const { return regularFontRenderer->fontFamily->getGlyph(' ', REGULAR)->advanceX; }
int EpdRenderer::getLineHeight() const { int EpdRenderer::getLineHeight() const {
return regularFontRenderer->fontFamily->getData(REGULAR)->advanceY * lineCompression; 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(); }

View File

@ -1,52 +1,58 @@
#pragma once #pragma once
#include <GxEPD2_BW.h> #include <EInkDisplay.h>
#include <EpdFontRenderer.hpp> #include <EpdFontRenderer.hpp>
#define XteinkDisplay GxEPD2_BW<GxEPD2_426_GDEQ0426T82, GxEPD2_426_GDEQ0426T82::HEIGHT>
class EpdRenderer { class EpdRenderer {
XteinkDisplay& display; EInkDisplay& einkDisplay;
EpdFontRenderer<XteinkDisplay>* regularFontRenderer; EpdFontRenderer<EInkDisplay>* regularFontRenderer;
EpdFontRenderer<XteinkDisplay>* smallFontRenderer; EpdFontRenderer<EInkDisplay>* smallFontRenderer;
EpdFontRenderer<XteinkDisplay>* uiFontRenderer; EpdFontRenderer<EInkDisplay>* uiFontRenderer;
int marginTop; int marginTop;
int marginBottom; int marginBottom;
int marginLeft; int marginLeft;
int marginRight; int marginRight;
EpdFontRendererMode fontRendererMode;
float lineCompression; float lineCompression;
public: public:
explicit EpdRenderer(XteinkDisplay& display); explicit EpdRenderer(EInkDisplay& einkDisplay);
~EpdRenderer(); ~EpdRenderer();
void drawPixel(int x, int y, bool state = true) const;
int getTextWidth(const char* text, EpdFontStyle style = REGULAR) const; int getTextWidth(const char* text, EpdFontStyle style = REGULAR) const;
int getUiTextWidth(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; 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 drawText(int x, int y, const char* text, bool state = true, EpdFontStyle style = REGULAR) const;
void drawUiText(int x, int y, const char* text, uint16_t color = 1, 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, uint16_t color = 1, 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 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 drawLine(int x1, int y1, int x2, int y2, bool state = true) const;
void drawRect(int x, int y, int width, int height, uint16_t color = 1) 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, uint16_t color = 1) const; void fillRect(int x, int y, int width, int height, bool state = true) const;
void drawCircle(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) const;
void fillCircle(int x, int y, int radius, uint16_t color = 1) const; void drawImageNoMargin(const uint8_t bitmap[], int x, int y, int width, int height) const;
void drawImage(const uint8_t bitmap[], int x, int y, int width, int height, bool invert = false,
bool mirrorY = false) const; void swapBuffers() const;
void drawImageNoMargin(const uint8_t bitmap[], int x, int y, int width, int height, bool invert = false, void copyGrayscaleLsbBuffers() const;
bool mirrorY = false) const; void copyGrayscaleMsbBuffers() const;
void clearScreen(bool black = false) const; void displayGrayBuffer() const;
void flushDisplay(bool partialUpdate = true) const; void clearScreen(uint8_t color = 0xFF) const;
void flushArea(int x, int y, int width, int height) 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 getPageWidth() const;
int getPageHeight() const; int getPageHeight() const;
int getSpaceWidth() const; int getSpaceWidth() const;
int getLineHeight() const; int getLineHeight() const;
// set margins // set margins
void setMarginTop(const int newMarginTop) { this->marginTop = newMarginTop; } void setMarginTop(const int newMarginTop) { this->marginTop = newMarginTop; }
void setMarginBottom(const int newMarginBottom) { this->marginBottom = newMarginBottom; } void setMarginBottom(const int newMarginBottom) { this->marginBottom = newMarginBottom; }
void setMarginLeft(const int newMarginLeft) { this->marginLeft = newMarginLeft; } void setMarginLeft(const int newMarginLeft) { this->marginLeft = newMarginLeft; }
void setMarginRight(const int newMarginRight) { this->marginRight = newMarginRight; } void setMarginRight(const int newMarginRight) { this->marginRight = newMarginRight; }
void setFontRendererMode(const EpdFontRendererMode mode) { this->fontRendererMode = mode; }
}; };

View File

@ -98,21 +98,15 @@ bool Section::persistPageDataToSD() {
return true; return true;
} }
void Section::renderPage() const { Page* Section::loadPageFromSD() const {
if (0 <= currentPage && currentPage < pageCount) {
const auto filePath = "/sd" + cachePath + "/page_" + std::to_string(currentPage) + ".bin"; const auto filePath = "/sd" + cachePath + "/page_" + std::to_string(currentPage) + ".bin";
std::ifstream inputFile(filePath); if (!SD.exists(filePath.c_str() + 3)) {
const Page* p = Page::deserialize(inputFile); Serial.printf("Page file does not exist: %s\n", filePath.c_str());
inputFile.close(); return nullptr;
p->render(renderer);
delete p;
} else if (pageCount == 0) {
Serial.println("No pages to render");
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", BOLD);
renderer.drawText((renderer.getPageWidth() - width) / 2, 300, "Out of bounds", 1, BOLD);
} }
std::ifstream inputFile(filePath);
Page* p = Page::deserialize(inputFile);
inputFile.close();
return p;
} }

View File

@ -26,5 +26,5 @@ class Section {
void setupCacheDir() const; void setupCacheDir() const;
void clearCache() const; void clearCache() const;
bool persistPageDataToSD(); bool persistPageDataToSD();
void renderPage() const; Page* loadPageFromSD() const;
}; };

View File

@ -165,7 +165,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(), 1, fontStyle); renderer.drawText(x + wordXpos[i], y, words[i].c_str(), true, fontStyle);
} }
} }

@ -1 +1 @@
Subproject commit 8224d278c58e76abf781c2e015f28a09419f27b2 Subproject commit a126d4b0bf66cd2895d11748774f7ec2c366cc4c

View File

@ -34,7 +34,7 @@ build_flags =
; Libraries ; Libraries
lib_deps = lib_deps =
zinggjm/GxEPD2@^1.6.5
https://github.com/leethomason/tinyxml2.git#11.0.0 https://github.com/leethomason/tinyxml2.git#11.0.0
BatteryMonitor=symlink://open-x4-sdk/libs/hardware/BatteryMonitor BatteryMonitor=symlink://open-x4-sdk/libs/hardware/BatteryMonitor
InputManager=symlink://open-x4-sdk/libs/hardware/InputManager InputManager=symlink://open-x4-sdk/libs/hardware/InputManager
EInkDisplay=symlink://open-x4-sdk/libs/display/EInkDisplay

View File

@ -1,7 +1,7 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
extern const uint8_t CrossLarge[] = { static const uint8_t CrossLarge[] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
#include <Arduino.h> #include <Arduino.h>
#include <EInkDisplay.h>
#include <EpdRenderer.h> #include <EpdRenderer.h>
#include <Epub.h> #include <Epub.h>
#include <GxEPD2_BW.h>
#include <InputManager.h> #include <InputManager.h>
#include <SD.h> #include <SD.h>
#include <SPI.h> #include <SPI.h>
@ -28,10 +28,9 @@
#define SD_SPI_CS 12 #define SD_SPI_CS 12
#define SD_SPI_MISO 7 #define SD_SPI_MISO 7
GxEPD2_BW<GxEPD2_426_GDEQ0426T82, GxEPD2_426_GDEQ0426T82::HEIGHT> display(GxEPD2_426_GDEQ0426T82(EPD_CS, EPD_DC, EInkDisplay einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY);
EPD_RST, EPD_BUSY));
InputManager inputManager; InputManager inputManager;
EpdRenderer renderer(display); EpdRenderer renderer(einkDisplay);
Screen* currentScreen; Screen* currentScreen;
CrossPointState appState; CrossPointState appState;
@ -123,7 +122,7 @@ void enterDeepSleep() {
// Enable Wakeup on LOW (button press) // Enable Wakeup on LOW (button press)
esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW); esp_deep_sleep_enable_gpio_wakeup(1ULL << InputManager::POWER_BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_LOW);
display.hibernate(); einkDisplay.deepSleep();
// Enter Deep Sleep // Enter Deep Sleep
esp_deep_sleep_start(); esp_deep_sleep_start();
@ -142,7 +141,7 @@ 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, false, false)); enterNewScreen(new FullScreenMessageScreen(renderer, inputManager, "Failed to load epub", REGULAR, EInkDisplay::HALF_REFRESH));
delay(2000); delay(2000);
onGoHome(); onGoHome();
} }
@ -170,10 +169,7 @@ void setup() {
SPI.begin(EPD_SCLK, SD_SPI_MISO, EPD_MOSI, EPD_CS); SPI.begin(EPD_SCLK, SD_SPI_MISO, EPD_MOSI, EPD_CS);
// Initialize display // Initialize display
const SPISettings spi_settings(SPI_FQ, MSBFIRST, SPI_MODE0); einkDisplay.begin();
display.init(115200, true, 2, false, SPI, spi_settings);
display.setRotation(3); // 270 degrees
display.setTextColor(GxEPD_BLACK);
Serial.println("Display initialized"); Serial.println("Display initialized");
exitScreen(); exitScreen();

View File

@ -11,4 +11,9 @@ void BootLogoScreen::onEnter() {
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);
renderer.drawUiText((pageWidth - width)/ 2, pageHeight / 2 + 70, "CrossPoint", true, BOLD);
const int bootingWidth = renderer.getSmallTextWidth("BOOTING");
renderer.drawSmallText((pageWidth - bootingWidth) / 2, pageHeight / 2 + 95, "BOOTING");
renderer.flushDisplay();
} }

View File

@ -1,11 +1,12 @@
#include "EpubReaderScreen.h" #include "EpubReaderScreen.h"
#include <EpdRenderer.h> #include <EpdRenderer.h>
#include <Epub/Page.h>
#include <SD.h> #include <SD.h>
#include "Battery.h" #include "Battery.h"
constexpr int PAGES_PER_REFRESH = 20; constexpr int PAGES_PER_REFRESH = 15;
constexpr unsigned long SKIP_CHAPTER_MS = 700; constexpr unsigned long SKIP_CHAPTER_MS = 700;
void EpubReaderScreen::taskTrampoline(void* param) { void EpubReaderScreen::taskTrampoline(void* param) {
@ -128,7 +129,7 @@ void EpubReaderScreen::displayTaskLoop() {
if (updateRequired) { if (updateRequired) {
updateRequired = false; updateRequired = false;
xSemaphoreTake(renderingMutex, portMAX_DELAY); xSemaphoreTake(renderingMutex, portMAX_DELAY);
renderPage(); renderScreen();
xSemaphoreGive(renderingMutex); xSemaphoreGive(renderingMutex);
} }
vTaskDelay(10 / portTICK_PERIOD_MS); vTaskDelay(10 / portTICK_PERIOD_MS);
@ -136,7 +137,7 @@ void EpubReaderScreen::displayTaskLoop() {
} }
// TODO: Failure handling // TODO: Failure handling
void EpubReaderScreen::renderPage() { void EpubReaderScreen::renderScreen() {
if (!epub) { if (!epub) {
return; return;
} }
@ -159,10 +160,12 @@ void EpubReaderScreen::renderPage() {
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() + margin * 2;
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(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.flushArea(x, y, w, h); renderer.flushDisplay(EInkDisplay::HALF_REFRESH);
pagesUntilFullRefresh = 0;
} }
section->setupCacheDir(); section->setupCacheDir();
@ -184,16 +187,29 @@ void EpubReaderScreen::renderPage() {
} }
renderer.clearScreen(); renderer.clearScreen();
section->renderPage();
if (section->pageCount == 0) {
Serial.println("No pages to render");
const int width = renderer.getTextWidth("Empty chapter", BOLD);
renderer.drawText((renderer.getPageWidth() - width) / 2, 300, "Empty chapter", true, BOLD);
renderStatusBar(); renderStatusBar();
if (pagesUntilFullRefresh <= 1) {
renderer.flushDisplay(false);
pagesUntilFullRefresh = PAGES_PER_REFRESH;
} else {
renderer.flushDisplay(); renderer.flushDisplay();
pagesUntilFullRefresh--; return;
} }
if (section->currentPage < 0 || 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);
renderer.drawText((renderer.getPageWidth() - width) / 2, 300, "Out of bounds", true, BOLD);
renderStatusBar();
renderer.flushDisplay();
return;
}
const Page* p = section->loadPageFromSD();
renderContents(p);
delete p;
File f = SD.open((epub->getCachePath() + "/progress.bin").c_str(), FILE_WRITE); File f = SD.open((epub->getCachePath() + "/progress.bin").c_str(), FILE_WRITE);
uint8_t data[4]; uint8_t data[4];
data[0] = currentSpineIndex & 0xFF; data[0] = currentSpineIndex & 0xFF;
@ -204,6 +220,36 @@ void EpubReaderScreen::renderPage() {
f.close(); f.close();
} }
void EpubReaderScreen::renderContents(const Page* p) {
p->render(renderer);
renderStatusBar();
if (pagesUntilFullRefresh <= 1) {
renderer.flushDisplay(EInkDisplay::HALF_REFRESH);
pagesUntilFullRefresh = PAGES_PER_REFRESH;
} else {
renderer.flushDisplay();
pagesUntilFullRefresh--;
}
// grayscale rendering
{
renderer.clearScreen(0x00);
renderer.setFontRendererMode(GRAYSCALE_LSB);
p->render(renderer);
renderer.copyGrayscaleLsbBuffers();
// Render and copy to MSB buffer
renderer.clearScreen(0x00);
renderer.setFontRendererMode(GRAYSCALE_MSB);
p->render(renderer);
renderer.copyGrayscaleMsbBuffers();
// display grayscale part
renderer.displayGrayBuffer();
renderer.setFontRendererMode(BW);
}
}
void EpubReaderScreen::renderStatusBar() const { void EpubReaderScreen::renderStatusBar() const {
const auto pageWidth = renderer.getPageWidth(); const auto pageWidth = renderer.getPageWidth();

View File

@ -20,7 +20,8 @@ class EpubReaderScreen final : public Screen {
static void taskTrampoline(void* param); static void taskTrampoline(void* param);
[[noreturn]] void displayTaskLoop(); [[noreturn]] void displayTaskLoop();
void renderPage(); void renderScreen();
void renderContents(const Page *p);
void renderStatusBar() const; void renderStatusBar() const;
public: public:

View File

@ -51,7 +51,7 @@ void FileSelectionScreen::onEnter() {
updateRequired = true; updateRequired = true;
xTaskCreate(&FileSelectionScreen::taskTrampoline, "FileSelectionScreenTask", xTaskCreate(&FileSelectionScreen::taskTrampoline, "FileSelectionScreenTask",
1024, // Stack size 2048, // Stack size
this, // Parameters this, // Parameters
1, // Priority 1, // Priority
&displayTaskHandle // Task handle &displayTaskHandle // Task handle
@ -120,7 +120,7 @@ void FileSelectionScreen::render() const {
const auto pageWidth = renderer.getPageWidth(); const auto pageWidth = renderer.getPageWidth();
const auto titleWidth = renderer.getTextWidth("CrossPoint Reader", BOLD); const auto titleWidth = renderer.getTextWidth("CrossPoint Reader", BOLD);
renderer.drawText((pageWidth - titleWidth) / 2, 0, "CrossPoint Reader", 1, BOLD); renderer.drawText((pageWidth - titleWidth) / 2, 0, "CrossPoint Reader", true, BOLD);
if (files.empty()) { if (files.empty()) {
renderer.drawUiText(10, 50, "No EPUBs found"); renderer.drawUiText(10, 50, "No EPUBs found");
@ -130,7 +130,7 @@ void FileSelectionScreen::render() const {
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 ? 0 : 1); renderer.drawUiText(10, 50 + i * 30, file.c_str(), i != selectorIndex);
} }
} }

View File

@ -8,7 +8,7 @@ void FullScreenMessageScreen::onEnter() {
const auto left = (renderer.getPageWidth() - width) / 2; const auto left = (renderer.getPageWidth() - width) / 2;
const auto top = (renderer.getPageHeight() - height) / 2; const auto top = (renderer.getPageHeight() - height) / 2;
renderer.clearScreen(invert); renderer.clearScreen();
renderer.drawUiText(left, top, text.c_str(), invert ? 0 : 1, style); renderer.drawUiText(left, top, text.c_str(), true, style);
renderer.flushDisplay(partialUpdate); renderer.flushDisplay(refreshMode);
} }

View File

@ -2,23 +2,22 @@
#include <string> #include <string>
#include <utility> #include <utility>
#include "EpdFontFamily.h" #include <EInkDisplay.h>
#include <EpdFontFamily.h>
#include "Screen.h" #include "Screen.h"
class FullScreenMessageScreen final : public Screen { class FullScreenMessageScreen final : public Screen {
std::string text; std::string text;
EpdFontStyle style; EpdFontStyle style;
bool invert; EInkDisplay::RefreshMode refreshMode;
bool partialUpdate;
public: public:
explicit FullScreenMessageScreen(EpdRenderer& renderer, InputManager& inputManager, std::string text, explicit FullScreenMessageScreen(EpdRenderer& renderer, InputManager& inputManager, std::string text,
const EpdFontStyle style = REGULAR, const bool invert = false, const EpdFontStyle style = REGULAR,
const bool partialUpdate = true) const EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH)
: Screen(renderer, inputManager), : Screen(renderer, inputManager),
text(std::move(text)), text(std::move(text)),
style(style), style(style),
invert(invert), refreshMode(refreshMode) {}
partialUpdate(partialUpdate) {}
void onEnter() override; void onEnter() override;
}; };

View File

@ -2,6 +2,18 @@
#include <EpdRenderer.h> #include <EpdRenderer.h>
#include "images/SleepScreenImg.h" #include "images/CrossLarge.h"
void SleepScreen::onEnter() { renderer.drawImageNoMargin(SleepScreenImg, 0, 0, 800, 480, false, true); } void SleepScreen::onEnter() {
const auto pageWidth = renderer.getPageWidth();
const auto pageHeight = renderer.getPageHeight();
renderer.clearScreen();
renderer.drawImage(CrossLarge, (pageHeight - 128) / 2, (pageWidth - 128) / 2, 128, 128);
const int width = renderer.getUiTextWidth("CrossPoint", BOLD);
renderer.drawUiText((pageWidth - width)/ 2, pageHeight / 2 + 70, "CrossPoint", true, BOLD);
const int bootingWidth = renderer.getSmallTextWidth("SLEEPING");
renderer.drawSmallText((pageWidth - bootingWidth) / 2, pageHeight / 2 + 95, "SLEEPING");
renderer.invertScreen();
renderer.flushDisplay(EInkDisplay::FULL_REFRESH);
}