diff --git a/Makefile b/Makefile index 16bff45..b70d9ed 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,8 @@ LDFLAGS = -Wl,--no-entry \ -Wl,--import-memory \ -Wl,--stack-first \ -Wl,--export-all \ - -Wl,--allow-undefined + -Wl,--undefined=Clay__MeasureText \ + -Wl,--undefined=Clay__QueryScrollOffset all: $(TARGET) @echo "Built $(TARGET) ($$(wc -c < $(TARGET)) bytes)" diff --git a/src/buffer.c b/src/buffer.c index 47f70d7..bc04cd6 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -5,10 +5,14 @@ typedef __SIZE_TYPE__ size_t; -static void *memcpy(void *dst, const void *src, size_t n) { +void *memcpy(void *dst, const void *src, size_t n) { return __builtin_memcpy(dst, src, n); } +void *memset(void *dst, int c, size_t n) { + return __builtin_memset(dst, c, n); +} + static size_t strlen(const char *s) { size_t n = 0; while (s[n]) diff --git a/src/clayterm.c b/src/clayterm.c index 75f1273..600ad55 100644 --- a/src/clayterm.c +++ b/src/clayterm.c @@ -1,12 +1,36 @@ -/* clayterm.c — cell buffer, diff engine, escape gen, Clay render backend */ +/* clayterm.c — WASM terminal rendering engine for Clay UI + * + * Public API (exported to WASM): + * clayterm_size — compute arena size for given dimensions + * init — initialize a Clayterm instance in provided memory + * reduce — decode command buffer, run Clay layout, render to ANSI + * output — pointer to output byte buffer + * length — length of output byte buffer + * measure — Clay text measurement callback + */ +#include "../clay/clay.h" #include "clayterm.h" #include "buffer.h" #include "cell.h" -#include "clay/clay.h" #include "utf8.h" #include "wcwidth.h" +/* ── Command buffer protocol ──────────────────────────────────────── */ + +#define OP_BEGIN_LAYOUT 0x01 +#define OP_OPEN_ELEMENT 0x02 +#define OP_TEXT 0x03 +#define OP_CLOSE_ELEMENT 0x04 +#define OP_END_LAYOUT 0x05 + +#define PROP_LAYOUT 0x01 +#define PROP_BG_COLOR 0x02 +#define PROP_CORNER_RADIUS 0x04 +#define PROP_BORDER 0x08 +#define PROP_CLIP 0x10 +#define PROP_FLOATING 0x20 + /* ── Instance state ───────────────────────────────────────────────── */ struct Clayterm { @@ -255,39 +279,57 @@ static void render_border(struct Clayterm *ct, int x0, int y0, int x1, int y1, setcell(ct, x1 - 1, y, 0x2502, fg, bg); } -static void walk(struct Clayterm *ct, Clay_RenderCommandArray cmds) { - for (int32_t i = 0; i < cmds.length; i++) { - Clay_RenderCommand *cmd = Clay_RenderCommandArray_Get(&cmds, i); - Clay_BoundingBox box = cmd->boundingBox; - int x0 = (int)box.x; - int y0 = (int)box.y; - int x1 = (int)(box.x + box.width); - int y1 = (int)(box.y + box.height); +/* ── Command buffer helpers ───────────────────────────────────────── */ - switch (cmd->commandType) { - case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: - render_rect(ct, x0, y0, x1, y1, &cmd->renderData.rectangle); - break; - case CLAY_RENDER_COMMAND_TYPE_TEXT: - render_text(ct, x0, y0, &cmd->renderData.text); - break; - case CLAY_RENDER_COMMAND_TYPE_BORDER: - render_border(ct, x0, y0, x1, y1, &cmd->renderData.border); - break; - case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: - ct->clipping = 1; - ct->clipx = x0; - ct->clipy = y0; - ct->clipw = x1 - x0; - ct->cliph = y1 - y0; - break; - case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: - ct->clipping = 0; - break; - default: - break; - } +static uint32_t rd(uint32_t *buf, int len, int *i) { + if (*i < len) + return buf[(*i)++]; + return 0; +} + +static float rdf(uint32_t *buf, int len, int *i) { + uint32_t v = rd(buf, len, i); + float f; + __builtin_memcpy(&f, &v, 4); + return f; +} + +static Clay_Color unpack_color(uint32_t c) { + return (Clay_Color){ + .r = (float)((c >> 16) & 0xff), + .g = (float)((c >> 8) & 0xff), + .b = (float)(c & 0xff), + .a = (float)((c >> 24) & 0xff), + }; +} + +static Clay_SizingAxis decode_axis(uint32_t *buf, int len, int *i) { + uint32_t type = rd(buf, len, i); + float a = rdf(buf, len, i); + float b = rdf(buf, len, i); + Clay_SizingAxis axis = {0}; + switch (type) { + case 0: /* FIT */ + axis.type = CLAY__SIZING_TYPE_FIT; + axis.size.minMax.min = a; + axis.size.minMax.max = b; + break; + case 1: /* GROW */ + axis.type = CLAY__SIZING_TYPE_GROW; + axis.size.minMax.min = a; + axis.size.minMax.max = b; + break; + case 2: /* PERCENT */ + axis.type = CLAY__SIZING_TYPE_PERCENT; + axis.size.percent = a; + break; + case 3: /* FIXED */ + axis.type = CLAY__SIZING_TYPE_FIXED; + axis.size.minMax.min = a; + axis.size.minMax.max = a; + break; } + return axis; } /* ── Public API ───────────────────────────────────────────────────── */ @@ -340,7 +382,132 @@ struct Clayterm *init(void *mem, int w, int h) { return ct; } -void render(struct Clayterm *ct, Clay_RenderCommandArray cmds) { +void reduce(struct Clayterm *ct, uint32_t *buf, int len) { + int i = 0; + uint32_t idx = 0; + + Clay_BeginLayout(); + + while (i < len) { + uint32_t op = rd(buf, len, &i); + + switch (op) { + case OP_OPEN_ELEMENT: { + /* read id string */ + uint32_t id_len = rd(buf, len, &i); + int id_words = (id_len + 3) / 4; + char *id_chars = (char *)&buf[i]; + i += id_words; + + if (id_len > 0) { + Clay_String str = {.length = (int32_t)id_len, .chars = id_chars}; + Clay_ElementId eid = Clay__HashString(str, idx++); + Clay__OpenElementWithId(eid); + } else { + Clay__OpenElement(); + } + + /* read property mask */ + uint32_t mask = rd(buf, len, &i); + Clay_ElementDeclaration decl = {0}; + + if (mask & PROP_LAYOUT) { + decl.layout.sizing.width = decode_axis(buf, len, &i); + decl.layout.sizing.height = decode_axis(buf, len, &i); + + uint32_t pad = rd(buf, len, &i); + decl.layout.padding.left = pad & 0xff; + decl.layout.padding.right = (pad >> 8) & 0xff; + decl.layout.padding.top = (pad >> 16) & 0xff; + decl.layout.padding.bottom = (pad >> 24) & 0xff; + + uint32_t gd = rd(buf, len, &i); + decl.layout.childGap = gd & 0xffff; + decl.layout.layoutDirection = (gd >> 16) & 0xff; + + uint32_t al = rd(buf, len, &i); + decl.layout.childAlignment.x = al & 0xff; + decl.layout.childAlignment.y = (al >> 8) & 0xff; + } + + if (mask & PROP_BG_COLOR) { + decl.backgroundColor = unpack_color(rd(buf, len, &i)); + } + + if (mask & PROP_CORNER_RADIUS) { + uint32_t cr = rd(buf, len, &i); + decl.cornerRadius.topLeft = (float)(cr & 0xff); + decl.cornerRadius.topRight = (float)((cr >> 8) & 0xff); + decl.cornerRadius.bottomLeft = (float)((cr >> 16) & 0xff); + decl.cornerRadius.bottomRight = (float)((cr >> 24) & 0xff); + } + + if (mask & PROP_BORDER) { + decl.border.color = unpack_color(rd(buf, len, &i)); + + uint32_t bw = rd(buf, len, &i); + decl.border.width.left = bw & 0xff; + decl.border.width.right = (bw >> 8) & 0xff; + decl.border.width.top = (bw >> 16) & 0xff; + decl.border.width.bottom = (bw >> 24) & 0xff; + } + + if (mask & PROP_CLIP) { + uint32_t cl = rd(buf, len, &i); + decl.clip.horizontal = cl & 0xff; + decl.clip.vertical = (cl >> 8) & 0xff; + } + + if (mask & PROP_FLOATING) { + decl.floating.offset.x = rdf(buf, len, &i); + decl.floating.offset.y = rdf(buf, len, &i); + decl.floating.parentId = rd(buf, len, &i); + + uint32_t fc = rd(buf, len, &i); + decl.floating.attachTo = fc & 0xff; + decl.floating.attachPoints.element = (fc >> 8) & 0xff; + decl.floating.attachPoints.parent = (fc >> 16) & 0xff; + decl.floating.zIndex = (int16_t)((fc >> 24) & 0xff); + } + + Clay__ConfigureOpenElement(decl); + break; + } + + case OP_TEXT: { + uint32_t col = rd(buf, len, &i); + uint32_t cfg = rd(buf, len, &i); + uint32_t str_len = rd(buf, len, &i); + int str_words = (str_len + 3) / 4; + char *str_chars = (char *)&buf[i]; + i += str_words; + + Clay_String text = {.length = (int32_t)str_len, .chars = str_chars}; + + Clay_TextElementConfig config = {0}; + config.textColor = unpack_color(col); + config.fontSize = cfg & 0xff; + config.fontId = (cfg >> 8) & 0xff; + config.wrapMode = (cfg >> 16) & 0xff; + /* attrs byte -> alpha channel for render_text to extract */ + config.textColor.a = (float)((cfg >> 24) & 0xff); + + Clay__OpenTextElement(text, Clay__StoreTextElementConfig(config)); + break; + } + + case OP_CLOSE_ELEMENT: + Clay__CloseElement(); + break; + + default: + break; + } + } + + Clay_RenderCommandArray cmds = Clay_EndLayout(); + + /* reset output state */ ct->out.length = 0; ct->lastfg = ct->lastbg = 0xffffffff; ct->lastx = ct->lasty = -1; @@ -348,7 +515,38 @@ void render(struct Clayterm *ct, Clay_RenderCommandArray cmds) { cells_clear(ct->back, ct->w, ct->h); /* walk Clay render commands into back buffer */ - walk(ct, cmds); + for (int32_t j = 0; j < cmds.length; j++) { + Clay_RenderCommand *cmd = Clay_RenderCommandArray_Get(&cmds, j); + Clay_BoundingBox box = cmd->boundingBox; + int x0 = (int)box.x; + int y0 = (int)box.y; + int x1 = (int)(box.x + box.width); + int y1 = (int)(box.y + box.height); + + switch (cmd->commandType) { + case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: + render_rect(ct, x0, y0, x1, y1, &cmd->renderData.rectangle); + break; + case CLAY_RENDER_COMMAND_TYPE_TEXT: + render_text(ct, x0, y0, &cmd->renderData.text); + break; + case CLAY_RENDER_COMMAND_TYPE_BORDER: + render_border(ct, x0, y0, x1, y1, &cmd->renderData.border); + break; + case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: + ct->clipping = 1; + ct->clipx = x0; + ct->clipy = y0; + ct->clipw = x1 - x0; + ct->cliph = y1 - y0; + break; + case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: + ct->clipping = 0; + break; + default: + break; + } + } /* diff front vs back, emit escape sequences */ present(ct); diff --git a/src/clayterm.h b/src/clayterm.h index fa7aa24..703497b 100644 --- a/src/clayterm.h +++ b/src/clayterm.h @@ -3,6 +3,8 @@ #ifndef CLAYTERM_H #define CLAYTERM_H +#include + #include "cell.h" struct Clayterm; @@ -10,7 +12,7 @@ struct Clayterm; /* WASM exports */ int clayterm_size(int w, int h); struct Clayterm *init(void *mem, int w, int h); -void render(struct Clayterm *ct, Clay_RenderCommandArray cmds); +void reduce(struct Clayterm *ct, uint32_t *buf, int len); char *output(struct Clayterm *ct); int length(struct Clayterm *ct); void measure(int ret, int txt); diff --git a/src/module.c b/src/module.c index 1f5ad1c..3e4c0bc 100644 --- a/src/module.c +++ b/src/module.c @@ -1,10 +1,9 @@ /* module.c — single compilation unit for clayterm WASM module */ -#include "clay/clay.h" +#include "../clay/clay.h" #include "buffer.c" #include "cell.c" -#include "clayterm.c" -#include "ops.c" #include "utf8.c" #include "wcwidth.c" +#include "clayterm.c" diff --git a/src/ops.c b/src/ops.c deleted file mode 100644 index 35bd8c7..0000000 --- a/src/ops.c +++ /dev/null @@ -1,187 +0,0 @@ -/* ops.c — command buffer reducer for Clay layout commands */ - -#include "ops.h" -#include "clay/clay.h" -#include "clayterm.h" - -/* ── Command buffer helpers ────────────────────────────────────────── */ - -static uint32_t rd(uint32_t *buf, int len, int *i) { - if (*i < len) - return buf[(*i)++]; - return 0; -} - -static float rdf(uint32_t *buf, int len, int *i) { - uint32_t v = rd(buf, len, i); - float f; - __builtin_memcpy(&f, &v, 4); - return f; -} - -static Clay_Color unpack_color(uint32_t c) { - return (Clay_Color){ - .r = (float)((c >> 16) & 0xff), - .g = (float)((c >> 8) & 0xff), - .b = (float)(c & 0xff), - .a = (float)((c >> 24) & 0xff), - }; -} - -static Clay_SizingAxis decode_axis(uint32_t *buf, int len, int *i) { - uint32_t type = rd(buf, len, i); - float a = rdf(buf, len, i); - float b = rdf(buf, len, i); - Clay_SizingAxis axis = {0}; - switch (type) { - case 0: /* FIT */ - axis.type = CLAY__SIZING_TYPE_FIT; - axis.size.minMax.min = a; - axis.size.minMax.max = b; - break; - case 1: /* GROW */ - axis.type = CLAY__SIZING_TYPE_GROW; - axis.size.minMax.min = a; - axis.size.minMax.max = b; - break; - case 2: /* PERCENT */ - axis.type = CLAY__SIZING_TYPE_PERCENT; - axis.size.percent = a; - break; - case 3: /* FIXED */ - axis.type = CLAY__SIZING_TYPE_FIXED; - axis.size.minMax.min = a; - axis.size.minMax.max = a; - break; - } - return axis; -} - -/* ── Command buffer reducer ───────────────────────────────────────── */ - -void reduce(struct Clayterm *ct, uint32_t *buf, int len) { - int i = 0; - uint32_t idx = 0; - - Clay_BeginLayout(); - - while (i < len) { - uint32_t op = rd(buf, len, &i); - - switch (op) { - case OP_OPEN_ELEMENT: { - /* read id string */ - uint32_t id_len = rd(buf, len, &i); - int id_words = (id_len + 3) / 4; - char *id_chars = (char *)&buf[i]; - i += id_words; - - if (id_len > 0) { - Clay_String str = {.length = (int32_t)id_len, .chars = id_chars}; - Clay_ElementId eid = Clay__HashString(str, idx++); - Clay__OpenElementWithId(eid); - } else { - Clay__OpenElement(); - } - - /* read property mask */ - uint32_t mask = rd(buf, len, &i); - Clay_ElementDeclaration decl = {0}; - - if (mask & PROP_LAYOUT) { - decl.layout.sizing.width = decode_axis(buf, len, &i); - decl.layout.sizing.height = decode_axis(buf, len, &i); - - uint32_t pad = rd(buf, len, &i); - decl.layout.padding.left = pad & 0xff; - decl.layout.padding.right = (pad >> 8) & 0xff; - decl.layout.padding.top = (pad >> 16) & 0xff; - decl.layout.padding.bottom = (pad >> 24) & 0xff; - - uint32_t gd = rd(buf, len, &i); - decl.layout.childGap = gd & 0xffff; - decl.layout.layoutDirection = (gd >> 16) & 0xff; - - uint32_t al = rd(buf, len, &i); - decl.layout.childAlignment.x = al & 0xff; - decl.layout.childAlignment.y = (al >> 8) & 0xff; - } - - if (mask & PROP_BG_COLOR) { - decl.backgroundColor = unpack_color(rd(buf, len, &i)); - } - - if (mask & PROP_CORNER_RADIUS) { - uint32_t cr = rd(buf, len, &i); - decl.cornerRadius.topLeft = (float)(cr & 0xff); - decl.cornerRadius.topRight = (float)((cr >> 8) & 0xff); - decl.cornerRadius.bottomLeft = (float)((cr >> 16) & 0xff); - decl.cornerRadius.bottomRight = (float)((cr >> 24) & 0xff); - } - - if (mask & PROP_BORDER) { - decl.border.color = unpack_color(rd(buf, len, &i)); - - uint32_t bw = rd(buf, len, &i); - decl.border.width.left = bw & 0xff; - decl.border.width.right = (bw >> 8) & 0xff; - decl.border.width.top = (bw >> 16) & 0xff; - decl.border.width.bottom = (bw >> 24) & 0xff; - } - - if (mask & PROP_CLIP) { - uint32_t cl = rd(buf, len, &i); - decl.clip.horizontal = cl & 0xff; - decl.clip.vertical = (cl >> 8) & 0xff; - } - - if (mask & PROP_FLOATING) { - decl.floating.offset.x = rdf(buf, len, &i); - decl.floating.offset.y = rdf(buf, len, &i); - decl.floating.parentId = rd(buf, len, &i); - - uint32_t fc = rd(buf, len, &i); - decl.floating.attachTo = fc & 0xff; - decl.floating.attachPoints.element = (fc >> 8) & 0xff; - decl.floating.attachPoints.parent = (fc >> 16) & 0xff; - decl.floating.zIndex = (int16_t)((fc >> 24) & 0xff); - } - - Clay__ConfigureOpenElement(decl); - break; - } - - case OP_TEXT: { - uint32_t col = rd(buf, len, &i); - uint32_t cfg = rd(buf, len, &i); - uint32_t str_len = rd(buf, len, &i); - int str_words = (str_len + 3) / 4; - char *str_chars = (char *)&buf[i]; - i += str_words; - - Clay_String text = {.length = (int32_t)str_len, .chars = str_chars}; - - Clay_TextElementConfig config = {0}; - config.textColor = unpack_color(col); - config.fontSize = cfg & 0xff; - config.fontId = (cfg >> 8) & 0xff; - config.wrapMode = (cfg >> 16) & 0xff; - /* attrs byte → alpha channel for render_text to extract */ - config.textColor.a = (float)((cfg >> 24) & 0xff); - - Clay__OpenTextElement(text, Clay__StoreTextElementConfig(config)); - break; - } - - case OP_CLOSE_ELEMENT: - Clay__CloseElement(); - break; - - default: - break; - } - } - - Clay_RenderCommandArray cmds = Clay_EndLayout(); - render(ct, cmds); -} diff --git a/src/ops.h b/src/ops.h deleted file mode 100644 index a1f9bf3..0000000 --- a/src/ops.h +++ /dev/null @@ -1,27 +0,0 @@ -/* ops.h — command buffer protocol for Clay layout commands */ - -#ifndef OPS_H -#define OPS_H - -#include - -struct Clayterm; - -/* Command buffer opcodes */ -#define OP_BEGIN_LAYOUT 0x01 -#define OP_OPEN_ELEMENT 0x02 -#define OP_TEXT 0x03 -#define OP_CLOSE_ELEMENT 0x04 -#define OP_END_LAYOUT 0x05 - -/* Property group masks for OPEN_ELEMENT */ -#define PROP_LAYOUT 0x01 -#define PROP_BG_COLOR 0x02 -#define PROP_CORNER_RADIUS 0x04 -#define PROP_BORDER 0x08 -#define PROP_CLIP 0x10 -#define PROP_FLOATING 0x20 - -void reduce(struct Clayterm *ct, uint32_t *buf, int len); - -#endif