转载自:http://blog.csdn.net/xuchenhuics/article/details/17715557
字体文件可以储存文字、图片等符号,通过 ArcMap 的符号配置工具可以使用字体文件( ttf 格式)中包含的图片或符号。在 Arc Engine 中,也可以使用 ICharacterMarkerSymbol 接口根据字体文件生成对应的符号,通过 ICharacterMarkerSymbol.CharacterIndex 属性,设置符号对应的 unicode 可以生成对应的字体符号。但在实际应用中,用户是不可能知道符号与 unicode 编码的映射关系。因此,需要将 ttf 文件中的符号以图形化的方式显示出来,让用户自主选择生成 ICharacterMarkerSymbol 对象。本文将介绍读取 ttf 文件中符号及绘制方法,并最终生成 CharacterMarkerSymbol 。
一、 字体文件 (ttf) 的格式
Ttf 文件的结构共包括四部分,字体头、位置索引、图元数据和图元指令。在我们上述的讨论中,我们只需要关注字体头部分,就可以满足我们的要求。(详细信息请参考: http://venrar.blog.163.com/blog/static/60699459200881102648855/ )
通过调用 Win32 API 函数: GetFontUnicodeRanges ,我们可以获得支持的图元的数量、支持的 UNICODE 区域的数量以及设备上下文中字体的这些区域的详细信息,上述信息以 GLYPHSET 结构返回。 GetFontUnicodeRanges 函数通常需要调用两 次。第一次调用时得到以 NULL 指针作为最后一个参数, GDI 会返回所需内存的大小。调用者然后分配所需的内存,再次调用以得到真正的数据。下面是 C# 里调用该函数的方法:
首先定义一个 FontRange 对象:
public class FontRange { public FontRange (ushort first,ushort count ) { this.first = first; end = (uint) first + (uint) count; } private ushort first; private uint end; public ushort Count { get {return (ushort) (end - (uint) first);} } public uint End { get {return end;} } public ushort First { get {return first;} } public ushort Last { get {return (ushort) (end - 1UL);} } public bool Contains ( ushort codePoint ) { return (codePoint >= first && (uint) codePoint < end); } }
复制代码
然后调用 GetFontUnicodeRanges 函数返回,下面是获取字体范围的关键方法,完整代码见附件。
public FontGlyphSet (Font font ) { IntPtr glyphSetData = IntPtr.Zero; IntPtr savedFont = HGDI_ERROR; IntPtr hdc = IntPtr.Zero; Graphics g = null; try { g = Graphics.FromHwnd(IntPtr.Zero); hdc = g.GetHdc(); IntPtr hFont = font.ToHfont(); savedFont = SelectObject(hdc, hFont); if (savedFont == HGDI_ERROR) throw new Exception( "Unexpected failure of SelectObject."); //第一次调用,返回所需内存 size = GetFontUnicodeRanges(hdc, IntPtr.Zero); if (size == 0) throw new Exception( "Unexpected failure of GetFontUnicodeRanges."); //分配内存 glyphSetData = Marshal.AllocHGlobal((int) size); //第二次调用,获取范围信息 if (GetFontUnicodeRanges(hdc, glyphSetData) == 0) throw new Exception( "Unexpected failure of GetFontUnicodeRanges."); //从内存里提取全局的范围信息 int offset = uintSize; flags = (uint) Marshal.ReadInt32( glyphSetData, offset); offset += uintSize; codePointCount = (uint) Marshal.ReadInt32( glyphSetData, offset); offset += uintSize; uint rangeCount = (uint) Marshal.ReadInt32( glyphSetData, offset); offset += uintSize; ranges = new FontRange[rangeCount]; //从内存提取范围信息,返回FontRange对象,一个字体文件包括多个Unicode范围 for(uint index = 0; index < rangeCount; index++) { ushort first = (ushort) Marshal.ReadInt16( glyphSetData, offset); offset += Marshal.SizeOf(typeof(ushort)); ushort count = (ushort) Marshal.ReadInt16( glyphSetData, offset); offset += Marshal.SizeOf(typeof(ushort)); ranges[index] = new FontRange(first, count); } } }
复制代码
二、符号绘制 符号绘制的关键是我们如何根据获得的Unicode值绘制对应的符号。有两种方式可以实现: 1、 将Unicode赋值给ICharacterMarkerSymbol.CharacterIndex ,生成CharacterMarkerSymbol对象,然后通过ISymbol.Draw方法将符号绘制到窗体上; 2、 使用.Net里的Graphics.DrawString()方法将符号绘制到窗体上。 这两种方法效果类似,但第一种方法将创建上百个CharacterMarkerSymbol,而且必须缓存这些对象(因为窗体上的绘制方法会频繁调用,缓存可以提高响应效率)。所以,我选择了第二种方法将符号绘制到窗体上。 为什么使用DrawString()方法可以绘制符号呢?我们都知道,电脑上的文字都有一个编码,我们可以把绘制文字的过程理解为电脑根据这个编码去字体库里找到相应的图片,然后绘制到屏幕上。在.net里,Char类型和int类型是可以转换的。因此,用如下代码就可以依据Unicode值绘制ttf文件里的符号:
Private void DrawFontMarker(Font font, int unicode ,Graphics graph, Rectangle rect,Brush fontBrush) { char marker=(char)unicode; graph.DrawString(marker.ToString(),font,fontBrush,rect); }
复制代码
实现了单个符号的绘制,对于多个符号,我们只需要依据步骤一里获得 Unicode 范围,用遍历的方法逐个绘制,即可将所有符号绘制到窗体上,并记录每个符号匹配的 Unicode 。
在绘制所有的符号时,要注意窗体的刷新范围,尽量使用局部重绘,这样可以提高绘制效率,避免窗体闪烁。
三、 CharacterMarkerSymbol生成
CharacterMarkerSymbol 的生成就比较简单。依据用户的操作,获得字体对象 Font 和 Unicode 编码,代码如下:
ICharacterMarkerSymbol symbol=new CharacterMarkerSymbolClass(); m_ symbol.Font = OLE.GetIFontDispFromFont(font) as stdole.IFontDisp; m_ symbol.CharacterIndex =unicode;
复制代码
下图为基于Engine开发的符号选择器中通过字体文件配置点状符号的效果图。