#pragma once #include #include #define ENABLE_GxEPD2_GFX 0 // When ENABLE_SCREENSHOT is active, use the patched copy of GxEPD2_BW.h from // lib/GxEPD2-patch/src/ that exposes getBuffer()/getBufferSize(). The include // guard (_GxEPD2_BW_H_) then prevents the installed library version from // being compiled again. Non-screenshot builds use the installed library as-is. #ifdef ENABLE_SCREENSHOT #include "../../../lib/GxEPD2-patch/src/GxEPD2_BW.h" #else #include #endif #include #include #include #include #include #include #include #include "DisplayDriver.h" #include "LemonFont.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 _use_lemon = false; 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 drawLemonChar(int16_t x, int16_t y, uint32_t cp, int sc); uint8_t lemonXAdvance(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 #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) // Size 1 scales with orientation (portrait 1×, landscape 2×); size 2 is always 12×16; // size 3 is FreeSans18pt (~17×28). Landscape = width >= height. int getCharWidth() const override { int sc = scale(); if (_text_sz == 3) return 17; if (_text_sz == 2) return 12 * sc; return (_use_lemon ? 5 : 6) * sc; } int getLineHeight() const override { int sc = scale(); if (_text_sz == 3) return 28; if (_text_sz == 2) return 16 * sc; return (_use_lemon ? 10 : 8) * sc; } void setLemonFont(bool enabled) override { _use_lemon = enabled; _vw_dirty = true; } bool isLemonFont() const override { return _use_lemon; } void translateUTF8ToBlocks(char* dest, const char* src, size_t dest_size) override { if (_use_lemon) { strncpy(dest, src, dest_size - 1); dest[dest_size - 1] = '\0'; } else { translateUTF8Static(dest, src, dest_size); } } bool begin(); bool isOn() override { return _isOn; } 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 (_use_lemon && _text_sz == 1) return lemonXAdvance(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; };