會探討這一篇,純粹只是有一天發現:
台灣人寫的很多簡體字,都是「台式簡體字」!不是大陸簡體字!
變體字的起源,手寫異體字

台灣有台式國語、台式英語(Desktop???)
台式泡菜、台式珍奶、還有台視新聞(…發神經,別理我)
以及各式台男台女之外
對,當然也有台式簡體字
繁體 vs 台式簡寫 vs 大陸標準
ex: 個、亇、个
ex: 點、奌、点
ex: 發、発、发

比較多是 快寫產生的書寫異體:俗字、俗體字(民間流行使用但未經官方標準化的字形)
或古文字或異體字的沿用、或者日本漢字流傳
久而久之,變成地方流行起來的,約定成俗的一個「區域性文字」
就是變體字的起源
相同的字,不同的編碼?
繁體、簡體、韓語、日語、俗寫...各種中國字的撰寫方式
在電腦編碼上又是一個大坑(電腦的文字地獄)
不過,若只是要在電腦上「呈現出文字」
這些都還只是小Case,真正的文字地獄如下!

在臺灣、香港、澳門、新加坡、馬來西亞等
這些仍在使用繁體字、漢字的地區
有極少數中文國字長的一模一樣(或者說相似),卻在Unicode有不同的編碼
明明字完全相同,在特定情形下,用Ctrl+F卻搜尋不到…
還需要做Unicode等價性(Unicode Normalization)
純HTML本身無法進行正規化,因為HTML只是「標記語言」
必須靠著前端JS或者後端程式處理編碼形式
舉個例子:⼈、人
左右兩個「人」其實是不同的字
什麼?你不相信
你說你在這個頁面上,按 Ctrl+F 兩個都可以搜尋的到?!
對,那是因為Chrome、Edge、Safari等瀏覽器幫我們做了模糊比對
如果使用IDE(Sublime、VSCode、Jetbrains…等等)會發現是搜尋不到的
因為這些撰寫程式碼的工具,多數會精準比對
但是,如果將拿這兩個字反白,右鍵 => 搜尋Google結果
會發現兩者搜尋出來的結果大相逕庭

對,就是Google搜到「不同字」去了
那是因為,兩個「人」底層的編碼本身不同
⼈(U+2F08)、人(U+4EBA)
左邊是「部首字」,右邊則是日常用字
對,「部首字」跟「日常用字」的編碼完全不同
為什麼要區分、獨立出部首字?
因為部首本身並非對應漢字
有些地方很龜毛,需要明確標示「這是部首,不是用字」
康熙字典等傳統字書需要數位化,部首獨立編碼更方便整理。詳見康熙部首

比對工具網址:http://www.mytju.com/classcode/tools/encode_utf8.asp
所謂「U+2F08、U+4EBA」就是Unicode的16進制,末四位數字就是上圖紅框處
對了,在Windows上的話
注音輸入法的話輸入 「`u2605(空白鍵)」就會出現"★"
所以輸入「`u2f08(空白鍵)」就會出現"⼈"
用注音符號直輸,稱作「Unicode輸入法」
再舉個例子:
神(U+FA19)、神(U+795E)
左邊是日文漢字、右邊是中文字
但是這種字在Wordpress或者大多文字編輯器中,會自動轉換成「神」
因為做了相容性正規化
如果要保留日文漢字,可以在複製到IDE中貼上
兼容字 vs 正字 vs 拆分字
還有Emoji😀、符號方塊✅等字元
就能知道電腦在做相容性、多國語系、字形字體上,有多麼痛苦了吧!
字體家族 (Font family)
順帶一提,其實同一個Emoji😀表情符號在
不同手機、不同平台,甚至是不同年份的版本型號
笑臉呈現出來的圖案可能都不同,因為視作是不同的字體家族 (Font family)


所以哪一天『你看到的是 😀,我看到的可能是 😊,他看到的可能是 😐』
在極端的情況下,這件事情是有可能發生的
不僅字體家族五花八門、文字效果


還有字體的粗度(細體字、粗體字)、寬度(影響字的間隔)
大多數字體的A-Z a-z 0-9 寬度不一定相同
有些講究眼球視覺、螢幕排版的軟體工程師,就會使用等寬字體MonoFont(monospaced typefaces) ex: consolas、liga sans

