支援部件檢字
如可用「口大心」或「因心」檢索「恩」字 方便查詢易拆組而用一般輸入法難輸入的字
P.S.已經做了些survey,稍後補上
昨天跟au討論時,得到了三個可能的方向
- 把中央研究院漢字部件檢字系統 拆成比較好用的格式
- 拿 single.fnt 但是座標部份我們不需要
- 有一個 GPLv3 的實作,剎那字引 ,另一個進路是跟作者連絡一下,看能不能包成比較容易用的 jquery plugin
粗略survey了一下
(1) 中央研究院漢字部件檢字系統 釋出聲明中提到:若將整個系統視為一個完整的專案,則以GPL3釋出。若將「檢字程式」與「漢字字型」視為可資區隔的二個獨立著作權客體,則「檢字程式」為GPL3,而「漢字字型」則為GFDL1.2及CC-BY-SA 2.5雙授權。
但「部件資料」的mdb檔究竟屬於「檢字程式」或「漢字字型」,亦或兩者皆非(無可用授權),看來是模糊不清的,可能需要致信中研院詢問?
雖然程式碼開放,但資料好像不能直接單獨下載,必需下載「安裝漢字構形資料庫」安裝檔EXE,再從其中取出。下載需要填些資料,不過因為是GPL&GFDL了,應該可以重新散佈沒問題。
順道筆記一下,安裝檔在MAC/Linux下可以用下列tool解開
- exe檔是self-extracted RAR,所以直接用unrar解開
- 得到CAB檔用cabextract解開
- mdb檔可用mdbtools匯出,或用ODBC讀取
(2) single.fnt 在此處找到GFDL授權的下載點 檔案是文字檔,用regular expression parse一下應該就OK了
(3) 剎那字引 其實我不確定是否必需跟作者連絡 若原程式是GPL3,是否可由我們自行改作即可? 亦或需要釐清源碼與資料的授權狀況?
他的資料都是包成RequireJS的模組,要重用應該相當方便 組件資料: http://nccu.ksana.tw/kzy/ids_char.js 反向索引: http://nccu.ksana.tw/kzy/decompose.js (key為部件,value為所有含該部件的字)
從網路上的資料看來,「剎那字引」及single.fnt都是出自「剎那工坊」這個組織 我想資料來源也會有所重疊
接下來的action items我想有:
- 定義前端呈現方式。如在原本的搜尋框輸入「大力」要直接出現「夯」嗎?或要由特殊的方式啟動組字查詢?
- 定義部件資料結構。如直接follow剎那字引的結構OK嗎?
- 準備部件資料。三個dataset的資料可能有所不同,但目前以single.fnt的資料授權最為明朗。定義好資料結構後,可以先用single.fnt的資料製作,先求有再求好。事後可以再比對與其他兩個dataset的差異
- 定義部件查詢細節。如1.部件是否有alias:可用"扌兆"查詢"挑",那"手兆"呢?剎那字引不行。2.部件展開:可用「因心」查詢「恩」,那「口大心」呢?剎那字引不行。
- 實作前端功能
同意,先求有再求好++
另外 5. 前端部份,http://timdream.org/jszhuyin/ 是 MIT license,也許可以參考。
做「5.前端部份」之前好像要先決定「1.定義前端呈現方式」?
我目前腦海的想像是,在原本的搜尋框輸入部件,而字會自動出現在候選清單中。 如原本輸入「女生」,會出現「女生、女生外嚮、神女生涯」候選 而加入部件檢字後,會變成「女生、姓、女生外嚮、神女生涯」 順序為:Exact match詞條優先、部件檢字、partial match詞條。
我自己覺得這個設計不是很好,期待更好的設計 我有空應該會先做item 2~4
好的,感謝!前端我再和 su-lip 組的朋友一起想想,也許用前綴符 =手兆 代表專門只顯示部件或拼音檢索。
我開了一個實驗repo在這邊 https://github.com/miaout17/moedict-component-testbed 一點點進度,single.fnt轉JSON (其實沒寫幾行code) https://raw.github.com/miaout17/moedict-component-testbed/master/single.fnt/comp_char.json
cc @gsklee
:+1:
14:53 < gsklee> au: yep 那如果組字的基本單位是筆畫的話 是不是有需要定個json的筆畫/基本部件描述格式?
筆畫部份,https://github.com/caasi/zh-stroke-data/tree/master/utf8 有 XML,可以直接轉 JSON (schema 請參 @caasi 的 coffee/draw.canvas.coffee 裡的用法製定)
部件部份,如果要拆 single.fnt,MGDesigner的說明如下:
02A73600| [人因] 0=亻XXXXXXXXX因YYYYYYYYY
我拆開解說,
"[人因] 0"的0",是異體字編碼,預設是0,這是因為unicode對異體字的規範不完善的解法,unicode沒有定義到的異體字,用這方式來處理。
所以 "02A73600| [人因] 0=亻XXXXXXXXX因YYYYYYYYY"裡面亻的部份可以看成這樣
亻X XX XX XX XX
第一個byte也是異體字碼,然後是這個字的子組件「亻」,要放進去的框架
再來四個bytes,是這個框架的左上角的點的位置(x,y),後四個bytes,是框架的寬度與高度(如果我記錯的話,後面4bytes就是框架右下角的點位置)。
以這個 ticket 的範圍,@miaout17 的 comp_char.json 只留部件,比較適合純前端完成。
但是也許可以多拆一個含座標的 .json,結合筆劃資訊,把教育部沒有寫出來的字補上?(這是假設部件順序就是筆劃順序的情況,還沒 verify 過。)
筆記一下今天在g0v irc看到的
組字demo: http://www.cnmc.tw/~buganini/chicomp/ 源碼: https://github.com/buganini/chicomp
另外,根據這個頁面,CNS11643全字庫的資料似乎全是CC授權 這樣的話應該有很多資料可以拿來用
bsdconv based的solution有一個缺點是目前只能轉出唯一的結果,也就是說[峰峯][群羣]只能得到一個結果,截至目前為止也沒有想到比較好的改法。 有一個可以帶來額外好處(異體字)的workaround是用bsdconv轉出一個結果後再用chvar http://www.cnmc.tw/~buganini/chvar/ https://github.com/buganini/chvar 查出相關的異體字。
順便講一下unihan和chvar的架構好了
unihan依據形音義組成異體字群 形︰貓猫 音義︰說説 //這兩個字被視為同形(abstract shape)
chvar的架構比較接近UAX#15的兩層式架構,第一層是標準等價,第二層是相容等價, [貓猫][說説][峰峯][群羣]都是標準等價 [後后]是相容等價,簡=>繁一對多的都是用這種方式處理,單字在第一層各自一個群,這些群在第二層才關連在一起 應該還可以有第三層的混淆等價如[意義][抬台]可以用來提供更高相容度,之前整理資料的時候可能有混一些到相容等價層去。
有了群組之後再定義不同scenarios下每個群的選用字,使用不同演算法可以獲得下列三種用途的轉換表︰
- 標準化(normalization)
- 代用字(transliteration)
- 模糊比對
目前有CN/TW/CP936/CP950/GB2312/GBK這些scenarios
標準化的用法是在標準等價的範圍內取用CN/TW即可得到1-gram的繁簡轉換表,再拿大詞庫轉成簡體再用這個轉回繁體,留下跟原始字串不一樣的部份,即可獲得還不錯的2+-gram的繁簡轉換表,剩下的是算是翻譯等級的問題。目前bsdconv的inter/{ZHTW,ZHCN}是由此產生。
代用字的用法是在轉換失敗發生時,在標準等價層如果有可用的CP936/CP950/GB2312/GBK選用字,即選用,如果沒有再到相容等價層去找,這樣在bsdconv即可用utf-8:cp950,cp950_trans達到transliteration,意義等同於iconv在做gb2312<=>big5發生的繁簡轉換。目前bsdconv的to/{CP950_TRANS,CP936_TRANS}是由此產生。
模糊比對就是直接往相容等價找CN或TW的選用字,根據CN/TW可以列出兩個表,搜尋結果一樣,只是視覺效果偏向簡體或繁體。[後后]都會被轉成"后"。目前bsdconv的inter/{ZH_FUZZY_TW,ZH_FUZZY_CN}是由此產生。
感謝 @buganini 提供的寶貴資訊! chvar 看來很有用,我來看看界面怎麼呈現。
@miaout17 的純前端難字部份檢索我想先用 leading = 來實做看看,等設計組有空再來做切換輸入方法的界面。
另外 single.fnt 的座標如果繼續拆出來,跟 @caasi 的 stroke composition + scalable 組合,理論上可以把難字組出一個最基本的書法形狀,先求有再求 collision avoidance,感謝大家持續填坑。m(_ _)m
single.fnt的座標資料 https://raw.github.com/miaout17/moedict-component-testbed/master/single.fnt/char_comp.json 肉眼觀察,看樣子資料應該是左上起點坐標XY、長寬
目前進度:http://caasi.github.io/zh-stroke-data/draw.plugin.html

