From 993871c6dfa19cf4e0bd4a16c9aad26cb0025d70 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Sun, 27 Sep 2026 14:54:53 +0100 Subject: Draw the TUI's cells as pixels with the symbols' glyphs vega pixels runs the same scripts as tui --dump and writes each frame as an image: the view's cells (only those on screen are evaluated) become glyphs, each drawn once per size with the exact area of its curves, in its tone's colour on its row's background. No page, no scene in between. A 1920x1350 grid takes 12 ms a frame here, writing the image included. Symbol files carry the font's line now (format 3: ascent and descent where format 2 kept zeros), the cell height and baseline; and the build tool takes fonts in order, later ones filling the codes earlier ones lack: a CJK font alone drew ambiguous-width arrows two cells wide in a one-cell grid, which the new check caught, so a monospace Latin font comes first and Hangul after. --- surfaces/main.c | 81 ++++++++++++++++++++++++++++++++++++++++++++----- surfaces/pixels.c | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ surfaces/vega.h | 14 +++++++++ 3 files changed, 178 insertions(+), 7 deletions(-) create mode 100644 surfaces/pixels.c (limited to 'surfaces') diff --git a/surfaces/main.c b/surfaces/main.c index 82131ab..d00bca6 100644 --- a/surfaces/main.c +++ b/surfaces/main.c @@ -20,6 +20,9 @@ * say sentences (the first natural-language surface), driven by a script * web [--listen ADDRESS] [--port N] * a web page (a GUI), on 127.0.0.1 unless another address is given + * pixels --symbols FONT.vsym [--size PX] --grid WxH --out PREFIX + * the TUI's frames drawn with a font's glyphs, driven by a + * script: each show writes PREFIX-N.ppm after the text * With --store the app's states live in that tree store: the app continues * where it was, whichever surface runs it. With --watch (tui, web) a module * written again replaces the running one and states keep their values by @@ -456,15 +459,45 @@ static int interactive(void) { /* ---- scripts -------------------------------------------------------- */ -typedef enum { SURFACE_CLI, SURFACE_SAY, SURFACE_TUI, SURFACE_WEB } Surface; +typedef enum { SURFACE_CLI, SURFACE_SAY, SURFACE_TUI, SURFACE_WEB, SURFACE_PIXELS } Surface; + +/* The pixel surface's font and where its frames go. */ +static VegaFace face; +static const char *frames_prefix; +static unsigned frames; + +/* A frame as a binary PPM (P6): prefix-N.ppm. */ +static bool write_frame(const VegaGrid *grid, char path[512]) { + int width = (int)grid->width * face.cell_width, height = (int)grid->height * face.cell_height; + uint32_t *pixels = malloc((size_t)width * (size_t)height * sizeof(*pixels)); + unsigned char *bytes = malloc((size_t)width * (size_t)height * 3); + bool ok = pixels && bytes; + if (ok) { + vega_pixels(grid, &face, pixels, (size_t)width); + for (size_t i = 0; i < (size_t)width * (size_t)height; ++i) { + bytes[3 * i] = (unsigned char)(pixels[i] >> 16); + bytes[3 * i + 1] = (unsigned char)(pixels[i] >> 8); + bytes[3 * i + 2] = (unsigned char)pixels[i]; + } + snprintf(path, 512, "%s-%u.ppm", frames_prefix, ++frames); + FILE *f = fopen(path, "wb"); + ok = f && fprintf(f, "P6\n%d %d\n255\n", width, height) > 0 && + fwrite(bytes, 3, (size_t)width * (size_t)height, f) == (size_t)width * (size_t)height; + if (f && fclose(f)) ok = false; + } + free(pixels); + free(bytes); + return ok; +} /* painting: show writes the terminal bytes of the frame, not the grid. */ static int script(Surface surface, unsigned width, unsigned height, bool painting) { static char line[1 << 16]; VegaGrid grid = {0}; - if (surface == SURFACE_TUI && !vega_grid(&grid, width, height)) return 2; + bool gridded = surface == SURFACE_TUI || surface == SURFACE_PIXELS; + if (gridded && !vega_grid(&grid, width, height)) return 2; bool shown = false; - drawn = surface == SURFACE_TUI ? &grid : NULL; + drawn = gridded ? &grid : NULL; bool color = surface == SURFACE_CLI && isatty(STDOUT_FILENO) && !getenv("NO_COLOR"); unsigned long counted = vega_evaluations(); while (fgets(line, sizeof(line), stdin)) { @@ -498,7 +531,7 @@ static int script(Surface surface, unsigned width, unsigned height, bool paintin } else if (!strcmp(command, "click")) { unsigned at_line = 0, at_column = 0; verb = "point"; - if (surface != SURFACE_TUI) { fputs("vega: click needs the tui\n", stderr); continue; } + if (!gridded) { fputs("vega: click needs the tui or pixels\n", stderr); continue; } acted = sscanf(line, "%*s %u %u", &at_line, &at_column) == 2 && at_line && shown; ok = acted && mouse(0, at_line - 1); } else if (!strcmp(command, "time")) { @@ -538,6 +571,11 @@ static int script(Surface surface, unsigned width, unsigned height, bool paintin vega_draw(&grid, &app, &screen, entry, entry_length, typing); dump(&grid); printf("cells %lu\n", vega_evaluations() - counted); + char path[512]; + if (surface == SURFACE_PIXELS) { + if (!write_frame(&grid, path)) { fprintf(stderr, "vega: cannot write %s\n", path); return 1; } + printf("frame %s %dx%d\n", path, (int)grid.width * face.cell_width, (int)grid.height * face.cell_height); + } } counted = vega_evaluations(); } else { @@ -556,8 +594,28 @@ static int script(Surface surface, unsigned width, unsigned height, bool paintin return 0; } +/* The symbols a pixel surface draws with, read whole and kept. */ +static bool open_face(const char *path, float size) { + static VegaSymbols symbols; + FILE *f = fopen(path, "rb"); + long length = f && !fseek(f, 0, SEEK_END) ? ftell(f) : -1; + unsigned char *bytes = length > 0 && !fseek(f, 0, SEEK_SET) ? malloc((size_t)length) : NULL; + bool ok = bytes && fread(bytes, 1, (size_t)length, f) == (size_t)length; + if (f) fclose(f); + char error[128] = "cannot read it"; + ok = ok && vega_symbols_open(&symbols, bytes, (size_t)length, error, sizeof(error)); + if (ok && !vega_face_open(&face, &symbols, size)) { + ok = false; + snprintf(error, sizeof(error), "no line and digit to lay a grid out with"); + } + if (!ok) fprintf(stderr, "vega: %s: %s\n", path, error); + return ok; /* the bytes stay: the face reads them */ +} + int main(int argc, char **argv) { unsigned width = 0, height = 0, port = 8080; + const char *symbols_path = NULL; + float size = 16; bool dumped = false, painting = false, watch = false, usage = argc < 3; const char *store = NULL, *listen_on = "127.0.0.1"; for (int i = 3; !usage && i < argc; i += 2) { @@ -571,24 +629,33 @@ int main(int argc, char **argv) { else if (!strcmp(argv[i], "--listen") && !strcmp(argv[1], "web")) listen_on = argv[i + 1]; else if (!strcmp(argv[i], "--port") && !strcmp(argv[1], "web") && sscanf(argv[i + 1], "%u", &port) == 1 && port < 65536) {} - else if ((!strcmp(argv[i], "--dump") || !strcmp(argv[i], "--paint")) && + else if ((!strcmp(argv[i], "--dump") || !strcmp(argv[i], "--paint") || + (!strcmp(argv[i], "--grid") && !strcmp(argv[1], "pixels"))) && sscanf(argv[i + 1], "%ux%u", &width, &height) == 2 && width && height && width <= 1000 && height <= 1000) dumped = true, painting = !strcmp(argv[i], "--paint"); + else if (!strcmp(argv[i], "--symbols") && !strcmp(argv[1], "pixels")) symbols_path = argv[i + 1]; + else if (!strcmp(argv[i], "--out") && !strcmp(argv[1], "pixels")) frames_prefix = argv[i + 1]; + else if (!strcmp(argv[i], "--size") && !strcmp(argv[1], "pixels") && sscanf(argv[i + 1], "%f", &size) == 1) {} else usage = true; } - if (usage || (dumped && strcmp(argv[1], "tui")) || (!strcmp(argv[1], "check") && argc != 3)) { + bool pixels = argc > 1 && !strcmp(argv[1], "pixels"); + if (usage || (dumped && strcmp(argv[1], "tui") && !pixels) || (!strcmp(argv[1], "check") && argc != 3) || + (pixels && (!dumped || !symbols_path || !frames_prefix))) { fputs("vega tui|cli|say MODULE.pmod [--store PATH]\n" "vega tui MODULE.pmod [--store PATH] [--watch] | --dump|--paint WIDTHxHEIGHT < script\n" "vega web MODULE.pmod [--store PATH] [--listen ADDRESS] [--port N] [--watch]\n" + "vega pixels MODULE.pmod [--store PATH] --symbols FONT.vsym [--size PX] --grid WIDTHxHEIGHT --out PREFIX < script\n" "vega check MODULE.pmod\n", stderr); return 2; } + if (pixels && !open_face(symbols_path, size)) return 2; if (!strcmp(argv[1], "check")) { if (!load_module(argv[2], &modules[0])) return 2; return vega_check(&modules[0], print_finding, argv[2]) ? 1 : 0; } Surface surface = !strcmp(argv[1], "cli") ? SURFACE_CLI : !strcmp(argv[1], "say") ? SURFACE_SAY : - !strcmp(argv[1], "tui") ? SURFACE_TUI : !strcmp(argv[1], "web") ? SURFACE_WEB : (Surface)-1; + !strcmp(argv[1], "tui") ? SURFACE_TUI : !strcmp(argv[1], "web") ? SURFACE_WEB : + pixels ? SURFACE_PIXELS : (Surface)-1; if ((int)surface < 0) { fprintf(stderr, "vega: unknown surface %s\n", argv[1]); return 2; } if (!load(argv[2], store)) return 2; if (watch && !dumped) { diff --git a/surfaces/pixels.c b/surfaces/pixels.c new file mode 100644 index 0000000..67e7bc5 --- /dev/null +++ b/surfaces/pixels.c @@ -0,0 +1,90 @@ +#include "vega.h" +#include +#include +#include + +/* The pixel surface: the TUI's grid of cells, each drawn with its glyph + * (drawn once per size, the exact area of its curves) in its tone's colour. + * The view's cells go straight into pixels: no page, no scene. */ + +/* Colours, 0xRRGGBB: the background, a focused row's, and one per tone. */ +static const uint32_t background = 0x1e1e2e, focused = 0x313244, strong = 0xffffff; +static const uint32_t tones[VEGA_TONES] = {0xcdd6f4, 0x7f849c, 0x89b4fa, 0xa6e3a1, 0xf9e2af, 0xf38ba8}; + +bool vega_face_open(VegaFace *face, const VegaSymbols *symbols, float pixels) { + *face = (VegaFace){0}; + if (!symbols->units || symbols->ascent <= symbols->descent || pixels < 4 || pixels > 256) return false; + float scale = pixels / symbols->units; + /* A cell is as wide as a digit (the grid is monospace: wide characters + * take two) and as tall as the font's line. */ + VegaSymbol zero; + if (!vega_symbol(symbols, '0', &zero) || zero.advance <= 0) return false; + face->cell_width = (int)lroundf(zero.advance * scale); + face->baseline = (int)ceilf(symbols->ascent * scale); + face->cell_height = face->baseline + (int)ceilf(-symbols->descent * scale); + face->glyphs = vega_glyphs_open(symbols, scale); + return face->glyphs && face->cell_width > 0; +} + +void vega_face_close(VegaFace *face) { + vega_glyphs_close(face->glyphs); + *face = (VegaFace){0}; +} + +static void fill(uint32_t *out, size_t stride, int x, int y, int width, int height, uint32_t color) { + for (int j = 0; j < height; ++j) + for (int i = 0; i < width; ++i) out[(size_t)(y + j) * stride + (size_t)(x + i)] = color; +} + +/* Coverage a (0-255) of color over the pixel: straight alpha, per channel. */ +static uint32_t blend(uint32_t under, uint32_t color, unsigned a) { + uint32_t out = 0; + for (int shift = 0; shift < 24; shift += 8) { + int u = (int)(under >> shift & 0xff), c = (int)(color >> shift & 0xff); + out |= (uint32_t)(u + ((c - u) * (int)a + 127) / 255) << shift; + } + return out; +} + +static void draw(uint32_t *out, size_t stride, int width, int height, const VegaBitmap *glyph, int pen_x, int pen_y, + uint32_t color) { + const VegaPlace *p = &glyph->place; + for (int j = 0; j < p->height; ++j) { + int y = pen_y - p->top + j; + if (y < 0 || y >= height) continue; + for (int i = 0; i < p->width; ++i) { + int x = pen_x + p->left + i; + unsigned a = glyph->pixels[(size_t)j * (size_t)p->width + (size_t)i]; + if (a && x >= 0 && x < width) out[(size_t)y * stride + (size_t)x] = blend(out[(size_t)y * stride + (size_t)x], color, a); + } + } +} + +/* A character the symbols lack: a box in its cell. */ +static void missing(uint32_t *out, size_t stride, int x, int y, int width, int height, int limit, uint32_t color) { + int left = x + 1, right = (x + width < limit ? x + width : limit) - 2, top = y + height / 5, bottom = y + height - height / 5; + for (int i = left; i <= right; ++i) out[(size_t)top * stride + (size_t)i] = out[(size_t)bottom * stride + (size_t)i] = color; + for (int j = top; j <= bottom; ++j) out[(size_t)j * stride + (size_t)left] = out[(size_t)j * stride + (size_t)right] = color; +} + +void vega_pixels(const VegaGrid *grid, VegaFace *face, uint32_t *out, size_t stride) { + int width = (int)grid->width * face->cell_width, height = (int)grid->height * face->cell_height; + /* Backgrounds first: a wide glyph spans its neighbour's cell. */ + for (unsigned r = 0; r < grid->height; ++r) + for (unsigned c = 0; c < grid->width; ++c) + fill(out, stride, (int)c * face->cell_width, (int)r * face->cell_height, face->cell_width, face->cell_height, + grid->cells[(size_t)r * grid->width + c].focus ? focused : background); + for (unsigned r = 0; r < grid->height; ++r) + for (unsigned c = 0; c < grid->width; ++c) { + const VegaCell *cell = &grid->cells[(size_t)r * grid->width + c]; + if (!cell->ch || cell->ch == ' ') continue; + uint32_t color = cell->strong ? strong : tones[cell->tone < VEGA_TONES ? cell->tone : 0]; + int x = (int)c * face->cell_width, y = (int)r * face->cell_height; + const VegaBitmap *glyph = vega_glyph(face->glyphs, cell->ch); + unsigned cells = vega_width(cell->ch) == 2 ? 2 : 1; + if (!glyph) missing(out, stride, x, y, face->cell_width * (int)cells, face->cell_height, width, color); + else draw(out, stride, width, height, glyph, x, y + face->baseline, color); + if (cell->mark && (glyph = vega_glyph(face->glyphs, cell->mark))) + draw(out, stride, width, height, glyph, x, y + face->baseline, color); + } +} diff --git a/surfaces/vega.h b/surfaces/vega.h index f01667b..b8e0044 100644 --- a/surfaces/vega.h +++ b/surfaces/vega.h @@ -1,6 +1,7 @@ #ifndef VEGA_H #define VEGA_H #include "module.h" +#include "symbols.h" #include /* Vega reads a verified Pure module by convention (docs/design/001-first-component.md): @@ -121,4 +122,17 @@ void vega_grid_free(VegaGrid *); void vega_draw(VegaGrid *, PureApp *, const VegaScreen *, const char *prompt, size_t prompt_length, bool typing); /* The part and item a line of the last drawn grid shows. */ bool vega_hit(const VegaGrid *, unsigned line, unsigned *part, size_t *item); + +/* Pixels: the grid drawn with a font's glyphs (a symbol file, pixels per em). + * A cell is as wide as a digit and as tall as the font's line; each glyph is + * drawn once, the first time a cell shows it. */ +typedef struct { + VegaGlyphs *glyphs; + int cell_width, cell_height, baseline; +} VegaFace; +bool vega_face_open(VegaFace *, const VegaSymbols *, float pixels); +void vega_face_close(VegaFace *); +/* The grid into out: 0xRRGGBB pixels, grid->width * cell_width across and + * grid->height * cell_height down, stride pixels apart. */ +void vega_pixels(const VegaGrid *, VegaFace *, uint32_t *out, size_t stride); #endif -- cgit v1.2.3