深入理解Emoji(三) —— Emoji詳解

深入理解Emoji(一) —— 字符集,字符集編碼
深入理解Emoji(二) —— 字節(jié)序和BOM

Emoji字符是Unicode字符集中一部分. 特定形象的Emoji表情符號對應到特定的Unicode字節(jié)。常見的Emoji表情符號在Unicode字符集中的范圍和具體的字節(jié)映射關系, 可通過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表情應該也是屬于一個字符,占據(jù)著Unicode的一個碼點,為什么會出現(xiàn)2、4甚至是7、11個字符長度的情況呢?我們去看看String.length()的源碼:

public int length() {
        return value.length >> coder();
    }

coder()這個方法是判斷當前的編碼獲取相應的值,默認是UTF-16,值為1,因為Java內部的默認編碼是UTF-16。也就是說,當字符的碼點在輔助平面時,字符的字節(jié)數(shù)為4,String.length()的實現(xiàn)方式會將其判斷為長度為2。Emoji表情所有的碼點都在輔助平面上,那就解釋了第一個,為什么長度為2,那大于2的那些又是怎么回事呢?這就涉及到Unicode的一個很重要的特性:組合字符

組合字符

Unicode 包含一個系統(tǒng),可以合并多個編碼點,動態(tài)組合字符。此系統(tǒng)用各種方式增加靈活性,而不引起編碼點的巨大組合膨脹。
例如,在歐洲語言中,組合標記出現(xiàn)在變音符和字母的使用中。 Unicode 支持各種各樣的變音符號,包括尖音符號的和重音符號、元音變音符號、變音符號等等。所有這些變音符可以被使用在任何字母表的字母中。事實上,多個變音符號可以被使用在一個字母上。

如果 Unicode 試圖為每個字母組合或變音符組合分配一個獨立的編碼點,事情會變得無法控制。相反,動態(tài)組合系統(tǒng)可以讓你構造你想要的任何字符,通過以一個基礎編碼點(字母)開始然后附加額外的編碼點,被稱作“組合標識”,來指定變音符。當一個文字渲染器看到字符串中有這樣的序列時,它會自動堆疊變音符到基礎字母的上面或下面來造出一個組合字符。

例如,帶重音的字符“á” 會被表示成由兩個編碼點組成的字符串:U+0041 “A” 拉丁大寫字母 a 加上 U+0301 “??”組合尖音符號。這個字符串自動被渲染成單個字符:“á”。

有時候我們會看到某些人的簽名中有很奇怪的字符,其實他們就是利用了組合字符。比如á?? 就是多添加了幾個尖音符號:U+0041U+0301U+0301U+0301,是不是感覺挺有意思??

字位簇

如上所見,Unicode 包含多種情況,用戶認為的一個“字符” 事實上底下可能由多個編碼點組成。Unicode 使用「字位簇」的概念來表示這種情況。一個由一個或多個編碼點組成的字符串構成一個 “用戶感知的字符”。

UAX #29 為字位叢定義了精確的規(guī)則。它大約是 “一個基本的編碼點接著任意數(shù)量的組合標記”,但是真實的定義有點復雜;它包含了朝鮮語字母,和 emoji ZWJ 序列。

字位簇主要被用在文本編輯:它們對光標和文本選擇來說是最明顯的單元。使用字位簇,確保在復制和粘貼文本時不會突然丟掉一些符號,同時左右方向鍵也總是以一個可見字符的距離移動,等等。

另一個用到字位簇的地方是,執(zhí)行字符串長度限制——比如在數(shù)據(jù)庫域中。其實,底層的限制可能是類似 UTF-8 中的字節(jié)長度之類的東西,你不能簡單的通過截斷字節(jié)的方式來限制長度。至少,你得 “舍去” 最近的編碼點;但更好的是,舍去最近的字位簇。除此以外,你可以通過舍棄它的一個注音符號破壞一個字符,中斷一個 jamo 序列或 ZWJ 序列。

Emoji組合規(guī)則

現(xiàn)在,我們知道了一個Emoji表情可能由多個碼點組成,這些碼點都遵循著一定的規(guī)則來組合成不同的Emoji表情,我們來看下幾種常見的規(guī)則:

  • 單Unicode

最基本的Emoji表情,碼點位于輔助平面上。在UTF-16下通過String.length()會被判斷為2個長度,可以使用String.codePoints()通過碼點數(shù)來獲取正確的長度。

單Unicode組成的笑臉

  • 雙Unicode

最具代表性的就是旗幟序列(Flag Sequence),這類 Emoji 串是通過兩個地域指示符(regional_indicator)組合的方式來表示一個國家的國旗。總共有 26 個地域指示符(U+1F1E6~U+1F1FF),每個指示符又對應于一個英文字母含義,例如 U+1F1E8 為地域指示符 C, U+1F1F3 為地域指示符 N。這些指示符兩兩組合表示一個國旗CN即中國國旗(????),在不支持Emoji5.0的系統(tǒng)上,會被顯示為兩個字母Emoji表情(?? ??)。并不是 26 x 26 種組合是全部合法的,合法的 Flag Sequence 只有 256 種。這種Emoji表情通過String.length()會被判斷為4個長度。

