早在遙遠的DOS時代,點陣漢字庫爲計算機處理漢字起到了關鍵作用。當時的顯示器在圖形模式下的分辨率只有640x480甚至320x200,顯示漢字直接使用點陣字庫在屏幕上打點就可以了。如今的電腦屏幕甚至手機、電視屏幕都已經進入視網膜高清屏時代,字體也早使用了矢量化技術。其實在工控機等嵌入式設備領域點陣字庫依舊用途廣泛。除此之外,前輩們苦心整理的這些HZK12、HZK16、HZK24漢字點陣字庫還有什麼用途嗎?本文我們就嘗試用twaver的3d技術來繼續發揮這些點陣字庫的餘熱。
字庫
網上可以輕鬆搜索到hzk12、hzk16、hzk24、hzk32等各規格的點陣字庫文件。以最簡單常用的漢字集合gb2312爲例,6763個漢字,對12的點陣字庫來說,只有不到200k。但是12的點陣有點太粗糙了,視覺上已經很難接受,甚至無法辨認。16的顯示效果略好,文件在260k左右。32點陣的漢字尺寸會達到幾兆,一個漢字點陣=32x32=1024個點,無論用3d還是2d來處理,量都有點大。所以這裏選擇16的字庫做例子。另外一般24的點陣字庫主要用於打印,其方向是反的,程序處理時需要注意循環方向。
現在也有軟件可以自動生成點陣的漢字庫,指定機器上的字庫和分辨率然後處理即可。如果對點陣字庫不滿意,您可以自己生成一個。對於16的點陣來說,能顯示清楚就不錯了,字體樣式美觀性就談不上了,所以重新生成也沒什麼意義。這些字庫中的文字點陣已經是優化處理成最好的顯示效果了。
要在js中處理這些點陣數據,直接用二進制文件太麻煩,最好是處理成js的格式,例如數組、json等。首先要熟悉一下字庫的結構。
hzk16二進制點陣文件包含了GB2312中定義的漢字。GB2312收錄簡化漢字及符號、字母、日文假名等共7445個圖形字符,其中漢字佔6763個。GB2312規定“對任意一個圖形字符都採用兩個字節表示,每個字節均採用七位編碼表示”,習慣上稱第一個字節爲“高字節”,第二個字節爲“低字節”。GB2312 將代碼表分爲94個區,對應第一字節;每個區94個位,對應第二字節,兩個字節的值分別爲區號值和位號值加32(2OH),因此也稱爲區位碼。01-09 區爲符號、數字區,16-87區爲漢字區,10-15區、88-94區是有待進一步標準化的空白區。GB2312將收錄的漢字分成兩級:第一級是常用漢字計3755個,置於16-55區,按漢語拼音字母/筆形順序排列;第二級漢字是次常用漢字計3008個,置於56-87區,按部首/筆畫順序排列。每個漢字由16x16個點定義,也就是每個漢字2x16=32個字節。所以,知道了一個漢字的區位碼,就能算出漢字點陣的絕對偏移位置。下面代碼顯示瞭如何從點陣字節序列中獲得一個漢字的點陣字節數據:
//漢字點陣數據在字庫文件中的偏移: //偏移 = ((區碼-1) * 94 + 位碼) * 一個點陣字模佔用的字節數 //區位碼都是從1開始,所以別忘記減1。 var offset = ((code1 -1) * 94 + (code2-1)) * 32; [/javascript] 爲了讓上面代碼能工作,就要準備一個包含點陣字庫每個字節的數組給js,便於處理。這裏用java寫了幾句代碼,轉換hzk16文件,然後另存爲一個js認識的數據文件(一個數組變量)。 [java] import java.io.*; public class Main { public static void main(String[] args) { String type = "16"; try { StringBuilder result = new StringBuilder(); result.append("var hzk" + type + "=[\n\t"); FileInputStream stream = new FileInputStream("C:/twaver/hzk" + type); int c; while ((c = stream.read()) != -1) { result.append(c + ","); } stream.close(); result.append("\n];"); DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("C:/twaver/hzk" + type + ".js"))); out.writeBytes(result.toString()); out.close(); } catch (Exception e) { e.printStackTrace(); } } }
運行上面java代碼,即可讀取c:\twaver\hzk16點陣文件並生成一個c:\twaver\hzk16.js的js文件,中間包含了每一個字節的數組:
var hzk16=[ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0//....此處省略一萬字... ];
有了點陣數據,就可以繼續上面的代碼,循環讀取一個漢字的點陣信息了。假設我們有一個區位碼爲4000的漢字要顯示:
var code=4000; var code1=parseInt(code/100); var code2=(code % 100) ; //offset=(94*(區碼-1)+(位碼-1))*32 var offset=((code1-1)*94+(code2-1))*32; var FontSize=16; for(var row=0; row<FontSize;row++){ var rowMask1=hzk16[offset+row*2]; var rowMask2=hzk16[offset+row*2+1]; var rowMask=rowMask1 << 8 | rowMask2; for(var column=0; column<FontSize; column++){ var position=FontSize-column-1; var m=Math.pow(2, position); var flag=rowMask & m; flag=flag>>position; //這個點顯示還是不顯示,這個flag說了算。繼續處理顯示... updateNode(nodes[row*FontSize+column], flag); } }
有了點陣信息,接下來就可以用twaver的強大3d場景來顯示了。
顯示
先用最簡單的:我們用一個16x16的立方體矩陣來顯示一個點陣漢字。然後把這256個立方體記下來:
function createNodes(){ var nodes=[]; for(var i=0;i<16;i++){ for(var j=0;j<16;j++){ nodes.push(createNode(box, i, j)); } } return nodes; }
接下來在沙盤上顯示漢字。通過點陣控制每一個點,有打點的位置,其立方體修改圖片、變色、高亮、位置拉高;否則保持原位不動。繼續上面程序的updateNode函數:
function updateNode(node, flag){ var height= flag ? 20 : 2; var pic=flag ? 'test.png' : 'test2.png'; node.setStyle('m.texture.image', pic); //等等其他效果變化...
爲了讓效果更生動,我們使用twaver提供的內置動畫:讓立方體慢慢變色、變高。這裏大家剛好也可以再熟悉一下twaver的動畫技術:
var animateRotate=new twaver.Animate({ from: oldHeight, to: height, //變化速度由近到遠,產生“波浪”效果 dur: (node.getClient('row')+node.getClient('column'))*80, easing: 'easeBothStrong', onUpdate: function (value) { //動畫高度、動畫顏色 node.setHeight(value); node.setPositionY(value/2-1); var percent=parseInt(255*(value-2)/(20-2)); var red=percent.toString(16); if(red.length==1){ red='0'+red; } var color='#'+red+'AA00'; node.s({ 'm.color': color, 'm.ambient': color, }); } }); //啓動動畫 animateRotate.play();
轉碼
還有一個問題,就是如何獲得一個漢字的區位碼?畢竟display([4034, 4098, 2093, 4233])這樣的代碼沒有display('賽瓦軟件')更方便。於是要研究一下js中如何直接獲得字符的區位碼。
現在的操作系統基本上都是用unicode這些國際通用格式。而unicode和gbk/gb2312這些本土國標中的字符編碼基本沒有什麼對應關係。一般操作系統都會有api可以獲得轉換,但在js裏面,這意味着我們只能自行準備一張對應表來轉換了。
其實想想也不麻煩:我們準備一個字符串,字符序列按gb2312中漢字的順序進行排列。然後給一個漢字,我們就看它在字符串中出現的位置。這個位置也就是其區位碼偏移,然後就可以算出區碼和位碼了。
根據這個思路,找到了大牛秋水無痕在2002年9月17日的一篇博文:,其中介紹了這個思路,並提供了一份文字表。在下載時需要注意,最好重新創建一個新文件並把文字表重新複製保存,避免文件本身編碼差異造成亂碼或無法轉碼的情況。
根據博文思路,根據需求重新寫了一下讀取區位碼的函數:
function getGBCodes(str){ var i,c,p,q,result=[]; for(i=0;i<str.length;i++){ if(str.charCodeAt(i)>=0x4e00){ var p=strGB.indexOf(str.charAt(i)); if(p>=0){ q=p%94; p=(p-q)/94; var code1=0xB0+p-0xA0; var code2=0xA1+q-0xA0; var code=code1*100+code2; result.push(code); } }else{ result.push(0); } } return result; }
給任意字符串,遍歷每個字符,算出其區碼和位碼,組合成4位數區位碼放入數組中返回。注意這裏簡化起見,只包含了gb2312中的常用漢字,特殊字符等均未處理。
最後,顯示個字符串得瑟一下:
var box = new mono.DataBox(); var nodes=initNodes(); var codes=getGBCodes('賽瓦軟件'); var index=0; function init() { var camera = new mono.PerspectiveCamera(30, 1.5, 10, 10000); camera.setPosition(50,200,500); var network= new mono.Network3D(box, camera, myCanvas); var interaction = new mono.DefaultInteraction(network); network.setInteractions([new mono.SelectionInteraction(network), interaction]); mono.Utils.autoAdjustNetworkBounds(network,document.documentElement,'clientWidth','clientHeight'); var pointLight = new mono.PointLight(0xFFFFFF,1); pointLight.setPosition(100,1000,-1000); box.add(pointLight); var pointLight = new mono.PointLight(0xFFFFFF,1); pointLight.setPosition(-1000,-100,0); box.add(pointLight); box.add(new mono.AmbientLight(0x888888)); setInterval(function(){ displayWord(codes[index]); index=index==codes.length ? 0 : index+1; }, 2000); }
最後看視頻效果:
其他
其實在3d中處理漢字還是比較麻煩的。漢字字符多形狀複雜,對於英文字庫來說還行,但對於漢字庫來說,如果要將其矢量信息轉到json中供3d使用則數據量太大,很難實現(做特定的若干個漢字處理倒是可行)。一般處理方法只能是將漢字渲染在圖片上進行顯示。這樣的不足是漢字結果是扁平的一張圖片,沒有模型信息和3d效果。用點陣字庫,雖然略顯粗糙,但是可以換來完全的模型化的字符信息和顯示效果,充分利用3d的各種效果。而增加的js數據量也不大(幾百k)。這也爲大家提供了一種3d中處理漢字的一種思路。
如需要本文相關代碼和資料請發郵件或論壇留言。謝謝!