CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
RenderHelpers.cpp
Go to the documentation of this file.
1
6
8#include "cdc_core/Cp437.h"
9#include <goodisplay/gdey029T94.h>
10#include <algorithm>
11#include <cstring>
12
13namespace cdc::ui::render {
14
15namespace {
23void writeRaw(Gdey029T94* gfx, const char* text) {
24 for (const uint8_t* p = reinterpret_cast<const uint8_t*>(text); *p; ++p) {
25 gfx->write(*p);
26 }
27}
28} // namespace
29
40void printTruncated(Gdey029T94* gfx, const char* text, int maxWidthPx) {
41 if (!gfx || !text || maxWidthPx <= 0) return;
42
43 int16_t x1, y1;
44 uint16_t w, h;
45 gfx->getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
46 if (static_cast<int>(w) <= maxWidthPx) {
47 writeRaw(gfx, text);
48 return;
49 }
50
51 constexpr char ELLIPSIS[] = "...";
52 uint16_t ew, eh;
53 int16_t ex1, ey1;
54 gfx->getTextBounds(ELLIPSIS, 0, 0, &ex1, &ey1, &ew, &eh);
55
56 const int budget = maxWidthPx - static_cast<int>(ew);
57 if (budget <= 0) {
58 writeRaw(gfx, ELLIPSIS);
59 return;
60 }
61
62 char buf[128];
63 size_t len = std::strlen(text);
64 if (len >= sizeof(buf)) len = sizeof(buf) - 1;
65 std::memcpy(buf, text, len);
66 buf[len] = '\0';
67
68 while (len > 0) {
69 buf[len] = '\0';
70 gfx->getTextBounds(buf, 0, 0, &x1, &y1, &w, &h);
71 if (static_cast<int>(w) <= budget) break;
72 --len;
73 }
74
75 writeRaw(gfx, buf);
76 writeRaw(gfx, ELLIPSIS);
77}
78
79void drawHeaderLeft(Gdey029T94* gfx, const char* title, int x, int y,
80 uint16_t width, int underlineOffset) {
81 if (!gfx) return;
82
83 if (title && title[0] != '\0') {
84 gfx->setCursor(x, y);
85 writeRaw(gfx, title);
86 }
87 gfx->drawFastHLine(0, y + underlineOffset, width, EPD_BLACK);
88}
89
98void drawHeaderCentered(Gdey029T94* gfx, const char* title, int y, uint16_t width) {
99 if (!gfx || !title || title[0] == '\0') return;
100
101 int16_t x1, y1;
102 uint16_t w, h;
103 gfx->getTextBounds(title, 0, 0, &x1, &y1, &w, &h);
104 gfx->setCursor((width - w) / 2, y);
105 writeRaw(gfx, title);
106}
107
118void drawFooterBar(Gdey029T94* gfx, uint16_t width, uint16_t height,
119 const char* prefix, const char* hint, bool force) {
120 if (!gfx) return;
121 if (!force && !prefix && !hint) return;
122
123 gfx->fillRect(0, height - FOOTER_HEIGHT, width, FOOTER_HEIGHT, EPD_BLACK);
124 gfx->setFont(nullptr); // built-in 6x8 glcdfont; never inherit a caller's GFX font
125 gfx->setTextSize(1);
126 gfx->setTextColor(EPD_WHITE);
127 gfx->setCursor(4, height - 12);
128
129 if (prefix) {
130 writeRaw(gfx, prefix);
131 }
132 if (hint) {
133 writeRaw(gfx, hint);
134 }
135}
136
148void drawScrollIndicator(Gdey029T94* gfx, int x, int y, int listHeight,
149 uint16_t totalItems, uint16_t visibleItems,
150 uint16_t scrollPos) {
151 if (!gfx) return;
152 if (totalItems <= visibleItems) return;
153
154 gfx->fillRect(x, y, SCROLL_INDICATOR_WIDTH, listHeight, EPD_WHITE);
155
156 const int midX = x + (SCROLL_INDICATOR_WIDTH / 2);
157 const int leftX = x + 1;
158 const int rightX = x + SCROLL_INDICATOR_WIDTH - 1;
159
160 if (scrollPos > 0) {
161 const int topY = y + 4;
162 gfx->fillTriangle(
163 midX, topY,
164 leftX, topY + 6,
165 rightX, topY + 6,
166 EPD_BLACK
167 );
168 }
169
170 if (scrollPos + visibleItems < totalItems) {
171 const int arrowY = y + listHeight - 12;
172 gfx->fillTriangle(
173 midX, arrowY + 8,
174 leftX, arrowY + 2,
175 rightX, arrowY + 2,
176 EPD_BLACK
177 );
178 }
179
180 const int barHeight = listHeight - 24;
181 if (barHeight <= 0) return;
182
183 const int barX = x + (SCROLL_INDICATOR_WIDTH / 2) - 2;
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
190 : 0;
191
192 gfx->drawRect(barX, barY, 4, barHeight, EPD_BLACK);
193 gfx->fillRect(barX, barY + thumbPos, 4, thumbHeight, EPD_BLACK);
194}
195
205void drawDialogFrame(Gdey029T94* gfx, int x, int y, int w, int h) {
206 if (!gfx) return;
207
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);
211}
212
218uint8_t cp437ToLatin1(uint8_t c) {
219 switch (c) {
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;
246 default: return c;
247 }
248}
249
250
251namespace {
252
253struct NamedEntity { const char* name; uint32_t cp; };
254
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},
291 {"euro", 0x20AC},
292 {"hellip", 0x2026}, {"mdash", 0x2014}, {"ndash", 0x2013},
293 {"lsquo", 0x2018}, {"rsquo", 0x2019},
294 {"ldquo", 0x201C}, {"rdquo", 0x201D},
295 {"bull", 0x2022},
296};
297
298void appendUtf8(char*& w, char* end, uint32_t cp) {
299 if (cp < 0x80) {
300 if (w < end) *w++ = static_cast<char>(cp);
301 } else if (cp < 0x800) {
302 if (w + 2 <= end) {
303 *w++ = static_cast<char>(0xC0 | (cp >> 6));
304 *w++ = static_cast<char>(0x80 | (cp & 0x3F));
305 }
306 } else if (cp < 0x10000) {
307 if (w + 3 <= end) {
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));
311 }
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));
317 }
318}
319
320bool parseEntity(const char* p, uint32_t* cp_out, const char** after) {
321 if (*p != '&') return false;
322 const char* q = p + 1;
323 uint32_t cp = 0;
324 if (*q == '#') {
325 ++q;
326 bool hex = (*q == 'x' || *q == 'X');
327 if (hex) ++q;
328 const char* digits = q;
329 while (*q && *q != ';') {
330 int d;
331 char c = *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;
335 else return false;
336 cp = cp * (hex ? 16u : 10u) + static_cast<uint32_t>(d);
337 ++q;
338 }
339 if (*q != ';' || q == digits) return false;
340 *cp_out = cp;
341 *after = q + 1;
342 return true;
343 }
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] == ';') {
347 *cp_out = e.cp;
348 *after = q + nl + 1;
349 return true;
350 }
351 }
352 return false;
353}
354
355} // namespace
356
357void utf8ToCp437Inplace(char* buf) {
358 if (!buf) return;
359 std::string cp437 = cdc::core::cp437::fromUtf8(buf);
360 std::memcpy(buf, cp437.c_str(), cp437.size() + 1);
361}
362
363namespace {
364
365void utf8ToLatin1Inplace(char* buf) {
366 if (!buf) return;
367 uint8_t* r = reinterpret_cast<uint8_t*>(buf);
368 uint8_t* w = r;
369 while (*r) {
370 uint8_t c = *r;
371 uint32_t cp = 0;
372 uint8_t cont = 0;
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; }
378 ++r;
379 bool ok = true;
380 for (uint8_t i = 0; i < cont; i++) {
381 if ((*r & 0xC0) != 0x80) { ok = false; break; }
382 cp = (cp << 6) | (*r & 0x3F);
383 ++r;
384 }
385 if (!ok) continue;
386 if (cp < 0x100) {
387 *w++ = static_cast<uint8_t>(cp);
388 }
389 }
390 *w = '\0';
391}
392
393} // namespace
394
395void decodeWebText(const char* in, char* out, size_t out_size,
396 DisplayTarget target) {
397 if (!in || !out || out_size == 0) return;
398 char* w = out;
399 char* end = out + out_size - 1;
400 const char* r = in;
401 while (*r && w < end) {
402 if (*r == '&') {
403 uint32_t cp;
404 const char* after;
405 if (parseEntity(r, &cp, &after)) {
406 appendUtf8(w, end, cp);
407 r = after;
408 continue;
409 }
410 }
411 *w++ = *r++;
412 }
413 *w = '\0';
414 if (target == DisplayTarget::Latin1) {
415 utf8ToLatin1Inplace(out);
416 } else {
418 }
419}
420
421void drawCp437Text(Gdey029T94* gfx, const char* text) {
422 if (!gfx || !text) return;
423 for (const uint8_t* p = reinterpret_cast<const uint8_t*>(text); *p; ++p) {
424 gfx->write(cp437ToLatin1(*p));
425 }
426}
427
428void drawText(Gdey029T94* gfx, const char* text, const GFXfont* font) {
429 if (!gfx || !text) return;
430 // Make the active font match the encoding chosen below: the built-in
431 // glcdfont (font == nullptr) is CP437-indexed and gets raw bytes; Latin-1
432 // GFX fonts get CP437->Latin1 mapping. Setting it here prevents a stale
433 // font from mismatching the bytes we emit.
434 gfx->setFont(font);
435 if (!font) {
436 writeRaw(gfx, text); // built-in glcdfont: raw CP437 bytes
437 } else {
438 drawCp437Text(gfx, text); // Latin-1 GFX font: CP437->Latin1 per byte
439 }
440}
441
442void printText(Gdey029T94* gfx, const char* text) {
443 if (!gfx || !text) return;
444 gfx->setFont(nullptr); // built-in glcdfont (CP437-indexed)
445 writeRaw(gfx, text);
446}
447
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) {
451 if (!gfx || !text) {
452 if (x1) *x1 = x0;
453 if (y1) *y1 = y0;
454 if (w) *w = 0;
455 if (h) *h = 0;
456 return;
457 }
458 gfx->setFont(font);
459 if (!font) {
460 gfx->getTextBounds(text, x0, y0, x1, y1, w, h);
461 } else {
462 measureCp437Text(gfx, text, x0, y0, x1, y1, w, h);
463 }
464}
465
466const GFXfont* pickFontThatFits(Gdey029T94* gfx,
467 const char* text,
468 int maxWidthPx,
469 const GFXfont* const* candidates,
470 size_t count,
471 bool cp437) {
472 if (count == 0) return nullptr;
473 if (!gfx || !text || !candidates || maxWidthPx <= 0) {
474 return candidates[count - 1];
475 }
476
477 const GFXfont* selected = candidates[count - 1];
478 for (size_t i = 0; i < count; ++i) {
479 const GFXfont* f = candidates[i];
480 gfx->setFont(f);
481 gfx->setTextSize(1);
482 int16_t x1, y1;
483 uint16_t w = 0, h = 0;
484 if (cp437) {
485 measureCp437Text(gfx, text, 0, 0, &x1, &y1, &w, &h);
486 } else {
487 gfx->getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
488 }
489 if (static_cast<int>(w) <= maxWidthPx) {
490 selected = f;
491 break;
492 }
493 }
494 gfx->setFont(selected);
495 gfx->setTextSize(1);
496 return selected;
497}
498
499void measureCp437Text(Gdey029T94* gfx, const char* text, int16_t x0, int16_t y0,
500 int16_t* x1, int16_t* y1, uint16_t* w, uint16_t* h) {
501 if (!gfx || !text) {
502 if (x1) *x1 = x0;
503 if (y1) *y1 = y0;
504 if (w) *w = 0;
505 if (h) *h = 0;
506 return;
507 }
508 char buf[128];
509 size_t i = 0;
510 for (const uint8_t* p = reinterpret_cast<const uint8_t*>(text); *p && i + 1 < sizeof(buf); ++p) {
511 buf[i++] = static_cast<char>(cp437ToLatin1(*p));
512 }
513 buf[i] = '\0';
514 gfx->getTextBounds(buf, x0, y0, x1, y1, w, h);
515}
516
517} // namespace cdc::ui::render
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).
Definition Cp437.cpp:58
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.