9#include <goodisplay/gdey029T94.h>
23void writeRaw(Gdey029T94* gfx,
const char* text) {
24 for (
const uint8_t* p =
reinterpret_cast<const uint8_t*
>(text); *p; ++p) {
41 if (!gfx || !text || maxWidthPx <= 0)
return;
45 gfx->getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
46 if (
static_cast<int>(w) <= maxWidthPx) {
51 constexpr char ELLIPSIS[] =
"...";
54 gfx->getTextBounds(ELLIPSIS, 0, 0, &ex1, &ey1, &ew, &eh);
56 const int budget = maxWidthPx -
static_cast<int>(ew);
58 writeRaw(gfx, ELLIPSIS);
63 size_t len = std::strlen(text);
64 if (len >=
sizeof(buf)) len =
sizeof(buf) - 1;
65 std::memcpy(buf, text, len);
70 gfx->getTextBounds(buf, 0, 0, &x1, &y1, &w, &h);
71 if (
static_cast<int>(w) <= budget)
break;
76 writeRaw(gfx, ELLIPSIS);
80 uint16_t width,
int underlineOffset) {
83 if (title && title[0] !=
'\0') {
87 gfx->drawFastHLine(0, y + underlineOffset, width, EPD_BLACK);
99 if (!gfx || !title || title[0] ==
'\0')
return;
103 gfx->getTextBounds(title, 0, 0, &x1, &y1, &w, &h);
104 gfx->setCursor((width - w) / 2, y);
105 writeRaw(gfx, title);
119 const char* prefix,
const char* hint,
bool force) {
121 if (!force && !prefix && !hint)
return;
124 gfx->setFont(
nullptr);
126 gfx->setTextColor(EPD_WHITE);
127 gfx->setCursor(4, height - 12);
130 writeRaw(gfx, prefix);
149 uint16_t totalItems, uint16_t visibleItems,
150 uint16_t scrollPos) {
152 if (totalItems <= visibleItems)
return;
157 const int leftX = x + 1;
161 const int topY = y + 4;
170 if (scrollPos + visibleItems < totalItems) {
171 const int arrowY = y + listHeight - 12;
180 const int barHeight = listHeight - 24;
181 if (barHeight <= 0)
return;
184 const int barY = y + 12;
185 int thumbHeight = std::max(10, barHeight *
static_cast<int>(visibleItems) /
186 static_cast<int>(totalItems));
187 int scrollRange =
static_cast<int>(totalItems - visibleItems);
188 int thumbPos = scrollRange > 0 ? (barHeight - thumbHeight) *
189 static_cast<int>(scrollPos) / scrollRange
192 gfx->drawRect(barX, barY, 4, barHeight, EPD_BLACK);
193 gfx->fillRect(barX, barY + thumbPos, 4, thumbHeight, EPD_BLACK);
208 gfx->fillRect(x, y, w, h, EPD_WHITE);
209 gfx->drawRect(x, y, w, h, EPD_BLACK);
210 gfx->drawRect(x + 1, y + 1, w - 2, h - 2, EPD_BLACK);
220 case 0x80:
return 0xC7;
case 0x81:
return 0xFC;
221 case 0x82:
return 0xE9;
case 0x83:
return 0xE2;
222 case 0x84:
return 0xE4;
case 0x85:
return 0xE0;
223 case 0x86:
return 0xE5;
case 0x87:
return 0xE7;
224 case 0x88:
return 0xEA;
case 0x89:
return 0xEB;
225 case 0x8A:
return 0xE8;
case 0x8B:
return 0xEF;
226 case 0x8C:
return 0xEE;
case 0x8D:
return 0xEC;
227 case 0x8E:
return 0xC4;
case 0x8F:
return 0xC5;
228 case 0x90:
return 0xC9;
case 0x91:
return 0xE6;
229 case 0x92:
return 0xC6;
case 0x93:
return 0xF4;
230 case 0x94:
return 0xF6;
case 0x95:
return 0xF2;
231 case 0x96:
return 0xFB;
case 0x97:
return 0xF9;
232 case 0x98:
return 0xFF;
case 0x99:
return 0xD6;
233 case 0x9A:
return 0xDC;
case 0x9B:
return 0xA2;
234 case 0x9C:
return 0xA3;
case 0x9D:
return 0xA5;
235 case 0xA0:
return 0xE1;
case 0xA1:
return 0xED;
236 case 0xA2:
return 0xF3;
case 0xA3:
return 0xFA;
237 case 0xA4:
return 0xF1;
case 0xA5:
return 0xD1;
238 case 0xA6:
return 0xAA;
case 0xA7:
return 0xBA;
239 case 0xA8:
return 0xBF;
case 0xAA:
return 0xAC;
240 case 0xAB:
return 0xBD;
case 0xAC:
return 0xBC;
241 case 0xAD:
return 0xA1;
case 0xAE:
return 0xAB;
242 case 0xAF:
return 0xBB;
case 0xE1:
return 0xDF;
243 case 0xE6:
return 0xB5;
case 0xF1:
return 0xB1;
244 case 0xF6:
return 0xF7;
case 0xF8:
return 0xB0;
245 case 0xFD:
return 0xB2;
253struct NamedEntity {
const char*
name; uint32_t cp; };
255constexpr NamedEntity kNamedEntities[] = {
256 {
"amp",
'&'}, {
"lt",
'<'}, {
"gt",
'>'},
257 {
"quot",
'"'}, {
"apos",
'\''}, {
"nbsp", 0x00A0},
258 {
"auml", 0x00E4}, {
"Auml", 0x00C4},
259 {
"ouml", 0x00F6}, {
"Ouml", 0x00D6},
260 {
"uuml", 0x00FC}, {
"Uuml", 0x00DC},
261 {
"szlig", 0x00DF}, {
"ssharp", 0x00DF},
262 {
"aacute", 0x00E1}, {
"Aacute", 0x00C1},
263 {
"eacute", 0x00E9}, {
"Eacute", 0x00C9},
264 {
"iacute", 0x00ED}, {
"Iacute", 0x00CD},
265 {
"oacute", 0x00F3}, {
"Oacute", 0x00D3},
266 {
"uacute", 0x00FA}, {
"Uacute", 0x00DA},
267 {
"agrave", 0x00E0}, {
"Agrave", 0x00C0},
268 {
"egrave", 0x00E8}, {
"Egrave", 0x00C8},
269 {
"igrave", 0x00EC}, {
"Igrave", 0x00CC},
270 {
"ograve", 0x00F2}, {
"Ograve", 0x00D2},
271 {
"ugrave", 0x00F9}, {
"Ugrave", 0x00D9},
272 {
"acirc", 0x00E2}, {
"Acirc", 0x00C2},
273 {
"ecirc", 0x00EA}, {
"Ecirc", 0x00CA},
274 {
"icirc", 0x00EE}, {
"Icirc", 0x00CE},
275 {
"ocirc", 0x00F4}, {
"Ocirc", 0x00D4},
276 {
"ucirc", 0x00FB}, {
"Ucirc", 0x00DB},
277 {
"atilde", 0x00E3}, {
"Atilde", 0x00C3},
278 {
"ntilde", 0x00F1}, {
"Ntilde", 0x00D1},
279 {
"otilde", 0x00F5}, {
"Otilde", 0x00D5},
280 {
"ccedil", 0x00E7}, {
"Ccedil", 0x00C7},
281 {
"aring", 0x00E5}, {
"Aring", 0x00C5},
282 {
"aelig", 0x00E6}, {
"AElig", 0x00C6},
283 {
"oslash", 0x00F8}, {
"Oslash", 0x00D8},
284 {
"yuml", 0x00FF}, {
"Yuml", 0x0178},
285 {
"copy", 0x00A9}, {
"reg", 0x00AE}, {
"trade", 0x2122},
286 {
"deg", 0x00B0}, {
"plusmn", 0x00B1}, {
"para", 0x00B6},
287 {
"sect", 0x00A7}, {
"micro", 0x00B5}, {
"middot", 0x00B7},
288 {
"laquo", 0x00AB}, {
"raquo", 0x00BB},
289 {
"iexcl", 0x00A1}, {
"iquest", 0x00BF},
290 {
"cent", 0x00A2}, {
"pound", 0x00A3}, {
"yen", 0x00A5},
292 {
"hellip", 0x2026}, {
"mdash", 0x2014}, {
"ndash", 0x2013},
293 {
"lsquo", 0x2018}, {
"rsquo", 0x2019},
294 {
"ldquo", 0x201C}, {
"rdquo", 0x201D},
298void appendUtf8(
char*& w,
char* end, uint32_t cp) {
300 if (w < end) *w++ =
static_cast<char>(cp);
301 }
else if (cp < 0x800) {
303 *w++ =
static_cast<char>(0xC0 | (cp >> 6));
304 *w++ =
static_cast<char>(0x80 | (cp & 0x3F));
306 }
else if (cp < 0x10000) {
308 *w++ =
static_cast<char>(0xE0 | (cp >> 12));
309 *w++ =
static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
310 *w++ =
static_cast<char>(0x80 | (cp & 0x3F));
312 }
else if (w + 4 <= end) {
313 *w++ =
static_cast<char>(0xF0 | (cp >> 18));
314 *w++ =
static_cast<char>(0x80 | ((cp >> 12) & 0x3F));
315 *w++ =
static_cast<char>(0x80 | ((cp >> 6) & 0x3F));
316 *w++ =
static_cast<char>(0x80 | (cp & 0x3F));
320bool parseEntity(
const char* p, uint32_t* cp_out,
const char** after) {
321 if (*p !=
'&')
return false;
322 const char* q = p + 1;
326 bool hex = (*q ==
'x' || *q ==
'X');
328 const char* digits = q;
329 while (*q && *q !=
';') {
332 if (c >=
'0' && c <=
'9') d = c -
'0';
333 else if (hex && c >=
'a' && c <=
'f') d = c -
'a' + 10;
334 else if (hex && c >=
'A' && c <=
'F') d = c -
'A' + 10;
336 cp = cp * (hex ? 16u : 10u) +
static_cast<uint32_t
>(d);
339 if (*q !=
';' || q == digits)
return false;
344 for (
const auto& e : kNamedEntities) {
345 size_t nl = std::strlen(e.name);
346 if (std::strncmp(q, e.name, nl) == 0 && q[nl] ==
';') {
360 std::memcpy(buf, cp437.c_str(), cp437.size() + 1);
365void utf8ToLatin1Inplace(
char* buf) {
367 uint8_t* r =
reinterpret_cast<uint8_t*
>(buf);
373 if ((c & 0x80) == 0) { *w++ = c; ++r;
continue; }
374 else if ((c & 0xE0) == 0xC0) { cp = c & 0x1F; cont = 1; }
375 else if ((c & 0xF0) == 0xE0) { cp = c & 0x0F; cont = 2; }
376 else if ((c & 0xF8) == 0xF0) { cp = c & 0x07; cont = 3; }
377 else { ++r;
continue; }
380 for (uint8_t i = 0; i < cont; i++) {
381 if ((*r & 0xC0) != 0x80) { ok =
false;
break; }
382 cp = (cp << 6) | (*r & 0x3F);
387 *w++ =
static_cast<uint8_t
>(cp);
397 if (!in || !out || out_size == 0)
return;
399 char* end = out + out_size - 1;
401 while (*r && w < end) {
405 if (parseEntity(r, &cp, &after)) {
406 appendUtf8(w, end, cp);
415 utf8ToLatin1Inplace(out);
422 if (!gfx || !text)
return;
423 for (
const uint8_t* p =
reinterpret_cast<const uint8_t*
>(text); *p; ++p) {
428void drawText(Gdey029T94* gfx,
const char* text,
const GFXfont* font) {
429 if (!gfx || !text)
return;
443 if (!gfx || !text)
return;
444 gfx->setFont(
nullptr);
448void measureText(Gdey029T94* gfx,
const char* text,
const GFXfont* font,
449 int16_t x0, int16_t y0, int16_t* x1, int16_t* y1,
450 uint16_t* w, uint16_t* h) {
460 gfx->getTextBounds(text, x0, y0, x1, y1, w, h);
469 const GFXfont*
const* candidates,
472 if (count == 0)
return nullptr;
473 if (!gfx || !text || !candidates || maxWidthPx <= 0) {
474 return candidates[count - 1];
477 const GFXfont* selected = candidates[count - 1];
478 for (
size_t i = 0; i < count; ++i) {
479 const GFXfont* f = candidates[i];
483 uint16_t w = 0, h = 0;
487 gfx->getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
489 if (
static_cast<int>(w) <= maxWidthPx) {
494 gfx->setFont(selected);
500 int16_t* x1, int16_t* y1, uint16_t* w, uint16_t* h) {
510 for (
const uint8_t* p =
reinterpret_cast<const uint8_t*
>(text); *p && i + 1 <
sizeof(buf); ++p) {
514 gfx->getTextBounds(buf, x0, y0, x1, y1, w, h);
Canonical CP437 <-> Unicode/UTF-8 codec.
char name[cdc::hal::ISecureElement::RMEM_NAME_LEN]
std::string fromUtf8(const char *s)
Convert a UTF-8 string to CP437 bytes (unmapped chars dropped).
void drawFooterBar(Gdey029T94 *gfx, uint16_t width, uint16_t height, const char *prefix, const char *hint, bool force=false)
Draws footer bar with optional prefix and hint text.
void drawText(Gdey029T94 *gfx, const char *text, const GFXfont *font)
Draws CP437-encoded text correctly for the given font: the built-in glcdfont (font == nullptr) is CP4...
void drawDialogFrame(Gdey029T94 *gfx, int x, int y, int w, int h)
Draws a framed dialog box with double border.
void drawHeaderCentered(Gdey029T94 *gfx, const char *title, int y, uint16_t width)
Draws a centered header title.
void utf8ToCp437Inplace(char *buf)
Decodes a UTF-8 string in place to CP437 single bytes. Truncates if the buffer is too small....
void drawHeaderLeft(Gdey029T94 *gfx, const char *title, int x, int y, uint16_t width, int underlineOffset=18)
void measureText(Gdey029T94 *gfx, const char *text, const GFXfont *font, int16_t x0, int16_t y0, int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h)
Measures CP437 text exactly as drawText would render it with font, so width-based layout (centering,...
uint8_t cp437ToLatin1(uint8_t c)
Maps a CP437 byte to the equivalent Latin-1 byte for use with Unicode/Latin-1 indexed GFX fonts (e....
void decodeWebText(const char *in, char *out, size_t out_size, DisplayTarget target=DisplayTarget::Cp437)
Normalises a web payload for display: HTML named/numeric entities decode first, then UTF-8 multibyte ...
constexpr int SCROLL_INDICATOR_WIDTH
const GFXfont * pickFontThatFits(Gdey029T94 *gfx, const char *text, int maxWidthPx, const GFXfont *const *candidates, size_t count, bool cp437=false)
Picks the largest font from candidates whose rendered width of text fits within maxWidthPx....
void measureCp437Text(Gdey029T94 *gfx, const char *text, int16_t x0, int16_t y0, int16_t *x1, int16_t *y1, uint16_t *w, uint16_t *h)
Measures a CP437 string using the current font (via Latin-1 mapping).
void printTruncated(Gdey029T94 *gfx, const char *text, int maxWidthPx)
Print text at the current cursor, truncated with an ellipsis to fit maxWidthPx. Caller must have alre...
void printText(Gdey029T94 *gfx, const char *text)
Draws CP437 text with the built-in 6x8 glyph font, byte-for-byte.
DisplayTarget
Display encoding targets for decodeWebText().
@ Latin1
FreeMonoBold*pt8b fonts (Latin-1 indexed, 0x20..0xFF).
constexpr int FOOTER_HEIGHT
void drawCp437Text(Gdey029T94 *gfx, const char *text)
Prints a CP437 string by mapping each byte to Latin-1 before drawing. Use with TTF-derived GFX fonts ...
void drawScrollIndicator(Gdey029T94 *gfx, int x, int y, int listHeight, uint16_t totalItems, uint16_t visibleItems, uint16_t scrollPos)
Draws scroll arrows and scrollbar thumb.