go語言:徹底掌握emoji

nanjingfm發表於2021-03-05

關鍵詞:emoji、unicode、utf8、go、表情

emoji就是一些意形符號。

首先,你必須能夠區分unicodeutf8字元之間的區別:

  • unicode,字符集,就是一個表格,記錄這字元和碼點(通常表示為U+0031)之間的關係
  • utf8,是unicode的編碼方案之一(還有utf16、utf32等)
  • 字元,是人類可以閱讀的符號。emoji就是一批比較特殊的符號(可以理解為圖片或者畫素點集合)

因為是一種實現,所以不同平臺實現的各不一樣。以ok表情為例,各平臺的實現如下:

image-20210305114724730

emoji-sequences.txt

Basic_Emoji

基本emoji,包含兩種型別

  • 單一unicode字元
  • 單一unicode字元後面增加*U+FE0E*或者U+FE0F分別表示以黑白文字模式還是彩色模式展示表情

emoji-variation-sequences.txt)

Emoji_Keycap_Sequence

鍵帽序列,都是0-9、#、*開頭,然後後面緊跟著U+FE0FU+20E3兩個字元組合而成。

字元長度:均是3字元

image-20210305114818842

需要注意的是蘋果輸入法打出的鍵帽序列是反的,即U+20E3在前U+FE0F在後面

func TestEmoji(t *testing.T)  {
    s := "1⃣️"
    fmt.Printf("位元組數:%d, 字元數:%d\n", len(s), len([]rune(s)))
    hexDump(s)
}

func hexDump(s string)  {
    fmt.Printf("字串:%s\n======================\n", s)
    for index, item := range []rune(s) {
        hex := strconv.FormatInt(int64(item), 16)
        fmt.Printf("序號%d:Unicode 碼點:U+%s,位元組數:%d\n", index, hex, utf8.RuneLen(item))
    }
}

// 位元組數:7, 字元數:3
// 字串:1⃣️
// ======================
// 序號0:Unicode 碼點:U+31,位元組數:1
// 序號1:Unicode 碼點:U+20e3,位元組數:3
// 序號2:Unicode 碼點:U+fe0f,位元組數:3

RGI_Emoji_Flag_Sequence

RGI(Recommended for General Interchange)表示可以在日常的交流中使用。

image-20210305114841749

如上表所示,U+1F1E6 ~ U+1F1FF,分表代表A ~ Z共計26個字元。

  • 比如中國縮寫是CN,所以對應的旗幟編碼就是U+1F1E8(C) U+1F1F3(N)
  • 比如美國縮寫是US,所以對應的旗幟編碼就是U+1F1FA(U) U+1F1F8(S)

RGI_Emoji_Tag_Sequence

這裡有三個比較特殊的地區,分別是:英格蘭蘇格蘭威爾士

英格蘭(????????????):U+1F3F4 U+E0067 U+E0062 U+E0065 U+E006E U+E0067 U+E007F

蘇格蘭(???????):U+1F3F4 U+E0067 U+E0062 U+E0073 U+E0063 U+E0074 U+E007F

威爾士(???????):U+1F3F4 U+E0067 U+E0062 U+E0077 U+E006C U+E0073 U+E007F

他們是英國的組成國,具體請參考這裡

RGI_Emoji_Modifier_Sequence

Unicode定義了5個用於emoji的膚色修飾字元,即特定的表情加上膚色修飾字元就會展示不同的顏色:

  • U+1F3FB:light skin tone
  • U+1F3FC:medium-light skin tone
  • U+1F3FD:medium skin tone
  • U+1F3FE:medium-dark skin tone
  • U+1F3FF:dark skin tone

比如?的表情其實有5種膚色:

image-20210305114900051

image-20210305114916531

image-20210305114925859

emoji-zwj-sequences.txt)

ZWJZero Width Joiner,也就是零寬度連結符號。ZWJ的unicode程式碼為U+200D,因為是沒有寬度的連結符,所以不可見,他的作用就是連結兩個字元,比如“?‍?‍?‍?”就是由U+200D連結四個字元而成:

image-20210305114949696

字串:?‍?‍?‍?,佔用位元組數:25, 字元數:7
======================
序號0:Unicode 碼點:U+1f468,位元組數:4
序號1:Unicode 碼點:U+200d,位元組數:3
序號2:Unicode 碼點:U+1f469,位元組數:4
序號3:Unicode 碼點:U+200d,位元組數:3
序號4:Unicode 碼點:U+1f467,位元組數:4
序號5:Unicode 碼點:U+200d,位元組數:3
序號6:Unicode 碼點:U+1f466,位元組數:4

當然這樣的組合並不是無限的,所有合法的組合都可以在這裡找到。

  • emoji字元非固定長度,單個字元佔用3-4個字元,所以判斷是否是emoji表情,不能簡單透過長度判斷。

image-20210305115002804

  • emoji符號可以由一個或者多個字元組合而成,比如?‍?‍?‍?是由7個字元組成,共佔用25個位元組。

go-xman/go.emoji

原理就是根據emoji兩個官方文件 Emoji SequenceEmoji ZWJ Sequence,官方已經將可以組合的emoji表情一一列舉出來了。

程式碼實現就是將所有合法的序列全部匯出成為一棵樹。當檢查字串子串的時候,匹配樹中所代表的合法的子串就可以了。

image-20210305115018241

func TestEmoji(t *testing.T)  {
    s := "?‍?‍???"
    _ = emoji.ReplaceAllEmojiFunc(s, func(emoji string) string {
        hexDump(emoji)
        return ""
    })
}

func hexDump(s string)  {
    fmt.Printf("字串:%s,佔用位元組數:%d, 字元數:%d\n======================\n", s, len(s), len([]rune(s)))
    for index, item := range []rune(s) {
        hex := strconv.FormatInt(int64(item), 16)
        fmt.Printf("序號%d:Unicode 碼點:U+%s,位元組數:%d\n", index, hex, utf8.RuneLen(item))
    }
}

=== RUN   TestEmoji
字串:?‍?‍?,佔用位元組數:18, 字元數:5
======================
序號0:Unicode 碼點:U+1f469,位元組數:4
序號1:Unicode 碼點:U+200d,位元組數:3
序號2:Unicode 碼點:U+1f469,位元組數:4
序號3:Unicode 碼點:U+200d,位元組數:3
序號4:Unicode 碼點:U+1f466,位元組數:4
字串:??,佔用位元組數:8, 字元數:2
======================
序號0:Unicode 碼點:U+1f1e8,位元組數:4
序號1:Unicode 碼點:U+1f1f3,位元組數:4
--- PASS: TestEmoji (0.00s)
PASS
本作品採用《CC 協議》,轉載必須註明作者和本文連結
您的點贊、評論和關注,是我創作的不懈動力。 學無止境,讓我們一起加油,在技術的衚衕裡越走越深!

相關文章