【笔记】 emoji 字符的识别与过滤

在这里插入图片描述

识别和过滤 emoji 字符前,先来了解 emoji 字符。


像下面这种字符,是本文要讨论的 emoji (绘文字)字符:

😄😊😃😍😉

Emoji 绘文字(日语:絵文字/えもじ emoji)不是图片,每个emoji都像文字一样拥有独立编码并且可以存放于字库中,所以可以理解其为图形文字,实际使用中也是和文字一样的使用,可以复制粘贴和输入。

Emoji 是 Unicode 的一部分,它在 Unicode 中有对应的码点( CodePoint),也就是说,Emoji 符号就是一个 Unicode 字符。

常见的Emoji表情符号在Unicode字符集中的范围和具体的字节映射关系
可通过 Emoji Unicode Tables 查看到:https://apps.timwhitlock.info/emoji/tables/unicode#block-6c-other-additional-symbols
注:本篇文章在不同平台下观看效果会不一样

Emoji 码点

Unicode 组织的 Unicode® Emoji Charts v11.0 页面中可以找到完整的 Emoji 码点数据: emoji-data.txt

通过 脚本 getEmojiData.sh 处理 ,可以得到一个 完整的的码表

#! /bin/bash
cat "$1" | sed -n -e '/^#/d' -e '/^$/d' -e 's/[ ]*;.*$//p' | sort -u
$ ./getEmojiData.sh emoji-data.txt > emoji-all-data.txt

Emoji 在 Unicode 编码中的位置

