FreeType2教程第二部分

第二部分 管理字形

介绍

     这是“FreeType2 教程”的第二部分。它将教会你如何html

* 检索字形度量 
* 便捷地管理字形图像 
* 检索全局度量(包括字距调整) 
* 渲染一个简单的字符串(采用字距调整) 
* 渲染一个居中的字符串(采用字距调整) 
* 渲染一个经变换的字符串(采用居中) 
* 在须要时以预设字体单位的格式获取度量,以及把它们缩放到设备空间数组

1.字形度量缓存

 

     顾名思义,字形度量是对应每个字形的特定距离,以此描述如何对文本排版。 
     一般一个字形有两个度量集:用来排版水平文本排列的字形(拉丁文、西里尔文、阿拉伯文、希伯来文等等)和用来排版垂直文本排列的字形(中文、日文、韩文等等)。 
     要注意的是只有不多的字体格式提供了垂直度量。你可使用宏FT_HAS_VERTICAL测试某个给出的face对象是否包含垂直度量,当结果为真时表示包含垂直度量。 
     每一个的字形度量均可以先装载字形到face的字形槽,而后经过face->glyph->metrics结构访问,其类型为FT_Glyph_Metrics。咱们将在下面详细讨论它,如今,咱们只关注该结构包含以下的字段:函数

Width 
这是字形图像的边框的宽度。它与排列方向无关。 
Height 
这是字形图像的边框的高度。它与排列方向无关。千万不要把它和FT_Size_Metrics的height字段混淆。 
horiBearingX 
用于水平文本排列,这是从当前光标位置到字形图像最左边的边界的水平距离。 
horiBearingY 
用于水平文本排列,这是从当前光标位置(位于基线)到字形图像最上边的边界的水平距离。 horiAdvance 
用于水平文本排列,当字形做为字符串的一部分被绘制时,这用来增长笔位置的水平距离。 
vertBearingX 
用于垂直文本排列,这是从当前光标位置到字形图像最左边的边框的垂直距离。 
vertBearingY 
用于垂直文本排列,这是从当前光标位置(位于基线)到字形图像最上边的边框的垂直距离。 vertAdvance 
用于垂直文本排列,当字形做为字符串的一部分被绘制时,这用来增长笔位置的垂直距离。 
注意:由于不是全部的字体都包含垂直度量,当FT_HAS_VERTICAL为假时,vertBearingX,vertBearingY和vertAdvance的值是不可靠的。布局

     下面的图形更清楚地图解了度量。第一个图解了水平度量,其基线为水平轴:FreeType2教程第二部分测试

 

     对于垂直文本排列,基线是垂直的,与垂直轴一致:字体

     Face->glyph->metrics中的度量一般以26.6象素格式(例如1/64象素)表示,除非你在调用FT_Load_Glyph或FT_Load_Char时使用了FT_LOAD_NO_SCALE标志,这样的话度量会用原始字体单位表示。 
     字形槽(glyph slot)对象也有一些其余有趣的字段能够减轻开发者的工做。你能够经过face->glyph->xxx访问它们,其中xxx是下面字段之一:spa

Advance 
这个字段是一个FT_Vector,保存字形的经变换步长。当你经过FT_Set_Transform使用变换时,这是颇有用的,这在第一部分的循环文本例子中已经展现过了。这个值是默认的(metrics.horiAdvance,0),除非你在装载字形图像时指定FT_LOAD_VERTICAL,那么它将会为(0,metrics.vertAdvance),这点与第一部分的例子不一样。 
linearHoriAdvance 
这个字段包含字形水平推动宽度的线性刻度值。实际上,字形槽返回的metrics.horiAdvance值一般四舍五入为整数象素坐标(例如,它是64的倍数),字体驱动器用它装载字形图像。linearHoriAdvance是一个16.16固定浮点数,提供了以1/65536象素为单位的原始字形推动宽度的值。它能够用来完成伪设备无关文字排版。 
linearVertAdvance 
这与linearHoriAdvance相似,但它用于字形的垂直推动高度。只有当字体face包含垂直度量时这个值才是可靠的。.net

2.管理字形图像

     装载到字形槽的字形能够转换到一幅字形位图中,这能够在装载时使用FT_LOAD_RENDER标志或者调用FT_Render_Glyph函数实现。每一次你装载一个新的字形到字形槽,前面装载的字形将会从字形槽中抹去。 
     可是,你可能须要从字形槽中提取这个字形,并在你的应用程序中缓存它,或者进行附加的变换,或者在转换成位图前测量它。 
     FreeType 2 API有一个特殊的扩展可以以一种灵活和普通的方式处理字形图像。要使用它,你首先须要包含FT_GLYPH_H头文件,以下:设计

