您好,欢迎来到素彩网!

关于TrueType字体的结构(3)

来源:sc115.com | 155 次浏览 | 2015-09-27

标签: 字体 结构

KTrueType类处理TrueType字体的装入和解码,随书光盘中有它的完整源代码。DecodeGlyph给出图元索引和可选的变换矩阵,处理的是单个图元的解码。参数curve是KCurve类,用于把TrueType图元定义保存为32位的点的赎罪以及一个标志数组,以梗用GDI进行显示。这些代码可以作为简单TrueType字体编辑器的基础。
代码中调用了GetGlyph方法,该方法用位置表索引找到该图元的GlyphHeader结构。从中得到图元的轮廓线数目。注意必须反转该值的字节序,因为TrueType字体用的是Big-Endian字节序。如果该值为负值,说明这是一个合成图元,应该转而调用DecodeCompositeGlyph方法。接下支的代码定位了endPtsOfContours数组,找出点的总数,然后跳过指令找到标志数组的起始位置。
接下去需要长到的是x坐标数组的始位置和长度,这需要遍历标志数组一次。对于每一个控制点,它在x坐标数组中所占空间可能为0到2个字节,这取决于它的相对坐标是0、单个字节还是两个字节。
根据x坐标数组的地址和长度可以得到y坐标的地址。接下去的代码遍历所有的轮廓线,解码其中的控制点,把相对坐标转换为绝对坐标,然后把它加入到曲线对象中。如果需要的话,会对每个控制点做变换。
回想一下,TrueType使用的是二阶Bezier曲线,允许在两个曲线上的点之间有多个不在曲线上的点。为了简化曲线制作算法,KCurve::Add方法在每两个不在曲线上的点之间加入一个额外的在曲线上的点。

处理了简单图元之后,我们来看看合成图元。合成图元用一个经变换的图元序列定义。每个经变换的图元的定义包括三个部分:一个标志、一个图元索引和一个变换矩阵。标志字段决定了变换矩阵的编码方式。编码的目的也是为了节省一些空间,加外还说明了是否已到达序列的终点。一个完整的2D affine变换需要6个值。但如果只是平移的话,只需要两个值(dx,dy),这两个值可以保存为两个字节或两个字。如果x和y以相同的值缩放,加外还需要一个缩放值。取一般的情况下仍然需要6个值,但是很多时候可以节省几个字节。用于变换的值以2.14的有符号定点格式保存,dx和dy值除外,这两个值以整数形式保存。得到合成图元的过程实际上是变换和组合几个图元的过程。比如,如果字体中的一个图元是另一个图元的精确镜像,它只需定义为一个合成图元,可以通过对另一个图像做镜像变换即可。程序清单14-3列出了解码合成图元的代码。


