[Freetype]字體渲染FreeType源碼解析總結(jié)

作者 ---- 小老虎(Numberwolf)

Github: https://github.com/numberwolf

多媒體/音視頻/圖形/播放器技術(shù)/跨平臺(tái)技術(shù)架構(gòu)


O 背景

我們常常會(huì)在 圖片/視頻等 二維多媒體素材中看到有文字的存在,同時(shí)也會(huì)在視頻剪輯工具中給視頻嵌入文字字幕操作。那么這些都是如何實(shí)現(xiàn)的呢?

—— 看起來(lái)很簡(jiǎn)單的東西,往往比想象中要復(fù)雜的多。

1 字體渲染基礎(chǔ)原理

我們常見的字體嵌入圖像中,往往需要準(zhǔn)備一個(gè)TTF等格式的字體文件。(如果不選擇往往是默認(rèn)操作系統(tǒng)的字體)

而這些被設(shè)計(jì)好的字體,最終是以點(diǎn)陣的方式 存儲(chǔ)于TTF字體文件中。也就是說(shuō),每一個(gè)字符都會(huì)被存入其中,如果當(dāng)前字體設(shè)計(jì)不存在的文字,那么最終可能會(huì)以亂碼的形式存在。


FreeType初始化

int frontSize = 36; // pixel
FT_Library ft;
FT_Face face;
if (FT_Init_FreeType(&ft)) {
    printf("ERROR::FREETYPE: FT_New_Memory_Face Could not init FreeType Library\n");
}

if (FT_New_Memory_Face(ft, (const FT_Byte *)_fontData, _dataSize, 0, &face)) {
    printf("ERROR::FREETYPE: FT_New_Memory_Face Failed to load font\n");
}

FT_Set_Pixel_Sizes(face, 0, frontSize);

FT_Face 為索引串講數(shù)據(jù)結(jié)構(gòu)

FT_Face類

一個(gè)外觀對(duì)象對(duì)應(yīng)單個(gè)字體外觀,即一個(gè)特定風(fēng)格的特定外觀類型,例如Arial和Arial Italic是兩個(gè)不同的外觀。

一個(gè)外觀對(duì)象通常使用FT_New_Face()來(lái)創(chuàng)建,這個(gè)函數(shù)接受如下參數(shù):一個(gè)FT_Library句柄,一個(gè)表示字體文件的C文件路徑名,一個(gè)決定從文件中裝載外觀的索引(一個(gè)文件中可能有不同的外觀),和FT_Face句柄的地址,它返回一個(gè)錯(cuò)誤碼。

FT_Error FT_New_Face( FT_Library library,
                        const char* filepathname,
                        FT_Long face_index,
                        FT_Face* face);

函數(shù)調(diào)用成功,返回0,face參數(shù)將被設(shè)置成一個(gè)非NULL值。

外觀對(duì)象包含一些用來(lái)描述全局字體數(shù)據(jù)的屬性,可以被客戶程序直接訪問(wèn)。例如外觀中字形的數(shù)量、外觀家族的名稱、風(fēng)格名稱、EM大小等,詳見FT_FaceRec定義。

typedef struct  FT_FaceRec_
{
    FT_Long           num_faces;
    FT_Long           face_index;

    FT_Long           face_flags;
    FT_Long           style_flags;

    FT_Long           num_glyphs; // font total count

    FT_String*        family_name;
    FT_String*        style_name;

    FT_Int            num_fixed_sizes;
    FT_Bitmap_Size*   available_sizes;

    FT_Int            num_charmaps;
    FT_CharMap*       charmaps;

    FT_Generic        generic;

    /*# The following member variables (down to `underline_thickness`) */
    /*# are only relevant to scalable outlines; cf. @FT_Bitmap_Size    */
    /*# for bitmap fonts.                                              */
    FT_BBox           bbox;

    FT_UShort         units_per_EM;
    FT_Short          ascender;
    FT_Short          descender;
    FT_Short          height;

    FT_Short          max_advance_width;
    FT_Short          max_advance_height;

    FT_Short          underline_position;
    FT_Short          underline_thickness;

    // 字形槽的目的是提供一個(gè)地方,可以很容易地一個(gè)個(gè)地裝入字形映象,而不管它的格式(位圖、向量輪廓或其他)
    FT_GlyphSlot      glyph; // glyph link list
    // 每個(gè)FT_Face對(duì)象都有一個(gè)或多個(gè)FT_Size對(duì)象,一個(gè)尺寸對(duì)象用來(lái)存放指定字符寬度和高度的特定數(shù)據(jù),每個(gè)新創(chuàng)建的外觀對(duì)象有一個(gè)尺寸,可以通過(guò)face->size直接訪問(wèn)
    FT_Size           size;
    ...
} FT_FaceRec