涉及筆畫呈現、字型渲染、字體設計
這一部分再追根究柢,就會變成「螢幕像素點學問」了!
可玩看看「FontForge字型編輯器」,打造一套屬於自己的字體

文字的方向性
除了呈現出字型、能夠搜尋之外,文字還有分方向喔!
噢,此處不是指這種直式、橫式的書寫方式
因為這並非由Unicode 碼點層(字符本身)控管
而是交由CSS或者排版層的控制,做文字呈現樣式(writing mode)


以上都屬於CSS可控制的方向語法
Unicode控制字符:LTR、RTL、BOM
然而,此處指的文字方向(由Unicode 碼點層管理的)為
書寫方向屬性(Bidirectional Class):左到右、右到左
LTR(Left to Right)左到右語言:
英文、中文、日文、德文、俄文…等等
RTL(Right to Left)右到左語言:
阿拉伯文、希伯來文(以色列的語言)…等等
然而面臨LTR、RTL「混用」的時候,為了避免順序錯亂,就需要以下控制符號:
名稱 | 碼點 | 縮寫 | 用途說明 |
---|---|---|---|
左到右嵌入(Left-to-Right Embedding) | U+202A | LRE | 讓後續區塊強制 LTR 顯示 |
右到左嵌入(Right-to-Left Embedding) | U+202B | RLE | 讓後續區塊強制 RTL 顯示 |
左到右覆蓋(Override) | U+202D | LRO | 讓所有文字按 LTR 解釋 |
右到左覆蓋(Override) | U+202E | RLO | 強制從右邊開始排整段 |
嵌入結束符(Pop Directional Formatting) | U+202C | 結束前面開啟的方向嵌入 | |
左到右標記(Mark) | U+200E | LRM | 插入一個不可見但具有 LTR 方向的小空格 |
右到左標記(Mark) | U+200F | RLM | 插入 RTL 空格 |
雙向隔離開始/結束(FSI/PDI) | U+2068/U+2069 | FSI/PDI | HTML5 用來解決嵌入段落中混亂的情境 |
這些方向控制字元有些是「看不見」但能「感受到」(排版異常、游標亂跳、文字順序怪異)
例如可以玩玩看:
RLO/PDF(U+202E/U+202C):
複製這行文字 => 在這段文字後面打的字會反過來:123
複製這行文字 => 在這段文字後面打的字會反過來:123結束反轉
LRM/RLM(U+200E/U+200F):
複製這行文字 => 文字游標移動時會發現多一個空白
FSI/PDI(U+2068/U+2069):
複製這行文字 => 文字游標移動時會發現多一個空白

(在Jetbrians IDE上呈現的樣子)
RLO其實是資安攻擊技巧
你只要複製此行:evilfdp.exe
修改Windows的一個附檔名,便能製造以下的效果
明明本質上仍是.exe檔案,卻「看」起來像是.pdf

BOM(Byte Order Mark,位元順序標記)
BOM (byte-order mark) 是用來標示Unicode的位元組順序
通常會出現在文件或檔案的第一個字元,以告訴你接下來編碼是什麼、解讀的方向
標記「位元序是大端/小端」
Big Endian: 大端序(大尾序),重要的數寫(高位)寫在前(記憶體位址低位)
Little Endian: 小端序(小尾序),重要的數寫(高位)寫在後(記憶體位址高位)

