feat: Add dictionary word lookup feature with cached index

Implements StarDict-based dictionary lookup from the reader menu,
adapted from upstream PR #857 with /.dictionary/ folder path,
std::vector compatibility (PR #802), HTML definition rendering,
orientation-aware button hints, side button hints with CCW text
rotation, sparse index caching to SD card, pronunciation line
filtering, and reorganized reader menu with bookmark stubs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
cottongin
2026-02-12 19:36:14 -05:00
parent 905f694576
commit 8d4bbf284d
17 changed files with 2195 additions and 9 deletions

View File

@@ -905,6 +905,92 @@ void GfxRenderer::drawTextRotated90CW(const int fontId, const int x, const int y
}
}
void GfxRenderer::drawTextRotated90CCW(const int fontId, const int x, const int y, const char* text, const bool black,
const EpdFontFamily::Style style) const {
// Cannot draw a NULL / empty string
if (text == nullptr || *text == '\0') {
return;
}
if (fontMap.count(fontId) == 0) {
Serial.printf("[%lu] [GFX] Font %d not found\n", millis(), fontId);
return;
}
const auto font = fontMap.at(fontId);
// No printable characters
if (!font.hasPrintableChars(text, style)) {
return;
}
// For 90° counter-clockwise rotation:
// 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;
const int ascender = font.getData(style)->ascender;
int yPos = y; // Current Y position (increases as we draw characters)
uint32_t cp;
while ((cp = utf8NextCodepoint(reinterpret_cast<const uint8_t**>(&text)))) {
const EpdGlyph* glyph = font.getGlyph(cp, 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;
}
}
uint8_t* GfxRenderer::getFrameBuffer() const { return frameBuffer; }
size_t GfxRenderer::getBufferSize() { return HalDisplay::BUFFER_SIZE; }

View File

@@ -111,9 +111,11 @@ class GfxRenderer {
std::string truncatedText(int fontId, const char* text, int maxWidth,
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
// Helper for drawing rotated text (90 degrees clockwise, for side buttons)
// Helpers for drawing rotated text (for side buttons)
void drawTextRotated90CW(int fontId, int x, int y, const char* text, bool black = true,
EpdFontFamily::Style style = EpdFontFamily::REGULAR) const;
void drawTextRotated90CCW(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