来源: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; i
}
// 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/