感謝 @miaout17 @caasi ,今天和 @MIND0S 把「以常用字為部件」的 450 字都組出來了:
https://www.moedict.tw/?draw#鍌夯昇肽

檔案庫在 https://github.com/g0v/zh-stroke-data/tree/compose — 主要只有 scale-missing.ls 一支程式。
接下來需要做的:
- [ ] 組出「以常用字的部份為部件」的字 (見 missing-components.csv 及 scale-missing.ls line 27)
- [ ] 上圖可以看到「昇」的「日」和「肽」的「月」更適合用部件來組,而非整字。
@kcwu 提出一個想法,就是建一個單字->部件/位置/大小的索引,在組字時自動去找該部件最接近的出現位置,如上兩例也許會找到「昌」的「日」和「肚」的「月」,效果應會較好。
這在部件重疊時尤其有必要: https://www.moedict.tw/?draw#%E9%AD%8D%E9%AD%8E

也許 Yahoo Hack Taiwan 時(或之前)可以來做?
試著整理 scale-missing 做的事情給自己用:

看來要統統用 livescript 改寫了? :p
@caasi 除了在 IRC 上提過的 recursive parts table 外,另一個想法是在 scale-missing 做出「昇」上方的「日」時,用 order by st_area(st_intersection(上方的日, outline))/st_area(上方的日) 去找所有 outlines 裡的字,再回 char_comp 確認該字有「日」部件,如果有就使用。這樣就不用事先建 parts table 了。
(perl intersection_union.pl | psql zh 就會建 outlines table,之後的 cd sql2 那一列不用跑。)
不過,如果字型在 scale 時粗細變化太大(如「肽」左方的「月」),那也許 hausdorff_distance cutoff 還比較準。可能用最小的例子(「昇、昌、昱」三字,「魍、魅、傀」三字)先測看看再決定。
然後如果 ST_HausdorffDistance(track) 不穩定,ST_HausdorffDistance( ST_ConcaveHull(ST_COLLECT(ARRAY[outline of component strokes]), 0.99) ) 可能會比較合適,用 ST_ConcaveHull 也可以避免 bounding box 在「魍」的「鬼」部件的問題。
@caasi 如果部件先不用 Canvas+Box 畫,另做一個 UI 用原本的 Raphael SVG 畫,在「萌」裡下的「明」的那群 <path> 用 <a> 包起來,讓使用者可以點按之後連到「明」字,會不會比較容易做?(這也可以衍生成 WordFlex 這類的界面。)

看來 kTotalStrokes 與教育部的筆劃還是有點出入XD 但整體效果好超多!
合成時的 log :https://github.com/caasi/zh-stroke-data/blob/compose/scale-missing.log 還是有缺部件,來改 components.json 成新格式...!
@caasi ++ 我用 revised moe 的資料修正了筆劃數,見 https://github.com/g0v/zh-stroke-data/commit/7aff025770c09d13f249759936088dd0921e2c2e
scale-missing.ls 邏輯修好了,現在組出來幾乎都比原來的好,僅餘少數重疊(魎)和少數比例不當(夯)。
終於要來寫 tiebreak 了... 目前想到的是「各部件交叉面積最小而總合面積最大」min(intersection) max(union),如果交叉面積無法避免,也許各自往端點 scale?
另一個想法是拿教育部標準楷書的字形 outline 作參考集,scale 部件讓 hausdorff_distance 最小化。
啊,剛剛才做了筆劃數修正,結果發現重複了 revised-strokes.json 的結果 0rz
https://github.com/caasi/zh-stroke-data/tree/compose/total-strokes
最後猜筆劃那邊就像昨天你提到的,找出現最多次的筆劃數。 到是還沒把結果再反饋回去,看能不能找出更多未知的筆劃就是了。
@caasi++ 土炮變成地圖炮了!
我這邊 TW-Kai 轉 postgis 也跑完了,現在手邊沒電腦,待會回去 merge, 半夜把 tiebreak 跑完。
@caasi 用新筆劃數跑完的結果: https://github.com/g0v/zh-stroke-data/blob/compose/tiebreak-results.csv
「草」字頭手動修正成四劃了,「肉」偏旁時要改四劃,還沒有修正。
其他看來還不錯!有空可以試著組看看。
試著組好了,"make scale" 之後打開 demo.html 可以看到全部。
「禾」、「戈」、「王」、「肉」這幾個部件還是有筆劃數或筆劃序的問題。
其他大約 80% 是 OK 的,偶爾有筆劃重疊、粗細不均,但大致是可以用的。
@audreyt++!!
來 make scale 看看!
重新發明輪子(mouse hover)中:http://caasigd.org/zh-stroke-data/draw.2.0.html 並把 total-strokes 以 mod 0xFF 拆開方便稍後取用。
這個弄完來看看您提到的 SVG 要怎麼拆部件比較方便。
update: 好像少了 tiebreak-results.ls ?
好的。 http://snapsvg.io/ 似乎可以用 clip 的方式把筆順(甚至動畫)用 svg 也做出來,也許可以拖放編輯部件位置(?) 或按部件更換成其他候選部件。
不過 Canvas-based 程式庫,如 @miau715 提到的 Fabric.js 或 Paper.js http://paperjs.org/ 也都有類似的功能 (hit testing),所以不一定要換成 SVG,看哪個比較好寫。
update: Sorry, 已補上 tiebreak-results.ls 和 gen-refs.ls。
剛想到與其手動修正「王」不要從「噩」取,也許可以算出部件和原字的相似程度,太不相似的則不取。晚上再來跑看看。
不知道哪邊沒搞好,我畫出來的結果是:

另外 hit test 其實不是大問題,想找的是可以讓 event bubble 上來,又能云許我自己控制 sprite 該怎麼畫的 lib 。看了一下 Paper.js ,有自己的 Path ,可能是首選。
今晚土砲完,剩下三天來看看 Fabric.js 、Paper.js 、 three.js (想要直接靠 z 做縮放)哪邊可以比較快 port 過去好了。( 11/1 打算放空!希望可以避免過度緊張XD)
這跟我畫的結果相同,我會再想想怎麼排除錯誤/消失的部件。
11/1 放空是最好了!如果過度緊張的話,也可以完全不管比賽,專心把 oEmbed 寫完,然後聊天交朋友什麼的。 XD