diff options
Diffstat (limited to 'surfaces')
| -rw-r--r-- | surfaces/draw.c | 102 | ||||
| -rw-r--r-- | surfaces/main.c | 78 | ||||
| -rw-r--r-- | surfaces/pixels.c | 322 | ||||
| -rw-r--r-- | surfaces/vega.h | 51 |
4 files changed, 472 insertions, 81 deletions
diff --git a/surfaces/draw.c b/surfaces/draw.c index 31188c7..ec0b536 100644 --- a/surfaces/draw.c +++ b/surfaces/draw.c @@ -199,6 +199,7 @@ void vega_say(FILE *out, PureApp *app, const VegaScreen *screen, bool all) { /* ---- TUI ------------------------------------------------------------ */ bool vega_grid(VegaGrid *grid, unsigned width, unsigned height) { + grid->span_count = grid->text_used = 0; 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)); @@ -220,14 +221,73 @@ void vega_grid_free(VegaGrid *grid) { free(grid->cells); free(grid->shown); free(grid->rows); + free(grid->spans); + free(grid->text); *grid = (VegaGrid){0}; } -typedef struct { VegaGrid *grid; unsigned row, column, end; uint8_t tone; bool focus, strong; } Pen; +/* Where text goes. reach is where the pen's last text stopped (written: it + * wrote some): text written there follows it. right sets its text flush + * with its end, when set as type. */ +typedef struct { + VegaGrid *grid; + unsigned row, column, end; + uint8_t tone; + bool focus, strong, written, right; + unsigned reach; +} Pen; + +static Pen pen_at(VegaGrid *grid, unsigned row, unsigned column, unsigned end, uint8_t tone, bool focus, bool strong) { + return (Pen){grid, row, column, end, tone, focus, strong, false, false, 0}; +} + +/* The text as a span of the columns [pen's column, end), for a surface that + * sets type (the cells get it too; after them, stop() says where they + * reached). Out of memory, the span is lost: the cells still show it. */ +static void span(Pen *pen, const char *text, size_t length, unsigned end) { + VegaGrid *g = pen->grid; + if (!length || pen->column >= end) return; + if (g->span_count == g->spans_allocated) { + size_t n = g->spans_allocated ? 2 * g->spans_allocated : 64; + VegaSpan *grown = realloc(g->spans, n * sizeof(*grown)); + if (!grown) return; + g->spans = grown; + g->spans_allocated = n; + } + if (g->text_used + length > g->text_allocated) { + size_t n = g->text_allocated ? g->text_allocated : 4096; + while (n < g->text_used + length) n *= 2; + char *grown = realloc(g->text, n); + if (!grown) return; + g->text = grown; + g->text_allocated = n; + } + memcpy(g->text + g->text_used, text, length); + g->spans[g->span_count++] = (VegaSpan){pen->row, pen->column, end, pen->column, (uint32_t)g->text_used, + (uint32_t)length, pen->tone, pen->focus, pen->strong, + pen->written && pen->reach == pen->column, pen->right}; + g->text_used += length; +} + +/* The pen's text was written: where its cells reached. */ +static void stop(Pen *pen, size_t spans) { + VegaGrid *g = pen->grid; + if (g->span_count > spans) g->spans[g->span_count - 1].reach = pen->column; + pen->written = true; + pen->reach = pen->column; +} /* 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 put_cells(Pen *pen, const char *text, size_t length); static void write_text(Pen *pen, const char *text, size_t length) { + size_t spans = pen->grid->span_count; + span(pen, text, length, pen->end); + put_cells(pen, text, length); + stop(pen, spans); +} + +static void put_cells(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; @@ -261,16 +321,19 @@ static void fill(Pen *pen) { /* 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; + size_t spans = pen->grid->span_count; if (pen->column + width < end) pen->end = pen->column + width; + span(pen, text, length, pen->end); if (vega_text_width(text, length) > pen->end - pen->column && pen->end > pen->column) { --pen->end; - write_text(pen, text, length); + put_cells(pen, text, length); pen->end = end; - write_cstr(pen, "\xe2\x80\xa6"); + put_cells(pen, "\xe2\x80\xa6", 3); } else { - write_text(pen, text, length); + put_cells(pen, text, length); pen->end = end; } + stop(pen, spans); } static const char *key_symbol(const char *key) { @@ -294,8 +357,9 @@ static void draw_list(VegaGrid *grid, PureApp *app, const VegaPart *part, int in 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}; + Pen pen = pen_at(grid, top + r, 0, grid->width, (uint8_t)tone, focused, false); write_cstr(&pen, focused ? "> " : " "); + pen.written = false; /* the label starts at its column, however wide the marker is set */ 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; @@ -314,8 +378,13 @@ static void draw_list(VegaGrid *grid, PureApp *app, const VegaPart *part, int in 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}); + if (pen.column < start) { + Pen gap = pen_at(grid, pen.row, pen.column, start, pen.tone, focused, false); + fill(&gap); + } pen.column = start > pen.column ? start : pen.column; + pen.written = false; + pen.right = true; write_fit(&pen, badge, badge_length, badge_width - 2); } fill(&pen); @@ -344,7 +413,7 @@ static void draw_table(VegaGrid *grid, PureApp *app, const VegaPart *part, int i 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}; + Pen head = pen_at(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; @@ -355,13 +424,17 @@ static void draw_table(VegaGrid *grid, PureApp *app, const VegaPart *part, int i 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}; + Pen pen = pen_at(grid, top + 1 + r, 0, grid->width, (uint8_t)vega_tone(app, part, row), focused, false); write_cstr(&pen, focused ? "> " : " "); + pen.written = false; 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}); + if (pen.column < end) { + Pen gap = pen_at(grid, pen.row, pen.column, end, pen.tone, focused, false); + fill(&gap); + } pen.column = end; } fill(&pen); @@ -378,6 +451,7 @@ bool vega_hit(const VegaGrid *grid, unsigned line, unsigned *part, size_t *item) void vega_draw(VegaGrid *grid, PureApp *app, const VegaScreen *screen, const char *prompt, size_t prompt_length, bool typing) { const PureModule *m = app->module; + grid->span_count = grid->text_used = 0; 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; @@ -386,7 +460,7 @@ void vega_draw(VegaGrid *grid, PureApp *app, const VegaScreen *screen, const cha 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}; + Pen pen = pen_at(grid, row++, 0, grid->width, VEGA_NORMAL, false, true); write_fit(&pen, text, length, grid->width); } unsigned groups = 0; @@ -394,7 +468,7 @@ void vega_draw(VegaGrid *grid, PureApp *app, const VegaScreen *screen, const cha 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}; + Pen pen = pen_at(grid, row++, 0, grid->width, VEGA_MUTED, false, false); write_cstr(&pen, m->view_names[part->view]); write_cstr(&pen, " "); pen.tone = VEGA_NORMAL; @@ -411,7 +485,7 @@ void vega_draw(VegaGrid *grid, PureApp *app, const VegaScreen *screen, const cha ++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}; + Pen head = pen_at(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); @@ -426,7 +500,7 @@ void vega_draw(VegaGrid *grid, PureApp *app, const VegaScreen *screen, const cha row += share; } /* Keys the app answers to, then the text being typed. */ - Pen help = {grid, bottom, 0, grid->width, VEGA_MUTED, false, false}; + Pen help = pen_at(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; @@ -439,7 +513,7 @@ void vega_draw(VegaGrid *grid, PureApp *app, const VegaScreen *screen, const cha 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}; + Pen pen = pen_at(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; diff --git a/surfaces/main.c b/surfaces/main.c index d58625a..6c7e368 100644 --- a/surfaces/main.c +++ b/surfaces/main.c @@ -21,10 +21,11 @@ * say sentences (the first natural-language surface), driven by a script * web [--listen ADDRESS] [--port N] * a web page (a GUI), on 127.0.0.1 unless another address is given - * pixels --symbols FONT.vsym [--size PX] --grid WxH --out PREFIX - * the TUI's frames drawn with a font's glyphs, driven by a - * script: each show writes PREFIX-N.ppm after the text - * display --symbols FONT.vsym [--size PX] [--card PATH] [--writeback WxH --out PREFIX] + * pixels --symbols FONT.vsym... [--size PX] --grid WxH --out PREFIX + * the TUI's frames drawn in fonts (in order of preference; + * set as type when the first shapes), driven by a script: + * each show writes PREFIX-N.ppm after the text + * display --symbols FONT.vsym... [--size PX] [--card PATH] [--writeback WxH --out PREFIX] * [--live | --input EVENTS] * the same on a display through KMS, as many cells as fit, * driven by a script, or live: keyboards, mice and touch @@ -865,22 +866,37 @@ static int script(Surface surface, unsigned width, unsigned height, bool paintin /* Surfaces that draw glyphs. */ static bool glyphed(const char *surface) { return !strcmp(surface, "pixels") || !strcmp(surface, "display"); } -/* The symbols a pixel surface draws with, read whole and kept. */ -static bool open_face(const char *path, float size) { - static VegaSymbols symbols; - FILE *f = fopen(path, "rb"); - long length = f && !fseek(f, 0, SEEK_END) ? ftell(f) : -1; - unsigned char *bytes = length > 0 && !fseek(f, 0, SEEK_SET) ? malloc((size_t)length) : NULL; - bool ok = bytes && fread(bytes, 1, (size_t)length, f) == (size_t)length; - if (f) fclose(f); - char error[128] = "cannot read it"; - ok = ok && vega_symbols_open(&symbols, bytes, (size_t)length, error, sizeof(error)); - if (ok && !vega_face_open(&face, &symbols, size)) { - ok = false; - snprintf(error, sizeof(error), "no line and digit to lay a grid out with"); +/* The fonts a pixel surface draws with (--symbols, in order of + * preference), read whole and kept. */ +static const char *symbols_paths[VEGA_FACES]; +static unsigned symbols_count; +static bool add_symbols(const char *path) { + if (symbols_count == VEGA_FACES) return false; + symbols_paths[symbols_count++] = path; + return true; +} +static bool open_face(float size) { + static VegaSymbols symbols[VEGA_FACES]; + const VegaSymbols *opened[VEGA_FACES]; + char error[160] = "cannot read it"; + for (unsigned k = 0; k < symbols_count; ++k) { + const char *path = symbols_paths[k]; + FILE *f = fopen(path, "rb"); + long length = f && !fseek(f, 0, SEEK_END) ? ftell(f) : -1; + unsigned char *bytes = length > 0 && !fseek(f, 0, SEEK_SET) ? malloc((size_t)length) : NULL; + bool ok = bytes && fread(bytes, 1, (size_t)length, f) == (size_t)length; + if (f) fclose(f); + if (!ok || !vega_symbols_open(&symbols[k], bytes, (size_t)length, error, sizeof(error))) { + fprintf(stderr, "vega: %s: %s\n", path, error); + return false; + } + opened[k] = &symbols[k]; /* the bytes stay: the face reads them */ } - if (!ok) fprintf(stderr, "vega: %s: %s\n", path, error); - return ok; /* the bytes stay: the face reads them */ + if (!vega_face_open(&face, opened, symbols_count, size, error, sizeof(error))) { + fprintf(stderr, "vega: %s: %s\n", symbols_paths[0], error); + return false; + } + return true; } #ifdef VEGA_SURFACE @@ -899,7 +915,7 @@ __attribute__((visibility("default"))) int pure_surface_run(const PureSurface *s fputs("vega: this surface was built for another Pure\n", stderr); return 2; } - const char *symbols_path = NULL, *card = "/dev/dri/card1", *recorded = NULL; + const char *card = "/dev/dri/card1", *recorded = NULL; unsigned width = 0, height = 0; float size = 16; bool on_display = argc > 1 && !strcmp(argv[1], "display"), pixels = argc > 1 && !strcmp(argv[1], "pixels"); @@ -911,7 +927,7 @@ __attribute__((visibility("default"))) int pure_surface_run(const PureSurface *s continue; } if (i + 1 >= argc) usage = true; - else if (!strcmp(argv[i], "--symbols")) symbols_path = argv[i + 1]; + else if (!strcmp(argv[i], "--symbols")) usage = !add_symbols(argv[i + 1]); else if (!strcmp(argv[i], "--size") && sscanf(argv[i + 1], "%f", &size) == 1) {} else if (!strcmp(argv[i], "--card") && on_display) card = argv[i + 1]; else if (!strcmp(argv[i], "--input")) recorded = argv[i + 1]; @@ -922,15 +938,15 @@ __attribute__((visibility("default"))) int pure_surface_run(const PureSurface *s writeback = on_display; } else usage = true; } - if (usage || !symbols_path || (writeback && !frames_prefix) || (pixels && (!width || !frames_prefix)) || + if (usage || !symbols_count || (writeback && !frames_prefix) || (pixels && (!width || !frames_prefix)) || (live_input && recorded)) { - fputs("vega-surface.so display --symbols FONT.vsym [--size PX] [--card PATH] [--writeback WIDTHxHEIGHT --out PREFIX]\n" + fputs("vega-surface.so display --symbols FONT.vsym... [--size PX] [--card PATH] [--writeback WIDTHxHEIGHT --out PREFIX]\n" " [--live | --input EVENTS]\n" - "vega-surface.so pixels --symbols FONT.vsym [--size PX] --grid WIDTHxHEIGHT --out PREFIX [--input EVENTS]\n", + "vega-surface.so pixels --symbols FONT.vsym... [--size PX] --grid WIDTHxHEIGHT --out PREFIX [--input EVENTS]\n", stderr); return 2; } - if (!open_face(symbols_path, size)) return 2; + if (!open_face(size)) return 2; unsigned columns = width, rows = height, pixels_across = width * (unsigned)face.cell_width, pixels_down = height * (unsigned)face.cell_height; if (on_display) { @@ -967,7 +983,7 @@ __attribute__((visibility("default"))) int pure_surface_run(const PureSurface *s #else int main(int argc, char **argv) { unsigned width = 0, height = 0, port = 8080; - const char *symbols_path = NULL, *card = "/dev/dri/card1"; + const char *card = "/dev/dri/card1"; float size = 16; bool writeback = false, live_input = false; const char *recorded = NULL; @@ -1003,24 +1019,24 @@ int main(int argc, char **argv) { writeback = true; else if (!strcmp(argv[i], "--card") && !strcmp(argv[1], "display")) card = argv[i + 1]; else if (!strcmp(argv[i], "--input") && !strcmp(argv[1], "display")) recorded = argv[i + 1]; - else if (!strcmp(argv[i], "--symbols") && glyphed(argv[1])) symbols_path = argv[i + 1]; + else if (!strcmp(argv[i], "--symbols") && glyphed(argv[1])) usage = !add_symbols(argv[i + 1]); else if (!strcmp(argv[i], "--out") && glyphed(argv[1])) frames_prefix = argv[i + 1]; else if (!strcmp(argv[i], "--size") && glyphed(argv[1]) && sscanf(argv[i + 1], "%f", &size) == 1) {} else usage = true; } bool pixels = argc > 1 && !strcmp(argv[1], "pixels"), on_display = argc > 1 && !strcmp(argv[1], "display"); if (usage || (dumped && strcmp(argv[1], "tui") && !pixels) || (!strcmp(argv[1], "check") && argc != 3) || - (pixels && (!dumped || !symbols_path || !frames_prefix)) || (on_display && (!symbols_path || (writeback && !frames_prefix)))) { + (pixels && (!dumped || !symbols_count || !frames_prefix)) || (on_display && (!symbols_count || (writeback && !frames_prefix)))) { fputs("vega tui|cli|say MODULE.pmod [--store PATH]\n" "vega tui MODULE.pmod [--store PATH] [--watch] | --dump|--paint WIDTHxHEIGHT < script\n" "vega web MODULE.pmod [--store PATH] [--listen ADDRESS] [--port N] [--watch]\n" - "vega pixels MODULE.pmod [--store PATH] --symbols FONT.vsym [--size PX] --grid WIDTHxHEIGHT --out PREFIX < script\n" - "vega display MODULE.pmod [--store PATH] --symbols FONT.vsym [--size PX] [--card PATH]\n" + "vega pixels MODULE.pmod [--store PATH] --symbols FONT.vsym... [--size PX] --grid WIDTHxHEIGHT --out PREFIX < script\n" + "vega display MODULE.pmod [--store PATH] --symbols FONT.vsym... [--size PX] [--card PATH]\n" " [--writeback WIDTHxHEIGHT --out PREFIX] [--live | --input EVENTS] [--access] [< script]\n" "vega check MODULE.pmod\n", stderr); return 2; } - if ((pixels || on_display) && !open_face(symbols_path, size)) return 2; + if ((pixels || on_display) && !open_face(size)) return 2; if (on_display) { char error[256]; if (!(display = vega_display_open(card, writeback, writeback ? width : 0, writeback ? height : 0, error, sizeof(error)))) { diff --git a/surfaces/pixels.c b/surfaces/pixels.c index 304e96d..d58d5b2 100644 --- a/surfaces/pixels.c +++ b/surfaces/pixels.c @@ -1,33 +1,93 @@ #include "vega.h" +#include "text.h" #include <math.h> +#include <stdio.h> #include <stdlib.h> #include <string.h> -/* The pixel surface: the TUI's grid of cells, each drawn with its glyph - * (drawn once per size, the exact area of its curves) in its tone's colour. - * The view's cells go straight into pixels: no page, no scene. */ +/* The pixel surface: the TUI's grid drawn in a typeface, glyphs drawn once + * per size (the exact area of their curves) in their tone's colour, straight + * into pixels: no page, no scene. Two ways: + * - cells: each cell's character in its cell, in the first font that has it; + * - type, when the first font shapes: the layout's spans set as lines of + * text (text/layout.h). A span starts at its column, or where the text it + * follows stopped; it may go on to its end, but not past the next span's + * column that starts afresh; one that would is cut with an ellipsis. So + * the grid lays out (rows, columns, what is focused) and type shapes. */ /* Colours, 0xRRGGBB: the background, a focused row's, and one per tone. */ static const uint32_t background = 0x1e1e2e, focused = 0x313244, strong = 0xffffff; static const uint32_t tones[VEGA_TONES] = {0xcdd6f4, 0x7f849c, 0x89b4fa, 0xa6e3a1, 0xf9e2af, 0xf38ba8}; -bool vega_face_open(VegaFace *face, const VegaSymbols *symbols, float pixels) { +/* Whether a font that shapes has a character: its character map does, and + * its file holds that glyph (a file holds the glyphs its codes reach). */ +static bool has(void *context, unsigned font, uint32_t code) { + const VegaFace *face = context; + unsigned k = face->font_face[font]; + uint32_t glyph = vega_shaping_glyph(&face->shaping[k], code); + VegaSymbol symbol; + return glyph && vega_symbol(face->symbols[k], VEGA_GLYPH_SYMBOLS + glyph, &symbol); +} + +bool vega_face_open(VegaFace *face, const VegaSymbols *const *symbols, unsigned count, float pixels, char *error, + size_t error_size) { *face = (VegaFace){0}; - if (!symbols->units || symbols->ascent <= symbols->descent || pixels < 4 || pixels > 256) return false; - float scale = pixels / symbols->units; + face->fonts = (VegaFonts){{0}, 0, has, face}; + face->pixels = pixels; + if (!count || count > VEGA_FACES || pixels < 4 || pixels > 256) { + snprintf(error, error_size, "1 to %d fonts, at 4 to 256 pixels", VEGA_FACES); + return false; + } /* A cell is as wide as a digit (the grid is monospace: wide characters - * take two) and as tall as the font's line. */ + * take two) and as tall as the first font's line. */ + const VegaSymbols *first = symbols[0]; VegaSymbol zero; - if (!vega_symbol(symbols, '0', &zero) || zero.advance <= 0) return false; + if (!first->units || first->ascent <= first->descent || !vega_symbol(first, '0', &zero) || zero.advance <= 0) { + snprintf(error, error_size, "no line and digit to lay a grid out with"); + return false; + } + float scale = pixels / first->units; face->cell_width = (int)lroundf(zero.advance * scale); - face->baseline = (int)ceilf(symbols->ascent * scale); - face->cell_height = face->baseline + (int)ceilf(-symbols->descent * scale); - face->glyphs = vega_glyphs_open(symbols, scale); - return face->glyphs && face->cell_width > 0; + face->baseline = (int)ceilf(first->ascent * scale); + face->cell_height = face->baseline + (int)ceilf(-first->descent * scale); + if (face->cell_width <= 0) { + snprintf(error, error_size, "a digit narrower than a pixel"); + return false; + } + for (unsigned k = 0; k < count; ++k) { + face->symbols[k] = symbols[k]; + face->glyphs[k] = symbols[k]->units ? vega_glyphs_open(symbols[k], pixels / symbols[k]->units) : NULL; + face->count = k + 1; + if (!face->glyphs[k]) { + snprintf(error, error_size, "font %u: no glyphs to draw", k + 1); + goto fail; + } + /* Type is set when the first font shapes, in the fonts that do. */ + if (!symbols[k]->shaping || (k && !face->fonts.count)) continue; + char why[128]; + if (!vega_shaping_open(&face->shaping[k], symbols[k]->shaping, symbols[k]->shaping_size, why, sizeof(why))) { + snprintf(error, error_size, "font %u: %s", k + 1, why); + goto fail; + } + face->font_face[face->fonts.count] = k; + face->fonts.fonts[face->fonts.count++] = &face->shaping[k]; + } + if (face->fonts.count && !(face->shaper = vega_shaper_new())) { + snprintf(error, error_size, "out of memory"); + goto fail; + } + return true; +fail: + vega_face_close(face); + return false; } void vega_face_close(VegaFace *face) { - vega_glyphs_close(face->glyphs); + for (unsigned k = 0; k < face->count; ++k) vega_glyphs_close(face->glyphs[k]); + for (unsigned f = 0; f < face->fonts.count; ++f) vega_shaping_close(&face->shaping[face->font_face[f]]); + vega_shaper_free(face->shaper); + vega_layout_free(&face->layout); + free(face->work); *face = (VegaFace){0}; } @@ -60,9 +120,10 @@ static void draw(uint32_t *out, size_t stride, int width, int height, const Vega } } -/* A character the symbols lack: a box in its cell. */ +/* A character no font has: a box where it goes. */ static void missing(uint32_t *out, size_t stride, int x, int y, int width, int height, int limit, uint32_t color) { int left = x + 1, right = (x + width < limit ? x + width : limit) - 2, top = y + height / 5, bottom = y + height - height / 5; + if (left < 0 || right < left) return; for (int i = left; i <= right; ++i) out[(size_t)top * stride + (size_t)i] = out[(size_t)bottom * stride + (size_t)i] = color; for (int j = top; j <= bottom; ++j) out[(size_t)j * stride + (size_t)left] = out[(size_t)j * stride + (size_t)right] = color; } @@ -71,6 +132,220 @@ void vega_pixels_clear(uint32_t *out, size_t stride, unsigned width, unsigned he fill(out, stride, 0, 0, (int)width, (int)height, background); } +static uint32_t color_of(uint8_t tone, bool is_strong) { return is_strong ? strong : tones[tone < VEGA_TONES ? tone : 0]; } + +/* Cells [first, end) of a row, each character in the first font that has it. */ +static void draw_cells(const VegaGrid *grid, VegaFace *face, unsigned r, unsigned first, unsigned end, uint32_t *out, + size_t stride, int width, int height) { + for (unsigned c = first; c < end && c < grid->width; ++c) { + const VegaCell *cell = &grid->cells[(size_t)r * grid->width + c]; + if (!cell->ch || cell->ch == ' ') continue; + uint32_t color = color_of(cell->tone, cell->strong); + int x = (int)c * face->cell_width, y = (int)r * face->cell_height; + const VegaBitmap *glyph = NULL; + unsigned k = 0; + while (k < face->count && !(glyph = vega_glyph(face->glyphs[k], cell->ch))) ++k; + unsigned cells = vega_width(cell->ch) == 2 ? 2 : 1; + /* A wide character's glyph is centred in its two cells: a CJK font's full width is + * less than two cells of a monospace Latin font. */ + int centred = glyph && cells == 2 && glyph->advance < 2 * face->cell_width + ? (2 * face->cell_width - glyph->advance) / 2 : 0; + if (!glyph) missing(out, stride, x, y, face->cell_width * (int)cells, face->cell_height, width, color); + else draw(out, stride, width, height, glyph, x + centred, y + face->baseline, color); + if (!cell->mark) continue; + for (k = 0; k < face->count; ++k) + if ((glyph = vega_glyph(face->glyphs[k], cell->mark))) { + draw(out, stride, width, height, glyph, x, y + face->baseline, color); + break; + } + } +} + +/* ---- setting type ----------------------------------------------------- */ + +/* What setting works in: a line's characters, room for trying it cut, its + * grapheme boundaries, where each of its spans starts in it, and the spans + * in the order they are set. */ +typedef struct { + uint32_t *codes, *cut; + uint8_t *clusters; + size_t *order, *starts; +} Work; + +static bool work_for(VegaFace *face, const VegaGrid *grid, Work *w) { + /* A line's characters are at most the text's bytes. */ + size_t most = grid->text_used; + size_t need = 2 * grid->span_count * sizeof(size_t) + (2 * most + 2) * sizeof(uint32_t) + most + 1; + if (need > face->work_size) { + void *grown = realloc(face->work, need); + if (!grown) return false; + face->work = grown; + face->work_size = need; + } + w->order = face->work; + w->starts = w->order + grid->span_count; + w->codes = (uint32_t *)(w->starts + grid->span_count); + w->cut = w->codes + most + 1; + w->clusters = (uint8_t *)(w->cut + most + 1); + return true; +} + +/* A line set: where its pen starts (in the layout's x), how far all its + * glyphs go and how much of that shows (spaces at its end left out), its + * direction, and how many of its characters it keeps (all, or those before + * an ellipsis). */ +typedef struct { int32_t start, advance, shows; bool rtl; size_t kept; } Set; +typedef enum { SET, NO_ROOM, UNSET } Setting; + +static bool set_line(VegaFace *face, const uint32_t *codes, size_t count, Set *out) { + VegaLayout *l = &face->layout; + if (!vega_layout(l, face->shaper, &face->fonts, codes, count, VEGA_BIDI_AUTO, 0) || l->line_count != 1) return false; + const VegaLine *line = &l->lines[0]; + int32_t advance = 0; + for (size_t g = line->first; g < line->first + line->count; ++g) advance += l->glyphs[g].advance; + /* Right to left, the spaces at its end hang past its left edge. */ + out->rtl = l->level & 1; + out->start = out->rtl ? line->left - (advance - line->width) : line->left; + out->advance = advance; + out->shows = line->width; + out->kept = count; + return true; +} + +/* codes[0, count) cut after n characters, spaces before the cut left out, + * with an ellipsis: in w->cut, its length returned. */ +static size_t cut_at(Work *w, size_t n) { + while (n && vega_bidi_class(w->codes[n - 1]) == VEGA_BIDI_WS) --n; + memcpy(w->cut, w->codes, n * sizeof(*w->cut)); + w->cut[n] = 0x2026; + return n + 1; +} + +/* Set codes[0, count) to show within room (the first font's units): whole, + * or the longest start of it, of whole grapheme clusters, that fits with an + * ellipsis after it. */ +static Setting set_within(VegaFace *face, Work *w, size_t count, int32_t room, Set *out) { + if (!set_line(face, w->codes, count, out)) return UNSET; + if (out->shows <= room) return SET; + vega_graphemes(w->codes, count, w->clusters); + size_t low = 0, high = count, best = SIZE_MAX; /* the cut is a cluster boundary in [low, high) */ + while (low < high) { + size_t middle = low + (high - low) / 2; + while (middle > low && !w->clusters[middle]) --middle; + Set tried; + if (!set_line(face, w->cut, cut_at(w, middle), &tried)) return UNSET; + if (tried.shows <= room) { + best = middle; + for (low = middle + 1; low < high && !w->clusters[low];) ++low; + } else + high = middle; + } + if (best == SIZE_MAX) return NO_ROOM; + size_t kept = best; + if (!set_line(face, w->cut, cut_at(w, best), out)) return UNSET; + out->kept = kept; + return SET; +} + +/* The layout's glyphs with their pen starting at x (pixels), each in the + * colour of the span its character came from (an ellipsis: the one it + * cuts). */ +static void draw_set(VegaFace *face, const Set *set, const VegaSpan *const *spans, const size_t *starts, size_t count, + double x, int top, uint32_t *out, size_t stride, int width, int height) { + const VegaLayout *l = &face->layout; + double scale = (double)face->pixels / face->fonts.fonts[0]->upem; + for (size_t g = 0; g < l->glyph_count; ++g) { + const VegaPlaced *p = &l->glyphs[g]; + size_t character = p->cluster < set->kept ? p->cluster : set->kept ? set->kept - 1 : 0, k = 0; + while (k + 1 < count && starts[k + 1] <= character) ++k; + uint32_t color = color_of(spans[k]->tone, spans[k]->strong); + unsigned font = face->font_face[p->font]; + const VegaBitmap *b = vega_glyph(face->glyphs[font], VEGA_GLYPH_SYMBOLS + p->glyph); + int gx = (int)lround(x + (p->x - set->start) * scale); + if (b) draw(out, stride, width, height, b, gx, top + face->baseline - (int)lround(p->y * scale), color); + else if (p->advance > 0) + missing(out, stride, gx, top, (int)lround(p->advance * scale), face->cell_height, width, color); + } +} + +static bool before(const VegaSpan *spans, size_t a, size_t b) { + const VegaSpan *x = &spans[a], *y = &spans[b]; + if (x->row != y->row) return x->row < y->row; + if (x->column != y->column) return x->column < y->column; + return a < b; +} + +/* Spans by row, then column, then as written: an insertion sort, as they + * come nearly in order. */ +static void sort_spans(size_t *order, size_t count, const VegaSpan *spans) { + for (size_t i = 0; i < count; ++i) order[i] = i; + for (size_t i = 1; i < count; ++i) + for (size_t k = i; k && before(spans, order[k], order[k - 1]); --k) { + size_t t = order[k]; + order[k] = order[k - 1]; + order[k - 1] = t; + } +} + +/* Each line of text: a span, and those that follow it. It starts at the + * span's column (a right one ends at its end) and has until the nearest of + * its spans' ends and the next span on the row. */ +static bool set_type(const VegaGrid *grid, VegaFace *face, uint32_t *out, size_t stride, int width, int height) { + Work w; + if (!work_for(face, grid, &w)) return false; + size_t n = grid->span_count; + sort_spans(w.order, n, grid->spans); + double scale = (double)face->pixels / face->fonts.fonts[0]->upem; + int caret = -1; + const VegaSpan *line[VEGA_TEXT]; + for (size_t i = 0, j; i < n; i = j) { + const VegaSpan *s = &grid->spans[w.order[i]]; + unsigned end = s->end, reach = s->reach; + size_t spans = 0, count = 0; + for (j = i; j < n && spans < VEGA_TEXT; ++j) { + const VegaSpan *t = &grid->spans[w.order[j]]; + if (j > i && (t->row != s->row || !t->follows)) break; + if (t->end < end) end = t->end; + reach = t->reach; + line[spans] = t; + w.starts[spans++] = count; + for (size_t at = 0; at < t->length;) { + uint32_t code; + at += vega_decode(grid->text + t->at, t->length, at, &code); + code = vega_visible(code); + w.codes[count++] = code == 0x2028 || code == 0x2029 ? ' ' : code; /* one line: no breaks in it */ + } + } + if (j < n && grid->spans[w.order[j]].row == s->row && grid->spans[w.order[j]].column < end) + end = grid->spans[w.order[j]].column; + if (end > grid->width) end = grid->width; + if (end <= s->column) continue; + double box = (double)s->column * face->cell_width, limit = (double)end * face->cell_width; + int top = (int)s->row * face->cell_height; + Set set; + Setting setting = set_within(face, &w, count, (int32_t)floor((limit - box) / scale), &set); + if (setting == UNSET) { + /* No font can set it (none has a plan for its script): its cells, then. */ + draw_cells(grid, face, s->row, s->column, reach, out, stride, width, height); + continue; + } + if (setting == NO_ROOM) continue; + /* Right to left, the spaces at its end hang past its left edge. */ + double hang = (set.advance - set.shows) * scale, x = s->right ? limit - set.advance * scale + (set.rtl ? 0 : hang) + : box - (set.rtl ? hang : 0); + draw_set(face, &set, line, w.starts, spans, x, top, out, stride, width, height); + /* The cursor at the end of the row's last line: right to left, its left. */ + if ((int)s->row == grid->cursor_row) caret = (int)lround(set.rtl ? (x > box ? x : box) : x + set.advance * scale); + } + /* The text cursor, after the typed text. */ + if (caret >= 0) { + int top = grid->cursor_row * face->cell_height, bar = face->cell_height / 16 + 1; + int x = caret < width - bar ? caret : width - bar; + fill(out, stride, x, top + face->cell_height / 8, bar, face->cell_height - face->cell_height / 4, tones[VEGA_NORMAL]); + } + return true; +} + void vega_pixels(const VegaGrid *grid, VegaFace *face, uint32_t *out, size_t stride) { int width = (int)grid->width * face->cell_width, height = (int)grid->height * face->cell_height; /* Backgrounds first: a wide glyph spans its neighbour's cell. */ @@ -78,21 +353,6 @@ void vega_pixels(const VegaGrid *grid, VegaFace *face, uint32_t *out, size_t str for (unsigned c = 0; c < grid->width; ++c) fill(out, stride, (int)c * face->cell_width, (int)r * face->cell_height, face->cell_width, face->cell_height, grid->cells[(size_t)r * grid->width + c].focus ? focused : background); - for (unsigned r = 0; r < grid->height; ++r) - for (unsigned c = 0; c < grid->width; ++c) { - const VegaCell *cell = &grid->cells[(size_t)r * grid->width + c]; - if (!cell->ch || cell->ch == ' ') continue; - uint32_t color = cell->strong ? strong : tones[cell->tone < VEGA_TONES ? cell->tone : 0]; - int x = (int)c * face->cell_width, y = (int)r * face->cell_height; - const VegaBitmap *glyph = vega_glyph(face->glyphs, cell->ch); - unsigned cells = vega_width(cell->ch) == 2 ? 2 : 1; - /* A wide character's glyph is centred in its two cells: a CJK font's full width is - * less than two cells of a monospace Latin font. */ - int centred = glyph && cells == 2 && glyph->advance < 2 * face->cell_width - ? (2 * face->cell_width - glyph->advance) / 2 : 0; - if (!glyph) missing(out, stride, x, y, face->cell_width * (int)cells, face->cell_height, width, color); - else draw(out, stride, width, height, glyph, x + centred, y + face->baseline, color); - if (cell->mark && (glyph = vega_glyph(face->glyphs, cell->mark))) - draw(out, stride, width, height, glyph, x, y + face->baseline, color); - } + if (face->fonts.count && set_type(grid, face, out, stride, width, height)) return; + for (unsigned r = 0; r < grid->height; ++r) draw_cells(grid, face, r, 0, grid->width, out, stride, width, height); } diff --git a/surfaces/vega.h b/surfaces/vega.h index 9f30ee4..ff880c5 100644 --- a/surfaces/vega.h +++ b/surfaces/vega.h @@ -1,5 +1,6 @@ #ifndef VEGA_H #define VEGA_H +#include "layout.h" #include "module.h" #include "symbols.h" #include <poll.h> @@ -108,6 +109,18 @@ typedef struct { uint8_t tone; bool focus, strong; } VegaCell; +/* What the layout wrote as text, for a surface that sets type rather than + * filling cells: a piece of UTF-8 given the columns [column, end) of a row, + * whose cells it filled up to reach. It follows the span before it when it + * goes on where that one's text stopped (the same line of text, in another + * tone perhaps): set as text, it starts where that one's ends, not at its + * column. right: flush with its end (a badge). */ +typedef struct { + unsigned row, column, end, reach; + uint32_t at, length; /* in the grid's text */ + uint8_t tone; + bool focus, strong, follows, right; +} VegaSpan; typedef struct { unsigned width, height; VegaCell *cells, *shown; @@ -115,6 +128,10 @@ typedef struct { 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 */ + VegaSpan *spans; + size_t span_count, spans_allocated; + char *text; + size_t text_used, text_allocated; } VegaGrid; bool vega_grid(VegaGrid *, unsigned width, unsigned height); void vega_grid_free(VegaGrid *); @@ -124,14 +141,38 @@ void vega_draw(VegaGrid *, PureApp *, const VegaScreen *, const char *prompt, si /* The part and item a line of the last drawn grid shows. */ bool vega_hit(const VegaGrid *, unsigned line, unsigned *part, size_t *item); -/* Pixels: the grid drawn with a font's glyphs (a symbol file, pixels per em). - * A cell is as wide as a digit and as tall as the font's line; each glyph is - * drawn once, the first time a cell shows it. */ +/* Pixels: the grid drawn in a typeface, fonts (symbol files) in order of + * preference at pixels per em. A cell is as wide as the first font's digit + * and as tall as its line; each glyph is drawn once, the first time it + * shows. When the first font shapes (its file has a shaping section), the + * pixels set type: each span the layout wrote is set as a line of text + * (text/layout.h: bidi, scripts, each grapheme cluster in the first of the + * fonts that shape and have it), from its column or from where the text it + * follows stopped, and cut with an ellipsis where it would pass its end or + * the next span's column; a text cursor after the typed text. Otherwise + * each cell's character is drawn in its cell, in the first font that has + * it. */ +enum { VEGA_FACES = 8 }; typedef struct { - VegaGlyphs *glyphs; + unsigned count; + const VegaSymbols *symbols[VEGA_FACES]; /* kept by the caller while the face is open */ + VegaGlyphs *glyphs[VEGA_FACES]; + VegaShaping shaping[VEGA_FACES]; /* of the fonts that shape */ int cell_width, cell_height, baseline; + float pixels; + /* Setting type: the fonts that shape (VegaFonts, each one of the + * faces'), and what setting works in. */ + VegaFonts fonts; + unsigned font_face[VEGA_LAYOUT_FONTS]; + VegaShaper *shaper; + VegaLayout layout; + void *work; + size_t work_size; } VegaFace; -bool vega_face_open(VegaFace *, const VegaSymbols *, float pixels); +/* Open count fonts as a face (it must not move while open: it points into + * itself); false with why. */ +bool vega_face_open(VegaFace *, const VegaSymbols *const *symbols, unsigned count, float pixels, char *error, + size_t error_size); void vega_face_close(VegaFace *); /* The grid into out: 0xRRGGBB pixels, grid->width * cell_width across and * grid->height * cell_height down, stride pixels apart. */ |
