aboutsummaryrefslogtreecommitdiffstats
path: root/surfaces
diff options
context:
space:
mode:
Diffstat (limited to 'surfaces')
-rw-r--r--surfaces/main.c81
-rw-r--r--surfaces/pixels.c90
-rw-r--r--surfaces/vega.h14
3 files changed, 178 insertions, 7 deletions
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 <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* 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 <stdio.h>
/* 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