#include FT_GLYPH_H

     如今咱们将解释如何使用这个文件定义的这个函数。

 

a.提取字形图像

 

     你能够很简单地提取一个字形图像。这里有一贯代码向你展现如何去作:

FT_Glyph glyph; 

...
error = FT_Load_Glyph( face, glyph_index, FT_LOAD_NORMAL );
if ( error ) { ... }

error = FT_Get_Glyph( face->glyph, &glyph );
if ( error ) { ... }

     显而易见,咱们:

* 建立一个类型为FT_Glyph,名为glyph的变量。这是一个字形图像的句柄(即指针)。

* 装载字形图像(一般状况下)到face的字形槽中。咱们不使用FT_LOAD_RENDER由于咱们想抓取一个可缩放的字形图像,以便后面对其进行变换。

* 经过调用FT_Get_Glyph,把字形图像从字形槽复制到新的FT_Glyph对象glyph中。这个函数返回一个错误码而且设置glyph。

     要很是留意,被取出的字形跟字形槽中的原始字形的格式是同样的。例如,若是咱们从TrueType字体文件中装载一个字形,字形图像将是可伸缩的矢量轮廓。

     若是你想知道字形是如何建模和存储的,你能够访问flyph->format字段。一个字形对象能够经过调用FT_Done_Glyph来销毁。

     字形对象正好包含一个字形图像和一个2D矢量,2D矢量以16.16固定浮点坐标的形式表示字形的推动步长。后者能够直接经过glyph->advance访问它。

     注意,不一样于其余TrueType对象,库不保存所有分配了的字形对象的列表。这意味着你必须本身销毁它们,而不是依靠FT_Done_FreeType完成所有的清除。

 

b.变换和复制字形图像

 

     若是字形图像是可伸缩的(glyph->format不等于FT_GLYPH_FORMAT_BITMAP),那么就能够随时经过调用FT_Glyph_Transform来变换该图像。

     你也能够经过FT_Glyph_Copy复制一个字形图像。这里是一些例子代码:

FT_Glyph glyph, glyph2;
FT_Matrix matrix;
FT_Vector delta;

