FreeType之字形分析(二)

来源:互联网 发布:linux 断电 丢失 文件 编辑:程序博客网 时间:2024/06/05 20:57

 上接 http://blog.csdn.net/u013945158/article/details/77776910

==========================================

1.1   测量字形图像

通过API FT_Glyph_Get_CBox()可以获取任意字形图像(无论有无缩放)的控制(边界)box。(翻译的不太好,理解时候可以想象一下上篇中字形的描述指标附图。)

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

为了更好的理解,还是把图重新贴一遍,如下:


  typedef struct  FT_BBox_

  {

    FT_Pos xMin, yMin;

    FT_Pos xMax, yMax;

 

 } FT_BBox;

源码中的FT_BBox如上,四个值可以理解为字符框的对角(左下角和右上角)的坐标。左下角(xMin,yMin),右上角(xMax,yMax),且坐标的原点就是图中的Origin。当yMin是负值时候,这个值给出了字形的descender量,可以理解为字形原点以下的部分;另外科普一下descender的概念:小写字体中超出x字母高度部分。

如果字形是以flag FT_LOAD_NO_SCALE加载的,则FT_Glyph_Get_CBox中bbox mode必须被设置为FT_GLYPH_BBOX_UNSCALED,来获取无缩放的26.6像素格式的bbox.

  typedef enum FT_Glyph_BBox_Mode_

  {

    FT_GLYPH_BBOX_UNSCALED  = 0,

    FT_GLYPH_BBOX_SUBPIXELS = 0,

    FT_GLYPH_BBOX_GRIDFIT   = 1,    //网格拟合

    FT_GLYPH_BBOX_TRUNCATE  = 2,    //以像素单位即整数形式

    FT_GLYPH_BBOX_PIXELS    = 3     //网格拟合的像素坐标

 } FT_Glyph_BBox_Mode;

请注意,API获取的bbox是唯一的,可用于计算字符的宽度和高度:

width  = bbox.xMax - bbox.xMin;

height = bbox.yMax - bbox.yMin;

对于26.6坐标系,如果使用FT_GLYPH_BBOX_GRIDFIT作为bbox mode,坐标系也会被网格化,bbox值会有如下变化:

bbox.xMin= FLOOR( bbox.xMin )

bbox.yMin= FLOOR( bbox.yMin )

bbox.xMax= CEILING( bbox.xMax )

bbox.yMax = CEILING( bbox.yMax )

另外,有一个类似的API:FT_Outline_Get_BBox(). 这个API的代价是,执行的比较慢。在非旋转字符轮廓获取时候,不会用到这个API。

 

1.2   转换字形图为位图

使用之前的API,我们能够方便的对字形图像进行缓存和变换,而字符的最终显示,往往需要转换成位图。所幸,Freetype为我们提供了方便的API来完成这一动作:FT_Glyph_To_Bitmap()

FT_Vector  origin;

origin.x = 32; /* 1/2 pixel in 26.6 format */

origin.y = 0;

error = FT_Glyph_To_Bitmap(

          &glyph,

          render_mode,

          &origin,

          1 );          /* destroy original image == true */

参数1:glyph. 是一个字形的句柄。当函数被调用的时候,会去访问一个字形对象。呼叫结束后,这个handle指向一个新的包含一个渲染后的位图的字形对象。

参数2:标准渲染类型。用来标示我们需要什么样的位图的。

          FT_RENDER_MODE_DEFAULT:8位抗锯齿的位图

   FT_RENDER_MODE_MONO:1位单色位图。

参数3:一个二维向量。用于在字形变换前移动原始字形图像。调用结束后,源字形图像恢复原来位置。如果不需要渲染前不需要移动变换,传NULL。

参数4:bool类型标志位,用于表明源字形图像是否被销毁。False:不销毁;true:销毁。

 

新的字形对象包含一个位图,此时必须使用FT_BitmapGlyph类型对该位图进行强制类型变换才能够方位其内容。

主要成员:

left: 字符位图左边到Origin(0,0)的水平距离,整数像素坐标。

