Skip to content

Commit 45f56e0

Browse files
StevenTCramerclaude
andcommitted
fix: use exact Emoji_Presentation=Yes code points from Unicode 16.0
Replace blanket ranges (U+2300-23FF, U+25A0-25FF, U+2600-27BF, U+2B00-2BFF) with precise Emoji_Presentation=Yes code points from emoji-data.txt. Text-presentation characters (☀♻✈▶◀▪▫ etc.) are now correctly width 1 as single runes, becoming width 2 only with VS16 as multi-codepoint clusters. Added missing: U+25FD-25FE (◽◾), U+270A-270B (✊✋). Removed false positives: U+23ED-23EF, U+23F1-23F2, U+23F8-23FA. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f1c8506 commit 45f56e0

3 files changed

Lines changed: 59 additions & 60 deletions

File tree

kanban/in-progress/016-fix-emoji-alignment-issue-in-table-borders.md

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,21 @@ Added `UnicodeWidth` utility class that calculates terminal display width accoun
2626

2727
### Wide character ranges covered (width 2)
2828

29-
**Emoji and symbols:**
30-
- U+2300-U+23FF — Miscellaneous Technical (⌚⌛⏰⏳)
31-
- U+25A0-U+25FF — Geometric Shapes (▶◀▪▫)
32-
- U+2600-U+26FF — Miscellaneous Symbols (☀☁⚡♻)
33-
- U+2700-U+27BF — Dingbats (✅❌✂✈)
34-
- U+2B00-U+2BFF — Miscellaneous Symbols and Arrows (⬛⬜⭐⭕)
29+
**Emoji_Presentation=Yes (BMP, from Unicode 16.0 emoji-data.txt):**
30+
- U+231A-231B ⌚⌛, U+23E9-23EC ⏩⏪⏫⏬, U+23F0 ⏰, U+23F3 ⏳
31+
- U+25FD-25FE ◽◾
32+
- U+2614-2615 ☔☕, U+2648-2653 ♈-♓, U+267F ♿, U+2693 ⚓, U+26A1 ⚡
33+
- U+26AA-26AB ⚪⚫, U+26BD-26BE ⚽⚾, U+26C4-26C5 ⛄⛅, U+26CE ⛎
34+
- U+26D4 ⛔, U+26EA ⛪, U+26F2-26F3 ⛲⛳, U+26F5 ⛵, U+26FA ⛺, U+26FD ⛽
35+
- U+2705 ✅, U+270A-270B ✊✋, U+2728 ✨, U+274C ❌, U+274E ❎
36+
- U+2753-2755 ❓❔❕, U+2757 ❗, U+2795-2797 ➕➖➗, U+27B0 ➰, U+27BF ➿
37+
- U+2B1B-2B1C ⬛⬜, U+2B50 ⭐, U+2B55 ⭕
38+
39+
**Text-presentation characters (☀♻✈▶◀▪▫ etc.)** are width 1 as single
40+
runes. They become width 2 when combined with VS16 (U+FE0F) as
41+
multi-codepoint grapheme clusters, handled automatically by GetTextWidth.
42+
43+
**Emoji blocks (SMP):**
3544
- U+1F000-U+1FAFF, U+1FC00-U+1FFFF — Emoji blocks
3645
- U+1F1E0-U+1F1FF — Regional indicator symbols (flags)
3746

samples/emoji-table-widget.cs

Lines changed: 17 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -18,58 +18,37 @@
1818
.Border(BorderStyle.Rounded))
1919
.WriteLine();
2020

21-
// ── Miscellaneous Symbols U+2600-U+26FF and Dingbats U+2700-U+27BF ──
21+
// ── Emoji_Presentation=Yes (width 2 without VS16) ──
2222
terminal
23-
.WriteRule("Misc Symbols + Dingbats")
23+
.WriteRule("Default Emoji Presentation")
2424
.WriteTable(t => t
2525
.AddColumn("Symbol")
2626
.AddColumn("Name")
2727
.AddColumn("Range")
28-
.AddRow("☀ Sun", "U+2600", "Misc Symbols")
2928
.AddRow("⚡ Zap", "U+26A1", "Misc Symbols")
30-
.AddRow("♻ Recycle", "U+267B", "Misc Symbols")
3129
.AddRow("✅ Check", "U+2705", "Dingbats")
3230
.AddRow("❌ Cross", "U+274C", "Dingbats")
33-
.AddRow("✈ Plane", "U+2708", "Dingbats")
31+
.AddRow("⭐ Star", "U+2B50", "Misc Sym+Arrows")
32+
.AddRow("⬛ Black", "U+2B1B", "Misc Sym+Arrows")
33+
.AddRow("☔ Rain", "U+2614", "Misc Symbols")
34+
.AddRow("⌚ Watch", "U+231A", "Misc Technical")
35+
.AddRow("⏰ Alarm", "U+23F0", "Misc Technical")
36+
.AddRow("➕ Plus", "U+2795", "Dingbats")
37+
.AddRow("❗ Exclaim", "U+2757", "Dingbats")
3438
.Border(BorderStyle.Rounded))
3539
.WriteLine();
3640

