Хабр довольно враждебно настроен к Эмодзи (здесь они просто не отображаются), считая их чем-то вроде «падонкаффского» языка. Не для серьёзных людей. Ведь и то и другое появилось примерно в одно время. И если «олбанский» йезыг быстро ушёл в небытие, то Эмодзи эволюционировали от простых точек с запятой и скобочек до полноправных символов в кодировке Юникод. Автор данной статьи предлагает посмотреть, что у этих маленьких сущностей «под капотом» (Здесь и далее курсивом — примечания переводчика).

EmojiFamilyHeader
Оригинальная статья написана для сайта Contentful Стефаном Джудисом (Twitter, GitHub).

Эмодзи – это основа текстового общения наших дней. Без этих маленьких символов, много бесед в чатах сегодня занчивались бы неловким молчанием или недопониманием. Я всё ещё помню старые добрые времена, когда СМС-ки были крутой штукой.

Предложение пообщаться в чате без смайликов скорее всего приведёт к сообщению «Ты что шутишь?». Все быстро поняли, что юмор и сарказм (кстати, нам бы не помешало быть менее саркастичными) не просто передать, используя лишь письменные знаки. В какой-то момент возник первый Эмодзи, и они быстро стали одним из фундаментальных компонентов любой беседы в текстовом формате.

Хоть я и использую Эмодзи каждый день, я никогда не задавался вопросом, как же они работают. Очевидно, что они каким-то образом связаны с Юникодом, но я и понятия не имел, что там творится под капотом. И мне, честно говоря, было всё равно.

Всё поменялось, когда я набрёл на твит Веса Боса (Wes Bos), в котором он показал несколько JavaScript-операций над строкой, содержащей семью Эмодзи.


Допустим, что использование spread-оператора в такой строке не сильно меня удивило, но тот факт, что один видимый символ был разделён на три символа и две пустые строки, несколько меня озадачил. И то, что свойство строки length вернуло значение 8, удивило меня ещё больше, так как в массиве, который вернул spread-оператор, значений было 5, но никак не 8.

Недолго думая, я открыл консоль, и убедился в том, что всё происходит именно так, как Вес описал. Так что же здесь происходит? Я решил копнуть глубже в Юникод, JavaScript и семью Эмодзи, чтобы во всём разобраться.

Юникод спешит на помощь


Чтобы понять, почему JavaScript обрабатывает Эмодзи таким образом, мы должны заглянуть глубже в сам Юникод.

Юникод – это международный стандарт кодировки символов в IT-индустрии. Он устанавливает соответствие между каждой буквой, знаком или символом и числовым значением. Благодаря Юникоду мы можем делиться документами, которые содержат например специальные немецкие символы (умлауты) ?, a, o, с людьми, системы которых их не используют. Благодаря Юникоду, кодировки работают на разных платформах и окружениях.

В Юникоде определяется 1 114 112 различных символов, и они обычно представляются с помощью U+ с последующим числом в шестнадцатеричной системе счисления. Диапазон символов Юникод начинается U+0000 и заканчивается U+10FFFF.

Всё кодовое пространство (более одного миллиона символов) разбито на 17 т. н. «плоскостей», и каждая плоскость включает в себя свыше 65 000 символов. Наиболее важная – нулевая, «Базовая Мультиязычная Плоскость» (“Basic Multilingual Plane”, BMP). Её диапазон от U+0000 до U+FFFF.

Базовая плоскость содержит символы почти всех современных языков, плюс большое количество других символов. Остальные 16 плоскостей называются дополнительными и используются для различных целей, таких как – вы и сами могли догадаться – определение большинства символов Эмодзи.

Как определяются Эмодзи?


Как мы знаем, Эмодзи определяются как минимум одним символом из набора Юникода. Если посмотреть на все Эмодзи, представленные в Полном списке Эмодзи, то можно заметить, что их там много. И под словом «много» я имею в виду действительно много. Вы можете спросить себя, как много различных Эмодзи определено в Юникоде на сегодняшний день? Ответом на этот вопрос, как это часто случается в IT, будет «Это зависит от…», и мы должны разобраться с этим, прежде чем получим ответ.