可以觀察到 FT_GlyphSlotRec是以一個(gè) 鏈表的形式存儲(chǔ)的數(shù)據(jù)

typedef struct  FT_GlyphSlotRec_
{
    FT_Library        library;
    FT_Face           face;
    FT_GlyphSlot      next; // linklist node 字行槽是個(gè)鏈表的數(shù)據(jù)結(jié)構(gòu)
    FT_UInt           glyph_index; /* new in 2.10; was reserved previously */
    FT_Generic        generic;
    ...
} FT_GlyphSlotRec

每個(gè)glyph字符在字槽對(duì)應(yīng)一個(gè)索引 glyph_index

常使用方法來(lái)確定某個(gè)字符在 glyph 鏈表是否存在,查找對(duì)應(yīng) glyph_index

wchar_t t = str[n]; // 確定字符

FT_UInt  glyph_index;

glyph_index = FT_Get_Char_Index(face, t); // 確定字符槽索引

FT_Error error = FT_Load_Glyph( face, glyph_index, FT_LOAD_DEFAULT ); // 加載
if ( error ) continue;

error = FT_Render_Glyph( face->glyph, ft_render_mode_normal ); // 渲染
if ( error ) continue;

FT_CharMap類

FT_CharMap類型用來(lái)作為一個(gè)字符地圖對(duì)象的句柄,一個(gè)字符圖是一種表或字典,用來(lái)將字符碼從某種編碼轉(zhuǎn)換成字體的字形索引。

單個(gè)外觀可能包含若干字符圖,每個(gè)對(duì)應(yīng)一個(gè)指定的字符指令系統(tǒng),例如Unicode、Apple Roman、Windows codepages等等。

每個(gè)FT_CharMap對(duì)象包含一個(gè)platform和encoding屬性,用來(lái)標(biāo)識(shí)它對(duì)應(yīng)的字符指令系統(tǒng)。每個(gè)字體格式都提供它們自己的FT_CharMapRec的繼承類型并實(shí)現(xiàn)它們。

一個(gè)face對(duì)象包含一個(gè)或多個(gè)字符表(charmap),字符表是用來(lái)轉(zhuǎn)換字符碼到字形索引的。例如,很多TrueType字體包含兩個(gè)字符表,一個(gè)用來(lái)轉(zhuǎn)換Unicode字符碼到字形索引,另一個(gè)用來(lái)轉(zhuǎn)換Apple Roman編碼到字形索引。這樣的字體既可以用在Windows(使用Unicode)和Macintosh(使用Apple Roman)。同時(shí)要注意,一個(gè)特定的字符表可能沒(méi)有覆蓋完字體里面的全部字形。

typedef struct  FT_CharMapRec_
{
    FT_Face      face;
    FT_Encoding  encoding;
    FT_UShort    platform_id;
    FT_UShort    encoding_id;

} FT_CharMapRec;

具體Encoding可以參考下放枚舉

當(dāng)新建一個(gè)face對(duì)象時(shí),它默認(rèn)選擇Unicode字符表。如果字體沒(méi)包含Unicode字符表,F(xiàn)reeType會(huì)嘗試在字形名的基礎(chǔ)上模擬一個(gè)。注意,如果字形名是不標(biāo)準(zhǔn)的那么模擬的字符表有可能遺漏某些字形。對(duì)于某些字體,包括符號(hào)字體和舊的亞洲手寫字體,Unicode模擬是不可能的。

