aboutsummaryrefslogtreecommitdiffstats
path: root/src/width.c
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-25 15:24:05 +0100
committereunhhu <[email protected]>2026-09-25 15:24:05 +0100
commit12c377ea70c8928f96d02d0885744bd6818c10c6 (patch)
treee2ba24cb2cd61394ff84f605daa54d8c8e36a0d1 /src/width.c
parentd9dbe8a1dbdd30f3e336dc1445d852ede3de3930 (diff)
downloadvega-12c377ea70c8928f96d02d0885744bd6818c10c6.tar.gz
vega-12c377ea70c8928f96d02d0885744bd6818c10c6.zip
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, <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.
Diffstat (limited to 'src/width.c')
-rw-r--r--src/width.c70
1 files changed, 70 insertions, 0 deletions
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;
+}