Skip to content

Commit d4bc79b

Browse files
authored
🧼 generate compact wcwidth tables (#49)
* ref(wcwidth): generate efficient wcwidth tables * ref(wcwidth): deslop * perf(wcwidth): add BMP filter + fast path, use single table for binary search * ref(ci): add verify task * deno fmt * ref(wcwidth): generate clang-format friendly code * fix(verify): handle --check flag * deno lint * ref(wcwidth): add datahash * ref(wcwidth): correctness pass * ref(wcwidth): performance pass * ref(wcwidth): upgrade to unicode 17 * ref(wcwidth): regen * fix format * fix(render): measure non-printable codepoints as one cell The draw path coerces wcwidth() == -1 to one cell and emits U+FFFD, but the Clay measure callback skipped those codepoints entirely, so a fit-sized box around text containing a control character or noncharacter measured narrower than what the renderer draws. Mirror draw_text in measure(): coerce -1 to 1, and substitute U+FFFD on UTF-8 decode failure instead of passing an uninitialized codepoint to wcwidth(). * fix(wcwidth): treat surrogates as non-printable utf8_decode() accepts CESU-8 surrogate encodings, and the Unicode 17 tables default unlisted codepoints to width 1, so a surrogate reaching draw_text was emitted to the terminal as invalid UTF-8 (the old termbox2 table returned -1 for U+D800..U+DFFF and the emit path sanitized it to U+FFFD). Guard the surrogate range in the generated wcwidth() so iswprint() rejects it and the emit path substitutes U+FFFD again. Also derive the generated header's Unicode version from the same constant as the data URL; it was left saying 16.0 after the 17.0 upgrade.
1 parent 917616b commit d4bc79b

6 files changed

Lines changed: 838 additions & 1106 deletions

File tree

.github/workflows/verify.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ jobs:
3434
# Restore to v2.x once v2.9.1 ships.
3535
deno-version: v2.8.3
3636

37-
- name: format
38-
run: deno task fmt:check
37+
- name: verify
38+
run: deno task verify
3939

4040
- name: lint
4141
run: deno lint

deno.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
"fmt": "deno fmt && clang-format -i src/*.c src/*.h",
77
"fmt:check": "deno fmt --check && clang-format --dry-run --Werror src/*.c src/*.h",
88
"build:npm": "deno run -A tasks/build-npm.ts",
9-
"bench": "deno run -A bench/mod.ts"
9+
"bench": "deno run -A bench/mod.ts",
10+
"gen-wcwidth": "deno run --allow-net --allow-write=src/wcwidth.c tasks/gen-wcwidth.ts",
11+
"gen-wcwidth:check": "deno run --allow-net --allow-read=src/wcwidth.c tasks/gen-wcwidth.ts --check",
12+
"verify": "deno task fmt:check && deno task gen-wcwidth:check"
1013
},
1114
"imports": {
1215
"@std/testing": "jsr:@std/testing@1",

src/clayterm.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -856,8 +856,13 @@ void measure(int ret, int txt) {
856856
int n = utf8_decode(&cp, p);
857857
if (n <= 0) {
858858
n = 1;
859+
cp = 0xfffd;
859860
}
861+
/* Mirror draw_text: non-printables render as one U+FFFD cell, so
862+
* they must measure as one cell. */
860863
int cw = wcwidth(cp);
864+
if (cw < 0)
865+
cw = 1;
861866
if (cw > 0)
862867
w += cw;
863868
p += n;

0 commit comments

Comments
 (0)