typedef enum  FT_Encoding_
{
    FT_ENC_TAG( FT_ENCODING_NONE, 0, 0, 0, 0 ),

    FT_ENC_TAG( FT_ENCODING_MS_SYMBOL, 's', 'y', 'm', 'b' ),
    FT_ENC_TAG( FT_ENCODING_UNICODE,   'u', 'n', 'i', 'c' ),

    FT_ENC_TAG( FT_ENCODING_SJIS,    's', 'j', 'i', 's' ),
    FT_ENC_TAG( FT_ENCODING_PRC,     'g', 'b', ' ', ' ' ),
    FT_ENC_TAG( FT_ENCODING_BIG5,    'b', 'i', 'g', '5' ),
    FT_ENC_TAG( FT_ENCODING_WANSUNG, 'w', 'a', 'n', 's' ),
    FT_ENC_TAG( FT_ENCODING_JOHAB,   'j', 'o', 'h', 'a' ),

    /* for backward compatibility */
    FT_ENCODING_GB2312     = FT_ENCODING_PRC,
    FT_ENCODING_MS_SJIS    = FT_ENCODING_SJIS,
    FT_ENCODING_MS_GB2312  = FT_ENCODING_PRC,
    FT_ENCODING_MS_BIG5    = FT_ENCODING_BIG5,
    FT_ENCODING_MS_WANSUNG = FT_ENCODING_WANSUNG,
    FT_ENCODING_MS_JOHAB   = FT_ENCODING_JOHAB,

    FT_ENC_TAG( FT_ENCODING_ADOBE_STANDARD, 'A', 'D', 'O', 'B' ),
    FT_ENC_TAG( FT_ENCODING_ADOBE_EXPERT,   'A', 'D', 'B', 'E' ),
    FT_ENC_TAG( FT_ENCODING_ADOBE_CUSTOM,   'A', 'D', 'B', 'C' ),
    FT_ENC_TAG( FT_ENCODING_ADOBE_LATIN_1,  'l', 'a', 't', '1' ),

    FT_ENC_TAG( FT_ENCODING_OLD_LATIN_2, 'l', 'a', 't', '2' ),

    FT_ENC_TAG( FT_ENCODING_APPLE_ROMAN, 'a', 'r', 'm', 'n' )

} FT_Encoding;

初始化Face

一個(gè)外觀對(duì)象通常使用FT_New_Face()來(lái)創(chuàng)建,這個(gè)函數(shù)接受如下參數(shù):一個(gè)FT_Library句柄,一個(gè)表示字體文件的C文件路徑名,一個(gè)決定從文件中裝載外觀的索引(一個(gè)文件中可能有不同的外觀),和FT_Face句柄的地址,它返回一個(gè)錯(cuò)誤碼。

FT_Library library, // 這個(gè)類型對(duì)應(yīng)一個(gè)庫(kù)的單一實(shí)例句柄 FT_Init_FreeType 新建

FT_EXPORT_DEF( FT_Error )
  FT_New_Memory_Face( FT_Library      library,
                      const FT_Byte*  file_base, // 內(nèi)存讀入二進(jìn)制TTF數(shù)據(jù)
                      FT_Long         file_size,
                      FT_Long         face_index,
                      FT_Face        *aface )
  {
    FT_Open_Args  args;


    /* test for valid `library' and `face' delayed to `FT_Open_Face' */
    if ( !file_base )
      return FT_THROW( Invalid_Argument );

    /*
        #define FT_OPEN_MEMORY    0x1 // 內(nèi)存
        #define FT_OPEN_STREAM    0x2
        #define FT_OPEN_PATHNAME  0x4 // 文件打開
        #define FT_OPEN_DRIVER    0x8
        #define FT_OPEN_PARAMS    0x10
    */
    args.flags       = FT_OPEN_MEMORY;
    args.memory_base = file_base; // 指針指向 ttf 內(nèi)存數(shù)據(jù)
    args.memory_size = file_size;
    args.stream      = NULL;

    // 調(diào)用內(nèi)部方法
    return ft_open_face_internal( library, &args, face_index, aface, 1 );
  }

