CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
host_api_surface.cpp
Go to the documentation of this file.
1
11
14#include "cdc_image/JpegEncoder.h"
16#include "cdc_views/Fonts.h"
19#include "host_str_conv.h"
20
21#include <cstring>
22#include <memory>
23#include <string>
24
25namespace pm = cdc::plugin_manager;
26namespace render = cdc::ui::render;
27
28extern "C" void* plg_get_active_plugin(void);
29
30namespace {
31
32constexpr uint16_t SURFACE_MAX_W = 1024;
33constexpr uint16_t SURFACE_MAX_H = 2048;
34constexpr uint16_t INK = 1;
35
36struct SurfaceSlot {
37 std::unique_ptr<cdc::ui::MonoSurface> surf;
38 void* plugin = nullptr;
39 uint32_t handle = 0;
40 uint8_t font_id = 0;
41 uint8_t text_size = 1;
42 uint8_t shade = 255;
43 bool inverted = false;
44};
45
46// Global pool: at most HOST_SURFACE_MAX_PER_PLUGIN per plugin, and only a
47// handful of plugins are resident at once.
48constexpr uint8_t MAX_SLOTS = 8;
49SurfaceSlot s_slots[MAX_SLOTS];
50uint32_t s_next_handle = 1;
51
52SurfaceSlot* find_slot(uint32_t handle) {
53 if (handle == 0) return nullptr;
54 void* plugin = plg_get_active_plugin();
55 for (auto& s : s_slots) {
56 if (s.surf && s.handle == handle && s.plugin == plugin) return &s;
57 }
58 return nullptr;
59}
60
61// Applies the slot's font/size/color state before text drawing or measuring.
62const GFXfont* apply_text_state(SurfaceSlot& s) {
63 const GFXfont* font = cdc::ui::getGfxFont(s.font_id);
64 s.surf->setFont(font);
65 s.surf->setTextSize(s.text_size);
66 s.surf->setTextColor(s.inverted ? 0 : INK);
67 s.surf->setTextWrap(false);
68 return font;
69}
70
71} // namespace
72
73extern "C" {
74
75int host_surface_create(uint16_t w, uint16_t h) {
76 if (w == 0 || h == 0 || w > SURFACE_MAX_W || h > SURFACE_MAX_H) {
78 }
79 const size_t bytes = static_cast<size_t>((w + 7) / 8) * h;
81
82 void* plugin = plg_get_active_plugin();
83 if (!plugin) return HOST_ERR_GENERIC;
84
85 uint8_t owned = 0;
86 for (const auto& s : s_slots) {
87 if (s.surf && s.plugin == plugin) owned++;
88 }
90
91 for (auto& s : s_slots) {
92 if (s.surf) continue;
93 auto surf = std::make_unique<cdc::ui::MonoSurface>(w, h);
94 if (!surf->ok()) return HOST_ERR_NO_MEMORY;
95 s.surf = std::move(surf);
96 s.plugin = plugin;
97 s.handle = s_next_handle++;
98 s.font_id = 0;
99 s.text_size = 1;
100 s.shade = 255;
101 s.inverted = false;
102 return static_cast<int>(s.handle);
103 }
104 return HOST_ERR_NO_MEMORY;
105}
106
107int host_surface_destroy(uint32_t surface) {
108 SurfaceSlot* s = find_slot(surface);
109 if (!s) return HOST_ERR_NOT_FOUND;
110 *s = SurfaceSlot{};
111 return HOST_OK;
112}
113
114int host_surface_clear(uint32_t surface) {
115 SurfaceSlot* s = find_slot(surface);
116 if (!s) return HOST_ERR_NOT_FOUND;
117 s->surf->clear();
118 return HOST_OK;
119}
120
121int host_surface_set_font(uint32_t surface, uint8_t font_id) {
122 SurfaceSlot* s = find_slot(surface);
123 if (!s) return HOST_ERR_NOT_FOUND;
124 if (font_id >= cdc::ui::kFontIdCount) return HOST_ERR_INVALID_ARG;
125 s->font_id = font_id;
126 return HOST_OK;
127}
128
129int host_surface_set_text_size(uint32_t surface, uint8_t size) {
130 SurfaceSlot* s = find_slot(surface);
131 if (!s) return HOST_ERR_NOT_FOUND;
132 if (size < 1 || size > 4) return HOST_ERR_INVALID_ARG;
133 s->text_size = size;
134 return HOST_OK;
135}
136
137int host_surface_set_text_color(uint32_t surface, uint8_t inverted) {
138 SurfaceSlot* s = find_slot(surface);
139 if (!s) return HOST_ERR_NOT_FOUND;
140 s->inverted = inverted != 0;
141 return HOST_OK;
142}
143
144int host_surface_set_shade(uint32_t surface, uint8_t shade) {
145 SurfaceSlot* s = find_slot(surface);
146 if (!s) return HOST_ERR_NOT_FOUND;
147 s->shade = shade;
148 return HOST_OK;
149}
150
151int host_surface_draw_text(uint32_t surface, int16_t x, int16_t y, const char* text) {
152 SurfaceSlot* s = find_slot(surface);
153 if (!s) return HOST_ERR_NOT_FOUND;
154 if (!text) return HOST_ERR_INVALID_ARG;
155 const GFXfont* font = apply_text_state(*s);
156 std::string cp = pm::toDisplay(text);
157 s->surf->setCursor(x, y);
158 render::drawText(s->surf.get(), cp.c_str(), font);
159 return HOST_OK;
160}
161
162int host_surface_draw_text_aligned(uint32_t surface, int16_t x, int16_t y, int16_t w,
163 const char* text, uint8_t align) {
164 SurfaceSlot* s = find_slot(surface);
165 if (!s) return HOST_ERR_NOT_FOUND;
166 if (!text || align > 2) return HOST_ERR_INVALID_ARG;
167 const GFXfont* font = apply_text_state(*s);
168 std::string cp = pm::toDisplay(text);
169
170 int16_t draw_x = x;
171 if (align == 1 || align == 2) {
172 int16_t bx, by;
173 uint16_t bw, bh;
174 render::measureText(s->surf.get(), cp.c_str(), font, 0, 0, &bx, &by, &bw, &bh);
175 if (align == 1) draw_x = x + (w - static_cast<int16_t>(bw)) / 2;
176 else draw_x = x + w - static_cast<int16_t>(bw);
177 }
178 s->surf->setCursor(draw_x, y);
179 render::drawText(s->surf.get(), cp.c_str(), font);
180 return HOST_OK;
181}
182
183int host_surface_measure_text(uint32_t surface, const char* text,
184 uint16_t* out_w, uint16_t* out_h) {
185 SurfaceSlot* s = find_slot(surface);
186 if (!s) return HOST_ERR_NOT_FOUND;
187 if (!text || !out_w || !out_h) return HOST_ERR_INVALID_ARG;
188 const GFXfont* font = apply_text_state(*s);
189 std::string cp = pm::toDisplay(text);
190 int16_t bx, by;
191 render::measureText(s->surf.get(), cp.c_str(), font, 0, 0, &bx, &by, out_w, out_h);
192 return HOST_OK;
193}
194
195int host_surface_draw_pixel(uint32_t surface, int16_t x, int16_t y) {
196 SurfaceSlot* s = find_slot(surface);
197 if (!s) return HOST_ERR_NOT_FOUND;
198 s->surf->drawPixel(x, y, INK);
199 return HOST_OK;
200}
201
202int host_surface_draw_line(uint32_t surface, int16_t x0, int16_t y0, int16_t x1, int16_t y1) {
203 SurfaceSlot* s = find_slot(surface);
204 if (!s) return HOST_ERR_NOT_FOUND;
205 s->surf->drawLine(x0, y0, x1, y1, INK);
206 return HOST_OK;
207}
208
209int host_surface_draw_rect(uint32_t surface, int16_t x, int16_t y, int16_t w, int16_t h,
210 uint8_t filled) {
211 SurfaceSlot* s = find_slot(surface);
212 if (!s) return HOST_ERR_NOT_FOUND;
213 if (!filled) {
214 s->surf->drawRect(x, y, w, h, INK);
215 } else if (s->shade >= 255) {
216 s->surf->fillRect(x, y, w, h, INK);
217 } else if (s->shade > 0) {
218 render::fillRectDither(s->surf.get(), x, y, w, h, s->shade, INK);
219 }
220 return HOST_OK;
221}
222
223int host_surface_draw_circle(uint32_t surface, int16_t x, int16_t y, int16_t r,
224 uint8_t filled) {
225 SurfaceSlot* s = find_slot(surface);
226 if (!s) return HOST_ERR_NOT_FOUND;
227 if (!filled) {
228 s->surf->drawCircle(x, y, r, INK);
229 } else if (s->shade >= 255) {
230 s->surf->fillCircle(x, y, r, INK);
231 } else if (s->shade > 0) {
232 render::fillCircleDither(s->surf.get(), x, y, r, s->shade, INK);
233 }
234 return HOST_OK;
235}
236
237int host_surface_draw_triangle(uint32_t surface, int16_t x0, int16_t y0,
238 int16_t x1, int16_t y1, int16_t x2, int16_t y2,
239 uint8_t filled) {
240 SurfaceSlot* s = find_slot(surface);
241 if (!s) return HOST_ERR_NOT_FOUND;
242 if (!filled) {
243 s->surf->drawTriangle(x0, y0, x1, y1, x2, y2, INK);
244 } else if (s->shade >= 255) {
245 s->surf->fillTriangle(x0, y0, x1, y1, x2, y2, INK);
246 } else if (s->shade > 0) {
247 render::fillTriangleDither(s->surf.get(), x0, y0, x1, y1, x2, y2, s->shade, INK);
248 }
249 return HOST_OK;
250}
251
252int host_surface_draw_round_rect(uint32_t surface, int16_t x, int16_t y,
253 int16_t w, int16_t h, int16_t r, uint8_t filled) {
254 SurfaceSlot* s = find_slot(surface);
255 if (!s) return HOST_ERR_NOT_FOUND;
256 if (filled) s->surf->fillRoundRect(x, y, w, h, r, INK);
257 else s->surf->drawRoundRect(x, y, w, h, r, INK);
258 return HOST_OK;
259}
260
261int host_surface_hline(uint32_t surface, int16_t x, int16_t y, int16_t w) {
262 SurfaceSlot* s = find_slot(surface);
263 if (!s) return HOST_ERR_NOT_FOUND;
264 s->surf->drawFastHLine(x, y, w, INK);
265 return HOST_OK;
266}
267
268int host_surface_vline(uint32_t surface, int16_t x, int16_t y, int16_t h) {
269 SurfaceSlot* s = find_slot(surface);
270 if (!s) return HOST_ERR_NOT_FOUND;
271 s->surf->drawFastVLine(x, y, h, INK);
272 return HOST_OK;
273}
274
275int host_surface_draw_bitmap(uint32_t surface, int16_t x, int16_t y,
276 int16_t w, int16_t h, const uint8_t* data, uint32_t len) {
277 SurfaceSlot* s = find_slot(surface);
278 if (!s) return HOST_ERR_NOT_FOUND;
279 if (!data || w <= 0 || h <= 0) return HOST_ERR_INVALID_ARG;
280 const uint32_t needed = static_cast<uint32_t>((w + 7) / 8) * static_cast<uint32_t>(h);
281 if (len < needed) return HOST_ERR_INVALID_ARG;
282 s->surf->drawBitmap(x, y, data, w, h, INK);
283 return HOST_OK;
284}
285
286int host_surface_export(uint32_t surface, uint8_t* out, size_t out_size,
287 uint16_t* out_stride_bytes) {
288 SurfaceSlot* s = find_slot(surface);
289 if (!s) return HOST_ERR_NOT_FOUND;
290 if (!out || !out_stride_bytes) return HOST_ERR_INVALID_ARG;
291 const size_t bytes = s->surf->byteSize();
292 if (out_size < bytes) return HOST_ERR_NO_MEMORY;
293 std::memcpy(out, s->surf->buffer(), bytes);
294 *out_stride_bytes = s->surf->strideBytes();
295 return static_cast<int>(bytes);
296}
297
298int host_surface_export_jpg(uint32_t surface, uint8_t quality,
299 uint8_t* out, size_t out_size, uint32_t* out_len) {
300 SurfaceSlot* s = find_slot(surface);
301 if (!s) return HOST_ERR_NOT_FOUND;
302 if (!out || !out_len) return HOST_ERR_INVALID_ARG;
303 size_t len = 0;
304 int rc = cdc::image::encodeMonoJpeg(s->surf->buffer(),
305 static_cast<uint16_t>(s->surf->width()),
306 static_cast<uint16_t>(s->surf->height()),
307 s->surf->strideBytes(), quality,
308 out, out_size, &len);
309 *out_len = static_cast<uint32_t>(len);
310 switch (rc) {
311 case 0: return HOST_OK;
312 case -3: return HOST_ERR_NO_MEMORY;
313 case -2: return HOST_ERR_INVALID_ARG;
314 default: return HOST_ERR_GENERIC;
315 }
316}
317
318int host_surface_draw_sprite(uint32_t surface, int16_t x, int16_t y,
319 uint32_t sprite) {
320 SurfaceSlot* s = find_slot(surface);
321 if (!s) return HOST_ERR_NOT_FOUND;
323 if (!c) return HOST_ERR_NOT_FOUND;
325 if (!c->spriteFrameView(sprite, &fv)) return HOST_ERR_NOT_FOUND;
326 render::BlitOpts opts;
327 opts.mask = fv.mask;
332 opts.scale = fv.scale;
333 render::drawBitmapMasked(s->surf.get(), x, y, fv.data,
334 static_cast<int16_t>(fv.w),
335 static_cast<int16_t>(fv.h), opts, INK, 0);
336 return HOST_OK;
337}
338
339int host_sprite_create_from_surface(uint32_t surface, uint16_t frame_w,
340 uint16_t frame_h, uint16_t frame_count) {
341 SurfaceSlot* s = find_slot(surface);
342 if (!s) return HOST_ERR_NOT_FOUND;
344 if (!c) return HOST_ERR_NOT_FOUND;
345 const uint16_t surfW = static_cast<uint16_t>(s->surf->width());
346 const uint16_t surfH = static_cast<uint16_t>(s->surf->height());
347 if (frame_w == 0 || frame_h == 0 || frame_count == 0
348 || frame_w > surfW || frame_h > surfH) {
350 }
351 const uint16_t cellsPerRow = surfW / frame_w;
352 const uint16_t rowsNeeded = static_cast<uint16_t>(
353 (frame_count + cellsPerRow - 1) / cellsPerRow);
354 if (static_cast<uint32_t>(rowsNeeded) * frame_h > surfH) {
356 }
357
358 // Repack the grid cells into a vertically stacked sheet. Cells are not
359 // byte-aligned in the surface, so this walks pixels.
360 const uint16_t stride = static_cast<uint16_t>((frame_w + 7) / 8);
361 const uint32_t sheetBytes = static_cast<uint32_t>(stride) * frame_h * frame_count;
362 auto sheet = std::make_unique<uint8_t[]>(sheetBytes);
363 if (!sheet) return HOST_ERR_NO_MEMORY;
364 std::memset(sheet.get(), 0, sheetBytes);
365
366 const uint8_t* src = s->surf->buffer();
367 const uint16_t srcStride = s->surf->strideBytes();
368 for (uint16_t f = 0; f < frame_count; ++f) {
369 const uint16_t cellX = static_cast<uint16_t>((f % cellsPerRow) * frame_w);
370 const uint16_t cellY = static_cast<uint16_t>((f / cellsPerRow) * frame_h);
371 uint8_t* dstFrame = sheet.get() + static_cast<uint32_t>(f) * stride * frame_h;
372 for (uint16_t yy = 0; yy < frame_h; ++yy) {
373 const uint8_t* srcRow = src + static_cast<uint32_t>(cellY + yy) * srcStride;
374 uint8_t* dstRow = dstFrame + static_cast<uint32_t>(yy) * stride;
375 for (uint16_t xx = 0; xx < frame_w; ++xx) {
376 uint16_t sx = static_cast<uint16_t>(cellX + xx);
377 if (srcRow[sx >> 3] & (0x80 >> (sx & 7))) {
378 dstRow[xx >> 3] |= static_cast<uint8_t>(0x80 >> (xx & 7));
379 }
380 }
381 }
382 }
383
384 int32_t handle = c->spriteCreate(frame_w, frame_h, frame_count,
385 sheet.get(), sheetBytes);
386 if (handle > 0) return static_cast<int>(handle);
387 return handle == 0 ? HOST_ERR_INVALID_ARG : HOST_ERR_NO_MEMORY;
388}
389
390// Free every surface owned by a plugin being unloaded.
391void plg_surface_on_unload(void* plugin) {
392 for (auto& s : s_slots) {
393 if (s.surf && s.plugin == plugin) s = SurfaceSlot{};
394 }
395}
396
397} // extern "C"
Offscreen 1-bpp Adafruit_GFX render target in PSRAM.
Singleton that owns plugin-pushed UI views (lists, confirms, inputs).
cdc::ui::CanvasView * canvasView()
static PluginUiState & instance() noexcept
int host_sprite_create_from_surface(uint32_t surface, uint16_t frame_w, uint16_t frame_h, uint16_t frame_count)
Create a sprite by slicing a surface into a row-major grid of frame_w x frame_h cells.
int host_surface_set_font(uint32_t surface, uint8_t font_id)
Select the font for subsequent text calls (HOST_FONT_* id).
int host_surface_draw_text(uint32_t surface, int16_t x, int16_t y, const char *text)
Draw UTF-8 text with the current font/size at (x, y) baseline-top.
int host_surface_draw_bitmap(uint32_t surface, int16_t x, int16_t y, int16_t w, int16_t h, const uint8_t *data, uint32_t len)
Blit a packed 1-bpp bitmap (MSB-first, set bit = black) at (x, y).
int host_surface_vline(uint32_t surface, int16_t x, int16_t y, int16_t h)
Draw a vertical line of height h.
int host_surface_draw_pixel(uint32_t surface, int16_t x, int16_t y)
Draw a pixel (current shade decides ink: shade > 0 draws black).
int host_surface_draw_sprite(uint32_t surface, int16_t x, int16_t y, uint32_t sprite)
Stamp a sprite's current frame onto a surface at (x, y).
int host_surface_measure_text(uint32_t surface, const char *text, uint16_t *out_w, uint16_t *out_h)
Measure UTF-8 text with the surface's current font/size.
int host_surface_destroy(uint32_t surface)
Destroy a surface and free its memory.
int host_surface_draw_line(uint32_t surface, int16_t x0, int16_t y0, int16_t x1, int16_t y1)
Draw a line.
int host_surface_draw_text_aligned(uint32_t surface, int16_t x, int16_t y, int16_t w, const char *text, uint8_t align)
Draw UTF-8 text aligned within a w-wide box starting at x.
int host_surface_draw_rect(uint32_t surface, int16_t x, int16_t y, int16_t w, int16_t h, uint8_t filled)
Draw a rectangle; filled ones use the current shade.
int host_surface_draw_circle(uint32_t surface, int16_t x, int16_t y, int16_t r, uint8_t filled)
Draw a circle centered at (x, y); filled ones use the current shade.
int host_surface_set_text_color(uint32_t surface, uint8_t inverted)
Draw text white-on-black (inverted != 0) or black-on-white.
int host_surface_draw_triangle(uint32_t surface, int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint8_t filled)
Draw a triangle; filled ones use the current shade.
#define HOST_SURFACE_MAX_PER_PLUGIN
Maximum surfaces a plugin may hold at once.
Definition host_api.h:2181
int host_surface_export(uint32_t surface, uint8_t *out, size_t out_size, uint16_t *out_stride_bytes)
Copy the surface's packed pixel rows into out.
int host_surface_set_shade(uint32_t surface, uint8_t shade)
Set the fill shade for filled shapes: 0 white .. 255 solid black.
int host_surface_clear(uint32_t surface)
Clear a surface to white.
int host_surface_create(uint16_t w, uint16_t h)
Create a w x h surface, cleared to white.
#define HOST_SURFACE_MAX_BYTES
Maximum packed pixel bytes per surface (stride * height).
Definition host_api.h:2183
int host_surface_set_text_size(uint32_t surface, uint8_t size)
Set the integer text scale (1..4) for subsequent text calls.
int host_surface_hline(uint32_t surface, int16_t x, int16_t y, int16_t w)
Draw a horizontal line of width w.
int host_surface_draw_round_rect(uint32_t surface, int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, uint8_t filled)
Draw a rounded rectangle with corner radius r.
int host_surface_export_jpg(uint32_t surface, uint8_t quality, uint8_t *out, size_t out_size, uint32_t *out_len)
Encode the surface as a grayscale JPEG.
CDC Badge OS plugin host API - canonical C ABI contract.
#define HOST_OK
Definition host_api.h:37
#define HOST_ERR_INVALID_ARG
Definition host_api.h:39
#define HOST_ERR_NO_MEMORY
Definition host_api.h:43
#define HOST_ERR_NOT_FOUND
Definition host_api.h:41
#define HOST_ERR_GENERIC
Definition host_api.h:38
void * plg_get_active_plugin(void)
void * plg_get_active_plugin(void)
void plg_surface_on_unload(void *plugin)
Internal UTF-8 <-> CP437 helpers for the plugin host API boundary.
std::string toDisplay(const char *utf8)
Decode a UTF-8 (with optional HTML entities) string into CP437 bytes.
@ SPRITE_FLAG_ROT_90
Rotate 90 deg clockwise (before flips).
Definition CanvasAnim.h:62
@ SPRITE_FLAG_OPAQUE
Unset data bits paint white.
Definition CanvasAnim.h:59
void drawText(Adafruit_GFX *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 drawBitmapMasked(Adafruit_GFX *gfx, int16_t x, int16_t y, const uint8_t *data, int16_t w, int16_t h, const BlitOpts &opts, uint16_t fg, uint16_t bg)
Blit a packed 1-bpp bitmap with optional mask plane, flipping, 90-degree rotation,...
void fillTriangleDither(Adafruit_GFX *gfx, int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint8_t shade, uint16_t color)
Dithered filled triangle; see fillRectDither.
void fillCircleDither(Adafruit_GFX *gfx, int16_t cx, int16_t cy, int16_t r, uint8_t shade, uint16_t color)
Dithered filled circle; see fillRectDither.
void measureText(Adafruit_GFX *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,...
void fillRectDither(Adafruit_GFX *gfx, int16_t x, int16_t y, int16_t w, int16_t h, uint8_t shade, uint16_t color)
Fills a rectangle with an ordered-dither fake-grey pattern.
const GFXfont * getGfxFont(FontId id)
Resolves a FontId to its underlying GFX font pointer.
Definition Fonts.cpp:26
constexpr uint8_t kFontIdCount
Number of entries in the canonical font table.
Definition Fonts.h:24
Replay accessors: geometry, flags and pixel pointers of a sprite.
Definition CanvasAnim.h:267
const uint8_t * data
Current frame pixels.
Definition CanvasAnim.h:268
const uint8_t * mask
Mask plane or null.
Definition CanvasAnim.h:269
Options for drawBitmapMasked. Zero-init = plain transparent blit.
uint8_t scale
Integer upscale 1..4.
const uint8_t * mask
Mask plane (same layout) or null.
bool opaque
Without a mask: unset bits paint bg.