top: 字符位图顶边到原点的垂直距离。

bitmap: 字形对象的bitmap描述子。

 

2.      全局字形指标

不像字形指标那样,全局字形指标用于描述整个字体surface的距离和特征。可以使用26.6像素格式或者未缩放字体单元的可扩展格式。

2.1   设计全局字形指标

对于可扩展格式,所有全局指标被表示为字体单元,以便后续缩放到设备空间,根据本文档该部分最后描述的规则。这些属性可以通过FT_Face句柄来直接访问。

在使用这几个变量的时候,请检查字体face的格式是否是可扩展的。

units_per_EM:不懂。。。囧。字体face的EM块的大小。如本节最后所描述,该值被用于可扩展格式字形来适应设备像素坐标。该值可变,2048 for TrueType,1000 for Type1或CFF。

bbox:全局边界盒,被定义为能够包络字形face的最小矩形。

Ascender:字符最高点到水平基线的距离(Wiki上说是高于小写字母x的那一段)。

Descender: 字符最低点到水平基线的距离,一般负值

Height:基线到基线的距离???一般大于ascender和decender的绝对值的和。

max_advance_width:这个域表明字库中所有字形水平焦点最大advance。可用于快速计算文本字串的最大advance宽度。

max_advance_height 同上,区别在于用在垂直布局的文本中。

underline_position:用于下划线文本的下划线相对基线的垂直距离,如果低于基线,为负值。

underline_thickness:下划线宽度。

 

2.2   全局字形指标缩放

如上所述的一些全局指标,每一个尺寸的对象均有一个可缩放的版本。这些指标均可以通过face->size->metrics结构体(FT_Size_Metrics类型)来访问。这些指标值是没有经过网格拟合的,且独立于任何微调过程。换句话说就是,在像素级获取附加指标不会依赖于微调过程。它们也是用26.6像素格式表述,但是因为历史原因被取整。

Ascender 原始ascender缩放版本 进位取整

Descender 原始descender缩放版本 舍位取整

Height 原始height缩放版本(从一个基线到下一个基线的垂直距离)。可能是这个结构中你唯一会使用到的域,取整

Max_advance 原始设计最大advance的缩放版,取整

 

2.3   字距

字距是用来调整文本字符串中两个相邻字形图像距离的操作,以改善文本整体外观。例如,字符A后面跟这一个字符V,因此这两个字形中间的字距可以稍微减小以避免附加的对角线空白。

 

理论上,字距可以同时发生在两个字形水平和垂直方向上;但是,在实际应用中往往仅发生在一个方向上。

 

不是所有的字体都含有字距信息,不是所有的字间距格式都被Freetype支持。比如,对于TrueType字体,Freetype的API仅仅支持通过’kern’表来访问字间距。OpenType字间距通过’GPOS’的表是不支持的。你需要更高级的库,比如HarfBuzz, Pango, ICU,因为GPOS字间距需要上下文字符串处理。

 

有些时候,字体文件会通过关联文件来包含字体的指标,这些关联文件包含字间距等指标,但是不包含字形图像。比如,Type1格式中,字形图像存储在以扩展名为.pfa或者.pfb的文件中,而字形指标存储在以扩展名为.afm或者.pfm的文件中。

 

Freetype 2允许我们处理这种情况,通过提供FT_Attach_File和FT_Attach_Stream这样的APIs.这两个API均被用于加载附加的字形指标到face对象。如下例子:

error = FT_New_Face( library, "/usr/share/fonts/cour.pfb",0, &face );                

if ( error ) { ... }

error = FT_Attach_File( face, "/usr/share/fonts/cour.afm" );

if ( error )

{ ... could not read kerning and additional metrics ... }

FT_Attach_Stream的用法类似FT_Attach_File,只不过两个的区别是后者通过C字符串的文档路径来确定字形文件,而前者通过FT_Stream句柄来加载字形指标。

 

上述API具有很强的通用性,可以被用于加载各种附加的信息给指定的face对象。附加内容的属性是完全被定义的。

 

2.4   字间距的访问

Freetype2允许通过FT_Get_KerningAPI来访问两个字形之间的字间距。

