Почему Свифт считает этот кластер графем двумя персонажами вместо одного?

Вообще, Свифт действительно умел считать графемные кластеры одним персонажем. Например, если я хочу сделать ливанский флаг, я могу объединить два символа Юникода

  • U+1F1F1 РЕГИОНАЛЬНЫЙ ИНДИКАТОР СИМВОЛ ПИСЬМО L
  • U + 1F1E7 РЕГИОНАЛЬНЫЙ ИНДИКАТОР СИМВОЛ ПИСЬМО В

и, как и ожидалось, это один из символов в Swift:

let s = "\u{1f1f1}\u{1f1e7}"
assert(s.characters.count == 1)
assert(s.utf16.count == 4)
assert(s.utf8.count == 8)

Однако, скажем, я хочу сделать смайлик для велосипедиста из Fitzpatrick Type-5. Если я объединю

  • U+1F6B4 БИЦИКЛИСТ
  • U+1F3FE EMOJI МОДИФИКАТОР FITZPATRICK TYPE-5

Свифт считает эту комбинацию двумя персонажами!

let s = "\u{1f6b4}\u{1f3fe}"
assert(s.characters.count == 2)   // <----- WHY?
assert(s.utf16.count == 4)
assert(s.utf8.count == 8)

Почему это два символа вместо одного?

Чтобы показать, почему я ожидал, что это будет 1, обратите внимание, что этот кластер фактически интерпретируется как действительный эмодзи:

введите описание изображения здесь

1 ответ

Решение

Часть ответа дана в сообщении об ошибке, упомянутом в комментарии emrys57. При разбиении строки Unicode на "символы" Swift, по-видимому, использует границы кластера Grapheme, определенные в текстовой сегментации Unicode UAX #29. Существует правило не разбивать символы региональных индикаторов, но для модификаторов Emoji такого правила не существует. Итак, согласно UAX #29, строка "\u{1f6b4}\u{1f3fe}" содержит два графемных кластера. Посмотрите это сообщение от Кена Уистлера в списке рассылки Unicode для объяснения:

Это объясняется тем фактом, что резервное поведение для модификаторов просто в виде независимых пиктографических бликов, то есть изображений образцов цвета. [...] Вам нужны дополнительные конкретные знания об этих последовательностях - они не просто выпадают из реализации по умолчанию правил UAX #29 для кластеров графем.

Другие вопросы по тегам