[Tech] Emoji
像是 :( 、:-) 這樣的表情符號(emoticons)其實已經出現很久了,其辭源來自於表示 emotion(情緒)的 icon(符號)。
不過,現在狹義上指稱的 Emoji,得追溯回 1999 年的日本,NTT DOCOMO 的工程師栗田穰崇在 12x12 的網格上設計的 176 個符號,用來表達天氣狀況、食物、以及星座等意思——這裡的 Emoji,辭源是來自於「絵(え [e])文字(もじ [moji])」——話說不少人以為那個 E 跟 E-mail 的發音一樣,把他讀成「以摸幾」,但從本質來看,Emoji 的發音應該要是「誒摸幾」才對——不過語言是拿來溝通的,大家都聽得懂就好。

Image Source: MoMA
這裡最有趣的一點是,雖然 Emoticon 跟 Emoji 的辭源一個源自英文、一個源自日文,但拼法卻都很接近,指的也是差不多的東西。
在 2000 年時,便有人提案將這些可愛的小東西——在當時被稱作 NTT DoCoMo Pictographs——加入 Unicode 內(L2/00-152 提案),不過在當時的時空背景下,沒有人能保證這些小東西是不是真的泛用且能用,草案便沒了然後。
不過,這些彩色的小東西依舊受到日本人的喜愛,各家電信廠商也接連推出自己的 Emoji 符號集。在當時,每間公司使用的編碼集和標準都不一樣,為了讓不同廠商或手機的使用者可以在信件和訊息中互傳 Emoji,所以彼此之間約定了交叉的碼位映射表,用來解決相容性的問題。
這種辦法在日本境內或許還勉強可行,不過一但有國外使用者使用未約定映射表的設備瀏覽文章,Emoji 就會顯示出亂碼。
在 2006 年左右,Google 打算將 Gmail 引入日本,為了打入市場,必須優先解決 Emoji 編碼的問題,於是便率先使用了 Unicode 的 PUA 區段為 Emoji 分配碼位——但使用 PUA 的問題依舊在於,彼此需要事先約定,如果兩個機構的 PUA 區段有重疊的分配,勢必得搬遷其中一邊的碼位來滿足字符的顯示。
講來講去,沒有標準就是一個最大的問題。
另一方面,積極將 iPhone 引入日本市場的 Apple,也遇到了 Emoji 的編碼和顯示問題。於是,Google 與 Apple 兩科技大佬便決定聯手推動將 Emoji 添加到 Unicode 的提案(L2/09-026 提案)。雖然這中間受到了許多質疑,但 Unicode 最終還是於 2009 年的 5.2 標準中加入 114 個 Emoji,在隔年的 Unicode 6.0 中又加入了 608 個,並持續增添至今。
字符的長度與編碼
在繼續之前,我們先複習一下前面提到的 Unicode 編碼問題。一般而言,大多的顯示引擎都是採用 UTF-16 編碼,所以位於 BMP 上的字符都可以透過 2 個 Byte 表示、而位於輔助平面的字符,像是 CJK 擴充區的漢字,則需要透過代理對(surrogate pair)——一個高位代理、一個低位代理——共 4 個 Byte 表示。
而對於遵從 UCS-2 標準的 javascript 而言,會將 2 個 Byte 視為一個字符,所以我們可以透過 .length
和 .charCodeAt()
得到:
|
|

對於這種藉由代理對顯示、位於 輔助平面 的字符,我們可以透過碼位的加加減減,將代理對的碼位轉回實際的碼位:
|
|
所以對 Emoji 這種同樣位於輔助平面的字符而言,其長度應該要為 2
:
|
|
不過如果我們繼續嘗試其他的 Emoji…
|
|
奇怪?為什麼有的 Emoji 的長度不是 2
?
那是因為有許多的 Emoji 其實是透過組合字符,也就是 ccmp 的 feature 來組合的,像是旗幟、膚色、家庭成員的組成、以及一些有趣的小東西們。我們可以透過點點點語法 [... ]
把這些 Emoji 展開來看看:
|
|
膚色:菲茨派屈克修飾符
為何表情 Emoji 的膚色看起來這麼「黃」?—— 即使是最純的亞洲黃種人,膚色也不會是這麼不自然的顏色。這是因為 Emoji 在設計時, 為了讓其本身的意思相對「中立」,不會偏向白人、黃人、或是黑人,所以才挑選了這麼不自然的黃作為「表示人類」的膚色。
不過,為了讓人們可以自由地選擇膚色,在 Unicode 裡面,還是特別定義了五種用來修飾膚色的菲茨派屈克修飾符(Fitzpatrick Modifier),這是由美國皮膚科醫生 Thomas B. Fitzpatrick 於 1975 年提出的膚色色表。雖然仍有許多人批評其系統過時、刻板印象過深,但仍然因其相對簡單的定義而使用至今。