FT_Vector  kerning;

...

error = FT_Get_Kerning( face,          /* handle to face object */

                        left,          /* left glyph index      */

                        right,         /* right glyph index     */

                        kerning_mode,  /* kerning mode     */

                        &kerning );    /* target vector         */

这支API接收一个face对象的句柄,左右两个字形索引是必须的,获取的就是这两个字形之间的间距,kerning mode同样需要,最后一个字间距引用变量需要传入来接收获取的字间距值,是一个vector。

 

Kerning mode和上面章节中讲到的bbox mode非常相似,是一个枚举值,用来描述字符间距在vector中如何表示。

FT_KERNING_DEFAULT  26.6格式栅格化像素值(64的倍数),对于缩放格式,意味着缩放加取整

FT_KERNING_UNFITTED 26.6格式未栅格化像素值,缩放,无取整

FT_KERNING_UNSCALED 返回design字间距,未缩放。后续可以使用下面章节介绍的计算方法来缩放。

 

3.      简单的文本渲染:字距和居中对齐

为了展示我们所学东西的效果,我们这里展示一个修改前文代码来渲染一个文本字符串,增强支持字距和延时呈现。

3.1   字距支持

我们依然考虑向Latin这样的左到右的的文本。我们在code中简单的获取两个字符之间的间距,然后适当的调整pen的位置。

FT_GlyphSlot  slot = face->glyph;  /* a small shortcut */

FT_UInt       glyph_index;

FT_Bool       use_kerning;

FT_UInt       previous;

int           pen_x, pen_y, n;

 

... initialize library ...

... create face object ...

... set character size ...

 

pen_x = 300;

pen_y = 200;

 

use_kerning = FT_HAS_KERNING( face );   //note2

previous    = 0;     //note4

 

for ( n = 0; n < num_chars; n++ )

{

  /* convert character code to glyph index */

  glyph_index = FT_Get_Char_Index( face, text[n] );  //note1

 

  /* retrieve kerning distance and move pen position */

  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;

  }

 

  /* load glyph image into the slot (erase previous one) */

  error = FT_Load_Glyph( face,glyph_index, FT_LOAD_RENDER );

  if ( error )

    continue;  /* ignore errors */

 

  /* now draw to our target surface */

  my_draw_bitmap( &slot->bitmap,

                  pen_x + slot->bitmap_left,

                  pen_y - slot->bitmap_top );

 

  /* increment pen position */

  pen_x += slot->advance.x >> 6;     //2^6=64  note3

 

  /* record current glyph index */

  previous = glyph_index;

}

Note:

1.      由于字间距被字形指标决定,所有我们需要将我们的字符代码显式转换为字形索引,然后再调用FT_Load_Glyph而非FT_Load_Char。因为字间距的获取需要通过字形索引。

2.      使用bool类型use_kerning来check字库是否支持kerning,这是宏定义FT_HAS_KERNING的值,在已知字库不包含kerning时候不去比调用FT_Get_Kerning,相对会有比较快的调用速度。

3.      绘制下一个字符之前,移动pen的位置

4.      我们初始化previous变量的值为0,它对应于正在被丢掉的字形(使用index来标记)。

5.      我们不对FT_Get_Kerning进行返回错误码进行检验,因为当有错误发生时,这个函数往往设置delta的内容为(0,0)。

 

3.2   居中

示例代码逐渐变得有意思起来,但是对于实际应用依然太简单。比如,pen的位置是在渲染之前确定的;正常而言,在计算最终位置或者执行自动换行等操作之前需要确定文本的轮廓并测量。

下面我们分解我们的文本渲染函数为两部分,两部分连续:第一部分,将单个字形图像定位到基线上,而第二部分渲染字形。如我们看到,他有很多优点。

开始为存储单个字符图像,并定位到基线上,

FT_GlyphSlot  slot = face->glyph;   /* a small shortcut */

FT_UInt       glyph_index;

FT_Bool       use_kerning;

FT_UInt       previous;

int           pen_x, pen_y, n;

 

