深入理解Emoji(三) —— Emoji详解
深入理解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详解相关推荐
- Transformer(二)--论文理解:transformer 结构详解
转载请注明出处:https://blog.csdn.net/nocml/article/details/110920221 本系列传送门: Transformer(一)–论文翻译:Attention ...
- DFT - 对芯片测试的理解(二) 详解
DFT - 对芯片测试的理解(二) 详解 参考: https://www.docin.com/p-2014360649.html The basic view of DFT scan chain 这图 ...
- C++11 并发指南三(Lock 详解)(转载)
multithreading 多线程 C++11 C++11多线程基本使用 C++11 并发指南三(Lock 详解) 在 <C++11 并发指南三(std::mutex 详解)>一文中我们 ...
- mac linux win三系统安装教程,macbookpro上安装三系统详解教程(macosxwindowslinuxubuntu).doc...
macbookpro上安装三系统详解教程(macosxwindowslinuxubuntu) macbook pro上安装三系统详解教程(mac os x+windows+linux ubuntu) ...
- 数据库三范式详解,优缺点,解决了什么问题?
https://zhuanlan.zhihu.com/p/20028672 三范式详解 解决了什么问题:(上面也有讲到)https://blog.csdn.net/qq_41174684/articl ...
- Spring深入理解之ComponentScan___@ComponentScan 详解
Spring深入理解之ComponentScan 一.概述 ComponentScan顾名思义包扫描,底层其实就可以通过递归算法+反射将其装载成bean来实现的,实在开发过程中,Spring已经帮我们 ...
- 强大的数据分析工具——Pandas操作、易错点、知识点三万字详解
一. Pandas数据结构 1.Series 2.DataFrame 3.从DataFrame中查询出Series DataFrame: 二维数据.整个表格.多行多列 Series:一维数据,一行或者 ...
- Android Telephony分析(三) ---- RILJ详解
前言 本文主要讲解RILJ工作原理,以便更好地分析代码,分析业务的流程. 这里说的RILJ指的是RIL.java (frameworks\opt\telephony\src\java\com\And ...
- C++11 并发指南三(Lock 详解)
在 <C++11 并发指南三(std::mutex 详解)>一文中我们主要介绍了 C++11 标准中的互斥量(Mutex),并简单介绍了一下两种锁类型.本节将详细介绍一下 C++11 标准 ...
- 【广度优先搜索】一个实例+两张动图彻底理解 BFS | 思路+代码详解 | 用 DFS 自动控制我们的小游戏
前言: 在 第一篇文章 中,我们讨论了 如何用 pygame 写一个小游戏,并用键盘交互控制 .接下来,我们将分别用 DFS .BFS .DRL 实现自动控制.DFS 已经在 这篇文章 中讨论过,现在 ...
最新文章
- HashTable原理与实现
- 动态注册客户端脚本的方法
- sqlserver 多排序的问题
- 《VMware Virtual SAN权威指南(原书第2版)》一1.5 什么是Virtual SAN
- .NET Core开发实战(第12课:配置变更监听)--学习笔记
- leetcode712. 两个字符串的最小ASCII删除和(动态规划)-Gogo
- 潜伏者与谍报密码(洛谷P1071题题解,Java语言描述)
- pymysql 返回数据为字典形式(key:value--列:值)
- 苹果或已放弃3月发布廉价新iPhone;贾跃亭回应家人巨额索赔;微软不再继续开发 Visual Basic | 极客头条...
- MYOP究竟能为站长提供哪些便利
- 君正X1500基于Minios的crash分析
- 关于Lambda表达式的简单语法理解,有参无参,有无返回值的格式的理解,仅限编程新手
- 冲突域和广播域的区分
- 在 PyCharm 中使用 PyInstaller 打包 EXE 之过程简记
- skynet master/slave 模式
- Impala graceful shutdown功能介绍
- 【机器学习|数学基础】Mathematics for Machine Learning系列之线性代数(5):克拉默法则
- 监控系统存储服务器和磁盘阵列,浅谈磁盘阵列如何应用于监控储存领域
- 100以内的质数(素数)
- 如何快速识别两张照片的相似程度(用百分比)
热门文章
- php ckey=6,ThinkPHP6 核心分析(十):事件
- wince系统改安卓系统_什么是实时操作系统(RTOS)
- soapui工具_基于开源的API测试工具!不再为web服务负载测试而发愁
- 华中科技大学计算机上机,华中科技大学计算机学院上机复试题目.doc
- 浏览器自动调html5,HTML5 浏览器支持
- linux登陆连接信息,成功登录后Linux关闭连接
- linux下的ping脚本,Linux下检测服务器Ping值的Shell脚本
- Java数据结构 栈中添加辅助栈实现min函数
- JavaScript入门经典(第4版)
- Win10如何查看我们的电池健康