C#+OpenGL+FreeType显示3D文字(1) - 从TTF文件导出字形贴图

+BIT祝威+悄悄在此留下版了个权的信息说:

最近需要用OpenGL绘制文字,这是个很费时费力的事。一般的思路就是解析TTF文件从而得到字形的贴图,然后通过OpenGL绘制贴图的方式显示文字。

本篇记录了解析TTF文件并把所有字形安排到一张大贴图上的过程。

C#+OpenGL+FreeType显示3D文字(1) - 从TTF文件导出字形贴图

C#+OpenGL+FreeType显示3D文字(1) - 从TTF文件导出字形贴图

想从零开始解析TTF文件是一个比较大的工程,所以目前就借助FreeType。FreeType是一个开源的跨平台的TTF文件解析器。当然,它还支持解析OpenType等格式的文件。

+BIT祝威+悄悄在此留下版了个权的信息说:

预备工作

找一个TTF文件

你可以从C:\Windows\Fonts里找到很多TTF文件,我就不提供了。TTF文件里包含了文字的字形。比如下面这两个就是本篇开头的两张贴图展示的TTF:

C#+OpenGL+FreeType显示3D文字(1) - 从TTF文件导出字形贴图

C#+OpenGL+FreeType显示3D文字(1) - 从TTF文件导出字形贴图

下载FreeType源码

你可以在Github上搜索到FreeType项目,也可以到别的什么地方下载某个版本的FreeType。比如我下载的是2.6版。

C#+OpenGL+FreeType显示3D文字(1) - 从TTF文件导出字形贴图

源码下载解压后如上图所示。

下载CMake

我想用visual studio打开并编译它,但它没有sln工程文件。所以我们先要用CMake来自动生成一个freetype.sln文件。CMake你可以自行百度找到,由于博客园提供的空间有限,我这里也不保留CMake的安装包了。

安装完CMake后桌面上会有这样一个图标。这就是CMake。

C#+OpenGL+FreeType显示3D文字(1) - 从TTF文件导出字形贴图

 

+BIT祝威+悄悄在此留下版了个权的信息说:

创建FreeType.sln

有了CMake,就可以创建freetype.sln了。

打开CMake,按下图所示指定所需的参数,即可生成freetype.sln。

C#+OpenGL+FreeType显示3D文字(1) - 从TTF文件导出字形贴图

 

下面这一步指定Visual Studio。

C#+OpenGL+FreeType显示3D文字(1) - 从TTF文件导出字形贴图

 

看到"Generating Done"就说明成功了。

C#+OpenGL+FreeType显示3D文字(1) - 从TTF文件导出字形贴图

 

打开文件夹,可以看到出现了freetype.sln。

C#+OpenGL+FreeType显示3D文字(1) - 从TTF文件导出字形贴图

 

编译生成freetype.dll

有了freetype.sln,用visual studio打开,即可编译生成freetype.lib。但我们需要的是DLL,怎么办?按照下述步骤即可实现。

修改宏定义

找到ftoption.h文件,如下图所示,添加两个宏定义。

1 #define FT_EXPORT(x) __declspec(dllexport) x
2 #define FT_BASE(x) __declspec(dllexport) x

C#+OpenGL+FreeType显示3D文字(1) - 从TTF文件导出字形贴图

修改项目配置

如下图所示,在freetype属性页,把配置类型和目标文件扩展名改为.dll。

C#+OpenGL+FreeType显示3D文字(1) - 从TTF文件导出字形贴图

生成freetype.DLL

现在重新编译项目,就会生成freetype.dll了。

C#+OpenGL+FreeType显示3D文字(1) - 从TTF文件导出字形贴图

C#+OpenGL+FreeType显示3D文字(1) - 从TTF文件导出字形贴图

+BIT祝威+悄悄在此留下版了个权的信息说:

C#调用FreeType.dll

效果图

拿到了freetype.dll,下一步就是在C#里调用它。本篇期望用它得到的效果图已经贴到本文开头,这里再贴一下:

