Unity 文本系统全解 字体引擎

我将尝试借助一些列文章介绍Unity中字体系统的使用方式和各种情况下的处理方案。在实际游戏开发过程中,字体作为一类特殊渲染存在很多时候我们仅是使用,而很少针对字体系统进行深入底层的了解。这也导致很多时候在特殊情况下出现一些问题而无从下手的原因。

Unity自身支持TrueType和OpenType两种字体格式。其原因也非常简单这两种字体格式是目前支持最为广泛的字体格式,与此同时,TrueType格式中的存在潜在专利已经过期,不会出现相关专利问题。通常情况下TrueType字体的文件后缀名为.ttf,OpenType字体文件后缀名为.otf。关于这两种字体的更多介绍可以参考TrueType WikiOpenType Wiki

Unity底层针对这两种的字体的实现使用的开源库FreeType,我们也不需要在上层业务逻辑中针对字体文件做特殊处理,这部分如果有兴趣可以自己查阅相关资料。本篇文章仅针对TTF格式做一个简单的介绍,以方便后续理解字体文件是如何被渲染到画面中的。

参考

TTF文件格式

TTF字体文件主要数据分为两大部分,第一部分是当前字体所包含的字符目录索引,文档中称之为The Font Directory。这部分数据中记录了当前字体所支持的字符都有哪些,对应的字符数据在数据流中的具体位置。第二部分则是字符描述数据,这些数据用来描述对应字符“长”成什么样子,其中最重要的是glyf图元数据。

一个字符的图元数据是如何表示的呢?可以简单的理解为通过复杂的点和贝塞尔曲线数据来表示,我们可以通过https://opentype.js.org/ 来查看一个字符文件中字符的绘制信息。

上面的描述并不严谨,你只需要知道TTF字体文件的基本概念即可。

想看一个TTF文件字体所有字符和信息可以使用 https://kekee000.github.io/fonteditor/ 这个在线字体编辑器。

FreeType 与 UnityEngine.TextCore.LowLevel

FreeType 是一个可免费使用的用于渲染字体的软件库,Unity借助FreeType解析字体文件,并将查询到的字符对应的glyph转换为bitmap数据,提交给Unity。Unity将其转换为渲染层可用的纹理来进行使用。虽然引擎内部和TextMeshPro已经帮我们完成了这部分操作,但通过 UnityEngine.TextCore.LowLevel 我们依然可以进行一些相对底层的操作。

实际项目中这部分内容没有太多意义,作为学习研究还是有一定意义的。

通过对API推测,UnityEngine.TextCore.LowLevel 中的 `FontEngine` 接口应该是C#对 `FreeType` C库 的桥接层。

我们可以通过一段简单的代码来了解 FontEngine 的相关API使用。

我将一个英文字体文件修改名称为 fontBytes.bytes 放到了 Resources 目录中,方便加载资源。

using UnityEditor;
using UnityEngine;
using UnityEngine.TextCore;
using UnityEngine.TextCore.LowLevel;
using TextAsset = UnityEngine.TextAsset;

public static class FontTest
{
    [MenuItem("Test/TestFont")]
    public static void Run()
    {
        var rel = FontEngine.InitializeFontEngine();
        Debug.Log($"FontEngine初始化结果:{rel}");

        var fontBytes = Resources.Load<TextAsset>("testfont");
        rel = FontEngine.LoadFontFace(fontBytes.bytes);
        Debug.Log($"FontEngine字体加载结果:{rel}");

        var faceInfo = FontEngine.GetFaceInfo();
        Debug.Log($"GetFaceInfo:{faceInfo.ToDes()}");

        var faces = FontEngine.GetFontFaces();
        Debug.Log($"GetFontFaces:{faces.Length}");

        var fontNames = FontEngine.GetSystemFontNames();
        Debug.Log($"系统字体:{fontNames}");

        CheckHasChar('a');
        CheckHasChar('中');


        FontEngine.UnloadAllFontFaces();
    }

    private static void CheckHasChar(char character)
    {
        if (FontEngine.TryGetGlyphIndex(character, out var glyphIndex))
        {
            Debug.Log($"当前字体文件包含字符:{character}, glyphIndex:{glyphIndex}");
            return;
        }

        Debug.Log($"当前字体文件不包含字符包含字符:{character}");
    }