Как я писал выше, Эмодзи определяются как минимум одним символом. Это означает, что есть некоторые Эмодзи, которые определяются комбинацией нескольких других Эмодзи и символов. Эти комбинации называются последовательностями. Благодаря последовательностям можно изменить нейтральный Эмодзи (обычно отображаемый с жёлтым цветом кожи), и сделать его более персональным.

Модификатор последовательностей для разных цветов кожи


Я всё ещё помню тот момент, когда я заметил, что могу в чате изменить значок поднятого вверх большого пальца так, чтобы он соответствовал моему цвету кожи. Это дало мне чувство сопричастности, и я почувствовал, что этот большой палец мне ближе, чем все мои предыдущие сообщения.

В Юникоде есть пять модификаторов для изменения нейтрального Эмодзи и представления всего разнообразия цветов кожи человечества. Модификаторы лежат в пределах от U+1F3FB до U+1F3FF и основываются на шкале Фицпатрика.

С помощью этих модификаторов мы можем превратить нейтральный Эмодзи в такой же, но с другим цветом кожи. Давайте посмотрим на пример:


Когда мы взяли девочку Эмодзи, символ которой U+1F467 и применили к ней модификатор цвета кожи (U+1F3FD), мы автоматически получили девочку с этим цветом кожи для тех систем, которые поддерживают эту последовательность.

ZWJ-последовательности для еще большего разнообразия


Цвет кожи — не единственное, что отличает людей друг от друга. Когда мы вспоминаем пример с семьей, становится понятно, что не каждая семья состоит из мужчины, женщины и мальчика.

В Юникоде есть символ для описания обычной семьи (U+1F46AEmoji family), но так выглядит не каждая семья. Мы можем создать любую семью, используя так называемую Zero-Width-Joiner (ZWJ) последовательность.

А вот как это работает: существует специальный символ, который называется объединителем нулевой ширины (U+200D).Этот символ работает как клей, показывая, что два символа должны быть отображены одним, когда это возможно.

Если подумать логически, что бы мы могли склеить, чтобы показать семью? Ответ прост — двух взрослых и ребенка. Используя ZWJ-последовательности, мы легко можем отобразить различные семьи.


Если посмотреть на список всех возможных последовательностей, можно увидеть, что вариантов там ещё больше, например, один папа с двумя девочками. К сожалению, на момент написания статьи, поддержка этих последовательностей не очень хорошая, но ZWJ-последовательности деградируют постепенно (Graceful degradation), возвращая последовательность отдельных Эмодзи. Это позволяет поддерживать семантичность.


Другая крутая штука — это то, что принципы объединения распространяются не только на семью Эмодзи. Например, давайте возьмём известный Эмодзи Дэвида Боуи (настоящее название — «певец»). Это тоже ZWJ-последовательность, состоящая из мужчины(U+1F468), ZWJ-объединителя и микрофона (U+1F3A4).
image
И, как вы уже могли догадаться, если мы заменим мужчину (U+1F468) женщиной (U+1F469), то получим певицу (или женскую версию Дэвида Боуи). Также можно добавить модификатор цвета кожи, тогда мы получим чернокожую певицу. Класс!


К сожалению, на момент написания статьи, поддержка этих новых символов также оставляет желать лучшего.

Разное количество Эмодзи


Итак, ответ на вопрос как много Эмодзи существует на сегодняшний день, зависит от того, что вы будете считать Эмодзи. Это количество символов, которые были использованы, чтобы отобразить Эмодзи? Или мы будем учитывать все варианты Эмодзи, которые могут быть отображены?

Если мы подсчитываем все варианты Эмодзи, которые могут быть отображены (включая последовательности и вариации), то получим 2 198. Если вам интересен процесс подсчёта, то вот целый раздел об этом на unicode.org.

Также к вопросу «Как подсчитывать» можно добавить тот факт, что новые Эмодзи и символы Юникода добавляются к спецификации постоянно, что делает отслеживание их точного количества ещё более сложной задачей.

