当点阵字库遇到3D
早在遥远的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;
为了让上面代码能工作,就要准备一个包含点阵字库每个字节的数组给js,便于处理。这里用java写了几句代码,转换hzk16文件,然后另存为一个js认识的数据文件(一个数组变量)。
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); }
最后看视频效果:
http://v.youku.com/v_show/id_XOTMzNjMxMjM2.html
其他
其实在3d中处理汉字还是比较麻烦的。汉字字符多形状复杂,对于英文字库来说还行,但对于汉字库来说,如果要将其矢量信息转到json中供3d使用则数据量太大,很难实现(做特定的若干个汉字处理倒是可行)。一般处理方法只能是将汉字渲染在图片上进行显示。这样的不足是汉字结果是扁平的一张图片,没有模型信息和3d效果。用点阵字库,虽然略显粗糙,但是可以换来完全的模型化的字符信息和显示效果,充分利用3d的各种效果。而增加的js数据量也不大(几百k)。这也为大家提供了一种3d中处理汉字的一种思路。
如需要本文相关代码和资料请发邮件或留言。谢谢!