![image](https://habrastorage.org/getpro/habr/post_images/52d/aff/bae/52daffbae3eb4c5c8d57967e8dd46bc7.png)
В течение последних нескольких недель Nikita Prokopov внедрял поддержку эмодзи для Skija. Он решил поделиться несколькими мелкими деталями того, как это «самое большое новшество в человеческом общении со времен изобретения буквы
![image](https://habrastorage.org/webt/_o/en/_a/_oen_amnna_f3fqy88cz9bd8a-u.jpeg)
Примечание переводчика: Хабр не поддерживает эмодзи, поэтому пришлось выкручиваться и заменить эмодзи картинками.
Unicode
Каждый символ на компьютере кодируется числом. Самая популярная кодировка — Unicode, а две самые распространенные подвариации — UTF-8 и UTF-16.
Unicode выделяет 221 (2 млн) символов, назывемых «codepoints». Из этих двух миллионов сейчас определены только ~150k символов. В эти 150 000 символов впихнули все языки, мёртвые и живые и прочие украшательства. Можно использовать различные шрифты, писать задом наперед и кверх ногами:
![image](https://habrastorage.org/webt/of/mc/zc/ofmczcu1tfoocp4aymixbubqgmc.jpeg)
![image](https://habrastorage.org/webt/im/gk/lt/imgkltudc_5jimbokohnrzsahcq.jpeg)
Направленная вправо двуглавая стрела с оперением и двумя вертикальными штрихами:
![image](https://habrastorage.org/webt/ux/nr/e3/uxnre3o9b8t9xmk0jfhd8_dkcik.jpeg)
![image](https://habrastorage.org/webt/so/gf/sj/sogfsjnwaiwxyae-3hzqc3b0mz4.jpeg)
![image](https://habrastorage.org/getpro/habr/post_images/550/497/126/5504971266f4628219d43d3b65e81454.png)
Обратите внимание и на блок с египетскими иероглифами (U+13000–U+1342F), там много интересного:
![image](https://habrastorage.org/getpro/habr/post_images/41c/96c/a5e/41c96ca5ee19683c0a5dd8a6b21d2f5e.png)
Базовые emoji
Эмодзи — это просто символы Unicode, которые располагаются тут U+1F300–1F6FF и тут U+1F900–1FAFF:
![image](https://habrastorage.org/getpro/habr/post_images/8a3/420/c6f/8a3420c6fbeb0f3d72065492b0c95021.png)
Эмодзи ведут себя как обычные буквы, с ними можно делать все операции, что и с буквами (прим. пер.: только не на Хабре!). Когда вы печатаете “A”, компьютер видит U+0041. Когда вы печатаете
![image](https://habrastorage.org/webt/f3/zf/9w/f3zf9w83yn9fuesztcrqlvkcimw.jpeg)
Эмодзи это шрифты
Почему же они отображаются как картинки? Растровые шрифты. Вы можете создать веселые png для глифов вместо скучных черно-белых векторов.
![image](https://habrastorage.org/getpro/habr/post_images/cd9/627/e32/cd9627e32061eb12fd30660b9d9ee6f2.png)
Каждая ОС поставляется с предустановленным шрифтом для эмодзи. В macOS/iOS это Apple Color Emoji. Windows — Segoe UI Emoji, Android — Noto Color Emoji.
На разных устройствах эмодзи, как и шрифты, выглядят по-разному. Некоторые приложения имеют свои эмодзи: WhatsApp, Twitter, Facebook.
![image](https://habrastorage.org/getpro/habr/post_images/2ff/2f3/d6c/2ff2f3d6c2304c1ce35cac52c139eecb.png)
Резервные шрифты
Вы пишете текст каким-то шрифтом, как туда вписывается эмодзи? И почему русский текст выглядит убого в Clubhouse или на Medium?
![image](https://habrastorage.org/getpro/habr/post_images/624/b9d/8c1/624b9d8c1579bc68c133ebc2af85ab12.png)
Вот вы печатаете символ U+1F419, а ваш шрифт, например, San Francisco. Но в шрифте San Francisco нет глифа для U+1F419, поэтому ваша ОС начинает искать другой шрифт, где такой глиф есть.
U+1F419 есть только в Apple Color Emoji. Поэтому вы видите это:
![image](https://habrastorage.org/webt/k9/uf/rv/k9ufrvxpiksldcgmkd7gleodafu.jpeg)
Какой бы шрифт вы не использовали, эмодзи выглядят одинаково.
![image](https://habrastorage.org/getpro/habr/post_images/352/aac/65c/352aac65c754e2126f86f4c61b52b1cd.png)
Variation selector-16
Некоторые эмодзи зародились в виде пиктограмм ещё в 1993 году, в разделах Miscellaneous Symbols U+2600-26FF или Dingbats U+2700-27FF:
![image](https://habrastorage.org/getpro/habr/post_images/8d8/e7f/217/8d8e7f2172b2ccda2e00ddf96bcbeae5.png)
Эти глифы совсем как буквы, черно-белые. Многие шрифты имеют свои
![image](https://habrastorage.org/webt/ti/j9/iq/tij9iqwz0utnlnk2hequcacn0ac.jpeg)
![image](https://habrastorage.org/getpro/habr/post_images/0a7/1c5/1ef/0a71c51ef531a1b5211f189e9b353bea.png)
У Apple Color Emoji есть своя версия:
![image](https://habrastorage.org/getpro/habr/post_images/c33/5f0/a14/c335f0a1453298435bcb78168d8ff5f3.png)
Как ОС понимает что отображать
![image](https://habrastorage.org/webt/ti/j9/iq/tij9iqwz0utnlnk2hequcacn0ac.jpeg)
![image](https://habrastorage.org/webt/88/an/vf/88anvf1syf7mgs_eyfj-wqq88wa.jpeg)
Познакомьтесь с U+FE0F, так же известным как VARIATION SELECTOR-16. Это подсказка текстовому рендеру переключиться на эмодзи.
![image](https://habrastorage.org/webt/ud/-4/dt/ud-4dtkab6lisn2wwyvolmuw6jg.jpeg)
Просто, элегантно и не надо выделять новые codepoints.
![image](https://habrastorage.org/webt/vu/_x/fs/vu_xfs63khdroqvn-n981qmwyek.jpeg)
Кластеры графем
Здесь мы сталкиваемся с другой проблемой — наши эмодзи теперь представляют собой не одну codepoint, а две. Это означает, что нам нужен способ определить границы символа.
Нам поможет кластер графем. Кластер графем — это последовательность codepoints, которая рассматривается как единый воспринимаемый человеком глиф.
Графемные кластеры были изобретены не только для эмодзи, они применимы и к обычным алфавитам.
![image](https://habrastorage.org/webt/lo/s5/fe/los5fejcqpu3lyqic0gqgqoidps.jpeg)
Графемные кластеры создают много сложностей для программистов. Вы не можете просто сделать
substring(0, 10)
, чтобы взять первые 10 символов — вы можете разделить эмодзи пополам.Реверс строки нужно делать хитро. U+263A U+FE0F имеет смысл, а U+FE0F U+263A — нет.
![image](https://habrastorage.org/getpro/habr/post_images/06d/d6c/55f/06dd6c55f5b39bbc7d886b6cc7aa2bfc.png)
Наконец, вы не можете просто вызвать
.length
для string. Ну, можно, но результат вас удивит. Если вы разработчик, попробуйте выполнить ![image](https://habrastorage.org/webt/x6/qq/oq/x6qqoqttsy_x81b89zu3mwgmvxu.jpeg)
Подсказка для программистов: если вы работаете с текстом, приобретите библиотеку, ориентированную на графемные кластеры. Для C, C++ и JVM это может быть ICU, Swift делает все правильно по умолчанию, для других — ищите сами.
![image](https://habrastorage.org/webt/ba/ey/yf/baeyyf-netzxu54ijazl-3frssm.jpeg)
Длина этой штуки 65, и её нельзя расщеплять. Живите теперь с этим.
Модификатор оттенка кожи
Большинство человеческих эмодзи изображают абстрактного желтого человека. Когда в 2015 году был добавлен оттенок кожи, вместо добавления новой codepoint для каждой комбинации эмодзи и оттенка кожи было добавлено только пять новых codepoints: U+1F3FB..U+1F3FF
Они не должны использоваться сами по себе, но должны быть добавлены к существующим эмодзи. Вместе они образуют лигатуру: если печатаем
![image](https://habrastorage.org/webt/32/hb/jz/32hbjzmprc36cwgx8jcn89kas90.jpeg)
![image](https://habrastorage.org/webt/tv/ri/sn/tvrisnid-uabu05sefew1atxtqu.jpeg)
![image](https://habrastorage.org/webt/tv/ri/sn/tvrisnid-uabu05sefew1atxtqu.jpeg)
![image](https://habrastorage.org/webt/9w/0n/ob/9w0nob-hil_vp_iccujlukvvjgc.jpeg)
Zero-width Joiner
Допустим, ваша подруга только что прислала вам фотографию яблока, которое она выращивает в своем саду. Вам нужно ответить — как? Вы можете отправить
![image](https://habrastorage.org/webt/wk/0x/zg/wk0xzglxni2apvet8szdj1nrjau.jpeg)
![image](https://habrastorage.org/webt/7e/9i/d2/7e9id27wx0gljkiczcnvdcjhxro.jpeg)
![image](https://habrastorage.org/webt/wv/tv/ui/wvtvuigmxvphijbomj1ldbhwati.jpeg)
![image](https://habrastorage.org/webt/tx/l-/rm/txl-rm0ao7hy5msduqybbskot-y.jpeg)
U+200D называется Zero-width Joiner, или сокращенно ZWJ. Он работает аналогично тому, что мы видели с оттенком кожи, но на этот раз вы можете объединить два самодостаточных эмодзи в один. Не все комбинации работают, но многие работают, иногда удивительным образом!
Некоторые примеры:
![image](https://habrastorage.org/webt/h9/-r/t2/h9-rt26vdafwuqkjbu6y2ztehqw.jpeg)
Одна странная несогласованность, которую я заметил, заключается в том, что цвет волос делается через ZWJ, в то время как оттенок кожи — это просто модификатор emoji без ZWJ. Почему? Понятия не имею.
![image](https://habrastorage.org/webt/jp/ab/3n/jpab3nr-7wvolql2zlfwiwfbdbm.jpeg)
К сожалению, некоторые эмодзи не реализованы в виде комбинаций с ZWJ. Я считаю это упущенные возможности:
![image](https://habrastorage.org/webt/iz/3i/9d/iz3i9dtbjpde5qgqwrsfwfbmyr4.jpeg)
Как напечатать ZWJ? Никак. Но вы можете скопировать его отсюда: “”. Примечание: это особый символ, поэтому ожидайте, что он будет вести себя странно. Вы его не видите, а он есть. (прим пер.: в оригинальной статье есть, а Хабр не позволяет)
Еще одна большая область, где ZWJ на коне — это конфигурация семей и отношений. Вот короткий сюжет для иллюстрации:
![image](https://habrastorage.org/webt/c8/d5/2n/c8d52nwx-koxbs2oyd2bgss2g88.jpeg)
Флаги
Флаги стран являются частью стандарта Unicode, но по какой-то причине не реализованы в Windows. Если вы читаете это в браузере из Windows — извините!
Флаги не имеют выделенных codepoints. Вместо этого они представляют собой двухбуквенные лигатуры.
![image](https://habrastorage.org/webt/rv/ae/cu/rvaecu3-fwdasnxs3rbbc4kjig0.jpeg)
Слева — Windows, справа — Mac
Правда, они не используют настоящие буквы. Вместо этого используется алфавит “regional indicator symbol letter” (U+1F1E6..1F1FF). Эти буквы не используются ни для чего, кроме составления флагов.
Что произойдет, если вы сложите вместе две случайные буквы? Не так уж много:
![image](https://habrastorage.org/webt/9v/xj/kv/9vxjkvwd6hooibgsempvprc6tg8.jpeg)
Если вы хотите поэкспериментировать, не стесняйтесь копировать и комбинировать из этого алфавита:
![image](https://habrastorage.org/webt/4b/y2/wn/4by2wn0qshqklzmhmw_dxe8hh_y.jpeg)
Существует 258 допустимых двухбуквенных комбинаций. Вы можете найти их все?
Забавный побочный эффект двухбуквенной лигатуры:
![image](https://habrastorage.org/webt/lp/70/9p/lp709pz0axyq6op3_el3ck70lki.jpeg)
Последовательности тэгов
Двухбуквенные лигатуры — это круто, но разве вы не хотите быть круче? Как насчет 32-буквенных лигатур? Вот вам последовательности тегов.
Последовательность тегов — это последовательность обычных эмодзи, за которой следует другая разновидность латинских букв (U+E0020..E007E), заканчивающаяся U+E007F CANCEL TAG.
В настоящее время они используются только для этих трех флагов: Англии, Шотландии и Уэльса:
![image](https://habrastorage.org/webt/xq/24/fh/xq24fhoxun0g3vc9zzo77efkrto.jpeg)
Keycaps
Не супер-захватывающе, но необходимо для полноты: последовательности Keycaps используют еще одно соглашение.
Это выглядит так: возьмите цифру * или #, превратите ее в эмодзи с U+FE0F, оберните в квадрат с U+20E3 COMBINING ENCLOSING KEYCAP
![image](https://habrastorage.org/webt/bo/za/lm/bozalmkpqk2upcw6e3eszkev2ds.jpeg)
Всего их 12:
![image](https://habrastorage.org/webt/da/tc/yy/datcyydpl7qxji-t3qkb7znwxok.jpeg)
Unicode updates
Unicode обновляется каждый год, и эмодзи являются основной частью каждого выпуска. Например, в Unicode 13 (март 2020 года) было добавлено 55 новых эмодзи.
На момент написания статьи ни последняя версия Mac OS (11.2.3), ни iOS (14.4.1) не поддерживают эмодзи из Unicode 13 типа:
![image](https://habrastorage.org/webt/we/rl/qg/werlqgwkxbtu6iqf8ymi7zgiwb0.jpeg)
Вот что я вижу в марте 2021 года:
![image](https://habrastorage.org/getpro/habr/post_images/2fe/36a/e9b/2fe36ae9b4d969ac38d02334d7d623de.png)
Но, благодаря магии ZWJ, я все еще могу понять, что происходит, просто не самым оптимальным способом.
Заключение
Подводя итог, можно сказать, что это семь способов кодировки эмодзи:
- Одиночный codepoint
- Одиночный codepoint + variation selector-16
- Модификатор оттенка кожи
- Последовательность с zero-width joiner
- Флаги
- Последовательность тэгов
- Последовательность Keycap
Методы из 1-4 могут быть объединены для построения довольно сложного сообщения:
![image](https://habrastorage.org/webt/jd/0w/fr/jd0wfrkldejbzh0wqkl89mu0qag.jpeg)
Если вы программист, не забывайте всегда использовать библиотеку ICU для:
- извлечения подстроки
- измерения длины строки
- реверс строки
Ключевое слово для гугления — «Grapheme Cluster». Это относится к эмодзи, к диакритическим знакам в западных языках, к индуцированным и корейским шрифтам, поэтому, пожалуйста, будьте внимательны.
![image](https://habrastorage.org/webt/8o/fg/l6/8ofgl6qhckxt9b_eycxt6vpx0ku.jpeg)