只要跟人類有關的 Emoji,或是出現過人類部位(手、腳、臉)的 Emoji,都可以透過 在 Emoji 後面 添加菲茨派屈克修飾符調整顏色,詳細的清單可以參考 Unicode 的文件。
ZWJ:零寬連字
零寬連字(Zero-width joiner, ZWJ, U+200D
),顧名思義就是寬度為零的 連字字符,可以 「強迫」的將前後兩個字符連在一起 ,用在 Emoji 上便能憑空組合出不佔用碼位的 Emoji。
以剛剛的消防員(Firefighter Emoji)來說,其實是被定義成是「人」+「消防車」(這什麼腦洞大開的組合方式),所以我們可以透過 ZWJ 連接「人 / 🧑 U+1F9D1
」與「消防車 / 🚒 U+1F6921
,得到「(不分性別的)消防員 / 🧑🚒」、或是「女性 / 👩 U+1F469
」與「消防車 / 🚒 U+1F692
」,得到「女消防員 / 👩🚒」。

同理,我們也可以將人像用來組合家庭成員,如果我們定義「父親 / 👨 U+1F468
」、「母親 / 👩 U+1F469
」、「女兒 / 👧 U+1F467
」、「兒子 / 👦 U+1F466
」的話,就可以組合出各式各樣的家庭。

國家、地區和組織
此外,因為國家、地區和組織有可能滅亡或新成立,因此旗幟區的符號其實也是透過組合字符的功能實現的,我們可以使用 Unicode 的旗幟英文區段(U+1F1E6
- U+1F1FF
)來拼寫國家、地區和組織的名稱,進而產生想要的旗幟。

feature 設定
ccmp 是一種多對一的轉換方式,也就是採用 LookupType 4 替換規則,這裡簡單的列出一些規則:
|
|
碼位的轉換
因此,想要把一個 Emoji 轉換成對應的碼位以用於儲存,其實不能很單純的使用 codeAtPoint
method 去轉換,而是要考慮各種分解的狀態:
- Emoji 的
.length
必定大於2
,需要代理對碼位的轉換。 - 對於
.length
為奇數的,必定存在至少一個.length
為 1 的 ZWJ 字符,碼位為U+200D
。 .length
大於2
的 Emoji,是由複數個 Emoji 組成,在轉換時必須遵從 FIFO 原則,依序列出。
|
|
轉回來則相對簡單,把剛剛得到的碼位一一排序輸出即可
|
|
Emoji 的輸入方法
在各個主流作業系統裡,其實都已經內建了輸入 Emoji 的方式,例如在 Windows 裡,可以透過「windows
+ .
」打開 Emoji 鍵盤、而在 macOS 裡,則可以透過「control
+ command
+ space
」啟用;置於 Android 和 iOS 移動設備端,也會有 Emoji 小鍵盤可以輸入。
不過,想必還是會有使用者不知道這些快捷鍵,至今還是透過複製貼上的方式來輸入,因此,我們想要直接放置一個 Emoji 挑選器——直觀、簡單、又方便。
Emoji-Mart
如果在 Github 上搜尋 Emoji picker 之類的關鍵字,可以找到不少的 repo,在這裡,我們以 Star 數最多、更新最為頻繁的 emoji-mart 套件 為例。
emoji-mart
除了 Picker(選擇器)的本體之外,還需要額外掛載紀錄 emoji 的 data
。除了遠端 fetch 回來的方法之外:
|
|
也可以直接把 data
的部分 bundle 起來:
|
|
Options
emoji-mart
的 repo 裡面有詳細紀錄建立一個 Picker 可以賦予的選項,其中最常設置的有以下幾個:
data
:Emoji 的資料。如上一章所示,差別就在於要聯網從 remote 抓取回來、或是直接 bundle 打包起來。onEmojiSelect
:選取 Emoji 時要觸發的動作。以今天的目標為例,就是要讓使用者在點選任一個 Emoji 時,可以傳回該 Emoji 的資料。emojiVersion
:Emoji 的版本。這個版本號與 Unicode 的版本號一致,換句話說,目前最新的 Unicode 15.1 對應的就是 Emoji 15.1 版本。比較可惜的是,emoji-mart
的版本目前停留在14.0
,也就是 2021 年九月的版本。更新版本的 Emoji 如(請確定你裝置的字型也有更新才會正確顯示) 🫨(顫抖的臉U+1FAE8
)、🫎(麋鹿U+1FACE
)、🪿(鵝U+1FABF
)、🪼(水母U+1FABC
)就不在 Picker 裡面。set
:要用哪套 Emoji。除了隨使用者裝置顯示的native
之外,可選的還有apple
、facebook
、google
以及twitter
。

至於要選擇哪一種風格集嘛~這裡提到的四種 set
可以說是四分天下,apple
和 google
風格佔據了 iOS/macOS 和 Android 的市場份額,而 facebook
和 twitter
也是許多人日常一定會使用的社群網站。使用者對他們應該都相對熟悉,所以端看網頁需求即可。
onEmojiSelect
每當使用者點選任一個 Emoji 的時候,就可以藉由 onEmojiSelect
回傳該 Emoji 的詳細資訊,我們可以印出這個資訊的 Object 看看:
|
|
在點選 🐬 之後,我們就可以得到 🐬 的資訊啦:
|
|
裡面有一些有趣的東西,像是 Emoji 本體 native
、名字 name
、代表 Unicode 唯一碼位的 unified
、可以找到該 Emoji 的關鍵字 keywords
等等。而我們真正需要的,可以是本體 native
,也可以是代表碼位的 unified
,畢竟兩者之間的關係是唯一的。
本文同步刊於 iThome。詳見DAY 20 | Emoji:可愛的小東西、DAY 24 | Emoji Mart:讓使用者挑喜歡的 Emoji。