return character display width, 0 (non-spacing or invalid character), 1 (single width) or 2 (double width)
| 343 | |
| 344 | // return character display width, 0 (non-spacing or invalid character), 1 (single width) or 2 (double width) |
| 345 | int Screen::wchar_width(uint32_t wc) |
| 346 | { |
| 347 | // ignore invisible characters, such as invalid UTF-8 |
| 348 | if (wc == 0) |
| 349 | return 0; |
| 350 | |
| 351 | // control characters are double width to display them e.g. as \t or ^I |
| 352 | if (wc < 0x20 || wc == 0x7f) |
| 353 | return 2; |
| 354 | |
| 355 | /* Based on https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c with full table |
| 356 | generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" |
| 357 | from https://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt |
| 358 | this is a compressed table of combining character ranges, stored as |
| 359 | range.first << 8 | (range.last - range.first) |
| 360 | |
| 361 | We don't use wcwidth() because it is broken on some systems, e.g. MacOS |
| 362 | wcwidth(0x1f600) returns -1 for the emoticon U+1F600 |
| 363 | */ |
| 364 | static const uint32_t combining[] = { |
| 365 | 0x3006f, 0x48303, 0x48801, 0x5912c, 0x5bf00, 0x5c101, 0x5c401, 0x5c700, |
| 366 | 0x60003, 0x61005, 0x64b13, 0x67000, 0x6d60e, 0x6e701, 0x6ea03, 0x70f00, |
| 367 | 0x71100, 0x7301a, 0x7a60a, 0x7eb08, 0x90101, 0x93c00, 0x94107, 0x94d00, |
| 368 | 0x95103, 0x96201, 0x98100, 0x9bc00, 0x9c103, 0x9cd00, 0x9e201, 0xa0101, |
| 369 | 0xa3c00, 0xa4101, 0xa4701, 0xa4b02, 0xa7001, 0xa8101, 0xabc00, 0xac104, |
| 370 | 0xac701, 0xacd00, 0xae201, 0xb0100, 0xb3c00, 0xb3f00, 0xb4102, 0xb4d00, |
| 371 | 0xb5600, 0xb8200, 0xbc000, 0xbcd00, 0xc3e02, 0xc4602, 0xc4a03, 0xc5501, |
| 372 | 0xcbc00, 0xcbf00, 0xcc600, 0xccc01, 0xce201, 0xd4102, 0xd4d00, 0xdca00, |
| 373 | 0xdd202, 0xdd600, 0xe3100, 0xe3406, 0xe4707, 0xeb100, 0xeb405, 0xebb01, |
| 374 | 0xec805, 0xf1801, 0xf3500, 0xf3700, 0xf3900, 0xf710d, 0xf8004, 0xf8601, |
| 375 | 0xf9007, 0xf9923, 0xfc600, |
| 376 | 0x102d03, 0x103200, 0x103601, 0x103900, 0x105801, 0x11609f, 0x135f00, |
| 377 | 0x171202, 0x173202, 0x175201, 0x177201, 0x17b401, 0x17b706, 0x17c600, |
| 378 | 0x17c90a, 0x17dd00, 0x180b02, 0x18a900, 0x192002, 0x192701, 0x193200, |
| 379 | 0x193902, 0x1a1701, 0x1b0003, 0x1b3400, 0x1b3604, 0x1b3c00, 0x1b4200, |
| 380 | 0x1b6b08, 0x1dc00a, 0x1dfe01, 0x200b04, 0x202a04, 0x206003, 0x206a05, |
| 381 | 0x20d01f, 0x302a05, 0x309901, 0xa80600, 0xa80b00, 0xa82501, 0xfb1e00, |
| 382 | 0xfe000f, 0xfe2003, 0xfeff00, 0xfff902, |
| 383 | 0x10a0102, 0x10a0501, 0x10a0c03, 0x10a3802, 0x10a3f00, 0x1d16702, 0x1d1730f, |
| 384 | 0x1d18506, 0x1d1aa03, 0x1d24202, 0xe000100, 0xe00205f, 0xe0100ef, |
| 385 | }; |
| 386 | |
| 387 | int min = 0; |
| 388 | int max = sizeof(combining) / sizeof(uint32_t) - 1; |
| 389 | |
| 390 | // binary search in table of non-spacing characters |
| 391 | if (wc >= (combining[0] >> 8) && wc <= (combining[max] >> 8) + (combining[max] & 0xff)) |
| 392 | { |
| 393 | while (max >= min) |
| 394 | { |
| 395 | int mid = (min + max) / 2; |
| 396 | if (wc < (combining[mid] >> 8)) |
| 397 | max = mid - 1; |
| 398 | else if (wc > (combining[mid] >> 8) + (combining[mid] & 0xff)) |
| 399 | min = mid + 1; |
| 400 | else |
| 401 | return 0; |
| 402 | } |
nothing calls this directly
no outgoing calls
no test coverage detected