Возвращаясь к строкам в JavaScript и 16-битной кодировке


В UTF-16, строковом формате, используемом в JavaScript, для представления большинства символов используется одно 16-битное кодовое значение (2 байта). Это означает, что чуть более 65000 различных кодовых значений может поместиться в один символ JavaScript. Это в точности совпадает с Базовой Мультиязычной плоскостью (BMP). Так давайте попытаемся сопоставить символы Юникода с несколькими символами, определенными в BMP.


Когда мы применяем к этим строкам свойство length, мы получаем единицу, и это полностью соответствует нашим ожиданиям. Но что произойдет, если я захочу использовать символ в JavaScript, который находится вне в диапазоне BMP?

Суррогатные пары спешат на помощь


Два символа, определенных в Базовой Плоскости, можно объединить, чтобы отобразить другой символ, который лежит за её пределами. Эта комбинация называется суррогатной парой.

Символы, лежащие в промежутке от U+D800 до U+DBFF, зарезервированы для так называемых старших или «ведущих» суррогатов, а символы в промежутке от U+DC00 до U+DFFF для младших или «замыкающих».

Эти два символа всегда должны использоваться в парах, начиная старшим и заканчивая младшим суррогатом. Затем применяется специальная формула для декодирования символов, лежащих вне диапазона.

Давайте рассмотрим пример:


Обычный мужчина в Эмодзи представлен символом U+1F468. Этот символ не может быть представлен одним 16-битным символом JavaScript. Поэтому для отображения одного символа вне BMP (U+1F468) должна быть использована суррогатная пара, состоящая из двух символов, входящих в BMP (U+D83D и U+DC68).

Для анализа символов в JavaScript существует два метода. Мы можем использовать charCodeAt, который вернет коды «суррогатных» псевдо-символов, если они используются для составления общего символа. Второй метод — codePointAt, который вернет код объединенной пары суррогатных символов, если мы «попали» в «ведущий» суррогатный символ или вернет код «замыкающего» суррогатного символа, если мы «попали» в него.

Вы думаете что это ужасно сбивает с толку? Я тоже так считаю и очень рекомендую вам внимательно прочитать статьи на MDN про эти два метода (charCodeAt, codePointAt) (также об этом можно почитать на learn.javascript.ru).

Давайте внимательнее посмотрим на символ мужчины в Эмодзи и посчитаем. Используя charCodeAt, мы можем получить коды «суррогатных» псевдо-символов, использующихся в суррогатной паре.

Первый символ имеет значение 55357, что соответствует D83D в шестнадцатеричной системе счисления. Это «ведущий» псевдосимвол. Второе значение 56424 соответствует DC68 и является «замыкающим» псевдосимволом. Это классическая суррогатная пара, которая в результате вычисления по формуле даст результат 128104, что соответствует символу мужчины в Эмодзи.


Количество символов и свойство length


Разобравшись с кодами Юникод и символами, мы можем приступить к странному поведению свойства length. Оказывается, оно возвращает количество кодовых значений Юникода, а не символов, которые мы видим, как мы думали в начале. Это может привести к трудностям в отлавливании ошибок при работе с Unicode в строках JavaScript – так что будьте внимательны, когда вы имеете дело с символами, лежащими вне BMP.

Заключение


Давайте вернёмся к примеру Веса, с которого всё началось.


Семья Эмодзи, которую мы здесь видим, состоит из мужчины, женщины и мальчика. Spread-оператор будет возвращать отдельные символы Эмодзи. Пустые строки на самом деле не пустые — это ZWJ-объединители. Свойство length, в этом случае вернёт 2 для каждого символа Эмодзи и 1 для ZWJ-объединителей. В результате мы получим 8.

