Emojis в / из кодовых точек в Javascript - PullRequest
3 голосов
/ 04 ноября 2019

В создаваемой мной гибридной игре для Android и Cordova я позволяю пользователям указывать идентификатор в виде Emoji + буквенно-цифровое имя - то есть 0..9, A..Z, a..z - имя. Например,

taStackoverflow

Идентификаторы пользователя на стороне сервера хранятся с частями Emoji и Name, разделенными только частью Name, которая должна быть уникальной. Время от времени игра отображает «таблицу лиг», чтобы пользователь мог видеть, насколько хорошо они выступают по сравнению с другими игроками. Для этого сервер отправляет обратно последовательность из десяти значений «высокого балла», состоящих из эмодзи, имени и счета.

Затем он представляется пользователю в виде таблицы с тремя столбцами - по одному для Emoji, Name и Score. И здесь я столкнулся с небольшой проблемой. Первоначально я наивно полагал, что смогу понять эмодзи, просто взглянув на handle.codePointAt(0). Когда до меня дошло, что Emoji на самом деле может быть последовательностью из одного или нескольких 16-битных значений Unicode, я изменил свой код следующим образом

Часть 1 : разделение предоставленного пользователем «дескриптора»

var i,username,
    codepoints = [], 
    handle = "?‍️StackOverflow",
    len = handle,length; 

 while ((i < len) && (255 < handle.codePointAt(i))) 
 {codepoints.push(handle.codePointAt(i));i += 2;}

 username = handle.substring(codepoints.length + 1);

На данный момент у меня есть ручка "dissected" с

 codepoints =  [128587, 8205, 65039];
 username = 'Stackoverflow;

Примечание объяснения для i += 2 и использования handle.length выше. Эта статья предполагает, что

  • handle.codePointAt (n) вернет кодовую точку для полной суррогатной пары, если вы нажмете ведущую суррогатную матрицу. В моем случае, поскольку эмодзи должно быть первым персонажем, главные суррогаты для последовательности 16-битных юникодов для эмодзи находятся на 0,2,4....
  • Из той же статьи я узнал, что String.length в Javascript вернет число 16-битных кодовых единиц .

Часть II - Reгенерация Emojis для «таблицы лиги»

Предположим, что данные таблицы лиги, которые мои серверы возвращают в приложение, содержат запись {emoji: [128583, 8205, 65039],username:"Stackexchange",points:100} для символа смайликов ?‍️. Теперь вот надоедливая вещь. Если я делаю

var origCP = [],
    i = 0, 
    origEmoji = '?‍️',
    origLen = origEmoji.length;

    while ((i < origLen) && (255 < origEmoji.codePointAt(i)) 
    {origCP.push(origEmoji.codePointAt(i);i += 2;}

, я получаю

 origLen = 5, origCP = [128583, 8205, 65039]

Однако, если я восстанавливаю эмодзи из предоставленных данных

 var reEmoji = String.fromCodePoint.apply(String,[128583, 8205, 65039]),
     reEmojiLen = reEmoji.length;

, я получаю

reEmoji = '?‍️' 
reEmojiLen = 4;

Таким образом, в то время как reEmoji имеет правильные эмодзи, его сообщаемая длина загадочным образом сократилась до 4 кодовых единиц вместо оригинальных 5.

Если я затем извлеку кодовые точки из восстановленных смайликов

var reCP = [],
    i = 0;

while ((i < reEmojiLen) && (255 < reEmoji.codePointAt(i)) 
{reCP.push(reEmoji.codePointAt(i);i += 2;} 

, что дает мне

 reCP =  [128583, 8205];

Даже любопытный, origEmoji.codePointAt(3) дает значение задней суррогатной пары 9794, в то время как reEmoji.codePointAt(3) дает значение следующей полной суррогатной пары 65039.

В этот момент я мог бы просто сказать

Меня это действительно волнует?

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

Может ли кто-нибудь здесь пролить свет на происходящее?

1 Ответ

2 голосов
/ 04 ноября 2019

эмодзи более сложны, чем просто отдельные символы, они входят в «последовательности», например, zwj-последовательность (объединяет несколько эмодзи в одно изображение) или последовательность представления (предоставляют различные варианты одного и того же символа) и некоторые другие, см. tr51 для всех неприятных деталей.

Если вы "сбросите" свою строку следующим образом

str = "?‍️StackOverflow"

console.log(...[...str].map(x => x.codePointAt(0).toString(16)))

вы увидите, что на самом деле это (неправильно сформированная) zwj-последовательность, обернутая в последовательность презентации.

Итак, чтобы точно нарезать эмодзи, вынужно перебрать строку как массив кодовых точек (не единиц!) и извлечь плоскость 1 CP (> 0xffff) + ZWJ's + варианты выбора. Пример:

function sliceEmoji(str) {
    let res = ['', ''];

    for (let c of str) {
        let n = c.codePointAt(0);
        let isEmoji = n > 0xfff || n === 0x200d || (0xfe00 <= n && n <= 0xfeff);
        res[1 - isEmoji] += c;
    }
    return res;
}

function hex(str) {
    return [...str].map(x => x.codePointAt(0).toString(16))
}

myStr = "?‍️StackOverflow"

console.log(sliceEmoji(myStr))
console.log(sliceEmoji(myStr).map(hex))
...