2008-09-19 44 views
31

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

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

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

回答

1

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

不幸的是,即使在OpenType/TrueType字体的世界中也没有真正的明确解决方案;字符到字形的映射有大约十几种不同的定义,这取决于字体的类型和它最初设计的平台。您可以尝试查看微软的OpenType spec副本中的cmap table definition,但这不太容易阅读。

0

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

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

-Edit- 旧的95时代的API似乎已被一个新的API废弃了,微软称之为“Uniscribe”,它应该能够做到你所需要的。

+3

恰恰相反 - UniScribe使得它更难*做OP的工作,因为UniScribe的目的是使找到字形透明的过程。例如,UniScribe将使用Font Fallback来选择一个不同的字体,该字体实际上*不会包含缺少的字形。 – bzlm 2008-09-25 19:54:10

10

下面是使用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; 
} 

然后,因为要检查一个char toCheck和字体theFont测试它反对......使用VB.Net

if (!CheckIfCharInFont(toCheck, theFont) { 
    // not present 
} 

相同的代码

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

如果你想经常调用这个函数,你可能需要缓存你获得的范围,也可能把它封装在CharInFontChecker类或其他类中。 – jfs 2008-09-19 20:28:48

0

的由Scott Nichols发布的代码非常棒,除了一个bug:如果glyph 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看起来像这样:

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 
1

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

[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; 
    } 
相关问题