編碼類型 | BOM Bytes(16進位) | 說明 |
---|---|---|
UTF-8 | EF BB BF | 可選 UTF-8 只有一個單位,不存在位元組順序問題 |
UTF-16 BE | FE FF | 大端序 |
UTF-16 LE | FF FE | 小端序 |
UTF-32 BE | 00 00 FE FF | 大端序 |
UTF-32 LE | FF FE 00 00 | 小端序 |
BOM與LTR、RTL問題本質上相同,都涉及到判讀的左右方向
但是處理的層級不同
前者是「位元組序(byte order)」,後者是「文字顯示方向(glyph flow)」
萬國碼感知(Unicode-aware)做的事情包山包海
包含了:特殊控制碼、方向控制、正規化合字
夠令人頭疼了吧!
UTF-8、UTF-16、UTF-32
編碼形式 | 每個字元所用位元組數 | 說明 |
---|---|---|
UTF-8 | 1~4 bytes | 英文字為 1 byte 中文字多為 3 bytes 最省空間,廣泛使用 |
UTF-16 | 2 or 4 bytes | 多數字元用 2 byte 少數補充字元用 4 byte |
UTF-32 | 4 bytes | 每個字元 4 byte,格式簡單粗暴,但最浪費空間 |
UTF-8 編碼範圍
UTF8(Unicode 範圍) | 位元組數 | 說明 |
---|---|---|
U+0000 ~ U+007F | 1 byte | 基本 ASCII(英文、數字) |
U+0080 ~ U+07FF | 2 bytes | 拉丁文、希臘文 |
U+0800 ~ U+FFFF | 3 bytes | 中文、日文、韓文、常用符號 |
U+10000 ~ U+10FFFF | 4 bytes | emoji、冷僻漢字、歷史字母 |
之前還遇過一個專案裡面,程式碼註解區域非常多的BS
控制字元
原來是倒退鍵Backspace 控制字元 (U+0008
),
在VSCode內建不會顯示、肉眼上完全看不出來(用Jetbrains IDE會顯示)
只是,這個字符在多數情況下是打不出來的(控制字元通常不會顯示!)
因為按「退格鍵」時不會插入 U+0008,而是刪除字元
會發生問題的原因在於,直接從Word或PDF文件上複製文字時,保留了控制字元

在瀏覽器或IDE直接輸入BS(U+0008
)字元,是不會顯示出來、也不會有倒退效果的
但如果在終端機(Terminal)、CMD上試試看,可以成功倒退!
字元 | 名稱 | Unicode | 常見問題 |
---|---|---|---|
\x00 | NUL | U+0000 | 某些 JSON 解析器會直接炸掉 |
\x08 | BACKSPACE | U+0008 | 干擾搜尋、看不見、比對錯 |
\x0B | VERTICAL TAB | U+000B | 讓部分編輯器排版錯亂 |
\x1B | ESCAPE | U+001B | 常殘留於終端 ANSI 控制序列中 |
搜尋正規化(Unicode normalization)
搜尋時的正規化還分成以下四種:
NFC、NFD、NFKC、NFKD
這些的「個別代號」是:
C:Composition(合成)
D:Decomposition(分解)
K:Compatibility(兼容)
所謂 C「合成」就是
把可以組合的字元合併成一個預組合字元(單一碼位)
ex: e (U+0065) + ́ (U+0301) → é (U+00E9)
ex: D
+ ̇
(dot above) → Ḋ
(U+1E0A)
所謂 D「分解」就是
是把一個預組合字元拆成基本字元 + 裝飾符(多個碼位)
ex: é
(U+00E9) → e
(U+0065) + ́
(U+0301)
ex: Ḋ
(U+1E0A) → D
(U+0044) + ̇
(U+0307)
所謂 K「兼容」就是
只處裡「外觀不同但意思完全一樣」的字元:
ex: 「①」可以當作「1」
ex: 「㎏」可以當作「kg」
ex: 「𝒜𝒷𝒸」,可以當作「 Abc」
ex: 「Ⅳ」,可以當作「IV」
ex: 「ABC123」可以當作「ABC123」
但是
不會讓「IV」變成「4」,因為IV可能代表別的意思,不一定是數字4
不會讓「é」變成「e」,因為é有重音
不會讓「ñ」變成「n」,因為ñ有鼻音
(但我心裡吐槽,這種做法永遠存在「模糊邊界」,因為是人為決定的語意判斷)
而C/D「合成/分解」就是
不論Input、不管輸入的字串為何,只要發現能合成的就合成/能拆的就拆
NFC:合併字元,保留語意
NFD:拆開字元,保留語意
NFKC:合併字元,並簡化格式(兼容語意)
NFKD:拆開字元,並簡化格式(兼容語意)
ex: 如果有 ㄅ̄𝟙
與 é ℌ ②
兩筆資料,以下是四種不同的處理結果:
模式 | 結果 (注音符號) | 結果 (國際字元) | 做了什麼? | 適合用途 |
---|---|---|---|---|
NFC | ㄅ̄𝟙 | é ℌ ② | 保留字元結構 保留樣式 (最高保真) | 儲存、顯示 保留樣式 |
NFD | ㄅ + ˉ + 𝟙 | e + ´ + ℌ + ② | 拆開字元結構 保留樣式 (保留花俏) | 文字分析 拼音處理 |
NFKC | ㄅ̄1 | é H 2 | 保留字元結構 簡化樣式 (去掉花俏) | 比對前處理 輸入正規化 |
NFKD | ㄅ + ˉ + 1 | e + ´ + H + 2 | 拆開字元結構 簡化樣式 (最乾淨) | 搜尋引擎 資料比對 轉純文字化 |

