使用 CNS 11643 全字庫字形屬性資料輔助修改倉頡字根定義

CNS 11643 中文標準交換碼又稱全字庫, 是台灣政府訂定的中文字集,其有自己的字型與編碼, 編碼也與後來發展的 unicode 不同。 全字庫有釋出字形屬性資料,也就是一個漢字由哪些部件組成的資料。 在修改倉頡字根時,可以據此找出哪些字含有哪些部件, 如找出所有帶有 的中文字,並修改其編碼。

重定義倉頡字根

倉頡中的 是拆成 尸一尸戈一 。 如果是完整的拆解,應該是 尸戈一尸戈一 , 但因為字根省略規則,字首只取首尾二碼,字身則全取, 所以變成 尸一尸戈一

在倉頡中某些二點的字形可以拆成 , 例如 可以拆成 卜十 , 但看來倉頡三代中 的二點不在這個範圍內。

由於省略規則,羽在擔任字首、次字首、第三個字身等時候, 會被拆成 尸一 尸一一 等我覺得很奇怪的拆法, 對此我很不滿;我認為應該要拆成 尸卜 才對。

自定義輸入法對照表

某些輸入法允許使用者自行定義編碼, 可以讓使用者自己客制化輸入哪些字根會輸出哪些字。 如果用倉頡這種拆字輸入法,就可以自己修改哪些字根可以出哪些字。 例如我可以把 木尸一一 改成 木尸卜卜 ; 或是直接依據其它東西,像把 定義成 金日廿 , 因為這三個字剛好是 cat 的對應按鍵。

相對於注音,不知道為什麼很少這樣做。 可能因為注音的重碼率太高,多半得搭配智慧選字, 所以輸入法引擎會對注音輸入做很多黑魔法, 不是簡單一張字根對照表能解決的。 拆字的輸入法都是直接出字,少數情況才要選字。

關於表格的格式,很久以前台灣的輸入法界還很繁榮的時候, 弄出了一個副檔名 cin 的格式,讓各輸入法引擎能匯入各種輸入法。 我知道至少 open vanilla、奇摩輸入法、gcin 都是這個格式。 雖然各引撆可能會有自定義的選項。 這種格式的來源,可能是中文 linux 發行版 CLE 中的 xcin 輸入法。 後來的 open vanillaximjscin, 都有 cin 格式的說明文件。

網路上可以找到一些輸入法的 cin 表格, 我自己也有做一份 supcj.cin 供個人使用。 格式就是用 % 定義特殊指令, 字根與字的對應則是用空格分隔,一行一組。

以 gcin 來說,就是用 gcin2tab, 把 cin 檔案轉成 gcin 使用的 gtab 檔, 然後丟到 /usr/share/gcin/table/ , 再在 gtab.list 裡加入檔名就可以了。 所以如果想加字,例如讓 cat 對應成貓, 就在 supcj.cin 中加入這行: cat 貓 ,再重新產生表格即可。

在 windows xp 也有一個工具叫通用輸入法編輯工具, 可以讓使用者以文字格式匯入表格,然後產生輸入法。 不過在 windows 8 後架構改變, 這個工具產生的輸入法在一些新版程式不能動,也不再內建這個工具。所以很多人從 xp 把輸入法編輯工具的 exe 檔複製過去。

所以,當時較通用格式有二種, windows 的通用輸入法編輯工具的格式,和 cin 的格式。 不過後來不在這個時空背景下的輸入法, 也就是 2000 年左右的台灣,就會定義自己的格式, 例如中國開發的 scim,非中文國家的 ibus, 或是完全生在新時代的 pime。

不過說是重新定義,反正也是大同小異, 像 scim 就是改用 tab 來分隔字根和字, 用等號定義參數,註解要用三個井號。 以會寫程式的來說,要修改都不是什麼難事, pime 雖然改用 json 來當表格, 但也提供了一個轉換程式把 cin 轉換過去。

人力找出含有特定字根的字

要重定義羽和弱的編碼,首先就要找出所有帶該字形的字, 但單純從倉頡的對應來看其實不太準。 如第一節所述,因為倉頡的省略規則, 同一個部件羽,可能是尸一、尸一一、尸一尸戈一, 甚至如果羽是最後一個字身,又有超過三個字首,那羽就會被編為 。 同樣的,如果編碼中含有尸一,也不一定代表該字含有羽, 例如 這個字也可以拆成 尸一

