perf: Port upstream font drawing performance optimization (PR #978)
Cherry-pick upstream commit 07d715e which refactors renderChar and
drawTextRotated90CW into a template-based renderCharImpl, hoisting
the is2Bit branch outside inner pixel loops for 15-23% speedup.
Additionally extends the template with Rotated90CCW to fix two bugs
in the mod's drawTextRotated90CCW: operator precedence in bmpVal
calculation and missing compressed font support via getGlyphBitmap.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -59,6 +59,130 @@ static inline void rotateCoordinates(const GfxRenderer::Orientation orientation,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class TextRotation { None, Rotated90CW, Rotated90CCW };
|
||||||
|
|
||||||
|
// Shared glyph rendering logic for normal and rotated text.
|
||||||
|
// Coordinate mapping and cursor advance direction are selected at compile time via the template parameter.
|
||||||
|
template <TextRotation rotation>
|
||||||
|
static void renderCharImpl(const GfxRenderer& renderer, GfxRenderer::RenderMode renderMode,
|
||||||
|
const EpdFontFamily& fontFamily, const uint32_t cp, int* cursorX, int* cursorY,
|
||||||
|
const bool pixelState, const EpdFontFamily::Style style) {
|
||||||
|
const EpdGlyph* glyph = fontFamily.getGlyph(cp, style);
|
||||||
|
if (!glyph) {
|
||||||
|
glyph = fontFamily.getGlyph(REPLACEMENT_GLYPH, style);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!glyph) {
|
||||||
|
LOG_ERR("GFX", "No glyph for codepoint %d", cp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EpdFontData* fontData = fontFamily.getData(style);
|
||||||
|
const bool is2Bit = fontData->is2Bit;
|
||||||
|
const uint8_t width = glyph->width;
|
||||||
|
const uint8_t height = glyph->height;
|
||||||
|
const int left = glyph->left;
|
||||||
|
const int top = glyph->top;
|
||||||
|
|
||||||
|
const uint8_t* bitmap = renderer.getGlyphBitmap(fontData, glyph);
|
||||||
|
|
||||||
|
if (bitmap != nullptr) {
|
||||||
|
int outerBase, innerBase;
|
||||||
|
if constexpr (rotation == TextRotation::Rotated90CW) {
|
||||||
|
outerBase = *cursorX + fontData->ascender - top; // screenX = outerBase + glyphY
|
||||||
|
innerBase = *cursorY - left; // screenY = innerBase - glyphX
|
||||||
|
} else if constexpr (rotation == TextRotation::Rotated90CCW) {
|
||||||
|
outerBase = *cursorX + fontData->advanceY - 1 - fontData->ascender + top; // screenX = outerBase - glyphY
|
||||||
|
innerBase = *cursorY + left; // screenY = innerBase + glyphX
|
||||||
|
} else {
|
||||||
|
outerBase = *cursorY - top; // screenY = outerBase + glyphY
|
||||||
|
innerBase = *cursorX + left; // screenX = innerBase + glyphX
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is2Bit) {
|
||||||
|
int pixelPosition = 0;
|
||||||
|
for (int glyphY = 0; glyphY < height; glyphY++) {
|
||||||
|
int outerCoord;
|
||||||
|
if constexpr (rotation == TextRotation::Rotated90CCW) {
|
||||||
|
outerCoord = outerBase - glyphY;
|
||||||
|
} else {
|
||||||
|
outerCoord = outerBase + glyphY;
|
||||||
|
}
|
||||||
|
for (int glyphX = 0; glyphX < width; glyphX++, pixelPosition++) {
|
||||||
|
int screenX, screenY;
|
||||||
|
if constexpr (rotation == TextRotation::Rotated90CW) {
|
||||||
|
screenX = outerCoord;
|
||||||
|
screenY = innerBase - glyphX;
|
||||||
|
} else if constexpr (rotation == TextRotation::Rotated90CCW) {
|
||||||
|
screenX = outerCoord;
|
||||||
|
screenY = innerBase + glyphX;
|
||||||
|
} else {
|
||||||
|
screenX = innerBase + glyphX;
|
||||||
|
screenY = outerCoord;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t byte = bitmap[pixelPosition >> 2];
|
||||||
|
const uint8_t bit_index = (3 - (pixelPosition & 3)) * 2;
|
||||||
|
// the direct bit from the font is 0 -> white, 1 -> light gray, 2 -> dark gray, 3 -> black
|
||||||
|
// we swap this to better match the way images and screen think about colors:
|
||||||
|
// 0 -> black, 1 -> dark grey, 2 -> light grey, 3 -> white
|
||||||
|
const uint8_t bmpVal = 3 - ((byte >> bit_index) & 0x3);
|
||||||
|
|
||||||
|
if (renderMode == GfxRenderer::BW && bmpVal < 3) {
|
||||||
|
// Black (also paints over the grays in BW mode)
|
||||||
|
renderer.drawPixel(screenX, screenY, pixelState);
|
||||||
|
} else if (renderMode == GfxRenderer::GRAYSCALE_MSB && (bmpVal == 1 || bmpVal == 2)) {
|
||||||
|
// Light gray (also mark the MSB if it's going to be a dark gray too)
|
||||||
|
// We have to flag pixels in reverse for the gray buffers, as 0 leave alone, 1 update
|
||||||
|
renderer.drawPixel(screenX, screenY, false);
|
||||||
|
} else if (renderMode == GfxRenderer::GRAYSCALE_LSB && bmpVal == 1) {
|
||||||
|
// Dark gray
|
||||||
|
renderer.drawPixel(screenX, screenY, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int pixelPosition = 0;
|
||||||
|
for (int glyphY = 0; glyphY < height; glyphY++) {
|
||||||
|
int outerCoord;
|
||||||
|
if constexpr (rotation == TextRotation::Rotated90CCW) {
|
||||||
|
outerCoord = outerBase - glyphY;
|
||||||
|
} else {
|
||||||
|
outerCoord = outerBase + glyphY;
|
||||||
|
}
|
||||||
|
for (int glyphX = 0; glyphX < width; glyphX++, pixelPosition++) {
|
||||||
|
int screenX, screenY;
|
||||||
|
if constexpr (rotation == TextRotation::Rotated90CW) {
|
||||||
|
screenX = outerCoord;
|
||||||
|
screenY = innerBase - glyphX;
|
||||||
|
} else if constexpr (rotation == TextRotation::Rotated90CCW) {
|
||||||
|
screenX = outerCoord;
|
||||||
|
screenY = innerBase + glyphX;
|
||||||
|
} else {
|
||||||
|
screenX = innerBase + glyphX;
|
||||||
|
screenY = outerCoord;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t byte = bitmap[pixelPosition >> 3];
|
||||||
|
const uint8_t bit_index = 7 - (pixelPosition & 7);
|
||||||
|
|
||||||
|
if ((byte >> bit_index) & 1) {
|
||||||
|
renderer.drawPixel(screenX, screenY, pixelState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr (rotation == TextRotation::Rotated90CW) {
|
||||||
|
*cursorY -= glyph->advanceX;
|
||||||
|
} else if constexpr (rotation == TextRotation::Rotated90CCW) {
|
||||||
|
*cursorY += glyph->advanceX;
|
||||||
|
} else {
|
||||||
|
*cursorX += glyph->advanceX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// IMPORTANT: This function is in critical rendering path and is called for every pixel. Please keep it as simple and
|
// IMPORTANT: This function is in critical rendering path and is called for every pixel. Please keep it as simple and
|
||||||
// efficient as possible.
|
// efficient as possible.
|
||||||
void GfxRenderer::drawPixel(const int x, const int y, const bool state) const {
|
void GfxRenderer::drawPixel(const int x, const int y, const bool state) const {
|
||||||
@@ -115,7 +239,7 @@ void GfxRenderer::drawCenteredText(const int fontId, const int y, const char* te
|
|||||||
|
|
||||||
void GfxRenderer::drawText(const int fontId, const int x, const int y, const char* text, const bool black,
|
void GfxRenderer::drawText(const int fontId, const int x, const int y, const char* text, const bool black,
|
||||||
const EpdFontFamily::Style style) const {
|
const EpdFontFamily::Style style) const {
|
||||||
const int yPos = y + getFontAscenderSize(fontId);
|
int yPos = y + getFontAscenderSize(fontId);
|
||||||
int xpos = x;
|
int xpos = x;
|
||||||
|
|
||||||
// cannot draw a NULL / empty string
|
// cannot draw a NULL / empty string
|
||||||
@@ -890,68 +1014,12 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y
|
|||||||
|
|
||||||
const auto& font = fontIt->second;
|
const auto& font = fontIt->second;
|
||||||
|
|
||||||
// For 90° clockwise rotation:
|
int xPos = x;
|
||||||
// Original (glyphX, glyphY) -> Rotated (glyphY, -glyphX)
|
int yPos = y;
|
||||||
// Text reads from bottom to top
|
|
||||||
|
|
||||||
int yPos = y; // Current Y position (decreases as we draw characters)
|
|
||||||
|
|
||||||
uint32_t cp;
|
uint32_t cp;
|
||||||
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) {
|
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) {
|
||||||
const EpdGlyph* glyph = font.getGlyph(cp, style);
|
renderCharImpl<TextRotation::Rotated90CW>(*this, renderMode, font, cp, &xPos, &yPos, black, style);
|
||||||
if (!glyph) {
|
|
||||||
glyph = font.getGlyph(REPLACEMENT_GLYPH, style);
|
|
||||||
}
|
|
||||||
if (!glyph) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EpdFontData* fontData = font.getData(style);
|
|
||||||
const int is2Bit = fontData->is2Bit;
|
|
||||||
const uint8_t width = glyph->width;
|
|
||||||
const uint8_t height = glyph->height;
|
|
||||||
const int left = glyph->left;
|
|
||||||
const int top = glyph->top;
|
|
||||||
|
|
||||||
const uint8_t* bitmap = getGlyphBitmap(fontData, glyph);
|
|
||||||
|
|
||||||
if (bitmap != nullptr) {
|
|
||||||
for (int glyphY = 0; glyphY < height; glyphY++) {
|
|
||||||
for (int glyphX = 0; glyphX < width; glyphX++) {
|
|
||||||
const int pixelPosition = glyphY * width + glyphX;
|
|
||||||
|
|
||||||
// 90° clockwise rotation transformation:
|
|
||||||
// screenX = x + (ascender - top + glyphY)
|
|
||||||
// screenY = yPos - (left + glyphX)
|
|
||||||
const int screenX = x + (fontData->ascender - top + glyphY);
|
|
||||||
const int screenY = yPos - left - glyphX;
|
|
||||||
|
|
||||||
if (is2Bit) {
|
|
||||||
const uint8_t byte = bitmap[pixelPosition / 4];
|
|
||||||
const uint8_t bit_index = (3 - pixelPosition % 4) * 2;
|
|
||||||
const uint8_t bmpVal = 3 - (byte >> bit_index) & 0x3;
|
|
||||||
|
|
||||||
if (renderMode == BW && bmpVal < 3) {
|
|
||||||
drawPixel(screenX, screenY, black);
|
|
||||||
} else if (renderMode == GRAYSCALE_MSB && (bmpVal == 1 || bmpVal == 2)) {
|
|
||||||
drawPixel(screenX, screenY, false);
|
|
||||||
} else if (renderMode == GRAYSCALE_LSB && bmpVal == 1) {
|
|
||||||
drawPixel(screenX, screenY, false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const uint8_t byte = bitmap[pixelPosition / 8];
|
|
||||||
const uint8_t bit_index = 7 - (pixelPosition % 8);
|
|
||||||
|
|
||||||
if ((byte >> bit_index) & 1) {
|
|
||||||
drawPixel(screenX, screenY, black);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move to next character position (going up, so decrease Y)
|
|
||||||
yPos -= glyph->advanceX;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -962,77 +1030,20 @@ void GfxRenderer::drawTextRotated90CCW(const int fontId, const int x, const int
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fontMap.count(fontId) == 0) {
|
const auto fontIt = fontMap.find(fontId);
|
||||||
|
if (fontIt == fontMap.end()) {
|
||||||
LOG_ERR("GFX", "Font %d not found", fontId);
|
LOG_ERR("GFX", "Font %d not found", fontId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto font = fontMap.at(fontId);
|
|
||||||
|
|
||||||
// For 90° counter-clockwise rotation:
|
const auto& font = fontIt->second;
|
||||||
// Mirror of CW: glyphY maps to -X direction, glyphX maps to +Y direction
|
|
||||||
// Text reads from top to bottom
|
|
||||||
|
|
||||||
const int advanceY = font.getData(style)->advanceY;
|
int xPos = x;
|
||||||
const int ascender = font.getData(style)->ascender;
|
int yPos = y;
|
||||||
|
|
||||||
int yPos = y; // Current Y position (increases as we draw characters)
|
|
||||||
|
|
||||||
uint32_t cp;
|
uint32_t cp;
|
||||||
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) {
|
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) {
|
||||||
const EpdGlyph* glyph = font.getGlyph(cp, style);
|
renderCharImpl<TextRotation::Rotated90CCW>(*this, renderMode, font, cp, &xPos, &yPos, black, style);
|
||||||
if (!glyph) {
|
|
||||||
glyph = font.getGlyph(REPLACEMENT_GLYPH, style);
|
|
||||||
}
|
|
||||||
if (!glyph) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int is2Bit = font.getData(style)->is2Bit;
|
|
||||||
const uint32_t offset = glyph->dataOffset;
|
|
||||||
const uint8_t width = glyph->width;
|
|
||||||
const uint8_t height = glyph->height;
|
|
||||||
const int left = glyph->left;
|
|
||||||
const int top = glyph->top;
|
|
||||||
|
|
||||||
const uint8_t* bitmap = &font.getData(style)->bitmap[offset];
|
|
||||||
|
|
||||||
if (bitmap != nullptr) {
|
|
||||||
for (int glyphY = 0; glyphY < height; glyphY++) {
|
|
||||||
for (int glyphX = 0; glyphX < width; glyphX++) {
|
|
||||||
const int pixelPosition = glyphY * width + glyphX;
|
|
||||||
|
|
||||||
// 90° counter-clockwise rotation transformation:
|
|
||||||
// screenX = mirrored CW X (right-to-left within advanceY span)
|
|
||||||
// screenY = yPos + (left + glyphX) (downward)
|
|
||||||
const int screenX = x + advanceY - 1 - (ascender - top + glyphY);
|
|
||||||
const int screenY = yPos + left + glyphX;
|
|
||||||
|
|
||||||
if (is2Bit) {
|
|
||||||
const uint8_t byte = bitmap[pixelPosition / 4];
|
|
||||||
const uint8_t bit_index = (3 - pixelPosition % 4) * 2;
|
|
||||||
const uint8_t bmpVal = 3 - (byte >> bit_index) & 0x3;
|
|
||||||
|
|
||||||
if (renderMode == BW && bmpVal < 3) {
|
|
||||||
drawPixel(screenX, screenY, black);
|
|
||||||
} else if (renderMode == GRAYSCALE_MSB && (bmpVal == 1 || bmpVal == 2)) {
|
|
||||||
drawPixel(screenX, screenY, false);
|
|
||||||
} else if (renderMode == GRAYSCALE_LSB && bmpVal == 1) {
|
|
||||||
drawPixel(screenX, screenY, false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const uint8_t byte = bitmap[pixelPosition / 8];
|
|
||||||
const uint8_t bit_index = 7 - (pixelPosition % 8);
|
|
||||||
|
|
||||||
if ((byte >> bit_index) & 1) {
|
|
||||||
drawPixel(screenX, screenY, black);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move to next character position (going down, so increase Y)
|
|
||||||
yPos += glyph->advanceX;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1097,7 +1108,7 @@ bool GfxRenderer::storeBwBuffer() {
|
|||||||
* Uses chunked restoration to match chunked storage.
|
* Uses chunked restoration to match chunked storage.
|
||||||
*/
|
*/
|
||||||
void GfxRenderer::restoreBwBuffer() {
|
void GfxRenderer::restoreBwBuffer() {
|
||||||
// Check if any all chunks are allocated
|
// Check if all chunks are allocated
|
||||||
bool missingChunks = false;
|
bool missingChunks = false;
|
||||||
for (const auto& bwBufferChunk : bwBufferChunks) {
|
for (const auto& bwBufferChunk : bwBufferChunks) {
|
||||||
if (!bwBufferChunk) {
|
if (!bwBufferChunk) {
|
||||||
@@ -1112,13 +1123,6 @@ void GfxRenderer::restoreBwBuffer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < BW_BUFFER_NUM_CHUNKS; i++) {
|
for (size_t i = 0; i < BW_BUFFER_NUM_CHUNKS; i++) {
|
||||||
// Check if chunk is missing
|
|
||||||
if (!bwBufferChunks[i]) {
|
|
||||||
LOG_ERR("GFX", "!! BW buffer chunks not stored - this is likely a bug");
|
|
||||||
freeBwBufferChunks();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const size_t offset = i * BW_BUFFER_CHUNK_SIZE;
|
const size_t offset = i * BW_BUFFER_CHUNK_SIZE;
|
||||||
memcpy(frameBuffer + offset, bwBufferChunks[i], BW_BUFFER_CHUNK_SIZE);
|
memcpy(frameBuffer + offset, bwBufferChunks[i], BW_BUFFER_CHUNK_SIZE);
|
||||||
}
|
}
|
||||||
@@ -1139,66 +1143,9 @@ void GfxRenderer::cleanupGrayscaleWithFrameBuffer() const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GfxRenderer::renderChar(const EpdFontFamily& fontFamily, const uint32_t cp, int* x, const int* y,
|
void GfxRenderer::renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, int* y, bool pixelState,
|
||||||
const bool pixelState, const EpdFontFamily::Style style) const {
|
EpdFontFamily::Style style) const {
|
||||||
const EpdGlyph* glyph = fontFamily.getGlyph(cp, style);
|
renderCharImpl<TextRotation::None>(*this, renderMode, fontFamily, cp, x, y, pixelState, style);
|
||||||
if (!glyph) {
|
|
||||||
glyph = fontFamily.getGlyph(REPLACEMENT_GLYPH, style);
|
|
||||||
}
|
|
||||||
|
|
||||||
// no glyph?
|
|
||||||
if (!glyph) {
|
|
||||||
LOG_ERR("GFX", "No glyph for codepoint %d", cp);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const EpdFontData* fontData = fontFamily.getData(style);
|
|
||||||
const int is2Bit = fontData->is2Bit;
|
|
||||||
const uint8_t width = glyph->width;
|
|
||||||
const uint8_t height = glyph->height;
|
|
||||||
const int left = glyph->left;
|
|
||||||
|
|
||||||
const uint8_t* bitmap = getGlyphBitmap(fontData, glyph);
|
|
||||||
|
|
||||||
if (bitmap != nullptr) {
|
|
||||||
for (int glyphY = 0; glyphY < height; glyphY++) {
|
|
||||||
const int screenY = *y - glyph->top + glyphY;
|
|
||||||
for (int glyphX = 0; glyphX < width; glyphX++) {
|
|
||||||
const int pixelPosition = glyphY * width + glyphX;
|
|
||||||
const int screenX = *x + left + glyphX;
|
|
||||||
|
|
||||||
if (is2Bit) {
|
|
||||||
const uint8_t byte = bitmap[pixelPosition / 4];
|
|
||||||
const uint8_t bit_index = (3 - pixelPosition % 4) * 2;
|
|
||||||
// the direct bit from the font is 0 -> white, 1 -> light gray, 2 -> dark gray, 3 -> black
|
|
||||||
// we swap this to better match the way images and screen think about colors:
|
|
||||||
// 0 -> black, 1 -> dark grey, 2 -> light grey, 3 -> white
|
|
||||||
const uint8_t bmpVal = 3 - (byte >> bit_index) & 0x3;
|
|
||||||
|
|
||||||
if (renderMode == BW && bmpVal < 3) {
|
|
||||||
// Black (also paints over the grays in BW mode)
|
|
||||||
drawPixel(screenX, screenY, pixelState);
|
|
||||||
} else if (renderMode == GRAYSCALE_MSB && (bmpVal == 1 || bmpVal == 2)) {
|
|
||||||
// Light gray (also mark the MSB if it's going to be a dark gray too)
|
|
||||||
// We have to flag pixels in reverse for the gray buffers, as 0 leave alone, 1 update
|
|
||||||
drawPixel(screenX, screenY, false);
|
|
||||||
} else if (renderMode == GRAYSCALE_LSB && bmpVal == 1) {
|
|
||||||
// Dark gray
|
|
||||||
drawPixel(screenX, screenY, false);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const uint8_t byte = bitmap[pixelPosition / 8];
|
|
||||||
const uint8_t bit_index = 7 - (pixelPosition % 8);
|
|
||||||
|
|
||||||
if ((byte >> bit_index) & 1) {
|
|
||||||
drawPixel(screenX, screenY, pixelState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*x += glyph->advanceX;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GfxRenderer::getOrientedViewableTRBL(int* outTop, int* outRight, int* outBottom, int* outLeft) const {
|
void GfxRenderer::getOrientedViewableTRBL(int* outTop, int* outRight, int* outBottom, int* outLeft) const {
|
||||||
|
|||||||
@@ -38,10 +38,9 @@ class GfxRenderer {
|
|||||||
uint8_t* bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr};
|
uint8_t* bwBufferChunks[BW_BUFFER_NUM_CHUNKS] = {nullptr};
|
||||||
std::map<int, EpdFontFamily> fontMap;
|
std::map<int, EpdFontFamily> fontMap;
|
||||||
FontDecompressor* fontDecompressor = nullptr;
|
FontDecompressor* fontDecompressor = nullptr;
|
||||||
void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, const int* y, bool pixelState,
|
void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, int* y, bool pixelState,
|
||||||
EpdFontFamily::Style style) const;
|
EpdFontFamily::Style style) const;
|
||||||
void freeBwBufferChunks();
|
void freeBwBufferChunks();
|
||||||
const uint8_t* getGlyphBitmap(const EpdFontData* fontData, const EpdGlyph* glyph) const;
|
|
||||||
template <Color color>
|
template <Color color>
|
||||||
void drawPixelDither(int x, int y) const;
|
void drawPixelDither(int x, int y) const;
|
||||||
template <Color color>
|
template <Color color>
|
||||||
@@ -136,6 +135,9 @@ class GfxRenderer {
|
|||||||
void restoreBwBuffer(); // Restore and free the stored buffer
|
void restoreBwBuffer(); // Restore and free the stored buffer
|
||||||
void cleanupGrayscaleWithFrameBuffer() const;
|
void cleanupGrayscaleWithFrameBuffer() const;
|
||||||
|
|
||||||
|
// Font helpers
|
||||||
|
const uint8_t* getGlyphBitmap(const EpdFontData* fontData, const EpdGlyph* glyph) const;
|
||||||
|
|
||||||
// Low level functions
|
// Low level functions
|
||||||
uint8_t* getFrameBuffer() const;
|
uint8_t* getFrameBuffer() const;
|
||||||
static size_t getBufferSize();
|
static size_t getBufferSize();
|
||||||
|
|||||||
Reference in New Issue
Block a user