#pragma once #include #include #define ENABLE_GxEPD2_GFX 0 // Always use the patched copy of GxEPD2_BW.h from lib/GxEPD2-patch/src/: it // adds writeInverseForRedrive() (needed by every e-ink build, see endFrame()) // and, under ENABLE_SCREENSHOT, getBuffer()/getBufferSize(). Both need the // class's private framebuffer, so they have to live in the header itself. The // include guard (_GxEPD2_BW_H_) prevents the installed library version from // being compiled as well; the copy tracks the 1.6.2 pin every e-ink variant's // lib_deps uses. #include "../../../lib/GxEPD2-patch/src/GxEPD2_BW.h" #include #include #include #include #include #include #include #include "DisplayDriver.h" #include "MiscFixedFont.h" #ifndef DISPLAY_ROTATION #define DISPLAY_ROTATION 0 #endif // Panel native dimensions before rotation. Derived from the model class when // EINK_DISPLAY_MODEL is set; override with EINK_PANEL_W/H for other panels. #if defined(EINK_DISPLAY_MODEL) #ifndef EINK_PANEL_W #define EINK_PANEL_W EINK_DISPLAY_MODEL::WIDTH #endif #ifndef EINK_PANEL_H #define EINK_PANEL_H EINK_DISPLAY_MODEL::HEIGHT #endif #else #ifndef EINK_PANEL_W #define EINK_PANEL_W 200 #define EINK_PANEL_H 200 #endif #endif // Odd rotations (1, 3) swap width and height. #define EINK_DISP_W ((DISPLAY_ROTATION & 1) ? EINK_PANEL_H : EINK_PANEL_W) #define EINK_DISP_H ((DISPLAY_ROTATION & 1) ? EINK_PANEL_W : EINK_PANEL_H) class GxEPDDisplay : public DisplayDriver { #if defined(EINK_DISPLAY_MODEL) GxEPD2_BW display; #else GxEPD2_BW display; #endif bool _init = false; bool _isOn = false; bool _single_font = true; // e-ink is single-font too now (misc-fixed 6x9), matching the OLED driver uint16_t _curr_color; CRC32 display_crc; int last_display_crc_value = 0; int _text_sz = 1; uint8_t _full_refresh_interval = 0; uint8_t _partial_count = 0; int16_t drawGlyph(int16_t x, int16_t y, uint32_t cp, int sc); uint8_t glyphXAdvance(uint32_t cp, int sc); int scale() const { return (width() >= height()) ? 2 : 1; } public: #if defined(EINK_DISPLAY_MODEL) GxEPDDisplay() : DisplayDriver(EINK_DISP_W, EINK_DISP_H), display(EINK_DISPLAY_MODEL(PIN_DISPLAY_CS, PIN_DISPLAY_DC, PIN_DISPLAY_RST, PIN_DISPLAY_BUSY)) {} #else GxEPDDisplay() : DisplayDriver(EINK_DISP_W, EINK_DISP_H), display(GxEPD2_150_BN(DISP_CS, DISP_DC, DISP_RST, DISP_BUSY)) {} #endif #ifdef ENABLE_SCREENSHOT const uint8_t* getBuffer() override { return display.getBuffer(); } uint16_t getBufferSize() override { return display.getBufferSize(); } uint8_t getDisplayType() override { return 1; } // 1 = e-ink // Return GxEPD2's own reported dimensions (uses WIDTH_VISIBLE, not the full physical WIDTH // stored in DisplayDriver). After setRotation(1): width()=HEIGHT, height()=WIDTH_VISIBLE. int screenshotWidth() override { return (int)display.width(); } int screenshotHeight() override { return (int)display.height(); } uint8_t screenshotRotation() override { return (uint8_t)display.getRotation(); } #endif // Line height and approx. char width for each font size: // 1 = FreeSans9pt (lineH=16, charW≈9) // 2 = FreeSansBold12pt (lineH=20, charW≈12) // 3 = FreeSans18pt (lineH=28, charW≈17) // Built-in font scale used for size 4 (huge clock digits): 6×8 cell × 7 = 42×56. static const int BIG_TEXT_SCALE = 7; // Size 1 scales with orientation (portrait 1×, landscape 2×); size 2 is always 12×16; // size 3 is FreeSans18pt (~17×28); size 4 is the built-in font at 7× (~42×56), // used only for the stacked HH/MM clock on portrait e-ink. Landscape = width >= height. int getCharWidth() const override { int sc = scale(); if (_text_sz == 4) return 6 * BIG_TEXT_SCALE; if (_text_sz == 3) return 17; if (_text_sz == 2) return 12 * sc; return 6 * sc; // misc-fixed 6x9 is 6px wide } int getLineHeight() const override { int sc = scale(); if (_text_sz == 4) return 8 * BIG_TEXT_SCALE; if (_text_sz == 3) return 28; if (_text_sz == 2) return 16 * sc; return (_single_font ? 9 : 8) * sc; // misc-fixed 6x9 box height } void setSingleFont(bool) override { } // single-font: ignore toggles, stay misc-fixed 6x9 bool isSingleFont() const override { return _single_font; } void translateUTF8ToBlocks(char* dest, const char* src, size_t dest_size) override { if (_single_font) { strncpy(dest, src, dest_size - 1); dest[dest_size - 1] = '\0'; } else { translateUTF8Static(dest, src, dest_size); } } bool begin(); bool isOn() override { return _isOn; } bool isEink() override { return true; } void turnOn() override; void turnOff() override; void clear() override; void startFrame(Color bkg = DARK) override; void setTextSize(int sz) override; void setColor(Color c) override; void setCursor(int x, int y) override; void print(const char* str) override; void fillRect(int x, int y, int w, int h) override; void drawRect(int x, int y, int w, int h) override; void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override; uint16_t getTextWidth(const char* str) override; uint16_t getCodepointWidth(uint32_t cp) override { if (_single_font && _text_sz == 1) return glyphXAdvance(cp, scale()); return DisplayDriver::getCodepointWidth(cp); } void setDisplayRotation(uint8_t rot) override; void setFullRefreshInterval(uint8_t n) override { _full_refresh_interval = n; _partial_count = 0; } void endFrame() override; };