表情符号字符串长度
#javascript #unicode #emoji

对如何计数所见内容做出了适度的贡献,而不是组成的内容

tldr

要产生与观察到的不同图形符号相对应的Unicode字符串计数,需要在原始字符串上执行几个还原操作。

我们需要在适当的情况下删除替代和变体编码以及修饰符。我们还需要考虑零宽木连接器(ZWJ)连接器。最终结果可以在index.ts文件或本文档的底部看到。

我们计算我们看到的

但JavaScript不

只要其 ivem>份量的总和。

我们期望我们识别是一个单元 - 字母,标点符号或其他不同的图形符号 - 也应视为不可分割的和计数,一一 /em>,直到我们到达结束。

直觉,这似乎很清楚。就像Hello一词具有5个不同的字母一样,以下每个表情符号:ð©,€,€,ðð»,ðü½ð»或𧧧被视为独立的单独单位。因此,计算字符串Hello 👋🏻的各个部分应等于7。

length

除非这不是JavaScript中的方式。

"👋🏻".length; // => Expected 1, got 4.
"👨‍👩‍👧‍👧".length; // => Expected 1, got 11.
"🤽🏿‍♀️".length; // => Expected 1, got 7.
"Hello 👋🏻".length; // => Expected 7, got 10.
"Family 👨‍👩‍👧‍👧".length; // => Expected 8, got 18.

为什么会发生这种差异?

从编码到外观

许多是一个

上面的原始长度结果实际上代表了对产生观察到的符号的所需Unicode字符组合的正确评估。 length操作并不计算我们立即期望的 - 最终的视觉单元结果 - 但 所有的零件都合并在一起以构成最终外观:一个苍白的手,一个家庭,一个家庭,一个女人玩水。

实际上是由更原始的符号组成的简单符号应该是非常熟悉的想法。当我们学会写作时,我们看到每个字母都是由不同的线组成的。单个字母I是用一行绘制的,单个字母H由三个不同的行组合组合。

输出其他符号,例如表情符号,也可能需要组成。挥舞的手表情符号可以具有肤色修饰符。可以将水上球员的性别(女人)具有中等深色的肤色。心脏可以具有红色变体。等等...

为了帮助我们了解如何计算我们所看到的和感知的,已经做出了不同的解释,建议和策略,并具有不同程度的成功和灵活性。 14 本文试图以此为基础并提供相对紧凑的功能,该功能将允许计算许多不同的Unicode字符串的长度,尤其是当它们包含表情符号字符时。 2

显然,它不是完美的

显然,欢迎任何建议ð

计数规则

忽略不会看到的

在我们的 - 公认的轶事 - 测试中,我们一直观察到字符串计数至少与我们期望的符号数量一样长,但是它们有时可以超出示波。我们没有观察到比最终符号的数量低的计数。

额外的计数是由于修改 connect 字符的字符所致,这些字符对最终外观有影响,但并未作为单独的符号。<<<<<<<<<<<<<<<<< /p>

因此,有两组主要的规则将指导我们的代码结构:

通常应忽略修饰符

  • 替代对(两个字符的组合生成一个单个符号以扩展Unicode空间的目的) 3 ,应忽略,因为这对表示单个视觉实体。
  • 变体编码(例如,针对红色心脏表情符号的编码)应与它们所代表的变体的角色融合在一起,并且不应具有视觉含义。
  • 同样,肤色选择器增强了另一个身体部位表情符号,并与该外观融合在一起。
    • 例外:对于具有自己图形表示的修饰符(例如肤色修饰符),如果自己使用,则应该算作不同的。