    public static string ToDes(this FaceInfo face)
    {
        var rel = "FaceInfo 详细信息\n" +
                  $"高线: {face.ascentLine}\n" +
                  $"基准线: {face.baseline}\n" +
                  $"大写字母线: {face.capLine}\n" +
                  $"低线: {face.descentLine}\n" +
                  $"字体的名称: {face.familyName}\n" +
                  $"行高表示连续文本行之间的距离: {face.lineHeight}\n" +
                  $"等分线: {face.meanLine}\n" +
                  $"用于采样字体的磅值: {face.pointSize}\n" +
                  $"字体的相对比例: {face.scale}\n" +
                  $"删除线的位置: {face.strikethroughOffset}\n" +
                  $"删除线的粗细: {face.strikethroughThickness}\n" +
                  $"字体的样式名称: {face.styleName}\n" +
                  $"使用下标的字符位置: {face.subscriptOffset}\n" +
                  $"下标字符的相对大小/比例: {face.subscriptSize}\n" +
                  $"使用上标的字符位置: {face.superscriptOffset}\n" +
                  $"上标字符的相对大小/比例: {face.superscriptSize}\n" +
                  $"制表符的宽度: {face.tabWidth}\n" +
                  $"下划线的位置: {face.underlineOffset}\n" +
                  $"下划线的粗细: {face.underlineThickness}";
        return rel;
    }
}

运行工具代码,可以看到如下打印:

FontEngine初始化结果:Success
UnityEngine.Debug:Log (object)
FontTest:Run () (at Assets/FontTest/FontTest.cs:17)

FontEngine字体加载结果:Success
UnityEngine.Debug:Log (object)
FontTest:Run () (at Assets/FontTest/FontTest.cs:21)

GetFaceInfo:FaceInfo 详细信息
高线: 1854
基准线: 0
大写字母线: 1409
低线: -434
字体的名称: Liberation Sans
行高表示连续文本行之间的距离: 2355
等分线: 1082
用于采样字体的磅值: 2048
字体的相对比例: 1
删除线的位置: 432.8
删除线的粗细: 150
字体的样式名称: Regular
使用下标的字符位置: -434
下标字符的相对大小/比例: 0.5
使用上标的字符位置: 1854
上标字符的相对大小/比例: 0.5
制表符的宽度: 569
下划线的位置: -292
下划线的粗细: 150
UnityEngine.Debug:Log (object)
FontTest:Run () (at Assets/FontTest/FontTest.cs:24)

GetFontFaces:1
UnityEngine.Debug:Log (object)
FontTest:Run () (at Assets/FontTest/FontTest.cs:27)

系统字体:System.String[]
UnityEngine.Debug:Log (object)
FontTest:Run () (at Assets/FontTest/FontTest.cs:30)

当前字体文件包含字符:a, glyphIndex:68
UnityEngine.Debug:Log (object)
FontTest:CheckHasChar (char) (at Assets/FontTest/FontTest.cs:43)
FontTest:Run () (at Assets/FontTest/FontTest.cs:32)

当前字体文件不包含字符包含字符:中
UnityEngine.Debug:Log (object)
FontTest:CheckHasChar (char) (at Assets/FontTest/FontTest.cs:47)
FontTest:Run () (at Assets/FontTest/FontTest.cs:33)

可以看到通过直接对 .ttf 文件的读取解析得到了字体相关信息,这些信息用来做排版渲染等操作。值得注意的是,英文字体中并不包含中文字符,所以就上面示例中字符“中”并不能在字符文件中找到,这也是为什么我们在Unity中会看到不支持的字体被渲染为一个方块的原因。

Unity是如何将字体文件渲染成一张纹理的呢?

实际上是通过内部API实现的,我们在外部无法访问该API,除非重新修改并生成UnityEngine的DLL文件才可以实现。主要的API有如下2个:

还有一些渲染相关的API,我并没有在TextMeshPro中找到调用的地方,一些API调用但是被废弃了。

这些接口的实现都在原生层实现,具体实现思路无法得知。但唯一可以确定的是,将字形渲染到一张纹理中,是需要一定性能消耗的。

字体渲染简单流程

我们可以简单来梳理一下一个文本的渲染流程。实际执行时会更多的细节

  1. 将指定的字体文件进行加载
  2. 解析字体文件,并准备好相关所需要的动态数据,如Texture纹理,glyf字符图元,这些数据会被反复使用,需要一个对象池来进行存储
  3. 收到指令开始渲染某一个字符
  4. 先查询当前字体中是否包含对应的字符,如果没有则用默认的方块图形替代
  5. 查询到了字符,检索出glyf字符图元信息
  6. 将字符图元信息和预先准备好的Texture纹理提交给字体引擎
  7. 字体引擎会将字符图元渲染到纹理中
  8. 用新的Texture纹理提交到GPU中(如果纹理有变化),然后将其渲染到画面中。
  9. 如果当前的Texture纹理已经被填充满了(没有任何空间绘制新字符),创建一个新的Texture纹理,再次提交给字体引擎

