Files
Crosspoint/lib/GfxRenderer/GfxRenderer.h
T

300 lines
16 KiB
C++

#pragma once
#include <EpdFontFamily.h>
#include <HalDisplay.h>
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 <cstring>
#include <map>
#include <string>
#include <vector>
#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<uint8_t*> bwBufferChunks;
std::map<int, EpdFontFamily> 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<int, SdCardFont*> 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 <Color color>
void drawPixelDither(int x, int y) const;
template <Color color>
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 <Color color>
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<int, EpdFontFamily>& 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<int, SdCardFont*>& 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<std::string>& 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;
// 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<std::string> 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;
};