一開始我想那就找出所有帶有尸一的字再人力過濾, 反正應該不會太多。 結果蠻多的,如果把罕用字都算進來的話大約有 1000 個字。 後來去掉 unicode cjk 擴展平面 A B C D, 就大概少了一半,但還是有大約一半是誤判不含羽的字。 自己肉眼判斷,但後來發現誤判率超高, 我挑剩字裡大概還有一成是漏挑的。

後來想想這樣不行,何況還有漏挑的; 剛好看到 gcin 論壇 有人分享了 cns 中文標準交換碼的開放政府資料 , 含有每個字由哪些部件組成的資料, 於是就拿來用了。

使用全字庫漢字部件屬性資料篩選字根

全字庫的開放資料我是在上面 論壇裡提供的 data.gov 連結 下載的。 下載 zip 解壓縮後,會用到的有 MapingTable/Unicode Properties 二個資料夾。

例如我們要找羽這個部件,首先因為全字庫有自己的編碼, 要找出羽的全字庫編碼。 想偷懶可以去 全字庫網站查 ; 不然就是 echo -n 羽 | iconv -t UTF-32BE | xxd -p , 然後用 unicode 碼在 MapingTable/CNS2UNICODE_Unicode BMP.txt 裡查。 例如羽 unicode 是 7FBD, 就 grep 7FBD MapingTables/Unicode/CNS2UNICODE_Unicode* , 可以查到羽的全字庫編碼是 1-4851

MapingTables/Unicode/CNS2UNICODE_Unicode* 的格式即是用 tab 分隔,第一欄是全字庫編碼, 第二欄是 unicode 編碼。 其中 BMP 是對應到 unicode 的基本平面,2 15 則是第 2 和 15。

然後用 1-4851 去 Properties/CNS_component.txt 裡查: grep 1-4851 Properties/CNS_component.txt , 可以找到羽是由 415 這個單獨部件組成的; 所以可以判斷羽在全字庫中是視為一個部件存在。 接著就找出所有帶用 415 這個部件的字: awk '($2 ~ 415) {print "^" $1}' Properties/CNS_component.txt > 415.txt , 然後把查出來的全字庫編碼對應回 unicode: grep -f 415.txt 'MappingTables/Unicode/CNS2UNICODE_Unicode BMP.txt' > u.txt , 最後再把 unicode 編碼轉回 utf8: awk '{print $2, "000a"}' u.txt | xxd -p -r | iconv -f UTF-16BE

Properties/CNS_component.txt 的格式一樣是 tab 分隔, 第一欄全字庫編碼,第二欄是用逗號分隔的部件列表, 部件是用十進位數字表示。 想知道哪個數字是哪個部件, 可以在 Properties/CNS_component_word.zip 裡的圖片看; 在 zip 裡放 zip,政府單位不意外。 或是也可以直接看那個字長什麼樣子去判斷, 或找只含有那個部件的字。

如果要找 2 15 平面的話,因為前面的指令, 是利用 utf 16 如果一個字可以用 2 byte 表示,就可以直接用 2 byte 表示。 所以才能直接轉成二進位再轉到 utf 16。 另外要指定 be 是要讓位元組順序不要反過來,不然 7FBD 會變成 BD7F。 如果 2 15 平面則會超過 2 byte,可以改成用 utf 32, 但要在前面補 0 補到八個 16 進位數字,也就是 4 byte。 2 15 平面的字幾乎用不到,而且量還不少,所以我就沒有處理。 如果要處理的話指令如下:

awk '($2 ~ 415) {print "^" $1}' Properties/CNS_component.txt  |
    grep -h -f - MapingTables/Unicode/CNS2UNICODE_Unicode\ [12]* |
    awk '{printf "000%s0000000a", $2}' |
    xxd -p -r | iconv -f UTF-32BE

這樣找出來的字都可以確定有羽這個部件,也可以確定沒有漏。 關於弱就少很多,就算把 2 15 都算進去,也不到 200 個。

修改倉頡編碼

再來就是要把編碼修正。 由於字首字身的省略規則不一樣,所以只能一個個看。 我認為比較快的做法是,挑出所有的 字根, 然後標記其中不需要被取代為 的,其它就全部取代。 我的做法是用 vi 搜尋 字根對應的 m 字母, 如果 m 不是屬於該字中的羽的一部份, 就用 vi 的 r. 指令把 m 取代為 z (倉頡中幾乎用不到 z), 然後按 n 搜尋下一個 m, 下一個開始就可以把 r. 簡化成 . ,直接重複上一個動作。 最後再用 sed 's/m/y/g; s/z/m/g' 把所有 改成 z 改回