內(nèi)部方法

 static FT_Error
  ft_open_face_internal( FT_Library           library,
                         const FT_Open_Args*  args,
                         FT_Long              face_index, // 0
                         FT_Face             *aface,
                         FT_Bool              test_mac_fonts ) // 1
  {
    ...

    // #define FT_OPEN_DRIVER    0x8

    // 打開加在 FT_Face
    error = open_face( driver, &stream, external_stream, face_index,
                             num_params, params, &face );

    // 如果成功,則將該Face加載faces_list 鏈表
    if ( !error )
        goto Success;

    ...
// goto
Success:
    FT_TRACE4(( "FT_Open_Face: New face object, adding to list\n" ));

    /* add the face object to its driver's list */
    if ( FT_QNEW( node ) )
      goto Fail;

    node->data = face;
    /* don't assume driver is the same as face->driver, so use */
    /* face->driver instead.                                   */
    FT_List_Add( &face->driver->faces_list, node ); // 這里是一個(gè)鏈表存儲(chǔ)

    ...
  }

open_face方法 查找unicode charmap : find_unicode_charmap

  /**************************************************************************
   *
   * @Function:
   *   open_face
   *
   * @Description:
   *   This function does some work for FT_Open_Face().
   */
  static FT_Error
  open_face( FT_Driver      driver,
             FT_Stream      *astream,
             FT_Bool        external_stream,
             FT_Long        face_index,
             FT_Int         num_params,
             FT_Parameter*  params,
             FT_Face       *aface ) 
  {

    ...
    if ( clazz->init_face )
      error = clazz->init_face( *astream,
                                face,
                                (FT_Int)face_index,
                                num_params,
                                params );
    *astream = face->stream; /* Stream may have been changed. */
    if ( error )
      goto Fail;


    /* select Unicode charmap by default */
    error2 = find_unicode_charmap( face ); // 查找unicode charmap
    ...
  }

FT_Memory

所有內(nèi)存管理操作通過(guò)基礎(chǔ)層中3個(gè)特定例程完成,叫做FT_AllocFT_ReallocFT_Free,每個(gè)函數(shù)需要一個(gè) FT_Memory句柄作為它的第一個(gè)參數(shù)。它是一個(gè)用來(lái)描述當(dāng)前內(nèi)存池/管理器對(duì)象的指針。在庫(kù)初始化時(shí),在FT_Init_FreeType中調(diào)用函數(shù)FT_New_Memory創(chuàng)建一個(gè)內(nèi)存管理器,這個(gè)函數(shù)位于ftsystem部件當(dāng)中。

  /**************************************************************************
   *
   * @struct:
   *   FT_MemoryRec
   *
   * @description:
   *   A structure used to describe a given memory manager to FreeType~2.
   *
   * @fields:
   *   user ::
   *     A generic typeless pointer for user data.
   *
   *   alloc ::
   *     A pointer type to an allocation function.
   *
   *   free ::
   *     A pointer type to an memory freeing function.
   *
   *   realloc ::
   *     A pointer type to a reallocation function.
   *
   */
  struct  FT_MemoryRec_
  {
    void*            user;
    FT_Alloc_Func    alloc; // 分配
    FT_Free_Func     free; // 釋放
    FT_Realloc_Func  realloc; // 再分配
  };

FT_Done_Face 這是一個(gè)釋放FT_Face的函數(shù), 內(nèi)部FT_Memory參與管理

可以看到

  • 從driver的鏈表中尋查找對(duì)應(yīng)FT_Face

之前是從ft_open_face_internal內(nèi)部將Face添加進(jìn)driver的鏈表(上文提到過(guò))

  • 從鏈表摘除Face

  • 釋放Face對(duì)應(yīng)Node節(jié)點(diǎn)

  • FT_Memory 銷毀Face

