终于在gvim中把 isi_ansi 字体摆平了!! 从此以后,gvim中我的 guifont选项取值就是"zrf_ansi14:h14:cANSI", zrf是我的大名的缩写,以此纪念迄今为止花费我最长时间的一个难题.
对于编辑程序文本,一个等宽字体是必备的,位图的即可,因为不需要大小变来变去的字体,毕竟是文本文件(Emacs有灵活的字体设置能力,可以编辑文本时显示不同大小的字体),国外的程序员已经提供了很多这样的字体下载,可是,首先萝卜白菜各有所爱,别人喜欢的不一定是你所欣赏的,另外,作为中国人,文本中不免会出现汉字,这些字体能否与汉字和平共处?第一个问题我不需要下载他们的字体了,我已经有了我的最爱:isi_ansi,netterm中带的一个等宽位图字体,但这个字体恰恰不能与中文和平共处,具体问题是这样的:在一个程序中选择这种字体来显示,绝大多数的中文汉字可以正确显示,而且还相当美观,但对于汉字的两个双引号,却错误地显示为一个反引号和一个框框,有时候我从别的程序COPY带有这种汉字引号的文本到gvim中时,非常丑陋,尤其是那种显示两个人一来一往对话的文本,双引号频频出现。这使得vim这样的编辑器作为一个中文文本的阅读器反而不够格了,这是个小问题,但也是个真正的难题,困扰了我7年之久。对字体的工作原理我也不了解,7年之间我也曾多次尝试着google,尝试用字体编辑软件能否定制出一个满足我上述需要的字体:即只对ASCII(我的意思是真正的ASCII,最高位为0的, 不是那种OEM的对128-255之间字符有各种不标准的扩展的那种)使用isi_ansi,对其它的字符则使用宋体。
isifont1.dll 的格式终于搞清楚了, 原来只是一个16位的NE格式DLL, 其中只有FontDir和 Font两项资源. 系统Fonts目录下的一些.fon文件, 实质上也是这个格式.搞清楚这个其实只用了 TotalCommander的ALT-F3提供的插件功能.原来尝试过用unix的fileisifont1.dll,只说是一个MS-DOS的MZ文件或OS/2文件,或Windows可执行文件。
但还没有办法把其中的点阵取出来, 通过fontforge替换掉宋体字中的对应点阵的字母数字.
熟悉fontforge的操作需要掌握相关的字体设计方面的术语,在北大中文论坛有人把fontforge整个打包,省去了在windows下的繁琐安装,COPY过来直接就可以用,fontforge是可以用了,但我仍然迷失在令人目眩的菜单中。
MS 的sbit 据说可以提取其中的内嵌点阵,但这个“其中”指的是TTF文件,只能是TTF文件,对于FON字体文件格式它无能为力。这个工具在北大中文论坛可以下载,一个很小的文件。但对我没用。
继续以不同的关键字在Google里搜索,鉴于昨天已经找到一篇文章:
介绍了windows中的资源文件的格式,由于字体是资源的一种,所以也附带地把字体资源的结构解释了,给出了FNT文件头格式。正是通过对这个FNT文件头格式的解读和修改,最终解了我7年之痒的isi_ansi字体问题。这篇文章中给出了下面的结构定义:
struct FontDirEntry {
WORD dfVersion;
DWORD dfSize;
char dfCopyright[60];
WORD dfType;
WORD dfPoints;
WORD dfVertRes;
WORD dfHorizRes;
WORD dfAscent;
WORD dfInternalLeading;
WORD dfExternalLeading;
BYTE dfItalic;
BYTE dfUnderline;
BYTE dfStrikeOut;
WORD dfWeight;
BYTE dfCharSet;
WORD dfPixWidth;
WORD dfPixHeight;
BYTE dfPitchAndFamily;
WORD dfAvgWidth;
WORD dfMaxWidth;
BYTE dfFirstChar;
BYTE dfLastChar;
BYTE dfDefaultChar;
BYTE dfBreakChar;
WORD dfWidthBytes;
DWORD dfDevice;
DWORD dfFace;
DWORD dfReserved;
char szDeviceName[];
char szFaceName[];
};
|
但在没有任何注释的情况下,我没有信心根据这个就能更改FNT文件,以其中出现的字段名作关键字搜索,"fnt FontDirEntry",好险,google只给出了6项结果,可贵的是,这6项里面给出了最终极为有用的一个fontforge的源文件:
该文件中定义了与上面等同的结构,并且对很多字段有详细的注释。
/* Windows FNT header. A FON file may contain several FNTs */ struct fntheader { uint16 version; /* Either 0x200 or 0x300 */ uint32 filesize; char copyright[60+1]; uint16 type; #define FNT_TYPE_VECTOR 0x0001 /* If set a vector FNT, else raster (we only parse rasters) */ /* not used, mbz 0x0002 */ #define FNT_TYPE_MEMORY 0x0004 /* If set font is in ROM */ /* not used, mbz 0x0078 */ #define FNT_TYPE_DEVICE 0x0080 /* If set font is "realized by a device" whatever that means */ /* reserved for device 0xff00 */ uint16 pointsize; /* design pointsize */ uint16 vertres; /* Vertical resolution of font */ uint16 hortres; /* Horizontal resolution of font */ uint16 ascent; uint16 internal_leading; uint16 external_leading; uint8 italic; /* set to 1 for italic fonts */ uint8 underline; /* set to 1 for underlined fonts */ uint8 strikeout; /* set to 1 for struckout fonts */ uint16 weight; /* 1-1000 windows weight value */ uint8 charset; /* ??? */ uint16 width; /* non-0 => fixed width font, width of all chars */ uint16 height; /* height of font bounding box */ uint8 pitchfamily; #define FNT_PITCH_VARIABLE 0x01 /* Variable width font */ #define FNT_FAMILY_MASK 0xf0 #define FNT_FAMILY_DONTCARE 0x00 #define FNT_FAMILY_SERIF 0x10 #define FNT_FAMILY_SANSSERIF 0x20 #define FNT_FAMILY_FIXED 0x30 #define FNT_FAMILY_SCRIPT 0x40 #define FNT_FAMILY_DECORATIVE 0x50 uint16 avgwidth; /* Width of "X" */ uint16 maxwidth; uint8 firstchar; uint8 lastchar; uint8 defchar; /* ?-firstchar */ uint8 breakchar; /* 32-firstchar */ uint16 widthbytes; /* Number of bytes in a row */ uint32 deviceoffset; /* set to 0 */ uint32 faceoffset; /* Offset from start of file to face name (C string) */ uint32 bitspointer; /* set to 0 */ uint32 bitsoffset; /* Offset from start of file to start of bitmap info */ uint8 mbz1; /* These fields are not present in 2.0 and are not meaningful in 3.0 */ /* they are there for future expansion */ uint32 flags; #define FNT_FLAGS_FIXED 0x0001 #define FNT_FLAGS_PROPORTIONAL 0x0002 #define FNT_FLAGS_ABCFIXED 0x0004 #define FNT_FLAGS_ABCPROP 0x0008 #define FNT_FLAGS_1COLOR 0x0010 #define FNT_FLAGS_16COLOR 0x0020 #define FNT_FLAGS_256COLOR 0x0040 #define FNT_FLAGS_RGBCOLOR 0x0080 uint16 aspace; uint16 bspace; uint16 cspace; uint32 coloroffset; /* Offset to color table */ uint8 mbz2[16]; /* Freetype says 4. Online docs say 16 & earlier versions were wrong... */ #if 0 /* Font data */ union { /* Width below is width of bitmap, and advance width */ /* so no chars can extend before 0 or after advance */ struct v2chars { uint16 width; uint16 offset; } v2; struct v3chars { uint16 width; uint32 offset; } v3; } chartable[/*lastchar-firstchar+2*/258]; /* facename */ /* devicename */ /* bitmaps */ /* Each character is stored in column-major order with one byte for each row */ /* then the second byte for each row, ... */ #endif };
|
在VS.NET 2003中可以直接打开 isifont1.dll文件,并且可以显示出FONTDIR和FONT两个目录项,双击查看其内容,可以发现嵌入在二进制数据中的版权信息,的确是用来定义字体的资源,但不能直接在这上面作修改,修改之后再保存时会有问题,说它不能转换为PE格式,毕竟这是NE格式文件。
尝试在.NET的编辑器中CTRL-A全选然后COPY到UltraEdit中,但这个COPY对二进制数据没有处理好,COPY过去后的内容跟原来内容不符,ASCII值为0的数据都丢了,没想到在字体目录项上单击后有个Export菜单,可以把内容一字不差地另存出来。
这个另存出来的文件就是FNT文件格式了,可以对上面定义的FNT文件头对它进行解读,用UltraEdit打开之后对照着上面给出的结构定义,可以精准地解读这个二进制的文件了,这一点给了我很大的希望,其中两个数据域引起了我的注意:
开始字符,结束字符
查看这两个项,发现开始字符总是0x20, 而结束字符是0xFF, 也就是说,这个 点阵字体定义了从空格到ASCII为255的这些个字符,那么当我选择这种字体时 ,汉字的双字节引号被错误地显示为一个方框和反向单引号很可能就是因为这 个字体中对128-255之间的字符也进行了定义,猜测归猜测,真正证实的时候 我发现不是那么直白的事,用FontExplorer 对照isi_ansi字体中每个ASCII对 应的Glphy, 然后用windows自带的字符映射表找出汉字左双引号对应的 Unicode的Code Point, 是0x201C, 这不是计算机的内码,所以直接用它解读 还不行, 用 echo “|xxd -g1 可以看到它在CP936下的编码为0xa1 0xb0,可 是不论是isi_ansi中的0xa1还是0xb0都跟它错误显示的字符形状对不上号。至 于windows在内部如何生成一个字符在指定字体中对应的点击图,目前我还是 完全不知情状态。但是,仍然有很大的可能性windows把汉字的这个左引号映 射为128-255的 isi_ansi的某个字符上,毕竟这个字体对这个范围的字符有 定义,而且这不是一个多字节语言字符集,所以windows完全有理由这么干。 这并不是操作系统的什么错误。我所要做的只是取道别处,绕行通过。
大胆假设:把结束字符设为7F,那么所有的汉字都是最高位置位的,windows将会用其它字体来生成该字符的位图,对中文操作系统而言,就是宋体。
小心求证:把通过 .NET 2003 export出来的文件另存为 .fon文件,找到对应的这个“结束字符”域,改为7F, 保存,双击这个.fon文件,windows可以正确显示这个字体,但字体名字仍然是原来的。
大壮贼胆之后,我设想把字体的名字也改过来,文件头有一个font face的偏移,偏移处就是 isi_ansi这样的字串,我把它改成zrf_ansi, 同时把坏事做到底,我把版权信息也改成了2007-End of World,ZhaoZhongCun. 整个文件中额外地包括了128-255之间的字符的位图,这一段数据是不会被字体生成器用到了,但是为了避免把它们删除后调整文件的其它部分就把它留在那好了,毕竟这只是个10K左右的文件。
迫不及待COPY到 %WINDOWS%\Fonts 目录,系统显示“安装字体”的对话框了,有戏,窃喜,在gvim中选择我的字体,确定按钮之后,窗口一阵痛苦的扭曲,似乎僵死了,按了一下CTRL-L,一切正常!刚才的扭曲可能是因为系统第一次使用这个字体资源要做一些初始化,我用来做测试显示的几个中文标点符号全部显示正确,只是字体太小了点,我这才发现刚才做的那个只是10pt的,最合用的是14pt的那个,继续从.NET中把10, 12, 14, 18pt的都给挖出来。
不过对我这种蛮暴的做法会产生4个fnt文件,在copy到系统字体目录时需要手工改成fon文件,windows根据内容可以识别出来。这4个文件不能都叫同一个zrf_ansi, 这样windows在安装字体时会提示说这个字体已安装。这个问题简单,把字体名改成zrf_ansi10, zrf_ansi12即可。
另外,通过TotalCommander把.fon文件用鼠标直接拖到%WINDOWS%\Fonts目录时,并不会被系统截获,这是真正意义上的COPY文件,只有通过CTRL-C,然后在%WINDOWS%\Fonts再CTRL-V时系统才会截获你不仅仅是要COPY文件,而且是想安装字体。这样一来在程序中才可以选择这些字体。
WORD中的字体选择下拉框中只显示TTF字体,不过我也不需要在WORD里使用这个点阵字体。
如果在givm中选择宋体,那么无论字号如何选择,出来的宋体汉字跟选择了isi_ansi字体时显示的汉字有极细微的差别,用Snagit把两个窗口的汉字贴出来放大对比就会发现,这个又不知道是windows在处理点阵字体与TTF字体混合使用时的什么花招,反正我的字体审美观已经被牢牢拴死在isi_ansi上了。这是我刚出道时通过netterm访问linux时一见钟情的字体。由netterm自带,此后的漫长岁月中,netterm我是不用了,不过isifont1.dll我却把它抠出来,走哪带哪。
结论:FON文件只是普通的NE格式文件,含有资源,当然资源必需是FONTRES类型才会被WINDOWS识别出是一个字体,寻找到FON文件后可以改扩展名为DLL,用Visual Studio打开,提取出其中的资源,就是FNT文件,FNT文件可以根据上面提及的文件头格式把它的128-255之间的字符屏蔽掉不用,达到在一个程序中只使用该字体的0-127部分的目的。
后记:上面COPY 4个文件的做法显得有点太暴力,毕竟原来的isi字体一个文件就解决了众多的字体问题,如果真想把这4个合成一个自己的字体,也是有办法的,不过我不想再去试了,下面是搜出来的微软的一篇文章,就是讲怎么
把FNT文件打包成资源放进一个DLL中,这个DLL就是实际上的FON文件:
这个是机器翻译成中文后的文章,可以理解大意
阅读(2266) | 评论(0) | 转发(0) |