| 214 | |
| 215 | template <TextRotation rotation = TextRotation::None> |
| 216 | static void renderCharImpl(const GfxRenderer& renderer, GfxRenderer::RenderMode renderMode, |
| 217 | const EpdFontFamily& fontFamily, const uint32_t cp, int cursorX, int cursorY, |
| 218 | const bool pixelState, const EpdFontFamily::Style style) { |
| 219 | const EpdGlyph* glyph = fontFamily.getGlyph(cp, style); |
| 220 | if (!glyph) { |
| 221 | LOG_ERR("GFX", "No glyph for codepoint %d", cp); |
| 222 | return; |
| 223 | } |
| 224 | |
| 225 | const EpdFontData* fontData = fontFamily.getData(style); |
| 226 | const bool is2Bit = fontData->is2Bit; |
| 227 | const uint8_t width = glyph->width; |
| 228 | const uint8_t height = glyph->height; |
| 229 | const int left = glyph->left; |
| 230 | const int top = glyph->top; |
| 231 | |
| 232 | // Tiled-grayscale band culling: if this glyph's physical y-extent is entirely |
| 233 | // outside the active strip, skip it before the expensive bitmap decode. This |
| 234 | // is what makes per-band re-rendering cheap. No-op outside strip mode. |
| 235 | if constexpr (rotation == TextRotation::Rotated90CW) { |
| 236 | const int ob = cursorX + fontData->ascender - top; |
| 237 | const int ib = cursorY - left; |
| 238 | if (!renderer.glyphIntersectsStrip(ob, ib - (width - 1), ob + height - 1, ib)) { |
| 239 | return; |
| 240 | } |
| 241 | } else { |
| 242 | const int gx0 = cursorX + left; |
| 243 | const int gy0 = cursorY - top; |
| 244 | if (!renderer.glyphIntersectsStrip(gx0, gy0, gx0 + width - 1, gy0 + height - 1)) { |
| 245 | return; |
| 246 | } |
| 247 | } |
| 248 | |
| 249 | const uint8_t* bitmap = renderer.getGlyphBitmap(fontData, glyph); |
| 250 | |
| 251 | if (bitmap != nullptr) { |
| 252 | // For Normal: outer loop advances screenY, inner loop advances screenX |
| 253 | // For Rotated: outer loop advances screenX, inner loop advances screenY (in reverse) |
| 254 | int outerBase, innerBase; |
| 255 | if constexpr (rotation == TextRotation::Rotated90CW) { |
| 256 | outerBase = cursorX + fontData->ascender - top; // screenX = outerBase + glyphY |
| 257 | innerBase = cursorY - left; // screenY = innerBase - glyphX |
| 258 | } else { |
| 259 | outerBase = cursorY - top; // screenY = outerBase + glyphY |
| 260 | innerBase = cursorX + left; // screenX = innerBase + glyphX |
| 261 | } |
| 262 | |
| 263 | if (is2Bit) { |
| 264 | int pixelPosition = 0; |
| 265 | for (int glyphY = 0; glyphY < height; glyphY++) { |
| 266 | const int outerCoord = outerBase + glyphY; |
| 267 | for (int glyphX = 0; glyphX < width; glyphX++, pixelPosition++) { |
| 268 | int screenX, screenY; |
| 269 | if constexpr (rotation == TextRotation::Rotated90CW) { |
| 270 | screenX = outerCoord; |
| 271 | screenY = innerBase - glyphX; |
| 272 | } else { |
| 273 | screenX = innerBase + glyphX; |
nothing calls this directly
no test coverage detected