模糊搜尋的種類(Fuzzy Search, Semantic Matching)
Café、Cafe、cafe 三者明明是不同的字
但是在瀏覽器內搜尋”Cafe”,三者都能夠尋找的到
這是因為瀏覽器使用了模糊搜尋
為了回答「模糊搜尋」到底可以分成哪幾種?這類問題
我整理了至今為止提出的疑惑,最終分類成這六大類型
大類型 | 說明 | 子分類/代表例子 |
---|---|---|
A. 字形與格式變化 (外觀模糊) | 同符號不同樣式 1. 字母大小寫 2. 全形/半形 3. 風格字符、符號樣式 | 1. Apple vs apple 、Δ vs δ2. ABC vs ABC 3. ℌ vs H vs 𝕳、① vs 1 vs 𝟙 |
B. 語音與書寫變體 (語音書寫模糊) | 同音不同字體系 1. 同音異字 2. 注音簡寫、羅馬拼音 3. 簡體/繁體/漢字 4. 片假/平假 | 1. Café vs Cafe 2. ㄅ̄ → ㄅ、ㄅㄨˋ→ bu4、 zhong guo → 中國 3. 花 vs 華 、绘 vs 会 、干 vs 幹、神 vs 神4. ひ vs ヒ |
C. 編碼與文字標準差異(技術層模糊) | 同樣語意但使用不同Unicode編碼 1. Unicode 表示不同、組合字與合字 2. 異體字(獨立的部首字) | 1. é vs e + ´ 、Å vs A + ° 2. ⼈ vs 人 |
D. 語形與語法變化 (語法模糊) | 語法層級的形式差異 1. 單複數 2. 時態 3. 詞性變化 4. 變格 5. 語序 | 1. car vs cars、mouse vs mice 2. run vs ran 、eat vs ate3. beauty vs beautiful、decision vs decide 4. they vs them5. 我愛你 vs 你我愛 |
E. 拼寫與輸入誤差 (人為錯誤模糊) | 拼寫錯誤 拼音錯字 鍵盤誤觸 | gooogle vs google ariplane vs airplane zhongguo vs zhong guo |
F. 語意近似 (語意模糊) | 同義詞(替代詞) 語意擴展 詞組顛倒但意圖相同 | car vs automobile virus vs pathogen University of Tokyo vs Tokyo University USA vs United States vs US vs America |
很明顯
大多數的電腦系統、瀏覽器,只支援到 A、B、C 這三類字面模糊
後面三者(D、E、F)則需要仰賴更強的語言處理系統、或者使用神經網路模型
ex: 拼寫容錯 => Levenshtein距離、Edit distance、Soundex(拼音容錯)
ex: 同義/語意模糊 => 人工同義詞詞庫(WordNet)、詞向量(Word2Vec、GloVe、FastText)
ex: 上下文語意 => BERT、GPT、文心一言 等(Transformer架構)
手機輸入法、Google搜尋都有做到 D、E 的支援,幫助打字糾錯或猜你想打什麼
而語音辨識(Speech Recognition)又是另外一篇大主題了
結尾
語言學(意義上)講究的是:語素、詞素(morpheme)
編碼層級(Unicode)探討的是:碼點(code point)
視覺實務(人眼感知)研究的是:像素字體叢集(grapheme cluster)
終極哲學問題:
到底如何定義"一個字"呢?這真的是能夠被量化定義的嗎?
我的「字」與你的「字」,看起來一樣、寫起來一樣、讀起來也一樣
但實際上,真的一樣嗎?

一旦「講出來」
語言就從內在的「感受」轉化為外在的「表演」,它已經不是原本的那個「字」了
語言不是事物本體(essence),而是事物的界面(interface)
發佈留言