当我们使用文本时,选择动态字体,则会执行上面的流程。每次遇到纹理中没有字符的情况下,都会执行这些操作。除去字符纹理在运行时的CPU消耗,我们要存储字体文件,这需要占用一定的内存,。尤其中日韩这三种语言的字体文件通常较大。当遇到的字符越来越多的时候,Texture纹理的数量也会越来越多。

为此,TextMeshPro 提供了静态字体纹理这种方式。也就是将上面运行时所需要的操作在开发时就已经制作好,运行时只需要适应预先渲染好的纹理即可。运行中也不需要动态加载字体文件,从内存和CPU的角度来讲省去了非常多的操作环节。

但静态字体纹理也有一定的局限性。例如,当预先设定的字符集中不包含所需要的字符时,文本显示就会出现方块的问题。我们并没有两全其美的解决方案,只能在一定程度上缓解最糟糕的情况放生,具体的思路我会在后面的文章中逐步分析。

一些零碎的小知识

这部分内容没啥太大用处,仅仅是分析代码时的一些总结和发现。

FontEngineError枚举的来源

当我们调用 FontEngine.InitializeFontEngine 这个接口时,会有一个返回值,类型为 TextCore.LowLevel.FontEngineError 。这个枚举类型定义了 14 个错误类型。那么这些错误类型是从哪里来的呢?

  1. Success 成功执行函数时返回的错误代码。
  2. Invalid_File_Path 当源字体文件的文件路径显示无效时由 LoadFontFace 函数返回的错误代码。
  3. Invalid_File_Format 当源字体文件的格式未知或无效时由 LoadFontFace 函数返回的错误代码。
  4. Invalid_File_Structure 当源字体文件显示无效或格式不正确时由 LoadFontFace 函数返回的错误代码。
  5. Invalid_File 表示无效字体文件的错误代码。
  6. Invalid_Table 表示加载字体文件的一个表失败的错误代码。
  7. Invalid_Glyph_Index 引用无效或超出范围的字形索引值时由 LoadGlyph 函数返回的错误代码。
  8. Invalid_Character_Code 引用无效的 Unicode 字符值时由 LoadGlyph 函数返回的错误代码。
  9. Invalid_Pixel_Size LoadGlyph 或 SetFaceSize 函数使用无效的 pointSize 值时返回的错误代码。
  10. Invalid_Library 表示初始化字体引擎库失败的错误代码。
  11. Invalid_Face 表示无效字体的错误代码。
  12. Invalid_Library_or_Face 表示初始化字体引擎库失败和/或字体加载成功的错误代码。
  13. Atlas_Generation_Cancelled 已取消 FontEngine 字形打包或渲染过程时返回的错误代码。
  14. OpenTypeLayoutLookup_Mismatch OpenType Layout related errors.

他们的原始代码如下:

 public enum FontEngineError
 {
     Success                 = 0x0,

     // Font file structure, type or path related errors.
     Invalid_File_Path       = 0x1,
     Invalid_File_Format     = 0x2,
     Invalid_File_Structure  = 0x3,
     Invalid_File            = 0x4,
     Invalid_Table           = 0x8,

     // Glyph related errors.
     Invalid_Glyph_Index     = 0x10,
     Invalid_Character_Code  = 0x11,
     Invalid_Pixel_Size      = 0x17,

     //
     Invalid_Library         = 0x21,

     // Font face related errors.
     Invalid_Face            = 0x23,

     Invalid_Library_or_Face = 0x29,

     // Font atlas generation and glyph rendering related errors.
     Atlas_Generation_Cancelled  = 0x64,
     Invalid_SharedTextureData   = 0x65,

     // OpenType Layout related errors.
     OpenTypeLayoutLookup_Mismatch = 0x74,

     // Additional errors codes will be added as necessary to cover new FontEngine features and functionality.
 }

这些错误值实际上来源于 FreeType 库的 fterrdef.h 头文件定义。本质上,是将 FreeType 的操作结果值直接传递到了C#层。而 fterrdef.h 中定义的错误类型可不只这些。

