From 2920e389f624263efe515d9a5f33f6b822efff5e Mon Sep 17 00:00:00 2001 From: eunhhu Date: Sun, 27 Sep 2026 13:45:31 +0100 Subject: Name Vega's parts: lib/, surfaces/ and symbols/ src/ held two parts, the surfaces that show modules to people and the symbol format with its CPU and GPU drawing. As in Pure (design 009), top level directories are now the parts, and the README says what each builds. --- src/draw.c | 456 --------------------------- src/main.c | 604 ------------------------------------ src/screen.c | 229 -------------- src/symbols-gl.c | 260 ---------------- src/symbols-gl.h | 24 -- src/symbols-tool.c | 881 ----------------------------------------------------- src/symbols.c | 185 ----------- src/symbols.h | 54 ---- src/vega.h | 124 -------- src/web.c | 453 --------------------------- src/width.c | 70 ----- 11 files changed, 3340 deletions(-) delete mode 100644 src/draw.c delete mode 100644 src/main.c delete mode 100644 src/screen.c delete mode 100644 src/symbols-gl.c delete mode 100644 src/symbols-gl.h delete mode 100644 src/symbols-tool.c delete mode 100644 src/symbols.c delete mode 100644 src/symbols.h delete mode 100644 src/vega.h delete mode 100644 src/web.c delete mode 100644 src/width.c (limited to 'src') 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 -#include - -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 -#include -#include -#include -#include -#include -#include -#include - -/* 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 -#include -#include - -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 _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 -#include -#include - -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 - -/* 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 -#include -#include -#include FT_FREETYPE_H -#include FT_OUTLINE_H -#include -#include -#include -#include -#include - -#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\"", "') : 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, "'), *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 -#include -#include -#include - -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 -#include -#include - -/* 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 - -/* 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 `
_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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* 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 -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[] = { -"" -"Vega
", -"

" -"
" -""}; - -/* 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; -} -- cgit v1.2.3