旗幟序列組成的國旗

  • 變量選擇器

在眾多Emoji中, 有一些特殊的Emoji 并沒有顯示的樣式, 只是起到了控制的作用。這些控制型的Emoji 與基礎Emoji 出現(xiàn)在一起, 可以展示更多的樣式。比如 變量選擇器

變量選擇器-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字符的后面, 可以起到控制作用(前提是必須系統(tǒng)支持, 否則會被忽略)。在UTF-16下通過String.length()會被判斷為2個長度。

變量選擇器樣式對比

而在VS-16的基礎上,還有一種鍵帽序列(KeyCap Sequence),這類 emoji 序列是將數(shù)字(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, 可以對人體膚色進行改變,改變對象僅限于"表示人身體部位的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的豐富度的同時,不需要增加過多的碼點。那性別,職業(yè)呢?是不是也可以用這種方式,答案是肯定的,只不過實現(xiàn)的方法有點不一樣。

通常,每一個emoji表情都是由特定的字符來展現(xiàn)的,新創(chuàng)造一個emoji表情意味著要新建一個符號來與之關聯(lián)。以膚色和性別為例,標準碼協(xié)會提出更多創(chuàng)造性的解決方案,比如選擇將多個代碼結合在一起來創(chuàng)建一個新表情。

不同性別的表情所代表的職業(yè)如何來展現(xiàn)的呢?以一個標準的“男性”或是“女性”表情再添加個代表職業(yè)的表情,就能展現(xiàn)“男性”某職業(yè)或女性某職業(yè)這樣一個表情,而不是兩個表情。這種特殊不可見的排列方式被稱為“無縫連接”(“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(??) 經(jīng) ZWJ 連接而成的。長度為8,而上面問題里提到的???????????,可以看到多了一個連接符和一個長度為2的基本Emoji表情,所以打印出來是11。

SWJ

當然不局限于家庭人物,包括職業(yè),運動等許多都是用這種方式組成的
SWJ職業(yè)

SWJ運動

標準碼協(xié)會利用ZWJ字符序列的方式(可以跨多平臺使用),使得各IT公司可以輕易地進行開發(fā),不過同時也有個明顯的問題。Emoji表情和ZWJ字符串不需要標準碼協(xié)會批準就可以建立并在自有平臺上使用。即使在不支持ZWJ的老版本中,最多也是顯示兩個或是兩個以上獨立的表情,添加新的代碼不會破壞其他或是出現(xiàn)丑陋的問號塊。

不需要耗一個月甚至一年的時間等候審批,可以使表情開發(fā)變得更快,蘋果或是谷歌可以自主添加標志或解決問題,而不會影響與其他平臺的兼容。另一方面,這也使以ZWJ序列排列出的表現(xiàn)被跨平臺支持,但事實上卻沒能被支持。各個平臺都在開發(fā)屬于自己的表情,會導致不同平臺間的符號不兼容,比如字符長度的問題,在IOS系統(tǒng)上,一個Emoji表情發(fā)送到Android手機上,可能會出現(xiàn)4、5個,如果在有長度限制的條件下,便可能會出現(xiàn)截斷的問題。

Emoji的碎片化

標準碼協(xié)會提供所有表情符號的名稱和簡單的圖片,但任何Emoji文章展示,你通過手機和電腦看起來也有輕微的區(qū)別。不同的操作系統(tǒng)和程序開發(fā)者都想通過不同的emoji表情來達到更美觀,而不是用統(tǒng)一的通用字符集。如同我們的截圖,同一個Emoji表情碼,有不同的平臺上有各式各樣的表現(xiàn)形式。又因為SWJ的存在,導致各個平臺有屬于自己的一套表情,這就導致了Emoji的混亂,這點其實跟Unicode的“統(tǒng)一”多多少少是有點沖突的。但不管怎么說,Emoji都是一個非常偉大且成功的發(fā)明。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 某一天,leader找到我說,felix啊,這里有個小需求,給我們的實名認證中的地址加入字數(shù)限制,一天時間綽綽有余...
    felix9閱讀 20,790評論 0 15
  • 在當前這個時代(比如說公元2016年),如果你并不是在維護歷史遺留的文本處理代碼,沒有在每個地方都使用Unicod...
    縱橫而樂閱讀 2,845評論 3 16
  • 本文轉自 微信公眾號 jobbole 程序員世界對這個名字發(fā)自內心的恐懼和敬畏。我們都知道在我們的軟件中應該 “支...
    faremax閱讀 1,754評論 0 3
  • 文/蘇子游 不知道,你有沒有過這樣一種經(jīng)歷,不知道為什么,最近好像過得不是太順心。 復習了好久的期末考結果考砸了,...
    蘇子游閱讀 1,582評論 6 21
  • 別忘了真正去活 起初—我想進大學,想得要死; 隨后—我巴不得大學趕快畢業(yè),能早點工作; 接著—我想結婚,想有...
    周春Baby閱讀 200評論 0 0

友情鏈接更多精彩內容