C#+OpenGL+FreeType显示3D文字(1) - 从TTF文件导出字形贴图

字形(Glyph)信息

英文单词Glyph的意思是"字形;图象字符;纵沟纹"。这就是字形。

一个字形有3个重要的信息:宽度、高度、基线高度。

C#+OpenGL+FreeType显示3D文字(1) - 从TTF文件导出字形贴图

如上图所示,从f到k,这是6个字形。

每个字形都用红色矩形围了起来。这些红色的矩形高度对每个字形都是相同的,只有宽度不同。

每个字形还用黄色矩形围了起来,这些黄矩形的宽度、高度就是字形的宽度、高度。

图中还有一个蓝色的矩形,它的上边沿与红色矩形是重合的(为了方便看,我没有画重合),它的下边沿就是"基线"。对于字母"g",它有一部分在基线上方,一部分在基线下方,在基线上方那部分的高度就称为"基线高度"。

所以,基线高度就是一个字形的黄色矩形框上边沿到蓝色矩形框下边沿的距离。有了这个基线高度,各个字形才能整齐地排列到贴图上。

如果没有基线高度的概念,你看的到贴图就会是这个样子:

C#+OpenGL+FreeType显示3D文字(1) - 从TTF文件导出字形贴图

与上面的效果图对比一下,你就知道它们的区别了。

封装freetype.dll

