2008-09-19 11 views
31

Ich arbeite an einem Projekt, das PDFs generiert, die relativ komplexe mathematische und naturwissenschaftliche Formeln enthalten können. Der Text wird in Times New Roman gerendert, das eine ziemlich gute Unicode-Abdeckung aufweist, aber nicht vollständig ist. Wir haben ein System, um in einer Unicode-vollständigen Schriftart für Codepunkte zu tauschen, die kein Zeichen in TNR haben (wie die meisten der "fremden" Mathe-Symbole), aber ich kann anscheinend keine Möglichkeit finden, abzufragen die * .ttf-Datei, um zu sehen, ob eine gegebene Glyphe vorhanden ist. Bisher habe ich nur eine Nachschlagetabelle programmiert, in der Codepunkte vorhanden sind, aber ich bevorzuge eine automatische Lösung.Gibt es eine Möglichkeit, programmgesteuert festzustellen, ob eine Schriftartdatei über eine bestimmte Unicode-Glyphe verfügt?

Ich benutze VB.Net in einem Web-System unter ASP.net, aber Lösungen in jeder Programmiersprache/Umgebung würden geschätzt.

Bearbeiten: Die Win32-Lösung sieht hervorragend aus, aber der spezielle Fall, den ich versuche zu lösen, ist in einem ASP.Net-Web-System. Gibt es eine Möglichkeit, dies zu tun, ohne die Windows-API-DLLs in meine Website zu integrieren?

Antwort

1

FreeType ist eine Bibliothek, die (unter anderem) TrueType-Schriftart-Dateien lesen kann und verwendet werden kann, um die Schriftart für eine bestimmte Glyphe abzufragen. FreeType ist jedoch für das Rendern konzipiert. Wenn Sie es verwenden, können Sie möglicherweise mehr Code abrufen, als für diese Lösung erforderlich ist.

Leider gibt es nicht einmal innerhalb der Welt der OpenType/TrueType-Schriften eine klare Lösung; Das Character-to-Glyph-Mapping hat ungefähr ein Dutzend verschiedene Definitionen, abhängig vom Typ der Schriftart und der Plattform, für die sie ursprünglich entwickelt wurde. Sie könnten versuchen, die cmap table definition in Microsofts Kopie der OpenType spec zu betrachten, aber es ist nicht gerade einfach zu lesen.

0

Dieses Microsoft-KB-Artikel helfen: http://support.microsoft.com/kb/241020

Es ist ein vom Bit (ursprünglich geschrieben wurde für Windows 95), aber der allgemeine Grundsatz gelten kann nach wie vor. Der Beispielcode ist C++, aber da er nur Standard-Windows-APIs aufruft, wird er höchstwahrscheinlich auch in .NET-Sprachen mit ein wenig Ellenbogenfett funktionieren.

-Edit- Es scheint, dass die alte 95-Ära APIs hat durch eine neue API von Microsoft als „Uniscribe“ überholt worden, die in der Lage sein sollten, zu tun, was Sie brauchen.

+3

Im Gegenteil - UniScribe macht es * schwerer * zu tun, was das OP will, denn UniScribe soll den Prozess der Glyphtransparenz transparent machen. UniScribe verwendet zum Beispiel Font Fallback, um eine andere Schriftart auszuwählen, die * tatsächlich * eine fehlende Glyphe enthält. – bzlm

10

Hier ist ein Pass bei der Verwendung von C# und der 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; 
} 

Dann gegeben, ein Zeichen toCheck, die Sie überprüfen möchten, und ein Font theFont es zu testen gegen ...

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

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

Wenn Sie dies häufig aufrufen möchten, möchten Sie wahrscheinlich die Bereiche, die Sie erhalten haben, zwischenspeichern, vielleicht kapseln Sie es in einer CharInFontChecker-Klasse oder was auch immer. – jfs

0

Die Code geschrieben von Scott Nichols ist großartig, mit Ausnahme eines Fehlers: Wenn die Glyph-ID größer als Int16.MaxValue ist, wird eine OverflowException ausgelöst. Um es zu beheben, habe ich die folgende Funktion:

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 

und verändert dann die Haupt for-Schleife in der Funktion GetUnicodeRangesForFont wie folgt aussehen:

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

Scotts Antwort ist gut. Hier ist ein weiterer Ansatz, der wahrscheinlich schneller ist, wenn nur ein paar Zeichenfolgen pro Schriftart überprüft werden (in unserem Fall 1 Zeichen pro Schriftart). Aber wahrscheinlich langsamer, wenn Sie eine Schriftart verwenden, um eine Tonne Text zu überprüfen.

[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; 
    }