
В течение последних нескольких недель Nikita Prokopov внедрял поддержку эмодзи для Skija. Он решил поделиться несколькими мелкими деталями того, как это «самое большое новшество в человеческом общении со времен изобретения буквы

Примечание переводчика: Хабр не поддерживает эмодзи, поэтому пришлось выкручиваться и заменить эмодзи картинками.
Unicode
Каждый символ на компьютере кодируется числом. Самая популярная кодировка — Unicode, а две самые распространенные подвариации — UTF-8 и UTF-16.
Unicode выделяет 221 (2 млн) символов, назывемых «codepoints». Из этих двух миллионов сейчас определены только ~150k символов. В эти 150 000 символов впихнули все языки, мёртвые и живые и прочие украшательства. Можно использовать различные шрифты, писать задом наперед и кверх ногами:


Направленная вправо двуглавая стрела с оперением и двумя вертикальными штрихами:



Обратите внимание и на блок с египетскими иероглифами (U+13000–U+1342F), там много интересного:

Базовые emoji
Эмодзи — это просто символы Unicode, которые располагаются тут U+1F300–1F6FF и тут U+1F900–1FAFF:

Эмодзи ведут себя как обычные буквы, с ними можно делать все операции, что и с буквами (прим. пер.: только не на Хабре!). Когда вы печатаете “A”, компьютер видит U+0041. Когда вы печатаете

Эмодзи это шрифты
Почему же они отображаются как картинки? Растровые шрифты. Вы можете создать веселые png для глифов вместо скучных черно-белых векторов.

Каждая ОС поставляется с предустановленным шрифтом для эмодзи. В macOS/iOS это Apple Color Emoji. Windows — Segoe UI Emoji, Android — Noto Color Emoji.
На разных устройствах эмодзи, как и шрифты, выглядят по-разному. Некоторые приложения имеют свои эмодзи: WhatsApp, Twitter, Facebook.

Резервные шрифты
Вы пишете текст каким-то шрифтом, как туда вписывается эмодзи? И почему русский текст выглядит убого в Clubhouse или на Medium?

Вот вы печатаете символ U+1F419, а ваш шрифт, например, San Francisco. Но в шрифте San Francisco нет глифа для U+1F419, поэтому ваша ОС начинает искать другой шрифт, где такой глиф есть.
U+1F419 есть только в Apple Color Emoji. Поэтому вы видите это:

Какой бы шрифт вы не использовали, эмодзи выглядят одинаково.

Variation selector-16
Некоторые эмодзи зародились в виде пиктограмм ещё в 1993 году, в разделах Miscellaneous Symbols U+2600-26FF или Dingbats U+2700-27FF:

Эти глифы совсем как буквы, черно-белые. Многие шрифты имеют свои


У Apple Color Emoji есть своя версия:

Как ОС понимает что отображать


Познакомьтесь с U+FE0F, так же известным как VARIATION SELECTOR-16. Это подсказка текстовому рендеру переключиться на эмодзи.

Просто, элегантно и не надо выделять новые codepoints.

Кластеры графем
Здесь мы сталкиваемся с другой проблемой — наши эмодзи теперь представляют собой не одну codepoint, а две. Это означает, что нам нужен способ определить границы символа.
Нам поможет кластер графем. Кластер графем — это последовательность codepoints, которая рассматривается как единый воспринимаемый человеком глиф.
Графемные кластеры были изобретены не только для эмодзи, они применимы и к обычным алфавитам.

Графемные кластеры создают много сложностей для программистов. Вы не можете просто сделать
substring(0, 10)
, чтобы взять первые 10 символов — вы можете разделить эмодзи пополам.Реверс строки нужно делать хитро. U+263A U+FE0F имеет смысл, а U+FE0F U+263A — нет.

Наконец, вы не можете просто вызвать
.length
для string. Ну, можно, но результат вас удивит. Если вы разработчик, попробуйте выполнить 
Подсказка для программистов: если вы работаете с текстом, приобретите библиотеку, ориентированную на графемные кластеры. Для C, C++ и JVM это может быть ICU, Swift делает все правильно по умолчанию, для других — ищите сами.