FT_EXPORT_DEF( FT_Error )
  FT_Done_Face( FT_Face  face )
  {
    FT_Error     error;
    FT_Driver    driver;
    FT_Memory    memory;
    FT_ListNode  node;
    ...

    driver = face->driver;
    memory = driver->root.memory;

    /* find face in driver's list */
    node = FT_List_Find( &driver->faces_list, face ); // 從driver的鏈表中尋查找對(duì)應(yīng)FT_Face
    if ( node )
    {
      /* remove face object from the driver's list */
      FT_List_Remove( &driver->faces_list, node ); // 從鏈表摘除Face
      FT_FREE( node ); // 釋放Face對(duì)應(yīng)Node節(jié)點(diǎn)

      /* now destroy the object proper */
      destroy_face( memory, face, driver ); // FT_Memory 銷毀Face
      error = FT_Err_Ok;
    }

    ...

FT_FREE 最終利用memory釋放對(duì)應(yīng)對(duì)象指針后,賦值為NULL

  FT_BASE_DEF( void )
  ft_mem_free( FT_Memory   memory,
               const void *P )
  {
    if ( P )
      memory->free( memory, (void*)P ); // free
  }

destroy_face 的釋放過(guò)程

  • 釋放一些通用型數(shù)據(jù),例如字形 API可能會(huì)給字符一個(gè)緩存
/* discard auto-hinting data */
    if ( face->autohint.finalizer )
      face->autohint.finalizer( face->autohint.data );
  • 釋放字符槽(是一個(gè)鏈表), 參考上文 FT_GlyphSlotRec數(shù)據(jù)結(jié)構(gòu)
while ( face->glyph )
      FT_Done_GlyphSlot( face->glyph );
  • 將FT_Size全部釋放
FT_Size類
  • 每個(gè)FT_Face對(duì)象都有一個(gè)或多個(gè)FT_Size對(duì)象,一個(gè)尺寸對(duì)象用來(lái)存放指定字符寬度和高度的特定數(shù)據(jù),每個(gè)新創(chuàng)建的外觀對(duì)象有一個(gè)尺寸,可以通過(guò)face->size直接訪問(wèn)。

  • 尺寸對(duì)象的內(nèi)容可以通過(guò)調(diào)用FT_Set_Pixel_Sizes()FT_Set_Char_Size()來(lái)改變。(參考上文FreeType初始化)

  • 一個(gè)新的尺寸對(duì)象可以通過(guò)FT_New_Size()創(chuàng)建,通過(guò)FT_Done_Size()銷毀,一般客戶程序無(wú)需做這一步,它們通常可以使用每個(gè)FT_Face缺省提供的尺寸對(duì)象。

/* discard all sizes for this face */
    FT_List_Finalize( &face->sizes_list,
                      (FT_List_Destructor)destroy_size,
                      memory,
                      driver );
    face->size = NULL;
  • 釋放charmaps
/* discard charmaps */
    destroy_charmaps( face, memory );
  • 釋放face 的format:字段face->glyph->format描述了字形槽中存儲(chǔ)的字形圖像的格式
/* finalize format-specific stuff */
    if ( clazz->done_face )
      clazz->done_face( face );
  • 釋放stream

字體文件總是通過(guò)FT_Stream對(duì)象讀取,F(xiàn)T_StreamRec的定義位于公共文件<freetype/ftsystem.h>中,可以允許客戶開發(fā)者提供自己的流實(shí)現(xiàn)。FT_New_Face()函數(shù)會(huì)自動(dòng)根據(jù)他第二個(gè)參數(shù),一個(gè)C路徑名創(chuàng)建一個(gè)新的流對(duì)象。它通過(guò)調(diào)用由 ftsystem部件提供的FT_New_Stream()完成,后者時(shí)可替換的,在不同平臺(tái)上,流的實(shí)現(xiàn)可能大不一樣。

/* close the stream for this face if needed */
    FT_Stream_Free(
      face->stream,
      ( face->face_flags & FT_FACE_FLAG_EXTERNAL_STREAM ) != 0 );

    face->stream = NULL;

  • 釋放
FT_FREE( face );
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容