Материал проходит вычитку — возможны неточности.
Урок 32 из 118

Emoji и составные emoji

Клиент «ФинГуру» поставил эмодзи семьи 👨‍👩‍👧 в название команды. Счётчик символов в интерфейсе показал пять значков вместо одного, и клиент написал в поддержку. Тикет упал Мише.

Главная мысль

один видимый эмодзи — это часто не одна руна, а цепочка code point'ов (ZWJ, модификаторы тона, вариационные селекторы, флаги), поэтому len, []rune и RuneCountInString видят его по кусочкам.

Руна — это code point, и предыдущий урок закончился на том, что видимый символ не всегда совпадает с code point'ом. Эмодзи — самый наглядный пример: один «смайлик» на экране может быть одним, двумя, пятью и даже семью code point'ами.

Один эмодзи — четыре байта

Большинство эмодзи лежат вне базовой плоскости Unicode (code point'ы больше U+FFFF), поэтому в UTF-8 каждый занимает четыре байта:

Go

Тут всё «честно»: один code point — одна руна. Это самый простой случай, и на нём ничего не ломается.

Модификатор тона кожи

Добавим модификатор тона. «👍🏽» — это два code point'а: сам палец U+1F44D и модификатор U+1F3FD. Человек видит один символ, Go — две руны:

Go

ZWJ-последовательности

Самый коварный случай — составные эмодзи, склеенные специальным невидимым символом ZWJ (U+200D, zero width joiner). Семья «👨‍👩‍👧» — это пять code point'ов: 👨 + ZWJ + 👩 + ZWJ + 👧.

Go

range честно переберёт все пять рун — но ни одна из них по отдельности не является тем, что видит пользователь.

Пример 4 (неочевидное): руны ломаются на составных эмодзи

Всё, что построено на рунах, перестаёт работать на составных эмодзи. Разворот строки через []rune из урока развернёт «👨‍👩‍👧» в «👧‍👩‍👨» — порядок рун изменится, и вместо семьи получится бессмыслица. Для «👍🏽» модификатор тона встанет перед пальцем, и символ сломается.

Флаги — ещё одна ловушка: «🇷🇺» это два regional indicator symbol'а (U+1F1F7 и U+1F1FA), которые только вместе образуют флаг. У «1️⃣» — три code point'а: цифра, вариационный селектор U+FE0F и keycap U+20E3.

В следующем уроке

Вы узнаете:

  • как вариационный селектор U+FE0F превращает «1» в «1️⃣»
  • чем ручная склейка кластеров отличается от uniseg
Задание · вопросы

Проверьте себя

Вопрос 1 из 3

Что произойдёт при развороте "👍🏽" через []rune?

Вопрос 2 из 3

Сколько рун в составном эмодзи "👨‍👩‍👧"?

Вопрос 3 из 3

Почему len("👍") == 4, а utf8.RuneCountInString("👍") == 1?

Итоги

«Символ» в Go — это руна, один code point. Видимый символ может быть последовательностью рун: эмодзи с тоном — две, ZWJ-семья — пять, флаг — две. Поэтому для задач «сколько значков видит пользователь» или «разбить на видимые символы» рун недостаточно — нужна сегментация по grapheme clusters, и готового обхода по ним в стандартной библиотеке нет (обычно берут github.com/rivo/uniseg).

Наш сайт собирает куки. Чтобы тебе было удобнее им пользоваться. Нажимая на кнопку, ты даёшь разрешение собирать куки