作者 ---- 小老虎(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_Alloc
、FT_Realloc
、FT_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 );