#pragma once #include #include class FontCacheManager; class SdCardFont; #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 unsigned int REFRESH_OVERRIDE_NONE = 0; HalDisplay& display; std::atomic renderMode; std::atomic orientation; std::atomic fadingFix; // Text darkness for 2-bit grayscale glyph rendering. std::atomic textDarkness; // 0 = Normal — true 4-level AA (raw=1 → light gray, raw=2 → dark gray) // 1 = Dark — historical default; raw=2 collapses to black // 2 = Extra Dark — both AA shades go black in the grayscale plane // 3 = Maximum — grayscale pass skipped entirely; only the hard BW // pass remains, so AA pixels render as solid black // with the FAST waveform (no gray-LUT softening) // Only affects AA pixels in GRAYSCALE_MSB / GRAYSCALE_LSB rendering of 2-bit fonts. // 1-bit fonts and the BW pass are unchanged. Default is 1 to preserve historical // rendering. See drawMaskFor2BitMode() in GfxRenderer.cpp for the per-level // pixel breakdown and a worked example glyph. uint8_t* frameBuffer = nullptr; uint16_t panelWidth = 0; // set in begin() uint16_t panelHeight = 0; // set in begin() uint16_t panelWidthBytes = 0; // set in begin() uint32_t frameBufferSize = 0; // set in begin() uint16_t bwSnapshotRowStart = 0; uint16_t bwSnapshotRowEnd = 0; size_t bwSnapshotSizeBytes = 0; size_t bwBufferChunkSize = BW_BUFFER_CHUNK_SIZE; std::vector bwBufferChunks; std::map fontMap; // Mutable because ensureSdCardFontReady() is const (called from layout code that // holds a const GfxRenderer&) but triggers SD card reads and heap allocation // inside the SdCardFont objects. Same pragmatic compromise as fontCacheManager_. mutable std::map sdCardFonts_; // Mutable because drawText() is const but needs to delegate scan-mode // recording to the (non-const) FontCacheManager. Same pragmatic compromise // as before, concentrated in a single pointer instead of four fields. mutable FontCacheManager* fontCacheManager_ = nullptr; mutable std::atomic refreshOverride = REFRESH_OVERRIDE_NONE; void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, int* y, bool pixelState, EpdFontFamily::Style style) const; void freeBwBufferChunks(); template void drawPixelDither(int x, int y) const; template void fillArc(int maxRadius, int cx, int cy, int xDir, int yDir) const; // Write a patterned horizontal span directly to the physical framebuffer using byte-level operations. // phyY: physical row; phyX_start/phyX_end: inclusive physical column range. // patternByte is repeated across the span; partial edge bytes are blended with existing content. // Bit layout: MSB-first (bit 7 = phyX=0); 0 bits = dark pixel, 1 bits = white pixel. void fillPhysicalHSpanByte(int phyY, int phyX_start, int phyX_end, uint8_t patternByte) const; // Write a solid horizontal span directly to the physical framebuffer using byte-level operations. // Thin wrapper around fillPhysicalHSpanByte: state=true → 0x00 (dark), false → 0xFF (white). void fillPhysicalHSpan(int phyY, int phyX_start, int phyX_end, bool state) const; public: explicit GfxRenderer(HalDisplay& halDisplay) : display(halDisplay), renderMode(static_cast(BW)), orientation(static_cast(Portrait)), fadingFix(false), textDarkness(1) {} ~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 removeFont(int fontId) { fontMap.erase(fontId); } void setFontCacheManager(FontCacheManager* m) { fontCacheManager_ = m; } FontCacheManager* getFontCacheManager() const { return fontCacheManager_; } const std::map& getFontMap() const { return fontMap; } void registerSdCardFont(int fontId, SdCardFont* font) { sdCardFonts_[fontId] = font; } void unregisterSdCardFont(int fontId) { sdCardFonts_.erase(fontId); } void clearSdCardFonts() { sdCardFonts_.clear(); } const std::map& getSdCardFonts() const { return sdCardFonts_; } bool isSdCardFont(int fontId) const { return sdCardFonts_.count(fontId) > 0; } // Ensure SD card font glyph data is loaded for the given text. Called from layout code // (which holds a const GfxRenderer&) before measuring word widths. Safe to call on // non-SD fonts (no-op). void ensureSdCardFontReady(int fontId, const char* utf8Text) const; // Clear the cumulative SD card font metadata cache built up by repeated // ensureSdCardFontReady() calls. Call between sections to bound the cumulative // codepoint set growth across pagination. Safe to call when no SD font is active. void clearSdCardFontAccumulation() const; // Orientation control (affects logical width/height and coordinate transforms) void setOrientation(const Orientation o) { orientation.store(static_cast(o), std::memory_order_relaxed); } Orientation getOrientation() const { return static_cast(orientation.load(std::memory_order_relaxed)); } // Fading fix control void setFadingFix(const bool enabled) { fadingFix.store(enabled, std::memory_order_relaxed); } // Screen ops int getScreenWidth() const; int getScreenHeight() const; void displayBuffer(HalDisplay::RefreshMode refreshMode = HalDisplay::FAST_REFRESH) const; void setNextDisplayRefreshMode(HalDisplay::RefreshMode refreshMode) 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 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 drawIconInverted(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 total inter-word advance: fp4::toPixel(spaceAdvance + kern(leftCp,' ') + kern(' ',rightCp)). /// Using a single snap avoids the +/-1 px rounding error that arises when space advance and kern are /// snapped separately and then added as integers. int getSpaceAdvance(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.store(static_cast(mode), std::memory_order_relaxed); } RenderMode getRenderMode() const { return static_cast(renderMode.load(std::memory_order_relaxed)); } // Text darkness control: // 0 = Normal, 1 = Dark, 2 = Extra Dark, 3 = Maximum. // Only affects anti-aliased pixels in 2-bit (grayscale) glyph rendering; // 1-bit fonts and the BW pass are unchanged. See drawMaskFor2BitMode() in // GfxRenderer.cpp for the per-level pixel breakdown and a worked example. void setTextDarkness(const uint8_t d) { textDarkness.store(d, std::memory_order_relaxed); } uint8_t getTextDarkness() const { return static_cast(textDarkness.load(std::memory_order_relaxed)); } void copyGrayscaleLsbBuffers() const; void copyGrayscaleMsbBuffers() const; void displayGrayBuffer() const; bool storeBwBuffer(); // Returns true if buffer was stored successfully bool storeBwBufferRect(int x, int y, int width, int height); // Store only rows intersecting logical rect 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; size_t getBufferSize() const; uint16_t getDisplayWidth() const { return panelWidth; } uint16_t getDisplayHeight() const { return panelHeight; } uint16_t getDisplayWidthBytes() const { return panelWidthBytes; } };