37-
// ── Miscellaneous Technical U+2300-U+23FF ──
41+
// ── Text presentation + VS16 (width 2 only with ️) ──
3842
terminal
39-
.WriteRule("Misc Technical")
43+
.WriteRule("Text Presentation + VS16")
4044
.WriteTable(t => t
41-
.AddColumn("Symbol")
42-
.AddColumn("Name")
43-
.AddRow("⌚ Watch", "U+231A")
44-
.AddRow("⌛ Hourglass", "U+231B")
45-
.AddRow("⏰ Alarm", "U+23F0")
46-
.AddRow("⏳ Timer", "U+23F3")
47-
.Border(BorderStyle.Rounded))
48-
.WriteLine();
49-
50-
// ── Geometric Shapes U+25A0-U+25FF ──
51-
terminal
52-
.WriteRule("Geometric Shapes")
53-
.WriteTable(t => t
54-
.AddColumn("Symbol")
55-
.AddColumn("Name")
56-
.AddRow("▶ Play", "U+25B6")
57-
.AddRow("◀ Reverse", "U+25C0")
58-
.AddRow("▪ Small Square", "U+25AA")
59-
.AddRow("▫ White Square", "U+25AB")
60-
.Border(BorderStyle.Rounded))
61-
.WriteLine();
62-
63-
// ── Misc Symbols and Arrows U+2B00-U+2BFF ──
64-
terminal
65-
.WriteRule("Misc Symbols and Arrows")
66-
.WriteTable(t => t
67-
.AddColumn("Symbol")
45+
.AddColumn("With VS16")
6846
.AddColumn("Name")
69-
.AddRow("⬛ Black Square", "U+2B1B")
70-
.AddRow("⬜ White Square", "U+2B1C")
71-
.AddRow("⭐ Star", "U+2B50")
72-
.AddRow("⭕ Circle", "U+2B55")
47+
.AddRow("☀️ Sun", "U+2600 + U+FE0F")
48+
.AddRow("♻️ Recycle", "U+267B + U+FE0F")
49+
.AddRow("✈️ Plane", "U+2708 + U+FE0F")
50+
.AddRow("▶️ Play", "U+25B6 + U+FE0F")
51+
.AddRow("☁️ Cloud", "U+2601 + U+FE0F")
7352
.Border(BorderStyle.Rounded))
7453
.WriteLine();
7554

source/timewarp-terminal/widgets/unicode-width.cs

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,23 +41,34 @@ or UnicodeCategory.EnclosingMark
4141
if (value is >= 0xE0100 and <= 0xE01EF) // Variation selectors supplement
4242
return 0;
4343

44-
// ── Emoji and symbol ranges (width 2 in modern terminals) ──
45-
46-
// Miscellaneous Technical (⌚⌛⏰⏳⏩ etc.)
47-
if (value is >= 0x2300 and <= 0x23FF)
48-
return 2;
49-
// Geometric Shapes (▶◀▪▫ etc.)
50-
if (value is >= 0x25A0 and <= 0x25FF)
51-
return 2;
52-
// Miscellaneous Symbols (☀☁⚡♻ etc.)
53-
if (value is >= 0x2600 and <= 0x26FF)
54-
return 2;
55-
// Dingbats (✅❌✂✈ etc.)
56-
if (value is >= 0x2700 and <= 0x27BF)
57-
return 2;
58-
// Miscellaneous Symbols and Arrows (⬛⬜⭐⭕⬆⬇ etc.)
59-
if (value is >= 0x2B00 and <= 0x2BFF)
44+
// ── Emoji_Presentation=Yes code points (U+2000-U+2BFF) ──
45+
// Source: Unicode 16.0 emoji-data.txt — only code points that render
46+
// as width 2 without VS16. Text-presentation characters become width 2
47+
// when combined with VS16 as multi-codepoint clusters (handled by GetTextWidth).
48+
if (value is 0x231A or 0x231B // ⌚⌛
49+
or (>= 0x23E9 and <= 0x23EC) // ⏩⏪⏫⏬
50+
or 0x23F0 or 0x23F3 // ⏰⏳
51+
or 0x25FD or 0x25FE // ◽◾
52+
or 0x2614 or 0x2615 // ☔☕
53+
or (>= 0x2648 and <= 0x2653) // ♈♉♊♋♌♍♎♏♐♑♒♓
54+
or 0x267F or 0x2693 or 0x26A1 // ♿⚓⚡
55+
or 0x26AA or 0x26AB // ⚪⚫
56+
or 0x26BD or 0x26BE // ⚽⚾
57+
or 0x26C4 or 0x26C5 or 0x26CE // ⛄⛅⛎
58+
or 0x26D4 or 0x26EA // ⛔⛪
59+
or 0x26F2 or 0x26F3 or 0x26F5 // ⛲⛳⛵
60+
or 0x26FA or 0x26FD // ⛺⛽
61+
or 0x2705 // ✅
62+
or 0x270A or 0x270B // ✊✋
63+
or 0x2728 // ✨
64+
or 0x274C or 0x274E // ❌❎
65+
or (>= 0x2753 and <= 0x2755) or 0x2757 // ❓❔❕❗
66+
or (>= 0x2795 and <= 0x2797) // ➕➖➗
67+
or 0x27B0 or 0x27BF // ➰➿
68+
or 0x2B1B or 0x2B1C // ⬛⬜
69+
or 0x2B50 or 0x2B55) // ⭐⭕
6070
return 2;
71+
6172
// Wide angle brackets
6273
if (value is >= 0x2329 and <= 0x232A)
6374
return 2;

0 commit comments

Comments
 (0)