fterrdef.h源码

/****************************************************************************
 *
 * fterrdef.h
 *
 *   FreeType error codes (specification).
 *
 * Copyright (C) 2002-2024 by
 * David Turner, Robert Wilhelm, and Werner Lemberg.
 *
 * This file is part of the FreeType project, and may only be used,
 * modified, and distributed under the terms of the FreeType project
 * license, LICENSE.TXT.  By continuing to use, modify, or distribute
 * this file you indicate that you have read the license and
 * understand and accept it fully.
 *
 */


  /**************************************************************************
   *
   * @section:
   *  error_code_values
   *
   * @title:
   *  Error Code Values
   *
   * @abstract:
   *  All possible error codes returned by FreeType functions.
   *
   * @description:
   *  The list below is taken verbatim from the file `fterrdef.h` (loaded
   *  automatically by including `FT_FREETYPE_H`).  The first argument of the
   *  `FT_ERROR_DEF_` macro is the error label; by default, the prefix
   *  `FT_Err_` gets added so that you get error names like
   *  `FT_Err_Cannot_Open_Resource`.  The second argument is the error code,
   *  and the last argument an error string, which is not used by FreeType.
   *
   *  Within your application you should **only** use error names and
   *  **never** its numeric values!  The latter might (and actually do)
   *  change in forthcoming FreeType versions.
   *
   *  Macro `FT_NOERRORDEF_` defines `FT_Err_Ok`, which is always zero.  See
   *  the 'Error Enumerations' subsection how to automatically generate a
   *  list of error strings.
   *
   */


  /**************************************************************************
   *
   * @enum:
   *   FT_Err_XXX
   *
   */

  /* generic errors */

  FT_NOERRORDEF_( Ok,                                        0x00,
                  "no error" )

  FT_ERRORDEF_( Cannot_Open_Resource,                        0x01,
                "cannot open resource" )
  FT_ERRORDEF_( Unknown_File_Format,                         0x02,
                "unknown file format" )
  FT_ERRORDEF_( Invalid_File_Format,                         0x03,
                "broken file" )
  FT_ERRORDEF_( Invalid_Version,                             0x04,
                "invalid FreeType version" )
  FT_ERRORDEF_( Lower_Module_Version,                        0x05,
                "module version is too low" )
  FT_ERRORDEF_( Invalid_Argument,                            0x06,
                "invalid argument" )
  FT_ERRORDEF_( Unimplemented_Feature,                       0x07,
                "unimplemented feature" )
  FT_ERRORDEF_( Invalid_Table,                               0x08,
                "broken table" )
  FT_ERRORDEF_( Invalid_Offset,                              0x09,
                "broken offset within table" )
  FT_ERRORDEF_( Array_Too_Large,                             0x0A,
                "array allocation size too large" )
  FT_ERRORDEF_( Missing_Module,                              0x0B,
                "missing module" )
  FT_ERRORDEF_( Missing_Property,                            0x0C,
                "missing property" )

  /* glyph/character errors */

  FT_ERRORDEF_( Invalid_Glyph_Index,                         0x10,
                "invalid glyph index" )
  FT_ERRORDEF_( Invalid_Character_Code,                      0x11,
                "invalid character code" )
  FT_ERRORDEF_( Invalid_Glyph_Format,                        0x12,
                "unsupported glyph image format" )
  FT_ERRORDEF_( Cannot_Render_Glyph,                         0x13,
                "cannot render this glyph format" )
  FT_ERRORDEF_( Invalid_Outline,                             0x14,
                "invalid outline" )
  FT_ERRORDEF_( Invalid_Composite,                           0x15,
                "invalid composite glyph" )
  FT_ERRORDEF_( Too_Many_Hints,                              0x16,
                "too many hints" )
  FT_ERRORDEF_( Invalid_Pixel_Size,                          0x17,
                "invalid pixel size" )
  FT_ERRORDEF_( Invalid_SVG_Document,                        0x18,
                "invalid SVG document" )

  /* handle errors */

  FT_ERRORDEF_( Invalid_Handle,                              0x20,
                "invalid object handle" )
  FT_ERRORDEF_( Invalid_Library_Handle,                      0x21,
                "invalid library handle" )
  FT_ERRORDEF_( Invalid_Driver_Handle,                       0x22,
                "invalid module handle" )
  FT_ERRORDEF_( Invalid_Face_Handle,                         0x23,
                "invalid face handle" )
  FT_ERRORDEF_( Invalid_Size_Handle,                         0x24,
                "invalid size handle" )
  FT_ERRORDEF_( Invalid_Slot_Handle,                         0x25,
                "invalid glyph slot handle" )
  FT_ERRORDEF_( Invalid_CharMap_Handle,                      0x26,
                "invalid charmap handle" )
  FT_ERRORDEF_( Invalid_Cache_Handle,                        0x27,
                "invalid cache manager handle" )
  FT_ERRORDEF_( Invalid_Stream_Handle,                       0x28,
                "invalid stream handle" )

  /* driver errors */

  FT_ERRORDEF_( Too_Many_Drivers,                            0x30,
                "too many modules" )
  FT_ERRORDEF_( Too_Many_Extensions,                         0x31,
                "too many extensions" )

  /* memory errors */

  FT_ERRORDEF_( Out_Of_Memory,                               0x40,
                "out of memory" )
  FT_ERRORDEF_( Unlisted_Object,                             0x41,
                "unlisted object" )

  /* stream errors */

  FT_ERRORDEF_( Cannot_Open_Stream,                          0x51,
                "cannot open stream" )
  FT_ERRORDEF_( Invalid_Stream_Seek,                         0x52,
                "invalid stream seek" )
  FT_ERRORDEF_( Invalid_Stream_Skip,                         0x53,
                "invalid stream skip" )
  FT_ERRORDEF_( Invalid_Stream_Read,                         0x54,
                "invalid stream read" )
  FT_ERRORDEF_( Invalid_Stream_Operation,                    0x55,
                "invalid stream operation" )
  FT_ERRORDEF_( Invalid_Frame_Operation,                     0x56,
                "invalid frame operation" )
  FT_ERRORDEF_( Nested_Frame_Access,                         0x57,
                "nested frame access" )
  FT_ERRORDEF_( Invalid_Frame_Read,                          0x58,
                "invalid frame read" )

  /* raster errors */

  FT_ERRORDEF_( Raster_Uninitialized,                        0x60,
                "raster uninitialized" )
  FT_ERRORDEF_( Raster_Corrupted,                            0x61,
                "raster corrupted" )
  FT_ERRORDEF_( Raster_Overflow,                             0x62,
                "raster overflow" )
  FT_ERRORDEF_( Raster_Negative_Height,                      0x63,
                "negative height while rastering" )

  /* cache errors */

  FT_ERRORDEF_( Too_Many_Caches,                             0x70,
                "too many registered caches" )

  /* TrueType and SFNT errors */

  FT_ERRORDEF_( Invalid_Opcode,                              0x80,
                "invalid opcode" )
  FT_ERRORDEF_( Too_Few_Arguments,                           0x81,
                "too few arguments" )
  FT_ERRORDEF_( Stack_Overflow,                              0x82,
                "stack overflow" )
  FT_ERRORDEF_( Code_Overflow,                               0x83,
                "code overflow" )
  FT_ERRORDEF_( Bad_Argument,                                0x84,
                "bad argument" )
  FT_ERRORDEF_( Divide_By_Zero,                              0x85,
                "division by zero" )
  FT_ERRORDEF_( Invalid_Reference,                           0x86,
                "invalid reference" )
  FT_ERRORDEF_( Debug_OpCode,                                0x87,
                "found debug opcode" )
  FT_ERRORDEF_( ENDF_In_Exec_Stream,                         0x88,
                "found ENDF opcode in execution stream" )
  FT_ERRORDEF_( Nested_DEFS,                                 0x89,
                "nested DEFS" )
  FT_ERRORDEF_( Invalid_CodeRange,                           0x8A,
                "invalid code range" )
  FT_ERRORDEF_( Execution_Too_Long,                          0x8B,
                "execution context too long" )
  FT_ERRORDEF_( Too_Many_Function_Defs,                      0x8C,
                "too many function definitions" )
  FT_ERRORDEF_( Too_Many_Instruction_Defs,                   0x8D,
                "too many instruction definitions" )
  FT_ERRORDEF_( Table_Missing,                               0x8E,
                "SFNT font table missing" )
  FT_ERRORDEF_( Horiz_Header_Missing,                        0x8F,
                "horizontal header (hhea) table missing" )
  FT_ERRORDEF_( Locations_Missing,                           0x90,
                "locations (loca) table missing" )
  FT_ERRORDEF_( Name_Table_Missing,                          0x91,
                "name table missing" )
  FT_ERRORDEF_( CMap_Table_Missing,                          0x92,
                "character map (cmap) table missing" )
  FT_ERRORDEF_( Hmtx_Table_Missing,                          0x93,
                "horizontal metrics (hmtx) table missing" )
  FT_ERRORDEF_( Post_Table_Missing,                          0x94,
                "PostScript (post) table missing" )
  FT_ERRORDEF_( Invalid_Horiz_Metrics,                       0x95,
                "invalid horizontal metrics" )
  FT_ERRORDEF_( Invalid_CharMap_Format,                      0x96,
                "invalid character map (cmap) format" )
  FT_ERRORDEF_( Invalid_PPem,                                0x97,
                "invalid ppem value" )
  FT_ERRORDEF_( Invalid_Vert_Metrics,                        0x98,
                "invalid vertical metrics" )
  FT_ERRORDEF_( Could_Not_Find_Context,                      0x99,
                "could not find context" )
  FT_ERRORDEF_( Invalid_Post_Table_Format,                   0x9A,
                "invalid PostScript (post) table format" )
  FT_ERRORDEF_( Invalid_Post_Table,                          0x9B,
                "invalid PostScript (post) table" )
  FT_ERRORDEF_( DEF_In_Glyf_Bytecode,                        0x9C,
                "found FDEF or IDEF opcode in glyf bytecode" )
  FT_ERRORDEF_( Missing_Bitmap,                              0x9D,
                "missing bitmap in strike" )
  FT_ERRORDEF_( Missing_SVG_Hooks,                           0x9E,
                "SVG hooks have not been set" )

  /* CFF, CID, and Type 1 errors */

  FT_ERRORDEF_( Syntax_Error,                                0xA0,
                "opcode syntax error" )
  FT_ERRORDEF_( Stack_Underflow,                             0xA1,
                "argument stack underflow" )
  FT_ERRORDEF_( Ignore,                                      0xA2,
                "ignore" )
  FT_ERRORDEF_( No_Unicode_Glyph_Name,                       0xA3,
                "no Unicode glyph name found" )
  FT_ERRORDEF_( Glyph_Too_Big,                               0xA4,
                "glyph too big for hinting" )

  /* BDF errors */

  FT_ERRORDEF_( Missing_Startfont_Field,                     0xB0,
                "`STARTFONT' field missing" )
  FT_ERRORDEF_( Missing_Font_Field,                          0xB1,
                "`FONT' field missing" )
  FT_ERRORDEF_( Missing_Size_Field,                          0xB2,
                "`SIZE' field missing" )
  FT_ERRORDEF_( Missing_Fontboundingbox_Field,               0xB3,
                "`FONTBOUNDINGBOX' field missing" )
  FT_ERRORDEF_( Missing_Chars_Field,                         0xB4,
                "`CHARS' field missing" )
  FT_ERRORDEF_( Missing_Startchar_Field,                     0xB5,
                "`STARTCHAR' field missing" )
  FT_ERRORDEF_( Missing_Encoding_Field,                      0xB6,
                "`ENCODING' field missing" )
  FT_ERRORDEF_( Missing_Bbx_Field,                           0xB7,
                "`BBX' field missing" )
  FT_ERRORDEF_( Bbx_Too_Big,                                 0xB8,
                "`BBX' too big" )
  FT_ERRORDEF_( Corrupted_Font_Header,                       0xB9,
                "Font header corrupted or missing fields" )
  FT_ERRORDEF_( Corrupted_Font_Glyphs,                       0xBA,
                "Font glyphs corrupted or missing fields" )

  /* */


/* END */

这些差异的根本原因在于,没有定义的错误根本用不到,或者说“绝对不会出现”。例如 0x400x41 两个内存相关的错误,大概率Unity底层已经进行了处理,不太可能让上层业务逻辑去处理这方面的事情。

还有一些API可以在C#接口和 FreeType 库中找到对应的桥接痕迹,更多的就不再展开赘述了。

写在最后

弄清楚字体的基本原理,可以帮助我们后续针对不同问题,不用业务情况找到一个比较合理的解决方案。不至于“一通瞎搞”,各种尝试,即使得到了想要的结果,也不知道其中的缘由。

针对本篇文章中出现的一些技术点,我也没有进行太过深入研究。只是将我想要知道一些原理性的东西整理出来,有个大致的脉络,算是“浅尝辄止”。如果你对某一个方面的话题很感兴趣想探讨相关话题,可以发邮件给我。