FT_Glyph      glyphs[MAX_GLYPHS];   /* glyph image    */

FT_Vector     pos   [MAX_GLYPHS];   /* glyph position */

FT_UInt       num_glyphs;

 

 

... initialize library ...

... create face object ...

... set character size ...

 

pen_x = 0;   /* start at (0,0) */

pen_y = 0;

 

num_glyphs  = 0;

use_kerning = FT_HAS_KERNING( face );

previous    = 0;

 

for ( n = 0; n < num_chars; n++ )

{

  /* convert character code to glyph index */

  glyph_index = FT_Get_Char_Index( face, text[n] );

 

  /* retrieve kerning distance and move pen position */

  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;

  }

 

  /* store current pen position */

  pos[num_glyphs].x = pen_x;

  pos[num_glyphs].y = pen_y;

 

  /* load glyph image into the slot without rendering */

  error = FT_Load_Glyph( face, glyph_index, FT_LOAD_DEFAULT );

  if ( error )

    continue;  /* ignore errors, jump to next glyph */

 

  /* extract glyph image and store it in our table */

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

  if ( error )

    continue;  /* ignore errors, jump to next glyph */

 

  /* increment pen position */

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

 

  /* record current glyph index */

  previous = glyph_index;

 

  /* increment number of glyphs */

  num_glyphs++;

}

这相对于我们之前的code有小幅改动;我们从slot中提取每个字形图像,然后存储,同步存储字形的位置。

需要注意,pen_x存储的是所有文本字符串字形的总advance;现在我们可以通过一个函数计算文本字符串的轮廓box。

void  compute_string_bbox( FT_BBox  *abbox )

{

  FT_BBox  bbox;

  FT_BBox  glyph_bbox;

 

 

  /* initialize string bbox to "empty" values */

  bbox.xMin = bbox.yMin =  32000;

  bbox.xMax = bbox.yMax = -32000;

 

  /* for each glyph image, compute its bounding box, */

  /* translate it, and grow the string bbox          */

  for ( n = 0; n < num_glyphs; n++ )

  {

    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;

  }

 

  /* check that we really grew the string bbox */

  if ( bbox.xMin > bbox.xMax )

  {

    bbox.xMin = 0;

    bbox.yMin = 0;

    bbox.xMax = 0;

    bbox.yMax = 0;

  }

 

  /* return string bbox */

  *abbox = bbox;

}

该函数的计算结果轮廓box格式为整数像素,可以被用来计算渲染之前的最终pen的位置。

一般地,上述函数并没有计算出字符串的外部轮廓box。一旦涉及字符微调,字形尺寸就必须从结果轮廓中得出。 对于反锯齿的像素图,FT_Outline_Get_BBox然后产生正确的结果。 如果需要1位单色位图,甚至有必要实际渲染字形,因为从轮廓转换到位图的规则也可以通过微调指令进行控制。

/* compute string dimensions in integer pixels */

string_width  = string_bbox.xMax - string_bbox.xMin;

string_height = string_bbox.yMax - string_bbox.yMin;

 

/* compute start pen position in 26.6 Cartesian pixels */

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 );

  }

}

备注:

1.      Pen的位置坐标是在笛卡尔坐标系中描述的(y轴向上)

2.      我们调用FT_Glyph_To_Bitmap,且带销毁参数为0(false),以避免破坏原始的字形图像。新的字形位图通过Image来访问,并且强制转换为FT_BitmapGlyph。

3.      变换发生在调用FT_Glyph_To_Bitmap的时候。这回确保自行图像的位图的左上角在笛卡尔坐标系中被设置到正确像素位置。

4.      当然,我们仍需要将像素坐标从笛卡尔坐标系转换到设备坐标系中,因此,在调用 my_draw_bitmap 的时候设置my_target_height -bitmap->top.

 

同样的循环可以在display surface的任何位置渲染,而不用重新加载字形图像。



==============================================================

笔者水平有限,如果错误,还请包含指正,谢谢!

待续。。。



==============================================================

笔者水平有限,如果错误,还请包含指正,谢谢!

待续。。。