第二部分 管理字形
介绍
* 检索字形度量
* 便捷地管理字形图像 * 检索全局度量(包括字距调整) * 渲染一个简单的字符串(采用字距调整) * 渲染一个居中的字符串(采用字距调整) * 渲染一个经变换的字符串(采用居中) * 在需要时以预设字体单位的格式获取度量,以及把它们缩放到设备空间
1.字形度量
Width
这是字形图像的边框的宽度。它与排列方向无关。 Height 这是字形图像的边框的高度。它与排列方向无关。千万不要把它和FT_Size_Metrics的height字段混淆。 horiBearingX 用于水平文本排列,这是从当前光标位置到字形图像最左边的边界的水平距离。 horiBearingY 用于水平文本排列,这是从当前光标位置(位于基线)到字形图像最上边的边界的水平距离。 horiAdvance 用于水平文本排列,当字形作为字符串的一部分被绘制时,这用来增加笔位置的水平距离。 vertBearingX 用于垂直文本排列,这是从当前光标位置到字形图像最左边的边框的垂直距离。 vertBearingY 用于垂直文本排列,这是从当前光标位置(位于基线)到字形图像最上边的边框的垂直距离。 vertAdvance 用于垂直文本排列,当字形作为字符串的一部分被绘制时,这用来增加笔位置的垂直距离。 注意:因为不是所有的字体都包含垂直度量,当FT_HAS_VERTICAL为假时,vertBearingX,vertBearingY和vertAdvance的值是不可靠的。
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包含垂直度量时这个值才是可靠的。
2.管理字形图像
#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。
b.变换和复制字形图像
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 );
c.测量字形图像
FT_BBox bbox; ... FT_Glyph_Get_CBox( glyph, bbox_mode, &bbox );
width = bbox.xMax - bbox.xMin; height = bbox.yMax - bbox.yMin;
bbox.xMin = FLOOR( bbox.xMin ) bbox.yMin = FLOOR( bbox.yMin ) bbox.xMax = CEILING( bbox.xMax ) bbox.yMax = CEILING( bbox.yMax )
d.转换字形图像为位图
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,源字形对象不会被销毁,但是它的句柄丢失了(客户应用程序需要自己保留句柄)。
Left
类似于字形槽的bitmap_left字段。这是字形原点(0,0)到字形位图最左边象素的水平距离。它以整数象素的形式表示。
Top
类似于字形槽的bitmap_top字段。它是字形原点(0,0)到字形位图最高象素之间的垂直距离(更精确来说,到位图最上面的象素)。这个距离以整数象素的形式表示,并且y轴向上为正。Bitmap
这是一个字形对象的位图描述符,就像字形槽的bitmap字段。
3.全局字形度量
a.预设全局度量
如果是这样,你就可以访问全局预设度量了,如下:
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
当显示或者渲染下划线文本时,这个值等于下划线的垂直宽度。
b.伸缩的全局度量
注意这些值等于预设全局变量的伸缩版本,但没有做舍入或网格对齐。它们也完全独立于任何hinting处理。换句话说,不要依靠它们来获取象素级别的精确度量。它们以26.6象素格式表示。
ascender
原始预设ascender的伸缩版本。
descender
原始预设ascender的伸缩版本。
height
原始预设文本高度(text_height)的伸缩版本。这可能是这个结构中你真正会用到的字段。
max_advance
原始预设最大推进的伸缩版本。
c.字距调整
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 ) { ... 没能读取字距调整和附加的度量 ... }
最后,文件附加API是非常通用的,可以用来从指定的face中装载不同类型的附加信息。附加内容的种类完全是因字体格式而异的。
FT_Vector kerning; ... error = FT_Get_Kerning( face, left, right, kerning_mode, &kerning );
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++; }
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。
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;
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; }
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函数。
6.以预设字体单位的格式访问度量,并且伸缩它们
* 真正的所见即所得文字排版
* 为了字体转换或者分析的目的而访问字体内容
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
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设备象素。
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.访问预设度量(字形的和全局的)
结论