aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/draw.c456
-rw-r--r--src/main.c604
-rw-r--r--src/screen.c229
-rw-r--r--src/symbols-gl.c260
-rw-r--r--src/symbols-gl.h24
-rw-r--r--src/symbols-tool.c881
-rw-r--r--src/symbols.c185
-rw-r--r--src/symbols.h54
-rw-r--r--src/vega.h124
-rw-r--r--src/web.c453
-rw-r--r--src/width.c70
11 files changed, 0 insertions, 3340 deletions
diff --git a/src/draw.c b/src/draw.c
deleted file mode 100644
index 31188c7..0000000
--- a/src/draw.c
+++ /dev/null
@@ -1,456 +0,0 @@
-#include "vega.h"
-#include <stdlib.h>
-#include <string.h>
-
-static const char *const tone_names[VEGA_TONES] = {"normal", "muted", "accent", "good", "warning", "danger"};
-
-/* ---- CLI ------------------------------------------------------------ */
-
-/* Text for a pipe: control characters become their pictures so a value never
- * breaks a line. */
-static void put_text(FILE *out, const char *text, size_t length) {
- for (size_t at = 0; at < length;) {
- uint32_t codepoint;
- size_t size = vega_decode(text, length, at, &codepoint);
- if (vega_visible(codepoint) == codepoint) fwrite(text + at, 1, size, out);
- else {
- uint32_t picture = vega_visible(codepoint);
- fputc(0xE0 | (int)(picture >> 12), out);
- fputc(0x80 | (int)(picture >> 6 & 0x3F), out);
- fputc(0x80 | (int)(picture & 0x3F), out);
- }
- at += size;
- }
-}
-
-static void pad(FILE *out, unsigned count) {
- while (count--) fputc(' ', out);
-}
-
-static const char *const colors[VEGA_TONES] = {"", "\033[2m", "\033[1;36m", "\033[32m", "\033[33m", "\033[31m"};
-
-static void tone_start(FILE *out, VegaTone tone, bool color) {
- if (color && tone) fputs(colors[tone], out);
-}
-
-static void tone_end(FILE *out, VegaTone tone, bool color) {
- if (color && tone) fputs("\033[0m", out);
- else if (!color && tone) fprintf(out, " (%s)", tone_names[tone]);
-}
-
-static void print_list(FILE *out, PureApp *app, const VegaPart *part, bool color) {
- char text[VEGA_TEXT];
- size_t length, rows = vega_rows(app, part), focus = vega_focus(app, part);
- for (size_t row = 0; row < rows; ++row) {
- VegaTone tone = vega_tone(app, part, row);
- fputs(row == focus ? "> " : " ", out);
- tone_start(out, tone, color);
- if (vega_text(app, (uint32_t)part->label, row, text, &length)) put_text(out, text, length);
- if (part->badge >= 0 && vega_text(app, (uint32_t)part->badge, row, text, &length) && length) {
- fputs(" [", out);
- put_text(out, text, length);
- fputc(']', out);
- }
- if (part->detail >= 0 && vega_text(app, (uint32_t)part->detail, row, text, &length) && length) {
- fputs(" - ", out);
- put_text(out, text, length);
- }
- tone_end(out, tone, color);
- fputc('\n', out);
- }
-}
-
-static void print_table(FILE *out, PureApp *app, const VegaPart *part, bool color) {
- const PureModule *m = app->module;
- size_t rows = vega_rows(app, part), focus = vega_focus(app, part);
- unsigned widths[PURE_MODULE_VIEWS];
- char text[VEGA_TEXT];
- size_t length;
- for (unsigned c = 0; c < part->column_count; ++c) {
- const char *name = m->view_names[part->columns[c]];
- widths[c] = vega_text_width(name, strlen(name));
- for (size_t row = 0; row < rows; ++row)
- if (vega_text(app, part->columns[c], row, text, &length)) {
- unsigned width = vega_text_width(text, length);
- if (width > widths[c]) widths[c] = width;
- }
- }
- fputs(" ", out);
- for (unsigned c = 0; c < part->column_count; ++c) {
- const char *name = m->view_names[part->columns[c]];
- fputs(name, out);
- if (c + 1 < part->column_count) pad(out, widths[c] - (unsigned)strlen(name) + 2);
- }
- fputc('\n', out);
- for (size_t row = 0; row < rows; ++row) {
- VegaTone tone = vega_tone(app, part, row);
- fputs(row == focus ? "> " : " ", out);
- tone_start(out, tone, color);
- for (unsigned c = 0; c < part->column_count; ++c) {
- bool ok = vega_text(app, part->columns[c], row, text, &length);
- if (!ok) { memcpy(text, "-", 2); length = 1; }
- put_text(out, text, length);
- if (c + 1 < part->column_count) pad(out, widths[c] - vega_text_width(text, length) + 2);
- }
- tone_end(out, tone, color);
- fputc('\n', out);
- }
-}
-
-void vega_print(FILE *out, PureApp *app, const VegaScreen *screen, bool color) {
- const PureModule *m = app->module;
- char text[VEGA_TEXT];
- size_t length;
- if (screen->title >= 0 && vega_text(app, (uint32_t)screen->title, 0, text, &length)) {
- if (color) fputs("\033[1m", out);
- put_text(out, text, length);
- if (color) fputs("\033[0m", out);
- fputc('\n', out);
- }
- for (unsigned i = 0; i < screen->count; ++i) {
- const VegaPart *part = &screen->parts[i];
- const char *name = m->view_names[part->view];
- if (part->kind == VEGA_FIELD) {
- fprintf(out, "%s: ", name);
- if (vega_text(app, part->view, 0, text, &length)) put_text(out, text, length);
- else fputs("-", out);
- fputc('\n', out);
- continue;
- }
- fprintf(out, "%s (%zu)\n", name, vega_rows(app, part));
- if (part->kind == VEGA_LIST) print_list(out, app, part, color);
- else print_table(out, app, part, color);
- }
-}
-
-/* ---- NUI ------------------------------------------------------------ */
-
-/* Spoken text: control characters become spaces, and trailing ones go. */
-static void speak(FILE *out, const char *text, size_t length) {
- while (length && ((unsigned char)text[length - 1] <= 0x20)) --length;
- for (size_t i = 0; i < length; ++i) fputc((unsigned char)text[i] < 0x20 ? ' ' : text[i], out);
-}
-
-/* "Item 2 of 3: milk, done." Tones other than normal and muted are said. */
-static void say_focus(FILE *out, PureApp *app, const VegaPart *part) {
- const PureModule *m = app->module;
- char text[VEGA_TEXT];
- size_t length, rows = vega_rows(app, part);
- if (!rows) { fprintf(out, "%s is empty.\n", m->view_names[part->view]); return; }
- size_t focus = vega_focus(app, part);
- if (focus >= rows) { fprintf(out, "%s: none of %zu focused.\n", m->view_names[part->view], rows); return; }
- fprintf(out, "Item %zu of %zu: ", focus + 1, rows);
- VegaTone tone = vega_tone(app, part, focus);
- if (tone >= VEGA_ACCENT) fprintf(out, "%s, ", tone_names[tone]);
- if (part->kind == VEGA_LIST) {
- if (vega_text(app, (uint32_t)part->label, focus, text, &length)) speak(out, text, length);
- int more[] = {part->badge, part->detail};
- for (unsigned k = 0; k < 2; ++k)
- if (more[k] >= 0 && vega_text(app, (uint32_t)more[k], focus, text, &length) && length) {
- fputs(", ", out);
- speak(out, text, length);
- }
- } else {
- for (unsigned c = 0; c < part->column_count; ++c) {
- fprintf(out, "%s%s ", c ? ", " : "", m->view_names[part->columns[c]]);
- if (vega_text(app, part->columns[c], focus, text, &length)) speak(out, text, length);
- else fputs("none", out);
- }
- }
- fputs(".\n", out);
-}
-
-void vega_say(FILE *out, PureApp *app, const VegaScreen *screen, bool all) {
- const PureModule *m = app->module;
- char text[VEGA_TEXT];
- size_t length;
- if (!all) {
- /* The focused item where a list shows it, else the first list. */
- const VegaPart *first = NULL;
- for (unsigned i = 0; i < screen->count; ++i) {
- const VegaPart *part = &screen->parts[i];
- if (part->kind == VEGA_FIELD) continue;
- if (!first) first = part;
- if (vega_focus(app, part) < vega_rows(app, part)) { say_focus(out, app, part); return; }
- }
- if (first) say_focus(out, app, first);
- return;
- }
- if (screen->title >= 0 && vega_text(app, (uint32_t)screen->title, 0, text, &length)) {
- speak(out, text, length);
- fputs(".\n", out);
- }
- for (unsigned i = 0; i < screen->count; ++i) {
- const VegaPart *part = &screen->parts[i];
- const char *name = m->view_names[part->view];
- if (part->kind == VEGA_FIELD) {
- fprintf(out, "%s: ", name);
- if (vega_text(app, part->view, 0, text, &length)) speak(out, text, length);
- else fputs("none", out);
- fputs(".\n", out);
- continue;
- }
- size_t rows = vega_rows(app, part);
- fprintf(out, "%s, %zu item%s.\n", name, rows, rows == 1 ? "" : "s");
- if (rows) say_focus(out, app, part);
- }
-}
-
-/* ---- TUI ------------------------------------------------------------ */
-
-bool vega_grid(VegaGrid *grid, unsigned width, unsigned height) {
- size_t count = (size_t)width * height;
- VegaCell *cells = calloc(count ? count : 1, sizeof(*cells)), *shown = calloc(count ? count : 1, sizeof(*shown));
- struct VegaRow *rows = calloc(height ? height : 1, sizeof(*rows));
- if (!cells || !shown || !rows) { free(cells); free(shown); free(rows); return false; }
- for (unsigned r = 0; r < height; ++r) rows[r].part = -1;
- free(grid->cells);
- free(grid->shown);
- free(grid->rows);
- grid->cells = cells;
- grid->shown = shown;
- grid->rows = rows;
- grid->painted = false;
- grid->width = width;
- grid->height = height;
- return true;
-}
-
-void vega_grid_free(VegaGrid *grid) {
- free(grid->cells);
- free(grid->shown);
- free(grid->rows);
- *grid = (VegaGrid){0};
-}
-
-typedef struct { VegaGrid *grid; unsigned row, column, end; uint8_t tone; bool focus, strong; } Pen;
-
-/* Write text from the pen's column up to its end; a wide character that
- * does not fit is left out, and a mark joins the character before it. */
-static void write_text(Pen *pen, const char *text, size_t length) {
- VegaCell *line = pen->grid->cells + (size_t)pen->row * pen->grid->width;
- for (size_t at = 0; at < length;) {
- uint32_t codepoint;
- at += vega_decode(text, length, at, &codepoint);
- codepoint = vega_visible(codepoint);
- unsigned width = vega_width(codepoint);
- if (!width) {
- if (pen->column && !line[pen->column - 1].mark) {
- unsigned base = pen->column - 1;
- while (base && !line[base].ch) --base;
- line[base].mark = codepoint;
- }
- continue;
- }
- if (pen->column + width > pen->end) { pen->column = pen->end; return; }
- line[pen->column] = (VegaCell){codepoint, 0, pen->tone, pen->focus, pen->strong};
- if (width == 2) line[pen->column + 1] = (VegaCell){0, 0, pen->tone, pen->focus, pen->strong};
- pen->column += width;
- }
-}
-
-static void write_cstr(Pen *pen, const char *text) { write_text(pen, text, strlen(text)); }
-
-/* Fill the rest of the pen's span with spaces in its style (a focused row
- * is highlighted across). */
-static void fill(Pen *pen) {
- VegaCell *line = pen->grid->cells + (size_t)pen->row * pen->grid->width;
- while (pen->column < pen->end) line[pen->column++] = (VegaCell){' ', 0, pen->tone, pen->focus, pen->strong};
-}
-
-/* Text cut to width with an ellipsis. */
-static void write_fit(Pen *pen, const char *text, size_t length, unsigned width) {
- unsigned end = pen->end;
- if (pen->column + width < end) pen->end = pen->column + width;
- if (vega_text_width(text, length) > pen->end - pen->column && pen->end > pen->column) {
- --pen->end;
- write_text(pen, text, length);
- pen->end = end;
- write_cstr(pen, "\xe2\x80\xa6");
- } else {
- write_text(pen, text, length);
- pen->end = end;
- }
-}
-
-static const char *key_symbol(const char *key) {
- static const struct { const char *key, *symbol; } symbols[] = {
- {"ArrowUp", "\xe2\x86\x91"}, {"ArrowDown", "\xe2\x86\x93"}, {"ArrowLeft", "\xe2\x86\x90"},
- {"ArrowRight", "\xe2\x86\x92"}, {"Space", "Space"}, {"Enter", "Enter"}, {"Backspace", "Bksp"},
- {"Delete", "Del"}, {"Escape", "Esc"}, {"PageUp", "PgUp"}, {"PageDown", "PgDn"}};
- for (size_t i = 0; i < sizeof(symbols) / sizeof(*symbols); ++i)
- if (!strcmp(symbols[i].key, key)) return symbols[i].symbol;
- if (!strncmp(key, "Key", 3)) return key + 3;
- if (!strncmp(key, "Digit", 5)) return key + 5;
- return key;
-}
-
-static void draw_list(VegaGrid *grid, PureApp *app, const VegaPart *part, int index, size_t first, unsigned top,
- unsigned height) {
- char text[VEGA_TEXT];
- size_t length, rows = vega_rows(app, part), focus = vega_focus(app, part);
- for (unsigned r = 0; r < height && first + r < rows; ++r) {
- size_t row = first + r;
- grid->rows[top + r] = (struct VegaRow){index, row};
- bool focused = row == focus;
- VegaTone tone = vega_tone(app, part, row);
- Pen pen = {grid, top + r, 0, grid->width, (uint8_t)tone, focused, false};
- write_cstr(&pen, focused ? "> " : " ");
- char badge[VEGA_TEXT];
- size_t badge_length = 0;
- if (part->badge >= 0 && !vega_text(app, (uint32_t)part->badge, row, badge, &badge_length)) badge_length = 0;
- unsigned badge_width = badge_length ? vega_text_width(badge, badge_length) + 2 : 0;
- unsigned room = grid->width > pen.column + badge_width ? grid->width - pen.column - badge_width : 0;
- if (vega_text(app, (uint32_t)part->label, row, text, &length)) write_fit(&pen, text, length, room);
- if (part->detail >= 0 && vega_text(app, (uint32_t)part->detail, row, text, &length) && length) {
- unsigned used = pen.column;
- uint8_t saved = pen.tone;
- pen.tone = pen.tone ? pen.tone : VEGA_MUTED;
- write_cstr(&pen, " ");
- unsigned left = grid->width > badge_width + pen.column ? grid->width - badge_width - pen.column : 0;
- if (pen.column - used == 2) write_fit(&pen, text, length, left);
- pen.tone = saved;
- }
- if (badge_length && grid->width >= badge_width) {
- /* The badge ends at the last column, two cells after the label at least. */
- unsigned start = grid->width - (badge_width - 2);
- if (pen.column < start) fill(&(Pen){grid, pen.row, pen.column, start, pen.tone, focused, false});
- pen.column = start > pen.column ? start : pen.column;
- write_fit(&pen, badge, badge_length, badge_width - 2);
- }
- fill(&pen);
- }
-}
-
-static void draw_table(VegaGrid *grid, PureApp *app, const VegaPart *part, int index, size_t first, unsigned top,
- unsigned height) {
- const PureModule *m = app->module;
- char text[VEGA_TEXT];
- size_t length, rows = vega_rows(app, part), focus = vega_focus(app, part);
- unsigned visible = height ? height - 1 : 0, widths[PURE_MODULE_VIEWS], total = 2;
- /* Columns as wide as their header and the rows on screen, shared out when too wide. */
- for (unsigned c = 0; c < part->column_count; ++c) {
- const char *name = m->view_names[part->columns[c]];
- widths[c] = vega_text_width(name, strlen(name));
- for (unsigned r = 0; r < visible && first + r < rows; ++r)
- if (vega_text(app, part->columns[c], first + r, text, &length)) {
- unsigned width = vega_text_width(text, length);
- if (width > widths[c]) widths[c] = width;
- }
- total += widths[c] + 2;
- }
- if (part->column_count && total > grid->width) {
- unsigned share = grid->width > 2 + 2 * part->column_count ? (grid->width - 2) / part->column_count - 2 : 1;
- for (unsigned c = 0; c < part->column_count; ++c) if (widths[c] > share) widths[c] = share;
- }
- if (!height) return;
- Pen head = {grid, top, 2, grid->width, VEGA_MUTED, false, true};
- for (unsigned c = 0; c < part->column_count; ++c) {
- const char *name = m->view_names[part->columns[c]];
- unsigned start = head.column;
- write_fit(&head, name, strlen(name), widths[c]);
- head.column = start + widths[c] + 2 < grid->width ? start + widths[c] + 2 : grid->width;
- }
- for (unsigned r = 0; r < visible && first + r < rows; ++r) {
- size_t row = first + r;
- bool focused = row == focus;
- grid->rows[top + 1 + r] = (struct VegaRow){index, row};
- Pen pen = {grid, top + 1 + r, 0, grid->width, (uint8_t)vega_tone(app, part, row), focused, false};
- write_cstr(&pen, focused ? "> " : " ");
- for (unsigned c = 0; c < part->column_count; ++c) {
- unsigned start = pen.column, end = start + widths[c] + 2 < grid->width ? start + widths[c] + 2 : grid->width;
- if (vega_text(app, part->columns[c], row, text, &length)) write_fit(&pen, text, length, widths[c]);
- else write_cstr(&pen, "-");
- if (pen.column < end) fill(&(Pen){grid, pen.row, pen.column, end, pen.tone, focused, false});
- pen.column = end;
- }
- fill(&pen);
- }
-}
-
-bool vega_hit(const VegaGrid *grid, unsigned line, unsigned *part, size_t *item) {
- if (line >= grid->height || grid->rows[line].part < 0) return false;
- *part = (unsigned)grid->rows[line].part;
- *item = grid->rows[line].item;
- return true;
-}
-
-void vega_draw(VegaGrid *grid, PureApp *app, const VegaScreen *screen, const char *prompt, size_t prompt_length,
- bool typing) {
- const PureModule *m = app->module;
- for (size_t i = 0; i < (size_t)grid->width * grid->height; ++i) grid->cells[i] = (VegaCell){' ', 0, 0, false, false};
- for (unsigned r = 0; r < grid->height; ++r) grid->rows[r].part = -1;
- grid->cursor_row = grid->cursor_column = -1;
- if (!grid->width || !grid->height) return;
- char text[VEGA_TEXT];
- size_t length;
- unsigned row = 0, bottom = grid->height - 1 - (screen->prompt >= 0 && grid->height > 1);
- if (screen->title >= 0 && row < bottom && vega_text(app, (uint32_t)screen->title, 0, text, &length)) {
- Pen pen = {grid, row++, 0, grid->width, VEGA_NORMAL, false, true};
- write_fit(&pen, text, length, grid->width);
- }
- unsigned groups = 0;
- for (unsigned i = 0; i < screen->count; ++i) {
- const VegaPart *part = &screen->parts[i];
- if (part->kind != VEGA_FIELD) { ++groups; continue; }
- if (row >= bottom) continue;
- Pen pen = {grid, row++, 0, grid->width, VEGA_MUTED, false, false};
- write_cstr(&pen, m->view_names[part->view]);
- write_cstr(&pen, " ");
- pen.tone = VEGA_NORMAL;
- if (vega_text(app, part->view, 0, text, &length)) write_fit(&pen, text, length, grid->width);
- else { pen.tone = VEGA_MUTED; write_cstr(&pen, "-"); }
- }
- /* Lists and tables share the rows left; each keeps its focused row in view
- * and only its visible rows are evaluated. */
- unsigned left = bottom > row ? bottom - row : 0;
- for (unsigned i = 0, group = 0; i < screen->count; ++i) {
- const VegaPart *part = &screen->parts[i];
- if (part->kind == VEGA_FIELD) continue;
- unsigned share = left / groups + (group < left % groups);
- ++group;
- if (!share) continue;
- size_t rows = vega_rows(app, part), focus = vega_focus(app, part);
- Pen head = {grid, row, 0, grid->width, VEGA_NORMAL, false, true};
- write_cstr(&head, m->view_names[part->view]);
- char count[32];
- snprintf(count, sizeof(count), " (%zu)", rows);
- head.strong = false;
- head.tone = VEGA_MUTED;
- write_cstr(&head, count);
- unsigned area = share - 1, visible = part->kind == VEGA_TABLE && area ? area - 1 : area;
- size_t *first = &grid->first[i];
- *first = vega_scroll(*first, focus, visible, rows);
- if (part->kind == VEGA_LIST) draw_list(grid, app, part, (int)i, *first, row + 1, area);
- else draw_table(grid, app, part, (int)i, *first, row + 1, area);
- row += share;
- }
- /* Keys the app answers to, then the text being typed. */
- Pen help = {grid, bottom, 0, grid->width, VEGA_MUTED, false, false};
- if (screen->prompt >= 0) write_cstr(&help, typing ? "Tab keys " : "Tab type ");
- for (uint32_t i = 0; i < m->inputs; ++i) {
- if (m->input[i].kind != PURE_KIND_KEY) continue;
- bool bound = false;
- for (uint32_t h = 0; h < m->handlers && !bound; ++h) bound = m->handler[h].event == 1 + 2 * i;
- if (!bound) continue;
- write_cstr(&help, key_symbol(m->input_keys[i]));
- write_cstr(&help, " ");
- write_cstr(&help, m->input_names[i]);
- write_cstr(&help, " ");
- }
- if (screen->prompt >= 0 && grid->height > 1) {
- Pen pen = {grid, grid->height - 1, 0, grid->width, typing ? VEGA_ACCENT : VEGA_MUTED, false, false};
- write_cstr(&pen, m->input_names[screen->prompt]);
- write_cstr(&pen, "> ");
- pen.tone = typing ? VEGA_NORMAL : VEGA_MUTED;
- /* Show the end of a long entry. */
- size_t start = 0;
- unsigned room = grid->width > pen.column + 1 ? grid->width - pen.column - 1 : 0;
- while (start < prompt_length && vega_text_width(prompt + start, prompt_length - start) > room) {
- uint32_t skip;
- start += vega_decode(prompt, prompt_length, start, &skip);
- }
- write_text(&pen, prompt + start, prompt_length - start);
- if (typing) { grid->cursor_row = (int)pen.row; grid->cursor_column = (int)pen.column; }
- }
-}
diff --git a/src/main.c b/src/main.c
deleted file mode 100644
index 82131ab..0000000
--- a/src/main.c
+++ /dev/null
@@ -1,604 +0,0 @@
-#define _DEFAULT_SOURCE
-#include "vega.h"
-#include "module-store.h"
-#include "store.h"
-#include <poll.h>
-#include <signal.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/ioctl.h>
-#include <termios.h>
-#include <time.h>
-#include <unistd.h>
-
-/* vega SURFACE MODULE [--store PATH]: one module, one screen, several surfaces.
- * check what the module seems to mean but does not meet (exit 1)
- * tui interactive terminal
- * tui --dump WxH the terminal's frames as text, driven by a script
- * tui --paint WxH the bytes each frame sends to the terminal, driven by a script
- * cli plain lines for pipes, driven by a script
- * 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
- * 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
- * name: aup watch app.aup -o app.pmod beside it makes a saved edit show at once.
- * Script commands, one per line: key KEYNAME | text HEX|- | type HEX | enter |
- * tab | frame [N] | point VIEW ROW | click LINE COLUMN | time MS | tick TIMER | swap MODULE | show. key presses and
- * releases the input bound to that key; text commits text to the screen's text
- * input; type, enter and tab act on the TUI's input line; point focuses a row
- * (0 first) of a list or table; click is a mouse click on the last shown TUI
- * frame (1 first, as terminals report it); time sets the time the next
- * events see (live surfaces use the clock) and tick ticks a timer (live
- * surfaces tick them on time); swap replaces the module as --watch does. */
-
-static PureModule modules[2]; /* the running one (app.module) and the next */
-static unsigned current;
-static PureApp app;
-static VegaScreen screen;
-static VegaGrid *drawn; /* the TUI's last frame, which a click lands on */
-static char entry[VEGA_TEXT];
-static size_t entry_length;
-static bool typing;
-
-/* The module file last read, and a digest of its bytes. A loaded module
- * keeps nothing of them, so one buffer, grown to the largest, serves every load. */
-static unsigned char *module_bytes;
-static size_t module_capacity;
-static uint64_t module_digest;
-
-/* Read a module file whole into module_bytes: its size, or -1. */
-static long read_module(const char *path) {
- FILE *file = fopen(path, "rb");
- size_t size = 0;
- bool ok = file != NULL;
- while (ok && !feof(file)) {
- if (size == module_capacity) {
- size_t capacity = module_capacity ? module_capacity * 2 : 1 << 16;
- unsigned char *grown = capacity <= (8u << 20) ? realloc(module_bytes, capacity) : NULL;
- if (!grown) { ok = false; break; }
- module_bytes = grown;
- module_capacity = capacity;
- }
- size += fread(module_bytes + size, 1, module_capacity - size, file);
- ok = !ferror(file);
- }
- if (file) fclose(file);
- if (!ok) return -1;
- uint64_t h = UINT64_C(0xcbf29ce484222325) ^ size;
- for (size_t i = 0; i < size; ++i) h = (h ^ module_bytes[i]) * UINT64_C(0x100000001b3);
- module_digest = h;
- return (long)size;
-}
-
-/* Decode the module that read_module read from path. */
-static bool decode(const char *path, long size, PureModule *module) {
- if (size < 0) { fprintf(stderr, "vega: cannot read %s\n", path); return false; }
- char error[128];
- if (pure_module_load(module_bytes, (size_t)size, module, error, sizeof(error)) != PURE_OK) {
- fprintf(stderr, "vega: %s: %s\n", path, error);
- return false;
- }
- return true;
-}
-
-static bool load_module(const char *path, PureModule *module) { return decode(path, read_module(path), module); }
-
-static void print_finding(void *path, const char *name, const char *message) {
- printf("%s: %s: %s\n", (const char *)path, name, message);
-}
-
-static bool load(const char *path, const char *store) {
- if (!load_module(path, &modules[current])) return false;
- if (store && pure_store_init(store) != PURE_OK) { fprintf(stderr, "vega: cannot open store %s\n", store); return false; }
- PureResult started = pure_app_start(&app, &modules[current], store ? pure_tree_data(store) : (PureData){0});
- if (started != PURE_OK) { fprintf(stderr, "vega: cannot start (%d)\n", (int)started); return false; }
- vega_screen(&screen, &modules[current]);
- typing = screen.prompt >= 0;
- return true;
-}
-
-/* Replace the running app's module with the one read from path, states
- * keeping their values by name; a module that does not load or replace
- * leaves the app as it was. */
-static bool swap(const char *path, long size) {
- PureModule *next = &modules[!current];
- if (!decode(path, size, next)) return false;
- PureResult replaced = pure_app_replace(&app, next);
- if (replaced != PURE_OK) {
- fprintf(stderr, "vega: keeping the running module: cannot replace it (%d)\n", (int)replaced);
- return false;
- }
- bool had_prompt = screen.prompt >= 0;
- current = !current;
- vega_screen(&screen, next);
- typing = screen.prompt >= 0 && (typing || !had_prompt);
- return true;
-}
-
-/* --watch: the module file's bytes when last read (their digest), so a
- * module written again is seen however soon and whatever its size or file
- * times. Read at most five times a second, however often asked; bytes that
- * do not load are not tried again. */
-static const char *watched;
-static uint64_t watched_digest;
-static struct timespec watched_at;
-
-static bool module_changed(void) {
- struct timespec now;
- clock_gettime(CLOCK_MONOTONIC, &now);
- if (!watched || (now.tv_sec - watched_at.tv_sec) * 1000000000L + now.tv_nsec - watched_at.tv_nsec < 200000000L)
- return false;
- watched_at = now;
- long size = read_module(watched);
- if (size < 0 || module_digest == watched_digest) return false; /* a missing file is looked for again */
- watched_digest = module_digest;
- return swap(watched, size);
-}
-
-static int key_input(const char *key) { return vega_key_input(app.module, key); }
-
-/* A key press and release; false when the app has no such key or the press failed. */
-static bool press(const char *key, int *input) {
- *input = key_input(key);
- return *input >= 0 && vega_press(&app, key);
-}
-
-static bool commit(const char *text, size_t length) { return vega_commit(&app, &screen, text, length); }
-
-static const VegaPart *part_named(const char *name) {
- for (unsigned i = 0; i < screen.count; ++i)
- if (screen.parts[i].kind != VEGA_FIELD && !strcmp(app.module->view_names[screen.parts[i].view], name))
- return &screen.parts[i];
- return NULL;
-}
-
-/* A mouse button on a line of the last frame: a click focuses the row there;
- * the wheel moves the focus of the list or table under it. */
-static bool mouse(unsigned button, unsigned line) {
- unsigned index;
- size_t item;
- if (!drawn || !vega_hit(drawn, line, &index, &item)) return false;
- const VegaPart *part = &screen.parts[index];
- if (button == 64 || button == 65) {
- size_t focus = vega_focus(&app, part), rows = vega_rows(&app, part);
- if (focus < rows) { /* else the wheel focuses the row under it */
- if (button == 64 ? !focus : focus + 1 >= rows) return false;
- item = button == 64 ? focus - 1 : focus + 1;
- }
- } else if (button) return false;
- return vega_point(&app, part, item);
-}
-
-static int hex(char c) { return c >= '0' && c <= '9' ? c - '0' : c >= 'a' && c <= 'f' ? c - 'a' + 10 : -1; }
-
-static size_t unhex(const char *digits, char *out, size_t size) {
- size_t length = 0;
- if (!strcmp(digits, "-")) return 0;
- for (size_t i = 0; digits[i] && digits[i + 1] && length < size; i += 2)
- if (hex(digits[i]) >= 0 && hex(digits[i + 1]) >= 0) out[length++] = (char)(hex(digits[i]) << 4 | hex(digits[i + 1]));
- return length;
-}
-
-/* ---- terminal ------------------------------------------------------- */
-
-typedef struct { char *bytes; size_t used, capacity; } Out;
-
-static void out_put(Out *out, const char *bytes, size_t size) {
- if (out->used + size > out->capacity) {
- size_t capacity = out->capacity ? out->capacity : 4096;
- while (capacity < out->used + size) capacity *= 2;
- char *grown = realloc(out->bytes, capacity);
- if (!grown) return;
- out->bytes = grown;
- out->capacity = capacity;
- }
- memcpy(out->bytes + out->used, bytes, size);
- out->used += size;
-}
-
-static void out_codepoint(Out *out, uint32_t c) {
- char b[4];
- size_t n = c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4;
- if (n == 1) b[0] = (char)c;
- else if (n == 2) { b[0] = (char)(0xC0 | c >> 6); b[1] = (char)(0x80 | (c & 0x3F)); }
- else if (n == 3) { b[0] = (char)(0xE0 | c >> 12); b[1] = (char)(0x80 | (c >> 6 & 0x3F)); b[2] = (char)(0x80 | (c & 0x3F)); }
- else {
- b[0] = (char)(0xF0 | c >> 18); b[1] = (char)(0x80 | (c >> 12 & 0x3F));
- b[2] = (char)(0x80 | (c >> 6 & 0x3F)); b[3] = (char)(0x80 | (c & 0x3F));
- }
- out_put(out, b, n);
-}
-
-static const char *const tone_names[VEGA_TONES] = {"normal", "muted", "accent", "good", "warning", "danger"};
-
-/* The grid as text, one line per row: '>' marks a focused row, and a row's
- * tone (of its first character) follows a tab when it is not normal. */
-static void dump(const VegaGrid *grid) {
- Out out = {0};
- for (unsigned r = 0; r < grid->height; ++r) {
- const VegaCell *line = grid->cells + (size_t)r * grid->width;
- bool focus = false;
- int tone = -1;
- size_t start = out.used, end;
- for (unsigned c = 0; c < grid->width; ++c) {
- focus = focus || line[c].focus;
- if (tone < 0 && line[c].ch && line[c].ch != ' ') tone = line[c].tone;
- }
- out_put(&out, focus ? ">" : "|", 1);
- for (unsigned c = 0; c < grid->width; ++c) {
- if (!line[c].ch) continue;
- out_codepoint(&out, line[c].ch);
- if (line[c].mark) out_codepoint(&out, line[c].mark);
- }
- end = out.used;
- while (end > start + 1 && out.bytes[end - 1] == ' ') --end;
- out.used = end;
- if (tone > 0) { out_put(&out, "\t", 1); out_put(&out, tone_names[tone], strlen(tone_names[tone])); }
- out_put(&out, "\n", 1);
- }
- fwrite(out.bytes, 1, out.used, stdout);
- free(out.bytes);
-}
-
-static const char *const sgr_tones[VEGA_TONES] = {"", ";2", ";1;36", ";32", ";33", ";31"};
-
-static bool same_cell(const VegaCell *a, const VegaCell *b) {
- return a->ch == b->ch && a->mark == b->mark && a->tone == b->tone && a->focus == b->focus && a->strong == b->strong;
-}
-
-/* The terminal bytes that turn what it shows into the grid: each run of
- * changed cells after a cursor move (a short unchanged gap stays in the run,
- * being cheaper than another move), everything after a resize. */
-static void paint(VegaGrid *grid, Out *out) {
- bool all = !grid->painted;
- int style = -1;
- char code[48];
- if (all) out_put(out, "\033[2J", 4);
- for (unsigned r = 0; r < grid->height; ++r) {
- const VegaCell *line = grid->cells + (size_t)r * grid->width, *old = grid->shown + (size_t)r * grid->width;
- for (unsigned c = 0; c < grid->width;) {
- if (!all && same_cell(&line[c], &old[c])) { ++c; continue; }
- unsigned start = c, end = c + 1;
- for (unsigned k = c + 1, gap = 0; k < grid->width && gap <= 6; ++k) {
- if (all || !same_cell(&line[k], &old[k])) { end = k + 1; gap = 0; }
- else ++gap;
- }
- while (start && !line[start].ch) --start; /* the second half of a wide character: from its first */
- out_put(out, code, (size_t)snprintf(code, sizeof(code), "\033[%u;%uH", r + 1, start + 1));
- for (unsigned k = start; k < end; ++k) {
- if (!line[k].ch) continue;
- int next = line[k].tone | line[k].focus << 4 | line[k].strong << 5;
- if (next != style) {
- out_put(out, code, (size_t)snprintf(code, sizeof(code), "\033[0%s%s%sm", line[k].strong ? ";1" : "",
- line[k].focus ? ";7" : "",
- sgr_tones[line[k].tone < VEGA_TONES ? line[k].tone : 0]));
- style = next;
- }
- out_codepoint(out, line[k].ch);
- if (line[k].mark) out_codepoint(out, line[k].mark);
- }
- c = end;
- }
- }
- if (style > 0) out_put(out, "\033[0m", 4);
- if (grid->cursor_row >= 0)
- out_put(out, code, (size_t)snprintf(code, sizeof(code), "\033[%d;%dH\033[?25h", grid->cursor_row + 1,
- grid->cursor_column + 1));
- else out_put(out, "\033[?25l", 6);
- memcpy(grid->shown, grid->cells, (size_t)grid->width * grid->height * sizeof(*grid->cells));
- grid->painted = true;
-}
-
-static void paint_terminal(VegaGrid *grid) {
- Out out = {0};
- paint(grid, &out);
- if (write(STDOUT_FILENO, out.bytes, out.used) < 0) { /* the terminal went away; the loop ends on read */ }
- free(out.bytes);
-}
-
-static struct termios saved_terminal;
-static bool raw_terminal;
-static volatile sig_atomic_t resized = 1;
-
-static void on_resize(int signal_number) { (void)signal_number; resized = 1; }
-
-static void restore_terminal(void) {
- if (!raw_terminal) return;
- tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_terminal);
- const char *leave = "\033[?1006l\033[?1000l\033[0m\033[?25h\033[?1049l";
- if (write(STDOUT_FILENO, leave, strlen(leave)) < 0) { /* nothing left to tell */ }
- raw_terminal = false;
-}
-
-static bool raw_mode(void) {
- if (!isatty(STDIN_FILENO) || !isatty(STDOUT_FILENO) || tcgetattr(STDIN_FILENO, &saved_terminal)) return false;
- struct termios t = saved_terminal;
- t.c_iflag &= ~(tcflag_t)(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
- t.c_oflag &= ~(tcflag_t)OPOST;
- t.c_cflag |= CS8;
- t.c_lflag &= ~(tcflag_t)(ECHO | ICANON | IEXTEN | ISIG);
- t.c_cc[VMIN] = 1;
- t.c_cc[VTIME] = 0;
- if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &t)) return false;
- raw_terminal = true;
- atexit(restore_terminal);
- const char *enter = "\033[?1049h\033[?25l\033[?1000h\033[?1006h"; /* mouse buttons, reported as SGR */
- return write(STDOUT_FILENO, enter, strlen(enter)) >= 0;
-}
-
-/* Whether the app takes Tab itself; otherwise Tab switches typing and keys. */
-static bool tab_switches(void) { return screen.prompt >= 0 && key_input("Tab") < 0; }
-
-/* One key from the terminal, as typed text or as a Pure key name. */
-static void handle_key(const char *name) {
- int input;
- if (!strcmp(name, "Tab") && tab_switches()) { typing = !typing; return; }
- if (typing && !strcmp(name, "Enter")) {
- if (entry_length && commit(entry, entry_length)) entry_length = 0;
- return;
- }
- if (typing && !strcmp(name, "Backspace")) {
- while (entry_length && ((unsigned char)entry[entry_length - 1] & 0xC0) == 0x80) --entry_length;
- if (entry_length) --entry_length;
- return;
- }
- press(name, &input);
-}
-
-static void handle_text(const char *bytes, size_t length) {
- if (typing) {
- if (entry_length + length < sizeof(entry)) { memcpy(entry + entry_length, bytes, length); entry_length += length; }
- return;
- }
- if (length != 1) return;
- char c = bytes[0], name[16];
- if (c == ' ') strcpy(name, "Space");
- else if (c >= 'a' && c <= 'z') snprintf(name, sizeof(name), "Key%c", c - 'a' + 'A');
- else if (c >= 'A' && c <= 'Z') snprintf(name, sizeof(name), "Key%c", c);
- else if (c >= '0' && c <= '9') snprintf(name, sizeof(name), "Digit%c", c);
- else return;
- handle_key(name);
-}
-
-/* Parse terminal input: escape sequences, control keys and UTF-8 text. */
-static bool handle_input(const unsigned char *in, size_t length) {
- static const struct { const char *sequence, *key; } sequences[] = {
- {"[A", "ArrowUp"}, {"[B", "ArrowDown"}, {"[C", "ArrowRight"}, {"[D", "ArrowLeft"}, {"[H", "Home"},
- {"[F", "End"}, {"OA", "ArrowUp"}, {"OB", "ArrowDown"}, {"OC", "ArrowRight"}, {"OD", "ArrowLeft"},
- {"OH", "Home"}, {"OF", "End"}, {"[1~", "Home"}, {"[7~", "Home"}, {"[4~", "End"}, {"[8~", "End"},
- {"[3~", "Delete"}, {"[5~", "PageUp"}, {"[6~", "PageDown"}};
- for (size_t at = 0; at < length;) {
- unsigned char c = in[at];
- if (c == 0x03 || c == 0x11) return false; /* Ctrl-C, Ctrl-Q */
- if (c == 0x1B && at + 2 < length && in[at + 1] == '[' && in[at + 2] == '<') {
- /* A mouse report: ESC [ < button ; column ; line, M on press, m on release. */
- unsigned fields[3] = {0, 0, 0}, field = 0;
- size_t end = at + 3;
- for (; end < length && field < 3; ++end) {
- if (in[end] >= '0' && in[end] <= '9') fields[field] = fields[field] * 10 + (unsigned)(in[end] - '0');
- else if (in[end] == ';') ++field;
- else break;
- }
- if (end < length && in[end] == 'M' && field == 2 && fields[2]) mouse(fields[0], fields[2] - 1);
- at = end + 1;
- continue;
- }
- if (c == 0x1B) {
- size_t matched = 0;
- for (size_t i = 0; i < sizeof(sequences) / sizeof(*sequences) && !matched; ++i) {
- size_t n = strlen(sequences[i].sequence);
- if (at + 1 + n <= length && !memcmp(in + at + 1, sequences[i].sequence, n)) {
- handle_key(sequences[i].key);
- matched = 1 + n;
- }
- }
- if (!matched) { handle_key("Escape"); matched = 1; }
- at += matched;
- continue;
- }
- if (c == '\r' || c == '\n') handle_key("Enter");
- else if (c == 0x7F || c == 0x08) handle_key("Backspace");
- else if (c == '\t') handle_key("Tab");
- else if (c >= 0x20) {
- size_t n = c < 0x80 ? 1 : c < 0xE0 ? 2 : c < 0xF0 ? 3 : 4;
- if (at + n > length) n = length - at;
- handle_text((const char *)in + at, n);
- at += n;
- continue;
- }
- ++at;
- }
- return true;
-}
-
-static int interactive(void) {
- if (!raw_mode()) { fputs("vega: the tui needs a terminal (or use --dump WxH)\n", stderr); return 2; }
- signal(SIGWINCH, on_resize);
- VegaGrid grid = {0};
- drawn = &grid;
- VegaTimers timers;
- vega_timers_start(&timers, app.module, vega_now());
- for (;;) {
- if (resized) {
- struct winsize size;
- resized = 0;
- if (!ioctl(STDOUT_FILENO, TIOCGWINSZ, &size) && size.ws_col && size.ws_row)
- vega_grid(&grid, size.ws_col, size.ws_row);
- else vega_grid(&grid, 80, 24);
- }
- /* Tick what is due, draw, then wait for input, a frame or the next tick. */
- bool ticked = false;
- long due = vega_timers_run(&timers, &app, vega_now(), &ticked);
- vega_draw(&grid, &app, &screen, entry, entry_length, typing);
- paint_terminal(&grid);
- struct pollfd wait = {STDIN_FILENO, POLLIN, 0};
- int timeout = screen.ticks ? 16 : watched ? 200 : -1;
- if (due >= 0 && (timeout < 0 || due < timeout)) timeout = (int)due;
- int ready = poll(&wait, 1, timeout);
- app.now = vega_now();
- if (ready == 0) {
- if (screen.ticks) pure_app_frame(&app);
- if (module_changed()) vega_timers_start(&timers, app.module, app.now);
- continue;
- }
- if (ready < 0) continue; /* a signal, such as a resize */
- unsigned char in[256];
- ssize_t got = read(STDIN_FILENO, in, sizeof(in));
- if (got <= 0 || !handle_input(in, (size_t)got)) break;
- }
- restore_terminal();
- drawn = NULL;
- vega_grid_free(&grid);
- return 0;
-}
-
-/* ---- scripts -------------------------------------------------------- */
-
-typedef enum { SURFACE_CLI, SURFACE_SAY, SURFACE_TUI, SURFACE_WEB } Surface;
-
-/* 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 shown = false;
- drawn = surface == SURFACE_TUI ? &grid : NULL;
- bool color = surface == SURFACE_CLI && isatty(STDOUT_FILENO) && !getenv("NO_COLOR");
- unsigned long counted = vega_evaluations();
- while (fgets(line, sizeof(line), stdin)) {
- char command[16] = "", argument[sizeof(line)] = "";
- line[strcspn(line, "\r\n")] = 0;
- if (sscanf(line, "%15s %65535s", command, argument) < 1) continue;
- bool acted = true, ok = true;
- int input = -1;
- const char *verb = "do that";
- if (!strcmp(command, "key")) {
- ok = press(argument, &input);
- if (input < 0) fprintf(stderr, "vega: no input answers %s\n", argument);
- } else if (!strcmp(command, "text")) {
- char text[VEGA_TEXT];
- ok = commit(text, unhex(argument, text, sizeof(text)));
- } else if (!strcmp(command, "type")) {
- entry_length += unhex(argument, entry + entry_length, sizeof(entry) - 1 - entry_length);
- acted = false;
- } else if (!strcmp(command, "enter")) {
- handle_key("Enter");
- } else if (!strcmp(command, "tab")) {
- handle_key("Tab");
- acted = false;
- } else if (!strcmp(command, "point")) {
- char name[PURE_MODULE_NAME];
- long row = -1;
- const VegaPart *part = sscanf(line, "%*s %31s %ld", name, &row) == 2 ? part_named(name) : NULL;
- verb = "point";
- if (!part) fprintf(stderr, "vega: no list or table %s\n", name);
- ok = part && row >= 0 && vega_point(&app, part, (size_t)row);
- } 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; }
- 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")) {
- /* The time the next events see: scripts replay any. */
- char *end;
- double now = strtod(argument, &end);
- acted = false;
- if (!*argument || *end || !(now >= 0 && now < 1e15)) fprintf(stderr, "vega: time takes milliseconds\n");
- else app.now = now;
- } else if (!strcmp(command, "tick")) {
- input = pure_module_input(app.module, argument);
- verb = "tick";
- if (input < 0 || app.module->input[input].kind != PURE_KIND_TIMER) {
- fprintf(stderr, "vega: no timer %s\n", argument);
- input = -1;
- ok = false;
- } else ok = pure_app_tick(&app, (uint32_t)input) == PURE_OK;
- } else if (!strcmp(command, "swap")) {
- acted = false;
- if (!swap(argument, read_module(argument))) fprintf(stderr, "vega: kept the running module\n");
- } else if (!strcmp(command, "frame")) {
- long count = *argument ? strtol(argument, NULL, 10) : 1;
- for (long i = 0; i < count && i < 600; ++i) ok = pure_app_frame(&app) == PURE_OK && ok;
- } else if (!strcmp(command, "show")) {
- acted = false;
- if (surface == SURFACE_CLI) vega_print(stdout, &app, &screen, color);
- else if (surface == SURFACE_SAY) vega_say(stdout, &app, &screen, true);
- else if ((shown = true) && painting) {
- Out out = {0};
- vega_draw(&grid, &app, &screen, entry, entry_length, typing);
- paint(&grid, &out);
- printf("paint %zu\n", out.used);
- fwrite(out.bytes, 1, out.used, stdout);
- putchar('\n');
- free(out.bytes);
- } else {
- vega_draw(&grid, &app, &screen, entry, entry_length, typing);
- dump(&grid);
- printf("cells %lu\n", vega_evaluations() - counted);
- }
- counted = vega_evaluations();
- } else {
- fprintf(stderr, "vega: unknown command %s\n", command);
- continue;
- }
- if (!ok && input >= 0) fprintf(stderr, "vega: %s failed\n", app.module->input_names[input]);
- if (surface == SURFACE_SAY && acted) {
- if (!ok) printf("Could not %s.\n", input >= 0 ? app.module->input_names[input] : verb);
- else vega_say(stdout, &app, &screen, false);
- }
- fflush(stdout);
- }
- drawn = NULL;
- vega_grid_free(&grid);
- return 0;
-}
-
-int main(int argc, char **argv) {
- unsigned width = 0, height = 0, port = 8080;
- 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) {
- if (!strcmp(argv[i], "--watch") && (!strcmp(argv[1], "tui") || !strcmp(argv[1], "web"))) {
- watch = true;
- --i; /* a flag without a value */
- continue;
- }
- if (i + 1 >= argc) usage = true;
- else if (!strcmp(argv[i], "--store")) store = argv[i + 1];
- 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")) &&
- sscanf(argv[i + 1], "%ux%u", &width, &height) == 2 && width && height && width <= 1000 && height <= 1000)
- dumped = true, painting = !strcmp(argv[i], "--paint");
- else usage = true;
- }
- if (usage || (dumped && strcmp(argv[1], "tui")) || (!strcmp(argv[1], "check") && argc != 3)) {
- 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 check MODULE.pmod\n", stderr);
- 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;
- 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) {
- watched = argv[2];
- watched_digest = module_digest; /* of what load read */
- }
- int status = surface == SURFACE_WEB ? vega_web(&app, &screen, listen_on, port, watched ? module_changed : NULL) :
- surface == SURFACE_TUI && !dumped ? interactive() : script(surface, width, height, painting);
- /* Frame changes are stored with events; keep the last ones too. */
- if (pure_app_save(&app) != PURE_OK) { fputs("vega: cannot save\n", stderr); status = status ? status : 1; }
- pure_app_stop(&app);
- return status;
-}
diff --git a/src/screen.c b/src/screen.c
deleted file mode 100644
index fdecaf4..0000000
--- a/src/screen.c
+++ /dev/null
@@ -1,229 +0,0 @@
-#define _POSIX_C_SOURCE 200809L
-#include "vega.h"
-#include "module-text.h"
-#include <math.h>
-#include <string.h>
-#include <time.h>
-
-double vega_now(void) {
- struct timespec t;
- clock_gettime(CLOCK_REALTIME, &t);
- return floor((double)t.tv_sec * 1000 + (double)t.tv_nsec / 1e6);
-}
-
-void vega_timers_start(VegaTimers *timers, const PureModule *m, double now) {
- for (uint32_t i = 0; i < m->inputs; ++i)
- timers->next[i] = m->input[i].kind == PURE_KIND_TIMER ? now + m->input[i].period : 0;
-}
-
-long vega_timers_run(VegaTimers *timers, PureApp *app, double now, bool *ticked) {
- const PureModule *m = app->module;
- double soonest = -1;
- for (uint32_t i = 0; i < m->inputs; ++i) {
- if (m->input[i].kind != PURE_KIND_TIMER) continue;
- if (timers->next[i] <= now) {
- app->now = now;
- pure_app_tick(app, i); /* a failed tick changes nothing, as any failed event */
- *ticked = true;
- timers->next[i] = now + m->input[i].period;
- }
- if (soonest < 0 || timers->next[i] - now < soonest) soonest = timers->next[i] - now;
- }
- return soonest < 0 ? -1 : (long)ceil(soonest);
-}
-
-static unsigned long evaluations;
-
-unsigned long vega_evaluations(void) { return evaluations; }
-
-static int column(const PureModule *m, uint32_t table, const char *name, uint8_t type) {
- int view = pure_module_column(m, table, name);
- return view >= 0 && m->view_type[view] == type ? view : -1;
-}
-
-void vega_screen(VegaScreen *screen, const PureModule *m) {
- memset(screen, 0, sizeof(*screen));
- screen->module = m;
- screen->title = pure_module_view(m, "title");
- if (screen->title >= 0 && m->view_type[screen->title] != PURE_VIEW_TEXT) screen->title = -1;
- screen->prompt = -1;
- for (uint32_t i = 0; i < m->inputs && screen->prompt < 0; ++i)
- if (m->input[i].kind == PURE_KIND_TEXT) screen->prompt = (int)i;
- for (uint32_t h = 0; h < m->handlers; ++h) screen->ticks = screen->ticks || m->handler[h].event == 0;
- /* A <table>_focus view belongs to its table, not to the fields. */
- bool focus_view[PURE_MODULE_VIEWS] = {false};
- for (uint32_t i = 0; i < m->views; ++i) {
- if (m->view_type[i] != PURE_VIEW_TABLE) continue;
- char name[PURE_MODULE_NAME + 8];
- snprintf(name, sizeof(name), "%s_focus", m->view_names[i]);
- int focus = pure_module_view(m, name);
- if (focus >= 0 && m->view_type[focus] == PURE_VIEW_NUM) focus_view[focus] = true;
- }
- for (uint32_t i = 0; i < m->views && screen->count < VEGA_PARTS; ++i) {
- if (m->view_table[i] || (int)i == screen->title || focus_view[i]) continue;
- VegaPart *part = &screen->parts[screen->count++];
- *part = (VegaPart){.kind = VEGA_FIELD, .view = i, .focus = -1, .pointer = -1, .label = -1, .tone = -1, .detail = -1,
- .badge = -1};
- if (m->view_type[i] != PURE_VIEW_TABLE) continue;
- char name[PURE_MODULE_NAME + 8];
- snprintf(name, sizeof(name), "%s_focus", m->view_names[i]);
- part->focus = pure_module_view(m, name);
- if (part->focus >= 0 && !focus_view[part->focus]) part->focus = -1;
- part->pointer = part->focus >= 0 ? pure_module_position(m, (uint32_t)part->focus) : -1;
- part->label = column(m, i, "label", PURE_VIEW_TEXT);
- part->tone = column(m, i, "tone", PURE_VIEW_NUM);
- part->kind = part->label >= 0 ? VEGA_LIST : VEGA_TABLE;
- if (part->kind == VEGA_LIST) {
- part->detail = column(m, i, "detail", PURE_VIEW_TEXT);
- part->badge = column(m, i, "badge", PURE_VIEW_TEXT);
- }
- for (uint32_t k = 0; k < m->views; ++k)
- if (m->view_table[k] == i + 1 && (int)k != part->tone) part->columns[part->column_count++] = k;
- }
-}
-
-static bool is_view(const PureModule *m, int view, uint8_t type) {
- return view >= 0 && m->view_type[view] == type;
-}
-
-unsigned vega_check(const PureModule *m, void (*report)(void *, const char *name, const char *message), void *context) {
- unsigned found = 0;
- char name[2 * PURE_MODULE_NAME + 8], message[256];
-#define FIND(...) do { snprintf(message, sizeof(message), __VA_ARGS__); report(context, name, message); ++found; } while (0)
- int title = pure_module_view(m, "title");
- snprintf(name, sizeof(name), "title");
- if (title >= 0 && m->view_type[title] != PURE_VIEW_TEXT) FIND("a title is text; this one shows as a field");
- for (uint32_t i = 0; i < m->views; ++i) {
- if (m->view_table[i]) continue;
- const char *view = m->view_names[i];
- size_t length = strlen(view);
- snprintf(name, sizeof(name), "%s", view);
- if (length > 6 && !strcmp(view + length - 6, "_focus")) {
- char table[PURE_MODULE_NAME];
- snprintf(table, sizeof(table), "%.*s", (int)(length - 6), view);
- int owner = pure_module_view(m, table);
- if (!is_view(m, owner, PURE_VIEW_TABLE))
- FIND("names no list or table %s; it shows as a field", table);
- else if (m->view_type[i] != PURE_VIEW_NUM) FIND("the focused row is a number; %s shows no focus", table);
- }
- if (m->view_type[i] != PURE_VIEW_TABLE) continue;
- int label = pure_module_column(m, i, "label"), tone = pure_module_column(m, i, "tone");
- bool list = is_view(m, label, PURE_VIEW_TEXT);
- unsigned shown = 0;
- for (uint32_t k = 0; k < m->views; ++k) {
- if (m->view_table[k] != i + 1) continue;
- const char *column = m->view_names[k];
- bool text = m->view_type[k] == PURE_VIEW_TEXT;
- snprintf(name, sizeof(name), "%s.%s", view, column);
- if ((int)k == label && !text) FIND("a list's label is text; %s shows as a table", view);
- else if ((int)k == tone && text) FIND("a tone is a number (vega.normal ... vega.danger); %s", list ?
- "the list ignores it" : "it shows as a column");
- else if (list && (!strcmp(column, "badge") || !strcmp(column, "detail")) && !text)
- FIND("a list's %s is text; this one is not shown", column);
- else if (list && (int)k != label && (int)k != tone && strcmp(column, "badge") && strcmp(column, "detail"))
- FIND("a list shows label, badge, detail and tone; this column is not shown");
- shown += (int)k != tone || text;
- }
- snprintf(name, sizeof(name), "%s", view);
- if (!list && !shown) FIND("a table with no column to show");
- }
- int prompt = -1;
- for (uint32_t i = 0; i < m->inputs; ++i) {
- if (m->input[i].kind != PURE_KIND_TEXT) continue;
- snprintf(name, sizeof(name), "%s", m->input_names[i]);
- if (prompt >= 0) FIND("only the first text input (%s) has a place to type into", m->input_names[prompt]);
- else prompt = (int)i;
- }
-#undef FIND
- return found;
-}
-
-typedef struct { char *out; size_t used; } Collect;
-
-static bool collect(void *context, PureSpan span) {
- Collect *c = context;
- size_t room = VEGA_TEXT - 1 - c->used;
- size_t take = span.size < room ? span.size : room;
- /* Never cut a character: back off to a UTF-8 boundary. */
- if (take < span.size) while (take && (span.data[take] & 0xC0) == 0x80) --take;
- if (take) memcpy(c->out + c->used, span.data, take);
- c->used += take;
- return true;
-}
-
-bool vega_text(PureApp *app, uint32_t view, size_t row, char out[VEGA_TEXT], size_t *length) {
- const PureModule *m = app->module;
- bool column = m->view_table[view] != 0;
- ++evaluations;
- *length = 0;
- out[0] = 0;
- if (m->view_type[view] == PURE_VIEW_NUM) {
- double value;
- if (!(column ? pure_app_cell(app, view, row, &value) : pure_app_view(app, view, &value))) return false;
- *length = pure_format_number(value, out);
- out[*length] = 0;
- return true;
- }
- Collect c = {out, 0};
- PureResult result = column ? pure_app_cell_text(app, view, row, collect, &c) : pure_app_view_text(app, view, collect, &c);
- out[c.used] = 0;
- *length = c.used;
- return result == PURE_OK;
-}
-
-bool vega_number(PureApp *app, uint32_t view, size_t row, double *out) {
- ++evaluations;
- return app->module->view_table[view] ? pure_app_cell(app, view, row, out) : pure_app_view(app, view, out);
-}
-
-size_t vega_rows(PureApp *app, const VegaPart *part) {
- return part->kind == VEGA_FIELD ? 0 : pure_app_rows(app, part->view);
-}
-
-size_t vega_focus(PureApp *app, const VegaPart *part) {
- size_t rows = vega_rows(app, part), row;
- double focus;
- if (part->focus < 0 || !rows || !vega_number(app, (uint32_t)part->focus, 0, &focus) || !(focus >= 0)) return 0;
- bool filtered = app->module->view_code[part->view].length > 0;
- size_t item = focus < (double)SIZE_MAX ? (size_t)focus : SIZE_MAX;
- if (pure_app_item_row(app, part->view, item, &row)) return row;
- return filtered ? VEGA_NO_ROW : rows - 1;
-}
-
-size_t vega_scroll(size_t first, size_t focus, size_t visible, size_t rows) {
- if (focus < rows && focus < first) first = focus;
- if (focus < rows && visible && focus >= first + visible) first = focus - visible + 1;
- if (first + visible > rows) first = rows > visible ? rows - visible : 0;
- return first;
-}
-
-int vega_key_input(const PureModule *m, const char *key) {
- for (uint32_t i = 0; i < m->inputs; ++i)
- if (m->input[i].kind == PURE_KIND_KEY && !strcmp(m->input_keys[i], key)) return (int)i;
- return -1;
-}
-
-bool vega_press(PureApp *app, const char *key) {
- int input = vega_key_input(app->module, key);
- if (input < 0) return false;
- bool ok = pure_app_input(app, (uint32_t)input, PURE_EDGE_DOWN) == PURE_OK;
- pure_app_input(app, (uint32_t)input, PURE_EDGE_UP);
- return ok;
-}
-
-bool vega_commit(PureApp *app, const VegaScreen *screen, const char *text, size_t length) {
- return screen->prompt >= 0 &&
- pure_app_text(app, (uint32_t)screen->prompt, (PureSpan){(const unsigned char *)text, length}) == PURE_OK;
-}
-
-bool vega_point(PureApp *app, const VegaPart *part, size_t row) {
- size_t item; /* a filtered list's row shows some item of the list */
- return part->pointer >= 0 && pure_app_row_item(app, part->view, row, &item) &&
- pure_app_point(app, (uint32_t)part->pointer, item) == PURE_OK;
-}
-
-VegaTone vega_tone(PureApp *app, const VegaPart *part, size_t row) {
- double tone;
- if (part->tone < 0 || !vega_number(app, (uint32_t)part->tone, row, &tone)) return VEGA_NORMAL;
- return tone >= 0 && tone < VEGA_TONES && tone == floor(tone) ? (VegaTone)tone : VEGA_NORMAL;
-}
diff --git a/src/symbols-gl.c b/src/symbols-gl.c
deleted file mode 100644
index 0e78efe..0000000
--- a/src/symbols-gl.c
+++ /dev/null
@@ -1,260 +0,0 @@
-#include "symbols-gl.h"
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-enum { BANDS = 16 };
-
-/* Coordinates travel doubled, so implied points stay whole: 2x of a point
- * is its raw i16 without the flag, and an implied point's is the sum of its
- * two neighbours' coordinates. */
-#define SHARED \
- "#version 310 es\n" \
- "precision highp float;\n" \
- "precision highp int;\n"
-
-static const char vertex_source[] = SHARED
- "layout(location = 0) in vec4 place; /* origin x, y in pixels; pixels per unit; unused */\n"
- "layout(location = 1) in vec4 bounds; /* left, bottom, right, top in units */\n"
- "layout(location = 2) in uint bands; /* its first band */\n"
- "uniform vec2 target;\n"
- "out vec2 units;\n"
- "flat out float scale;\n"
- "flat out ivec2 rows; /* bottom and top in units */\n"
- "flat out uint base;\n"
- "void main() {\n"
- " vec2 corner = vec2(float(gl_VertexID & 1), float(gl_VertexID >> 1));\n"
- " vec2 pad = vec2(1.0 / place.z);\n"
- " units = mix(bounds.xy - pad, bounds.zw + pad, corner);\n"
- " vec2 pixel = place.xy + vec2(units.x, -units.y) * place.z;\n"
- " gl_Position = vec4(pixel.x / target.x * 2.0 - 1.0, 1.0 - pixel.y / target.y * 2.0, 0.0, 1.0);\n"
- " scale = place.z;\n"
- " rows = ivec2(bounds.yw);\n"
- " base = bands;\n"
- "}\n";
-
-static const char fragment_source[] = SHARED
- "layout(std430, binding = 0) readonly buffer Points { uint points[]; };\n"
- "layout(std430, binding = 1) readonly buffer Bands { uint bands[]; };\n"
- "uniform uint listed; /* where the lists of curves start in bands */\n"
- "in vec2 units;\n"
- "flat in float scale;\n"
- "flat in ivec2 rows;\n"
- "flat in uint base;\n"
- "out vec4 color;\n"
- "\n"
- "ivec3 point(uint i) { /* doubled x, doubled y, off */\n"
- " uint w = points[i];\n"
- " int x = int(w << 16u) >> 16, y = int(w) >> 16;\n"
- " return ivec3(x & ~1, y & ~1, x & 1);\n"
- "}\n"
- "int band(int doubled) { /* which of 16 bands a doubled y lies in */\n"
- " return clamp((doubled - 2 * rows.x) * 16 / (2 * (rows.y - rows.x) + 2), 0, 15);\n"
- "}\n"
- "int roots(float a, float b, float c, inout float ts[10], int n) {\n"
- " float r0 = -1.0, r1 = -1.0;\n"
- " if (a == 0.0) { if (b != 0.0) r0 = -c / b; }\n"
- " else {\n"
- " float d = b * b - 4.0 * a * c;\n"
- " if (d >= 0.0) {\n"
- " float q = -0.5 * (b + (b >= 0.0 ? sqrt(d) : -sqrt(d)));\n"
- " r0 = q / a;\n"
- " if (q != 0.0) r1 = c / q;\n"
- " }\n"
- " }\n"
- " if (r0 > 0.0 && r0 < 1.0) ts[n++] = r0;\n"
- " if (r1 > 0.0 && r1 < 1.0) ts[n++] = r1;\n"
- " return n;\n"
- "}\n"
- "/* As curve_area in symbols.c: the curve's share of the pixel [0, 1]^2. */\n"
- "float area(vec2 p0, vec2 p1, vec2 p2) {\n"
- " if (max(max(p0.y, p1.y), p2.y) <= 0.0 || min(min(p0.y, p1.y), p2.y) >= 1.0 || min(min(p0.x, p1.x), p2.x) >= 1.0)\n"
- " return 0.0;\n"
- " if (max(max(p0.x, p1.x), p2.x) <= 0.0) return clamp(p2.y, 0.0, 1.0) - clamp(p0.y, 0.0, 1.0);\n"
- " vec2 a = p0 - 2.0 * p1 + p2, b = p0 - p1;\n"
- " float ts[10];\n"
- " ts[0] = 0.0;\n"
- " int n = 1;\n"
- " n = roots(a.y, -2.0 * b.y, p0.y, ts, n);\n"
- " n = roots(a.y, -2.0 * b.y, p0.y - 1.0, ts, n);\n"
- " n = roots(a.x, -2.0 * b.x, p0.x, ts, n);\n"
- " n = roots(a.x, -2.0 * b.x, p0.x - 1.0, ts, n);\n"
- " ts[n++] = 1.0;\n"
- " for (int i = 1; i < n; ++i)\n"
- " for (int j = i; j > 0 && ts[j] < ts[j - 1]; --j) { float t = ts[j]; ts[j] = ts[j - 1]; ts[j - 1] = t; }\n"
- " float u0 = 1.0 - p0.x, u1 = 2.0 * b.x, u2 = -a.x, v0 = -2.0 * b.y, v1 = 2.0 * a.y;\n"
- " float k1 = u0 * v0, k2 = (u0 * v1 + u1 * v0) / 2.0, k3 = (u1 * v1 + u2 * v0) / 3.0, k4 = u2 * v1 / 4.0, sum = 0.0;\n"
- " for (int i = 0; i + 1 < n; ++i) {\n"
- " float ta = ts[i], tb = ts[i + 1], t = (ta + tb) / 2.0;\n"
- " if (tb <= ta) continue;\n"
- " float y = (a.y * t - 2.0 * b.y) * t + p0.y, x = (a.x * t - 2.0 * b.x) * t + p0.x;\n"
- " if (y <= 0.0 || y >= 1.0 || x >= 1.0) continue;\n"
- " if (x <= 0.0) sum += (a.y * tb - 2.0 * b.y) * tb - (a.y * ta - 2.0 * b.y) * ta;\n"
- " else sum += (((k4 * tb + k3) * tb + k2) * tb + k1) * tb - (((k4 * ta + k3) * ta + k2) * ta + k1) * ta;\n"
- " }\n"
- " return sum;\n"
- "}\n"
- "void main() {\n"
- " vec2 corner = units - vec2(0.5 / scale); /* the pixel's lower left, in units */\n"
- " int b0 = band(2 * int(floor(corner.y))), b1 = band(2 * int(ceil(corner.y + 1.0 / scale)));\n"
- " float sum = 0.0;\n"
- " for (int b = b0; b <= b1; ++b) {\n"
- " uint at = bands[base + 2u * uint(b)], end = at + bands[base + 2u * uint(b) + 1u];\n"
- " for (; at < end; ++at) {\n"
- " uint k = bands[listed + at];\n"
- " ivec3 p = point(k), n = point(k + 1u);\n"
- " ivec2 s, c, e;\n"
- " if (p.z == 1) {\n"
- " ivec3 q = point(k - 1u);\n"
- " s = q.z == 1 ? (q.xy + p.xy) / 2 : q.xy;\n"
- " c = p.xy;\n"
- " e = n.z == 1 ? (p.xy + n.xy) / 2 : n.xy;\n"
- " } else {\n"
- " s = p.xy;\n"
- " c = (p.xy + n.xy) / 2;\n"
- " e = n.xy;\n"
- " }\n"
- " /* a curve over several of this pixel's bands counts in the first */\n"
- " if (max(band(min(min(s.y, c.y), e.y)), b0) != b) continue;\n"
- " sum += area((vec2(s) * 0.5 - corner) * scale, (vec2(c) * 0.5 - corner) * scale, (vec2(e) * 0.5 - corner) * scale);\n"
- " }\n"
- " }\n"
- " color = vec4(min(abs(sum), 1.0), 0.0, 0.0, 1.0);\n"
- "}\n";
-
-static GLuint compile(GLenum type, const char *source, char *error, size_t error_size) {
- GLuint shader = glCreateShader(type);
- glShaderSource(shader, 1, &source, NULL);
- glCompileShader(shader);
- GLint ok;
- glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
- if (!ok) {
- glGetShaderInfoLog(shader, (GLsizei)error_size, NULL, error);
- glDeleteShader(shader);
- return 0;
- }
- return shader;
-}
-
-/* Doubled coordinates make implied points whole; bands as the shader cuts them. */
-static int band(int doubled, int bottom, int top) {
- int b = (doubled - 2 * bottom) * BANDS / (2 * (top - bottom) + 2);
- return b < 0 ? 0 : b >= BANDS ? BANDS - 1 : b;
-}
-
-bool vega_gl_open(VegaGl *gl, const VegaSymbols *s, char *error, size_t error_size) {
- *gl = (VegaGl){0};
- GLuint vertex = compile(GL_VERTEX_SHADER, vertex_source, error, error_size);
- GLuint fragment = vertex ? compile(GL_FRAGMENT_SHADER, fragment_source, error, error_size) : 0;
- if (!fragment) return false;
- gl->program = glCreateProgram();
- glAttachShader(gl->program, vertex);
- glAttachShader(gl->program, fragment);
- glLinkProgram(gl->program);
- glDeleteShader(vertex);
- glDeleteShader(fragment);
- GLint ok;
- glGetProgramiv(gl->program, GL_LINK_STATUS, &ok);
- if (!ok) {
- glGetProgramInfoLog(gl->program, (GLsizei)error_size, NULL, error);
- return false;
- }
- /* The point table, as the file has it. */
- glGenBuffers(1, &gl->points);
- glBindBuffer(GL_SHADER_STORAGE_BUFFER, gl->points);
- glBufferData(GL_SHADER_STORAGE_BUFFER, (GLsizeiptr)(s->points ? s->points : 1) * VEGA_POINT_BYTES,
- s->bytes + VEGA_SYMBOLS_HEADER + (size_t)s->symbols * VEGA_SYMBOL_BYTES, GL_STATIC_DRAW);
- /* Bands: per symbol, 16 (start, count) pairs into one list of curves,
- * each curve (by the point that begins it) in every band it reaches. */
- size_t table = (size_t)s->symbols * BANDS * 2, listed = 0;
- uint32_t *counts = calloc(table + 1, sizeof(*counts));
- for (int pass = 0; pass < 2; ++pass) {
- uint32_t *bands = pass ? malloc((table + listed + 1) * sizeof(*bands)) : NULL;
- size_t next = 0;
- for (uint32_t i = 0; i < s->symbols; ++i) {
- VegaSymbol symbol = vega_symbol_at(s, i);
- uint32_t *mine = pass ? bands + (size_t)i * BANDS * 2 : counts + (size_t)i * BANDS * 2;
- if (pass)
- for (int b = 0; b < BANDS; ++b) {
- mine[2 * b] = (uint32_t)next;
- next += counts[(size_t)i * BANDS * 2 + 2 * b + 1];
- mine[2 * b + 1] = 0;
- }
- VegaCurve c;
- for (uint32_t k = symbol.first; k < symbol.first + symbol.count; ++k) {
- if (!vega_symbol_curve(s, &symbol, k, &c)) continue;
- float low = c.y0 < c.y1 ? c.y0 : c.y1, high = c.y0 > c.y1 ? c.y0 : c.y1;
- low = c.y2 < low ? c.y2 : low;
- high = c.y2 > high ? c.y2 : high;
- int from = band((int)(2 * low), symbol.bottom, symbol.top), to = band((int)(2 * high), symbol.bottom, symbol.top);
- for (int b = from; b <= to; ++b) {
- if (pass) bands[table + mine[2 * b] + mine[2 * b + 1]] = k;
- ++mine[2 * b + 1];
- listed += !pass;
- }
- }
- }
- if (pass) {
- glGenBuffers(1, &gl->bands);
- glBindBuffer(GL_SHADER_STORAGE_BUFFER, gl->bands);
- glBufferData(GL_SHADER_STORAGE_BUFFER, (GLsizeiptr)((table + listed + 1) * sizeof(*bands)), bands, GL_STATIC_DRAW);
- free(bands);
- }
- }
- free(counts);
- glGenVertexArrays(1, &gl->array);
- glGenBuffers(1, &gl->instances);
- glBindVertexArray(gl->array);
- glBindBuffer(GL_ARRAY_BUFFER, gl->instances);
- glEnableVertexAttribArray(0);
- glEnableVertexAttribArray(1);
- glEnableVertexAttribArray(2);
- glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 36, (void *)0);
- glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 36, (void *)16);
- glVertexAttribIPointer(2, 1, GL_UNSIGNED_INT, 36, (void *)32);
- glVertexAttribDivisor(0, 1);
- glVertexAttribDivisor(1, 1);
- glVertexAttribDivisor(2, 1);
- glBindVertexArray(0);
- glUseProgram(gl->program);
- glUniform1ui(glGetUniformLocation(gl->program, "listed"), (GLuint)table);
- gl->viewport = glGetUniformLocation(gl->program, "target");
- if (glGetError() != GL_NO_ERROR) {
- snprintf(error, error_size, "GL error while uploading");
- return false;
- }
- return true;
-}
-
-void vega_gl_draw(VegaGl *gl, const VegaSymbols *s, const VegaGlyph *glyphs, uint32_t count, int width, int height) {
- float *instances = malloc((size_t)(count ? count : 1) * 36);
- for (uint32_t i = 0; i < count; ++i) {
- VegaSymbol symbol = vega_symbol_at(s, glyphs[i].symbol);
- float *f = instances + (size_t)i * 9;
- f[0] = glyphs[i].x, f[1] = glyphs[i].y, f[2] = glyphs[i].scale, f[3] = 0;
- f[4] = symbol.left, f[5] = symbol.bottom, f[6] = symbol.right, f[7] = symbol.top;
- uint32_t base = glyphs[i].symbol * BANDS * 2;
- memcpy(f + 8, &base, 4);
- }
- glUseProgram(gl->program);
- glUniform2f(gl->viewport, (float)width, (float)height);
- glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, gl->points);
- glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, gl->bands);
- glBindVertexArray(gl->array);
- glBindBuffer(GL_ARRAY_BUFFER, gl->instances);
- glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)count * 36, instances, GL_STREAM_DRAW);
- glEnable(GL_BLEND);
- glBlendFunc(GL_ONE, GL_ONE);
- glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, (GLsizei)count);
- glBindVertexArray(0);
- free(instances);
-}
-
-void vega_gl_close(VegaGl *gl) {
- glDeleteBuffers(1, &gl->points);
- glDeleteBuffers(1, &gl->bands);
- glDeleteBuffers(1, &gl->instances);
- glDeleteVertexArrays(1, &gl->array);
- glDeleteProgram(gl->program);
- *gl = (VegaGl){0};
-}
diff --git a/src/symbols-gl.h b/src/symbols-gl.h
deleted file mode 100644
index 4c6db67..0000000
--- a/src/symbols-gl.h
+++ /dev/null
@@ -1,24 +0,0 @@
-#ifndef VEGA_SYMBOLS_GL_H
-#define VEGA_SYMBOLS_GL_H
-#include "symbols.h"
-#include <GLES3/gl31.h>
-
-/* Symbols drawn by the GPU (OpenGL ES 3.1): the file's point table goes up
- * as it is, one storage buffer, and each pixel of a symbol's box sums the
- * exact area of the curves crossing its row, as vega_coverage does. Rows
- * are cut into bands at upload, each listing its curves, so a pixel reads
- * only those. Needs a current context. */
-typedef struct {
- GLuint program, points, bands, instances, array;
- GLint viewport;
-} VegaGl;
-
-/* A symbol placed: its origin in pixels from the target's top left, and
- * pixels per unit. */
-typedef struct { float x, y, scale; uint32_t symbol; } VegaGlyph;
-
-bool vega_gl_open(VegaGl *, const VegaSymbols *, char *error, size_t error_size);
-/* Coverage added into the red channel of the bound framebuffer. */
-void vega_gl_draw(VegaGl *, const VegaSymbols *, const VegaGlyph *, uint32_t count, int width, int height);
-void vega_gl_close(VegaGl *);
-#endif
diff --git a/src/symbols-tool.c b/src/symbols-tool.c
deleted file mode 100644
index a94feb3..0000000
--- a/src/symbols-tool.c
+++ /dev/null
@@ -1,881 +0,0 @@
-#define _POSIX_C_SOURCE 200809L
-/* vega-symbols: build symbol files from fonts and SVG icons, and measure them
- * against FreeType (docs/design/002-symbol-format.md).
- * vega-symbols build [--font FONT.ttf --codes 0x20-0x7e,...] [--svg CODE=ICON.svg ...] [--units N] -o OUT.vsym
- * vega-symbols compare FONT.ttf OUT.vsym --sizes 12,16,32,64
- * vega-symbols info OUT.vsym
- * vega-symbols render OUT.vsym CODE PIXELS OUT.pgm
- * vega-symbols gpu OUT.vsym --sizes 12,16,32,64 [--frames N] */
-#include "symbols-gl.h"
-#include "symbols.h"
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <ft2build.h>
-#include FT_FREETYPE_H
-#include FT_OUTLINE_H
-#include <math.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <time.h>
-
-#define PI 3.14159265358979323846
-
-typedef struct { double x, y; } Point;
-/* An outline as the pen drew it. smooth: the end lies halfway between this
- * control and the next, by construction, so it may go unwritten. */
-typedef enum { MOVE, LINE, QUAD } Kind;
-typedef struct { Kind kind; bool smooth; Point control, end; } Segment;
-typedef struct { Segment *items; size_t count, capacity; Point at, start; bool open; double font_scale; } Path;
-typedef struct { uint32_t code; size_t first, count; int16_t advance; } Built; /* segments [first, first + count) */
-
-static void fail(const char *why) {
- fprintf(stderr, "vega-symbols: %s\n", why);
- exit(1);
-}
-
-static void add(Path *path, Segment segment) {
- if (path->count == path->capacity) {
- path->capacity = path->capacity ? 2 * path->capacity : 64;
- if (!(path->items = realloc(path->items, path->capacity * sizeof(*path->items)))) fail("out of memory");
- }
- path->items[path->count++] = segment;
- path->at = segment.end;
-}
-
-static bool same(Point a, Point b) { return a.x == b.x && a.y == b.y; }
-
-static void line_to(Path *path, Point to) {
- if (!same(to, path->at)) add(path, (Segment){LINE, false, to, to});
-}
-
-static void quad_to(Path *path, Point control, Point to) { add(path, (Segment){QUAD, false, control, to}); }
-
-static Point lerp(Point a, Point b, double t) { return (Point){a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t}; }
-static double length(double x, double y) { return sqrt(x * x + y * y); }
-
-/* Whether the cubic of these differences stays within tolerance of zero
- * (split in halves until its control points do). */
-static bool fits(Point p0, Point p1, Point p2, Point p3, double tolerance) {
- if (length(p1.x, p1.y) <= tolerance && length(p2.x, p2.y) <= tolerance) return true;
- Point mid = {(p0.x + 3 * (p1.x + p2.x) + p3.x) / 8, (p0.y + 3 * (p1.y + p2.y) + p3.y) / 8};
- if (length(mid.x, mid.y) > tolerance) return false;
- Point d = {(p3.x + p2.x - p1.x - p0.x) / 8, (p3.y + p2.y - p1.y - p0.y) / 8};
- return fits(p0, lerp(p0, p1, 0.5), (Point){mid.x - d.x, mid.y - d.y}, mid, tolerance) &&
- fits(mid, (Point){mid.x + d.x, mid.y + d.y}, lerp(p2, p3, 0.5), p3, tolerance);
-}
-
-/* The piece of cubic c over [a, b], as a cubic. */
-static void piece(const Point c[4], double a, double b, Point out[4]) {
- Point q[4];
- for (int k = 0; k < 2; ++k) {
- double t = k ? b : a, u = 1 - t;
- Point e = {u * u * u * c[0].x + 3 * u * u * t * c[1].x + 3 * u * t * t * c[2].x + t * t * t * c[3].x,
- u * u * u * c[0].y + 3 * u * u * t * c[1].y + 3 * u * t * t * c[2].y + t * t * t * c[3].y};
- Point d = {3 * (u * u * (c[1].x - c[0].x) + 2 * u * t * (c[2].x - c[1].x) + t * t * (c[3].x - c[2].x)),
- 3 * (u * u * (c[1].y - c[0].y) + 2 * u * t * (c[2].y - c[1].y) + t * t * (c[3].y - c[2].y))};
- double h = (b - a) / 3 * (k ? -1 : 1);
- q[3 * k] = e;
- q[1 + k] = (Point){e.x + d.x * h, e.y + d.y * h};
- }
- memcpy(out, q, sizeof(q));
-}
-
-/* A cubic as a quadratic spline whose joins lie halfway between neighbouring
- * controls (so they go unwritten), within tolerance: the approach of cu2qu
- * (fontTools). With n pieces, piece i takes its control on the line between
- * the points where the cubic's end tangents reach 1.5 times their control
- * arms, at i / (n - 1); the fewest pieces that fit win. */
-static bool spline(const Point c[4], int n, double tolerance, Point *controls) {
- if (n == 1) {
- /* One quadratic: its control where the end tangents meet. */
- Point a = {c[1].x - c[0].x, c[1].y - c[0].y}, b = {c[2].x - c[3].x, c[2].y - c[3].y};
- double cross = a.x * b.y - a.y * b.x;
- if (fabs(cross) < 1e-12) return false;
- double t = ((c[3].x - c[0].x) * b.y - (c[3].y - c[0].y) * b.x) / cross;
- controls[0] = (Point){c[0].x + a.x * t, c[0].y + a.y * t};
- Point q1 = lerp(c[0], controls[0], 2.0 / 3), q2 = lerp(c[3], controls[0], 2.0 / 3);
- return fits((Point){0, 0}, (Point){q1.x - c[1].x, q1.y - c[1].y}, (Point){q2.x - c[2].x, q2.y - c[2].y},
- (Point){0, 0}, tolerance);
- }
- Point from = c[0], before = {0, 0};
- for (int i = 0; i < n; ++i) {
- Point p[4];
- piece(c, (double)i / n, (double)(i + 1) / n, p);
- Point near = lerp(p[0], p[1], 1.5), far = lerp(p[3], p[2], 1.5);
- controls[i] = lerp(near, far, (double)i / (n - 1));
- }
- for (int i = 0; i < n; ++i) {
- Point p[4];
- piece(c, (double)i / n, (double)(i + 1) / n, p);
- Point to = i + 1 < n ? lerp(controls[i], controls[i + 1], 0.5) : c[3];
- Point after = {to.x - p[3].x, to.y - p[3].y};
- Point q1 = lerp(from, controls[i], 2.0 / 3), q2 = lerp(to, controls[i], 2.0 / 3);
- if (length(after.x, after.y) > tolerance ||
- !fits(before, (Point){q1.x - p[1].x, q1.y - p[1].y}, (Point){q2.x - p[2].x, q2.y - p[2].y}, after, tolerance))
- return false;
- from = to;
- before = after;
- }
- return true;
-}
-
-static void cubic_to(Path *path, Point c1, Point c2, Point to) {
- Point c[4] = {path->at, c1, c2, to}, controls[64];
- if (same(c[0], c1) && same(c1, c2) && same(c2, to)) return;
- for (int n = 1; n <= 64; ++n)
- if (spline(c, n, 1, controls)) {
- for (int i = 0; i < n; ++i)
- add(path, (Segment){QUAD, i + 1 < n, controls[i], i + 1 < n ? lerp(controls[i], controls[i + 1], 0.5) : to});
- return;
- }
- fail("a cubic that 64 quadratics do not fit");
-}
-
-static void close_path(Path *path) {
- if (path->open) {
- line_to(path, path->start);
- /* A contour of a move alone draws nothing: drop it. */
- if (path->count && path->items[path->count - 1].kind == MOVE) --path->count;
- }
- path->open = false;
-}
-
-static void move_to(Path *path, Point to) {
- close_path(path);
- add(path, (Segment){MOVE, false, to, to});
- path->start = to;
- path->open = true;
-}
-
-/* ---- Fonts, through FreeType's outline walk (in its units, times font_scale). */
-
-static Point point_of(const Path *path, const FT_Vector *v) {
- return (Point){(double)v->x * path->font_scale, (double)v->y * path->font_scale};
-}
-static int ft_move(const FT_Vector *to, void *user) { move_to(user, point_of(user, to)); return 0; }
-static int ft_line(const FT_Vector *to, void *user) { line_to(user, point_of(user, to)); return 0; }
-static int ft_conic(const FT_Vector *c, const FT_Vector *to, void *user) {
- quad_to(user, point_of(user, c), point_of(user, to));
- return 0;
-}
-static int ft_cubic(const FT_Vector *c1, const FT_Vector *c2, const FT_Vector *to, void *user) {
- cubic_to(user, point_of(user, c1), point_of(user, c2), point_of(user, to));
- return 0;
-}
-
-/* ---- SVG icons: the d of each path, the viewBox's height as the em. */
-
-static const char *number(const char *p, double *out) {
- while (*p == ' ' || *p == ',' || *p == '\n' || *p == '\t' || *p == '\r') ++p;
- char *end;
- *out = strtod(p, &end);
- return end == p ? NULL : end;
-}
-
-/* An arc's flags are single digits, perhaps with no space between them. */
-static const char *flag(const char *p, double *out) {
- while (*p == ' ' || *p == ',' || *p == '\n' || *p == '\t' || *p == '\r') ++p;
- if (*p != '0' && *p != '1') return NULL;
- *out = *p - '0';
- return p + 1;
-}
-
-typedef struct { double x, y, width, height, scale; } Box;
-
-static Point svg_point(const Box *box, double x, double y) {
- return (Point){(x - box->x) * box->scale, (box->y + box->height - y) * box->scale};
-}
-
-/* An elliptical arc from (x1, y1) to (x2, y2) as cubics of at most a quarter
- * turn each (SVG 1.1, F.6.5: from its endpoints to its center). */
-static void arc_to(Path *path, const Box *box, double x1, double y1, double rx, double ry, double degrees, bool large,
- bool sweep, double x2, double y2) {
- rx = fabs(rx);
- ry = fabs(ry);
- if ((x1 == x2 && y1 == y2)) return;
- if (!rx || !ry) {
- line_to(path, svg_point(box, x2, y2));
- return;
- }
- double phi = degrees * PI / 180, cp = cos(phi), sp = sin(phi);
- double hx = (x1 - x2) / 2, hy = (y1 - y2) / 2, px = cp * hx + sp * hy, py = -sp * hx + cp * hy;
- double lambda = px * px / (rx * rx) + py * py / (ry * ry);
- if (lambda > 1) { rx *= sqrt(lambda); ry *= sqrt(lambda); }
- double num = rx * rx * ry * ry - rx * rx * py * py - ry * ry * px * px, den = rx * rx * py * py + ry * ry * px * px;
- double k = (large == sweep ? -1 : 1) * sqrt(fmax(num / den, 0));
- double ccx = k * rx * py / ry, ccy = -k * ry * px / rx;
- double cx = cp * ccx - sp * ccy + (x1 + x2) / 2, cy = sp * ccx + cp * ccy + (y1 + y2) / 2;
- double start = atan2((py - ccy) / ry, (px - ccx) / rx), turn = atan2((-py - ccy) / ry, (-px - ccx) / rx) - start;
- if (sweep && turn < 0) turn += 2 * PI;
- if (!sweep && turn > 0) turn -= 2 * PI;
- int n = (int)ceil(fabs(turn) / (PI / 2) - 1e-9);
- double step = turn / n, kappa = 4.0 / 3 * tan(step / 4);
- for (int i = 0; i < n; ++i) {
- double a = start + i * step, b = a + step;
- double u[4][2] = {{cos(a), sin(a)}, {cos(a) - kappa * sin(a), sin(a) + kappa * cos(a)},
- {cos(b) + kappa * sin(b), sin(b) - kappa * cos(b)}, {cos(b), sin(b)}};
- Point q[4];
- for (int j = 0; j < 4; ++j)
- q[j] = svg_point(box, cx + rx * cp * u[j][0] - ry * sp * u[j][1], cy + rx * sp * u[j][0] + ry * cp * u[j][1]);
- if (i == n - 1) q[3] = svg_point(box, x2, y2);
- cubic_to(path, q[1], q[2], q[3]);
- }
-}
-
-static void svg_path(Path *path, const Box *box, const char *d) {
- double cx = 0, cy = 0, sx = 0, sy = 0, lx = 0, ly = 0; /* current, subpath start, last control */
- char command = 0, last = 0;
- for (const char *p = d; *p;) {
- while (*p == ' ' || *p == ',' || *p == '\n' || *p == '\t' || *p == '\r') ++p;
- if (!*p) break;
- if (strchr("MmLlHhVvQqTtCcSsZzAa", *p)) command = *p++;
- else if (!command) fail("SVG path data without a command");
- bool relative = command >= 'a';
- double ox = relative ? cx : 0, oy = relative ? cy : 0, v[7];
- char upper = (char)(command & ~32);
- int want = upper == 'M' || upper == 'L' || upper == 'T' ? 2 : upper == 'H' || upper == 'V' ? 1 :
- upper == 'Q' || upper == 'S' ? 4 : upper == 'C' ? 6 : upper == 'A' ? 7 : 0;
- for (int i = 0; i < want; ++i)
- if (!(p = (upper == 'A' && (i == 3 || i == 4) ? flag : number)(p, &v[i]))) fail("SVG path data");
- switch (upper) {
- case 'Z':
- close_path(path);
- cx = sx; cy = sy;
- path->at = svg_point(box, cx, cy);
- command = 0; /* a number after closepath is an error */
- break;
- case 'A': {
- double x = ox + v[5], y = oy + v[6];
- arc_to(path, box, cx, cy, v[0], v[1], v[2], v[3] != 0, v[4] != 0, x, y);
- cx = x; cy = y;
- break;
- }
- case 'M':
- cx = ox + v[0]; cy = oy + v[1]; sx = cx; sy = cy;
- move_to(path, svg_point(box, cx, cy));
- command = relative ? 'l' : 'L'; /* pairs after a moveto are linetos */
- break;
- case 'L': cx = ox + v[0]; cy = oy + v[1]; line_to(path, svg_point(box, cx, cy)); break;
- case 'H': cx = ox + v[0]; line_to(path, svg_point(box, cx, cy)); break;
- case 'V': cy = (relative ? cy : 0) + v[0]; line_to(path, svg_point(box, cx, cy)); break;
- case 'Q':
- lx = ox + v[0]; ly = oy + v[1]; cx = ox + v[2]; cy = oy + v[3];
- quad_to(path, svg_point(box, lx, ly), svg_point(box, cx, cy));
- break;
- case 'T':
- lx = last == 'Q' || last == 'T' ? 2 * cx - lx : cx; ly = last == 'Q' || last == 'T' ? 2 * cy - ly : cy;
- cx = ox + v[0]; cy = oy + v[1];
- quad_to(path, svg_point(box, lx, ly), svg_point(box, cx, cy));
- break;
- case 'C': case 'S': {
- double x1, y1;
- if (upper == 'C') { x1 = ox + v[0]; y1 = oy + v[1]; }
- else { x1 = last == 'C' || last == 'S' ? 2 * cx - lx : cx; y1 = last == 'C' || last == 'S' ? 2 * cy - ly : cy; }
- int at = upper == 'C' ? 2 : 0;
- lx = ox + v[at]; ly = oy + v[at + 1];
- double x = ox + v[at + 2], y = oy + v[at + 3];
- cubic_to(path, svg_point(box, x1, y1), svg_point(box, lx, ly), svg_point(box, x, y));
- cx = x; cy = y;
- break;
- }
- }
- last = upper;
- }
- close_path(path);
-}
-
-static char *read_file(const char *name, size_t *size) {
- FILE *f = fopen(name, "rb");
- if (!f) return NULL;
- fseek(f, 0, SEEK_END);
- long length = ftell(f);
- fseek(f, 0, SEEK_SET);
- char *bytes = length >= 0 ? malloc((size_t)length + 1) : NULL;
- if (bytes && fread(bytes, 1, (size_t)length, f) != (size_t)length) { free(bytes); bytes = NULL; }
- fclose(f);
- if (bytes) { bytes[length] = 0; *size = (size_t)length; }
- return bytes;
-}
-
-static const char *attribute(const char *from, const char *end, const char *name) {
- size_t length = strlen(name);
- for (const char *p = from; p && p < end; ++p) {
- p = strstr(p, name);
- if (!p || p >= end) return NULL;
- if ((p == from || p[-1] == ' ' || p[-1] == '\n' || p[-1] == '\t') && p[length] == '=' &&
- (p[length + 1] == '"' || p[length + 1] == '\'')) return p + length + 2;
- }
- return NULL;
-}
-
-/* Only solid, nonzero-filled paths: anything else an icon asks for would be
- * drawn wrong, so it is refused rather than dropped. */
-static const char *const unsupported[] = {"transform", "opacity", "evenodd", "stroke", "fill=\"none\"", "<use",
- "<rect", "<circle", "<ellipse", "<poly", "<line", "<radial", "<clip",
- "<mask", "<image", "<text", "<filter", "<pattern"};
-
-static void svg_icon(Path *path, const char *file, uint16_t units, int16_t *advance) {
- size_t size;
- char *svg = read_file(file, &size), why[512];
- if (!svg) fail("cannot read an SVG file");
- for (size_t i = 0; i < sizeof(unsupported) / sizeof(*unsupported); ++i)
- if (strstr(svg, unsupported[i])) {
- snprintf(why, sizeof(why), "%s: icons hold only solid paths, not %s", file, unsupported[i]);
- fail(why);
- }
- const char *tag = strstr(svg, "<svg"), *tag_end = tag ? strchr(tag, '>') : NULL, *view = tag ? attribute(tag, tag_end, "viewBox") : NULL;
- /* Without a viewBox, the width and height (in px) are the user space. */
- Box box = {0};
- const char *width = tag ? attribute(tag, tag_end, "width") : NULL, *height = tag ? attribute(tag, tag_end, "height") : NULL;
- if (view ? !(view = number(view, &box.x)) || !(view = number(view, &box.y)) || !(view = number(view, &box.width)) ||
- !number(view, &box.height)
- : !width || !height || !number(width, &box.width) || !number(height, &box.height) || box.height <= 0) {
- snprintf(why, sizeof(why), "%s: an icon needs a viewBox, or a width and height", file);
- fail(why);
- }
- box.scale = units / box.height;
- *advance = (int16_t)lround(box.width * box.scale);
- for (const char *p = svg; (p = strstr(p, "<path"));) {
- const char *end = strchr(p, '>'), *d = attribute(p, end, "d");
- if (d) {
- char quote = d[-1];
- const char *close = strchr(d, quote);
- char *data = strndup(d, (size_t)(close - d));
- svg_path(path, &box, data);
- free(data);
- }
- p = end ? end : p + 5;
- }
- free(svg);
-}
-
-/* ---- Writing. */
-
-typedef struct { long x, y; bool off, move; } Emitted;
-typedef struct { Emitted *items; size_t count, capacity; } Points;
-
-static void put16(unsigned char *p, int v) { p[0] = (unsigned char)v; p[1] = (unsigned char)((unsigned)v >> 8); }
-static void put32(unsigned char *p, uint32_t v) { for (int i = 0; i < 4; ++i) p[i] = (unsigned char)(v >> (8 * i)); }
-
-static void emit(Points *points, Point at, bool off, bool move) {
- if (points->count == points->capacity) {
- points->capacity = points->capacity ? 2 * points->capacity : 256;
- if (!(points->items = realloc(points->items, points->capacity * sizeof(*points->items)))) fail("out of memory");
- }
- points->items[points->count++] = (Emitted){lround(at.x), lround(at.y), off, move};
-}
-
-/* Whether s ends where its control and the next curve's would imply. */
-static bool implied(const Segment *s, const Segment *next) {
- if (s->kind != QUAD || !next || next->kind != QUAD) return false;
- return s->smooth || (2 * lround(s->end.x) == lround(s->control.x) + lround(next->control.x) &&
- 2 * lround(s->end.y) == lround(s->control.y) + lround(next->control.y));
-}
-
-/* One symbol's segments as points: each contour an on point that starts it,
- * then per segment its control (if a curve) and its end, unless the next
- * curve's control implies it; the last end, back at the start, is written.
- * A contour that starts at an implied point (as FreeType starts TrueType's
- * when its first point is off) starts instead at its first written one. */
-static void emit_symbol(Points *points, const Segment *segments, size_t count) {
- for (size_t i = 0; i < count;) {
- size_t end = i + 1;
- while (end < count && segments[end].kind != MOVE) ++end;
- const Segment *body = segments + i + 1;
- size_t n = end - i - 1, from = 0;
- if (n > 1 && implied(&body[n - 1], &body[0]))
- for (size_t k = 0; k + 1 < n; ++k)
- if (!implied(&body[k], &body[k + 1])) { from = k + 1; break; }
- emit(points, from ? body[from - 1].end : segments[i].end, false, true);
- for (size_t m = 0; m < n; ++m) {
- const Segment *s = &body[(from + m) % n], *next = m + 1 < n ? &body[(from + m + 1) % n] : NULL;
- const Emitted *last = &points->items[points->count - 1];
- if (s->kind == LINE) {
- if (last->off || last->x != lround(s->end.x) || last->y != lround(s->end.y)) emit(points, s->end, false, false);
- continue;
- }
- emit(points, s->control, true, false);
- if (!implied(s, next)) emit(points, s->end, false, false);
- }
- i = end;
- }
-}
-
-static int by_code(const void *a, const void *b) {
- uint32_t x = ((const Built *)a)->code, y = ((const Built *)b)->code;
- return (x > y) - (x < y);
-}
-
-static void write_symbols(const char *out, uint16_t units, Built *built, size_t count, const Path *path) {
- qsort(built, count, sizeof(*built), by_code);
- for (size_t i = 1; i < count; ++i) if (built[i].code == built[i - 1].code) fail("a code is given twice");
- Points points = {0};
- size_t *first = calloc(count + 1, sizeof(*first));
- for (size_t i = 0; i < count; ++i) {
- first[i] = points.count;
- emit_symbol(&points, path->items + built[i].first, built[i].count);
- }
- first[count] = points.count;
- /* Halve units while every coordinate is even: a TrueType font's own. */
- for (;;) {
- bool even = units % 2 == 0 && units > 16;
- for (size_t i = 0; even && i < points.count; ++i) even = points.items[i].x % 2 == 0 && points.items[i].y % 2 == 0;
- for (size_t i = 0; even && i < count; ++i) even = built[i].advance % 2 == 0;
- if (!even) break;
- for (size_t i = 0; i < points.count; ++i) { points.items[i].x /= 2; points.items[i].y /= 2; }
- for (size_t i = 0; i < count; ++i) built[i].advance = (int16_t)(built[i].advance / 2);
- units /= 2;
- }
- size_t size = VEGA_SYMBOLS_HEADER + count * VEGA_SYMBOL_BYTES + points.count * VEGA_POINT_BYTES;
- unsigned char *bytes = calloc(1, size);
- if (!bytes) fail("out of memory");
- memcpy(bytes, "VEGASYM2", 8);
- put16(bytes + 8, units);
- put32(bytes + 12, (uint32_t)count);
- put32(bytes + 16, (uint32_t)points.count);
- put32(bytes + 20, (uint32_t)size);
- unsigned char *at = bytes + VEGA_SYMBOLS_HEADER + count * VEGA_SYMBOL_BYTES;
- for (size_t i = 0; i < points.count; ++i) {
- const Emitted *e = &points.items[i];
- if (e->x < -16384 || e->x > 16383 || e->y < -16384 || e->y > 16383) fail("a coordinate does not fit 15 bits");
- put16(at + 4 * i, (int)(2 * e->x + e->off));
- put16(at + 4 * i + 2, (int)(2 * e->y + e->move));
- }
- for (size_t i = 0; i < count; ++i) {
- long left = 0, bottom = 0, right = 0, top = 0;
- for (size_t k = first[i]; k < first[i + 1]; ++k) {
- const Emitted *e = &points.items[k];
- if (k == first[i]) { left = right = e->x; bottom = top = e->y; }
- left = e->x < left ? e->x : left;
- right = e->x > right ? e->x : right;
- bottom = e->y < bottom ? e->y : bottom;
- top = e->y > top ? e->y : top;
- }
- unsigned char *p = bytes + VEGA_SYMBOLS_HEADER + i * VEGA_SYMBOL_BYTES;
- put32(p, built[i].code);
- put32(p + 4, (uint32_t)first[i]);
- put32(p + 8, (uint32_t)(first[i + 1] - first[i]));
- put16(p + 12, (int)left);
- put16(p + 14, (int)bottom);
- put16(p + 16, (int)right);
- put16(p + 18, (int)top);
- put16(p + 20, built[i].advance);
- }
- FILE *f = fopen(out, "wb");
- if (!f || fwrite(bytes, 1, size, f) != size || fclose(f)) fail("cannot write the symbol file");
- char error[128];
- VegaSymbols check;
- if (!vega_symbols_open(&check, bytes, size, error, sizeof(error))) fail(error);
- size_t curves = 0;
- VegaCurve curve;
- for (uint32_t i = 0; i < check.symbols; ++i) {
- VegaSymbol symbol = vega_symbol_at(&check, i);
- for (uint32_t k = symbol.first; k < symbol.first + symbol.count; ++k) curves += vega_symbol_curve(&check, &symbol, k, &curve);
- }
- printf("{\"symbols\":%zu,\"points\":%zu,\"curves\":%zu,\"units\":%u,\"bytes\":%zu}\n", count, points.count, curves,
- units, size);
- free(bytes);
- free(first);
- free(points.items);
-}
-
-/* 0x20-0x7e,0xac00 as a list of codes. */
-static size_t codes_of(const char *text, uint32_t **out) {
- size_t count = 0, capacity = 0;
- *out = NULL;
- for (const char *p = text; *p;) {
- char *end;
- unsigned long first = strtoul(p, &end, 0), last = first;
- if (end == p) fail("--codes takes FIRST[-LAST],...");
- p = end;
- if (*p == '-') { last = strtoul(p + 1, &end, 0); p = end; }
- for (unsigned long c = first; c <= last; ++c) {
- if (count == capacity && !(*out = realloc(*out, (capacity = capacity ? 2 * capacity : 256) * sizeof(**out))))
- fail("out of memory");
- (*out)[count++] = (uint32_t)c;
- }
- if (*p == ',') ++p;
- }
- return count;
-}
-
-static double seconds(void) {
- struct timespec t;
- clock_gettime(CLOCK_MONOTONIC, &t);
- return (double)t.tv_sec + (double)t.tv_nsec / 1e9;
-}
-
-#define BUILD_USAGE "usage: vega-symbols build [--font FONT.ttf --codes 0x20-0x7e,...] [--svg CODE=ICON.svg ...] [--units N] -o OUT.vsym"
-
-static int build(int argc, char **argv) {
- const char *font = NULL, *out = NULL, *codes = NULL, **icons = calloc((size_t)argc, sizeof(*icons));
- size_t icon_count = 0;
- long units = 0;
- for (int i = 2; i < argc; i += 2) {
- if (i + 1 == argc) fail(BUILD_USAGE);
- if (!strcmp(argv[i], "-o")) out = argv[i + 1];
- else if (!strcmp(argv[i], "--font")) font = argv[i + 1];
- else if (!strcmp(argv[i], "--codes")) codes = argv[i + 1];
- else if (!strcmp(argv[i], "--units")) units = strtol(argv[i + 1], NULL, 10);
- else if (!strcmp(argv[i], "--svg")) icons[icon_count++] = argv[i + 1];
- else fail(BUILD_USAGE);
- }
- if (!out || !font != !codes) fail(BUILD_USAGE);
- FT_Library library = NULL;
- FT_Face face = NULL;
- if (font && (FT_Init_FreeType(&library) || FT_New_Face(library, font, 0, &face))) fail("cannot open the font");
- /* Twice a font's units by default: TrueType's implied points, halfway
- * between two control points, then land on whole units. (FreeType finds
- * them by integer halving, so outlines are doubled before it walks them.) */
- if (!units) units = face ? 2 * face->units_per_EM : 4096;
- if (units < 16 || units > 16384) fail("--units: 16 to 16384");
- uint32_t *list = NULL;
- size_t count = codes ? codes_of(codes, &list) : 0;
- Built *built = calloc(count + icon_count + 1, sizeof(*built));
- Path path = {.font_scale = face ? (double)units / (2.0 * face->units_per_EM) : 1};
- size_t made = 0;
- FT_Outline_Funcs walk = {ft_move, ft_line, ft_conic, ft_cubic, 0, 0};
- for (size_t i = 0; i < count; ++i) {
- FT_UInt glyph = FT_Get_Char_Index(face, list[i]);
- if (!glyph || FT_Load_Glyph(face, glyph, FT_LOAD_NO_SCALE)) continue; /* the font lacks it */
- size_t first = path.count;
- path.open = false;
- if (face->glyph->format == FT_GLYPH_FORMAT_OUTLINE) {
- FT_Outline_Transform(&face->glyph->outline, &(FT_Matrix){0x20000, 0, 0, 0x20000});
- if (FT_Outline_Decompose(&face->glyph->outline, &walk, &path)) fail("cannot walk an outline");
- }
- close_path(&path);
- long advance = lround((double)face->glyph->metrics.horiAdvance * units / face->units_per_EM);
- built[made++] = (Built){list[i], first, path.count - first, (int16_t)(advance > INT16_MAX ? INT16_MAX : advance)};
- }
- for (size_t i = 0; i < icon_count; ++i) {
- char *equals = strchr(icons[i], '=');
- if (!equals) fail("--svg takes CODE=FILE");
- size_t first = path.count;
- path.open = false;
- int16_t advance;
- svg_icon(&path, equals + 1, (uint16_t)units, &advance);
- built[made++] = (Built){(uint32_t)strtoul(icons[i], NULL, 0), first, path.count - first, advance};
- }
- write_symbols(out, (uint16_t)units, built, made, &path);
- if (face) FT_Done_Face(face);
- if (library) FT_Done_FreeType(library);
- free(icons);
- free(list);
- free(built);
- free(path.items);
- return 0;
-}
-
-static VegaSymbols open_symbols(const char *name, char **bytes) {
- size_t size;
- char error[128];
- VegaSymbols s;
- if (!(*bytes = read_file(name, &size))) fail("cannot read the symbol file");
- if (!vega_symbols_open(&s, (unsigned char *)*bytes, size, error, sizeof(error))) fail(error);
- return s;
-}
-
-static volatile long sink; /* keeps the timed loops' reads */
-static int ignore_point(const FT_Vector *to, void *user) { ++*(long *)user; (void)to; return 0; }
-static int ignore_conic(const FT_Vector *c, const FT_Vector *to, void *user) { (void)c; return ignore_point(to, user); }
-static int ignore_cubic(const FT_Vector *c1, const FT_Vector *c2, const FT_Vector *to, void *user) {
- (void)c1;
- (void)c2;
- return ignore_point(to, user);
-}
-
-/* What reading costs, both ways: opening (verifying the symbol file; parsing
- * the font's tables) and reaching every symbol's outline. */
-static void measure_open(FT_Library library, const char *font, const VegaSymbols *s) {
- size_t font_size;
- char *font_bytes = read_file(font, &font_size), error[128];
- if (!font_bytes) fail("cannot read the font");
- int rounds = 200;
- double started = seconds();
- for (int i = 0; i < rounds; ++i) {
- VegaSymbols again;
- if (!vega_symbols_open(&again, s->bytes, s->size, error, sizeof(error))) fail(error);
- }
- double open_us = (seconds() - started) / rounds * 1e6;
- started = seconds();
- for (int i = 0; i < rounds; ++i) {
- FT_Face face;
- if (FT_New_Memory_Face(library, (const FT_Byte *)font_bytes, (FT_Long)font_size, 0, &face)) fail("cannot open the font");
- FT_Done_Face(face);
- }
- double freetype_open_us = (seconds() - started) / rounds * 1e6;
- FT_Face face;
- if (FT_New_Memory_Face(library, (const FT_Byte *)font_bytes, (FT_Long)font_size, 0, &face)) fail("cannot open the font");
- FT_Outline_Funcs walk = {ignore_point, ignore_point, ignore_conic, ignore_cubic, 0, 0};
- long touched = 0, walked = 0;
- uint32_t most = 0;
- for (uint32_t i = 0; i < s->symbols; ++i) most = vega_symbol_at(s, i).count > most ? vega_symbol_at(s, i).count : most;
- VegaCurve *curves = malloc((most + 1) * sizeof(*curves));
- started = seconds();
- for (int r = 0; r < rounds; ++r)
- for (uint32_t i = 0; i < s->symbols; ++i) {
- VegaSymbol symbol;
- if (!vega_symbol(s, vega_symbol_at(s, i).code, &symbol)) fail("lookup");
- uint32_t count = vega_symbol_curves(s, &symbol, curves);
- touched += count ? (long)curves[count - 1].x2 : 0;
- }
- double outline_ns = (seconds() - started) / rounds / s->symbols * 1e9;
- started = seconds();
- for (int r = 0; r < rounds; ++r)
- for (uint32_t i = 0; i < s->symbols; ++i) {
- FT_UInt glyph = FT_Get_Char_Index(face, vega_symbol_at(s, i).code);
- if (glyph && !FT_Load_Glyph(face, glyph, FT_LOAD_NO_SCALE)) FT_Outline_Decompose(&face->glyph->outline, &walk, &walked);
- }
- double freetype_outline_ns = (seconds() - started) / rounds / s->symbols * 1e9;
- printf("{\"symbol_bytes\":%zu,\"font_bytes\":%zu,\"open_us\":%.1f,\"freetype_open_us\":%.1f,\"outline_ns\":%.0f,"
- "\"freetype_outline_ns\":%.0f}\n", s->size, font_size, open_us, freetype_open_us, outline_ns, freetype_outline_ns);
- sink = touched + walked;
- FT_Done_Face(face);
- free(font_bytes);
- free(curves);
-}
-
-/* Every symbol with a glyph in the font, at each size: our coverage against
- * FreeType's (no hinting, 8-bit gray) over FreeType's bitmap. */
-static int compare(int argc, char **argv) {
- if (argc < 6 || strcmp(argv[4], "--sizes")) fail("usage: vega-symbols compare FONT.ttf SYMBOLS.vsym --sizes 12,16,32,64");
- char *bytes;
- VegaSymbols s = open_symbols(argv[3], &bytes);
- FT_Library library;
- FT_Face face;
- if (FT_Init_FreeType(&library) || FT_New_Face(library, argv[2], 0, &face)) fail("cannot open the font");
- measure_open(library, argv[2], &s);
- for (const char *p = argv[5]; *p;) {
- char *end;
- long size = strtol(p, &end, 10);
- p = *end == ',' ? end + 1 : end;
- if (size <= 0 || FT_Set_Pixel_Sizes(face, 0, (FT_UInt)size)) fail("--sizes");
- double scale = (double)size / s.units, total = 0, ours_seconds = 0;
- uint64_t pixels = 0, close = 0;
- int worst = 0, glyphs = 0;
- unsigned char *raster = NULL;
- size_t raster_size = 0;
- for (uint32_t i = 0; i < s.symbols; ++i) {
- VegaSymbol symbol = vega_symbol_at(&s, i);
- FT_UInt glyph = FT_Get_Char_Index(face, symbol.code);
- if (!glyph || FT_Load_Glyph(face, glyph, FT_LOAD_NO_HINTING | FT_LOAD_RENDER)) continue;
- FT_Bitmap *b = &face->glyph->bitmap;
- size_t need = (size_t)b->width * b->rows;
- if (!need) continue;
- if (need > raster_size && !(raster = realloc(raster, raster_size = need))) fail("out of memory");
- double started = seconds();
- vega_symbol_raster(&s, &symbol, scale, face->glyph->bitmap_left / scale, face->glyph->bitmap_top / scale,
- (int)b->width, (int)b->rows, raster);
- ours_seconds += seconds() - started;
- for (unsigned j = 0; j < b->rows; ++j)
- for (unsigned i2 = 0; i2 < b->width; ++i2) {
- int theirs = b->buffer[j * (unsigned)b->pitch + i2], ours = raster[j * b->width + i2];
- int difference = abs(theirs - ours);
- total += difference;
- close += difference <= 16;
- if (difference > worst) worst = difference;
- ++pixels;
- }
- ++glyphs;
- }
- printf("{\"pixels_per_em\":%ld,\"glyphs\":%d,\"mean_difference\":%.2f,\"worst\":%d,\"within_16\":%.4f,"
- "\"reference_us_per_glyph\":%.1f}\n", size, glyphs, pixels ? total / (double)pixels : 0, worst,
- pixels ? (double)close / (double)pixels : 1, glyphs ? ours_seconds / glyphs * 1e6 : 0);
- free(raster);
- }
- FT_Done_Face(face);
- FT_Done_FreeType(library);
- free(bytes);
- return 0;
-}
-
-/* Each symbol's code, points, curves, bounds, advance and filled area (in square
- * units: the integral of x dy around its outline, as a check against other
- * readers of the same fonts and icons). */
-static int info(int argc, char **argv) {
- if (argc < 3) fail("usage: vega-symbols info SYMBOLS.vsym");
- char *bytes;
- VegaSymbols s = open_symbols(argv[2], &bytes);
- printf("{\"units\":%u,\"symbols\":[", s.units);
- for (uint32_t i = 0; i < s.symbols; ++i) {
- VegaSymbol symbol = vega_symbol_at(&s, i);
- double area = 0;
- uint32_t curves = 0;
- VegaCurve c;
- for (uint32_t k = symbol.first; k < symbol.first + symbol.count; ++k) {
- if (!vega_symbol_curve(&s, &symbol, k, &c)) continue;
- ++curves;
- /* x(t) = x0 - 2 bx t + ax t^2, y'(t) = -2 by + 2 ay t, over [0, 1]. */
- double ax = c.x0 - 2 * c.x1 + c.x2, bx = c.x0 - c.x1, ay = c.y0 - 2 * c.y1 + c.y2, by = c.y0 - c.y1;
- area += c.x0 * -2 * by + (c.x0 * 2 * ay + 4 * bx * by) / 2 + (-4 * bx * ay - 2 * ax * by) / 3 + ax * ay / 2;
- }
- printf("%s{\"code\":%u,\"points\":%u,\"curves\":%u,\"bounds\":[%d,%d,%d,%d],\"advance\":%d,\"area\":%.1f}",
- i ? "," : "", symbol.code, symbol.count, curves, symbol.left, symbol.bottom, symbol.right, symbol.top,
- symbol.advance, area);
- }
- printf("]}\n");
- free(bytes);
- return 0;
-}
-
-static int render(int argc, char **argv) {
- if (argc < 6) fail("usage: vega-symbols render SYMBOLS.vsym CODE PIXELS OUT.pgm");
- char *bytes;
- VegaSymbols s = open_symbols(argv[2], &bytes);
- VegaSymbol symbol;
- if (!vega_symbol(&s, (uint32_t)strtoul(argv[3], NULL, 0), &symbol)) fail("no such symbol");
- double pixels = atof(argv[4]), scale = pixels / s.units;
- int width = (int)ceil((symbol.advance > symbol.right ? symbol.advance : symbol.right) * scale) + 2;
- int height = (int)ceil(pixels * 1.25) + 2;
- unsigned char *raster = malloc((size_t)width * (size_t)height);
- vega_symbol_raster(&s, &symbol, scale, -1 / scale, (pixels + 1) / scale, width, height, raster);
- FILE *f = fopen(argv[5], "wb");
- if (!f) fail("cannot write");
- fprintf(f, "P5\n%d %d\n255\n", width, height);
- fwrite(raster, 1, (size_t)width * (size_t)height, f);
- fclose(f);
- free(raster);
- free(bytes);
- return 0;
-}
-
-/* ---- The GPU: a context of its own, no window (EGL, Mesa's surfaceless platform). */
-
-static void gl_context(void) {
- PFNEGLGETPLATFORMDISPLAYEXTPROC platform = (PFNEGLGETPLATFORMDISPLAYEXTPROC)eglGetProcAddress("eglGetPlatformDisplayEXT");
- EGLDisplay display = platform ? platform(EGL_PLATFORM_SURFACELESS_MESA, EGL_DEFAULT_DISPLAY, NULL) : EGL_NO_DISPLAY;
- EGLint major, minor, found;
- EGLConfig config;
- EGLint wanted[] = {EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, EGL_NONE};
- EGLint version[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_CONTEXT_MINOR_VERSION, 1, EGL_NONE};
- if (display == EGL_NO_DISPLAY || !eglInitialize(display, &major, &minor) || !eglBindAPI(EGL_OPENGL_ES_API) ||
- !eglChooseConfig(display, wanted, &config, 1, &found))
- fail("no EGL display for OpenGL ES 3.1");
- /* With no surface, a context needs no config (EGL_KHR_no_config_context). */
- EGLContext context = eglCreateContext(display, found ? config : EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, version);
- if (context == EGL_NO_CONTEXT || !eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, context))
- fail("no OpenGL ES 3.1 context");
-}
-
-/* A width x height target of 8-bit red, bound. */
-static GLuint gl_target(int width, int height) {
- GLuint texture, framebuffer;
- glGenTextures(1, &texture);
- glBindTexture(GL_TEXTURE_2D, texture);
- glTexStorage2D(GL_TEXTURE_2D, 1, GL_R8, width, height);
- glGenFramebuffers(1, &framebuffer);
- glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
- if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) fail("no framebuffer");
- glViewport(0, 0, width, height);
- glClearColor(0, 0, 0, 0);
- glClear(GL_COLOR_BUFFER_BIT);
- return texture;
-}
-
-/* Every symbol in a grid of cells, drawn by the GPU and by vega_coverage:
- * how far apart; then pages of text (glyphs cycling through the file)
- * drawn again and again: how long a page takes. */
-static int gpu(int argc, char **argv) {
- if (argc < 5 || strcmp(argv[3], "--sizes")) fail("usage: vega-symbols gpu SYMBOLS.vsym --sizes 12,16,32,64 [--frames N]");
- int frames = argc > 6 && !strcmp(argv[5], "--frames") ? atoi(argv[6]) : 20;
- if (frames < 1) fail("--frames");
- char *bytes, error[4096];
- VegaSymbols s = open_symbols(argv[2], &bytes);
- gl_context();
- printf("{\"renderer\":\"%s\"}\n", (const char *)glGetString(GL_RENDERER));
- VegaGl gl;
- double started = seconds();
- if (!vega_gl_open(&gl, &s, error, sizeof(error))) fail(error);
- glFinish();
- double open_ms = (seconds() - started) * 1e3;
- for (const char *p = argv[4]; *p;) {
- char *end;
- long size = strtol(p, &end, 10);
- p = *end == ',' ? end + 1 : end;
- if (size <= 0 || size > 512) fail("--sizes");
- double scale = (double)size / s.units;
- /* Cells that hold any symbol, so none reaches into another's. */
- int left = 0, bottom = 0, right = 0, top = 0;
- for (uint32_t i = 0; i < s.symbols; ++i) {
- VegaSymbol symbol = vega_symbol_at(&s, i);
- left = symbol.left < left ? symbol.left : left;
- bottom = symbol.bottom < bottom ? symbol.bottom : bottom;
- right = symbol.right > right ? symbol.right : right;
- top = symbol.top > top ? symbol.top : top;
- }
- int cell = (int)ceil((right - left > top - bottom ? right - left : top - bottom) * scale) + 4;
- int across = 4096 / cell, rows = (int)((s.symbols + (uint32_t)across - 1) / (uint32_t)across);
- if (rows * cell > 4096) rows = 4096 / cell;
- int width = across * cell, height = rows * cell;
- uint32_t shown = s.symbols < (uint32_t)(across * rows) ? s.symbols : (uint32_t)(across * rows);
- VegaGlyph *glyphs = malloc(shown * sizeof(*glyphs));
- for (uint32_t i = 0; i < shown; ++i)
- glyphs[i] = (VegaGlyph){(float)(i % (uint32_t)across * (uint32_t)cell) + 2 - (float)(left * scale),
- (float)(i / (uint32_t)across * (uint32_t)cell) + 2 + (float)(top * scale), (float)scale, i};
- GLuint texture = gl_target(width, height);
- vega_gl_draw(&gl, &s, glyphs, shown, width, height);
- unsigned char *drawn = malloc((size_t)width * (size_t)height), *reference = malloc((size_t)cell * (size_t)cell);
- glPixelStorei(GL_PACK_ALIGNMENT, 1);
- glReadPixels(0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, drawn);
- double total = 0;
- uint64_t pixels = 0;
- int worst = 0;
- for (uint32_t i = 0; i < shown; ++i) {
- VegaSymbol symbol = vega_symbol_at(&s, i);
- int x0 = (int)(i % (uint32_t)across) * cell, y0 = (int)(i / (uint32_t)across) * cell;
- vega_symbol_raster(&s, &symbol, scale, (x0 - glyphs[i].x) / scale, (glyphs[i].y - y0) / scale, cell, cell, reference);
- for (int j = 0; j < cell; ++j)
- for (int k = 0; k < cell; ++k) {
- /* GL's rows run bottom up */
- int gpu_value = drawn[(size_t)(height - 1 - (y0 + j)) * (size_t)width + (size_t)(x0 + k)];
- int difference = abs(gpu_value - reference[j * cell + k]);
- total += difference;
- worst = difference > worst ? difference : worst;
- ++pixels;
- }
- }
- /* Pages: glyphs at their advances in lines, a full target's worth. */
- int page_width = 1920, page_height = 1080;
- uint32_t page = 0, capacity = 1 << 16;
- VegaGlyph *text = malloc(capacity * sizeof(*text));
- float x = 0, line = (float)size * 1.2f;
- for (float y = line; y + (float)size * 0.3f < (float)page_height && page < capacity; ) {
- VegaSymbol symbol = vega_symbol_at(&s, page % s.symbols);
- float advance = (float)(symbol.advance * scale);
- if (x + advance > (float)page_width) { x = 0; y += line; continue; }
- text[page++] = (VegaGlyph){x, y, (float)scale, page % s.symbols};
- x += advance;
- }
- glDeleteTextures(1, &texture);
- texture = gl_target(page_width, page_height);
- vega_gl_draw(&gl, &s, text, page, page_width, page_height);
- glFinish();
- started = seconds();
- for (int f = 0; f < frames; ++f) vega_gl_draw(&gl, &s, text, page, page_width, page_height);
- glFinish();
- double page_ms = (seconds() - started) / frames * 1e3;
- printf("{\"pixels_per_em\":%ld,\"symbols\":%u,\"mean_difference\":%.3f,\"worst\":%d,\"page_glyphs\":%u,"
- "\"page_ms\":%.2f,\"upload_ms\":%.1f}\n", size, shown, pixels ? total / (double)pixels : 0, worst, page, page_ms, open_ms);
- glDeleteTextures(1, &texture);
- free(text);
- free(glyphs);
- free(drawn);
- free(reference);
- }
- vega_gl_close(&gl);
- free(bytes);
- return 0;
-}
-
-int main(int argc, char **argv) {
- if (argc > 1 && !strcmp(argv[1], "build")) return build(argc, argv);
- if (argc > 1 && !strcmp(argv[1], "compare")) return compare(argc, argv);
- if (argc > 1 && !strcmp(argv[1], "info")) return info(argc, argv);
- if (argc > 1 && !strcmp(argv[1], "render")) return render(argc, argv);
- if (argc > 1 && !strcmp(argv[1], "gpu")) return gpu(argc, argv);
- fail("vega-symbols build|compare|info|render|gpu ...");
- return 2;
-}
diff --git a/src/symbols.c b/src/symbols.c
deleted file mode 100644
index cedd68f..0000000
--- a/src/symbols.c
+++ /dev/null
@@ -1,185 +0,0 @@
-#include "symbols.h"
-#include <math.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-static const unsigned char magic[8] = {'V', 'E', 'G', 'A', 'S', 'Y', 'M', '2'};
-
-static uint16_t u16(const unsigned char *p) { return (uint16_t)(p[0] | p[1] << 8); }
-static int16_t i16(const unsigned char *p) { return (int16_t)u16(p); }
-static uint32_t u32(const unsigned char *p) { return p[0] | p[1] << 8 | (uint32_t)p[2] << 16 | (uint32_t)p[3] << 24; }
-
-VegaSymbol vega_symbol_at(const VegaSymbols *s, uint32_t index) {
- const unsigned char *p = s->bytes + VEGA_SYMBOLS_HEADER + (size_t)index * VEGA_SYMBOL_BYTES;
- return (VegaSymbol){u32(p), u32(p + 4), u32(p + 8), i16(p + 12), i16(p + 14), i16(p + 16), i16(p + 18), i16(p + 20)};
-}
-
-/* A point is two i16s, each its coordinate times two plus a flag: off the
- * curve in x, starting a contour in y. */
-VegaPoint vega_point(const VegaSymbols *s, uint32_t index) {
- const unsigned char *p = s->bytes + VEGA_SYMBOLS_HEADER + (size_t)s->symbols * VEGA_SYMBOL_BYTES +
- (size_t)index * VEGA_POINT_BYTES;
- int x = i16(p), y = i16(p + 2);
- /* x >> 1 without relying on how a negative number shifts */
- return (VegaPoint){(int16_t)((x - (x & 1)) / 2), (int16_t)((y - (y & 1)) / 2), x & 1, y & 1};
-}
-
-#define REFUSE(...) do { snprintf(error, error_size, __VA_ARGS__); return false; } while (0)
-
-bool vega_symbols_open(VegaSymbols *s, const unsigned char *bytes, size_t size, char *error, size_t error_size) {
- if (size < VEGA_SYMBOLS_HEADER || memcmp(bytes, magic, sizeof(magic))) REFUSE("not a symbol file");
- *s = (VegaSymbols){bytes, size, u16(bytes + 8), u32(bytes + 12), u32(bytes + 16)};
- uint64_t expected = VEGA_SYMBOLS_HEADER + (uint64_t)s->symbols * VEGA_SYMBOL_BYTES + (uint64_t)s->points * VEGA_POINT_BYTES;
- if (u16(bytes + 10) || u32(bytes + 24) || !s->units) REFUSE("header");
- if (u32(bytes + 20) != size || expected != size) REFUSE("size: %zu bytes for %u symbols and %u points", size, s->symbols, s->points);
- for (uint32_t i = 0; i < s->symbols; ++i) {
- VegaSymbol symbol = vega_symbol_at(s, i);
- const unsigned char *p = bytes + VEGA_SYMBOLS_HEADER + (size_t)i * VEGA_SYMBOL_BYTES;
- if (u16(p + 22)) REFUSE("symbol %u: flags", i);
- if (i && symbol.code <= vega_symbol_at(s, i - 1).code) REFUSE("symbol %u: codes out of order", i);
- if (symbol.first > s->points || symbol.count > s->points - symbol.first) REFUSE("symbol %u: points", i);
- if (symbol.left > symbol.right || symbol.bottom > symbol.top) REFUSE("symbol %u: bounds", i);
- /* An off point then always has both neighbours within the symbol. */
- if (symbol.count) {
- VegaPoint first = vega_point(s, symbol.first), last = vega_point(s, symbol.first + symbol.count - 1);
- if (first.off || !first.move) REFUSE("symbol %u: its first point does not start a contour on the curve", i);
- if (last.off) REFUSE("symbol %u: its last point is off the curve", i);
- }
- }
- return true;
-}
-
-bool vega_symbol(const VegaSymbols *s, uint32_t code, VegaSymbol *out) {
- uint32_t low = 0, high = s->symbols;
- while (low < high) {
- uint32_t middle = low + (high - low) / 2;
- VegaSymbol symbol = vega_symbol_at(s, middle);
- if (symbol.code == code) { *out = symbol; return true; }
- if (symbol.code < code) low = middle + 1; else high = middle;
- }
- return false;
-}
-
-bool vega_symbol_curve(const VegaSymbols *s, const VegaSymbol *symbol, uint32_t index, VegaCurve *out) {
- VegaPoint p = vega_point(s, index);
- if (p.off) {
- VegaPoint a = vega_point(s, index - 1), b = vega_point(s, index + 1);
- *out = (VegaCurve){a.off ? (a.x + p.x) * 0.5f : a.x, a.off ? (a.y + p.y) * 0.5f : a.y, p.x, p.y,
- b.off ? (p.x + b.x) * 0.5f : b.x, b.off ? (p.y + b.y) * 0.5f : b.y};
- return true;
- }
- if (index + 1 >= symbol->first + symbol->count) return false;
- VegaPoint b = vega_point(s, index + 1);
- if (b.off || b.move) return false;
- *out = (VegaCurve){p.x, p.y, (p.x + b.x) * 0.5f, (p.y + b.y) * 0.5f, b.x, b.y};
- return true;
-}
-
-/* Where a t^2 + b t + c = 0 inside (0, 1), appended to ts. */
-static int roots(double a, double b, double c, double *ts) {
- int n = 0;
- if (fabs(a) < 1e-12) {
- if (fabs(b) > 1e-12) ts[n++] = -c / b;
- } else {
- double d = b * b - 4 * a * c;
- if (d >= 0) {
- /* The root that does not cancel, and the other by the product. */
- double q = -0.5 * (b + copysign(sqrt(d), b));
- ts[n++] = q / a;
- if (q != 0) ts[n++] = c / q;
- }
- }
- int kept = 0;
- for (int i = 0; i < n; ++i) if (ts[i] > 0 && ts[i] < 1) ts[kept++] = ts[i];
- return kept;
-}
-
-static double clamp01(double v) { return v < 0 ? 0 : v > 1 ? 1 : v; }
-
-/* The curve's share of the pixel [0, 1]^2 (points relative to its lower left
- * corner, in pixels): the integral of clamp(1 - x, 0, 1) dy along the part
- * with y in [0, 1]. Over a closed outline these add up to the covered area,
- * negative for a counterclockwise one (Green's theorem), so the covered area
- * is exact, as FreeType's is. The curve splits where it crosses the pixel's
- * sides; each piece lies left of it (dy counts whole), right of it (nothing),
- * or across it (a cubic in t, integrated in closed form). */
-static double curve_area(double x0, double y0, double x1, double y1, double x2, double y2) {
- if (fmax(fmax(y0, y1), y2) <= 0 || fmin(fmin(y0, y1), y2) >= 1 || fmin(fmin(x0, x1), x2) >= 1) return 0;
- if (fmax(fmax(x0, x1), x2) <= 0) return clamp01(y2) - clamp01(y0);
- /* x(t) = x0 - 2 bx t + ax t^2, and y alike. */
- double ax = x0 - 2 * x1 + x2, bx = x0 - x1, ay = y0 - 2 * y1 + y2, by = y0 - y1;
- double ts[10] = {0};
- int n = 1;
- n += roots(ay, -2 * by, y0, ts + n);
- n += roots(ay, -2 * by, y0 - 1, ts + n);
- n += roots(ax, -2 * bx, x0, ts + n);
- n += roots(ax, -2 * bx, x0 - 1, ts + n);
- ts[n++] = 1;
- for (int i = 1; i < n; ++i)
- for (int j = i; j > 0 && ts[j] < ts[j - 1]; --j) { double t = ts[j]; ts[j] = ts[j - 1]; ts[j - 1] = t; }
- /* (1 - x(t)) y'(t) = (u0 + u1 t + u2 t^2)(v0 + v1 t) */
- double u0 = 1 - x0, u1 = 2 * bx, u2 = -ax, v0 = -2 * by, v1 = 2 * ay;
- double k1 = u0 * v0, k2 = (u0 * v1 + u1 * v0) / 2, k3 = (u1 * v1 + u2 * v0) / 3, k4 = u2 * v1 / 4, area = 0;
- for (int i = 0; i + 1 < n; ++i) {
- double ta = ts[i], tb = ts[i + 1], t = (ta + tb) / 2;
- if (tb <= ta) continue;
- double y = (ay * t - 2 * by) * t + y0, x = (ax * t - 2 * bx) * t + x0;
- if (y <= 0 || y >= 1 || x >= 1) continue;
- if (x <= 0) area += ((ay * tb - 2 * by) * tb) - ((ay * ta - 2 * by) * ta);
- else area += (((k4 * tb + k3) * tb + k2) * tb + k1) * tb - (((k4 * ta + k3) * ta + k2) * ta + k1) * ta;
- }
- return area;
-}
-
-/* Without branches on the flags, which no predictor guesses: implied points
- * and whether a point begins a curve come out of arithmetic on them. */
-uint32_t vega_symbol_curves(const VegaSymbols *s, const VegaSymbol *symbol, VegaCurve *out) {
- if (!symbol->count) return 0;
- const unsigned char *p = s->bytes + VEGA_SYMBOLS_HEADER + (size_t)s->symbols * VEGA_SYMBOL_BYTES +
- (size_t)symbol->first * VEGA_POINT_BYTES;
- uint32_t count = 0;
- int x = i16(p), y = i16(p + 2), off = x & 1;
- float bx = 0, by = 0, bo = 0, hx = (float)(x - off) * 0.5f, hy = (float)(y - (y & 1)) * 0.5f, ho = (float)off;
- for (uint32_t k = 1; k < symbol->count; ++k) {
- p += VEGA_POINT_BYTES;
- x = i16(p);
- y = i16(p + 2);
- float nx = (float)(x - (x & 1)) * 0.5f, ny = (float)(y - (y & 1)) * 0.5f, no = (float)(x & 1);
- /* before, or halfway to here if before is off; here if here is on */
- float sx = bx + (hx - bx) * 0.5f * bo, sy = by + (hy - by) * 0.5f * bo;
- out[count] = (VegaCurve){hx + (sx - hx) * ho, hy + (sy - hy) * ho, hx + (nx - hx) * 0.5f * (1 - ho),
- hy + (ny - hy) * 0.5f * (1 - ho), nx + (hx - nx) * 0.5f * no, ny + (hy - ny) * 0.5f * no};
- /* an off point begins a curve, and so does an on point before an on
- * point of the same contour (a line) */
- count += (uint32_t)(off | (((x & 1) ^ 1) & ((y & 1) ^ 1)));
- off = x & 1;
- bx = hx, by = hy, bo = ho;
- hx = nx, hy = ny, ho = no;
- }
- return count;
-}
-
-double vega_coverage(const VegaCurve *curves, uint32_t count, double scale, double x, double y) {
- /* The pixel's lower left corner, in units. */
- double left = x - 0.5 / scale, bottom = y - 0.5 / scale, area = 0;
- for (uint32_t k = 0; k < count; ++k) {
- const VegaCurve *c = &curves[k];
- area += curve_area((c->x0 - left) * scale, (c->y0 - bottom) * scale, (c->x1 - left) * scale, (c->y1 - bottom) * scale,
- (c->x2 - left) * scale, (c->y2 - bottom) * scale);
- }
- area = fabs(area);
- return area > 1 ? 1 : area;
-}
-
-void vega_symbol_raster(const VegaSymbols *s, const VegaSymbol *symbol, double scale, double left, double top, int width,
- int height, unsigned char *out) {
- VegaCurve *curves = malloc((symbol->count ? symbol->count : 1) * sizeof(*curves));
- uint32_t count = curves ? vega_symbol_curves(s, symbol, curves) : 0;
- for (int j = 0; j < height; ++j)
- for (int i = 0; i < width; ++i) {
- double coverage = vega_coverage(curves, count, scale, left + (i + 0.5) / scale, top - (j + 0.5) / scale);
- out[(size_t)j * (size_t)width + (size_t)i] = (unsigned char)lround(coverage * 255);
- }
- free(curves);
-}
diff --git a/src/symbols.h b/src/symbols.h
deleted file mode 100644
index 998ff3f..0000000
--- a/src/symbols.h
+++ /dev/null
@@ -1,54 +0,0 @@
-#ifndef VEGA_SYMBOLS_H
-#define VEGA_SYMBOLS_H
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
-
-/* Symbols: glyphs and icons as outlines of quadratic curves in one file,
- * verified once and then read where it lies (docs/design/002-symbol-format.md).
- * An outline is a chain of points, on or off the curve, as in TrueType: two
- * off points in a row imply the on point halfway between them. */
-enum { VEGA_SYMBOLS_HEADER = 28, VEGA_SYMBOL_BYTES = 24, VEGA_POINT_BYTES = 4 };
-typedef struct {
- int16_t x, y;
- bool off; /* a control point */
- bool move; /* starts a contour */
-} VegaPoint;
-typedef struct { float x0, y0, x1, y1, x2, y2; } VegaCurve; /* a line has its middle as control */
-typedef struct {
- uint32_t code, first, count; /* its points: [first, first + count) */
- int16_t left, bottom, right, top; /* bounds, in units, y up */
- int16_t advance;
-} VegaSymbol;
-typedef struct {
- const unsigned char *bytes;
- size_t size;
- uint16_t units; /* per em */
- uint32_t symbols, points;
-} VegaSymbols;
-
-/* Verify bytes as a symbol file (kept, not copied); false with why. What
- * reading needs is checked, per symbol: after this no read leaves the file
- * or its symbol. The points themselves are not: one outside its symbol's
- * bounds draws wrong. */
-bool vega_symbols_open(VegaSymbols *, const unsigned char *bytes, size_t size, char *error, size_t error_size);
-/* The symbol of code (a binary search), or false. */
-bool vega_symbol(const VegaSymbols *, uint32_t code, VegaSymbol *out);
-VegaSymbol vega_symbol_at(const VegaSymbols *, uint32_t index);
-VegaPoint vega_point(const VegaSymbols *, uint32_t index);
-/* The curve that point index of symbol begins, if any: an off point is the
- * control of one, and an on point followed by another on point in the same
- * contour starts a line. Every curve of a symbol is had once this way, from
- * the point and its two neighbours alone (as a GPU would fetch it). */
-bool vega_symbol_curve(const VegaSymbols *, const VegaSymbol *, uint32_t index, VegaCurve *out);
-/* All of a symbol's curves, in one pass over its points; out has room for
- * symbol->count (a symbol has no more curves than points). */
-uint32_t vega_symbol_curves(const VegaSymbols *, const VegaSymbol *, VegaCurve *out);
-/* The coverage (0 to 1) of the pixel whose center is (x, y) in units, at
- * scale pixels per unit: the exact area the curves' outline covers. */
-double vega_coverage(const VegaCurve *, uint32_t count, double scale, double x, double y);
-/* width x height 8-bit coverage, rows top first; pixel (i, j) has its center
- * at (left + (i + 0.5) / scale, top - (j + 0.5) / scale) in units. */
-void vega_symbol_raster(const VegaSymbols *, const VegaSymbol *, double scale, double left, double top, int width,
- int height, unsigned char *out);
-#endif
diff --git a/src/vega.h b/src/vega.h
deleted file mode 100644
index f01667b..0000000
--- a/src/vega.h
+++ /dev/null
@@ -1,124 +0,0 @@
-#ifndef VEGA_H
-#define VEGA_H
-#include "module.h"
-#include <stdio.h>
-
-/* Vega reads a verified Pure module by convention (docs/design/001-first-component.md):
- * - a text view named `title` is the screen's title;
- * - a table view with a text column `label` is a list; any other table is a
- * table. A number column `tone` gives a row its tone; text columns `detail`
- * and `badge` add to a list row;
- * - a number view named `<table>_focus` is the focused row of that table;
- * - every other number or text view is a field.
- * A tone says what a value means (lib/vega.aup); each surface decides how it
- * looks or sounds. Surfaces evaluate only the cells they present. */
-enum { VEGA_PARTS = 64, VEGA_TEXT = 1024 };
-typedef enum { VEGA_NORMAL, VEGA_MUTED, VEGA_ACCENT, VEGA_GOOD, VEGA_WARNING, VEGA_DANGER, VEGA_TONES } VegaTone;
-typedef enum { VEGA_FIELD, VEGA_LIST, VEGA_TABLE } VegaKind;
-
-typedef struct {
- VegaKind kind;
- uint32_t view;
- int focus; /* number view with the focused row, or -1 */
- int pointer; /* the index state that view shows, or -1: a surface may move it */
- int label, tone, detail, badge; /* columns, or -1 */
- uint32_t columns[PURE_MODULE_VIEWS]; /* a table's shown columns, in order */
- unsigned column_count;
-} VegaPart;
-
-typedef struct {
- const PureModule *module;
- int title; /* text view, or -1 */
- int prompt; /* the first text input, or -1 */
- bool ticks; /* it handles frames: a surface runs them, about 60 a second */
- VegaPart parts[VEGA_PARTS];
- unsigned count;
-} VegaScreen;
-
-void vega_screen(VegaScreen *, const PureModule *);
-
-/* The wall-clock time events see on a live surface: milliseconds since 1970
- * (UTC). Scripts set their own (time MS). */
-double vega_now(void);
-/* When each timer input ticks next. run ticks every one due at now (a tick
- * sees now) and gives the milliseconds until the next, or -1 without timers;
- * ticked says whether any did. Missed ticks are not made up. */
-typedef struct { double next[PURE_MODULE_INPUTS]; } VegaTimers;
-void vega_timers_start(VegaTimers *, const PureModule *, double now);
-long vega_timers_run(VegaTimers *, PureApp *, double now, bool *ticked);
-/* What a module seems to mean but does not meet: a list label that is not
- * text, a _focus view without its table, a column a list never shows, a
- * second text input. Each finding names the view or input it is about;
- * returns how many there were. */
-unsigned vega_check(const PureModule *, void (*report)(void *, const char *name, const char *message), void *context);
-
-/* A view (row ignored) or a column at a row as text; numbers in Pure's
- * shortest form. false when it has no value (for example a missing item). */
-bool vega_text(PureApp *, uint32_t view, size_t row, char out[VEGA_TEXT], size_t *length);
-bool vega_number(PureApp *, uint32_t view, size_t row, double *out);
-size_t vega_rows(PureApp *, const VegaPart *);
-/* The row of the focused item: the focus view names an item (an index's
- * position), shown on the row that holds it; VEGA_NO_ROW when a filtered
- * list leaves that item out. Clamped to the rows without a filter; 0
- * without a focus view. */
-#define VEGA_NO_ROW SIZE_MAX
-size_t vega_focus(PureApp *, const VegaPart *);
-/* Focus a row, as a user pointing at it (a click); false when the part's
- * focus is not a plain index position or the row is not there. */
-bool vega_point(PureApp *, const VegaPart *, size_t row);
-/* The first row to show of `rows` when `visible` fit: the least scrolling
- * from first that keeps the focused row in view (any, without one). */
-size_t vega_scroll(size_t first, size_t focus, size_t visible, size_t rows);
-/* The key input answering a key name (Pure's names, as KeyboardEvent.code
- * spells them), or -1; press sends its press and release. */
-int vega_key_input(const PureModule *, const char *key);
-bool vega_press(PureApp *, const char *key);
-/* Commit text to the screen's text input. */
-bool vega_commit(PureApp *, const VegaScreen *, const char *text, size_t length);
-VegaTone vega_tone(PureApp *, const VegaPart *, size_t row);
-/* Values evaluated so far, for checking that surfaces stay lazy. */
-unsigned long vega_evaluations(void);
-
-/* Display cells of a code point in a terminal: 0 for combining and
- * zero-width characters, 2 for East Asian wide and emoji, else 1. */
-unsigned vega_width(uint32_t codepoint);
-/* The code point at text[at] (valid UTF-8) and the bytes it takes. */
-size_t vega_decode(const char *text, size_t length, size_t at, uint32_t *codepoint);
-/* A control character shown as its control picture (U+2400 block), else itself. */
-uint32_t vega_visible(uint32_t codepoint);
-unsigned vega_text_width(const char *text, size_t length);
-
-/* CLI: everything, as plain lines for pipes; color only when asked. */
-void vega_print(FILE *, PureApp *, const VegaScreen *, bool color);
-/* NUI: the screen as sentences (all), or only the focused item of the first list. */
-void vega_say(FILE *, PureApp *, const VegaScreen *, bool all);
-/* GUI: the screen as a web page on listen:port (0: any free port, printed as
- * {"port":N}); runs until SIGINT or SIGTERM. reload, when given, is asked
- * a few times a second whether the module was replaced (the screen with it). */
-int vega_web(PureApp *, const VegaScreen *, const char *listen, unsigned port, bool (*reload)(void));
-
-/* TUI: a grid of cells, redrawn from the app on every change. first keeps each
- * list's scroll between frames; the focused row stays visible. shown is what
- * the terminal shows, so a paint sends only the cells that changed (all of
- * them until the first paint after a resize). */
-typedef struct {
- uint32_t ch, mark; /* a character and at most one combining mark; ch 0 continues a wide one */
- uint8_t tone;
- bool focus, strong;
-} VegaCell;
-typedef struct {
- unsigned width, height;
- VegaCell *cells, *shown;
- struct VegaRow { int part; size_t item; } *rows; /* the item each line shows, or part -1 */
- bool painted; /* shown holds what the terminal shows */
- size_t first[VEGA_PARTS];
- int cursor_row, cursor_column; /* where the terminal cursor goes, or -1 */
-} VegaGrid;
-bool vega_grid(VegaGrid *, unsigned width, unsigned height);
-void vega_grid_free(VegaGrid *);
-/* prompt is the text being typed for the screen's text input; typing says
- * whether keys go to it. */
-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);
-#endif
diff --git a/src/web.c b/src/web.c
deleted file mode 100644
index 535d92f..0000000
--- a/src/web.c
+++ /dev/null
@@ -1,453 +0,0 @@
-#define _GNU_SOURCE
-#include "vega.h"
-#include <arpa/inet.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <netinet/in.h>
-#include <poll.h>
-#include <signal.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/socket.h>
-#include <unistd.h>
-
-/* vega web: the screen as a web page, a GUI surface. The page asks for the
- * screen as JSON with the rows it can show per list or table, so only the
- * cells on the page are evaluated; it sends keys (KeyboardEvent.code names are
- * Pure's), committed text and clicks on rows, and an event stream tells every
- * open page when the app changed.
- *
- * GET / the page
- * GET /screen?rows=R&first=F,G title, fields, each list or table with R rows
- * from its last first row (F, G, ... by part), keys
- * POST /key KEYNAME press and release the input bound to the key
- * POST /text UTF-8 commit text to the screen's text input
- * POST /point PART ROW focus a row of a list or table (0 first)
- * GET /events server-sent events: one per change */
-
-enum { CLIENTS = 64, REQUEST = 1 << 16, ROWS_MAX = 500 };
-
-typedef struct { char *bytes; size_t used, capacity; } Buffer;
-
-typedef struct {
- int fd;
- bool events; /* an open event stream */
- bool closing;
- char in[REQUEST];
- size_t in_used;
- Buffer out;
- size_t out_at;
-} Client;
-
-static const char *const tone_names[VEGA_TONES] = {"normal", "muted", "accent", "good", "warning", "danger"};
-static volatile sig_atomic_t stopping;
-
-static void on_stop(int signal_number) {
- (void)signal_number;
- stopping = 1;
-}
-
-static void put(Buffer *b, const void *bytes, size_t size) {
- if (b->used + size > b->capacity) {
- size_t capacity = b->capacity ? b->capacity : 4096;
- while (capacity < b->used + size) capacity *= 2;
- char *grown = realloc(b->bytes, capacity);
- if (!grown) return;
- b->bytes = grown;
- b->capacity = capacity;
- }
- memcpy(b->bytes + b->used, bytes, size);
- b->used += size;
-}
-
-static void puts_(Buffer *b, const char *text) { put(b, text, strlen(text)); }
-
-static void putf(Buffer *b, const char *format, ...) __attribute__((format(printf, 2, 3)));
-#include <stdarg.h>
-static void putf(Buffer *b, const char *format, ...) {
- char line[256];
- va_list args;
- va_start(args, format);
- int n = vsnprintf(line, sizeof(line), format, args);
- va_end(args);
- if (n > 0) put(b, line, (size_t)n < sizeof(line) ? (size_t)n : sizeof(line) - 1);
-}
-
-/* A JSON string: UTF-8 as it is, quotes, backslashes and control characters escaped. */
-static void json_string(Buffer *b, const char *text, size_t length) {
- put(b, "\"", 1);
- for (size_t i = 0; i < length; ++i) {
- unsigned char c = (unsigned char)text[i];
- if (c == '"' || c == '\\') { put(b, "\\", 1); put(b, &text[i], 1); }
- else if (c < 0x20 || c == 0x7F) putf(b, "\\u%04x", c);
- else put(b, &text[i], 1);
- }
- put(b, "\"", 1);
-}
-
-static void json_name(Buffer *b, const char *name) { json_string(b, name, strlen(name)); }
-
-/* A view's text at a row, or null. */
-static void json_view(Buffer *b, PureApp *app, uint32_t view, size_t row) {
- char text[VEGA_TEXT];
- size_t length;
- if (vega_text(app, view, row, text, &length)) json_string(b, text, length);
- else puts_(b, "null");
-}
-
-static void screen_json(Buffer *b, PureApp *app, const VegaScreen *screen, size_t visible, size_t *firsts) {
- const PureModule *m = app->module;
- puts_(b, "{\"title\":");
- if (screen->title >= 0) json_view(b, app, (uint32_t)screen->title, 0);
- else puts_(b, "null");
- puts_(b, ",\"fields\":[");
- bool comma = false;
- for (unsigned i = 0; i < screen->count; ++i) {
- const VegaPart *part = &screen->parts[i];
- if (part->kind != VEGA_FIELD) continue;
- puts_(b, comma ? ",{\"name\":" : "{\"name\":");
- json_name(b, m->view_names[part->view]);
- puts_(b, ",\"text\":");
- json_view(b, app, part->view, 0);
- puts_(b, "}");
- comma = true;
- }
- puts_(b, "],\"parts\":[");
- comma = false;
- for (unsigned i = 0; i < screen->count; ++i) {
- const VegaPart *part = &screen->parts[i];
- if (part->kind == VEGA_FIELD) continue;
- size_t rows = vega_rows(app, part), focus = vega_focus(app, part);
- size_t first = vega_scroll(firsts[i], focus, visible, rows);
- firsts[i] = first;
- puts_(b, comma ? ",{\"part\":" : "{\"part\":");
- putf(b, "%u,\"name\":", i);
- json_name(b, m->view_names[part->view]);
- putf(b, ",\"kind\":\"%s\",\"rows\":%zu,\"focus\":", part->kind == VEGA_LIST ? "list" : "table", rows);
- if (focus < rows) putf(b, "%zu", focus);
- else puts_(b, "null");
- putf(b, ",\"first\":%zu,\"pointable\":%s,\"columns\":[", first, part->pointer >= 0 ? "true" : "false");
- if (part->kind == VEGA_TABLE)
- for (unsigned c = 0; c < part->column_count; ++c) {
- if (c) puts_(b, ",");
- json_name(b, m->view_names[part->columns[c]]);
- }
- puts_(b, "],\"items\":[");
- for (size_t row = first; row < first + visible && row < rows; ++row) {
- putf(b, "%s{\"row\":%zu,\"tone\":\"%s\"", row > first ? "," : "", row,
- tone_names[vega_tone(app, part, row)]);
- if (part->kind == VEGA_LIST) {
- const int columns[] = {part->label, part->badge, part->detail};
- const char *const names[] = {"label", "badge", "detail"};
- for (unsigned k = 0; k < 3; ++k) {
- if (columns[k] < 0) continue;
- putf(b, ",\"%s\":", names[k]);
- json_view(b, app, (uint32_t)columns[k], row);
- }
- } else {
- puts_(b, ",\"cells\":[");
- for (unsigned c = 0; c < part->column_count; ++c) {
- if (c) puts_(b, ",");
- json_view(b, app, part->columns[c], row);
- }
- puts_(b, "]");
- }
- puts_(b, "}");
- }
- puts_(b, "]}");
- comma = true;
- }
- puts_(b, "],\"keys\":[");
- comma = false;
- for (uint32_t i = 0; i < m->inputs; ++i) {
- if (m->input[i].kind != PURE_KIND_KEY) continue;
- bool bound = false;
- for (uint32_t h = 0; h < m->handlers && !bound; ++h) bound = m->handler[h].event == 1 + 2 * i;
- if (!bound) continue;
- puts_(b, comma ? ",{\"input\":" : "{\"input\":");
- json_name(b, m->input_names[i]);
- puts_(b, ",\"key\":");
- json_name(b, m->input_keys[i]);
- puts_(b, "}");
- comma = true;
- }
- puts_(b, "],\"prompt\":");
- if (screen->prompt >= 0) json_name(b, m->input_names[screen->prompt]);
- else puts_(b, "null");
- putf(b, ",\"events\":%llu}", (unsigned long long)app->events);
-}
-
-/* The page: one file, no dependencies. It keeps each part's first row, asks
- * for as many rows as fit, and redraws when the event stream says so. */
-static const char *const page_parts[] = {
-"<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\">"
-"<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><title>Vega</title><style>"
-":root{color-scheme:light dark;--fg:#1d1d1f;--bg:#fbfbfd;--line:#d2d2d7;--focus:#0a58ca;--muted:#86868b;"
-"--accent:#0071e3;--good:#1e8e3e;--warning:#b06000;--danger:#d93025}"
-"@media(prefers-color-scheme:dark){:root{--fg:#f5f5f7;--bg:#161618;--line:#3a3a3c;--focus:#4c8dff;--muted:#8e8e93;"
-"--accent:#64a8ff;--good:#5ec26a;--warning:#f0a33a;--danger:#ff6b5e}}"
-"body{margin:0;font:15px/1.5 system-ui,-apple-system,'Noto Sans KR',sans-serif;color:var(--fg);background:var(--bg)}"
-"main{max-width:760px;margin:0 auto;padding:24px 16px}h1{font-size:22px;margin:0 0 12px}"
-"dl{display:grid;grid-template-columns:max-content 1fr;gap:2px 16px;margin:0 0 16px}dt{color:var(--muted)}dd{margin:0}"
-"section{margin:0 0 20px}h2{font-size:14px;font-weight:600;margin:0 0 6px}h2 span{color:var(--muted);font-weight:400}"
-"ul{list-style:none;margin:0;padding:0;border:1px solid var(--line);border-radius:8px;overflow:hidden}"
-"li,tbody tr{cursor:pointer}li{display:flex;gap:10px;padding:7px 12px;border-top:1px solid var(--line)}"
-"li:first-child{border-top:0}.label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:pre}"
-".detail{color:var(--muted);white-space:pre}.badge{font-size:12px;padding:1px 8px;border:1px solid currentColor;"
-"border-radius:10px;align-self:center}[aria-selected=true]{background:var(--focus);color:#fff}"
-"table{width:100%;border-collapse:collapse;border:1px solid var(--line)}th{text-align:left;color:var(--muted);"
-"font-weight:500}th,td{padding:6px 12px;border-top:1px solid var(--line);white-space:pre}"
-".muted{color:var(--muted)}.accent{color:var(--accent)}.good{color:var(--good)}.warning{color:var(--warning)}"
-".danger{color:var(--danger)}[aria-selected=true].muted,[aria-selected=true] *{color:#fff}"
-"form{display:flex;gap:8px;margin-top:16px}input{flex:1;font:inherit;padding:8px 10px;border:1px solid var(--line);"
-"border-radius:8px;background:transparent;color:inherit}button{font:inherit;padding:8px 16px;border:1px solid var(--line);"
-"border-radius:8px;background:transparent;color:inherit;cursor:pointer}button:hover{border-color:var(--focus)}"
-"footer{margin-top:14px;color:var(--muted);font-size:13px}"
-"kbd{font:12px ui-monospace,monospace;border:1px solid var(--line);border-radius:4px;padding:0 5px;margin-right:4px}"
-"</style></head><body><main>",
-"<h1 id=title></h1><dl id=fields></dl><div id=parts></div>"
-"<form id=prompt hidden><input id=entry autocomplete=off><button>Enter</button></form><footer id=keys></footer></main>"
-"<script>",
-"'use strict';let screen=null,firsts=[],busy=false,again=false;const rows=Math.max(5,Math.floor((innerHeight-220)/34));"
-"const $=id=>document.getElementById(id);"
-"const el=(tag,props,...kids)=>{const e=document.createElement(tag);Object.assign(e,props||{});"
-"for(const k of kids)if(k!=null)e.append(k);return e};"
-"async function post(path,body){await fetch(path,{method:'POST',body});refresh()}"
-"async function refresh(){if(busy){again=true;return}busy=true;try{"
-"const r=await fetch('/screen?rows='+rows+'&first='+firsts.join(','));screen=await r.json();render()}"
-"finally{busy=false;if(again){again=false;refresh()}}}"
-"function render(){const s=screen;$('title').textContent=s.title?\?'';document.title=s.title?\?'Vega';"
-"$('fields').replaceChildren(...s.fields.flatMap(f=>[el('dt',{textContent:f.name}),el('dd',{textContent:f.text?\?'-'})]));"
-"firsts=s.parts.map(p=>p.first);"
-"$('parts').replaceChildren(...s.parts.map(p=>{const head=el('h2',{},p.name+' ',el('span',{textContent:'('+p.rows+')'}));"
-"const pick=row=>{if(p.pointable)post('/point',p.part+' '+row)};"
-"if(p.kind==='list'){const list=el('ul',{role:'listbox'});list.setAttribute('aria-label',p.name);"
-"for(const it of p.items){const item=el('li',{className:it.tone,onclick:()=>pick(it.row)},"
-"el('span',{className:'label',textContent:it.label?\?''}),it.detail?el('span',{className:'detail',textContent:it.detail}):null,"
-"it.badge?el('span',{className:'badge',textContent:it.badge}):null);item.setAttribute('role','option');"
-"item.setAttribute('aria-selected',it.row===p.focus);list.append(item)}"
-"list.onwheel=e=>{e.preventDefault();pick(Math.max(0,Math.min(p.rows-1,(p.focus?\?-1)+Math.sign(e.deltaY))))};"
-"return el('section',{},head,list)}"
-"const table=el('table',{},el('thead',{},el('tr',{},...p.columns.map(c=>el('th',{textContent:c})))));"
-"const body=el('tbody');for(const it of p.items){const tr=el('tr',{className:it.tone,onclick:()=>pick(it.row)},"
-"...it.cells.map(c=>el('td',{textContent:c?\?'-'})));tr.setAttribute('aria-selected',it.row===p.focus);body.append(tr)}"
-"table.append(body);table.onwheel=e=>{e.preventDefault();pick(Math.max(0,Math.min(p.rows-1,(p.focus?\?-1)+Math.sign(e.deltaY))))};"
-"return el('section',{},head,table)}));"
-"$('prompt').hidden=!s.prompt;if(s.prompt)$('entry').placeholder=s.prompt;"
-"const symbols={ArrowUp:'\u2191',ArrowDown:'\u2193',ArrowLeft:'\u2190',ArrowRight:'\u2192',Backspace:'Bksp',"
-"Delete:'Del',Escape:'Esc',PageUp:'PgUp',PageDown:'PgDn'};"
-"$('keys').replaceChildren(...s.keys.map(k=>el('span',{},el('kbd',{textContent:symbols[k.key]?\?k.key.replace(/^(Key|Digit)/,'')}),"
-"k.input+' ')))}"
-"$('prompt').onsubmit=e=>{e.preventDefault();const v=$('entry').value;if(v){$('entry').value='';post('/text',v)}};"
-"addEventListener('keydown',e=>{if(!screen||e.ctrlKey||e.metaKey||e.altKey)return;"
-"const typing=document.activeElement===$('entry');"
-"if(typing&&!['ArrowUp','ArrowDown','PageUp','PageDown','Escape'].includes(e.code))return;"
-"if(screen.keys.some(k=>k.key===e.code)){e.preventDefault();post('/key',e.code)}});"
-"new EventSource('/events').onmessage=refresh;refresh();"
-"</script></body></html>"};
-
-/* The page, joined once from its pieces. */
-static const Buffer *page(void) {
- static Buffer joined;
- if (!joined.used)
- for (size_t i = 0; i < sizeof(page_parts) / sizeof(*page_parts); ++i) puts_(&joined, page_parts[i]);
- return &joined;
-}
-
-static void respond(Client *c, int status, const char *reason, const char *type, const char *body, size_t length) {
- putf(&c->out, "HTTP/1.1 %d %s\r\nContent-Type: %s\r\nContent-Length: %zu\r\nCache-Control: no-store\r\n%s\r\n",
- status, reason, type, length, c->closing ? "Connection: close\r\n" : "");
- put(&c->out, body, length);
-}
-
-static bool query_number(const char *query, const char *name, size_t *out) {
- size_t length = strlen(name);
- for (const char *at = query; at && *at; at = strchr(at, '&') ? strchr(at, '&') + 1 : NULL)
- if (!strncmp(at, name, length) && at[length] == '=') {
- *out = strtoul(at + length + 1, NULL, 10);
- return true;
- }
- return false;
-}
-
-/* first=F,G,...: the parts' first rows, in the order of the screen's parts. */
-static void query_firsts(const char *query, const VegaScreen *screen, size_t *firsts) {
- const char *at = query ? strstr(query, "first=") : NULL;
- if (!at || (at != query && at[-1] != '&')) return;
- at += 6;
- for (unsigned i = 0; i < screen->count && *at && *at != '&'; ++i) {
- if (screen->parts[i].kind == VEGA_FIELD) continue;
- firsts[i] = strtoul(at, (char **)&at, 10);
- if (*at == ',') ++at;
- }
-}
-
-/* One request. Returns whether the app changed. */
-static bool handle(Client *c, PureApp *app, const VegaScreen *screen, const char *method, char *target,
- const char *body, size_t body_size) {
- char *query = strchr(target, '?');
- if (query) *query++ = 0;
- bool get = !strcmp(method, "GET"), post = !strcmp(method, "POST");
- uint64_t before = app->events;
- if (get && !strcmp(target, "/")) {
- respond(c, 200, "OK", "text/html; charset=utf-8", page()->bytes, page()->used);
- return false;
- }
- if (get && !strcmp(target, "/screen")) {
- size_t visible = 50, firsts[VEGA_PARTS] = {0};
- query_number(query, "rows", &visible);
- if (visible > ROWS_MAX) visible = ROWS_MAX;
- query_firsts(query, screen, firsts);
- Buffer json = {0};
- screen_json(&json, app, screen, visible, firsts);
- respond(c, 200, "OK", "application/json", json.bytes ? json.bytes : "", json.used);
- free(json.bytes);
- return false;
- }
- if (get && !strcmp(target, "/events")) {
- puts_(&c->out, "HTTP/1.1 200 OK\r\nContent-Type: text/event-stream\r\nCache-Control: no-store\r\n\r\n");
- putf(&c->out, "data: %llu\n\n", (unsigned long long)app->events);
- c->events = true;
- return false;
- }
- if (post && (!strcmp(target, "/key") || !strcmp(target, "/text") || !strcmp(target, "/point"))) {
- char text[VEGA_TEXT];
- size_t length = body_size < sizeof(text) - 1 ? body_size : sizeof(text) - 1;
- memcpy(text, body, length);
- text[length] = 0;
- bool ok = false;
- if (target[1] == 'k') ok = vega_press(app, text);
- else if (target[1] == 't') ok = vega_commit(app, screen, text, length);
- else {
- unsigned part = 0;
- long row = -1;
- ok = sscanf(text, "%u %ld", &part, &row) == 2 && part < screen->count && row >= 0 &&
- screen->parts[part].kind != VEGA_FIELD && vega_point(app, &screen->parts[part], (size_t)row);
- }
- const char *answer = ok ? "{\"ok\":true}" : "{\"ok\":false}";
- respond(c, 200, "OK", "application/json", answer, strlen(answer));
- return app->events != before;
- }
- respond(c, 404, "Not Found", "text/plain", "not found\n", 10);
- return false;
-}
-
-/* The requests complete in c->in, answered in order. */
-static bool serve(Client *c, PureApp *app, const VegaScreen *screen) {
- bool changed = false;
- while (!c->events && !c->closing) {
- char *end = memmem(c->in, c->in_used, "\r\n\r\n", 4);
- if (!end) {
- if (c->in_used == sizeof(c->in)) c->closing = true;
- return changed;
- }
- size_t head = (size_t)(end - c->in) + 4, body_size = 0;
- *end = 0;
- char *length = strcasestr(c->in, "\r\ncontent-length:");
- if (length) body_size = strtoul(length + 17, NULL, 10);
- if (body_size > sizeof(c->in) - head) { c->closing = true; return changed; }
- if (c->in_used < head + body_size) { *end = '\r'; return changed; }
- if (strcasestr(c->in, "\r\nconnection: close")) c->closing = true;
- char method[8] = "", target[1024] = "";
- if (sscanf(c->in, "%7s %1023s", method, target) == 2)
- changed = handle(c, app, screen, method, target, c->in + head, body_size) || changed;
- else c->closing = true;
- memmove(c->in, c->in + head + body_size, c->in_used - head - body_size);
- c->in_used -= head + body_size;
- }
- return changed;
-}
-
-static void drop(Client *c) {
- close(c->fd);
- free(c->out.bytes);
- c->fd = -1;
-}
-
-int vega_web(PureApp *app, const VegaScreen *screen, const char *listen_on, unsigned port, bool (*reload)(void)) {
- int listener = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0), one = 1;
- struct sockaddr_in address = {.sin_family = AF_INET, .sin_port = htons((uint16_t)port)};
- socklen_t size = sizeof(address);
- if (listener < 0 || inet_pton(AF_INET, listen_on, &address.sin_addr) != 1 ||
- setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) ||
- bind(listener, (struct sockaddr *)&address, sizeof(address)) || listen(listener, 64) ||
- getsockname(listener, (struct sockaddr *)&address, &size)) {
- fprintf(stderr, "vega: cannot listen on %s:%u\n", listen_on, port);
- return 2;
- }
- printf("{\"port\":%u}\n", ntohs(address.sin_port));
- fflush(stdout);
- signal(SIGINT, on_stop);
- signal(SIGTERM, on_stop);
- signal(SIGPIPE, SIG_IGN);
- static Client clients[CLIENTS];
- for (unsigned i = 0; i < CLIENTS; ++i) clients[i].fd = -1;
- VegaTimers timers;
- vega_timers_start(&timers, app->module, vega_now());
- while (!stopping) {
- /* Tick what is due; every page hears of it this round, without waiting. */
- bool ticked = false;
- long due = vega_timers_run(&timers, app, vega_now(), &ticked);
- struct pollfd fds[CLIENTS + 1];
- unsigned slot[CLIENTS + 1], count = 1;
- fds[0] = (struct pollfd){listener, POLLIN, 0};
- for (unsigned i = 0; i < CLIENTS; ++i) {
- Client *c = &clients[i];
- if (c->fd < 0) continue;
- fds[count] = (struct pollfd){c->fd, (short)(POLLIN | (c->out_at < c->out.used ? POLLOUT : 0)), 0};
- slot[count++] = i;
- }
- int timeout = ticked ? 0 : screen->ticks ? 33 : reload ? 200 : 1000;
- if (due >= 0 && due < timeout) timeout = (int)due;
- int ready = poll(fds, count, timeout);
- if (ready < 0 && errno != EINTR) break;
- bool changed = ticked;
- app->now = vega_now(); /* what the events of this round see */
- if (ready == 0 && screen->ticks) changed = pure_app_frame(app) == PURE_OK;
- if (reload && reload()) { /* a new module: every page draws it */
- changed = true;
- vega_timers_start(&timers, app->module, app->now);
- }
- if (fds[0].revents & POLLIN)
- for (int fd; (fd = accept4(listener, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC)) >= 0;) {
- unsigned free_slot = CLIENTS;
- for (unsigned i = 0; i < CLIENTS && free_slot == CLIENTS; ++i) if (clients[i].fd < 0) free_slot = i;
- if (free_slot == CLIENTS) { close(fd); continue; }
- clients[free_slot] = (Client){.fd = fd};
- }
- for (unsigned k = 1; k < count; ++k) {
- Client *c = &clients[slot[k]];
- if (fds[k].revents & (POLLIN | POLLHUP | POLLERR)) {
- ssize_t n = read(c->fd, c->in + c->in_used, sizeof(c->in) - c->in_used);
- if (n > 0) {
- c->in_used += (size_t)n;
- changed = serve(c, app, screen) || changed;
- } else if (n == 0 || (errno != EAGAIN && errno != EINTR)) { drop(c); continue; }
- }
- }
- /* Every open page hears of a change once. */
- if (changed)
- for (unsigned i = 0; i < CLIENTS; ++i)
- if (clients[i].fd >= 0 && clients[i].events)
- putf(&clients[i].out, "data: %llu\n\n", (unsigned long long)app->events);
- for (unsigned i = 0; i < CLIENTS; ++i) {
- Client *c = &clients[i];
- if (c->fd < 0) continue;
- while (c->out_at < c->out.used) {
- ssize_t n = write(c->fd, c->out.bytes + c->out_at, c->out.used - c->out_at);
- if (n <= 0) {
- if (n < 0 && (errno == EAGAIN || errno == EINTR)) break;
- c->closing = true;
- c->out_at = c->out.used;
- break;
- }
- c->out_at += (size_t)n;
- }
- if (c->out_at == c->out.used) c->out_at = c->out.used = 0;
- if (c->closing && !c->out.used) drop(c);
- }
- }
- for (unsigned i = 0; i < CLIENTS; ++i) if (clients[i].fd >= 0) drop(&clients[i]);
- close(listener);
- return 0;
-}
diff --git a/src/width.c b/src/width.c
deleted file mode 100644
index 1e893f6..0000000
--- a/src/width.c
+++ /dev/null
@@ -1,70 +0,0 @@
-#include "vega.h"
-
-typedef struct { uint32_t first, last; } Range;
-
-/* Zero-width: combining marks, Hangul vowel and final jamo, joiners,
- * variation selectors, format characters. */
-static const Range zero[] = {
- {0x0300, 0x036F}, {0x0483, 0x0489}, {0x0591, 0x05BD}, {0x0610, 0x061A}, {0x064B, 0x065F}, {0x0E31, 0x0E31},
- {0x0E34, 0x0E3A}, {0x0E47, 0x0E4E}, {0x1160, 0x11FF}, {0x1AB0, 0x1AFF}, {0x1DC0, 0x1DFF}, {0x200B, 0x200F},
- {0x202A, 0x202E}, {0x2060, 0x2064}, {0x20D0, 0x20FF}, {0x302A, 0x302F}, {0x3099, 0x309A}, {0xD7B0, 0xD7FF},
- {0xFE00, 0xFE0F}, {0xFE20, 0xFE2F}, {0xFEFF, 0xFEFF}, {0x1F3FB, 0x1F3FF}, {0xE0000, 0xE0FFF},
-};
-
-/* East Asian Wide and Fullwidth, and emoji shown as pictures. */
-static const Range wide[] = {
- {0x1100, 0x115F}, {0x231A, 0x231B}, {0x2329, 0x232A}, {0x23E9, 0x23EC}, {0x23F0, 0x23F0}, {0x23F3, 0x23F3},
- {0x25FD, 0x25FE}, {0x2614, 0x2615}, {0x2648, 0x2653}, {0x267F, 0x267F}, {0x2693, 0x2693}, {0x26A1, 0x26A1},
- {0x26AA, 0x26AB}, {0x26BD, 0x26BE}, {0x26C4, 0x26C5}, {0x26CE, 0x26CE}, {0x26D4, 0x26D4}, {0x26EA, 0x26EA},
- {0x26F2, 0x26F3}, {0x26F5, 0x26F5}, {0x26FA, 0x26FA}, {0x26FD, 0x26FD}, {0x2705, 0x2705}, {0x270A, 0x270B},
- {0x2728, 0x2728}, {0x274C, 0x274C}, {0x274E, 0x274E}, {0x2753, 0x2755}, {0x2757, 0x2757}, {0x2795, 0x2797},
- {0x27B0, 0x27B0}, {0x27BF, 0x27BF}, {0x2B1B, 0x2B1C}, {0x2B50, 0x2B50}, {0x2B55, 0x2B55}, {0x2E80, 0x303E},
- {0x3041, 0x33FF}, {0x3400, 0x4DBF}, {0x4E00, 0x9FFF}, {0xA000, 0xA4CF}, {0xA960, 0xA97F}, {0xAC00, 0xD7A3},
- {0xF900, 0xFAFF}, {0xFE10, 0xFE19}, {0xFE30, 0xFE6F}, {0xFF00, 0xFF60}, {0xFFE0, 0xFFE6}, {0x16FE0, 0x16FE4},
- {0x17000, 0x18CFF}, {0x1B000, 0x1B2FF}, {0x1F004, 0x1F004}, {0x1F0CF, 0x1F0CF}, {0x1F18E, 0x1F18E},
- {0x1F191, 0x1F19A}, {0x1F200, 0x1F251}, {0x1F300, 0x1F64F}, {0x1F680, 0x1F6FF}, {0x1F7E0, 0x1F7EB},
- {0x1F90C, 0x1F9FF}, {0x1FA70, 0x1FAFF}, {0x20000, 0x2FFFD}, {0x30000, 0x3FFFD},
-};
-
-static bool in(const Range *ranges, size_t count, uint32_t codepoint) {
- size_t low = 0, high = count;
- while (low < high) {
- size_t middle = (low + high) / 2;
- if (codepoint < ranges[middle].first) high = middle;
- else if (codepoint > ranges[middle].last) low = middle + 1;
- else return true;
- }
- return false;
-}
-
-unsigned vega_width(uint32_t codepoint) {
- if (in(zero, sizeof(zero) / sizeof(*zero), codepoint)) return 0;
- return in(wide, sizeof(wide) / sizeof(*wide), codepoint) ? 2 : 1;
-}
-
-uint32_t vega_visible(uint32_t codepoint) {
- if (codepoint < 0x20) return 0x2400 + codepoint;
- if (codepoint == 0x7F) return 0x2421;
- if (codepoint >= 0x80 && codepoint < 0xA0) return 0xFFFD;
- return codepoint;
-}
-
-size_t vega_decode(const char *text, size_t length, size_t at, uint32_t *codepoint) {
- const unsigned char *p = (const unsigned char *)text + at;
- size_t left = length - at, need = p[0] < 0x80 ? 1 : p[0] < 0xE0 ? 2 : p[0] < 0xF0 ? 3 : 4;
- if (need > left) { *codepoint = 0xFFFD; return left; }
- uint32_t value = need == 1 ? p[0] : need == 2 ? p[0] & 0x1Fu : need == 3 ? p[0] & 0x0Fu : p[0] & 0x07u;
- for (size_t i = 1; i < need; ++i) value = value << 6 | (p[i] & 0x3Fu);
- *codepoint = value;
- return need;
-}
-
-unsigned vega_text_width(const char *text, size_t length) {
- unsigned width = 0;
- for (size_t at = 0; at < length;) {
- uint32_t codepoint;
- at += vega_decode(text, length, at, &codepoint);
- width += vega_width(vega_visible(codepoint));
- }
- return width;
-}