CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
QRCodeView.cpp
Go to the documentation of this file.
1
7
9#include "cdc_ui/ViewStack.h"
10#include "cdc_ui/I18n.h"
11#include "cdc_hal/IDisplay.h"
12#include "cdc_log.h"
13#include "qrcode.h"
14#include "cdc_views/Fonts.h"
16#include <goodisplay/gdey029T94.h>
17#include <Fonts/FreeMonoBold9pt7b.h>
18#include <cstring>
19
20static const char* TAG = "QRCodeView";
21
25static constexpr int DISPLAY_WIDTH = 296;
26static constexpr int DISPLAY_HEIGHT = 128;
27static constexpr int QR_MARGIN = 0; // No margin - maximize QR size
28
29namespace cdc::ui {
30
34static struct {
37 int scale;
38 int actualSize; // Filled during sizing pass
39 bool sizingPass; // True = just measure, don't draw
40 Gdey029T94* display;
42
48static void qrDisplayCallback(esp_qrcode_handle_t qrcode) {
49 int size = esp_qrcode_get_size(qrcode);
50 s_qrCtx.actualSize = size;
51
52 // If sizing pass, just record size and return
53 if (s_qrCtx.sizingPass) {
54 return;
55 }
56
57 if (!s_qrCtx.display) return;
58
59 int scale = s_qrCtx.scale;
60 int x0 = s_qrCtx.offsetX;
61 int y0 = s_qrCtx.offsetY;
62
63 LOG_D(TAG, "QR size=%d, scale=%d, pos=(%d,%d)", size, scale, x0, y0);
64
65 for (int y = 0; y < size; y++) {
66 for (int x = 0; x < size; x++) {
67 bool black = esp_qrcode_get_module(qrcode, x, y);
68 uint16_t color = black ? EPD_BLACK : EPD_WHITE;
69
70 // Draw scaled pixel
71 for (int dy = 0; dy < scale; dy++) {
72 for (int dx = 0; dx < scale; dx++) {
73 s_qrCtx.display->drawPixel(x0 + x * scale + dx, y0 + y * scale + dy, color);
74 }
75 }
76 }
77 }
78}
79
87void QRCodeView::init(const char* data, const char* title, const char* subtitle) {
88 data_ = data;
89 title_ = title;
90 subtitle_ = subtitle;
91 customHint_ = nullptr;
92 qrModuleCount_ = 0;
93 qrScale_ = 1;
94 qrOffsetX_ = 0;
95 qrOffsetY_ = 0;
96 dirty_ = true;
97
98 LOG_D(TAG, "init: data='%s', title='%s'",
99 data ? data : "(null)", title ? title : "(null)");
100}
101
108 if (key == 'N' || key == 'n') {
110 }
112}
113
118const char* QRCodeView::getFooterHint() const {
119 if (customHint_) {
120 return customHint_;
121 }
122 return ui::tr("core.hint_back");
123}
124
129void QRCodeView::calculateLayout() {
130 if (!data_) return;
131
132 // First pass: determine actual QR size (without drawing)
133 esp_qrcode_config_t cfg = {
134 .display_func = qrDisplayCallback,
135 .max_qrcode_version = 20, // Max 97 modules
136 .qrcode_ecc_level = ESP_QRCODE_ECC_LOW,
137 .user_data = nullptr
138 };
139
140 s_qrCtx.sizingPass = true;
141 s_qrCtx.actualSize = 0;
142 s_qrCtx.display = nullptr;
143
144 esp_err_t err = esp_qrcode_generate(&cfg, data_);
145 if (err != ESP_OK) {
146 LOG_E(TAG, "QR sizing failed: %s", esp_err_to_name(err));
147 qrModuleCount_ = 97; // Fallback
148 } else {
149 qrModuleCount_ = s_qrCtx.actualSize;
150 if (qrModuleCount_ <= 0) qrModuleCount_ = 97;
151 }
152
153 // QR code uses full display height
154 int maxQrHeight = DISPLAY_HEIGHT;
155
156 // Calculate optimal scale to fill display height
157 qrScale_ = maxQrHeight / qrModuleCount_;
158 if (qrScale_ < 1) qrScale_ = 1;
159
160 // Calculate actual QR pixel size
161 int qrPixelSize = qrModuleCount_ * qrScale_;
162
163 // Center vertically in available space
164 int unusedHeight = maxQrHeight - qrPixelSize;
165 qrOffsetX_ = QR_MARGIN;
166 qrOffsetY_ = unusedHeight / 2;
167
168 LOG_I(TAG, "Layout: %d modules, scale=%d, %dx%d px",
169 qrModuleCount_, qrScale_, qrPixelSize, qrPixelSize);
170}
171
176void QRCodeView::renderQrCode() {
177 if (!data_) return;
178
179 hal::IDisplay* display = hal::getDisplayInstance();
180 if (!display) return;
181
182 auto* gfx = static_cast<Gdey029T94*>(display->getNativeHandle());
183 if (!gfx) return;
184
185 // Set up context for callback
186 s_qrCtx.sizingPass = false;
187 s_qrCtx.offsetX = qrOffsetX_;
188 s_qrCtx.offsetY = qrOffsetY_;
189 s_qrCtx.scale = qrScale_;
190 s_qrCtx.display = gfx;
191
192 esp_qrcode_config_t cfg = {
193 .display_func = qrDisplayCallback,
194 .max_qrcode_version = 20,
195 .qrcode_ecc_level = ESP_QRCODE_ECC_LOW,
196 .user_data = nullptr
197 };
198
199 esp_err_t err = esp_qrcode_generate(&cfg, data_);
200 if (err != ESP_OK) {
201 LOG_E(TAG, "QR render failed: %s", esp_err_to_name(err));
202 gfx->setFont(nullptr);
203 gfx->setCursor(10, 64);
204 cdc::ui::render::printText(gfx, ui::tr("core.qr_error"));
205 }
206}
207
212void QRCodeView::renderText() {
213 hal::IDisplay* display = hal::getDisplayInstance();
214 if (!display) return;
215
216 auto* gfx = static_cast<Gdey029T94*>(display->getNativeHandle());
217 if (!gfx) return;
218
219 // Reserve a fixed square region of DISPLAY_HEIGHT px on the left for the QR.
220 // Text always starts at a fixed offset so the layout is stable regardless
221 // of the actual rendered QR pixel size (which varies with module count and scale).
222 constexpr int QR_AREA_WIDTH = DISPLAY_HEIGHT;
223 int textAreaX = QR_AREA_WIDTH + 4;
224 int textAreaWidth = DISPLAY_WIDTH - textAreaX - 2;
225
226 // Draw title on right side (top). Picks the largest preset font in which
227 // the full title fits; falls back to the 5x7 built-in for long titles,
228 // which still wraps in up to two lines.
229 int y = 14;
230 if (title_ && title_[0]) {
231 using cdc::ui::FontId;
232 static const GFXfont* const TITLE_FONTS[] = {
233 &FreeMonoBold9pt7b,
235 };
236 const GFXfont* chosen = cdc::ui::render::pickFontThatFits(
237 gfx, title_, textAreaWidth, TITLE_FONTS, std::size(TITLE_FONTS), false);
238
239 const bool bold9pt = (chosen == &FreeMonoBold9pt7b);
240 const int charW = bold9pt ? 7 : 6;
241 const int lineH = bold9pt ? 16 : 10;
242 const int maxYStop = 80;
243 int maxChars = textAreaWidth / charW;
244 if (maxChars > 31) maxChars = 31;
245
246 const char* p = title_;
247 char line[32];
248 while (*p && y < maxYStop) {
249 int len = 0;
250 while (p[len] && len < maxChars) {
251 line[len] = p[len];
252 len++;
253 }
254 line[len] = '\0';
255
256 gfx->setCursor(textAreaX, y);
257 cdc::ui::render::drawText(gfx, line, chosen);
258
259 p += len;
260 y += lineH;
261 }
262 }
263
264 // Subtitle below title; supports multi-line via '\n' separators.
265 if (subtitle_ && subtitle_[0]) {
266 gfx->setFont(nullptr);
267 int maxChars = textAreaWidth / 6; // Approx char width for default font
268 if (maxChars > 31) maxChars = 31;
269
270 const char* p = subtitle_;
271 char line[32];
272 y += 4;
273 while (*p && y < 110) {
274 int len = 0;
275 while (p[len] && p[len] != '\n' && len < maxChars) {
276 line[len] = p[len];
277 len++;
278 }
279 line[len] = '\0';
280
281 gfx->setCursor(textAreaX, y);
283 y += 10;
284
285 p += len;
286 if (*p == '\n') p++;
287 }
288 }
289
290 // Draw hint at bottom right
291 gfx->setFont(nullptr);
292 const char* hint = getFooterHint();
293 if (hint) {
294 gfx->setCursor(textAreaX, 116);
296 }
297}
298
304void QRCodeView::render(bool partial) {
306 if (!display) return;
307
308 auto* gfx = static_cast<Gdey029T94*>(display->getNativeHandle());
309 if (!gfx) return;
310
311 if (!partial) {
312 gfx->fillScreen(EPD_WHITE);
313 }
314 gfx->setTextColor(EPD_BLACK);
315
316 if (!data_) {
317 gfx->setFont(nullptr);
318 gfx->setCursor(10, 64);
319 cdc::ui::render::printText(gfx, ui::tr("core.no_data"));
320 dirty_ = false;
321 return;
322 }
323
324 // Calculate layout on first render
325 if (qrModuleCount_ == 0) {
326 calculateLayout();
327 }
328
329 // Render QR code
330 renderQrCode();
331
332 // Render text
333 renderText();
334
335 dirty_ = false;
336}
337
341
343
352QRCodeView* showQRCode(const char* data, const char* title,
353 const char* subtitle, const char* hint) {
354 s_sharedQRCodeView.init(data, title, subtitle);
355 if (hint) {
356 s_sharedQRCodeView.setHint(hint);
357 }
359 return &s_sharedQRCodeView;
360}
361
362} // namespace cdc::ui
static const char * TAG
Internationalization with English fallbacks in code and overlay translations loaded at runtime from a...
static constexpr int DISPLAY_WIDTH
static constexpr int QR_MARGIN
static constexpr int DISPLAY_HEIGHT
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_D(tag, fmt,...)
Definition cdc_log.h:148
#define LOG_I(tag, fmt,...)
Definition cdc_log.h:147
#define LOG_E(tag, fmt,...)
Definition cdc_log.h:145
void render(bool partial) override
Renders the complete QR code view.
const char * getFooterHint() const override
Returns footer hint text.
InputResult onKey(char key) override
Handles key input by closing the QR view.
void init(const char *data, const char *title=nullptr, const char *subtitle=nullptr)
Initializes QR code content and layout state.
static ViewStack & instance()
Returns singleton view-stack instance.
Definition ViewStack.cpp:34
void push(IView *view, void *context=nullptr)
IDisplay * getDisplayInstance()
Returns lazily created singleton display instance.
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...
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 printText(Gdey029T94 *gfx, const char *text)
Draws CP437 text with the built-in 6x8 glyph font, byte-for-byte.
Centralized key-code constants for cdc_views.
Definition IModule.h:8
const char * tr(const char *key)
Look up a translation by string key.
Definition I18n.h:208
const GFXfont * getGfxFont(FontId id)
Resolves a FontId to its underlying GFX font pointer.
Definition Fonts.cpp:26
bool sizingPass
int offsetX
Gdey029T94 * display
int offsetY
static QRCodeView s_sharedQRCodeView
Convenience factory/helper function.
int actualSize
static struct cdc::ui::@307147344135044151327247213066152362355352344260 s_qrCtx
QR rendering context used by callback-driven rendering.
FontId
Canonical font identifier shared by firmware UI and plugin host API.
Definition Fonts.h:15
@ Builtin
Adafruit-GFX 6x8. Umlauts present at CP437 codepoints (0x84..0x9C). Pass raw CP437 bytes to draw them...
Definition Fonts.h:16
InputResult
Definition IView.h:10
static void qrDisplayCallback(esp_qrcode_handle_t qrcode)
Renders or measures the QR code through the ESP QR callback.
QRCodeView * showQRCode(const char *data, const char *title=nullptr, const char *subtitle=nullptr, const char *hint=nullptr)
Shows a shared QR code view instance.
static const char * TAG