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

This commit is contained in:
Dave Allie 2025-12-08 02:40:29 +11:00
parent 9e046d7086
commit ba66f36f4c
No known key found for this signature in database
GPG Key ID: F2FDDB3AD8D0276F
20 changed files with 11079 additions and 6615 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,23 +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, bool pixelState, 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, bool pixelState = true, 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); 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 bool pixelState, 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;
@ -35,7 +39,7 @@ 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, pixelState, style); renderChar(cp, x, y, pixelState, style, mode);
} }
*y += fontFamily->getData(style)->advanceY; *y += fontFamily->getData(style)->advanceY;
@ -77,7 +81,7 @@ void EpdFontRenderer<Renderable>::drawPixel(const int x, const int y, const bool
template <typename Renderable> template <typename Renderable>
void EpdFontRenderer<Renderable>::renderChar(const uint32_t cp, int* x, const int* y, const bool pixelState, void EpdFontRenderer<Renderable>::renderChar(const uint32_t cp, int* x, const int* y, const bool pixelState,
const EpdFontStyle style) { 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?
@ -90,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;
@ -105,6 +110,20 @@ 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);
@ -114,6 +133,7 @@ void EpdFontRenderer<Renderable>::renderChar(const uint32_t cp, int* x, const in
} }
} }
} }
}
*x += glyph->advanceX; *x += glyph->advanceX;
} }

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);
@ -27,6 +27,7 @@ EpdRenderer::EpdRenderer(EInkDisplay& einkDisplay)
marginBottom(30), marginBottom(30),
marginLeft(10), marginLeft(10),
marginRight(10), marginRight(10),
fontRendererMode(BW),
lineCompression(0.95f) { lineCompression(0.95f) {
this->regularFontRenderer = new EpdFontRenderer<EInkDisplay>(&bookerlyFontFamily, einkDisplay); this->regularFontRenderer = new EpdFontRenderer<EInkDisplay>(&bookerlyFontFamily, einkDisplay);
this->smallFontRenderer = new EpdFontRenderer<EInkDisplay>(&smallFontFamily, einkDisplay); this->smallFontRenderer = new EpdFontRenderer<EInkDisplay>(&smallFontFamily, einkDisplay);
@ -101,21 +102,21 @@ void EpdRenderer::drawText(const int x, const int y, const char* text, const boo
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, state, style); regularFontRenderer->renderString(text, &xpos, &ypos, state, style, fontRendererMode);
} }
void EpdRenderer::drawUiText(const int x, const int y, const char* text, const bool state, 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, state, style); uiFontRenderer->renderString(text, &xpos, &ypos, state, style, fontRendererMode);
} }
void EpdRenderer::drawSmallText(const int x, const int y, const char* text, const bool state, 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, state, 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,
@ -221,3 +222,9 @@ int EpdRenderer::getSpaceWidth() const { return regularFontRenderer->fontFamily-
int EpdRenderer::getLineHeight() const { int EpdRenderer::getLineHeight() const {
return regularFontRenderer->fontFamily->getData(REGULAR)->advanceY * lineCompression; return regularFontRenderer->fontFamily->getData(REGULAR)->advanceY * lineCompression;
} }
void EpdRenderer::copyGrayscaleLsbBuffers() const { einkDisplay.copyGrayscaleLsbBuffers(einkDisplay.getFrameBuffer()); }
void EpdRenderer::copyGrayscaleMsbBuffers() const { einkDisplay.copyGrayscaleMsbBuffers(einkDisplay.getFrameBuffer()); }
void EpdRenderer::displayGrayBuffer() const { einkDisplay.displayGrayBuffer(); }

View File

@ -13,6 +13,7 @@ class EpdRenderer {
int marginBottom; int marginBottom;
int marginLeft; int marginLeft;
int marginRight; int marginRight;
EpdFontRendererMode fontRendererMode;
float lineCompression; float lineCompression;
public: public:
@ -32,6 +33,9 @@ class EpdRenderer {
void drawImage(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) const;
void drawImageNoMargin(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 copyGrayscaleLsbBuffers() const;
void copyGrayscaleMsbBuffers() const;
void displayGrayBuffer() const;
void clearScreen(uint8_t color = 0xFF) const; void clearScreen(uint8_t color = 0xFF) const;
void flushDisplay(bool partialUpdate = true) const; void flushDisplay(bool partialUpdate = true) const;
void flushArea(int x, int y, int width, int height) const; void flushArea(int x, int y, int width, int height) const;
@ -46,4 +50,5 @@ class EpdRenderer {
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

@ -1,6 +1,7 @@
#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"
@ -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;
} }
@ -184,16 +185,27 @@ void EpubReaderScreen::renderPage() {
} }
renderer.clearScreen(); renderer.clearScreen();
section->renderPage();
renderStatusBar(); if (section->pageCount == 0) {
if (pagesUntilFullRefresh <= 1) { Serial.println("No pages to render");
renderer.flushDisplay(false); const int width = renderer.getTextWidth("Empty chapter", BOLD);
pagesUntilFullRefresh = PAGES_PER_REFRESH; renderer.drawText((renderer.getPageWidth() - width) / 2, 300, "Empty chapter", true, BOLD);
} 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);
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 +216,30 @@ void EpubReaderScreen::renderPage() {
f.close(); f.close();
} }
void EpubReaderScreen::renderContents(const Page* p) const {
p->render(renderer);
renderStatusBar();
renderer.flushDisplay();
// 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) const;
void renderStatusBar() const; void renderStatusBar() const;
public: public: