2025-03-04 23:09:43 +11:00
|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
#include <stdint.h>
|
2025-09-16 17:17:15 -07:00
|
|
|
#include <string.h>
|
2025-03-04 23:09:43 +11:00
|
|
|
|
|
|
|
|
class DisplayDriver {
|
|
|
|
|
int _w, _h;
|
|
|
|
|
protected:
|
|
|
|
|
DisplayDriver(int w, int h) { _w = w; _h = h; }
|
|
|
|
|
public:
|
2025-04-11 22:23:47 +02:00
|
|
|
enum Color { DARK=0, LIGHT, RED, GREEN, BLUE, YELLOW, ORANGE }; // on b/w screen, colors will be !=0 synonym of light
|
2025-03-04 23:09:43 +11:00
|
|
|
|
|
|
|
|
int width() const { return _w; }
|
|
|
|
|
int height() const { return _h; }
|
|
|
|
|
|
|
|
|
|
virtual bool isOn() = 0;
|
|
|
|
|
virtual void turnOn() = 0;
|
|
|
|
|
virtual void turnOff() = 0;
|
2025-04-01 14:46:48 +13:00
|
|
|
virtual void clear() = 0;
|
2025-03-04 23:09:43 +11:00
|
|
|
virtual void startFrame(Color bkg = DARK) = 0;
|
|
|
|
|
virtual void setTextSize(int sz) = 0;
|
|
|
|
|
virtual void setColor(Color c) = 0;
|
|
|
|
|
virtual void setCursor(int x, int y) = 0;
|
|
|
|
|
virtual void print(const char* str) = 0;
|
2025-08-08 20:01:31 +10:00
|
|
|
virtual void printWordWrap(const char* str, int max_width) { print(str); } // fallback to basic print() if no override
|
2025-03-04 23:09:43 +11:00
|
|
|
virtual void fillRect(int x, int y, int w, int h) = 0;
|
|
|
|
|
virtual void drawRect(int x, int y, int w, int h) = 0;
|
|
|
|
|
virtual void drawXbm(int x, int y, const uint8_t* bits, int w, int h) = 0;
|
2025-05-04 18:17:18 -07:00
|
|
|
virtual uint16_t getTextWidth(const char* str) = 0;
|
2026-05-16 13:27:34 +02:00
|
|
|
virtual int getCharWidth() const { return 6; } // advance width of a typical character (xAdvance for monospace)
|
|
|
|
|
virtual int getLineHeight() const { return 8; } // pixel rows per line (cell height, not just glyph height)
|
2025-08-08 20:01:31 +10:00
|
|
|
virtual void drawTextCentered(int mid_x, int y, const char* str) { // helper method (override to optimise)
|
|
|
|
|
int w = getTextWidth(str);
|
|
|
|
|
setCursor(mid_x - w/2, y);
|
|
|
|
|
print(str);
|
|
|
|
|
}
|
2025-09-30 09:21:12 +02:00
|
|
|
virtual void drawTextRightAlign(int x_anch, int y, const char* str) {
|
|
|
|
|
int w = getTextWidth(str);
|
|
|
|
|
setCursor(x_anch - w, y);
|
|
|
|
|
print(str);
|
|
|
|
|
}
|
|
|
|
|
virtual void drawTextLeftAlign(int x_anch, int y, const char* str) {
|
|
|
|
|
setCursor(x_anch, y);
|
|
|
|
|
print(str);
|
|
|
|
|
}
|
2025-09-16 17:17:15 -07:00
|
|
|
|
feat: transliterate accented/diacritic characters to ASCII in translateUTF8ToBlocks
Replaces the block-character fallback with proper transliteration for
Polish (ą→a, ć→c, ę→e, ł→l, ń→n, ó→o, ś→s, ź→z, ż→z), German (ä ö ü ß),
and common French/Spanish/Portuguese accents. Unknown codepoints fall back
to '?' instead of █.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 14:46:53 +02:00
|
|
|
static char transliterateCodepoint(uint32_t cp) {
|
|
|
|
|
switch (cp) {
|
|
|
|
|
// Polish
|
|
|
|
|
case 0x0105: return 'a'; case 0x0104: return 'A'; // ą Ą
|
|
|
|
|
case 0x0107: return 'c'; case 0x0106: return 'C'; // ć Ć
|
|
|
|
|
case 0x0119: return 'e'; case 0x0118: return 'E'; // ę Ę
|
|
|
|
|
case 0x0142: return 'l'; case 0x0141: return 'L'; // ł Ł
|
|
|
|
|
case 0x0144: return 'n'; case 0x0143: return 'N'; // ń Ń
|
|
|
|
|
case 0x00F3: return 'o'; case 0x00D3: return 'O'; // ó Ó
|
|
|
|
|
case 0x015B: return 's'; case 0x015A: return 'S'; // ś Ś
|
|
|
|
|
case 0x017A: return 'z'; case 0x0179: return 'Z'; // ź Ź
|
|
|
|
|
case 0x017C: return 'z'; case 0x017B: return 'Z'; // ż Ż
|
|
|
|
|
// German
|
|
|
|
|
case 0x00E4: return 'a'; case 0x00C4: return 'A'; // ä Ä
|
|
|
|
|
case 0x00F6: return 'o'; case 0x00D6: return 'O'; // ö Ö
|
|
|
|
|
case 0x00FC: return 'u'; case 0x00DC: return 'U'; // ü Ü
|
|
|
|
|
case 0x00DF: return 's'; // ß
|
|
|
|
|
// French/Spanish/Portuguese common accents
|
|
|
|
|
case 0x00E0: case 0x00E1: case 0x00E2: case 0x00E3: return 'a';
|
|
|
|
|
case 0x00C0: case 0x00C1: case 0x00C2: case 0x00C3: return 'A';
|
|
|
|
|
case 0x00E8: case 0x00E9: case 0x00EA: case 0x00EB: return 'e';
|
|
|
|
|
case 0x00C8: case 0x00C9: case 0x00CA: case 0x00CB: return 'E';
|
|
|
|
|
case 0x00EC: case 0x00ED: case 0x00EE: case 0x00EF: return 'i';
|
|
|
|
|
case 0x00CC: case 0x00CD: case 0x00CE: case 0x00CF: return 'I';
|
|
|
|
|
case 0x00F2: case 0x00F4: case 0x00F5: return 'o';
|
|
|
|
|
case 0x00D2: case 0x00D4: case 0x00D5: return 'O';
|
|
|
|
|
case 0x00F9: case 0x00FA: case 0x00FB: return 'u';
|
|
|
|
|
case 0x00D9: case 0x00DA: case 0x00DB: return 'U';
|
|
|
|
|
case 0x00E7: return 'c'; case 0x00C7: return 'C'; // ç Ç
|
|
|
|
|
case 0x00F1: return 'n'; case 0x00D1: return 'N'; // ñ Ñ
|
|
|
|
|
case 0x00FD: return 'y'; case 0x00DD: return 'Y'; // ý Ý
|
2026-05-16 15:05:53 +02:00
|
|
|
default: return '\xDB'; // CP437 full block █
|
feat: transliterate accented/diacritic characters to ASCII in translateUTF8ToBlocks
Replaces the block-character fallback with proper transliteration for
Polish (ą→a, ć→c, ę→e, ł→l, ń→n, ó→o, ś→s, ź→z, ż→z), German (ä ö ü ß),
and common French/Spanish/Portuguese accents. Unknown codepoints fall back
to '?' instead of █.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 14:46:53 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// convert UTF-8 to ASCII, transliterating accented/diacritic characters
|
2025-09-16 17:17:15 -07:00
|
|
|
virtual void translateUTF8ToBlocks(char* dest, const char* src, size_t dest_size) {
|
|
|
|
|
size_t j = 0;
|
feat: transliterate accented/diacritic characters to ASCII in translateUTF8ToBlocks
Replaces the block-character fallback with proper transliteration for
Polish (ą→a, ć→c, ę→e, ł→l, ń→n, ó→o, ś→s, ź→z, ż→z), German (ä ö ü ß),
and common French/Spanish/Portuguese accents. Unknown codepoints fall back
to '?' instead of █.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 14:46:53 +02:00
|
|
|
const uint8_t* p = (const uint8_t*)src;
|
|
|
|
|
while (*p && j < dest_size - 1) {
|
|
|
|
|
uint8_t c = *p++;
|
|
|
|
|
if (c < 0x80) {
|
|
|
|
|
if (c >= 32) dest[j++] = c;
|
|
|
|
|
} else {
|
|
|
|
|
uint32_t cp = c;
|
|
|
|
|
if ((c & 0xE0) == 0xC0) {
|
|
|
|
|
cp = c & 0x1F;
|
|
|
|
|
if (*p) cp = (cp << 6) | (*p++ & 0x3F);
|
|
|
|
|
} else if ((c & 0xF0) == 0xE0) {
|
|
|
|
|
cp = c & 0x0F;
|
|
|
|
|
if (*p) cp = (cp << 6) | (*p++ & 0x3F);
|
|
|
|
|
if (*p) cp = (cp << 6) | (*p++ & 0x3F);
|
|
|
|
|
} else {
|
|
|
|
|
while (*p && (*p & 0xC0) == 0x80) p++;
|
2026-05-16 15:05:53 +02:00
|
|
|
dest[j++] = '\xDB';
|
feat: transliterate accented/diacritic characters to ASCII in translateUTF8ToBlocks
Replaces the block-character fallback with proper transliteration for
Polish (ą→a, ć→c, ę→e, ł→l, ń→n, ó→o, ś→s, ź→z, ż→z), German (ä ö ü ß),
and common French/Spanish/Portuguese accents. Unknown codepoints fall back
to '?' instead of █.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-16 14:46:53 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
dest[j++] = transliterateCodepoint(cp);
|
2025-09-16 17:17:15 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
dest[j] = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// draw text with ellipsis if it exceeds max_width
|
|
|
|
|
virtual void drawTextEllipsized(int x, int y, int max_width, const char* str) {
|
|
|
|
|
char temp_str[256]; // reasonable buffer size
|
|
|
|
|
size_t len = strlen(str);
|
|
|
|
|
if (len >= sizeof(temp_str)) len = sizeof(temp_str) - 1;
|
|
|
|
|
memcpy(temp_str, str, len);
|
|
|
|
|
temp_str[len] = 0;
|
|
|
|
|
|
|
|
|
|
if (getTextWidth(temp_str) <= max_width) {
|
|
|
|
|
setCursor(x, y);
|
|
|
|
|
print(temp_str);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// for variable-width fonts (GxEPD), add space after ellipsis
|
|
|
|
|
// for fixed-width fonts (OLED), keep tight spacing to save precious characters
|
|
|
|
|
const char* ellipsis;
|
|
|
|
|
// use a simple heuristic: if 'i' and 'l' have different widths, it's variable-width
|
|
|
|
|
int i_width = getTextWidth("i");
|
|
|
|
|
int l_width = getTextWidth("l");
|
|
|
|
|
if (i_width != l_width) {
|
|
|
|
|
ellipsis = "... "; // variable-width fonts: add space
|
|
|
|
|
} else {
|
|
|
|
|
ellipsis = "..."; // fixed-width fonts: no space
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int ellipsis_width = getTextWidth(ellipsis);
|
|
|
|
|
int str_len = strlen(temp_str);
|
|
|
|
|
|
|
|
|
|
while (str_len > 0 && getTextWidth(temp_str) > max_width - ellipsis_width) {
|
|
|
|
|
temp_str[--str_len] = 0;
|
|
|
|
|
}
|
|
|
|
|
strcat(temp_str, ellipsis);
|
|
|
|
|
|
|
|
|
|
setCursor(x, y);
|
|
|
|
|
print(temp_str);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-10 15:18:35 +02:00
|
|
|
virtual void setBrightness(uint8_t level) { } // level 0-4 (min to max), no-op default
|
2025-03-04 23:09:43 +11:00
|
|
|
virtual void endFrame() = 0;
|
|
|
|
|
};
|