#pragma once #include #include #include #include #include #include #include "Bitmap.h" // Color representation: uint8_t mapped to 4x4 Bayer matrix dithering levels // 0 = transparent, 1-16 = gray levels (white to black) enum Color : uint8_t { Clear = 0x00, White = 0x01, LightGray = 0x05, DarkGray = 0x0A, Black = 0x10 }; class GfxRenderer { public: enum RenderMode { BW, GRAYSCALE_LSB, GRAYSCALE_MSB }; // Logical screen orientation from the perspective of callers enum Orientation { Portrait, // 480x800 logical coordinates (current default) LandscapeClockwise, // 800x480 logical coordinates, rotated 180° (swap top/bottom) PortraitInverted, // 480x800 logical coordinates, inverted LandscapeCounterClockwise // 800x480 logical coordinates, native panel orientation }; private: static constexpr size_t BW_BUFFER_CHUNK_SIZE = 8000; // 8KB chunks to allow for non-contiguous memory static constexpr size_t BW_BUFFER_NUM_CHUNKS = HalDisplay::BUFFER_SIZE / BW_BUFFER_CHUNK_SIZE; static_assert(BW_BUFFER_CHUNK_SIZE * BW_BUFFER_NUM_CHUNKS == HalDisplay::BUFFER_SIZE, "BW buffer chunking does not line up with display buffer size"); HalDisplay& display; RenderMode renderMode; Orientation orientation; bool fadingFix; uint8_t* frameBuffer = nullptr; uint8_t* bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr}; std::map fontMap; FontDecompressor* fontDecompressor = nullptr; void freeBwBufferChunks(); template void drawPixelDither(int x, int y) const; template void fillArc(int maxRadius, int cx, int cy, int xDir, int yDir) const; public: explicit GfxRenderer(HalDisplay& halDisplay) : display(halDisplay), renderMode(BW), orientation(Portrait), fadingFix(false) {} ~GfxRenderer() { freeBwBufferChunks(); } static constexpr int VIEWABLE_MARGIN_TOP = 9; static constexpr int VIEWABLE_MARGIN_RIGHT = 3; static constexpr int VIEWABLE_MARGIN_BOTTOM = 3; static constexpr int VIEWABLE_MARGIN_LEFT = 3; // Setup void begin(); // must be called right after display.begin() void insertFont(int fontId, EpdFontFamily font); void setFontDecompressor(FontDecompressor* d) { fontDecompressor = d; } void clearFontCache() { if (fontDecompressor) fontDecompressor->clearCache(); } // Orientation control (affects logical width/height and coordinate transforms) void setOrientation(const Orientation o) { orientation = o; } Orientation getOrientation() const { return orientation; } // Fading fix control void setFadingFix(const bool enabled) { fadingFix = enabled; } // Screen ops int getScreenWidth() const; int getScreenHeight() const; void displayBuffer(HalDisplay::RefreshMode refreshMode = HalDisplay::FAST_REFRESH) const; // EXPERIMENTAL: Windowed update - display only a rectangular region // void displayWindow(int x, int y, int width, int height) const; void invertScreen() const; void clearScreen(uint8_t color = 0xFF) const; void getOrientedViewableTRBL(int* outTop, int* outRight, int* outBottom, int* outLeft) const; // Drawing void drawPixel(int x, int y, bool state = true) const; void drawPixelGray(int x, int y, uint8_t val2bit) const; void drawLine(int x1, int y1, int x2, int y2, bool state = true) const; void drawLine(int x1, int y1, int x2, int y2, int lineWidth, bool state) const; void drawArc(int maxRadius, int cx, int cy, int xDir, int yDir, int lineWidth, bool state) const; void drawRect(int x, int y, int width, int height, bool state = true) const; void drawRect(int x, int y, int width, int height, int lineWidth, bool state) const; void drawRoundedRect(int x, int y, int width, int height, int lineWidth, int cornerRadius, bool state) const; void drawRoundedRect(int x, int y, int width, int height, int lineWidth, int cornerRadius, bool roundTopLeft, bool roundTopRight, bool roundBottomLeft, bool roundBottomRight, bool state) const; void fillRect(int x, int y, int width, int height, bool state = true) const; void fillRectDither(int x, int y, int width, int height, Color color) const; void fillRoundedRect(int x, int y, int width, int height, int cornerRadius, Color color) const; void fillRoundedRect(int x, int y, int width, int height, int cornerRadius, bool roundTopLeft, bool roundTopRight, bool roundBottomLeft, bool roundBottomRight, Color color) const; void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const; void drawIcon(const uint8_t bitmap[], int x, int y, int width, int height) const; void drawBitmap(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight, float cropX = 0, float cropY = 0) const; void drawBitmap1Bit(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight) const; void fillPolygon(const int* xPoints, const int* yPoints, int numPoints, bool state = true) const; // Text int getTextWidth(int fontId, const char* text, EpdFontFamily::Style style = EpdFontFamily::REGULAR) const; void drawCenteredText(int fontId, int y, const char* text, bool black = true, EpdFontFamily::Style style = EpdFontFamily::REGULAR) const; void drawText(int fontId, int x, int y, const char* text, bool black = true, EpdFontFamily::Style style = EpdFontFamily::REGULAR) const; int getSpaceWidth(int fontId, EpdFontFamily::Style style = EpdFontFamily::REGULAR) const; /// Returns the kerning adjustment for a space between two codepoints: /// kern(leftCp, ' ') + kern(' ', rightCp). Returns 0 if kerning is unavailable. int getSpaceKernAdjust(int fontId, uint32_t leftCp, uint32_t rightCp, EpdFontFamily::Style style) const; /// Returns the kerning adjustment between two adjacent codepoints. int getKerning(int fontId, uint32_t leftCp, uint32_t rightCp, EpdFontFamily::Style style) const; int getTextAdvanceX(int fontId, const char* text, EpdFontFamily::Style style) const; int getFontAscenderSize(int fontId) const; int getLineHeight(int fontId) const; std::string truncatedText(int fontId, const char* text, int maxWidth, EpdFontFamily::Style style = EpdFontFamily::REGULAR) const; /// Word-wrap \p text into at most \p maxLines lines, each no wider than /// \p maxWidth pixels. Overflowing words and excess lines are UTF-8-safely /// truncated with an ellipsis (U+2026). std::vector wrappedText(int fontId, const char* text, int maxWidth, int maxLines, EpdFontFamily::Style style = EpdFontFamily::REGULAR) const; // Helper for drawing rotated text (90 degrees clockwise, for side buttons) void drawTextRotated90CW(int fontId, int x, int y, const char* text, bool black = true, EpdFontFamily::Style style = EpdFontFamily::REGULAR) const; int getTextHeight(int fontId) const; // Grayscale functions void setRenderMode(const RenderMode mode) { this->renderMode = mode; } RenderMode getRenderMode() const { return renderMode; } void copyGrayscaleLsbBuffers() const; void copyGrayscaleMsbBuffers() const; void displayGrayBuffer() const; bool storeBwBuffer(); // Returns true if buffer was stored successfully void restoreBwBuffer(); // Restore and free the stored buffer void cleanupGrayscaleWithFrameBuffer() const; // Font helpers const uint8_t* getGlyphBitmap(const EpdFontData* fontData, const EpdGlyph* glyph) const; // Low level functions uint8_t* getFrameBuffer() const; static size_t getBufferSize(); };