深入理解Emoji(一) —— 字符集,字符集编码 深入理解Emoji(二) —— 字节序和BOM

Emoji字符是Unicode字符集中一部分. 特定形象的Emoji表情符号对应到特定的Unicode字节。常见的Emoji表情符号在Unicode字符集中的范围和具体的字节映射关系, 可通过Emoji Unicode Tables查看到。

注:本篇文章在不同平台下观看效果会不一样

问题引申

首先来看看我遇到的问题:

val smile  =  "?"
print("smile emoji length = ${smile.length}")val flag = "??"
print("flag emoji length = ${flag.length}")val portrait = "??‍?"
print("portrait emoji length = ${portrait.length}")val family = "?‍?‍?‍?"
print("family emoji length = ${family.length}")
复制代码

输出结果为:

smile emoji length = 2
flag emoji length = 4
portrait emoji length = 7
family emoji length = 11
复制代码

有没有觉得很奇怪,按我们之前所说,一个emoji表情应该也是属于一个字符,占据着Unicode的一个码点,为什么会出现2、4甚至是7、11个字符长度的情况呢?我们去看看String.length()的源码:

public int length() {return value.length >> coder();}
复制代码

coder()这个方法是判断当前的编码获取相应的值,默认是UTF-16,值为1,因为Java内部的默认编码是UTF-16。也就是说,当字符的码点在辅助平面时,String.length()的实现方式会将其判断为长度为2。Emoji表情所有的码点都在辅助平面上,那就解释了第一个,为什么长度为2,那大于2的那些又是怎么回事呢?这就涉及到Unicode的一个很重要的特性:组合字符

组合字符

Unicode 包含一个系统,可以合并多个编码点,动态组合字符。此系统用各种方式增加灵活性,而不引起编码点的巨大组合膨胀。 例如,在欧洲语言中,组合标记出现在变音符和字母的使用中。 Unicode 支持各种各样的变音符号,包括尖音符号的和重音符号、元音变音符号、变音符号等等。所有这些变音符可以被使用在任何字母表的字母中。事实上,多个变音符号可以被使用在一个字母上。

如果 Unicode 试图为每个字母组合或变音符组合分配一个独立的编码点,事情会变得无法控制。相反,动态组合系统可以让你构造你想要的任何字符,通过以一个基础编码点(字母)开始然后附加额外的编码点,被称作“组合标识”,来指定变音符。当一个文字渲染器看到字符串中有这样的序列时,它会自动堆叠变音符到基础字母的上面或下面来造出一个组合字符。

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

有时候我们会看到某些人的签名中有很奇怪的字符,其实他们就是利用了组合字符。比如Á́́ 就是多添加了几个尖音符号:U+0041U+0301U+0301U+0301,是不是感觉挺有意思?

字位簇

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

UAX #29 为字位丛定义了精确的规则。它大约是 “一个基本的编码点接着任意数量的组合标记”,但是真实的定义有点复杂;它包含了朝鲜语字母,和 emoji ZWJ 序列。

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

另一个用到字位簇的地方是,执行字符串长度限制——比如在数据库域中。其实,底层的限制可能是类似 UTF-8 中的字节长度之类的东西,你不能简单的通过截断字节的方式来限制长度。至少,你得 “舍去” 最近的编码点;但更好的是,舍去最近的字位簇。除此以外,你可以通过舍弃它的一个注音符号破坏一个字符,中断一个 jamo 序列或 ZWJ 序列。

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

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

  • #####双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 种。这种Emoji表情通过String.length()会被判断为4个长度。

  • #####变量选择器 在众多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字符的后面, 可以起到控制作用(前提是必须系统支持, 否则会被忽略)。在UTF-16下通过String.length()会被判断为2个长度。

而在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️⃣。其它与此类似。在UTF-16下通过String.length()会被判断为3个长度。 另外, 还有一些控制型的Emoji, 可以对人体肤色进行改变,改变对象仅限于"表示人身体部位的Emoji"。目前定义了五种修饰字符,分别表示颜色的由深及浅,它们分别是: U+1F3FB ~ U+1F3FF (?..?)共五个, 分别简称为: FITZ-1-2, FITZ-3, FITZ-4, FITZ-5, FITZ-6。例如,U+270D(✍️) 就是一个可以被修饰的 emoji 字符,那么它被U+1F3FF修饰后就会变成U+270D U+1F3FF(✍️?)。

  • 无缝连接序列