Мне очень понравилось моё погружение в Юникод. Если вам также интересна эта тема, я бы порекомендовал @fakeunicode Twitter-аккаунт. Там много интересного о том, на что способен Юникод. Кстати, вы знали, что есть даже подкасты и конференции об Эмодзи? Я буду и дальше следить за всем этим, потому что мне очень интересно узнавать больше об этих маленьких символах, которые мы используем повсеместно. Возможно эта тема заинтересовала и вас.
Поделиться с друзьями
-->

Комментарии (14)


  1. NeoCode
    23.02.2017 00:24
    +8

    Сейчас уже символы по сути генерировать можно. Цвета уже судя по всему есть в ограниченном виде (цвета кожи). А потом захотят анимацию и чего нибудь еще…
    Вангую, что через пару десятков лет это все закончится появлением в Юникоде специальных невидимых символов для условий, циклов, объявления переменных, внутренних арифметических операций и в конечном итоге будет какой нибудь UnicodeScript.


    1. DaneSoul
      23.02.2017 01:16
      +13

      Ага, и следом Эмодзи-вирусы и Эмодзи-трояны, а также плагины для браузеров отключающих это безобразие…


  1. johnnymmc
    23.02.2017 01:52
    +2

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


    1. Ares_ekb
      23.02.2017 06:17
      +9

      В этом плане даже картинки-мемы и то лучше.
      А есть у кого-нибудь инфа когда добавят мемасики в юникод? Очень нужно!


    1. electrovladyslav
      24.02.2017 01:05

      Вот хорошая статья про динамическую замену всех Эмодзи в тексте на картинки из твоего набора. Чтобы не было пустых квадратиков. Картинки могут быть любыми.
      Хотел привести её в конце статьи привести, в разделе «Что бы ещё почитать»


  1. k12th
    23.02.2017 02:02
    +1

    Поддержка эмодзи находится в довольно печальном состоянии, даже если не учитывать всякие хитрые комбинации.


    Я сделал недавно текстовую игру в порядке эксперимента, где действия обозначались эмодзи вместо слов. И все было прекрасно на Win10 Pro, пока я не открыл ее на мобильнике, где вместо половины картинок были квадратики. Списав на старую версию (4.4), я пошел на работу и открыл на Win10 Enterprise, где ситуация оказалось не сильно лучше. Пришлось искать специальный шрифт, который весит на полтора порядка самой игры, и делать отдельную версию, потому что нельзя же всех заставлять качать 6 мегов для текстовой-то игры.


    В общем, если вы спросите меня: «готова ли инфраструктура для всемирного перехода на эсперанто колобков и какашек?», мой твердый и решительный ответ будет: «может быть, подождите пока».


    1. VolCh
      23.02.2017 06:59
      +2

      Жду нормальной поддержки только с одной целью — выпиливание из популярного софта типа мессенджеров и почтовиков "умных" парсеров, заменяющих безобидный код на всякие непотребства. И хорошо если при отображении получателю, а не при отправке.


      1. k12th
        23.02.2017 11:13
        +1

        Вот это вообще бесит, да.


  1. Sirion
    23.02.2017 02:53
    +14

    Олбанский ищо фсех пириживёд.


  1. DjPhoeniX
    23.02.2017 09:14

    А как с комбинированной поддержкой «разноцветных» семейств? Можно сделать «семью» из чернокожей женщины, бледнолицего мужчины, девочки-азиата и европеоидного мальчика? // л*нь пробовать и искать коды, интересна теория


    1. dshster
      23.02.2017 14:33
      +1

      image

      Не совсем разноцветные, но что-то можно


      Кстати, это копипаста из консоли Firefox, он не отображает символ U+200D в кавычках, поэтому они отображаются пустыми. Но на самом деле это не так и если вводить вручную код, то ничего не получится.


      1. dshster
        23.02.2017 14:53

        Здесь «пустые» кавычки нужно заменить на String.fromCodePoint(0x200D), тогда всё будет работать.


        1. Bhudh
          23.02.2017 20:06

          А просто '\u200d' не сработает, что ли?


  1. Mithgol
    27.02.2017 03:31
    +2

    Хабр довольно враждебно настроен к Эмодзи (здесь они просто не отображаются)
    Проблема № 61.