Длина этой штуки 65, и её нельзя расщеплять. Живите теперь с этим.
Модификатор оттенка кожи
Большинство человеческих эмодзи изображают абстрактного желтого человека. Когда в 2015 году был добавлен оттенок кожи, вместо добавления новой codepoint для каждой комбинации эмодзи и оттенка кожи было добавлено только пять новых codepoints: U+1F3FB..U+1F3FF
Они не должны использоваться сами по себе, но должны быть добавлены к существующим эмодзи. Вместе они образуют лигатуру: если печатаем




Zero-width Joiner
Допустим, ваша подруга только что прислала вам фотографию яблока, которое она выращивает в своем саду. Вам нужно ответить — как? Вы можете отправить




U+200D называется Zero-width Joiner, или сокращенно ZWJ. Он работает аналогично тому, что мы видели с оттенком кожи, но на этот раз вы можете объединить два самодостаточных эмодзи в один. Не все комбинации работают, но многие работают, иногда удивительным образом!
Некоторые примеры:

Одна странная несогласованность, которую я заметил, заключается в том, что цвет волос делается через ZWJ, в то время как оттенок кожи — это просто модификатор emoji без ZWJ. Почему? Понятия не имею.

К сожалению, некоторые эмодзи не реализованы в виде комбинаций с ZWJ. Я считаю это упущенные возможности:

Как напечатать ZWJ? Никак. Но вы можете скопировать его отсюда: “”. Примечание: это особый символ, поэтому ожидайте, что он будет вести себя странно. Вы его не видите, а он есть. (прим пер.: в оригинальной статье есть, а Хабр не позволяет)
Еще одна большая область, где ZWJ на коне — это конфигурация семей и отношений. Вот короткий сюжет для иллюстрации:

Флаги
Флаги стран являются частью стандарта Unicode, но по какой-то причине не реализованы в Windows. Если вы читаете это в браузере из Windows — извините!
Флаги не имеют выделенных codepoints. Вместо этого они представляют собой двухбуквенные лигатуры.

Слева — Windows, справа — Mac
Правда, они не используют настоящие буквы. Вместо этого используется алфавит “regional indicator symbol letter” (U+1F1E6..1F1FF). Эти буквы не используются ни для чего, кроме составления флагов.
Что произойдет, если вы сложите вместе две случайные буквы? Не так уж много:

Если вы хотите поэкспериментировать, не стесняйтесь копировать и комбинировать из этого алфавита:

Существует 258 допустимых двухбуквенных комбинаций. Вы можете найти их все?
Забавный побочный эффект двухбуквенной лигатуры:

Последовательности тэгов
Двухбуквенные лигатуры — это круто, но разве вы не хотите быть круче? Как насчет 32-буквенных лигатур? Вот вам последовательности тегов.
Последовательность тегов — это последовательность обычных эмодзи, за которой следует другая разновидность латинских букв (U+E0020..E007E), заканчивающаяся U+E007F CANCEL TAG.
В настоящее время они используются только для этих трех флагов: Англии, Шотландии и Уэльса:

Keycaps
Не супер-захватывающе, но необходимо для полноты: последовательности Keycaps используют еще одно соглашение.
Это выглядит так: возьмите цифру * или #, превратите ее в эмодзи с U+FE0F, оберните в квадрат с U+20E3 COMBINING ENCLOSING KEYCAP

Всего их 12:

Unicode updates
Unicode обновляется каждый год, и эмодзи являются основной частью каждого выпуска. Например, в Unicode 13 (март 2020 года) было добавлено 55 новых эмодзи.
На момент написания статьи ни последняя версия Mac OS (11.2.3), ни iOS (14.4.1) не поддерживают эмодзи из Unicode 13 типа:

Вот что я вижу в марте 2021 года:

Но, благодаря магии ZWJ, я все еще могу понять, что происходит, просто не самым оптимальным способом.
Заключение
Подводя итог, можно сказать, что это семь способов кодировки эмодзи:
- Одиночный codepoint
- Одиночный codepoint + variation selector-16
- Модификатор оттенка кожи
- Последовательность с zero-width joiner
- Флаги
- Последовательность тэгов
- Последовательность Keycap
Методы из 1-4 могут быть объединены для построения довольно сложного сообщения:

Если вы программист, не забывайте всегда использовать библиотеку ICU для:
- извлечения подстроки
- измерения длины строки
- реверс строки
Ключевое слово для гугления — «Grapheme Cluster». Это относится к эмодзи, к диакритическим знакам в западных языках, к индуцированным и корейским шрифтам, поэтому, пожалуйста, будьте внимательны.
