美文网首页FFmpeg与音视频流媒体工程开发相关图形学
[Freetype]字体渲染FreeType源码解析总结

[Freetype]字体渲染FreeType源码解析总结

作者: _小老虎_ | 来源:发表于2022-08-14 12:17 被阅读0次

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

    Github: https://github.com/numberwolf

    多媒体/音视频/图形/播放器技术/跨平台技术架构


    O 背景

    我们常常会在 图片/视频等 二维多媒体素材中看到有文字的存在,同时也会在视频剪辑工具中给视频嵌入文字字幕操作。那么这些都是如何实现的呢?

    —— 看起来很简单的东西,往往比想象中要复杂的多。

    1 字体渲染基础原理

    我们常见的字体嵌入图像中,往往需要准备一个TTF等格式的字体文件。(如果不选择往往是默认操作系统的字体)

    而这些被设计好的字体,最终是以点阵的方式 存储于TTF字体文件中。也就是说,每一个字符都会被存入其中,如果当前字体设计不存在的文字,那么最终可能会以乱码的形式存在。


    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 为索引串讲数据结构

    FT_Face类

    一个外观对象对应单个字体外观,即一个特定风格的特定外观类型,例如Arial和Arial Italic是两个不同的外观。

    一个外观对象通常使用FT_New_Face()来创建,这个函数接受如下参数:一个FT_Library句柄,一个表示字体文件的C文件路径名,一个决定从文件中装载外观的索引(一个文件中可能有不同的外观),和FT_Face句柄的地址,它返回一个错误码。

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

    函数调用成功,返回0,face参数将被设置成一个非NULL值。

    外观对象包含一些用来描述全局字体数据的属性,可以被客户程序直接访问。例如外观中字形的数量、外观家族的名称、风格名称、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;
    
        // 字形槽的目的是提供一个地方,可以很容易地一个个地装入字形映象,而不管它的格式(位图、向量轮廓或其他)
        FT_GlyphSlot      glyph; // glyph link list
        // 每个FT_Face对象都有一个或多个FT_Size对象,一个尺寸对象用来存放指定字符宽度和高度的特定数据,每个新创建的外观对象有一个尺寸,可以通过face->size直接访问
        FT_Size           size;
        ...
    } FT_FaceRec
    

    可以观察到 FT_GlyphSlotRec是以一个 链表的形式存储的数据

    typedef struct  FT_GlyphSlotRec_
    {
        FT_Library        library;
        FT_Face           face;
        FT_GlyphSlot      next; // linklist node 字行槽是个链表的数据结构
        FT_UInt           glyph_index; /* new in 2.10; was reserved previously */
        FT_Generic        generic;
        ...
    } FT_GlyphSlotRec
    

    每个glyph字符在字槽对应一个索引 glyph_index

    常使用方法来确定某个字符在 glyph 链表是否存在,查找对应 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类型用来作为一个字符地图对象的句柄,一个字符图是一种表或字典,用来将字符码从某种编码转换成字体的字形索引。

    单个外观可能包含若干字符图,每个对应一个指定的字符指令系统,例如Unicode、Apple Roman、Windows codepages等等。

    每个FT_CharMap对象包含一个platform和encoding属性,用来标识它对应的字符指令系统。每个字体格式都提供它们自己的FT_CharMapRec的继承类型并实现它们。

    一个face对象包含一个或多个字符表(charmap),字符表是用来转换字符码到字形索引的。例如,很多TrueType字体包含两个字符表,一个用来转换Unicode字符码到字形索引,另一个用来转换Apple Roman编码到字形索引。这样的字体既可以用在Windows(使用Unicode)和Macintosh(使用Apple Roman)。同时要注意,一个特定的字符表可能没有覆盖完字体里面的全部字形。

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

    具体Encoding可以参考下放枚举

    当新建一个face对象时,它默认选择Unicode字符表。如果字体没包含Unicode字符表,FreeType会尝试在字形名的基础上模拟一个。注意,如果字形名是不标准的那么模拟的字符表有可能遗漏某些字形。对于某些字体,包括符号字体和旧的亚洲手写字体,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

    一个外观对象通常使用FT_New_Face()来创建,这个函数接受如下参数:一个FT_Library句柄,一个表示字体文件的C文件路径名,一个决定从文件中装载外观的索引(一个文件中可能有不同的外观),和FT_Face句柄的地址,它返回一个错误码。

    FT_Library library, // 这个类型对应一个库的单一实例句柄 FT_Init_FreeType 新建

    FT_EXPORT_DEF( FT_Error )
      FT_New_Memory_Face( FT_Library      library,
                          const FT_Byte*  file_base, // 内存读入二进制TTF数据
                          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 // 内存
            #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 内存数据
        args.memory_size = file_size;
        args.stream      = NULL;
    
        // 调用内部方法
        return ft_open_face_internal( library, &args, face_index, aface, 1 );
      }
    

    内部方法

     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 ); // 这里是一个链表存储
    
        ...
      }
    

    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

    所有内存管理操作通过基础层中3个特定例程完成,叫做FT_AllocFT_ReallocFT_Free,每个函数需要一个 FT_Memory句柄作为它的第一个参数。它是一个用来描述当前内存池/管理器对象的指针。在库初始化时,在FT_Init_FreeType中调用函数FT_New_Memory创建一个内存管理器,这个函数位于ftsystem部件当中。

      /**************************************************************************
       *
       * @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 这是一个释放FT_Face的函数, 内部FT_Memory参与管理

    可以看到

    • 从driver的链表中寻查找对应FT_Face

    之前是从ft_open_face_internal内部将Face添加进driver的链表(上文提到过)

    • 从链表摘除Face

    • 释放Face对应Node节点

    • 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的链表中寻查找对应FT_Face
        if ( node )
        {
          /* remove face object from the driver's list */
          FT_List_Remove( &driver->faces_list, node ); // 从链表摘除Face
          FT_FREE( node ); // 释放Face对应Node节点
    
          /* now destroy the object proper */
          destroy_face( memory, face, driver ); // FT_Memory 销毁Face
          error = FT_Err_Ok;
        }
    
        ...
    

    FT_FREE 最终利用memory释放对应对象指针后,赋值为NULL

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

    destroy_face 的释放过程

    • 释放一些通用型数据,例如字形 API可能会给字符一个缓存
    /* discard auto-hinting data */
        if ( face->autohint.finalizer )
          face->autohint.finalizer( face->autohint.data );
    
    • 释放字符槽(是一个链表), 参考上文 FT_GlyphSlotRec数据结构
    while ( face->glyph )
          FT_Done_GlyphSlot( face->glyph );
    
    • 将FT_Size全部释放
    FT_Size类
    • 每个FT_Face对象都有一个或多个FT_Size对象,一个尺寸对象用来存放指定字符宽度和高度的特定数据,每个新创建的外观对象有一个尺寸,可以通过face->size直接访问。

    • 尺寸对象的内容可以通过调用FT_Set_Pixel_Sizes()FT_Set_Char_Size()来改变。(参考上文FreeType初始化)

    • 一个新的尺寸对象可以通过FT_New_Size()创建,通过FT_Done_Size()销毁,一般客户程序无需做这一步,它们通常可以使用每个FT_Face缺省提供的尺寸对象。

    /* 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描述了字形槽中存储的字形图像的格式
    /* finalize format-specific stuff */
        if ( clazz->done_face )
          clazz->done_face( face );
    
    • 释放stream

    字体文件总是通过FT_Stream对象读取,FT_StreamRec的定义位于公共文件<freetype/ftsystem.h>中,可以允许客户开发者提供自己的流实现。FT_New_Face()函数会自动根据他第二个参数,一个C路径名创建一个新的流对象。它通过调用由 ftsystem部件提供的FT_New_Stream()完成,后者时可替换的,在不同平台上,流的实现可能大不一样。

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

    相关文章

      网友评论

        本文标题:[Freetype]字体渲染FreeType源码解析总结

        本文链接:https://www.haomeiwen.com/subject/oizcgrtx.html