A fork of https://github.com/crosspoint-reader/crosspoint-reader
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Remove EpdRenderer and create new GfxRenderer

+564 -590
-139
lib/EpdFontRenderer/EpdFontRenderer.hpp
··· 1 - #pragma once 2 - #include <EpdFontFamily.h> 3 - #include <HardwareSerial.h> 4 - #include <Utf8.h> 5 - 6 - inline int min(const int a, const int b) { return a < b ? a : b; } 7 - inline int max(const int a, const int b) { return a > b ? a : b; } 8 - 9 - enum EpdFontRendererMode { BW, GRAYSCALE_LSB, GRAYSCALE_MSB }; 10 - 11 - template <typename Renderable> 12 - class EpdFontRenderer { 13 - Renderable& renderer; 14 - void renderChar(uint32_t cp, int* x, const int* y, bool pixelState, EpdFontStyle style = REGULAR, 15 - EpdFontRendererMode mode = BW); 16 - 17 - public: 18 - const EpdFontFamily* fontFamily; 19 - explicit EpdFontRenderer(const EpdFontFamily* fontFamily, Renderable& renderer) 20 - : fontFamily(fontFamily), renderer(renderer) {} 21 - ~EpdFontRenderer() = default; 22 - void renderString(const char* string, int* x, int* y, bool pixelState = true, EpdFontStyle style = REGULAR, 23 - EpdFontRendererMode mode = BW); 24 - void drawPixel(int x, int y, bool pixelState); 25 - }; 26 - 27 - template <typename Renderable> 28 - void EpdFontRenderer<Renderable>::renderString(const char* string, int* x, int* y, const bool pixelState, 29 - const EpdFontStyle style, const EpdFontRendererMode mode) { 30 - // cannot draw a NULL / empty string 31 - if (string == nullptr || *string == '\0') { 32 - return; 33 - } 34 - 35 - // no printable characters 36 - if (!fontFamily->hasPrintableChars(string, style)) { 37 - return; 38 - } 39 - 40 - uint32_t cp; 41 - while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&string)))) { 42 - renderChar(cp, x, y, pixelState, style, mode); 43 - } 44 - 45 - *y += fontFamily->getData(style)->advanceY; 46 - } 47 - 48 - // TODO: Consolidate this with EpdRenderer implementation 49 - template <typename Renderable> 50 - void EpdFontRenderer<Renderable>::drawPixel(const int x, const int y, const bool pixelState) { 51 - uint8_t* frameBuffer = renderer.getFrameBuffer(); 52 - 53 - // Early return if no framebuffer is set 54 - if (!frameBuffer) { 55 - Serial.printf("!!No framebuffer\n"); 56 - return; 57 - } 58 - 59 - // Bounds checking (portrait: 480x800) 60 - if (x < 0 || x >= EInkDisplay::DISPLAY_HEIGHT || y < 0 || y >= EInkDisplay::DISPLAY_WIDTH) { 61 - Serial.printf("!!Outside range (%d, %d)\n", x, y); 62 - return; 63 - } 64 - 65 - // Rotate coordinates: portrait (480x800) -> landscape (800x480) 66 - // Rotation: 90 degrees clockwise 67 - const int16_t rotatedX = y; 68 - const int16_t rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x; 69 - 70 - // Calculate byte position and bit position 71 - const uint16_t byteIndex = rotatedY * EInkDisplay::DISPLAY_WIDTH_BYTES + (rotatedX / 8); 72 - const uint8_t bitPosition = 7 - (rotatedX % 8); // MSB first 73 - 74 - // Set or clear the bit 75 - if (pixelState) { 76 - frameBuffer[byteIndex] &= ~(1 << bitPosition); // Clear bit 77 - } else { 78 - frameBuffer[byteIndex] |= (1 << bitPosition); // Set bit 79 - } 80 - } 81 - 82 - template <typename Renderable> 83 - void EpdFontRenderer<Renderable>::renderChar(const uint32_t cp, int* x, const int* y, const bool pixelState, 84 - const EpdFontStyle style, const EpdFontRendererMode mode) { 85 - const EpdGlyph* glyph = fontFamily->getGlyph(cp, style); 86 - if (!glyph) { 87 - // TODO: Replace with fallback glyph property? 88 - glyph = fontFamily->getGlyph('?', style); 89 - } 90 - 91 - // no glyph? 92 - if (!glyph) { 93 - Serial.printf("No glyph for codepoint %d\n", cp); 94 - return; 95 - } 96 - 97 - const int is2Bit = fontFamily->getData(style)->is2Bit; 98 - const uint32_t offset = glyph->dataOffset; 99 - const uint8_t width = glyph->width; 100 - const uint8_t height = glyph->height; 101 - const int left = glyph->left; 102 - 103 - const uint8_t* bitmap = nullptr; 104 - bitmap = &fontFamily->getData(style)->bitmap[offset]; 105 - 106 - if (bitmap != nullptr) { 107 - for (int glyphY = 0; glyphY < height; glyphY++) { 108 - int screenY = *y - glyph->top + glyphY; 109 - for (int glyphX = 0; glyphX < width; glyphX++) { 110 - const int pixelPosition = glyphY * width + glyphX; 111 - int screenX = *x + left + glyphX; 112 - 113 - if (is2Bit) { 114 - const uint8_t byte = bitmap[pixelPosition / 4]; 115 - const uint8_t bit_index = (3 - pixelPosition % 4) * 2; 116 - 117 - const uint8_t val = (byte >> bit_index) & 0x3; 118 - if (mode == BW && val > 0) { 119 - drawPixel(screenX, screenY, pixelState); 120 - } else if (mode == GRAYSCALE_MSB && val == 1) { 121 - // TODO: Not sure how this anti-aliasing goes on black backgrounds 122 - drawPixel(screenX, screenY, false); 123 - } else if (mode == GRAYSCALE_LSB && val == 2) { 124 - drawPixel(screenX, screenY, false); 125 - } 126 - } else { 127 - const uint8_t byte = bitmap[pixelPosition / 8]; 128 - const uint8_t bit_index = 7 - (pixelPosition % 8); 129 - 130 - if ((byte >> bit_index) & 1) { 131 - drawPixel(screenX, screenY, pixelState); 132 - } 133 - } 134 - } 135 - } 136 - } 137 - 138 - *x += glyph->advanceX; 139 - }
-242
lib/EpdRenderer/EpdRenderer.cpp
··· 1 - #include "EpdRenderer.h" 2 - 3 - #include "builtinFonts/babyblue.h" 4 - #include "builtinFonts/bookerly_2b.h" 5 - #include "builtinFonts/bookerly_bold_2b.h" 6 - #include "builtinFonts/bookerly_bold_italic_2b.h" 7 - #include "builtinFonts/bookerly_italic_2b.h" 8 - #include "builtinFonts/ubuntu_10.h" 9 - #include "builtinFonts/ubuntu_bold_10.h" 10 - 11 - EpdFont bookerlyFont(&bookerly_2b); 12 - EpdFont bookerlyBoldFont(&bookerly_bold_2b); 13 - EpdFont bookerlyItalicFont(&bookerly_italic_2b); 14 - EpdFont bookerlyBoldItalicFont(&bookerly_bold_italic_2b); 15 - EpdFontFamily bookerlyFontFamily(&bookerlyFont, &bookerlyBoldFont, &bookerlyItalicFont, &bookerlyBoldItalicFont); 16 - 17 - EpdFont smallFont(&babyblue); 18 - EpdFontFamily smallFontFamily(&smallFont); 19 - 20 - EpdFont ubuntu10Font(&ubuntu_10); 21 - EpdFont ununtuBold10Font(&ubuntu_bold_10); 22 - EpdFontFamily ubuntuFontFamily(&ubuntu10Font, &ununtuBold10Font); 23 - 24 - EpdRenderer::EpdRenderer(EInkDisplay& einkDisplay) 25 - : einkDisplay(einkDisplay), 26 - marginTop(11), 27 - marginBottom(30), 28 - marginLeft(10), 29 - marginRight(10), 30 - fontRendererMode(BW), 31 - lineCompression(0.95f) { 32 - this->regularFontRenderer = new EpdFontRenderer<EInkDisplay>(&bookerlyFontFamily, einkDisplay); 33 - this->smallFontRenderer = new EpdFontRenderer<EInkDisplay>(&smallFontFamily, einkDisplay); 34 - this->uiFontRenderer = new EpdFontRenderer<EInkDisplay>(&ubuntuFontFamily, einkDisplay); 35 - } 36 - 37 - EpdRenderer::~EpdRenderer() { 38 - delete regularFontRenderer; 39 - delete smallFontRenderer; 40 - delete uiFontRenderer; 41 - } 42 - 43 - void EpdRenderer::drawPixel(const int x, const int y, const bool state) const { 44 - uint8_t* frameBuffer = einkDisplay.getFrameBuffer(); 45 - 46 - // Early return if no framebuffer is set 47 - if (!frameBuffer) { 48 - Serial.printf("!!No framebuffer\n"); 49 - return; 50 - } 51 - 52 - const int adjX = x + marginLeft; 53 - const int adjY = y + marginTop; 54 - 55 - // Bounds checking (portrait: 480x800) 56 - if (adjX < 0 || adjX >= EInkDisplay::DISPLAY_HEIGHT || adjY < 0 || adjY >= EInkDisplay::DISPLAY_WIDTH) { 57 - Serial.printf("!!Outside range (%d, %d)\n", adjX, adjY); 58 - return; 59 - } 60 - 61 - // Rotate coordinates: portrait (480x800) -> landscape (800x480) 62 - // Rotation: 90 degrees clockwise 63 - const int rotatedX = adjY; 64 - const int rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - adjX; 65 - 66 - // Calculate byte position and bit position 67 - const uint16_t byteIndex = rotatedY * EInkDisplay::DISPLAY_WIDTH_BYTES + (rotatedX / 8); 68 - const uint8_t bitPosition = 7 - (rotatedX % 8); // MSB first 69 - 70 - if (state) { 71 - frameBuffer[byteIndex] &= ~(1 << bitPosition); // Clear bit 72 - } else { 73 - frameBuffer[byteIndex] |= 1 << bitPosition; // Set bit 74 - } 75 - } 76 - 77 - int EpdRenderer::getTextWidth(const char* text, const EpdFontStyle style) const { 78 - int w = 0, h = 0; 79 - 80 - regularFontRenderer->fontFamily->getTextDimensions(text, &w, &h, style); 81 - 82 - return w; 83 - } 84 - 85 - int EpdRenderer::getUiTextWidth(const char* text, const EpdFontStyle style) const { 86 - int w = 0, h = 0; 87 - 88 - uiFontRenderer->fontFamily->getTextDimensions(text, &w, &h, style); 89 - 90 - return w; 91 - } 92 - 93 - int EpdRenderer::getSmallTextWidth(const char* text, const EpdFontStyle style) const { 94 - int w = 0, h = 0; 95 - 96 - smallFontRenderer->fontFamily->getTextDimensions(text, &w, &h, style); 97 - 98 - return w; 99 - } 100 - 101 - void EpdRenderer::drawText(const int x, const int y, const char* text, const bool state, 102 - const EpdFontStyle style) const { 103 - int ypos = y + getLineHeight() + marginTop; 104 - int xpos = x + marginLeft; 105 - regularFontRenderer->renderString(text, &xpos, &ypos, state, style, fontRendererMode); 106 - } 107 - 108 - void EpdRenderer::drawUiText(const int x, const int y, const char* text, const bool state, 109 - const EpdFontStyle style) const { 110 - int ypos = y + uiFontRenderer->fontFamily->getData(style)->advanceY + marginTop; 111 - int xpos = x + marginLeft; 112 - uiFontRenderer->renderString(text, &xpos, &ypos, state, style, fontRendererMode); 113 - } 114 - 115 - void EpdRenderer::drawSmallText(const int x, const int y, const char* text, const bool state, 116 - const EpdFontStyle style) const { 117 - int ypos = y + smallFontRenderer->fontFamily->getData(style)->advanceY + marginTop; 118 - int xpos = x + marginLeft; 119 - smallFontRenderer->renderString(text, &xpos, &ypos, state, style, fontRendererMode); 120 - } 121 - 122 - void EpdRenderer::drawTextBox(const int x, const int y, const std::string& text, const int width, const int height, 123 - const EpdFontStyle style) const { 124 - const size_t length = text.length(); 125 - // fit the text into the box 126 - int start = 0; 127 - int end = 1; 128 - int ypos = 0; 129 - while (true) { 130 - if (end >= length) { 131 - drawText(x, y + ypos, text.substr(start, length - start).c_str(), true, style); 132 - break; 133 - } 134 - 135 - if (ypos + getLineHeight() >= height) { 136 - break; 137 - } 138 - 139 - if (text[end - 1] == '\n') { 140 - drawText(x, y + ypos, text.substr(start, end - start).c_str(), true, style); 141 - ypos += getLineHeight(); 142 - start = end; 143 - end = start + 1; 144 - continue; 145 - } 146 - 147 - if (getTextWidth(text.substr(start, end - start).c_str(), style) > width) { 148 - drawText(x, y + ypos, text.substr(start, end - start - 1).c_str(), true, style); 149 - ypos += getLineHeight(); 150 - start = end - 1; 151 - continue; 152 - } 153 - 154 - end++; 155 - } 156 - } 157 - 158 - void EpdRenderer::drawLine(int x1, int y1, int x2, int y2, const bool state) const { 159 - if (x1 == x2) { 160 - if (y2 < y1) { 161 - std::swap(y1, y2); 162 - } 163 - for (int y = y1; y <= y2; y++) { 164 - drawPixel(x1, y, state); 165 - } 166 - } else if (y1 == y2) { 167 - if (x2 < x1) { 168 - std::swap(x1, x2); 169 - } 170 - for (int x = x1; x <= x2; x++) { 171 - drawPixel(x, y1, state); 172 - } 173 - } else { 174 - // TODO: Implement 175 - Serial.println("Line drawing not supported"); 176 - } 177 - } 178 - 179 - void EpdRenderer::drawRect(const int x, const int y, const int width, const int height, const bool state) const { 180 - drawLine(x, y, x + width - 1, y, state); 181 - drawLine(x + width - 1, y, x + width - 1, y + height - 1, state); 182 - drawLine(x + width - 1, y + height - 1, x, y + height - 1, state); 183 - drawLine(x, y, x, y + height - 1, state); 184 - } 185 - 186 - void EpdRenderer::fillRect(const int x, const int y, const int width, const int height, const bool state) const { 187 - for (int fillY = y; fillY < y + height; fillY++) { 188 - drawLine(x, fillY, x + width - 1, fillY, state); 189 - } 190 - } 191 - 192 - void EpdRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const { 193 - drawImageNoMargin(bitmap, x + marginLeft, y + marginTop, width, height); 194 - } 195 - 196 - // TODO: Support y-mirror? 197 - void EpdRenderer::drawImageNoMargin(const uint8_t bitmap[], const int x, const int y, const int width, 198 - const int height) const { 199 - einkDisplay.drawImage(bitmap, x, y, width, height); 200 - } 201 - 202 - void EpdRenderer::clearScreen(const uint8_t color) const { 203 - Serial.println("Clearing screen"); 204 - einkDisplay.clearScreen(color); 205 - } 206 - 207 - void EpdRenderer::invertScreen() const { 208 - uint8_t *buffer = einkDisplay.getFrameBuffer(); 209 - for (int i = 0; i < EInkDisplay::BUFFER_SIZE; i++) { 210 - buffer[i] = ~buffer[i]; 211 - } 212 - } 213 - 214 - void EpdRenderer::flushDisplay(const EInkDisplay::RefreshMode refreshMode) const { 215 - einkDisplay.displayBuffer(refreshMode); 216 - } 217 - 218 - // TODO: Support partial window update 219 - // void EpdRenderer::flushArea(const int x, const int y, const int width, const int height) const { 220 - // const int rotatedX = y; 221 - // const int rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x; 222 - // 223 - // einkDisplay.displayBuffer(EInkDisplay::FAST_REFRESH, rotatedX, rotatedY, height, width); 224 - // } 225 - 226 - int EpdRenderer::getPageWidth() const { return EInkDisplay::DISPLAY_HEIGHT - marginLeft - marginRight; } 227 - 228 - int EpdRenderer::getPageHeight() const { return EInkDisplay::DISPLAY_WIDTH - marginTop - marginBottom; } 229 - 230 - int EpdRenderer::getSpaceWidth() const { return regularFontRenderer->fontFamily->getGlyph(' ', REGULAR)->advanceX; } 231 - 232 - int EpdRenderer::getLineHeight() const { 233 - return regularFontRenderer->fontFamily->getData(REGULAR)->advanceY * lineCompression; 234 - } 235 - 236 - void EpdRenderer::swapBuffers() const { einkDisplay.swapBuffers(); } 237 - 238 - void EpdRenderer::copyGrayscaleLsbBuffers() const { einkDisplay.copyGrayscaleLsbBuffers(einkDisplay.getFrameBuffer()); } 239 - 240 - void EpdRenderer::copyGrayscaleMsbBuffers() const { einkDisplay.copyGrayscaleMsbBuffers(einkDisplay.getFrameBuffer()); } 241 - 242 - void EpdRenderer::displayGrayBuffer() const { einkDisplay.displayGrayBuffer(); }
-58
lib/EpdRenderer/EpdRenderer.h
··· 1 - #pragma once 2 - 3 - #include <EInkDisplay.h> 4 - 5 - #include <EpdFontRenderer.hpp> 6 - 7 - class EpdRenderer { 8 - EInkDisplay& einkDisplay; 9 - EpdFontRenderer<EInkDisplay>* regularFontRenderer; 10 - EpdFontRenderer<EInkDisplay>* smallFontRenderer; 11 - EpdFontRenderer<EInkDisplay>* uiFontRenderer; 12 - int marginTop; 13 - int marginBottom; 14 - int marginLeft; 15 - int marginRight; 16 - EpdFontRendererMode fontRendererMode; 17 - float lineCompression; 18 - 19 - public: 20 - explicit EpdRenderer(EInkDisplay& einkDisplay); 21 - ~EpdRenderer(); 22 - void drawPixel(int x, int y, bool state = true) const; 23 - int getTextWidth(const char* text, EpdFontStyle style = REGULAR) const; 24 - int getUiTextWidth(const char* text, EpdFontStyle style = REGULAR) const; 25 - int getSmallTextWidth(const char* text, EpdFontStyle style = REGULAR) const; 26 - void drawText(int x, int y, const char* text, bool state = true, EpdFontStyle style = REGULAR) const; 27 - void drawUiText(int x, int y, const char* text, bool state = true, EpdFontStyle style = REGULAR) const; 28 - void drawSmallText(int x, int y, const char* text, bool state = true, EpdFontStyle style = REGULAR) const; 29 - void drawTextBox(int x, int y, const std::string& text, int width, int height, EpdFontStyle style = REGULAR) const; 30 - void drawLine(int x1, int y1, int x2, int y2, bool state = true) const; 31 - void drawRect(int x, int y, int width, int height, bool state = true) const; 32 - void fillRect(int x, int y, int width, int height, bool state = true) const; 33 - void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const; 34 - void drawImageNoMargin(const uint8_t bitmap[], int x, int y, int width, int height) const; 35 - 36 - void swapBuffers() const; 37 - void copyGrayscaleLsbBuffers() const; 38 - void copyGrayscaleMsbBuffers() const; 39 - void displayGrayBuffer() const; 40 - void clearScreen(uint8_t color = 0xFF) const; 41 - 42 - void invertScreen() const; 43 - 44 - void flushDisplay(EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH) const; 45 - // void flushArea(int x, int y, int width, int height) const; 46 - 47 - int getPageWidth() const; 48 - int getPageHeight() const; 49 - int getSpaceWidth() const; 50 - int getLineHeight() const; 51 - 52 - // set margins 53 - void setMarginTop(const int newMarginTop) { this->marginTop = newMarginTop; } 54 - void setMarginBottom(const int newMarginBottom) { this->marginBottom = newMarginBottom; } 55 - void setMarginLeft(const int newMarginLeft) { this->marginLeft = newMarginLeft; } 56 - void setMarginRight(const int newMarginRight) { this->marginRight = newMarginRight; } 57 - void setFontRendererMode(const EpdFontRendererMode mode) { this->fontRendererMode = mode; } 58 - };
+11 -9
lib/Epub/Epub/EpubHtmlParserSlim.cpp
··· 1 1 #include "EpubHtmlParserSlim.h" 2 2 3 - #include <EpdRenderer.h> 3 + #include <GfxRenderer.h> 4 4 #include <HardwareSerial.h> 5 5 #include <expat.h> 6 6 ··· 133 133 } 134 134 135 135 // If we're about to run out of space, then cut the word off and start a new one 136 - if (self->partWordBufferIndex >= PART_WORD_BUFFER_SIZE - 2) { 136 + if (self->partWordBufferIndex >= MAX_WORD_SIZE) { 137 137 self->partWordBuffer[self->partWordBufferIndex] = '\0'; 138 138 self->currentTextBlock->addWord(replaceHtmlEntities(self->partWordBuffer), self->boldUntilDepth < self->depth, 139 139 self->italicUntilDepth < self->depth); ··· 257 257 258 258 if (!currentPage) { 259 259 currentPage = new Page(); 260 + currentPageNextY = marginTop; 260 261 } 261 262 262 - const int lineHeight = renderer.getLineHeight(); 263 - const int pageHeight = renderer.getPageHeight(); 263 + const int lineHeight = renderer.getLineHeight(fontId) * lineCompression; 264 + const int pageHeight = GfxRenderer::getScreenHeight() - marginTop - marginBottom; 264 265 265 266 // Long running task, make sure to let other things happen 266 267 vTaskDelay(1); 267 268 268 269 if (currentTextBlock->getType() == TEXT_BLOCK) { 269 - const auto lines = currentTextBlock->splitIntoLines(renderer); 270 + const auto lines = currentTextBlock->splitIntoLines(renderer, fontId, marginLeft + marginRight); 270 271 271 272 for (const auto line : lines) { 272 - if (currentPage->nextY + lineHeight > pageHeight) { 273 + if (currentPageNextY + lineHeight > pageHeight) { 273 274 completePageFn(currentPage); 274 275 currentPage = new Page(); 276 + currentPageNextY = marginTop; 275 277 } 276 278 277 - currentPage->elements.push_back(new PageLine(line, currentPage->nextY)); 278 - currentPage->nextY += lineHeight; 279 + currentPage->elements.push_back(new PageLine(line, marginLeft, currentPageNextY)); 280 + currentPageNextY += lineHeight; 279 281 } 280 282 // add some extra line between blocks 281 - currentPage->nextY += lineHeight / 2; 283 + currentPageNextY += lineHeight / 2; 282 284 } 283 285 // TODO: Image block support 284 286 // if (block->getType() == BlockType::IMAGE_BLOCK) {
+26 -8
lib/Epub/Epub/EpubHtmlParserSlim.h
··· 1 1 #pragma once 2 2 3 3 #include <expat.h> 4 - #include <limits.h> 5 4 5 + #include <climits> 6 6 #include <functional> 7 7 8 8 #include "blocks/TextBlock.h" 9 9 10 10 class Page; 11 - class EpdRenderer; 11 + class GfxRenderer; 12 12 13 - #define PART_WORD_BUFFER_SIZE 200 13 + #define MAX_WORD_SIZE 200 14 14 15 15 class EpubHtmlParserSlim { 16 16 const char* filepath; 17 - EpdRenderer& renderer; 17 + GfxRenderer& renderer; 18 18 std::function<void(Page*)> completePageFn; 19 19 int depth = 0; 20 20 int skipUntilDepth = INT_MAX; 21 21 int boldUntilDepth = INT_MAX; 22 22 int italicUntilDepth = INT_MAX; 23 - // If we encounter words longer than this, but this is pretty large 24 - char partWordBuffer[PART_WORD_BUFFER_SIZE] = {}; 23 + // buffer for building up words from characters, will auto break if longer than this 24 + // leave one char at end for null pointer 25 + char partWordBuffer[MAX_WORD_SIZE + 1] = {}; 25 26 int partWordBufferIndex = 0; 26 27 TextBlock* currentTextBlock = nullptr; 27 28 Page* currentPage = nullptr; 29 + int currentPageNextY = 0; 30 + int fontId; 31 + float lineCompression; 32 + int marginTop; 33 + int marginRight; 34 + int marginBottom; 35 + int marginLeft; 28 36 29 37 void startNewTextBlock(BLOCK_STYLE style); 30 38 void makePages(); ··· 34 42 static void XMLCALL endElement(void* userData, const XML_Char* name); 35 43 36 44 public: 37 - explicit EpubHtmlParserSlim(const char* filepath, EpdRenderer& renderer, 45 + explicit EpubHtmlParserSlim(const char* filepath, GfxRenderer& renderer, const int fontId, 46 + const float lineCompression, const int marginTop, const int marginRight, 47 + const int marginBottom, const int marginLeft, 38 48 const std::function<void(Page*)>& completePageFn) 39 - : filepath(filepath), renderer(renderer), completePageFn(completePageFn) {} 49 + : filepath(filepath), 50 + renderer(renderer), 51 + fontId(fontId), 52 + lineCompression(lineCompression), 53 + marginTop(marginTop), 54 + marginRight(marginRight), 55 + marginBottom(marginBottom), 56 + marginLeft(marginLeft), 57 + completePageFn(completePageFn) {} 40 58 ~EpubHtmlParserSlim() = default; 41 59 bool parseAndBuildPages(); 42 60 };
+17 -7
lib/Epub/Epub/Page.cpp
··· 3 3 #include <HardwareSerial.h> 4 4 #include <Serialization.h> 5 5 6 - void PageLine::render(EpdRenderer& renderer) { block->render(renderer, 0, yPos); } 6 + constexpr uint8_t PAGE_FILE_VERSION = 1; 7 + 8 + void PageLine::render(GfxRenderer& renderer, const int fontId) { block->render(renderer, fontId, xPos, yPos); } 7 9 8 10 void PageLine::serialize(std::ostream& os) { 11 + serialization::writePod(os, xPos); 9 12 serialization::writePod(os, yPos); 10 13 11 14 // serialize TextBlock pointed to by PageLine ··· 13 16 } 14 17 15 18 PageLine* PageLine::deserialize(std::istream& is) { 19 + int32_t xPos; 16 20 int32_t yPos; 21 + serialization::readPod(is, xPos); 17 22 serialization::readPod(is, yPos); 18 23 19 24 const auto tb = TextBlock::deserialize(is); 20 - return new PageLine(tb, yPos); 25 + return new PageLine(tb, xPos, yPos); 21 26 } 22 27 23 - void Page::render(EpdRenderer& renderer) const { 28 + void Page::render(GfxRenderer& renderer, const int fontId) const { 24 29 const auto start = millis(); 25 30 for (const auto element : elements) { 26 - element->render(renderer); 31 + element->render(renderer, fontId); 27 32 } 28 33 Serial.printf("Rendered page elements (%u) in %dms\n", elements.size(), millis() - start); 29 34 } 30 35 31 36 void Page::serialize(std::ostream& os) const { 32 - serialization::writePod(os, nextY); 37 + serialization::writePod(os, PAGE_FILE_VERSION); 33 38 34 39 const uint32_t count = elements.size(); 35 40 serialization::writePod(os, count); ··· 42 47 } 43 48 44 49 Page* Page::deserialize(std::istream& is) { 45 - auto* page = new Page(); 50 + uint8_t version; 51 + serialization::readPod(is, version); 52 + if (version != PAGE_FILE_VERSION) { 53 + Serial.printf("Page: Unknown version %u\n", version); 54 + return nullptr; 55 + } 46 56 47 - serialization::readPod(is, page->nextY); 57 + auto* page = new Page(); 48 58 49 59 uint32_t count; 50 60 serialization::readPod(is, count);
+9 -8
lib/Epub/Epub/Page.h
··· 8 8 // represents something that has been added to a page 9 9 class PageElement { 10 10 public: 11 + int xPos; 11 12 int yPos; 12 - explicit PageElement(const int yPos) : yPos(yPos) {} 13 + explicit PageElement(const int xPos, const int yPos) : xPos(xPos), yPos(yPos) {} 13 14 virtual ~PageElement() = default; 14 - virtual void render(EpdRenderer& renderer) = 0; 15 + virtual void render(GfxRenderer& renderer, int fontId) = 0; 15 16 virtual void serialize(std::ostream& os) = 0; 16 17 }; 17 18 ··· 20 21 const TextBlock* block; 21 22 22 23 public: 23 - PageLine(const TextBlock* block, const int yPos) : PageElement(yPos), block(block) {} 24 + PageLine(const TextBlock* block, const int xPos, const int yPos) : PageElement(xPos, yPos), block(block) {} 24 25 ~PageLine() override { delete block; } 25 - void render(EpdRenderer& renderer) override; 26 + void render(GfxRenderer& renderer, int fontId) override; 26 27 void serialize(std::ostream& os) override; 27 28 static PageLine* deserialize(std::istream& is); 28 29 }; 29 30 30 31 class Page { 31 32 public: 32 - int nextY = 0; 33 - // the list of block index and line numbers on this page 34 - std::vector<PageElement*> elements; 35 - void render(EpdRenderer& renderer) const; 36 33 ~Page() { 37 34 for (const auto element : elements) { 38 35 delete element; 39 36 } 40 37 } 38 + 39 + // the list of block index and line numbers on this page 40 + std::vector<PageElement*> elements; 41 + void render(GfxRenderer& renderer, int fontId) const; 41 42 void serialize(std::ostream& os) const; 42 43 static Page* deserialize(std::istream& is); 43 44 };
+46 -15
lib/Epub/Epub/Section.cpp
··· 1 1 #include "Section.h" 2 2 3 - #include <EpdRenderer.h> 3 + #include <GfxRenderer.h> 4 4 #include <SD.h> 5 5 6 6 #include <fstream> ··· 9 9 #include "Page.h" 10 10 #include "Serialization.h" 11 11 12 - constexpr uint8_t SECTION_FILE_VERSION = 2; 12 + constexpr uint8_t SECTION_FILE_VERSION = 3; 13 13 14 14 void Section::onPageComplete(const Page* page) { 15 15 Serial.printf("Page %d complete - free mem: %lu\n", pageCount, ESP.getFreeHeap()); ··· 24 24 delete page; 25 25 } 26 26 27 - void Section::writeCacheMetadata() const { 27 + void Section::writeCacheMetadata(const int fontId, const float lineCompression, const int marginTop, 28 + const int marginRight, const int marginBottom, const int marginLeft) const { 28 29 std::ofstream outputFile(("/sd" + cachePath + "/section.bin").c_str()); 29 30 serialization::writePod(outputFile, SECTION_FILE_VERSION); 31 + serialization::writePod(outputFile, fontId); 32 + serialization::writePod(outputFile, lineCompression); 33 + serialization::writePod(outputFile, marginTop); 34 + serialization::writePod(outputFile, marginRight); 35 + serialization::writePod(outputFile, marginBottom); 36 + serialization::writePod(outputFile, marginLeft); 30 37 serialization::writePod(outputFile, pageCount); 31 38 outputFile.close(); 32 39 } 33 40 34 - bool Section::loadCacheMetadata() { 41 + bool Section::loadCacheMetadata(const int fontId, const float lineCompression, const int marginTop, 42 + const int marginRight, const int marginBottom, const int marginLeft) { 35 43 if (!SD.exists(cachePath.c_str())) { 36 44 return false; 37 45 } ··· 42 50 } 43 51 44 52 std::ifstream inputFile(("/sd" + sectionFilePath).c_str()); 45 - uint8_t version; 46 - serialization::readPod(inputFile, version); 47 - if (version != SECTION_FILE_VERSION) { 48 - inputFile.close(); 49 - SD.remove(sectionFilePath.c_str()); 50 - Serial.printf("Section state file: Unknown version %u\n", version); 51 - return false; 53 + 54 + // Match parameters 55 + { 56 + uint8_t version; 57 + serialization::readPod(inputFile, version); 58 + if (version != SECTION_FILE_VERSION) { 59 + inputFile.close(); 60 + clearCache(); 61 + Serial.printf("Section state file: Unknown version %u\n", version); 62 + return false; 63 + } 64 + 65 + int fileFontId, fileMarginTop, fileMarginRight, fileMarginBottom, fileMarginLeft; 66 + float fileLineCompression; 67 + serialization::readPod(inputFile, fileFontId); 68 + serialization::readPod(inputFile, fileLineCompression); 69 + serialization::readPod(inputFile, fileMarginTop); 70 + serialization::readPod(inputFile, fileMarginRight); 71 + serialization::readPod(inputFile, fileMarginBottom); 72 + serialization::readPod(inputFile, fileMarginLeft); 73 + 74 + if (fontId != fileFontId || lineCompression != fileLineCompression || marginTop != fileMarginTop || 75 + marginRight != fileMarginRight || marginBottom != fileMarginBottom || marginLeft != fileMarginLeft) { 76 + inputFile.close(); 77 + clearCache(); 78 + Serial.println("Section state file: Parameters do not match, ignoring"); 79 + return false; 80 + } 52 81 } 82 + 53 83 serialization::readPod(inputFile, pageCount); 54 84 inputFile.close(); 55 85 Serial.printf("Loaded cache: %d pages\n", pageCount); ··· 63 93 64 94 void Section::clearCache() const { SD.rmdir(cachePath.c_str()); } 65 95 66 - bool Section::persistPageDataToSD() { 96 + bool Section::persistPageDataToSD(const int fontId, const float lineCompression, const int marginTop, 97 + const int marginRight, const int marginBottom, const int marginLeft) { 67 98 const auto localPath = epub->getSpineItem(spineIndex); 68 99 69 100 // TODO: Should we get rid of this file all together? ··· 83 114 84 115 const auto sdTmpHtmlPath = "/sd" + tmpHtmlPath; 85 116 86 - auto visitor = 87 - EpubHtmlParserSlim(sdTmpHtmlPath.c_str(), renderer, [this](const Page* page) { this->onPageComplete(page); }); 117 + auto visitor = EpubHtmlParserSlim(sdTmpHtmlPath.c_str(), renderer, fontId, lineCompression, marginTop, marginRight, 118 + marginBottom, marginLeft, [this](const Page* page) { this->onPageComplete(page); }); 88 119 success = visitor.parseAndBuildPages(); 89 120 90 121 SD.remove(tmpHtmlPath.c_str()); ··· 93 124 return false; 94 125 } 95 126 96 - writeCacheMetadata(); 127 + writeCacheMetadata(fontId, lineCompression, marginTop, marginRight, marginBottom, marginLeft); 97 128 98 129 return true; 99 130 }
+9 -6
lib/Epub/Epub/Section.h
··· 2 2 #include "Epub.h" 3 3 4 4 class Page; 5 - class EpdRenderer; 5 + class GfxRenderer; 6 6 7 7 class Section { 8 8 Epub* epub; 9 9 const int spineIndex; 10 - EpdRenderer& renderer; 10 + GfxRenderer& renderer; 11 11 std::string cachePath; 12 12 13 + void writeCacheMetadata(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom, 14 + int marginLeft) const; 13 15 void onPageComplete(const Page* page); 14 16 15 17 public: 16 18 int pageCount = 0; 17 19 int currentPage = 0; 18 20 19 - explicit Section(Epub* epub, const int spineIndex, EpdRenderer& renderer) 21 + explicit Section(Epub* epub, const int spineIndex, GfxRenderer& renderer) 20 22 : epub(epub), spineIndex(spineIndex), renderer(renderer) { 21 23 cachePath = epub->getCachePath() + "/" + std::to_string(spineIndex); 22 24 } 23 25 ~Section() = default; 24 - void writeCacheMetadata() const; 25 - bool loadCacheMetadata(); 26 + bool loadCacheMetadata(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom, 27 + int marginLeft); 26 28 void setupCacheDir() const; 27 29 void clearCache() const; 28 - bool persistPageDataToSD(); 30 + bool persistPageDataToSD(int fontId, float lineCompression, int marginTop, int marginRight, int marginBottom, 31 + int marginLeft); 29 32 Page* loadPageFromSD() const; 30 33 };
+2 -2
lib/Epub/Epub/blocks/Block.h
··· 1 1 #pragma once 2 2 3 - class EpdRenderer; 3 + class GfxRenderer; 4 4 5 5 typedef enum { TEXT_BLOCK, IMAGE_BLOCK } BlockType; 6 6 ··· 8 8 class Block { 9 9 public: 10 10 virtual ~Block() = default; 11 - virtual void layout(EpdRenderer& renderer) = 0; 11 + virtual void layout(GfxRenderer& renderer) = 0; 12 12 virtual BlockType getType() = 0; 13 13 virtual bool isEmpty() = 0; 14 14 virtual void finish() {}
+9 -8
lib/Epub/Epub/blocks/TextBlock.cpp
··· 1 1 #include "TextBlock.h" 2 2 3 - #include <EpdRenderer.h> 3 + #include <GfxRenderer.h> 4 4 #include <Serialization.h> 5 5 6 6 void TextBlock::addWord(const std::string& word, const bool is_bold, const bool is_italic) { ··· 10 10 wordStyles.push_back((is_bold ? BOLD_SPAN : 0) | (is_italic ? ITALIC_SPAN : 0)); 11 11 } 12 12 13 - std::list<TextBlock*> TextBlock::splitIntoLines(const EpdRenderer& renderer) { 13 + std::list<TextBlock*> TextBlock::splitIntoLines(const GfxRenderer& renderer, const int fontId, 14 + const int horizontalMargin) { 14 15 const int totalWordCount = words.size(); 15 - const int pageWidth = renderer.getPageWidth(); 16 - const int spaceWidth = renderer.getSpaceWidth(); 16 + const int pageWidth = GfxRenderer::getScreenWidth() - horizontalMargin; 17 + const int spaceWidth = renderer.getSpaceWidth(fontId); 17 18 18 19 words.shrink_to_fit(); 19 20 wordStyles.shrink_to_fit(); ··· 21 22 22 23 // measure each word 23 24 uint16_t wordWidths[totalWordCount]; 24 - for (int i = 0; i < words.size(); i++) { 25 + for (int i = 0; i < totalWordCount; i++) { 25 26 // measure the word 26 27 EpdFontStyle fontStyle = REGULAR; 27 28 if (wordStyles[i] & BOLD_SPAN) { ··· 33 34 } else if (wordStyles[i] & ITALIC_SPAN) { 34 35 fontStyle = ITALIC; 35 36 } 36 - const int width = renderer.getTextWidth(words[i].c_str(), fontStyle); 37 + const int width = renderer.getTextWidth(fontId, words[i].c_str(), fontStyle); 37 38 wordWidths[i] = width; 38 39 } 39 40 ··· 154 155 return lines; 155 156 } 156 157 157 - void TextBlock::render(const EpdRenderer& renderer, const int x, const int y) const { 158 + void TextBlock::render(const GfxRenderer& renderer, const int fontId, const int x, const int y) const { 158 159 for (int i = 0; i < words.size(); i++) { 159 160 // render the word 160 161 EpdFontStyle fontStyle = REGULAR; ··· 165 166 } else if (wordStyles[i] & ITALIC_SPAN) { 166 167 fontStyle = ITALIC; 167 168 } 168 - renderer.drawText(x + wordXpos[i], y, words[i].c_str(), true, fontStyle); 169 + renderer.drawText(fontId, x + wordXpos[i], y, words[i].c_str(), true, fontStyle); 169 170 } 170 171 } 171 172
+3 -3
lib/Epub/Epub/blocks/TextBlock.h
··· 40 40 void setStyle(const BLOCK_STYLE style) { this->style = style; } 41 41 BLOCK_STYLE getStyle() const { return style; } 42 42 bool isEmpty() override { return words.empty(); } 43 - void layout(EpdRenderer& renderer) override {}; 43 + void layout(GfxRenderer& renderer) override {}; 44 44 // given a renderer works out where to break the words into lines 45 - std::list<TextBlock*> splitIntoLines(const EpdRenderer& renderer); 46 - void render(const EpdRenderer& renderer, int x, int y) const; 45 + std::list<TextBlock*> splitIntoLines(const GfxRenderer& renderer, int fontId, int horizontalMargin); 46 + void render(const GfxRenderer& renderer, int fontId, int x, int y) const; 47 47 BlockType getType() override { return TEXT_BLOCK; } 48 48 void serialize(std::ostream& os) const; 49 49 static TextBlock* deserialize(std::istream& is);
+222
lib/GfxRenderer/GfxRenderer.cpp
··· 1 + #include "GfxRenderer.h" 2 + 3 + #include <Utf8.h> 4 + 5 + void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { fontMap.insert({fontId, font}); } 6 + 7 + void GfxRenderer::drawPixel(const int x, const int y, const bool state) const { 8 + uint8_t* frameBuffer = einkDisplay.getFrameBuffer(); 9 + 10 + // Early return if no framebuffer is set 11 + if (!frameBuffer) { 12 + Serial.printf("!!No framebuffer\n"); 13 + return; 14 + } 15 + 16 + // Rotate coordinates: portrait (480x800) -> landscape (800x480) 17 + // Rotation: 90 degrees clockwise 18 + const int rotatedX = y; 19 + const int rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x; 20 + 21 + // Bounds checking (portrait: 480x800) 22 + if (rotatedX < 0 || rotatedX >= EInkDisplay::DISPLAY_WIDTH || rotatedY < 0 || 23 + rotatedY >= EInkDisplay::DISPLAY_HEIGHT) { 24 + Serial.printf("!! Outside range (%d, %d)\n", x, y); 25 + return; 26 + } 27 + 28 + // Calculate byte position and bit position 29 + const uint16_t byteIndex = rotatedY * EInkDisplay::DISPLAY_WIDTH_BYTES + (rotatedX / 8); 30 + const uint8_t bitPosition = 7 - (rotatedX % 8); // MSB first 31 + 32 + if (state) { 33 + frameBuffer[byteIndex] &= ~(1 << bitPosition); // Clear bit 34 + } else { 35 + frameBuffer[byteIndex] |= 1 << bitPosition; // Set bit 36 + } 37 + } 38 + 39 + int GfxRenderer::getTextWidth(const int fontId, const char* text, const EpdFontStyle style) const { 40 + if (fontMap.count(fontId) == 0) { 41 + Serial.printf("Font %d not found\n", fontId); 42 + return 0; 43 + } 44 + 45 + int w = 0, h = 0; 46 + fontMap.at(fontId).getTextDimensions(text, &w, &h, style); 47 + return w; 48 + } 49 + 50 + void GfxRenderer::drawText(const int fontId, const int x, const int y, const char* text, const bool black, 51 + const EpdFontStyle style) const { 52 + const int yPos = y + getLineHeight(fontId); 53 + int xpos = x; 54 + 55 + // cannot draw a NULL / empty string 56 + if (text == nullptr || *text == '\0') { 57 + return; 58 + } 59 + 60 + if (fontMap.count(fontId) == 0) { 61 + Serial.printf("Font %d not found\n", fontId); 62 + return; 63 + } 64 + const auto font = fontMap.at(fontId); 65 + 66 + // no printable characters 67 + if (!font.hasPrintableChars(text, style)) { 68 + return; 69 + } 70 + 71 + uint32_t cp; 72 + while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) { 73 + renderChar(font, cp, &xpos, &yPos, black, style); 74 + } 75 + } 76 + 77 + void GfxRenderer::drawLine(int x1, int y1, int x2, int y2, const bool state) const { 78 + if (x1 == x2) { 79 + if (y2 < y1) { 80 + std::swap(y1, y2); 81 + } 82 + for (int y = y1; y <= y2; y++) { 83 + drawPixel(x1, y, state); 84 + } 85 + } else if (y1 == y2) { 86 + if (x2 < x1) { 87 + std::swap(x1, x2); 88 + } 89 + for (int x = x1; x <= x2; x++) { 90 + drawPixel(x, y1, state); 91 + } 92 + } else { 93 + // TODO: Implement 94 + Serial.println("Line drawing not supported"); 95 + } 96 + } 97 + 98 + void GfxRenderer::drawRect(const int x, const int y, const int width, const int height, const bool state) const { 99 + drawLine(x, y, x + width - 1, y, state); 100 + drawLine(x + width - 1, y, x + width - 1, y + height - 1, state); 101 + drawLine(x + width - 1, y + height - 1, x, y + height - 1, state); 102 + drawLine(x, y, x, y + height - 1, state); 103 + } 104 + 105 + void GfxRenderer::fillRect(const int x, const int y, const int width, const int height, const bool state) const { 106 + for (int fillY = y; fillY < y + height; fillY++) { 107 + drawLine(x, fillY, x + width - 1, fillY, state); 108 + } 109 + } 110 + 111 + void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const { 112 + einkDisplay.drawImage(bitmap, x, y, width, height); 113 + } 114 + 115 + void GfxRenderer::clearScreen(const uint8_t color) const { einkDisplay.clearScreen(color); } 116 + 117 + void GfxRenderer::invertScreen() const { 118 + uint8_t* buffer = einkDisplay.getFrameBuffer(); 119 + for (int i = 0; i < EInkDisplay::BUFFER_SIZE; i++) { 120 + buffer[i] = ~buffer[i]; 121 + } 122 + } 123 + 124 + void GfxRenderer::displayBuffer(const EInkDisplay::RefreshMode refreshMode) const { 125 + einkDisplay.displayBuffer(refreshMode); 126 + } 127 + 128 + // TODO: Support partial window update 129 + // void GfxRenderer::flushArea(const int x, const int y, const int width, const int height) const { 130 + // const int rotatedX = y; 131 + // const int rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x; 132 + // 133 + // einkDisplay.displayBuffer(EInkDisplay::FAST_REFRESH, rotatedX, rotatedY, height, width); 134 + // } 135 + 136 + // Note: Internal driver treats screen in command orientation, this library treats in portrait orientation 137 + int GfxRenderer::getScreenWidth() { return EInkDisplay::DISPLAY_HEIGHT; } 138 + int GfxRenderer::getScreenHeight() { return EInkDisplay::DISPLAY_WIDTH; } 139 + 140 + int GfxRenderer::getSpaceWidth(const int fontId) const { 141 + if (fontMap.count(fontId) == 0) { 142 + Serial.printf("Font %d not found\n", fontId); 143 + return 0; 144 + } 145 + 146 + return fontMap.at(fontId).getGlyph(' ', REGULAR)->advanceX; 147 + } 148 + 149 + int GfxRenderer::getLineHeight(const int fontId) const { 150 + if (fontMap.count(fontId) == 0) { 151 + Serial.printf("Font %d not found\n", fontId); 152 + return 0; 153 + } 154 + 155 + return fontMap.at(fontId).getData(REGULAR)->advanceY; 156 + } 157 + 158 + void GfxRenderer::swapBuffers() const { einkDisplay.swapBuffers(); } 159 + 160 + void GfxRenderer::copyGrayscaleLsbBuffers() const { einkDisplay.copyGrayscaleLsbBuffers(einkDisplay.getFrameBuffer()); } 161 + 162 + void GfxRenderer::copyGrayscaleMsbBuffers() const { einkDisplay.copyGrayscaleMsbBuffers(einkDisplay.getFrameBuffer()); } 163 + 164 + void GfxRenderer::displayGrayBuffer() const { einkDisplay.displayGrayBuffer(); } 165 + 166 + void GfxRenderer::renderChar(const EpdFontFamily& fontFamily, const uint32_t cp, int* x, const int* y, 167 + const bool pixelState, const EpdFontStyle style) const { 168 + const EpdGlyph* glyph = fontFamily.getGlyph(cp, style); 169 + if (!glyph) { 170 + // TODO: Replace with fallback glyph property? 171 + glyph = fontFamily.getGlyph('?', style); 172 + } 173 + 174 + // no glyph? 175 + if (!glyph) { 176 + Serial.printf("No glyph for codepoint %d\n", cp); 177 + return; 178 + } 179 + 180 + const int is2Bit = fontFamily.getData(style)->is2Bit; 181 + const uint32_t offset = glyph->dataOffset; 182 + const uint8_t width = glyph->width; 183 + const uint8_t height = glyph->height; 184 + const int left = glyph->left; 185 + 186 + const uint8_t* bitmap = nullptr; 187 + bitmap = &fontFamily.getData(style)->bitmap[offset]; 188 + 189 + if (bitmap != nullptr) { 190 + for (int glyphY = 0; glyphY < height; glyphY++) { 191 + const int screenY = *y - glyph->top + glyphY; 192 + for (int glyphX = 0; glyphX < width; glyphX++) { 193 + const int pixelPosition = glyphY * width + glyphX; 194 + const int screenX = *x + left + glyphX; 195 + 196 + if (is2Bit) { 197 + const uint8_t byte = bitmap[pixelPosition / 4]; 198 + const uint8_t bit_index = (3 - pixelPosition % 4) * 2; 199 + 200 + const uint8_t val = (byte >> bit_index) & 0x3; 201 + if (fontRenderMode == BW && val > 0) { 202 + drawPixel(screenX, screenY, pixelState); 203 + } else if (fontRenderMode == GRAYSCALE_MSB && val == 1) { 204 + // TODO: Not sure how this anti-aliasing goes on black backgrounds 205 + drawPixel(screenX, screenY, false); 206 + } else if (fontRenderMode == GRAYSCALE_LSB && val == 2) { 207 + drawPixel(screenX, screenY, false); 208 + } 209 + } else { 210 + const uint8_t byte = bitmap[pixelPosition / 8]; 211 + const uint8_t bit_index = 7 - (pixelPosition % 8); 212 + 213 + if ((byte >> bit_index) & 1) { 214 + drawPixel(screenX, screenY, pixelState); 215 + } 216 + } 217 + } 218 + } 219 + } 220 + 221 + *x += glyph->advanceX; 222 + }
+53
lib/GfxRenderer/GfxRenderer.h
··· 1 + #pragma once 2 + 3 + #include <EInkDisplay.h> 4 + 5 + #include <map> 6 + 7 + #include "EpdFontFamily.h" 8 + 9 + class GfxRenderer { 10 + public: 11 + enum FontRenderMode { BW, GRAYSCALE_LSB, GRAYSCALE_MSB }; 12 + 13 + private: 14 + EInkDisplay& einkDisplay; 15 + FontRenderMode fontRenderMode; 16 + std::map<int, EpdFontFamily> fontMap; 17 + void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, const int* y, bool pixelState, 18 + EpdFontStyle style) const; 19 + 20 + public: 21 + explicit GfxRenderer(EInkDisplay& einkDisplay) : einkDisplay(einkDisplay), fontRenderMode(BW) {} 22 + ~GfxRenderer() = default; 23 + 24 + // Setup 25 + void insertFont(int fontId, EpdFontFamily font); 26 + 27 + // Screen ops 28 + static int getScreenWidth(); 29 + static int getScreenHeight(); 30 + void displayBuffer(EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH) const; 31 + void invertScreen() const; 32 + void clearScreen(uint8_t color = 0xFF) const; 33 + 34 + // Drawing 35 + void drawPixel(int x, int y, bool state = true) const; 36 + void drawLine(int x1, int y1, int x2, int y2, bool state = true) const; 37 + void drawRect(int x, int y, int width, int height, bool state = true) const; 38 + void fillRect(int x, int y, int width, int height, bool state = true) const; 39 + void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const; 40 + 41 + // Text 42 + int getTextWidth(int fontId, const char* text, EpdFontStyle style = REGULAR) const; 43 + void drawText(int fontId, int x, int y, const char* text, bool black = true, EpdFontStyle style = REGULAR) const; 44 + void setFontRenderMode(const FontRenderMode mode) { this->fontRenderMode = mode; } 45 + int getSpaceWidth(int fontId) const; 46 + int getLineHeight(int fontId) const; 47 + 48 + // Low level functions 49 + void swapBuffers() const; 50 + void copyGrayscaleLsbBuffers() const; 51 + void copyGrayscaleMsbBuffers() const; 52 + void displayGrayBuffer() const; 53 + };
+29
src/config.h
··· 1 + #pragma once 2 + 3 + /** 4 + * Generated with: 5 + * ruby -rdigest -e 'puts [ 6 + * "./lib/EpdFont/builtinFonts/bookerly_2b.h", 7 + * "./lib/EpdFont/builtinFonts/bookerly_bold_2b.h", 8 + * "./lib/EpdFont/builtinFonts/bookerly_bold_italic_2b.h", 9 + * "./lib/EpdFont/builtinFonts/bookerly_italic_2b.h", 10 + * ].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)' 11 + */ 12 + #define READER_FONT_ID 1747632454 13 + 14 + /** 15 + * Generated with: 16 + * ruby -rdigest -e 'puts [ 17 + * "./lib/EpdFont/builtinFonts/ubuntu_10.h", 18 + * "./lib/EpdFont/builtinFonts/ubuntu_bold_10.h", 19 + * ].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)' 20 + */ 21 + #define UI_FONT_ID 225955604 22 + 23 + /** 24 + * Generated with: 25 + * ruby -rdigest -e 'puts [ 26 + * "./lib/EpdFont/builtinFonts/babyblue.h", 27 + * ].map{|f| Digest::SHA256.hexdigest(File.read(f)).to_i(16) }.sum % (2 ** 32) - (2 ** 31)' 28 + */ 29 + #define SMALL_FONT_ID 141891058
+31 -3
src/main.cpp
··· 1 1 #include <Arduino.h> 2 2 #include <EInkDisplay.h> 3 - #include <EpdRenderer.h> 4 3 #include <Epub.h> 4 + #include <GfxRenderer.h> 5 5 #include <InputManager.h> 6 6 #include <SD.h> 7 7 #include <SPI.h> 8 8 9 9 #include "Battery.h" 10 10 #include "CrossPointState.h" 11 + #include "builtinFonts/babyblue.h" 12 + #include "builtinFonts/bookerly_2b.h" 13 + #include "builtinFonts/bookerly_bold_2b.h" 14 + #include "builtinFonts/bookerly_bold_italic_2b.h" 15 + #include "builtinFonts/bookerly_italic_2b.h" 16 + #include "builtinFonts/ubuntu_10.h" 17 + #include "builtinFonts/ubuntu_bold_10.h" 18 + #include "config.h" 11 19 #include "screens/BootLogoScreen.h" 12 20 #include "screens/EpubReaderScreen.h" 13 21 #include "screens/FileSelectionScreen.h" ··· 30 38 31 39 EInkDisplay einkDisplay(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY); 32 40 InputManager inputManager; 33 - EpdRenderer renderer(einkDisplay); 41 + GfxRenderer renderer(einkDisplay); 34 42 Screen* currentScreen; 35 43 CrossPointState appState; 44 + 45 + // Fonts 46 + EpdFont bookerlyFont(&bookerly_2b); 47 + EpdFont bookerlyBoldFont(&bookerly_bold_2b); 48 + EpdFont bookerlyItalicFont(&bookerly_italic_2b); 49 + EpdFont bookerlyBoldItalicFont(&bookerly_bold_italic_2b); 50 + EpdFontFamily bookerlyFontFamily(&bookerlyFont, &bookerlyBoldFont, &bookerlyItalicFont, &bookerlyBoldItalicFont); 51 + 52 + EpdFont smallFont(&babyblue); 53 + EpdFontFamily smallFontFamily(&smallFont); 54 + 55 + EpdFont ubuntu10Font(&ubuntu_10); 56 + EpdFont ubuntuBold10Font(&ubuntu_bold_10); 57 + EpdFontFamily ubuntuFontFamily(&ubuntu10Font, &ubuntuBold10Font); 36 58 37 59 // Power button timing 38 60 // Time required to confirm boot from sleep ··· 141 163 enterNewScreen(new EpubReaderScreen(renderer, inputManager, epub, onGoHome)); 142 164 } else { 143 165 exitScreen(); 144 - enterNewScreen(new FullScreenMessageScreen(renderer, inputManager, "Failed to load epub", REGULAR, EInkDisplay::HALF_REFRESH)); 166 + enterNewScreen( 167 + new FullScreenMessageScreen(renderer, inputManager, "Failed to load epub", REGULAR, EInkDisplay::HALF_REFRESH)); 145 168 delay(2000); 146 169 onGoHome(); 147 170 } ··· 171 194 // Initialize display 172 195 einkDisplay.begin(); 173 196 Serial.println("Display initialized"); 197 + 198 + renderer.insertFont(READER_FONT_ID, bookerlyFontFamily); 199 + renderer.insertFont(UI_FONT_ID, ubuntuFontFamily); 200 + renderer.insertFont(SMALL_FONT_ID, smallFontFamily); 201 + Serial.println("Fonts loaded"); 174 202 175 203 exitScreen(); 176 204 enterNewScreen(new BootLogoScreen(renderer, inputManager));
+9 -8
src/screens/BootLogoScreen.cpp
··· 1 1 #include "BootLogoScreen.h" 2 2 3 - #include <EpdRenderer.h> 3 + #include <GfxRenderer.h> 4 4 5 + #include "config.h" 5 6 #include "images/CrossLarge.h" 6 7 7 8 void BootLogoScreen::onEnter() { 8 - const auto pageWidth = renderer.getPageWidth(); 9 - const auto pageHeight = renderer.getPageHeight(); 9 + const auto pageWidth = GfxRenderer::getScreenWidth(); 10 + const auto pageHeight = GfxRenderer::getScreenHeight(); 10 11 11 12 renderer.clearScreen(); 12 13 // Location for images is from top right in landscape orientation 13 14 renderer.drawImage(CrossLarge, (pageHeight - 128) / 2, (pageWidth - 128) / 2, 128, 128); 14 - const int width = renderer.getUiTextWidth("CrossPoint", BOLD); 15 - renderer.drawUiText((pageWidth - width)/ 2, pageHeight / 2 + 70, "CrossPoint", true, BOLD); 16 - const int bootingWidth = renderer.getSmallTextWidth("BOOTING"); 17 - renderer.drawSmallText((pageWidth - bootingWidth) / 2, pageHeight / 2 + 95, "BOOTING"); 18 - renderer.flushDisplay(); 15 + const int width = renderer.getTextWidth(UI_FONT_ID, "CrossPoint", BOLD); 16 + renderer.drawText(UI_FONT_ID, (pageWidth - width) / 2, pageHeight / 2 + 70, "CrossPoint", true, BOLD); 17 + const int bootingWidth = renderer.getTextWidth(SMALL_FONT_ID, "BOOTING"); 18 + renderer.drawText(SMALL_FONT_ID, (pageWidth - bootingWidth) / 2, pageHeight / 2 + 95, "BOOTING"); 19 + renderer.displayBuffer(); 19 20 }
+1 -1
src/screens/BootLogoScreen.h
··· 3 3 4 4 class BootLogoScreen final : public Screen { 5 5 public: 6 - explicit BootLogoScreen(EpdRenderer& renderer, InputManager& inputManager) : Screen(renderer, inputManager) {} 6 + explicit BootLogoScreen(GfxRenderer& renderer, InputManager& inputManager) : Screen(renderer, inputManager) {} 7 7 void onEnter() override; 8 8 };
+47 -36
src/screens/EpubReaderScreen.cpp
··· 1 1 #include "EpubReaderScreen.h" 2 2 3 - #include <EpdRenderer.h> 4 3 #include <Epub/Page.h> 4 + #include <GfxRenderer.h> 5 5 #include <SD.h> 6 6 7 7 #include "Battery.h" 8 + #include "config.h" 8 9 9 10 constexpr int PAGES_PER_REFRESH = 15; 10 11 constexpr unsigned long SKIP_CHAPTER_MS = 700; 12 + constexpr float lineCompression = 0.95f; 13 + constexpr int marginTop = 11; 14 + constexpr int marginRight = 10; 15 + constexpr int marginBottom = 30; 16 + constexpr int marginLeft = 10; 11 17 12 18 void EpubReaderScreen::taskTrampoline(void* param) { 13 19 auto* self = static_cast<EpubReaderScreen*>(param); ··· 150 156 const auto filepath = epub->getSpineItem(currentSpineIndex); 151 157 Serial.printf("Loading file: %s, index: %d\n", filepath.c_str(), currentSpineIndex); 152 158 section = new Section(epub, currentSpineIndex, renderer); 153 - if (!section->loadCacheMetadata()) { 159 + if (!section->loadCacheMetadata(READER_FONT_ID, lineCompression, marginTop, marginRight, marginBottom, 160 + marginLeft)) { 154 161 Serial.println("Cache not found, building..."); 155 162 156 163 { 157 - const int textWidth = renderer.getTextWidth("Indexing..."); 164 + const int textWidth = renderer.getTextWidth(READER_FONT_ID, "Indexing..."); 158 165 constexpr int margin = 20; 159 - const int x = (renderer.getPageWidth() - textWidth - margin * 2) / 2; 166 + const int x = (GfxRenderer::getScreenWidth() - textWidth - margin * 2) / 2; 160 167 constexpr int y = 50; 161 168 const int w = textWidth + margin * 2; 162 - const int h = renderer.getLineHeight() + margin * 2; 169 + const int h = renderer.getLineHeight(READER_FONT_ID) + margin * 2; 163 170 renderer.swapBuffers(); 164 171 renderer.fillRect(x, y, w, h, 0); 165 - renderer.drawText(x + margin, y + margin, "Indexing..."); 172 + renderer.drawText(READER_FONT_ID, x + margin, y + margin, "Indexing..."); 166 173 renderer.drawRect(x + 5, y + 5, w - 10, h - 10); 167 - renderer.flushDisplay(EInkDisplay::HALF_REFRESH); 174 + renderer.displayBuffer(EInkDisplay::HALF_REFRESH); 168 175 pagesUntilFullRefresh = 0; 169 176 } 170 177 171 178 section->setupCacheDir(); 172 - if (!section->persistPageDataToSD()) { 179 + if (!section->persistPageDataToSD(READER_FONT_ID, lineCompression, marginTop, marginRight, marginBottom, 180 + marginLeft)) { 173 181 Serial.println("Failed to persist page data to SD"); 174 182 delete section; 175 183 section = nullptr; ··· 190 198 191 199 if (section->pageCount == 0) { 192 200 Serial.println("No pages to render"); 193 - const int width = renderer.getTextWidth("Empty chapter", BOLD); 194 - renderer.drawText((renderer.getPageWidth() - width) / 2, 300, "Empty chapter", true, BOLD); 201 + const int width = renderer.getTextWidth(READER_FONT_ID, "Empty chapter", BOLD); 202 + renderer.drawText(READER_FONT_ID, (GfxRenderer::getScreenWidth() - width) / 2, 300, "Empty chapter", true, BOLD); 195 203 renderStatusBar(); 196 - renderer.flushDisplay(); 204 + renderer.displayBuffer(); 197 205 return; 198 206 } 199 207 200 208 if (section->currentPage < 0 || section->currentPage >= section->pageCount) { 201 209 Serial.printf("Page out of bounds: %d (max %d)\n", section->currentPage, section->pageCount); 202 - const int width = renderer.getTextWidth("Out of bounds", BOLD); 203 - renderer.drawText((renderer.getPageWidth() - width) / 2, 300, "Out of bounds", true, BOLD); 210 + const int width = renderer.getTextWidth(READER_FONT_ID, "Out of bounds", BOLD); 211 + renderer.drawText(READER_FONT_ID, (GfxRenderer::getScreenWidth() - width) / 2, 300, "Out of bounds", true, BOLD); 204 212 renderStatusBar(); 205 - renderer.flushDisplay(); 213 + renderer.displayBuffer(); 206 214 return; 207 215 } 208 216 ··· 221 229 } 222 230 223 231 void EpubReaderScreen::renderContents(const Page* p) { 224 - p->render(renderer); 232 + p->render(renderer, READER_FONT_ID); 225 233 renderStatusBar(); 226 234 if (pagesUntilFullRefresh <= 1) { 227 - renderer.flushDisplay(EInkDisplay::HALF_REFRESH); 235 + renderer.displayBuffer(EInkDisplay::HALF_REFRESH); 228 236 pagesUntilFullRefresh = PAGES_PER_REFRESH; 229 237 } else { 230 - renderer.flushDisplay(); 238 + renderer.displayBuffer(); 231 239 pagesUntilFullRefresh--; 232 240 } 233 241 234 242 // grayscale rendering 243 + // TODO: Only do this if font supports it 235 244 { 236 245 renderer.clearScreen(0x00); 237 - renderer.setFontRendererMode(GRAYSCALE_LSB); 238 - p->render(renderer); 246 + renderer.setFontRenderMode(GfxRenderer::GRAYSCALE_LSB); 247 + p->render(renderer, READER_FONT_ID); 239 248 renderer.copyGrayscaleLsbBuffers(); 240 249 241 250 // Render and copy to MSB buffer 242 251 renderer.clearScreen(0x00); 243 - renderer.setFontRendererMode(GRAYSCALE_MSB); 244 - p->render(renderer); 252 + renderer.setFontRenderMode(GfxRenderer::GRAYSCALE_MSB); 253 + p->render(renderer, READER_FONT_ID); 245 254 renderer.copyGrayscaleMsbBuffers(); 246 255 247 256 // display grayscale part 248 257 renderer.displayGrayBuffer(); 249 - renderer.setFontRendererMode(BW); 258 + renderer.setFontRenderMode(GfxRenderer::BW); 250 259 } 251 260 } 252 261 253 262 void EpubReaderScreen::renderStatusBar() const { 254 - const auto pageWidth = renderer.getPageWidth(); 255 - 263 + // Right aligned text for progress counter 256 264 const std::string progress = std::to_string(section->currentPage + 1) + " / " + std::to_string(section->pageCount); 257 - const auto progressTextWidth = renderer.getSmallTextWidth(progress.c_str()); 258 - renderer.drawSmallText(pageWidth - progressTextWidth, 765, progress.c_str()); 265 + const auto progressTextWidth = renderer.getTextWidth(SMALL_FONT_ID, progress.c_str()); 266 + renderer.drawText(SMALL_FONT_ID, GfxRenderer::getScreenWidth() - marginRight - progressTextWidth, 776, 267 + progress.c_str()); 259 268 269 + // Left aligned battery icon and percentage 260 270 const uint16_t percentage = battery.readPercentage(); 261 271 const auto percentageText = std::to_string(percentage) + "%"; 262 - const auto percentageTextWidth = renderer.getSmallTextWidth(percentageText.c_str()); 263 - renderer.drawSmallText(20, 765, percentageText.c_str()); 272 + const auto percentageTextWidth = renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str()); 273 + renderer.drawText(SMALL_FONT_ID, 20 + marginLeft, 776, percentageText.c_str()); 264 274 265 275 // 1 column on left, 2 columns on right, 5 columns of battery body 266 276 constexpr int batteryWidth = 15; 267 277 constexpr int batteryHeight = 10; 268 - constexpr int x = 0; 269 - constexpr int y = 772; 278 + constexpr int x = marginLeft; 279 + constexpr int y = 783; 270 280 271 281 // Top line 272 282 renderer.drawLine(x, y, x + batteryWidth - 4, y); ··· 287 297 } 288 298 renderer.fillRect(x + 1, y + 1, filledWidth, batteryHeight - 2); 289 299 300 + // Centered chatper title text 290 301 // Page width minus existing content with 30px padding on each side 291 - const int leftMargin = 20 + percentageTextWidth + 30; 292 - const int rightMargin = progressTextWidth + 30; 293 - const int availableTextWidth = pageWidth - leftMargin - rightMargin; 302 + const int titleMarginLeft = 20 + percentageTextWidth + 30 + marginLeft; 303 + const int titleMarginRight = progressTextWidth + 30 + marginRight; 304 + const int availableTextWidth = GfxRenderer::getScreenWidth() - titleMarginLeft - titleMarginRight; 294 305 const auto tocItem = epub->getTocItem(epub->getTocIndexForSpineIndex(currentSpineIndex)); 295 306 auto title = tocItem.title; 296 - int titleWidth = renderer.getSmallTextWidth(title.c_str()); 307 + int titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str()); 297 308 while (titleWidth > availableTextWidth) { 298 309 title = title.substr(0, title.length() - 8) + "..."; 299 - titleWidth = renderer.getSmallTextWidth(title.c_str()); 310 + titleWidth = renderer.getTextWidth(SMALL_FONT_ID, title.c_str()); 300 311 } 301 312 302 - renderer.drawSmallText(leftMargin + (availableTextWidth - titleWidth) / 2, 765, title.c_str()); 313 + renderer.drawText(SMALL_FONT_ID, titleMarginLeft + (availableTextWidth - titleWidth) / 2, 777, title.c_str()); 303 314 }
+2 -2
src/screens/EpubReaderScreen.h
··· 21 21 static void taskTrampoline(void* param); 22 22 [[noreturn]] void displayTaskLoop(); 23 23 void renderScreen(); 24 - void renderContents(const Page *p); 24 + void renderContents(const Page* p); 25 25 void renderStatusBar() const; 26 26 27 27 public: 28 - explicit EpubReaderScreen(EpdRenderer& renderer, InputManager& inputManager, Epub* epub, 28 + explicit EpubReaderScreen(GfxRenderer& renderer, InputManager& inputManager, Epub* epub, 29 29 const std::function<void()>& onGoHome) 30 30 : Screen(renderer, inputManager), epub(epub), onGoHome(onGoHome) {} 31 31 void onEnter() override;
+10 -8
src/screens/FileSelectionScreen.cpp
··· 1 1 #include "FileSelectionScreen.h" 2 2 3 - #include <EpdRenderer.h> 3 + #include <GfxRenderer.h> 4 4 #include <SD.h> 5 + 6 + #include "config.h" 5 7 6 8 void sortFileList(std::vector<std::string>& strs) { 7 9 std::sort(begin(strs), end(strs), [](const std::string& str1, const std::string& str2) { ··· 118 120 void FileSelectionScreen::render() const { 119 121 renderer.clearScreen(); 120 122 121 - const auto pageWidth = renderer.getPageWidth(); 122 - const auto titleWidth = renderer.getTextWidth("CrossPoint Reader", BOLD); 123 - renderer.drawText((pageWidth - titleWidth) / 2, 0, "CrossPoint Reader", true, BOLD); 123 + const auto pageWidth = GfxRenderer::getScreenWidth(); 124 + const auto titleWidth = renderer.getTextWidth(READER_FONT_ID, "CrossPoint Reader", BOLD); 125 + renderer.drawText(READER_FONT_ID, (pageWidth - titleWidth) / 2, 10, "CrossPoint Reader", true, BOLD); 124 126 125 127 if (files.empty()) { 126 - renderer.drawUiText(10, 50, "No EPUBs found"); 128 + renderer.drawText(UI_FONT_ID, 20, 60, "No EPUBs found"); 127 129 } else { 128 130 // Draw selection 129 - renderer.fillRect(0, 50 + selectorIndex * 30 + 2, pageWidth - 1, 30); 131 + renderer.fillRect(0, 60 + selectorIndex * 30 + 2, pageWidth - 1, 30); 130 132 131 133 for (size_t i = 0; i < files.size(); i++) { 132 134 const auto file = files[i]; 133 - renderer.drawUiText(10, 50 + i * 30, file.c_str(), i != selectorIndex); 135 + renderer.drawText(UI_FONT_ID, 20, 60 + i * 30, file.c_str(), i != selectorIndex); 134 136 } 135 137 } 136 138 137 - renderer.flushDisplay(); 139 + renderer.displayBuffer(); 138 140 }
+1 -1
src/screens/FileSelectionScreen.h
··· 24 24 void loadFiles(); 25 25 26 26 public: 27 - explicit FileSelectionScreen(EpdRenderer& renderer, InputManager& inputManager, 27 + explicit FileSelectionScreen(GfxRenderer& renderer, InputManager& inputManager, 28 28 const std::function<void(const std::string&)>& onSelect) 29 29 : Screen(renderer, inputManager), onSelect(onSelect) {} 30 30 void onEnter() override;
+9 -7
src/screens/FullScreenMessageScreen.cpp
··· 1 1 #include "FullScreenMessageScreen.h" 2 2 3 - #include <EpdRenderer.h> 3 + #include <GfxRenderer.h> 4 + 5 + #include "config.h" 4 6 5 7 void FullScreenMessageScreen::onEnter() { 6 - const auto width = renderer.getUiTextWidth(text.c_str(), style); 7 - const auto height = renderer.getLineHeight(); 8 - const auto left = (renderer.getPageWidth() - width) / 2; 9 - const auto top = (renderer.getPageHeight() - height) / 2; 8 + const auto width = renderer.getTextWidth(UI_FONT_ID, text.c_str(), style); 9 + const auto height = renderer.getLineHeight(UI_FONT_ID); 10 + const auto left = (GfxRenderer::getScreenWidth() - width) / 2; 11 + const auto top = (GfxRenderer::getScreenHeight() - height) / 2; 10 12 11 13 renderer.clearScreen(); 12 - renderer.drawUiText(left, top, text.c_str(), true, style); 13 - renderer.flushDisplay(refreshMode); 14 + renderer.drawText(UI_FONT_ID, left, top, text.c_str(), true, style); 15 + renderer.displayBuffer(refreshMode); 14 16 }
+5 -7
src/screens/FullScreenMessageScreen.h
··· 1 1 #pragma once 2 + #include <EInkDisplay.h> 3 + #include <EpdFontFamily.h> 4 + 2 5 #include <string> 3 6 #include <utility> 4 7 5 - #include <EInkDisplay.h> 6 - #include <EpdFontFamily.h> 7 8 #include "Screen.h" 8 9 9 10 class FullScreenMessageScreen final : public Screen { ··· 12 13 EInkDisplay::RefreshMode refreshMode; 13 14 14 15 public: 15 - explicit FullScreenMessageScreen(EpdRenderer& renderer, InputManager& inputManager, std::string text, 16 + explicit FullScreenMessageScreen(GfxRenderer& renderer, InputManager& inputManager, std::string text, 16 17 const EpdFontStyle style = REGULAR, 17 18 const EInkDisplay::RefreshMode refreshMode = EInkDisplay::FAST_REFRESH) 18 - : Screen(renderer, inputManager), 19 - text(std::move(text)), 20 - style(style), 21 - refreshMode(refreshMode) {} 19 + : Screen(renderer, inputManager), text(std::move(text)), style(style), refreshMode(refreshMode) {} 22 20 void onEnter() override; 23 21 };
+3 -3
src/screens/Screen.h
··· 1 1 #pragma once 2 2 #include <InputManager.h> 3 3 4 - class EpdRenderer; 4 + class GfxRenderer; 5 5 6 6 class Screen { 7 7 protected: 8 - EpdRenderer& renderer; 8 + GfxRenderer& renderer; 9 9 InputManager& inputManager; 10 10 11 11 public: 12 - explicit Screen(EpdRenderer& renderer, InputManager& inputManager) : renderer(renderer), inputManager(inputManager) {} 12 + explicit Screen(GfxRenderer& renderer, InputManager& inputManager) : renderer(renderer), inputManager(inputManager) {} 13 13 virtual ~Screen() = default; 14 14 virtual void onEnter() {} 15 15 virtual void onExit() {}
+9 -8
src/screens/SleepScreen.cpp
··· 1 1 #include "SleepScreen.h" 2 2 3 - #include <EpdRenderer.h> 3 + #include <GfxRenderer.h> 4 4 5 + #include "config.h" 5 6 #include "images/CrossLarge.h" 6 7 7 8 void SleepScreen::onEnter() { 8 - const auto pageWidth = renderer.getPageWidth(); 9 - const auto pageHeight = renderer.getPageHeight(); 9 + const auto pageWidth = GfxRenderer::getScreenWidth(); 10 + const auto pageHeight = GfxRenderer::getScreenHeight(); 10 11 11 12 renderer.clearScreen(); 12 13 renderer.drawImage(CrossLarge, (pageHeight - 128) / 2, (pageWidth - 128) / 2, 128, 128); 13 - const int width = renderer.getUiTextWidth("CrossPoint", BOLD); 14 - renderer.drawUiText((pageWidth - width)/ 2, pageHeight / 2 + 70, "CrossPoint", true, BOLD); 15 - const int bootingWidth = renderer.getSmallTextWidth("SLEEPING"); 16 - renderer.drawSmallText((pageWidth - bootingWidth) / 2, pageHeight / 2 + 95, "SLEEPING"); 14 + const int width = renderer.getTextWidth(UI_FONT_ID, "CrossPoint", BOLD); 15 + renderer.drawText(UI_FONT_ID, (pageWidth - width) / 2, pageHeight / 2 + 70, "CrossPoint", true, BOLD); 16 + const int bootingWidth = renderer.getTextWidth(SMALL_FONT_ID, "SLEEPING"); 17 + renderer.drawText(SMALL_FONT_ID, (pageWidth - bootingWidth) / 2, pageHeight / 2 + 95, "SLEEPING"); 17 18 renderer.invertScreen(); 18 - renderer.flushDisplay(EInkDisplay::FULL_REFRESH); 19 + renderer.displayBuffer(EInkDisplay::HALF_REFRESH); 19 20 }
+1 -1
src/screens/SleepScreen.h
··· 3 3 4 4 class SleepScreen final : public Screen { 5 5 public: 6 - explicit SleepScreen(EpdRenderer& renderer, InputManager& inputManager) : Screen(renderer, inputManager) {} 6 + explicit SleepScreen(GfxRenderer& renderer, InputManager& inputManager) : Screen(renderer, inputManager) {} 7 7 void onEnter() override; 8 8 };