From 12c377ea70c8928f96d02d0885744bd6818c10c6 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Fri, 25 Sep 2026 15:24:05 +0100 Subject: Add the first component and three surfaces Vega reads a verified Pure module by convention: a table view with a label column is a list (with optional badge, detail and tone), any other table is a table, _focus is its focused row, a title view is the title and other views are fields. lib/vega.aup gives aup apps the tones, which say what a row means; each surface decides how it looks or sounds. vega tui draws the screen in a terminal, keeps the focused row in view and evaluates only the cells on screen; vega cli prints plain lines for pipes; vega say speaks the screen and each focus change as sentences. Display width handles Hangul, CJK, emoji, combining marks and controls. tests/check.py compares the CLI and NUI output with a reference model of examples/todo.aup line for line, checks 9,600 TUI frames for scrolling, focus, badge alignment and the number of cells evaluated, drives the interactive TUI on a pseudo-terminal, and runs every aup example on every surface. --- src/width.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/width.c (limited to 'src/width.c') diff --git a/src/width.c b/src/width.c new file mode 100644 index 0000000..1e893f6 --- /dev/null +++ b/src/width.c @@ -0,0 +1,70 @@ +#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