有没有办法以编程方式确定字体文件是否具有特定的Unicode字形?
我正在开发一个项目,该项目生成的PDF可能包含相当复杂的数学和科学公式。文本以Times New Roman呈现,它具有相当好的Unicode覆盖范围,但并不完整。我们有一个系统可以用更完整的Unicode完整字体替换TNR中没有字形的代码点(例如大多数"陌生人"数学符号),但是我似乎找不到一种查询方法* .ttf文件,以查看是否存在给定的字形。到目前为止,我仅对存在代码点的查找表进行了硬编码,但我更喜欢自动解决方案。
我在ASP.net下的Web系统中使用VB.Net,但是任何编程语言/环境下的解决方案都将受到赞赏。
编辑:win32解决方案看起来很棒,但是我要解决的特定情况是在ASP.Net Web系统中。有没有没有将Windows API dll包含到我的网站中的方法?
解决方案
FreeType是一个可以读取TrueType字体文件(除其他外)的库,可用于查询字体中的特定字形。但是,FreeType是为呈现而设计的,因此使用FreeType可能会导致我们引入比该解决方案所需的代码更多的代码。
不幸的是,即使在OpenType / TrueType字体的世界中,也没有真正的解决方案。字符到字形的映射大约有十二种不同的定义,具体取决于字体的类型及其最初设计的平台。我们可能会尝试查看Microsoft的OpenType规范副本中的cmap表定义,但这并不是很容易阅读。
此Microsoft KB文章可能会有所帮助:
http://support.microsoft.com/kb/241020
这有点过时了(最初是为Windows 95编写的),但一般原理可能仍然适用。示例代码是C ++,但是由于它只是调用标准的Windows API,因此它在.NET语言中也很有可能会工作,并且需要一点弯头。
-编辑
看来旧的95年代API已被Microsoft称为" Uniscribe"的新API淘汰,该API应该能够执行我们需要的操作。
这是使用Windows API进行的传递。
[DllImport("gdi32.dll")] public static extern uint GetFontUnicodeRanges(IntPtr hdc, IntPtr lpgs); [DllImport("gdi32.dll")] public extern static IntPtr SelectObject(IntPtr hDC, IntPtr hObject); public struct FontRange { public UInt16 Low; public UInt16 High; } public List<FontRange> GetUnicodeRangesForFont(Font font) { Graphics g = Graphics.FromHwnd(IntPtr.Zero); IntPtr hdc = g.GetHdc(); IntPtr hFont = font.ToHfont(); IntPtr old = SelectObject(hdc, hFont); uint size = GetFontUnicodeRanges(hdc, IntPtr.Zero); IntPtr glyphSet = Marshal.AllocHGlobal((int)size); GetFontUnicodeRanges(hdc, glyphSet); List<FontRange> fontRanges = new List<FontRange>(); int count = Marshal.ReadInt32(glyphSet, 12); for (int i = 0; i < count; i++) { FontRange range = new FontRange(); range.Low = (UInt16)Marshal.ReadInt16(glyphSet, 16 + i * 4); range.High = (UInt16)(range.Low + Marshal.ReadInt16(glyphSet, 18 + i * 4) - 1); fontRanges.Add(range); } SelectObject(hdc, old); Marshal.FreeHGlobal(glyphSet); g.ReleaseHdc(hdc); g.Dispose(); return fontRanges; } public bool CheckIfCharInFont(char character, Font font) { UInt16 intval = Convert.ToUInt16(character); List<FontRange> ranges = GetUnicodeRangesForFont(font); bool isCharacterPresent = false; foreach (FontRange range in ranges) { if (intval >= range.Low && intval <= range.High) { isCharacterPresent = true; break; } } return isCharacterPresent; }
然后,给要检查的字符char和要使用的Font theFont进行测试...
if (!CheckIfCharInFont(toCheck, theFont) { // not present }
使用VB.Net的相同代码
<DllImport("gdi32.dll")> _ Public Shared Function GetFontUnicodeRanges(ByVal hds As IntPtr, ByVal lpgs As IntPtr) As UInteger End Function <DllImport("gdi32.dll")> _ Public Shared Function SelectObject(ByVal hDc As IntPtr, ByVal hObject As IntPtr) As IntPtr End Function Public Structure FontRange Public Low As UInt16 Public High As UInt16 End Structure Public Function GetUnicodeRangesForFont(ByVal font As Font) As List(Of FontRange) Dim g As Graphics Dim hdc, hFont, old, glyphSet As IntPtr Dim size As UInteger Dim fontRanges As List(Of FontRange) Dim count As Integer g = Graphics.FromHwnd(IntPtr.Zero) hdc = g.GetHdc() hFont = font.ToHfont() old = SelectObject(hdc, hFont) size = GetFontUnicodeRanges(hdc, IntPtr.Zero) glyphSet = Marshal.AllocHGlobal(CInt(size)) GetFontUnicodeRanges(hdc, glyphSet) fontRanges = New List(Of FontRange) count = Marshal.ReadInt32(glyphSet, 12) For i = 0 To count - 1 Dim range As FontRange = New FontRange range.Low = Marshal.ReadInt16(glyphSet, 16 + (i * 4)) range.High = range.Low + Marshal.ReadInt16(glyphSet, 18 + (i * 4)) - 1 fontRanges.Add(range) Next SelectObject(hdc, old) Marshal.FreeHGlobal(glyphSet) g.ReleaseHdc(hdc) g.Dispose() Return fontRanges End Function Public Function CheckIfCharInFont(ByVal character As Char, ByVal font As Font) As Boolean Dim intval As UInt16 = Convert.ToUInt16(character) Dim ranges As List(Of FontRange) = GetUnicodeRangesForFont(font) Dim isCharacterPresent As Boolean = False For Each range In ranges If intval >= range.Low And intval <= range.High Then isCharacterPresent = True Exit For End If Next range Return isCharacterPresent End Function
Scott Nichols发布的代码很棒,除了一个错误:如果字形id大于Int16.MaxValue,它将引发OverflowException。为了解决这个问题,我添加了以下功能:
Protected Function Unsign(ByVal Input As Int16) As UInt16 If Input > -1 Then Return CType(Input, UInt16) Else Return UInt16.MaxValue - (Not Input) End If End Function
然后将函数GetUnicodeRangesForFont中的main for循环更改为:
For i As Integer = 0 To count - 1 Dim range As FontRange = New FontRange range.Low = Unsign(Marshal.ReadInt16(glyphSet, 16 + (i * 4))) range.High = range.Low + Unsign(Marshal.ReadInt16(glyphSet, 18 + (i * 4)) - 1) fontRanges.Add(range) Next