连接器序列应暂停计数

  • 零宽度木匠(ZWJ)表示以前的和后续的独立符号应视为一个单元。
  • 连接器序列由ZWJ连接的单个图形链定义。
    • 例如,家庭表情符号的一种变体(𩧧)由四个独立符号组成('ð由ZWJ连接。所有这些不同的元素由于与ZWJ的联系而以视觉上的视觉和计数组合在一起。

代码实现

零件

  • 删除替代配对:将字符串散布到阵列中([...str])将删除任何替代对(臭名昭著的"💩".length等于2问题)。
  • 删除变体选择器:差异不会删除变体编码(使Emoji的编码使表情符号变成红色€€s符号),因此,这些仍然返回计数为了解决这个问题,我们 split 在捕获这些编码(/[\u{fe00}-\u{fe0f}]/gu)的正则表达式(REGEX)上的字符串。将字符串分开然后再次加入后,将删除变体(str.split(regex).join(""))。
  • 删除修饰符:相同的分裂方法,扭曲。我们仍然想计算修饰符,如果它们仅表示自己并因此出现 - 并且不会修改其他任何内容。因此,我们的分离器是一个综合:

    • 修饰符捕获:在这里,我们将自己限制在皮肤修饰符上,但很容易推断出其他情况:[\u{1f3fb}-\u{1f3ff}]
    • 否定的lookbehind :我们前提是修饰符是在它修改的事物之后出现的。因此,不应先于一个空间,也不应放置在线的开头。我们还为修饰符没有修改普通脚本字母的前提。因此,无论是捕获修饰符是否被捕获的情况,这种情况是:(?<!(\p{L}|^|\s|\p{Punctuation}))
    • 最终正则/(?<!(\p{L}|^|\s|\p{Punctuation}))[\u{1f3fb}-\u{1f3ff}]/gu
  • ZWJ的解释

    • 删除了替代物,变体和修饰符后,我们最后在ZWJ Capture Regex上拆分字符串:/\u{200d}/gu
    • 如果拆分长度为1,我们没有ZWJ,可以安全地加入过滤的字符串,张开并计数其长度。
    • 否则,我们通过以下方式减少数组来计算数组的长度:
    • 对于第一个元素,我们占用它的长度。
    • 对于后续元素,我们添加其长度,然后减去1 以调整当前元素通过ZWJ形成单个单元的事实。

整个交易

export const characterCount = (str: string) => {
  // Not strictly needed for the count, but why not normalize, if we can 😀
  const normalized = str.normalize();

  // Define regex selectors
  const variantsSelector = /[\u{fe00}-\u{fe0f}]/gu;
  const skinModifiers = /(?<!(\p{L}|^|\s|\p{Punctuation}))[\u{1f3fb}-\u{1f3ff}]/gu;
  const zeroJoinRegEx = /\u{200d}/gu;

  // Remove variants and modifiers.
  const purifiedStr = normalized
    .split(variantsSelector)
    .join("")
    .split(skinModifiers)
    .join("");

  //
  const splitWithZero = purifiedStr.split(zeroJoinRegEx);

  if (splitWithZero.length === 1) {
    return [...splitWithZero.join("")].length;
  }

  // Because an emoji that contains ZWJ can contain other text left and right from it
  // we need to count the entire text length from each part, then subtract one.
  // For example: "A 👩‍❤️‍👨 is two people and a heart" splits into  [ 'A 👩', '❤️', '👨 is two people and a heart' ]
  const total = splitWithZero.reduce((sum, curr, currIndex) => {
    if (currIndex === 0) return (sum += [...curr].length);
    sum += [...curr].length - 1;
    return sum;
  }, 0);

  return total;
};

  1. 例如,众所周知的"💩".length === 2网页在解释试图计算Unicode编码文本的长度的不同特殊性方面做得很棒。

    但是,在解决ZWJ问题时,每当检查的字符串具有多个字符时,它都不会正确计数。在这种情况下,它甚至将返回分数值!对于使用修饰符的情况,例如皮肤修饰符。

    也将失败。

    要观察这些差异,请查看示例test suite

  2. 有许多涉及Unicode和JavaScript的交集的重要来源。除了已经引用过的"💩".length === 2,您还邀请您看看The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)What every JavaScript developer should know about UnicodeJavaScript has a Unicode problem

  3. 参见Surrogate pairs and variation selectors