... 装载字形图像到 `glyph' ...


error = FT_Glyph_Copy( glyph, &glyph2 );
if ( error ) { ... 没法复制(内存不足) ... }


delta.x = -100 * 64; 
delta.y = 50 * 64;
FT_Glyph_Transform( glyph, 0, &delta );


matrix.xx = 0x10000L;
matrix.xy = 0.12 * 0x10000L;
matrix.yx = 0;
matrix.yy = 0x10000L;
FT_Glyph_Transform( glyph2, &matrix, 0 );

     注意,2×2矩阵变换老是适用于字形的16.16推动步长矢量,因此你不须要重修计算它。

 

c.测量字形图像

     你也能够经过FT_Glyph_Get_CBox函数检索任意字形图像(不管是可伸缩或者不可伸缩的)的控制(约束)框,以下:

FT_BBox bbox;
...
FT_Glyph_Get_CBox( glyph, bbox_mode, &bbox );

     坐标是跟字形的原点(0, 0)相关的,使用y向上的约定。这个函数取一个特殊的参数:bbox_mode来指出如何表示框坐标。

     若是字形装载时使用了FT_LOAD_NO_SCALE标志,bbox_mode必须设置为FT_GLYPH_BBOX_UNSCALED,以此来得到以26.6象素格式为单位表示的不可缩放字体。值FT_GLYPH_BBOX_SUBPIXELS是这个常量的另外一个名字。

     要注意,框(box)的最大坐标是惟一的,这意味着你老是能够以整数或26.6象素的形式计算字形图像的宽度和高度,公式以下:

width = bbox.xMax - bbox.xMin;
height = bbox.yMax - bbox.yMin;

     同时要注意,对于26.6坐标,若是FT_GLYPH_BBOX_GRIDFIT被用做为bbox_mode,坐标也将网格对齐,符合以下公式:

bbox.xMin = FLOOR( bbox.xMin )
bbox.yMin = FLOOR( bbox.yMin )
bbox.xMax = CEILING( bbox.xMax )
bbox.yMax = CEILING( bbox.yMax )

     要把bbox以整数象素坐标的形式表示,把bbox_mode设置为FT_GLYPH_BBOX_TRUNCATE。

     最后,要把约束框以网格对齐象素坐标的形式表示,把bbox_mode设置为FT_GLYPH_BBOX_PIXELS。

 

d.转换字形图像为位图

     当你已经把字形对象缓存或者变换后,你可能须要转换它到一个位图。这能够经过FT_Glyph_To_Bitmap函数简单得实现。它负责转换任何字形对象到位图,以下:

FT_Vector origin;

origin.x = 32; 
origin.y = 0;
error = FT_Glyph_To_Bitmap(
    &glyph,
    render_mode,
    &origin,
    1 );

     一些注解:

* 第一个参数是源字形句柄的地址。当这个函数被调用时,它读取该参数来访问源字形对象。调用结束后,这个句柄将指向一个新的包含渲染后的位图的字形对象。

* 第二个参数时一个标准渲染模式,用来指定咱们想要哪一种位图。它取FT_RENDER_MODE_DEFAULT时表示8位颜色深度的抗锯齿位图;它取FT_RENDER_MODE_MONO时表示1位颜色深度的黑白位图。

* 第三个参数是二维矢量的指针。该二维矢量是用来在转换前平移源字形图像的。要注意,函数调用后源图像将被平移回它的原始位置(这样源图像便不会有变化)。若是你在渲染前不须要平移源字形,设置这个指针为0。

* 最后一个参数是一个布尔值,用来指示该函数是否要销毁源字形对象。若是为false,源字形对象不会被销毁,可是它的句柄丢失了(客户应用程序须要本身保留句柄)。

     若是没返回错误,新的字形对象老是包含一个位图。而且你必须把它的句柄进行强制类型转换,转换为FT_BitmapGlyph类型,以此访问它的内容。这个类型是FT_Glyph的一种“子类”,它包含下面的附加字段(看FT_BitmapGlyphRec):

Left

相似于字形槽的bitmap_left字段。这是字形原点(0,0)到字形位图最左边象素的水平距离。它以整数象素的形式表示。

Top

相似于字形槽的bitmap_top字段。它是字形原点(0,0)到字形位图最高象素之间的垂直距离(更精确来讲,到位图最上面的象素)。这个距离以整数象素的形式表示,而且y轴向上为正。

Bitmap

这是一个字形对象的位图描述符,就像字形槽的bitmap字段。

3.全局字形度量

     不一样于字形度量,全局度量是用来描述整个字体face的距离和轮廓的。他们能够用26.6象素格式或者可缩放格式的“字体单位”来表示。

 

a.预设全局度量

 

     对于可缩放格式,所有全局度量都是以字体单位的格式表示的,这能够用来在稍后依照本教程本部分的最后一章描述的规则来缩放到设备空间。你能够经过FT_Face句柄的字段直接访问它们。

     然而,你须要在使用它们前检查字体face的格式是否可缩放。你可使用宏FT_IS_SCALEABLE来实现,当该字体是可缩放时它返回正。

若是是这样,你就能够访问全局预设度量了,以下:

units_per_EM

这是字体face的EM正方形的大小。它是可缩放格式用来缩放预设坐标到设备象素的,咱们在这部分的最后一章叙述它。一般这个值为2048(对于TrueType)或者1000(对于Type 1),可是其余值也是可能的。对于固定尺寸格式,如FNT/FON/PCF/BDF,它的值为1。

global_bbox

全局约束框被定义为最大矩形,该矩形能够包围字体face的全部字形。它只为水平排版而定义。

ascender

Ascender是从水平基线到字体face最高“字符”的坐标之间的垂直距离。不幸地,不一样的字体格式对ascender的定义是不一样的。对于某些来讲,它表明了所有大写拉丁字符(重音符合除外)的上沿值(ascent);对于其余,它表明了最高的重音符号的上沿值(ascent);最后,其余格式把它定义为跟global_bbox.yMax相同。

descender

Descender是从水平基线到字体face最低“字符”的坐标之间的垂直距离。不幸地,不一样的字体格式对descender的定义是不一样的。对于某些来讲,它表明了所有大写拉丁字符(重音符合除外)的下沿值(descent);对于其余,它表明了最高的重音符号的下沿值(descent);最后,其余格式把它定义为跟global_bbox.yMin相同。这个字段的值是负数。

text_height

这个字段是在使用这个字体书写文本时用来计算默认的行距的(例如,基线到基线之间的距离)。注意,一般它都比ascender和descent的绝对值之和还要大。另外,不保证使用这个距离后面就没有字形高于或低于基线。

max_advance_width

这个字段指出了字体中全部字形得最大的水平光标推动宽度。它能够用来快速计算字符串得最大推动宽度。它不等于最大字形图像宽度!

max_advance_height

跟max_advance_width同样,可是用在垂直文本排版。它只在字体提供垂直字形度量时才可用。

underline_position

当显示或者渲染下划线文本时,这个值等于下划线到基线的垂直距离。当下划线低于基线时这个值为负数。

underline_thickness

当显示或者渲染下划线文本时,这个值等于下划线的垂直宽度。

     如今注意,很不幸的,因为字体格式多种多样,ascender和descender的值是不可靠的。

 

 

b.伸缩的全局度量

 

     每个size对象同时包含了上面描述的某些全局度量的伸缩版本。它们能够经过face->size->metrics结构直接访问。

注意这些值等于预设全局变量的伸缩版本,但没有作舍入或网格对齐。它们也彻底独立于任何hinting处理。换句话说,不要依靠它们来获取象素级别的精确度量。它们以26.6象素格式表示。

ascender

原始预设ascender的伸缩版本。

descender

原始预设ascender的伸缩版本。

height

原始预设文本高度(text_height)的伸缩版本。这多是这个结构中你真正会用到的字段。

max_advance

原始预设最大推动的伸缩版本。

     注意,face->size->metrics结构还包含其余字段,用来伸缩预设坐标到设备空间。它们会在最后一章描述。

 

 

c.字距调整

 

     字距调整是调整字符串中两个并排的字形图像位置的过程,它能够改善文本的总体外观。基本上,这意味着当‘A’的跟着‘V’时,它们之间的间距能够稍微减小,以此避免额外的“对角线空白”。

     注意,理论上字距调整适用于水平和垂直方向的两个字形,可是,除了很是极端的状况外,几乎在全部状况下,它只会发生在水平方向。

     不是全部的字体格式包含字距调整信息。有时候它们依赖于一个附加的文件来保存不一样的字形度量,包括字距调整,但该文件不包含字形图像。一个显著的例子就是Type1格式。它的字形图像保存在一个扩展名为.pfa或.pfb的文件中,字距调整度量存放在一个附加的扩展名为.afm或.pfm的文件中。

     FreeType 2提供了FT_Attach_File和FT_Attach_Stream API来让你处理这种状况。两个函数都是用来装载附加的度量到一个face对象中,它经过从附加的特定格式文件中读取字距调整度量来实现。例如,你能够象下面那样打开一个Type1字体:

 

error = FT_New_Face( library, "/usr/shared/fonts/cour.pfb",
    0, &face );
if ( error ) { ... }

error = FT_Attach_File( face, "/usr/shared/fonts/cour.afm" );
if ( error )
{ ... 没能读取字距调整和附加的度量 ... }

     注意,FT_Attach_Stream跟FT_Attach_File是相似的,不一样的是它不是以C字符串指定附加文件,而是以一个FT_Stream句柄。另外,读取一个度量文件不是强制性的。

最后,文件附加API是很是通用的,能够用来从指定的face中装载不一样类型的附加信息。附加内容的种类彻底是因字体格式而异的。

     FreeType 2容许你经过FT_Get_Kerning函数获取两个字形的字距调整信息,该函数界面以下:

FT_Vector kerning;
...
error = FT_Get_Kerning( face, 
    left, 
    right, 
    kerning_mode, 
    &kerning );

     正如你所见到的,这个函数的参数有一个face对象的句柄、字距调整值所要求的左边和右边字形索引,以及一个称为字距调整模式的整数,和目标矢量的指针。目标矢量返回适合的距离值。

 

     字距调整模式跟前一章描述的bbox模式(bbox mode)是很相似的。这是一个枚举值,指示了目标矢量如何表示字距调整距离。

     默认值是FT_KERNING_DEFAULT,其数值为0。它指示字距调整距离以26.6网格对齐象素(这意味着该值是64的倍数)的形式表示。对于可伸缩格式,这意味着返回值是把预设字距调整距离先伸缩,而后舍入。

     值FT_KERNING_UNFITTED指示了字距调整距离以26.6非对齐象素(也就是,那不符合整数坐标)的形式表示。返回值是把预设字距调整伸缩,但不舍入。

     最后,值FT_KERNING_UNSCALED是用来返回预设字距调整距离,它以字体单位的格式表示。你能够在稍后用本部分的最后一章描述的算式把它拉伸到设备空间。

     注意,“左”和“右”位置是指字符串字形的可视顺序。这对双向或由右到左的文原本说是很重要的。

 

4.简单的文本渲染:字距调整+居中

     为了显示咱们刚刚学到的知识,如今咱们将示范如何修改第一部分给出的代码以渲染一个字符串,而且加强它,使它支持字距调整和延迟渲染。

 

a.字距调整支持

     要是咱们只考虑处理从左到右的文字,如拉丁文,那在咱们的代码上添加字距调整是很容易办到的。咱们只要获取两个字形之间的字距调整距离,而后适当地改变笔位置。代码以下:

FT_GlyphSlot slot = face->glyph; 
FT_UInt glyph_index;
FT_Bool use_kerning;
FT_UInt previous;
int pen_x, pen_y, n;

... 初始化库 ...
... 建立face对象 ...
... 设置字符尺寸 ...

pen_x = 300;
pen_y = 200;
use_kerning = FT_HAS_KERNING( face );
previous = 0;

for ( n = 0; n < num_chars; n++ )
{
    
    glyph_index = FT_Get_Char_Index( face, text[n] );

    
    if ( use_kerning && previous && glyph_index )
    {
        FT_Vector delta;
        FT_Get_Kerning( face, previous, glyph_index,
            ft_kerning_mode_default, &delta );

        pen_x += delta.x >> 6;
    }

    
    Error = FT_Load_Glyph(face, glyph_index, FT_LOAD_RENDER);

    if ( error )
        continue; 

    
    my_draw_bitmap( &slot->bitmap,

    pen_x + slot->bitmap_left,
    pen_y - slot->bitmap_top );

    
    pen_x += slot->advance.x >> 6;

    
    previous = glyph_index;
}

若干注解:

* 由于字距调整是由字形索引决定的,咱们须要显式转换咱们的字符代码到字形索引,而后调用FT_Load_Glyph而不是FT_Load_Char。

* 咱们使用一个名为use_kerning的变量,它的值为宏FT_HAS_KERNING的结果。当咱们知道字体face不含有字距调整信息,不调用FT_Get_kerning程序将执行得更快。

* 咱们在绘制一个新字形前移动笔位置。

* 咱们以值0初始化变量previous,这表示“字形缺失(missing glyph)”(在Postscript中,这用.notdef表示)。该字形也没有字距调整距离。

* 咱们不检查FT_Get_kerning返回得错误码。这是由于这个函数在错误发生时老是把delta置为(0,0)。

 

b.居中

 

     咱们的代码开始变得有趣了,但对普通应用来讲仍然有点太简单了。例如,笔的位置在咱们渲染前就决定了。一般,你要在计算文本的最终位置(居中,等)前布局它和测量它,或者执行自动换行。

     如今让咱们把文字渲染函数分解为两个大相径庭但连续的两部分:第一部分将在基线上定位每个字形图像,第二部分将渲染字形。咱们将看到,这有不少好处。

     咱们先保存每个独立的字形图像,以及它们在基线上面的位置。这能够经过以下的代码完成:

FT_GlyphSlot slot = face->glyph; 
FT_UInt glyph_index;
FT_Bool use_kerning;
FT_UInt previous;
int pen_x, pen_y, n;
FT_Glyph glyphs[MAX_GLYPHS]; 
FT_Vector pos [MAX_GLYPHS]; 
FT_UInt num_glyphs; 

... 初始化库 ...
... 建立face对象 ...
... 设置字符尺寸 ... 

pen_x = 0; 
pen_y = 0;
num_glyphs = 0;
use_kerning = FT_HAS_KERNING( face );
previous = 0;
for ( n = 0; n < num_chars; n++ )
{
        
        glyph_index = FT_Get_Char_Index( face, text[n] ); 

        
        if ( use_kerning && previous && glyph_index )
        {
        FT_Vector delta;
        FT_Get_Kerning( face, previous, glyph_index,
                FT_KERNING_DEFAULT, &delta );
                pen_x += delta.x >> 6;
                } 

        
        pos[num_glyphs].x = pen_x;
        pos[num_glyphs].y = pen_y; 

        
        error=FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT); 

        if ( error )
                continue;  

        
        error = FT_Get_Glyph( face->glyph, &glyphs[num_glyphs] );
        if ( error )
                continue;  

        
        pen_x += slot->advance.x >> 6; 

        
        previous = glyph_index; 

        
        num_glyphs++;
}

     相对于咱们以前的代码,这有轻微的变化:咱们从字形槽中提取每个字形图像,保存每个字形图像和它对应的位置在咱们的表中。

     注意pen_x包含字符串的总体前移值。如今咱们能够用一个很简单的函数计算字符串的边界框(bounding box),以下:

void compute_string_bbox( FT_BBox *abbox )
{
        FT_BBox bbox;
        
        bbox.xMin = bbox.yMin = 32000;
        bbox.xMax = bbox.yMax = -32000; 

        
        for ( n = 0; n < num_glyphs; n++ )
        {
                FT_BBox glyph_bbox;
                FT_Glyph_Get_CBox( glyphs[n], ft_glyph_bbox_pixels,
                        &glyph_bbox ); 

                glyph_bbox.xMin += pos[n].x;
                glyph_bbox.xMax += pos[n].x;
                glyph_bbox.yMin += pos[n].y;
                glyph_bbox.yMax += pos[n].y; 

                if ( glyph_bbox.xMin < bbox.xMin )
                        bbox.xMin = glyph_bbox.xMin; 

                if ( glyph_bbox.yMin < bbox.yMin )
                        bbox.yMin = glyph_bbox.yMin; 

                if ( glyph_bbox.xMax > bbox.xMax )
                        bbox.xMax = glyph_bbox.xMax; 

                if ( glyph_bbox.yMax > bbox.yMax )
                        bbox.yMax = glyph_bbox.yMax;
        } 

        
        if ( bbox.xMin > bbox.xMax )
        {
                bbox.xMin = 0;
                bbox.yMin = 0;
                bbox.xMax = 0;
                bbox.yMax = 0;
        } 

        
        *abbox = bbox;
}

     最终获得的边界框尺寸以整数象素的格式表示,而且能够随后在渲染字符串前用来计算最终的笔位置,以下:

string_width = string_bbox.xMax - string_bbox.xMin;
string_height = string_bbox.yMax - string_bbox.yMin; 


start_x = ( ( my_target_width - string_width ) / 2 ) * 64;
start_y = ( ( my_target_height - string_height ) / 2 ) * 64;
for ( n = 0; n < num_glyphs; n++ )
{
        FT_Glyph image;
        FT_Vector pen;
        image = glyphs[n]; 

        pen.x = start_x + pos[n].x;
        pen.y = start_y + pos[n].y;
        error = FT_Glyph_To_Bitmap(&image, FT_RENDER_MODE_NORMAL,
                &pen, 0 );
        if ( !error )
        {
                FT_BitmapGlyph bit = (FT_BitmapGlyph)image;
                my_draw_bitmap( bit->bitmap,
                        bit->left,
                        my_target_height - bit->top );
                FT_Done_Glyph( image );
        }
}

    一些说明:

* 笔位置以笛卡儿空间(例如,y向上)的形式表示。

* 咱们调用FT_Glyph_To_Bitmap时destroy参数设置为0(false),这是为了不破坏原始字形图像。在执行该调用后,新的字形位图经过image访问,而且它的类型转变为FT_BitmapGlyph。

* 当调用FT_Glyph_To_Bitmap时,咱们使用了平移。这能够确保位图字形对象的左区域和上区域已经被设置为笛卡儿空间中的正确的象素坐标。

* 固然,在渲染前咱们仍然须要把象素坐标从笛卡儿空间转换到设备空间。所以在调用my_draw_bitmap前要先计算my_target_height – bitmap->top。

     相同的循环能够用来把字符串渲染到咱们的显示面(surface)任意位置,而不须要每一次都从新装载咱们的字形图像。咱们也能够决定实现自动换行或者只是绘制。

 

5.高级文本渲染:变换 + 居中 + 字距调整

 

     如今咱们将修改咱们的代码,以即可以容易地变换已渲染的字符串,例如旋转它。咱们将以实行少量小改进开始:

 

a.打包而后平移字形

     咱们先把与一个字形图像相关的信息打包到一个结构体,而不是并行的数组。所以咱们定义下面的结构体类型:

typedef struct TGlyph_
{
        FT_UInt index; 
        FT_Vector pos; 
        FT_Glyph image; 
} TGlyph, *PGlyph;

     咱们在装载每个字形图像过程当中,在把它装载它在基线所在位置后便直接平移它。咱们将看到,这有若干好处。咱们的字形序列装载其于是变成:

FT_GlyphSlot slot = face->glyph; 
FT_UInt glyph_index;
FT_Bool use_kerning;
FT_UInt previous;
int pen_x, pen_y, n;
TGlyph glyphs[MAX_GLYPHS]; 
PGlyph glyph; 
FT_UInt num_glyphs; 

... 初始化库 ...
... 建立face对象 ...
... 设置字符尺寸 ... 

pen_x = 0; 
pen_y = 0;
num_glyphs = 0;
use_kerning = FT_HAS_KERNING( face );
previous = 0;
glyph = glyphs;
for ( n = 0; n < num_chars; n++ )
{
        glyph->index = FT_Get_Char_Index( face, text[n] );
        if ( use_kerning && previous && glyph->index )
        {
                FT_Vector delta;
                FT_Get_Kerning( face, previous, glyph->index,
                        FT_KERNING_MODE_DEFAULT, &delta ); 

                pen_x += delta.x >> 6;
        } 

        
        glyph->pos.x = pen_x;
        glyph->pos.y = pen_y;
        error = FT_Load_Glyph(face,glyph_index,FT_LOAD_DEFAULT);
        if ( error ) continue; 

        error = FT_Get_Glyph( face->glyph, &glyph->image );
        if ( error ) continue; 

        
        FT_Glyph_Transform( glyph->image, 0, &glyph->pos );
        pen_x += slot->advance.x >> 6;
        previous = glyph->index; 

        
        glyph++;
} 


num_glyphs = glyph - glyphs;

     注意,这个时候平移字形有若干好处。第一是当咱们计算字符串的边界框时不须要平移字形bbox。代码将会变成这样:

void compute_string_bbox( FT_BBox *abbox )
{
        FT_BBox bbox;
        bbox.xMin = bbox.yMin = 32000;
        bbox.xMax = bbox.yMax = -32000;
        for ( n = 0; n < num_glyphs; n++ )
        {
                FT_BBox glyph_bbox;
                FT_Glyph_Get_CBox( glyphs[n], &glyph_bbox );
                if (glyph_bbox.xMin < bbox.xMin)
                        bbox.xMin = glyph_bbox.xMin; 

                if (glyph_bbox.yMin < bbox.yMin)
                        bbox.yMin = glyph_bbox.yMin; 

                if (glyph_bbox.xMax > bbox.xMax)
                        bbox.xMax = glyph_bbox.xMax; 

                if (glyph_bbox.yMax > bbox.yMax)
                        bbox.yMax = glyph_bbox.yMax;
        } 

        if ( bbox.xMin > bbox.xMax )
        {
                bbox.xMin = 0;
                bbox.yMin = 0;
                bbox.xMax = 0;
                bbox.yMax = 0;
        } 

        *abbox = bbox;
}

     更详细描述:compute_string_bbox函数如今能够计算一个已转换的字形字符串的边界框。例如,咱们能够作以下的事情:

FT_BBox bbox;
FT_Matrix matrix;
FT_Vector delta; 

... 装载字形序列 ...
... 设置 "matrix" 和 "delta" ... 


for ( n = 0; n < num_glyphs; n++ )
        FT_Glyph_Transform( glyphs[n].image, &matrix, &delta ); 


compute_string_bbox( &bbox );

b.渲染一个已变换的字形序列

 

     不管如何,若是咱们想重用字形来以不一样的角度或变换方式绘制字符串,直接变换序列中的字形都不是一个好主意。更好的方法是在字形被渲染前执行放射变换,以下面的代码所示:

FT_Vector start;
FT_Matrix transform; 


compute_string_bbox( &string_bbox ); 


string_width = (string_bbox.xMax - string_bbox.xMin) / 64;
string_height = (string_bbox.yMax - string_bbox.yMin) / 64; 


start.x = ( ( my_target_width - string_width ) / 2 ) * 64;
start.y = ( ( my_target_height - string_height ) / 2 ) * 64; 


matrix.xx = (FT_Fixed)( cos( angle ) * 0x10000L );
matrix.xy = (FT_Fixed)(-sin( angle ) * 0x10000L );
matrix.yx = (FT_Fixed)( sin( angle ) * 0x10000L );
matrix.yy = (FT_Fixed)( cos( angle ) * 0x10000L ); 

for ( n = 0; n < num_glyphs; n++ )
{
        FT_Glyph image;
        FT_Vector pen;
        FT_BBox bbox; 

        
        error = FT_Glyph_Copy( glyphs[n].image, &image );
        if ( error ) continue; 

        
        FT_Glyph_Transform( image, &matrix, &start ); 

        
        
        FT_Glyph_Get_CBox( image, ft_glyph_bbox_pixels, &bbox );
        if ( bbox.xMax <= 0 || bbox.xMin >= my_target_width ||
                bbox.yMax <= 0 || bbox.yMin >= my_target_height )
                continue; 

        
        error = FT_Glyph_To_Bitmap(
                &image,
                FT_RENDER_MODE_NORMAL,
                0, 
                1 ); 
        if ( !error )
        {
                FT_BitmapGlyph bit = (FT_BitmapGlyph)image;
                my_draw_bitmap( bitmap->bitmap,
                bitmap->left,
                my_target_height - bitmap->top );
                FT_Done_Glyph( image );
        }
}

     这份代码相对于原始版本有少量改变:

* 咱们没改变原始的字形图像,而是变换该字形图像的拷贝。

* 咱们执行“剪取”操做以处理渲染和绘制的字形不在咱们的目标表面(surface)的状况。

* 当调用FT_Glyhp_To_Bitmap时,咱们老是销毁字形图像的拷贝,这是为了销毁已变换的图像。注意,即便当这个函数返回错误码,该图像依然会被销毁(这就是为何FT_Done_Glyph只在复合语句中被调用的缘由)。

* 平移字形序列到起始笔位置集成到FT_Glyph_Transform函数,而不是FT_Glyph_To_Bitmap函数。

     能够屡次调用这个函数以渲染字符串到不一样角度的,或者甚至改变计算start的方法以移动它到另外的地方。

     这份代码是FreeType 2示范程序ftstring.c的基础。它能够被简单地扩展,在第一部发完成高级文本布局或自动换行,而第二部分不需改变。

     不管如何,要注意一般的实现会使用一个字形缓冲以减小内存消耗。据个例子,让咱们假定咱们的字符串是“FreeType”。咱们将在咱们的表中保存字母‘e’的三个相同的字形图像,这不是最佳的(特别是当你遇到更长的字符串或整个页面时)。

 

6.以预设字体单位的格式访问度量,而且伸缩它们

 

     可伸缩的字体格式一般会为字体face中的每个字形保存一份矢量图像,该矢量图像称为轮廓。每个轮廓都定义在一个抽象的网格中,该网格被称为预设空间(design space),其坐标以名义上(nominal)的字体单位(font unit)表示。当装载一个字形图像时,字体驱动器一般会依照FT_Size对象所指定的当前字符象素尺寸把轮廓伸缩到设备空间。字体驱动器也能修改伸缩过的轮廓以大大地改善它在基于象素的表面(surface)中显示的效果。修改动做一般称为hinting或网格对齐。

     这一章描述了如何把预设坐标伸缩到设备空间,以及如何读取字形轮廓和如何获取以预设字体单位格式表示的度量。这对许多事情来讲都是重要的:

* 真正的所见即所得文字排版

* 为了字体转换或者分析的目的而访问字体内容

a.伸缩距离到设备空间

     咱们使用一个简单的伸缩变换把预设坐标伸缩到设备空间。变换系数借助字符象素尺寸来计算:

 

Device_x = design_x * x_scale
Device_y = design_y * y_scale
X_scale = pixel_size_x / EM_size
Y_scale = pixel_size_y / EM_size

     这里,值EM_Size是因字体而异的,而且对应预设空间的一个抽象矩形(称为EM)的大小。字体设计者使用该矩形建立字形图像。EM_Size以字体单元的形式表示。对于可伸缩字体格式,能够经过face->unix_per_EM直接访问。你应该使用FT_IS_SCALABLE宏检查某个字体face是否包含可伸缩字形图像,当包含时该宏返回true。

 

     当你调用函数FT_Set_Pixel_Sizes,你便指定了pixel_size_x和pixel_size_y的值。FreeType库将会当即使用该值计算x_scale和y_scale的值。

     当你调用函数FT_Set_Char_Size,你便以物理点的形式指定了字符尺寸。FreeType库将会使用该值和设备的解析度来计算字符象素尺寸和相应的比例因子。

     注意,在调用上面说起的两个函数后,你能够经过访问face->size->metrices结构的字段获得字符象素尺寸和比例因子的值。这些字段是:

 

X_ppem

 

这个字段表明了“每个EM的x方向象素”,这是以整数象素表示EM矩形的水平尺寸,也是字符水平象素尺寸,即上面例子所称的pixel_size_x。

y_ppem

这个字段表明了“每个EM的y方向象素”,这是以整数象素表示EM矩形的垂直尺寸,也是字符垂直象素尺寸,即上面例子所称的pixel_size_y。

X_scale

这是一个16.16固定浮点比例,用来把水平距离从预设空间直接伸缩到1/64设备象素。

y_scale

这是一个16.16固定浮点比例,用来把垂直距离从预设空间直接伸缩到1/64设备象素。

 

     你能够借助FT_MulFix函数直接伸缩一个以26.6象素格式表示的距离,以下所示:

pixels_x=FT_MulFix(design_x,face->size->metrics.x_scale);
pixels_y=FT_MulFix(design_y,face->size->metrics.y_scale); 

固然,你也可使用双精度浮点数更精确地伸缩该值:
FT_Size_Metrics* metrics = &face->size->metrics;  

double pixels_x, pixels_y;
double em_size, x_scale, y_scale; 


em_size = 1.0 * face->units_per_EM;
x_scale = metrics->x_ppem / em_size;
y_scale = metrics->y_ppem / em_size; 


pixels_x = design_x * x_scale;
pixels_y = design_y * y_scale;

b.访问预设度量(字形的和全局的)

 

     你能够以字体单位的格式访问字形度量,只要在调用FT_Load_Glyph或FT_Load_Char时简单地指定FT_LOAD_NO_SCALE位标志即可以了。度量返回在face->glyph_metrics,而且所有都以字体单位的格式表示。

     你可使用FT_KERNING_MODE_UNSCALED模式访问未伸缩的字距调整数据。

     最后,FT_Face句柄的字段包含少数几个全局度量,咱们已经在本部分的第三章叙述过了。

结论

     这是FreeType 2教程第二部分的结尾。如今你能够访问字形度量,管理字形图像,以及更巧妙地渲染文字(字距调整,测量,变换和缓冲)。

     如今你有了足够的知识可以以FreeType2为基础构建一个至关好的文字服务,并且要是你愿意,你能够在这里止步了。

相关文章
相关标签/搜索