Javascript 中的表情符号 to/from 个代码点
Emojis to/from codepoints in Javascript
在我创建的混合 Android/Cordova 游戏中,我让用户以表情符号 + 字母数字的形式提供标识符 - 即 0..9,A..Z,a..z -姓名。例如
️Whosebug
服务器端用户标识符与表情符号和名称部分分开存储,只有名称部分需要唯一。游戏会不时显示 "league table",这样用户就可以看到与其他玩家相比他们的表现如何。为此,服务器发回由表情符号、名称和分数组成的十个 "high score" 值序列。
然后在 table 中将其呈现给用户,其中包含三列 - 表情符号、名称和分数各一列。这是我遇到一个小问题的地方。最初我天真地认为我可以通过简单地查看 handle.codePointAt(0)
来找出表情符号。当我意识到表情符号实际上可以是一个或多个 16 位 Unicode 值的序列时,我按如下方式更改了我的代码
第 1 部分:剖析用户提供的 "handle"
var i,username,
codepoints = [],
handle = "️Whosebug",
len = handle,length;
while ((i < len) && (255 < handle.codePointAt(i)))
{codepoints.push(handle.codePointAt(i));i += 2;}
username = handle.substring(codepoints.length + 1);
此时我有 "disssected" 句柄
codepoints = [128587, 8205, 65039];
username = 'Whosebug;
对上面 i += 2
和 handle.length
的使用的解释注释。 This article 建议
- handle.codePointAt(n) 将 return 如果您点击领先的代理项,则完整代理项对的代码点。在我的例子中,由于表情符号必须是第一个字符,因此表情符号的 16 位 Unicode 序列的前导代理位于
0,2,4...
。
- 从同一篇文章中我了解到 Javascript 中的
String.length
将 return 16 位代码单元的 数量 .
第二部分 - 重新生成 "league table"
的表情符号
假设联盟 table 数据由我的服务器传回应用程序,其中包含表情符号字符 ️ 的条目 {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];
Even curioser,origEmoji.codePointAt(3)
给出了 9794
的尾随代理对值,而 reEmoji.codePointAt(3)
给出了下一个完整代理对的值 65039
.
此时我可以说
Do I really care?
毕竟,我只想在单独的专栏中显示联盟 table 表情符号,因此只要我获得正确的表情符号,引擎盖下发生的事情的细节并不重要。然而,这很可能会为未来埋下问题。
这里的任何人都可以阐明正在发生的事情吗?
表情符号比单个字符更复杂,它们出现在 "sequences",例如一个 zwj 序列(将多个表情符号组合成一个图像)或一个演示序列(提供相同符号的不同变体)等等,请参阅 tr51 了解所有令人讨厌的细节。
如果你"dump"你的字符串像这样
str = "️Whosebug"
console.log(...[...str].map(x => x.codePointAt(0).toString(16)))
您会发现它实际上是一个(格式不正确的)zwj 序列,包裹在演示序列中。
因此,要准确地分割表情符号,您需要将字符串迭代为代码点数组(不是单位!)并提取平面 1 CP (>0xffff) + ZWJ + 变体选择器。示例:
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 = "️Whosebug"
console.log(sliceEmoji(myStr))
console.log(sliceEmoji(myStr).map(hex))
在我创建的混合 Android/Cordova 游戏中,我让用户以表情符号 + 字母数字的形式提供标识符 - 即 0..9,A..Z,a..z -姓名。例如
️Whosebug
服务器端用户标识符与表情符号和名称部分分开存储,只有名称部分需要唯一。游戏会不时显示 "league table",这样用户就可以看到与其他玩家相比他们的表现如何。为此,服务器发回由表情符号、名称和分数组成的十个 "high score" 值序列。
然后在 table 中将其呈现给用户,其中包含三列 - 表情符号、名称和分数各一列。这是我遇到一个小问题的地方。最初我天真地认为我可以通过简单地查看 handle.codePointAt(0)
来找出表情符号。当我意识到表情符号实际上可以是一个或多个 16 位 Unicode 值的序列时,我按如下方式更改了我的代码
第 1 部分:剖析用户提供的 "handle"
var i,username,
codepoints = [],
handle = "️Whosebug",
len = handle,length;
while ((i < len) && (255 < handle.codePointAt(i)))
{codepoints.push(handle.codePointAt(i));i += 2;}
username = handle.substring(codepoints.length + 1);
此时我有 "disssected" 句柄
codepoints = [128587, 8205, 65039];
username = 'Whosebug;
对上面 i += 2
和 handle.length
的使用的解释注释。 This article 建议
- handle.codePointAt(n) 将 return 如果您点击领先的代理项,则完整代理项对的代码点。在我的例子中,由于表情符号必须是第一个字符,因此表情符号的 16 位 Unicode 序列的前导代理位于
0,2,4...
。 - 从同一篇文章中我了解到 Javascript 中的
String.length
将 return 16 位代码单元的 数量 .
第二部分 - 重新生成 "league table"
的表情符号假设联盟 table 数据由我的服务器传回应用程序,其中包含表情符号字符 ️ 的条目 {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];
Even curioser,origEmoji.codePointAt(3)
给出了 9794
的尾随代理对值,而 reEmoji.codePointAt(3)
给出了下一个完整代理对的值 65039
.
此时我可以说
Do I really care?
毕竟,我只想在单独的专栏中显示联盟 table 表情符号,因此只要我获得正确的表情符号,引擎盖下发生的事情的细节并不重要。然而,这很可能会为未来埋下问题。
这里的任何人都可以阐明正在发生的事情吗?
表情符号比单个字符更复杂,它们出现在 "sequences",例如一个 zwj 序列(将多个表情符号组合成一个图像)或一个演示序列(提供相同符号的不同变体)等等,请参阅 tr51 了解所有令人讨厌的细节。
如果你"dump"你的字符串像这样
str = "️Whosebug"
console.log(...[...str].map(x => x.codePointAt(0).toString(16)))
您会发现它实际上是一个(格式不正确的)zwj 序列,包裹在演示序列中。
因此,要准确地分割表情符号,您需要将字符串迭代为代码点数组(不是单位!)并提取平面 1 CP (>0xffff) + ZWJ + 变体选择器。示例:
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 = "️Whosebug"
console.log(sliceEmoji(myStr))
console.log(sliceEmoji(myStr).map(hex))