Skip to content

🐛 VS16 (U+FE0F) emoji measured at the wrong width #81

@natemoo-re

Description

@natemoo-re
  • Package: clayterm
  • Version: v0.6.0 (reproduced on main @ d75fb4b)
  • Runtime: Deno 2.x
  • OS: platform-independent (WASM); reproduced on macOS

Describe the bug

We measure text width by summing per-codepoint widths with no grapheme-cluster awareness, so a base character followed by U+FE0F (variation selector-16) is mis-measured—the base keeps its text-presentation width and FE0F adds 0, instead of the pair clustering into one width-2 emoji glyph. 🌡️⚠️✅ measures 1+0+1+0+2 = 4 columns instead of 6, and a lone ⚠️ measures 1 instead of 2. Plain CJK (你好你 => 6) and already-emoji-presentation bases (🌊🌊 => 4) are correct, so the gap is specifically base + FE0F.

To Reproduce

A base codepoint immediately followed by U+FE0F should be a single width-2 cluster. The lone ⚠️ box reports bounds.width 1 (want 2), the 🌡️⚠️✅ fit box reports 4 (want 6), and a fit-bordered box prints ┌──┐ instead of ┌──────┐.

Expected behavior

FE0F promotes its base to width-2 emoji presentation instead of collapsing to 0. 🌡️⚠️✅ is 2+2+2 = 6, and a fit-bordered box around it is 8 cells wide (6 content + 2 border) with a top border of ┌──────┐.

Additional Information

Failing test case on nm/repro/vs16-emoji-width (test · diff).

The width table is fine; the gap is in the measurement loops. measure() (src/clayterm.c:677) does w += wcwidth(cp) with no lookahead (src/clayterm.c:693-695), and wcwidth() (src/wcwidth.c:1095) maps U+FE0F to 0 (src/wcwidth.c:547) by design; render_text() (src/clayterm.c:269) has the same per-codepoint loop (src/clayterm.c:288). The fix likely lives in those two loops as a one-codepoint lookahead—when the next codepoint is U+FE0F, treat base + FE0F as a single width-2 cluster and consume both, leaving the table and wcwidth() untouched.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type
No fields configured for issues without a type.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions