汉字的几何中心
英文排版靠基线对齐,所以虽然英文字母有不同的高度深度,通过基线的对齐就能让读者感到文字不凌乱。而单词间的空格和单词长度的不同,能够让读者的视线产 生错落有致的张弛感。但对于中文排版而言,问题就没有那么复杂。中文排版没有空格,而且都是方块字,所以整体的感觉每行都是一条黑带子,而虽然汉字是方块 字,视觉上而言,阅读时仍然可能出现上下左右的波动。这是因为,每个汉字都有视觉中心,而人眼在阅读时,经过每个汉字的时候就会发现视觉中心会上下移动, 而且整体上在一行内也会感觉有疏密感,这无论在横排还是竖排中,都是会发生的。
视觉中心是人的主观感受,难以定量化,就好比很难有一个标准来衡量某人到底是漂亮的还是丑陋的一样。在很多字体设计软件中(比如FontLab的东 亚语言版本),都会给出一个圆心来辅助设计师设计汉字,这样能够提高字体的重心在设计阶段的质量。不过这还是靠字体设计师的审美,没有定量确定的方法。不 过主观感受在某些近似的情况下,依然可以通过某些模型被定量下来,比如说某美女身材很好,可以通过三围来说明;说明传言的真实性有多高,可以援引新华社辟 谣的次数来说明。虽然以偏概全,但是定量化的结论依然有参考价值,也为以后更好地修缮评价标准提供帮助。对于汉字的视觉重心,我们可以先用一个最简单的模 型来描述,就是把它近似为其几何重心(或者物理重心)。这样,我们可以为每个字体的重心平稳性打分,并且看到很多有趣的结论。
为此,我对目前国内出版业界的常用字体,通过程序设计的途径,获得每个字体的相关参数,并且用现实的文本素材加以测试,归纳汉字字体设计在这方面的 大致现状。其实早在三年前我就有用FreeType2库写一个程序做这个实验的打算,当初也和排字工人snl老师说过这件事情,不过由于课业、研究、升学 的繁忙给搁置下来了。现在手里有了一台苹果Mac电脑,自然做这样的事情要比裸写FreeType2代码要方便得多。所以昨晚花了一些时间折腾了当然了, 单纯的数据分析会比较无聊。本着让本博客文章更无聊的目标,在前半部分,我会介绍如何实现这样的计算。这部分内容对于希望熟悉Mac OS X下字体框架、文本引擎服务、渲染技术的人十非常有帮助的,我会着重讲述一下Mac OS X下的类似NSFontManager, NSFont等重要的编程接口及其原理。因此,如果你对于这些内容感兴趣,阅读本文,谢谢。
(最近几年我一直在业余时间做一些偏艺术的技术工作,就字体排印方面而言,《写一篇关于排版理论具体实践的文档来抛砖引 玉》(http://yulewang.spaces.live.com/blog/cns!5C815C994ABB661E!288.entry)、 《TeX相关软件中文字体嵌入一个存在已久的问题水落石出,兼谈不拘小节的中文字体设 计》(http://yulewang.spaces.live.com/blog/cns!5C815C994ABB661E!262.entry)、 《 CJK宏包中,中文字体的奥秘》(http://bbs.ctex.org/viewthread.php?tid=50078)等文章均被很多网站转载 或者讨论。希望这篇文章也能得到类似的效果吧。)
转载本文请著名作者(王越)和出处(http://yulewang.spaces.live.com)。
首先我们来谈谈字体。计算机字体经过三十年的发展,已经趋向稳定。目前不管是TrueType技术还是OpenType技术,都可以通过贝赛尔曲线描绘轮廓。事实上目前大部分的汉字字体也是这么做的。这里我选取了两个字体中的两个字符来说明。
图片来源:本博客汉字的字体用曲线描绘,有些字体(比如左边给出的方正康体),就是简单地使用曲线来描绘轮廓,而另一些字体(比如右边给出的中易楷体),每一个笔画 都用一条曲线来描绘。不管怎么样,这些曲线是通过点来描述的。仔细看右图的例子可以发现,汉字字体先是按秩序给出描述字体轮廓所需要的几个点,并且每个点 给出其控制参数,这样,计算机就可以通过二次或者三次样条插值产生出曲线,是所谓贝赛尔曲线。
得到曲线后,在平面上面每一个点的位置判断该点是否在曲线里面,来描绘该点是否应该在屏幕或者纸张上涂黑,就得到了这个字形的实际图像,如下图所示。
图片来源:本博客如果网格画得足够细密,自然字形图像就会很逼真,比如下图,填充的分辨率等于屏幕分辨率。
图片来源:本博客有了实际的字形图像后,汉字的中心就不再难判断了。
在这里还有最后一个问题,汉字在计算机中,排列的算法是什么。一般来说,现代计算机操作系统都对多国语言有一个统一的排版引擎架构(Layout Engine),大多都基于一定的规则。对于英文排版而言,就是将每个英文字母的原点(Origin)放在基线(Baseline)上,而每个英文字母字 形的实际占用宽度,为这个字形的Advancement(又叫Character Width,和绑定盒的宽度是完全不一样的概念)数值。下面的图片,很好地说明了这个原则。请牢记这些概念,以后的介绍中会经常提及Descent, Ascent等术语。
图片来源:苹果开发者文档中文排版的概念要比英文简单得多,相当于英文排版算法的一个子集,所以,适当引入一些更高级的定义后(断行位置、标点压缩等),中文直接可以用英文排版引擎的模式来处理。
问题又来了,汉字的基线(baseline)是哪一条呢?原点是哪一个呢?一个常见的误解,是汉字是方块字,如果要排列汉字,那只要把包括汉字的这 个正方形的左下角设置为原点,而占用宽度(Advancement)和高度(Ascent)为这个正方形的边长。但事实并非如此。在中文排版中为了追求科 学和美观汉字的底线和基线完全是两个不同的概念。请看下图。(注意,这里指通用的情况。事实上在很多排版系统中,其实用户可以设置汉字底线要比英文基线低 的数值)
图片来源:W3C事实上如果要引申开来,多国语言排版中,对于不同文字的处理是相当复杂的,涉及到的参考线会非常多,请看下面的图片。
图片来源W3C所以汉字的字型的原点(Origin Point),并非在字形盒子的左下角顶点。那这样新的问题又出现,如何把中文排版的概念一般化为普通的概念呢?基线和原点到底在哪里呢?这就涉及到汉字 制作的规则。一般而言,不管什么计算机字体,制作过程中,会确定一个全方(em size),全方在古代,代表英文字母m的宽度,当然在现在现代字体设计中不再硬性规定。汉字都是方块字,对于汉字中任意一个字形,字体的宽度 (Advancement)等于汉字的全方,字体的高(Ascent)加上字体的深(Decent),也为汉字的全方。
图片来源:本博客比如对于华文楷体,查看字体信息就会发现,设计的全方为1000,字高为800,字深为200。所以,对比左图可以发现,这个字体汉字字形的方块大 小,是由 x = -200, x=800, y = 0, y = 1000四条直线确定的。基线为x=0,原点为(0,0),而所谓的汉字底线,为x=-200。明白了这个原则,对以后理解如何产生完整的字体图像很有帮 助,比如要绘制一个字体图像,我们只要产生一个大小为全方的方块,把汉字的原点,放到方块的(0, 字深)坐标绘制即可。
在我们进入讲述如何编写程序渲染字体、计算其几何中心前,我们先来讲述一下,计算机是如何做到这一点的。在上面,我们已经对计算机字体做了一个大致的介 绍。还讲到了排版引擎。排版引擎的工作,是把用户需要渲染的文字以及用户选定的字体输入进来,然后,向操作系统中管理显示的模块输出字形,让其进行渲染。 目前不管Windows、Unix X Window还是Mac OS X,都有一套非常统一、类似的流程来完成这件事情。在Unix X Window中,目前已经有比较成熟的GTK+和Qt库,它们在很长时间内,分别为FreeType2字体库写了一套排版引擎,后来代码合并,公用部分称 为HarfBuzz,HarfBuzz是GTK+中的Pango和Qt中的Scribe两个排版引擎的核心,也将成为开源软件的标准。在Windows 中,很长时间采用win32 API来控制排版引擎,其排版引擎名字叫做Uniscribe,经过长年发展已经非常成熟,但是功能不尽人意。而Windows 7中,推出了一套全新的排版引擎架构,名为DirectWrite,终于可以和Mac并驾齐驱。Mac OS X有复杂的身世,前身NeXTStep系统的排版引擎是在NeXTStep库的NSLayoutManager中提供的,面向Objective C,比较高层。后来NeXT被苹果收购后,产生了所谓编程接口之争,事实上是老苹果员工和新苹果员工的技术之争,NeXTStep的编程接口称为 Yellow Box后又被定为Cocoa,而原本老Mac OS的编程接口经过改良后移植到Mac OS X上称为Carbon,同时,老Mac OS可以通过Blue Box,后称为Classic的虚拟技术在Mac OS X上执行。Classic中,排版引擎的编程接口为MLTE与QuickDraw。Carbon中提供了一个类似的现代的Unicode引擎,名为 ATS,并且可以给Cocoa调用。在很长一阵时间内ATS占了上风。但是由于苹果的编程接口之争最终把Carbon赶下台了,所以以Carbon为靠山 的ATS就被干掉了(由此可见,选择一个好的靠山是多么重要)。从Mac OS X 10.4开始,字体引擎确定为全新的Core Text架构,这是一项根植于Core Foundation架构的引擎,而熟悉Cocoa的朋友们知道,Core Foundation其实也就是Cocoa的基础。所以事实上,当你打开一个文本编辑器输入字符时,Core Text在机箱里默默工作。整个流程是,NSTextView调用了Cocoa的类似NSLayoutManger之类的接口(下文会介绍),而这些接口 又最终调用了Core Text。
图片来源:本博客不管Core Text, DirectWrite, Uniscribe, Harfbuzz, ATS, 还是ICU之类,它们干的事情是类似的,Windows, Unix X Window以及Mac OS X的排版引擎,无论流程和接口都是非常接近的。考虑到Harfbuzz和Core Text之类都是非常底层的编程接口,我们避免讲述那么繁琐的框架,转而从更加清晰简单的Cocoa的文字架构来讲述。读者阅读这些内容时,应该知道,我 讲Cocoa的文字框架,而事实上这套框架很大程度上是依赖于Core Text的,原理基本相同(甚至名字也类似,比如Cocoa的NSFont对应于Core Text的CTFont,Cocoa的NSFontDescriptor对应于Core Text的CTFontDescriptor,而Cocoa中NSFontManager中的Collection,对应于Core Text的CTFontCollection)。同时,由于Core Text和其他排版引擎也是类似的,所以本文虽然面对一个编程接口,但内容具有普适性。
为什么我将要大费周章地讲述排版引擎的功能呢?那是因为我们是通过具体的语料来计算语料中每个汉字的参数的。下面会提到,排版引擎最重要的功能,是 把字符转为字形(所谓char->glyph)。我们需要把语料内的文字转为一个个字形,就需要使用排版引擎,它在整个程序中,发挥了很重要的作 用。
终于切入正题了。这一讲中,我们描述排版引擎如何工作。用户输入了两个字符,f和i,并且指定使用了Gill Sans字体,那计算机是如何最终显示出来的呢?我们先给出一张流程图的图片,请对照图片来读下面的文字。
图片来源:本博客用户输入'f'和‘i'后,计算机开始把用户键盘的输入保存成它能明白的编码。比如在Mac OS X中,任何文字都是使用统一的Unicdoe编码进行描述。对于f和i,其编码分别为U+0066和U+0069。然后这个描述在读入后,会转为内部编 码。每个操作系统或者排版软件选取的内部表示可能有所不同,比如Mac的内部编码表示是UTF-32。古老的系统,8位编码、16位编码都是有的。 XeTeX则用32位中的21位来表示Unicode编码。在这个步骤中,在Coocoa层面,我们生成了一个NSString,它存储了U+0066和 U+0069这两个编码。
用户接着选定了一个字体,Gill Sans。这个字体在Cocoa层面,是一个NSFont对象。我们可以用[NSFont fontWithName:@"Gill Sans" size:12]来产生这个对象。
接着,NSFont和NSString就会被灌入所谓的排版引擎(由NSLayoutManager, NSTextStorage以及NSTextContainer组成,下面会描述),然后排版引擎分析得出,应该取出这个字体的189号字形。紧接着,排 版引擎通过Core Graphics把这个字符渲染在屏幕上或者其他地方,比如在NSImage中。这就是大致的流程。至于如何渲染,我们已经在前面中提到过,先产生轮廓, 后判断点是否在轮廓内。为了弥补屏幕分辨率的不足,会有一些额外的算法(称为抗锯齿),我们会在以后讲到。
然后,你就看到上图中的fi连写字了,事实上这是GillSans中的一个字形(Glyph),见下图:
图片来源:本博客所以,所有的秘密,都隐藏在排版引擎中。看到这里,大家就明白,排版引擎所做的事情,就是把字符串中的一个一个字符(char)转换到字体中的字形 (glyph),所有的排版引擎,不管ICU还是Core Text,上十万行代码,无非在做一件事情:转字符为字形(char to glyph)。在Cocoa框架中,排版引擎的流程如下:首先,NSTextStorage中,存储了刚才的NSFont和NSString,然后,这个 NSTextStorage挂载了一个NSLayoutManager,经过NSLayoutManager的分析,发现fi这两个字在使用Gill Sans字体时,存在连写(ligature),应该被理解为一个单一的glyph,这个glyph在这个字体中,序号是189号,名字叫做fi。经过这 层分析后,NSLayoutManger就挂载一个NSTextContainer,这个是排版引擎产生描绘字体位置的地方,它可以是一个长方形,也可以 是程序指定的任意形状。于是,NSLayoutManager在NSTextContainer中通过 drawGlyphsForGlyphRange:atPoint:指令,灌入了189号字形。最后,NSTextContainer可以传出给 NSTextView,通过Core Graphics绘制在屏幕上。下面是一个完整的原理图:
图片来源:本博客值得说明的是,用户可以对排版引擎做不同的设置,也可以产生在不同的目标大小/形状中,因此,即使对于同一个NSTextStorage,可以挂载 很多个不同的NSLayoutManager。同理,对于同样的NSLayoutManger,可以挂载不同的NSTextContainer。
我们的流程是,依次读入几个测试文本,然后依次选用中文字体对其进行排版,然后加以渲染,得到渲染结果,最后用数学方式对其进行评估。在这里,我们给出到渲染结果为止的代码。
首先,我们建立一个循环,读出程序执行目录中所有的中文测试文本,这个不难,可以用NSFileManager搞定。
其次我们依次读入每个字体。以前讲过, 只要知道字体的名字,我们就可以用[NSFont fontWithName:@"Gill Sans" size:12]之类的语句来读入。不过这太麻烦,我们搞个批处理。
在Mac OS X中,管理NSFont字体的一个接口被称为NSFontManager。用它可以操控字体安装、删除、和字体集管理等工作。我们先把所有需要测试的中文字体拖入FontBook,并且新建一个字体集,叫做Chinese:
然后就能用NSFontManager来载入所有Chinese字体集的字体了,为了计算更精确,我们载入50磅因的字:
根据前面所讲的,我们载入了字体,又有了文本文件的内容,接下来就交给排版引擎了。在前述中,我们说,一般流程是用NSLayoutManager 来取得NSGlyph,然后交给NSTextContainer,不过在这里,我们不需要把字排起来,我们只需要字形,所以获得NSGlyph就行了。首 先初始化各个物件:
然后为了描绘每一个汉字,我们得给一个方形区域。以前讲过,方形的边长为字高和字深的和,即全方。
然后对于每一个NSGlyph,我们画出来便是,我们准备一个NSImage,准备把字形画在NSImage上。
这其中要加入的描绘代码是最关键的。
第一步,我们需要关闭光栅化。光栅化是一项弥补显示器分辨率过于粗糙而创造的软件技术。在Mac OS X上默认所有的字体和图片都是光栅化过的。如果不经过光栅化,视觉上那种古典的字体渲染(即非黑即白)方法会看到锯齿。比如下面的@字符:
就会感觉很粗糙,为什么呢?看细节:
所谓光栅化,就是把边缘用一种过渡的方式来呈现,这样就能够产生一种柔和的过渡,比如这样处理后:
字体在粗分辨率的显示器上看起来就圆滑多了:
之所以要关掉光栅化,是因为我们不需显示出来,待会渲染字体时,按照颜色是纯黑还是纯白来区分某个点是不是在曲线内。
顺便说一下,随着显示器工艺不断提高,光栅化可能在将来会被淘汰。比如现在iPhone的分辨率已经到达400多dpi,超过肉眼分辨极限,因此不管如何,普通人是看不到锯齿的。
第二步,我们制造一个NSBezierPath物件,来表示一个贝赛尔曲线,并且把它的起始坐标移动到(0, 字深)位置,原因已经讲过了,这样能够确保整个字出现在方块以内。
第三步,把该循环所针对的字形([layoutManager glyphAtIndex:glyph])加入到这个贝赛尔曲线中,用appendBezierPathWithGlyph:inFont:这个接口
第四步,把方块的背景色改为黑色。
第五步,绘制贝赛尔曲线,用白色填充。
第六步,设置一个点阵的NSBitmapImageRep物件,来表述刚才画的NSImage。
上述六步,皆在前文代码段lockfocus和unlockfocus之间。具体代码如下:
然后,我们可以用([[representation colorAtX:j y:i] redComponent] == 1)这个条件来测试是否这个区域在字形轮廓曲线内。但是,colorAtX:y这个编程接口每次都要插值、产生一个NSColor物件,比较低效,不如直 接把整个representation物件导出成字符串判断。下面就是一个判断的例子。如果是在轮廓曲线内,那就画上一个+,否则画一个空格。
呵呵,看看我们得到什么?
o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o
o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o
o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o
o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o + + o o o o o o o o o o o o o o
o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o + + + + + o o o o o o o o o o o o
o o o o o o o o o o o o o o o o o o o o o + + o o o o o o o o o o o + + + + + + o o o o o o o o o o o
o o o o o o o o o o o o o o o o o o o o o + + + + + o o o o o o o o + + + + + + o o o o o o o o o o o
o o o o o o o o o o o o o o o o o o o o o + + + + + + o o o o o o + + + + + o o o o o o o o o o o o o
o o o o o o o o o o o o o o o o o o o o o o + + + + + + o o o o + + + + + o o o o o o o o o o o o o o
o o o o o o o + + o o o o o o o o o o o o o o + + + + + o o o o + + + + o o o o o o o o o o o o o o o
o o o o o o o + + + + + + o o o o o o o o o o o + + + + o o o + + + + o o o o o o o o o o o o o o o o
o o o o o o o + + + + + + + o o o o o o o o o o + + + + o o + + + o o o + + + + + + o o o o o o o o o
o o o o o o o o + + + + + + o o o o o o o o o o o o o o o + + + + + + + + + + + + + + o o o o o o o o
o o o o o o o o o + + + + + + o o o o o o + + + + + + + + + + + + + + + + + + + + + + + o o o o o o o
o o o o o o o o o + + + + + o o o + + + + + + + + + + + + + + + + + + + + + + + + + + o o o o o o o o
o o o o o o o o o o o + + + o o o + + + + + + + + + + + + + + o o o o o o o o o o o o o o o o o o o o
o o o o o o o o o o o o o o o o o o o + + + + + o o + + + + + o o o o o o o o o o o o o o o o o o o o
o o o o o o o o o o o o o o o o o o o o o o o o o o + + + + + o o o o o o o o o o o o o o o o o o o o
o o o o o o o o o o o o o o o o o o o o o o o o o o + + + + o o + + + + + + o o o o o o o o o o o o o
o o o o o o o o o o o o o o o o o o o o o + + o o + + + + + + + + + + + + + + o o o o o o o o o o o o
o o o o o o o o o o o o o o o o o o o o o + + + + + + + + + + + + + + + + + + + o o o o o o o o o o o
o o o o o o o o o o + + + + + o o o o o o + + + + + + + + o o o o o o + + + + + o o o o o o o o o o o
o o o o o o o o + + + + + + + + o o o o o o + + + o o o o o o o o o o + + + + o o o o o o o o o o o o
o o o o o + + + + + + + + + + + o o o o o o + + + o o o + + + + + + + + + + + o o o o o o o o o o o o
o o o o + + + + + + + + + + + + o o o o o o + + + + + + + + + + + + + + + + + o o o o o o o o o o o o
o o o o + + + + + o + + + + + o o o o o o o + + + + + + + + + + + + + + + + + o o o o o o o o o o o o
o o o o o o o o o o + + + + o o o o o o o o + + + + + + + o o o o o o + + + + o o o o o o o o o o o o
o o o o o o o o o + + + + o o o o o o o o o + + + o o o o o o o o o o + + + + o o o o o o o o o o o o
o o o o o o o o o + + + o o o o o o o o o o + + + o o o + + + + + + + + + + + o o o o o o o o o o o o
o o o o o o o o o + + + + o o o o o o o o o + + + + + + + + + + + + + + + + + o o o o o o o o o o o o
o o o o o o o o o o + + + + o o o o o o o o + + + + + + + + + + + + o + + + + o o o o o o o o o o o o
o o o o o o o o o o o + + + + o o o o o o + + + + + + + + o o o o o o + + + + o o o o o o o o o o o o
o o o o o o o o o o o + + + + o o o o o o + + + + o o o o o o o o o o + + + + o o o o o o o o o o o o
o o o o o o o o o o o o + + + o o o o o o + + + + o o + + + + + + + + + + + + o o o o o o o o o o o o
o o o o o o o o o o o o + + + o o o o o o + + + + + + + + + + + + + + + + + + o o o o o o o o o o o o
o o o o o o o o o o o o + + + o o o o o o + + + + + + + + + + + + + + + + + + o o o o o o o o o o o o
o o o + + + o + + + + + + + + + o o o o o o + + + + + o o o o o o o o + + + + o o o o o o o o o o o o
o o o + + + + + + + + + + + + + + + + + o o + + o o o o o o o o o o o o + + o o o o o o o o o o o o o
o o o + + + + + + + + + + + + + + + + + + + + + o o o o o o o o o o o o o o o o o o o o o o o o o o o
o o o o + + + + + + + o o o + + + + + + + + + + + + + + o o o o o o o o o o o o o o o o o o o o o o o
o o o o o + + o o o o o o o o o o o + + + + + + + + + + + + + + + + + + + + + + o o o o o o o o o o o
o o o o o o o o o o o o o o o o o o o o + + + + + + + + + + + + + + + + + + + + + + + + + + + + + o o
o o o o o o o o o o o o o o o o o o o o o o o + + + + + + + + + + + + + + + + + + + + + + + + + + o o
o o o o o o o o o o o o o o o o o o o o o o o o o o + + + + + + + + + + + + + + + + + + + + + o o o o
o o o o o o o o o o o o o o o o o o o o o o o o o o o o + + + + + + + + + + + + + + + + o o o o o o o
o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o + + + + + + + + + + + o o o o o o o o o
o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o + + + + o o o o o o o o o o o o o
o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o
o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o
o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o
o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o o
刚刚我们给计算机工作者上完了排版课。现在我们开始给字体设计师上数学课。
我们既然得到了每个汉字的点阵,那计算几何中心就变得非常方便了。下面向字体设计师说明一下几何中心的定义。
(摘自Wikipedia:)几何学中, n 维空间中一个对象 X 的几何中心或中心、重心、形心是将 X 分成矩相等的两部分的所有超平面的交点。非正式地说,它是 X 中所有点的平均。如果一个对象具有一致的密度,或者其形状和密度具有某种对称性足以确定几何中心,那么它的几何中心和质量中心重合。该条件是充分但不是必 要的。
几何中心在一般情况下,可以通过积分求出:
如果你不知道什么是积分,不要紧,因为我们已经把字体的轮廓曲线变为了有限个在轮廓内的点,所以我们不需要再按照贝赛尔曲线求积分了。那怎么计算有 限个点的几何中心呢?事实上,有限个点总存在几何中心,可以通过计算这些点的每个坐标分量的算术平均值得到。我们已经知道每个在曲线内的点的坐标,那很简 单,求所有坐标值的平均值就行了。公式如下:
我们在前面讲到,我们要判断每个汉字的几何中心的波动情况,那怎么判断这个中心抖动得厉害还是不厉害呢?在统计学中,存在一个可以衡量这个情况得指 标,叫标准差。标准差(Standard Deviation),在概率统计中最常使用作为统计分布程度(statistical dispersion)上的测量。标准差定义为方差的算术平方根,反映组内个体间的离散程度。
假设有一组数,x1,x2,.. xn,则其平均值为
根据平均值就能算出标准偏差:
当然,这需要保存大量得几何中心,才能算出标准差。实际情况下不需要这么浪费内存。实际上我们只要不断累加几何中心坐标和几何中心坐标得平方就可以,因为根据下面的推导:
所以得到下面这样等价的标准差计算公式,在我们的计算中,使用这个公式,因为它不需要保存每个计算出来的坐标,所以占用更小的计算机内存。
今天的数学课就讲到这里。恩。
有了上面讲述的数学知识,不难算出文本每个汉字的几何中心,以及每个中心的标准差。我们把每个汉字的大小假设为1,那如果几何中心是正中,坐标就是(0.5,0.5)。我们按照这个概念编写了完整的计算程序,这里贴出全部内容:
点击这里下载 Untitled.pdf
源文件: main.m
事实上,在Mac OS X中,可以对每个字体中的所有字形(glyph)进行计算。不过这样做并没有太大的价值,一方面如此大量的字形计算起来比较耗时,而且很多字形是英文字 形、数学字形等等,会给计算造成偏差。另一方面,这样的计算很不实际,在实际环境中,不同的汉字、标点等出现的频率不一样。为了得到大致的概况,我们必须 使用语料对每个字体进行测试
我们使用的语料很简单,节选了《老子道德经》的前十章内容。包括标点符号。具体的语料如下:
道可道也,非恒道也。名可名也,非恒名也。 “无”,名天地之始;“有”,名万物之母。 故,常“无”,欲以观其妙;常“有”,欲以观其徼。 此两者,同出而异名,同谓之玄。玄之又玄,众妙之门。天下皆知美之为美,斯恶已。皆知善之为善,斯不善已。有无相生,难易相成,长短相形,高下相盈,音声 相和,前后相随。恒也。是以圣人处无为之事,行不言之教;万物作而弗始,生而弗有,为而弗恃,功成而不居。夫唯弗居,是以不去。不尚贤,使民不争;不贵难 得之货,使民不为盗;不见可欲,使民心不乱。是以圣人之治,虚其心,实其腹,弱其志,强其骨。常使民无知无欲。使夫智者不敢为也。为无为,则无不治。道 冲,而用之或不盈。渊兮,似万物之宗。挫其锐,解其纷,和其光,同其尘。湛兮,似或存。吾不知谁之子,象帝之先。天地不仁,以万物为刍狗;圣人不仁,以百 姓为刍狗。天地之间,其犹橐籥乎?虚而不屈,动而愈出。多闻数穷,不如守中。谷神不死,是谓玄牝。玄牝之门,是谓天地根。绵绵若存,用之不勤。天长地久。 天地所以能长且久者,以其不自生,故能长生。是以圣人后其身而身先,外其身而身存。非以其无私邪?故能成其私。上善若水,水善利万物而不争。处众人之所 恶,故几于道。居善地,心善渊,与善仁,言善信,正善治,事善能,动善时。夫唯不争,故无尤。持而盈之,不如其已。揣而锐之,不可长保。金玉满堂,莫之能 守。富贵而骄,自遗其咎。功成身退,天之道。载营魄抱一,能无离乎?专气致柔,能婴儿乎?涤除玄览,能无疵乎?爱民治国,能无知乎?天门开阖,能为雌乎? 明白四达,能无为乎?生之、畜之,生而不有,为而不恃,长而不宰。是谓玄德。
对于这个简单的语料,我们对字体进行分析,下面贴出计算的结果:
------------------------------------------: simplified.txt
avg(x) | avg(y) | dev(x) | dev(y) | Font
0.43985 | 0.54518 | 0.11988 | 0.10920 | 楷体_GB2312
0.35586 | 0.55983 | 0.07977 | 0.09323 | 方正胖娃_GBK
0.38872 | 0.51516 | 0.09082 | 0.11344 | 方正姚体_GBK
0.36737 | 0.49480 | 0.08348 | 0.10310 | 方正康体_GBK
0.43656 | 0.55496 | 0.13693 | 0.13627 | Adobe 仿宋 Std R
0.49589 | 0.48770 | 0.04004 | 0.05729 | Adobe 明體 Std L
0.38893 | 0.51845 | 0.09360 | 0.11127 | 方正幼线_GBK
0.41994 | 0.51366 | 0.09867 | 0.11772 | 方正报宋_GBK
0.38445 | 0.51690 | 0.08938 | 0.10968 | 方正美黑_GBK
0.49101 | 0.48806 | 0.03729 | 0.05270 | 標楷體
0.41928 | 0.51786 | 0.09715 | 0.11679 | 方正楷体_GBK
0.40051 | 0.49357 | 0.09524 | 0.11131 | 方正宋三_GBK
0.49594 | 0.49991 | 0.04081 | 0.08196 | 蘋果儷中黑
0.43850 | 0.54883 | 0.12489 | 0.14114 | Hiragino Sans GB W3
0.39881 | 0.50868 | 0.08913 | 0.11522 | 方正隶二_GBK
0.36759 | 0.46376 | 0.08183 | 0.10632 | 方正黄草_GBK
0.39972 | 0.50639 | 0.09322 | 0.12575 | 方正稚艺_GBK
0.39744 | 0.50897 | 0.09174 | 0.11052 | 方正大黑_GBK
0.39265 | 0.51455 | 0.09031 | 0.11343 | 方正中等线_GBK
0.39556 | 0.50850 | 0.09027 | 0.10972 | 方正细黑一_GBK
0.38300 | 0.47908 | 0.08844 | 0.10953 | 方正行楷_GBK
0.44896 | 0.54178 | 0.10314 | 0.11222 | 黑体-简 细体
0.40160 | 0.51366 | 0.09277 | 0.11465 | 方正仿宋_GBK
0.50069 | 0.49787 | 0.04156 | 0.05165 | 儷宋 Pro
0.41487 | 0.50352 | 0.09917 | 0.10739 | 方正大标宋_GBK
0.38270 | 0.51429 | 0.08680 | 0.11886 | 方正华隶_GBK
0.37280 | 0.52402 | 0.08567 | 0.09162 | 方正超粗黑_GBK
0.40818 | 0.49060 | 0.09582 | 0.11724 | 方正新报宋_GBK
0.38191 | 0.52826 | 0.08711 | 0.11316 | 方正细倩_GBK
0.38348 | 0.50576 | 0.09024 | 0.10037 | 方正小标宋_GBK
0.45893 | 0.54223 | 0.10633 | 0.12079 | 华文宋体
0.36098 | 0.54927 | 0.08283 | 0.09876 | 方正琥珀_GBK
0.43332 | 0.54407 | 0.11657 | 0.11006 | 仿宋_GB2312
0.38575 | 0.51910 | 0.09256 | 0.11772 | 方正少儿_GBK
0.44328 | 0.54347 | 0.11546 | 0.13006 | Hiragino Sans GB W6
0.41396 | 0.51724 | 0.09294 | 0.12212 | 方正隶变_GBK
0.39979 | 0.48482 | 0.09159 | 0.10973 | 方正宋黑_GBK
0.39779 | 0.52249 | 0.09015 | 0.11445 | 方正黑体_GBK
0.49577 | 0.49402 | 0.03494 | 0.08145 | 儷黑 Pro
0.39235 | 0.50976 | 0.08854 | 0.12103 | 方正细圆_GBK
0.38987 | 0.52260 | 0.09006 | 0.11560 | 方正中倩_GBK
0.39387 | 0.50984 | 0.09050 | 0.11847 | 方正准圆_GBK
0.45150 | 0.53949 | 0.10257 | 0.11372 | 华文黑体
0.37492 | 0.52913 | 0.08609 | 0.10971 | 方正粗圆_GBK
0.50315 | 0.50508 | 0.04194 | 0.05252 | 教育部標準楷書
0.50358 | 0.49338 | 0.02966 | 0.05252 | 蘋果儷細宋
0.40123 | 0.49103 | 0.09402 | 0.13567 | 方正细珊瑚_GBK
0.47877 | 0.55272 | 0.09063 | 0.13882 | Adobe Myungjo Std M
0.45018 | 0.54543 | 0.11472 | 0.10136 | 宋体
0.40733 | 0.49853 | 0.09608 | 0.11518 | 方正魏碑_GBK
0.39021 | 0.52042 | 0.08851 | 0.11348 | 方正粗倩_GBK
0.41714 | 0.53040 | 0.09989 | 0.10440 | 方正舒体_GBK
0.39530 | 0.48484 | 0.09199 | 0.11200 | 方正书宋_GBK
0.38682 | 0.48519 | 0.08596 | 0.12400 | 方正瘦金书_GBK
0.38493 | 0.51120 | 0.08945 | 0.09789 | 方正粗宋_GBK
0.39911 | 0.50111 | 0.09313 | 0.11298 | 方正综艺_GBK
0.45119 | 0.54701 | 0.10306 | 0.11238 | 黑体-简 中等
0.46406 | 0.53945 | 0.11084 | 0.11517 | 华文楷体
0.41005 | 0.51193 | 0.09374 | 0.11328 | 方正隶书_GBK
0.41795 | 0.51261 | 0.09800 | 0.11838 | 方正宋一_GBK
0.37604 | 0.51438 | 0.08932 | 0.12327 | 方正平和_GBK
0.38121 | 0.51127 | 0.08816 | 0.11150 | 方正水柱_GBK
0.49929 | 0.50410 | 0.03242 | 0.05184 | 黑体-繁 中等
0.43142 | 0.51715 | 0.13844 | 0.13095 | Hei Regular
0.36013 | 0.55001 | 0.08477 | 0.08576 | 方正彩云_GBK
0.49648 | 0.50029 | 0.03513 | 0.04847 | 黑体-繁 细体
0.49871 | 0.48571 | 0.03808 | 0.05174 | 教育部標準宋體UN
0.43605 | 0.55082 | 0.13556 | 0.13478 | Adobe 黑体 Std R
0.44390 | 0.55862 | 0.13984 | 0.13316 | Adobe 楷体 Std R
0.46023 | 0.54950 | 0.10382 | 0.13292 | Adobe 宋体 Std L
0.44829 | 0.54160 | 0.10209 | 0.11222 | 华文细黑
0.45361 | 0.54493 | 0.10314 | 0.12013 | 华文仿宋
0.44318 | 0.54260 | 0.11083 | 0.10308 | 黑体
0.35741 | 0.54930 | 0.05794 | 0.10633 | Arial Unicode MS
0.39610 | 0.49941 | 0.09008 | 0.10989 | 方正细等线_GBK
0.42580 | 0.53361 | 0.13944 | 0.13479 | Kai Regular
0.49188 | 0.48894 | 0.04141 | 0.05908 | Adobe 繁黑體 Std B
0.43907 | 0.58945 | 0.12429 | 0.11954 | 隶书
0.41235 | 0.50259 | 0.09769 | 0.11477 | 方正新舒体_GBK
0.43726 | 0.54315 | 0.12475 | 0.12876 | 幼圆
从得出的数据看,可以归纳一些有意思的结论。
方正、中易等字体平均中心偏离正中,居左下角,标准差偏大(0.1左右)。类似中华民国教育部字标准楷书、黑体-繁等字体,平均中心均为正中,标准差很小(0.05左右)。可见繁体字体的中心平稳度超过简体字体。
这个事实上是由于标点符号导致的。在中国大陆,标点符号比如逗号句号,都放置于左下角,而在香港、台湾等地,标点符号位于字方中心位置。所以,港台的文字平稳性更好。这也是中国大陆书籍版面斑驳,每行有很强割裂感的主要原因。
所以,国家语委语言文字应用研究所《标点符号用法》课题组应该为用户的阅读困难和版面的斑驳付出责任。龚千炎、刘一玲两位《国家标准标点符号用法》起草人应该抓起来关着。
标点符号是造成中心跳跃的主要原因。如果除掉这个因素,只讨论汉字的重心平稳性,我们可以把上述语料的标点全部去除,进行讨论。产生的结果如下:
顿时可以发现,简体汉字字体的重心标准差明显减少,说明平稳性很高。最高的属于方正胖娃、方正琥珀、方正超粗黑等重笔画字体。原因是笔画越重,方框 内的点越密集,越容易做到这一点。对于常用字体宋体,苹果蘋果儷細宋表现最佳,方正书宋其次。华文宋体和Adobe宋体紧随其后,难分伯仲。在岁后则是中 易宋体、教育部标准宋体和报宋。方正新报宋的平稳性明显好于方正报宋。
对于竖排,我们考察Y方向的标准差。这时可以发现方正字体整体水平要好于中易、华文、Adobe等公司,基本维持在0.2左右,其他公司的字体一般维持在0.3以上。方正确实是国内字体设计领域的龙头企业。
最后一个实验,我们把节选的语料使用繁体(正体)汉字排版。看看会不会对汉字稳定性有所改善。
经过测试,方正系列字体,繁体和简体的标准差几乎不变,这里略去。这里给出其他字体的标准差。
可以看到,对于相同的字体,改为繁体后,标准差亦有所减少,虽然减少的数值有限,其原因主要是因为很多汉字的繁体和简体字形是相同。不过我们依然可 以看到平均的标准差减小了10%和15%左右。所以,说简化汉字破坏了汉字的美观性,一点不错。当初鼓吹简化汉字的人都该拉出去砍了。
在这个系列中,我们用数值统计的方法,对汉字字体的平衡性进行了初步的估值。虽然本文的方法很粗糙,但是事实上却提供了一个全新的评价方法。对于这 样一个题目而言,我们以后还可以用更好的模型来描述汉字的视觉重心,而不是简单地描述为几何中心。同时,我们可以对更多更复杂的语料进行分析。这个文章提 出了一个评价思路,我们还可以以这个思路,对汉字字体的其他美观性设立相关的指标进行评价,感兴趣的朋友们不妨试试。