上面说到,通过一些特定的Emoji组合,可以结合出不同肤色的表情,在增加Emoji的丰富度的同时,不需要增加过多的码点。那性别,职业呢?是不是也可以用这种方式,答案是肯定的,只不过实现的方法有点不一样。

通常,每一个emoji表情都是由特定的字符来展现的,新创造一个emoji表情意味着要新建一个符号来与之关联。以肤色和性别为例,标准码协会提出更多创造性的解决方案,比如选择将多个代码结合在一起来创建一个新表情。

不同性别的表情所代表的职业如何来展现的呢?以一个标准的“男性”或是“女性”表情再添加个代表职业的表情,就能展现“男性”某职业或女性某职业这样一个表情,而不是两个表情。这种特殊不可见的排列方式被称为“无缝连接”(“Zero-width joiner,即ZWJ”)。在iOS 10、Android N平台支持这种组合表情,看到ZWJ就知道显示一个表情而不是分离的两个。

U+200D便是连接这些表情的字符。例如,U+1F468 U+200D U+1F469 U+200D U+1F467 (?‍?‍?) 这个 emoji 表示家庭即由三个emoji字符,U+1F468(?), U+1F469(?), U+1F467(?) 经 ZWJ 连接而成的。长度为8,而上面问题里提到的?‍?‍?‍?,可以看到多了一个连接符和一个长度为2的基本Emoji表情,所以打印出来是11。

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

标准码协会利用ZWJ字符序列的方式(可以跨多平台使用),使得各IT公司可以轻易地进行开发,不过同时也有个明显的问题。**Emoji表情和ZWJ字符串不需要标准码协会批准就可以建立并在自有平台上使用。**即使在不支持ZWJ的老版本中,最多也是显示两个或是两个以上独立的表情,添加新的代码不会破坏其他或是出现丑陋的问号块。

不需要耗一个月甚至一年的时间等候审批,可以使表情开发变得更快,苹果或是谷歌可以自主添加标志或解决问题,而不会影响与其他平台的兼容。另一方面,这也使以ZWJ序列排列出的表现被跨平台支持,但事实上却没能被支持。各个平台都在开发属于自己的表情,会导致不同平台间的符号不兼容,比如字符长度的问题,在IOS系统上,一个Emoji表情发送到Android手机上,可能会出现4、5个,如果在有长度限制的条件下,便可能会出现截断的问题。

Emoji的碎片化

标准码协会提供所有表情符号的名称和简单的图片,但任何Emoji文章展示,你通过手机和电脑看起来也有轻微的区别。不同的操作系统和程序开发者都想通过不同的emoji表情来达到更美观,而不是用统一的通用字符集。如同我们的截图,同一个Emoji表情码,有不同的平台上有各式各样的表现形式。又因为SWJ的存在,导致各个平台有属于自己的一套表情,这就导致了Emoji的混乱,这点其实跟Unicode的“统一”多多少少是有点冲突的。但不管怎么说,Emoji都是一个非常伟大且成功的发明。

转载于:https://juejin.im/post/5c00b31a5188251d9e0c4a59