在 Unicode 编码中,emoji主要安排在 1号平面第241行至第247行(1F000-1F6FF),以及 0号平面第39行和40行(2600-27FF)等位置

  • 2010年10月发布的Unicode 6.0版首次收录绘文字编码,其中582个绘文字符号,66个已在其他位置编码,保留作兼容用途的绘文字符号。
  • 在Unicode 9.0 用22区块中共计1,126个字符表示绘文字,其中:
    • 1,085个是独立绘文字字符;
    • 26个是用来显示旗帜的区域指示符号;
    • 以及 12 个(#, * and 0-9)键帽符号;
    • 杂项符号及图形768个字符中有637是绘文字;
    • 增补符号及图形82个字符中有80个是绘文字;
    • 所有80个表情符号都是绘文字;
    • 交通及地图符号103个字符中有92个是绘文字;
    • 杂项符号256个字符中有80个是绘文字;
    • 装饰符号192个字符中有33个是绘文字。

Emoji 字符长度

上一篇 文章提到,像 java、js 这些常用的编成语言是以 utf-16 来处理字符编码的。

于是,获取字符长度的时候,其实是根据 16位(2字节) 作为一个编码单元来分割字符串长度的。

于是,我们会看到下面的情况:(明明只有一个字符,长度却不为一的情况)

// javascript
"😀".length // 2
"🇨🇳".length // 4
"👩🏽‍🦳".length // 7
"👨‍👩‍👧‍👧".length // 11

可是,长度为 2 编码单元 可以理解为 码点 在 辅助平面 上,但 4、7、11 怎么理解?

这就涉及到Unicode的一个很重要的特性:组合字符

组合字符、字位簇

组合字符

Unicode 包含一个系统,可以合并多个编码点,动态组合字符。此系统用各种方式增加灵活性,而不引起编码点的巨大组合膨胀。

例如:

带重音的字符 “Á” 会被表示成由两个编码点组成的字符串:U+0041 “A” 拉丁大写字母 a 加上 U+0301 “◌́”组合尖音符号。这个字符串自动被渲染成单个字符:“Á”。

字位簇

如上所见,Unicode 包含多种情况,用户认为的一个“字符” 事实上底下可能由多个编码点组成。Unicode 使用「字位簇」的概念来表示这种情况。一个由一个或多个编码点组成的字符串构成一个 “用户感知的字符”。

UAX #29 为字位丛定义了精确的规则。
字位簇主要被用在文本编辑:

  • 它们对光标和文本选择来说是最明显的单元;
  • 使用字位簇,确保在复制和粘贴文本时不会突然丢掉一些符号;
  • 同时左右方向键也总是以一个可见字符的距离移动;
  • 等等

组合规则

现在,我们知道了一个Emoji表情可能由多个码点组成,这些码点都遵循着一定的规则来组合成不同的 Emoji 表情,我们来看下几种常见的规则:

  1. 单Unicode
    最基本的Emoji表情,码点位于辅助平面上。在UTF-16下通过String.length()会被判断为2个长度,可以使用String.codePoints()通过码点数来获取正确的长度。
    在这里插入图片描述

    String.fromCodePoint(parseInt("1F600", 16))
    // '😀'
    
  2. 双Unicode
    最具代表性的就是旗帜序列(Flag Sequence),这类 Emoji 串是通过两个地域指示符(regional_indicator)组合的方式来表示一个国家的国旗。
    总共有 26 个地域指示符(U+1F1E6 ~ U+1F1FF),每个指示符又对应于一个英文字母含义,例如 U+1F1E8 为地域指示符 C, U+1F1F3 为地域指示符 N。这些指示符两两组合表示一个国旗CN即中国国旗(🇨🇳)

    在不支持Emoji5.0的系统上,会被显示为两个字母Emoji表情(🇨 🇳)。并不是 26 x 26 种组合是全部合法的,合法的 Flag Sequence 只有 256 种

    在这里插入图片描述

    String.fromCodePoint(parseInt("1F1E8", 16), parseInt("1F1F3", 16))
    // '🇨🇳'
    
  3. 变量选择器
    在众多 Emoji 中, 有一些特殊的 Emoji 并没有显示的样式, 只是起到了控制的作用。这些控制型的 Emoji 与基础 Emoji 出现在一起, 可以展示更多的样式。比如 变量选择器

    • 变量选择器 - 15 (VARIATION SELECTOR-15, 简写 VS-15):<U+FE0E>, 作用是让基础 Emoji 变成更接近文本样式 (text-style);
    • 变量选择器 - 16 (VARIATION SELECTOR-16, 简写 VS-16): <U+FE0F>, 作用则是让基础 Emoji 变成更接近Emoji 样式 (emoji-style).

    VS-15 和 VS-16 加在基础 Emoji 字符的后面, 可以起到控制作用 (前提是必须系统支持, 否则会被忽略)。

    在这里插入图片描述

    String.fromCodePoint(parseInt("26A0", 16));
    // '⚠'
    '⚠'.length
    // 1
    String.fromCodePoint(parseInt("26A0", 16), parseInt("FE0E", 16)); // 更接近文本样式 (text-style)
    // '⚠︎'
    '⚠︎'.length
    // 2
    '⚠' == '⚠︎'
    // false
    String.fromCodePoint(parseInt("26A0", 16), parseInt("FE0F", 16)); // 更接近Emoji 样式 (emoji-style).
    // '⚠️'
    

    而在 VS-16 的基础上,还有一种 键帽序列 (KeyCap Sequence),这类 emoji 序列是将数字 (0-9)*# 通过一个 U+20E3 字符转换为键帽的样式。由于这种样式要求必须以 emoji 风格展示,所有会在序列中添加样式限制 U+FE0F。例如 U+0023 U+FE0F U+20E3 的 emoji 样式即是 #️⃣,U+0030 U+FE0F U+20E3 的 emoji 样式即是 0️⃣。其它与此类似。

    String.fromCodePoint(parseInt("0023", 16));
    // '#'
    String.fromCodePoint(parseInt("0023", 16), parseInt("FE0F", 16));
    // '#️'
    String.fromCodePoint(parseInt("0023", 16), parseInt("FE0F", 16), parseInt("20E3", 16));
    // '#️⃣'
    

    在这里插入图片描述

    另外, 还有一些控制型的 Emoji, 可以对人体肤色进行改变,改变对象仅限于 “表示人身体部位的Emoji”。
    目前定义了五种修饰字符,分别表示颜色的由浅及深,它们分别是: U+1F3FB ~ U+1F3FF (🏻…🏿) 共五个, 分别简称为:

    1. FITZ-1-2
    2. FITZ-3
    3. FITZ-4
    4. FITZ-5
    5. FITZ-6

    例如,U+270D (✍️) 就是一个可以被修饰的 emoji 字符,那么它被 U+1F3FF 修饰后就会变成 U+270D U+1F3FF(✍️🏿)。

    var colors = ['1F3FB', '1F3FC', '1F3FD', '1F3FE', '1F3FF']
    for(var i=0; i<colors.length; i++) {
        var color = colors[i]
        var c = String.fromCodePoint(parseInt("270D", 16), parseInt(color, 16));
        console.log(c)
    }
    // ✍🏻
    // ✍🏼
    // ✍🏽
    // ✍🏾
    // ✍🏿
    
  4. 无缝连接序列
    上面说到,通过一些特定的Emoji组合,可以结合出不同肤色的表情
    性别,职业,等也可以结合
    这种特殊不可见的排列方式被称为 “无缝连接” (“Zero-width joiner,即ZWJ”)
    U+200D 便是连接这些表情的字符
    例如:U+1F468 U+200D U+1F469 U+200D U+1F467 (👨‍👩‍👧) 这个 emoji 表示家庭即由三个emoji字符经 ZWJ 连接而成的:

    1. U+1F468(👨)
    2. U+1F469(👩)
    3. U+1F467(👧)

    当然不局限于家庭人物,包括职业,运动等许多都是用这种方式组成的

    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述

Emoji的碎片化 💣

上述的 组合字符 提供非常便利的方案来快速的扩充 emoji 字符的丰富度。Emoji表情和ZWJ字符串不需要标准码协会批准就可以建立并在自有平台上使用 (不需要耗一个月甚至一年的时间等候审批,使表情开发变得更快)。即使在不支持ZWJ的老版本中,最多也是显示两个或是两个以上独立的表情,添加新的代码不会破坏其他或是出现丑陋的问号块。

标准码协会利用ZWJ字符序列的方式(可以跨多平台使用),使得各IT公司可以轻易地进行开发,不过同时也有个明显的问题。

苹果或是谷歌可以自主添加标志或解决问题,而不会影响与其他平台的兼容。这也使以ZWJ序列排列出的表现被跨平台支持,但事实上却没能被支持:

  • 各个平台都在开发属于自己的表情,会导致不同平台间的符号不兼容,比如字符长度的问题,在IOS系统上,一个Emoji表情发送到Android手机上,可能会出现4、5个,如果在有长度限制的条件下,便可能会出现截断的问题;
  • 标准码协会提供所有表情符号的名称和简单的图片,但任何Emoji文章展示,你通过手机和电脑看起来也有轻微的区别;
    (不同的操作系统和程序开发者都想通过不同的emoji表情来达到更美观,而不是用统一的通用字符集。)

上述的问题导致了Emoji的混乱,这点其实跟Unicode的“统一”多多少少是有点冲突的。但不管怎么说,Emoji都是一个非常伟大且成功的发明。

Emoji 的识别

识别 Emoji 问题在于划定其编码范围。但经过上面的描述我们知道,Emoji 的编码其实也是零散的(组合字符的使用导致任何一个普通字符都可能发展成 emoji 字符)

如何确定 Emoji 的编码范围?

可以查看官网给出的码点范围:http://www.unicode.org/charts/

相对应的,下面是识别 emoji 的 utf-16 编码正则表达式:
(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])

https://ihateregex.io/expr/emoji/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

骆言

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值