int KTrueType::DecodeCompositeGlyph(const void * pGlyph, KCurve & curve) const
{
KDataStream str(pGlyph);

unsigned flags;

int len = 0;

do
{
flags = str.GetWord();

unsigned glyphIndex = str.GetWord();

// Argument1 and argument2 can be either x and y offsets to be added to the glyph or two point numbers.
// In the latter case, the first point number indicates the point that is to be matched to the new glyph.
// The second number indicates the new glyph's "matched" point. Once a glyph is added, its point numbers
// begin directly after the last glyphs (endpoint of first glyph + 1).

// When arguments 1 and 2 are an x and a y offset instead of points and the bit ROUND_XY_TO_GRID is set to 1,
// the values are rounded to those of the closest grid lines before they are added to the glyph.
// X and Y offsets are described in FUnits.

signed short argument1;
signed short argument2;

if ( flags & ARG_1_AND_2_ARE_WORDS )
{
argument1 = str.GetWord(); // (SHORT or FWord) argument1;
argument2 = str.GetWord(); // (SHORT or FWord) argument2;
}
else
{
argument1 = (signed char) str.GetByte();
argument2 = (signed char) str.GetByte();
}

signed short xscale, yscale, scale01, scale10;

xscale = 1;
yscale = 1;
scale01 = 0;
scale10 = 0;

if ( flags & WE_HAVE_A_SCALE )
{
xscale = str.GetWord();
yscale = xscale; // Format 2.14
}
else if ( flags & WE_HAVE_AN_X_AND_Y_SCALE )
{
xscale = str.GetWord();
yscale = str.GetWord();
}
else if ( flags & WE_HAVE_A_TWO_BY_TWO )
{
xscale = str.GetWord();
scale01 = str.GetWord();
scale10 = str.GetWord();
yscale = str.GetWord();
}

if ( flags & ARGS_ARE_XY_VALUES )
{
XFORM xm;

xm.eDx = (float) argument1;
xm.eDy = (float) argument2;
xm.eM11 = xscale / (float) 16384.0;
xm.eM12 = scale01 / (float) 16384.0;
xm.eM21 = scale10 / (float) 16384.0;
xm.eM22 = yscale / (float) 16384.0;

len += DecodeGlyph(glyphIndex, curve, & xm);
}
else
assert(false);
}
while ( flags & MORE_COMPONENTS );

if ( flags & WE_HAVE_INSTRUCTIONS )
{
unsigned numInstr = str.GetWord();

for (unsigned i=0; istr.GetByte();
}

// The purpose of USE_MY_METRICS is to force the lsb and rsb to take on a desired value.
// For example, an i-circumflex (Unicode 00ef) is often composed of the circumflex and a dotless-i.
// In order to force the composite to have the same metrics as the dotless-i,
// set USE_MY_METRICS for the dotless-i component of the composite. Without this bit,
// the rsb and lsb would be calculated from the HMTX entry for the composite (or would need to be
// explicitly set with TrueType instructions).

// Note that the behavior of the USE_MY_METRICS operation is undefined for rotated composite components.

return len;
}



DecodeCompositeGlyph方法解码每个图元的标志、图元索引和变换矩阵,然后调用DecodeGlypgh方法进行解码。注意,对DecodeGlyph方法的调用包含一个有效的变换矩阵参数。当MORE_COMPONENTS标志结束时,该方法随之结束。随书光盘中有该方法完整的源代码。
解码后的TrueType字体的图元要用GDI制作还有一个小问题需要处理。GDI只制作三阶Bezier曲线,因此从图元表解码所得的二阶Bezier曲线的控制点需要转换为三阶Bezier曲线的控制点。通过对Bezier曲线原始数学定义的研究,可以得到如下用GDI制作二阶Bezier曲线的简单例程。

//draw a 2nd-degree Bezier curve segment
BOOL Bezier2(HDC hDC,int & x0,int & y0, int x1, int y1, int x2 ,int y2)
{
// p0 p1 p2 - > p0 (p0 + 2p1)/3 (2p1+p2)/3, p2

POINT P[3] = { { (x0+2*x1)/3,(y0+2*y1)/3},
{(2*x1+x2)/3,(2*y1+y2)/3},
{x2,y2} };
x0=x2;y0=y2;
return PolyBezierTo(hDC,P,3);
}

对于用三个控制点(p0,p1,p2)定义的二阶Bezier曲线,相应的三阶Bezier曲线的控制点为(p0,(p0+2*p1)/3,(2*p1+p2)/3,p2)。


4.图元指令
程序清单14-2和14-3给人的印象是TrueType字体的栅格器可以通过扫描和转换图元的轮廓来轻松地实现,比如,用GDI和StrokeAndFillPath函数来填充图元轮廓制作出来的路径。这种简单的字体栅格器的实现并不是很有用,除非它只用于高分辨诣的设备如打印机等。
简单栅格器得到的图像笔画粗细不一,有信息的遗漏,有字符特征的损失以及不对称等缺陷。当点阵变小是,情况不会更糟。总之,简单字体栅格器在小尺寸时会产生字迹模糊的结果。在大尺寸时会产生不好看的结果,只有在点阵增大时结果才会改善。
当在大的em-square(典型的是2048)中定义的图元轮廓缩小为小得多的网格时(如32*32),不可避免会损失精度并引入误差。

TrueType解决这个问题的方法是控制图元轮廓从em-square到栅格网格的缩放过程,使得到的结果看起来效果更好,和原始图元的设计尽量接近。这种技术被称为网格调整(grid fitting),它想达到的目标有:
消除网格位置的可能影响,保证笔画的粗细和网格的相对位置无关。
控制图元中关键位置的尺寸
保持对称性和衬线等 重要的图元设计细节。

TrueType字体中网格调整的需求在两个地方中编码:控制值表(control value table)和每个图元的网格调整指令。
控制值表("cvt"表)用于保存一个数组,这些值被称为网格调整指令。比如,对于有衬线的字体,基线、衬线高度、大写字母笔划的宽度等值都或以是被控制的值。它们可以以字体设计者已知的次序保存在控制值表中,然后用它们的索引来引用。在字体光栅化过程中,控制值表中的值根据点阵的大小缩放。在网络调整指令中引用这些值可以保证使用的值与网枸的位置无关。比如,如果水平线[14,0,25,200]可以用CVT表中的两个值定义为[14,0,14+CVT[stem_width],0+CVT[cap_height]],那么该线的宽度和高度会和所在网格的相对位置无关,保持不变。
每一个图元的定义中附加有一个指令序列,该指令序列被称为图元指令,该背景令序列用于控制此图元的网格高速。图元指令线用控制值表中的值,以保证在索引图元中这些值相同。
图元指令是一种基于堆栈的伪计算机的指令。堆栈计算机常用于计算机语言的解释性实现。比如,Forth(用于嵌入式系统的一种强大而简洁的语言)、RPL(HP计算器使用的语言)和Java虚拟机都是堆栈计算机。
堆栈计算机通常没有寄存器,所有的计算都在堆栈上进行(有些堆栈计算机使用分开的控制堆栈和数据堆栈)。比如,压入指令把一个值压入堆栈,弹出指令从堆栈中弹出上面的值,二元加法指令弹出上面的两个值 ,然后把它们的和压入堆栈。

字体知道帮你解决更多字体问题 http://www.zhaozi.cn/zhidao/

搜索: 字体 结构 

最热-字体安装

繁难字库生僻字大全

中文版Vista新设微软雅黑字体

使用11px及10px的清晰汉字字体

多种方法玩好Windows系统字体

Windows系统造字功能详细用法

为什么在安装字体时会出现文件损坏?

Windows7安装字体和用快捷方式安装字体

古汉字字体怎么输入?

方正GBK编码字库清单

象素字简介及象素字体查找好去处

为什么有些字体安装好后有的字打不出来

关于字体的一个惊人发现……

如何解决“无法新增字体”问题

素材
风趣 149风趣 149
风趣 155风趣 155
风趣 160风趣 160
风趣 110风趣 110
风趣 113风趣 113
风趣 116风趣 116
风趣 120风趣 120
风趣 126风趣 126
风趣 122风趣 122