深入理解Emoji(三) —— Emoji详解相关推荐

  1. Transformer(二)--论文理解:transformer 结构详解

    转载请注明出处:https://blog.csdn.net/nocml/article/details/110920221 本系列传送门: Transformer(一)–论文翻译:Attention ...

  2. DFT - 对芯片测试的理解(二) 详解

    DFT - 对芯片测试的理解(二) 详解 参考: https://www.docin.com/p-2014360649.html The basic view of DFT scan chain 这图 ...

  3. C++11 并发指南三(Lock 详解)(转载)

    multithreading 多线程 C++11 C++11多线程基本使用 C++11 并发指南三(Lock 详解) 在 <C++11 并发指南三(std::mutex 详解)>一文中我们 ...

  4. mac linux win三系统安装教程,macbookpro上安装三系统详解教程(macosxwindowslinuxubuntu).doc...

    macbookpro上安装三系统详解教程(macosxwindowslinuxubuntu) macbook pro上安装三系统详解教程(mac os x+windows+linux ubuntu) ...

  5. 数据库三范式详解,优缺点,解决了什么问题?

    https://zhuanlan.zhihu.com/p/20028672 三范式详解 解决了什么问题:(上面也有讲到)https://blog.csdn.net/qq_41174684/articl ...

  6. Spring深入理解之ComponentScan___@ComponentScan 详解

    Spring深入理解之ComponentScan 一.概述 ComponentScan顾名思义包扫描,底层其实就可以通过递归算法+反射将其装载成bean来实现的,实在开发过程中,Spring已经帮我们 ...

  7. 强大的数据分析工具——Pandas操作、易错点、知识点三万字详解

    一. Pandas数据结构 1.Series 2.DataFrame 3.从DataFrame中查询出Series DataFrame: 二维数据.整个表格.多行多列 Series:一维数据,一行或者 ...

  8. Android Telephony分析(三) ---- RILJ详解

    前言 本文主要讲解RILJ工作原理,以便更好地分析代码,分析业务的流程.  这里说的RILJ指的是RIL.java (frameworks\opt\telephony\src\java\com\And ...

  9. C++11 并发指南三(Lock 详解)

    在 <C++11 并发指南三(std::mutex 详解)>一文中我们主要介绍了 C++11 标准中的互斥量(Mutex),并简单介绍了一下两种锁类型.本节将详细介绍一下 C++11 标准 ...

  10. 【广度优先搜索】一个实例+两张动图彻底理解 BFS | 思路+代码详解 | 用 DFS 自动控制我们的小游戏

    前言: 在 第一篇文章 中,我们讨论了 如何用 pygame 写一个小游戏,并用键盘交互控制 .接下来,我们将分别用 DFS .BFS .DRL 实现自动控制.DFS 已经在 这篇文章 中讨论过,现在 ...

最新文章

  1. HashTable原理与实现
  2. 动态注册客户端脚本的方法
  3. sqlserver 多排序的问题
  4. 《VMware Virtual SAN权威指南(原书第2版)》一1.5 什么是Virtual SAN
  5. .NET Core开发实战(第12课:配置变更监听)--学习笔记
  6. leetcode712. 两个字符串的最小ASCII删除和(动态规划)-Gogo
  7. 潜伏者与谍报密码(洛谷P1071题题解,Java语言描述)
  8. pymysql 返回数据为字典形式(key:value--列:值)
  9. 苹果或已放弃3月发布廉价新iPhone;贾跃亭回应家人巨额索赔;微软不再继续开发 Visual Basic | 极客头条...
  10. MYOP究竟能为站长提供哪些便利
  11. 君正X1500基于Minios的crash分析
  12. 关于Lambda表达式的简单语法理解,有参无参,有无返回值的格式的理解,仅限编程新手
  13. 冲突域和广播域的区分
  14. 在 PyCharm 中使用 PyInstaller 打包 EXE 之过程简记
  15. skynet master/slave 模式
  16. Impala graceful shutdown功能介绍
  17. 【机器学习|数学基础】Mathematics for Machine Learning系列之线性代数(5):克拉默法则
  18. 监控系统存储服务器和磁盘阵列,浅谈磁盘阵列如何应用于监控储存领域
  19. 100以内的质数(素数)
  20. 如何快速识别两张照片的相似程度(用百分比)

热门文章

  1. php ckey=6,ThinkPHP6 核心分析(十):事件
  2. wince系统改安卓系统_什么是实时操作系统(RTOS)
  3. soapui工具_基于开源的API测试工具!不再为web服务负载测试而发愁
  4. 华中科技大学计算机上机,华中科技大学计算机学院上机复试题目.doc
  5. 浏览器自动调html5,HTML5 浏览器支持
  6. linux登陆连接信息,成功登录后Linux关闭连接
  7. linux下的ping脚本,Linux下检测服务器Ping值的Shell脚本
  8. Java数据结构 栈中添加辅助栈实现min函数
  9. JavaScript入门经典(第4版)
  10. Win10如何查看我们的电池健康