有了上面的基础,就可以开始干活了。首先要封装一些freetype相关的类型。这一步比较枯燥且冗长,我把核心类型放在这里,不想看直接跳过即可。

  1     public abstract class FreeTypeObjectBase<T> : IDisposable where T : class
  2     {
  3         /// <summary>
  4         /// 指针
  5         /// </summary>
  6         public IntPtr pointer;
  7 
  8         /// <summary>
  9         /// 对象
 10         /// </summary>
 11         public T obj;
 12 
 13         public override string ToString()
 14         {
 15             return string.Format("{0}: [{1}]", this.pointer, this.obj);
 16         }
 17 
 18         #region IDisposable Members
 19 
 20         /// <summary>
 21         /// Internal variable which checks if Dispose has already been called
 22         /// </summary>
 23         private Boolean disposed;
 24 
 25         /// <summary>
 26         /// Releases unmanaged and - optionally - managed resources
 27         /// </summary>
 28         /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
 29         private void Dispose(Boolean disposing)
 30         {
 31             if (disposed)
 32             {
 33                 return;
 34             }
 35 
 36             if (disposing)
 37             {
 38                 //TODO: Managed cleanup code here, while managed refs still valid
 39             }
 40             //TODO: Unmanaged cleanup code here
 41             ReleaseResource();
 42             this.pointer = IntPtr.Zero;
 43             this.obj = null;
 44 
 45             disposed = true;
 46         }
 47 
 48         /// <summary>
 49         /// Unmanaged cleanup code here
 50         /// </summary>
 51         protected abstract void ReleaseResource();
 52 
 53         /// <summary>
 54         /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
 55         /// </summary>
 56         public void Dispose()
 57         {
 58             // Call the private Dispose(bool) helper and indicate 
 59             // that we are explicitly disposing
 60             this.Dispose(true);
 61 
 62             // Tell the garbage collector that the object doesn't require any
 63             // cleanup when collected since Dispose was called explicitly.
 64             GC.SuppressFinalize(this);
 65         }
 66 
 67         #endregion
 68 
 69     }
 70 
 71     /// <summary>
 72     /// FreeType库
 73     /// </summary>
 74     public class FreeTypeLibrary : FreeTypeObjectBase<FT_Library>
 75     {
 76         /// <summary>
 77         /// 初始化FreeType库
 78         /// </summary>
 79         public FreeTypeLibrary()
 80         {
 81             int ret = FreeTypeAPI.FT_Init_FreeType(out this.pointer);
 82             if (ret != 0) { throw new Exception("Could not init freetype library!"); }
 83 
 84             this.obj = (FT_Library)Marshal.PtrToStructure(this.pointer, typeof(FT_Library));
 85             //lib = Marshal.PtrToStructure<Library>(libptr);
 86         }
 87 
 88         protected override void ReleaseResource()
 89         {
 90             FreeTypeAPI.FT_Done_FreeType(this.pointer);
 91         }
 92 
 93     }
 94 
 95     /// <summary>
 96     /// 初始化字体库
 97     /// </summary>
 98     public class FreeTypeFace : FreeTypeObjectBase<FT_Face>
 99     {
100 
101         /// <summary>
102         /// 初始化字体库
103         /// </summary>
104         /// <param name="library"></param>
105         /// <param name="fontFullname"></param>
106         /// <param name="size"></param>
107         public FreeTypeFace(FreeTypeLibrary library, string fontFullname)//, int size)
108         {
109             int retb = FreeTypeAPI.FT_New_Face(library.pointer, fontFullname, 0, out pointer);
110             if (retb != 0) { throw new Exception("Could not open font"); }
111 
112             this.obj = (FT_Face)Marshal.PtrToStructure(pointer, typeof(FT_Face));
113 
114         }
115 
116         /// <summary>
117         /// Unmanaged cleanup code here
118         /// </summary>
119         protected override void ReleaseResource()
120         {
121             FreeTypeAPI.FT_Done_Face(this.pointer);
122         }
123 
124     }
125  
126     /// <summary>
127     /// 把字形转换为纹理
128     /// </summary>
129     public class FreeTypeBitmapGlyph : FreeTypeObjectBase<FT_BitmapGlyph>
130     {
131         /// <summary>
132         /// char 
133         /// </summary>
134         public char glyphChar;
135         public GlyphRec glyphRec;
136 
137         /// <summary>
138         /// 把字形转换为纹理    
139         /// </summary>
140         /// <param name="face"></param>
141         /// <param name="c"></param>
142         public FreeTypeBitmapGlyph(FreeTypeFace face, char c, int size)
143         {
144             // Freetype measures the font size in 1/64th of pixels for accuracy 
145             // so we need to request characters in size*64
146             // 设置字符大小?
147             FreeTypeAPI.FT_Set_Char_Size(face.pointer, size << 6, size << 6, 96, 96);
148 
149             // Provide a reasonably accurate estimate for expected pixel sizes
150             // when we later on create the bitmaps for the font
151             // 设置像素大小?
152             FreeTypeAPI.FT_Set_Pixel_Sizes(face.pointer, size, size);
153 
154             // We first convert the number index to a character index
155             // 根据字符获取其编号
156             int index = FreeTypeAPI.FT_Get_Char_Index(face.pointer, Convert.ToChar(c));
157 
158             // Here we load the actual glyph for the character
159             // 加载此字符的字形
160             int ret = FreeTypeAPI.FT_Load_Glyph(face.pointer, index, FT_LOAD_TYPES.FT_LOAD_DEFAULT);
161             if (ret != 0) { throw new Exception(string.Format("Could not load character '{0}'", Convert.ToChar(c))); }
162             
163             int retb = FreeTypeAPI.FT_Get_Glyph(face.obj.glyphrec, out this.pointer);
164             if (retb != 0) return;
165             glyphRec = (GlyphRec)Marshal.PtrToStructure(face.obj.glyphrec, typeof(GlyphRec));
166 
167             FreeTypeAPI.FT_Glyph_To_Bitmap(out this.pointer, FT_RENDER_MODES.FT_RENDER_MODE_NORMAL, 0, 1);
168             this.obj = (FT_BitmapGlyph)Marshal.PtrToStructure(this.pointer, typeof(FT_BitmapGlyph));
169         }
170 
171         protected override void ReleaseResource()
172         {
173             //throw new NotImplementedException();
174         }
175     }
封装freetype的核心类型

相关文章: