#pragma once #include #include namespace BidiUtils { // Paragraph base direction for the Unicode BiDi algorithm (UAX#9). // AUTO: scan text for first strong directional character (P2/P3 rules) // LTR: force left-to-right paragraph embedding level // RTL: force right-to-left paragraph embedding level enum class BidiBaseDir : signed char { AUTO = -1, LTR = 0, RTL = 1 }; } // namespace BidiUtils class FontCacheManager; class SdCardFont; #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 HalDisplay& display; RenderMode renderMode; Orientation orientation; bool fadingFix; uint8_t* frameBuffer = nullptr; uint16_t panelWidth = HalDisplay::DISPLAY_WIDTH; uint16_t panelHeight = HalDisplay::DISPLAY_HEIGHT; uint16_t panelWidthBytes = HalDisplay::DISPLAY_WIDTH_BYTES; uint32_t frameBufferSize = HalDisplay::BUFFER_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_ below. 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; // Tiled grayscale strip target. When active, drawPixel()/clearScreen() // operate on a caller-owned scratch holding one horizontal band of physical // rows [_stripY0, _stripY0 + _stripRows) (panelWidthBytes wide) instead of // the shared framebuffer, clipping pixels outside the band. Lets grayscale // planes render band-by-band straight to the controller without destroying // the BW framebuffer (no storeBwBuffer). Mutable because the render path is // const. See beginStripTarget()/endStripTarget(). mutable uint8_t* _stripBuf = nullptr; mutable int _stripY0 = 0; mutable int _stripRows = 0; mutable bool _stripActive = false; 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; // Byte-aligned, orientation-specialized rectangle fill. Rotates the rect's // two opposing corners into physical-framebuffer space once, then walks each // physical row with head-mask / middle memset / tail-mask byte writes — no // per-pixel rotation, no per-pixel RMW. template void fillRectImpl(int x, int y, int width, int height) 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); // Clears both the flash-font map and any SD-font registration for fontId. // Coupled to avoid dangling SdCardFont* in sdCardFonts_ when callers free // the underlying SdCardFont and forget the SD-side unregister. void removeFont(int fontId) { fontMap.erase(fontId); sdCardFonts_.erase(fontId); } void setFontCacheManager(FontCacheManager* m) { fontCacheManager_ = m; } FontCacheManager* getFontCacheManager() const { return fontCacheManager_; } bool isFontCacheScanning() const; const std::map& getFontMap() const { return fontMap; } void registerSdCardFont(int fontId, SdCardFont* font) { sdCardFonts_[fontId] = font; } void unregisterSdCardFont(int fontId) { removeFont(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). // styleMask: bitmask of styles to prepare (bit 0=regular, 1=bold, 2=italic, 3=bold-italic). void ensureSdCardFontReady(int fontId, const char* utf8Text, uint8_t styleMask = 0x0F) const; void ensureSdCardFontReady(int fontId, const std::vector& words, bool includeHyphen, uint8_t styleMask = 0x0F) const; // 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; // Tiled grayscale strip target. While active, drawPixel() and clearScreen() // operate on `scratch` (panelWidthBytes * stripRows bytes, holding physical // rows [stripY0, stripY0 + stripRows)) instead of the framebuffer; pixels // whose physical row falls outside the band are clipped. The clip is applied // after the orientation rotate, so it is orientation-agnostic. Used to render // grayscale planes band-by-band without a full second buffer. void beginStripTarget(uint8_t* scratch, int stripY0, int stripRows) const; void endStripTarget() const; // Band culling for tiled grayscale. Takes a glyph bounding box in logical // screen coords and returns false only when a strip is active AND the box's // physical y-extent lies entirely outside the active band, letting callers // skip an expensive bitmap decode. Returns true when no strip is active. // Corners are rotated to physical, so it is orientation-aware. bool glyphIntersectsStrip(int x0, int y0, int x1, int y1) const; // Active pixel-write target for raw writers (DirectPixelWriter) that bypass // drawPixel for speed. When a strip target is active these return the band // scratch plus its physical-row origin and extent; otherwise the full // framebuffer ([0, panelHeight)). Writers subtract the origin and clip to the // extent, so they honor tiled-grayscale banding without per-pixel method calls. uint8_t* getWriteTarget() const { return _stripActive ? _stripBuf : frameBuffer; } int getWriteOriginY() const { return _stripActive ? _stripY0 : 0; } int getWriteRows() const { return _stripActive ? _stripRows : panelHeight; } // 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 maskRoundedRectOutsideCorners(int x, int y, int width, int height, int radius, Color color = Color::White) 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 size) 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; // Snapshot / restore a screen-coordinate framebuffer region (byte-aligned in // panel memory). readFramebufferRegion returns the bytes written to dst, or // 0 when the region is empty, offscreen, or exceeds dstCapacity. Pass the // same rectangle to writeFramebufferRegion to restore the saved pixels. // Enables partial-repaint patterns (e.g. moving a selection highlight) // without re-rendering the whole page. size_t readFramebufferRegion(int x, int y, int w, int h, uint8_t* dst, size_t dstCapacity) const; void writeFramebufferRegion(int x, int y, int w, int h, const uint8_t* src); // Text int getTextWidth(int fontId, const char* text, EpdFontFamily::Style style = EpdFontFamily::REGULAR, BidiUtils::BidiBaseDir baseDir = BidiUtils::BidiBaseDir::AUTO) const; void drawCenteredText(int fontId, int y, const char* text, bool black = true, EpdFontFamily::Style style = EpdFontFamily::REGULAR, BidiUtils::BidiBaseDir baseDir = BidiUtils::BidiBaseDir::AUTO) const; void drawText(int fontId, int x, int y, const char* text, bool black = true, EpdFontFamily::Style style = EpdFontFamily::REGULAR, BidiUtils::BidiBaseDir baseDir = BidiUtils::BidiBaseDir::AUTO) 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 = mode; } RenderMode getRenderMode() const { return renderMode; } // Grayscale preconditioning settle pass (no-op on X4). The rect overload // takes the gray region in LOGICAL screen coordinates and rotates it to the // panel; the no-arg overload settles the full frame. Call after the BW base // frame is displayed and before the grayscale planes are written. void preconditionGrayscale() const; void preconditionGrayscale(int x, int y, int w, int h) const; // Display the framebuffer as the base frame for a grayscale overlay that // follows (X3: OEM differential base waveform; others: plain display with // `fallback`). void displayGrayscaleBase(HalDisplay::RefreshMode fallback = HalDisplay::HALF_REFRESH) const; void copyGrayscaleLsbBuffers() const; void copyGrayscaleMsbBuffers() const; void displayGrayBuffer() const; // Tiled grayscale (X4): stream one band of a plane straight to controller RAM // from `scratch` (panelWidthBytes * numRows, physical rows [yStart, yStart+ // numRows)), bypassing the framebuffer. supportsStripGrayscale() gates use. void writeGrayscalePlaneStrip(bool lsbPlane, const uint8_t* scratch, int yStart, int numRows) const; bool supportsStripGrayscale() 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; // Lend the 48 KB framebuffer's bytes to a memory-hungry phase (chapter // builds) WITHOUT freeing the allocation, so it never moves and repeated // loans cannot fragment the heap. Between release and restore NOTHING may // draw or display — the panel keeps showing its last refreshed image. The // lent bytes are published via buildscratch::claim() for consumers like // InflateStream. restore returns the buffer white, so the caller must // redraw the full screen; it cannot fail (no allocation involved). void releaseFrameBufferForBuild(); bool restoreFrameBufferAfterBuild(); bool hasFrameBuffer() const { return frameBuffer != nullptr; } // RAII form of the loan above, for blocking build regions with early-return // error paths: restores on scope exit (or explicitly via end()). Display the // popup/screen the panel should hold BEFORE constructing one. Constructing // while the framebuffer is already lent yields an inert loan (nesting-safe). class FrameBufferLoan { public: explicit FrameBufferLoan(GfxRenderer& renderer); ~FrameBufferLoan() { end(); } void end(); FrameBufferLoan(const FrameBufferLoan&) = delete; FrameBufferLoan& operator=(const FrameBufferLoan&) = delete; private: GfxRenderer& renderer_; bool active_ = false; }; // 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; } // Region cache: take a logical (orientation-aware) rect, hit the framebuffer // bytes that the rect can have touched, and pump them in or out of a caller- // supplied buffer. Used by HomeActivity to snapshot just the cover tile // (~16 KB in Portrait) instead of cloning the entire 48 KB framebuffer. // // getRegionByteSize: required buffer length for the rect at current orientation. // copyRegionToBuffer / copyBufferToRegion: false if `bufSize` is smaller than that. size_t getRegionByteSize(int logicalX, int logicalY, int logicalW, int logicalH) const; bool copyRegionToBuffer(int logicalX, int logicalY, int logicalW, int logicalH, uint8_t* buf, size_t bufSize) const; bool copyBufferToRegion(int logicalX, int logicalY, int logicalW, int logicalH, const uint8_t* buf, size_t bufSize) const; };