有没有办法以编程方式确定一个字体文件是否具有特定的Unicode字形?

我正在开发一个可以包含相当复杂的math和科学公式的PDF文件。 该文本呈现在Times New Roman中,具有相当不错的Unicode覆盖范围,但并不完整。 我们有一个系统来replace在TNR中没有字形的代码点更多的Unicode字体完整字体(像大多数“陌生人”的math符号),但我似乎无法find一种方法来查询* .ttf文件来查看给定的字形是否存在。 到目前为止,我只是硬编码了哪个代码点的查找表,但我更喜欢自动解决scheme。

我在ASP.net下的web系统中使用VB.Net,但在任何编程语言/环境中的解决scheme将不胜感激。

编辑:win32解决scheme看起来不错,但我试图解决的具体情况是在ASP.Netnetworking系统。 有没有办法做到这一点,而不包括Windows API DLL到我的网站?

这里是使用C#和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; } 

然后,给一个字符检查,你想检查和字体theFont来testing它… …

 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 

FreeType是一个可以读取TrueType字体文件的库,可以用来查询特定字形的字体。 但是,FreeType是专为渲染而devise的,因此使用它可能会导致您获得比此解决scheme所需更多的代码。

不幸的是,即使在OpenType / TrueType字体的世界里也没有一个明确的解决scheme。 字符到字形的映射有十几个不同的定义,取决于字体的types和它最初devise的平台。 你可以试着看看微软的OpenType规范拷贝中的cmap表格定义 ,但是读起来并不容易。

斯科特的答案是好的。 这是另一种方法,如果每个字体检查一对string(在我们的例子中是每个字体1个string),速度可能会更快。 但是,如果您使用一种字体来检查大量文本,可能会更慢。

  [DllImport("gdi32.dll", EntryPoint = "CreateDC", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr CreateDC(string lpszDriver, string lpszDeviceName, string lpszOutput, IntPtr devMode); [DllImport("gdi32.dll", ExactSpelling = true, SetLastError = true)] private static extern bool DeleteDC(IntPtr hdc); [DllImport("Gdi32.dll")] private static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj); [DllImport("Gdi32.dll", CharSet = CharSet.Unicode)] private static extern int GetGlyphIndices(IntPtr hdc, [MarshalAs(UnmanagedType.LPWStr)] string lpstr, int c, Int16[] pgi, int fl); /// <summary> /// Returns true if the passed in string can be displayed using the passed in fontname. It checks the font to /// see if it has glyphs for all the chars in the string. /// </summary> /// <param name="fontName">The name of the font to check.</param> /// <param name="text">The text to check for glyphs of.</param> /// <returns></returns> public static bool CanDisplayString(string fontName, string text) { try { IntPtr hdc = CreateDC("DISPLAY", null, null, IntPtr.Zero); if (hdc != IntPtr.Zero) { using (Font font = new Font(new FontFamily(fontName), 12, FontStyle.Regular, GraphicsUnit.Point)) { SelectObject(hdc, font.ToHfont()); int count = text.Length; Int16[] rtcode = new Int16[count]; GetGlyphIndices(hdc, text, count, rtcode, 0xffff); DeleteDC(hdc); foreach (Int16 code in rtcode) if (code == 0) return false; } } } catch (Exception) { // nada - return true Trap.trap(); } return true; } 

此Microsoft知识库文章可能有所帮助: http : //support.microsoft.com/kb/241020

这有点过时(最初是为Windows 95编写的),但总的原则可能仍然适用。 示例代码是C ++,但是由于它只是调用标准的Windows API,所以它很可能在.NET语言中工作,还有一点肘部润滑脂。

– 编辑 – 似乎旧的95时代API已经被一个新的API废弃微软称为“ Uniscribe ”,应该能够做你需要它。

Scott Nichols发布的代码非常棒,除了一个bug:如果glyph id大于Int16.MaxValue,则会抛出OverflowExceptionexception。 为了解决这个问题,我添加了以下function:

 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 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