2008-09-19 11 views
31

Estoy trabajando en un proyecto que genera archivos PDF que pueden contener fórmulas matemáticas y científicas bastante complejas. El texto se representa en Times New Roman, que tiene una cobertura Unicode bastante buena, pero no completa. Tenemos un sistema para intercambiar una fuente completa más Unicode para los puntos de código que no tienen un glifo en TNR (como la mayoría de los símbolos matemáticos "desconocidos"), pero parece que no puedo encontrar una manera de consultar el archivo * .ttf para ver si un glifo dado está presente. Hasta ahora, he codificado una tabla de búsqueda de los puntos de código que están presentes, pero preferiría una solución automática.¿Hay alguna manera de determinar mediante programación si un archivo de fuente tiene un glifo Unicode específico?

Estoy usando VB.Net en un sistema web bajo ASP.net, pero las soluciones en cualquier lenguaje de programación/entorno serán apreciadas.

Editar: La solución win32 se ve excelente, pero el caso específico que estoy tratando de resolver está en un sistema web ASP.Net. ¿Hay alguna manera de hacer esto sin incluir los DLL de Windows API en mi sitio web?

Respuesta

1

FreeType es una biblioteca que puede leer archivos de fuentes TrueType (entre otros) y se puede utilizar para consultar la fuente de un glifo específico. Sin embargo, FreeType está diseñado para renderizar, por lo que su uso podría hacer que extraigas más código del que necesitas para esta solución.

Desafortunadamente, no existe realmente una solución clara incluso dentro del mundo de las fuentes OpenType/TrueType; la asignación de carácter a glifo tiene alrededor de una docena de definiciones diferentes según el tipo de fuente y para qué plataforma se diseñó originalmente. Puede tratar de mirar el cmap table definition en la copia de Microsoft del OpenType spec, pero no es exactamente fácil de leer.

0

Este artículo de Microsoft KB puede ayudar: http://support.microsoft.com/kb/241020

Es un poco de fecha (fue escrito originalmente para Windows 95), pero el principio general todavía se pueden aplicar. El código de muestra es C++, pero dado que solo llama a las API estándar de Windows, lo más probable es que funcione también en los lenguajes .NET con un poco de esfuerzo.

-Edit- Parece que las antiguas API de 95-era han sido obsoletas por una nueva API que Microsoft llama "Uniscribe", que debería poder hacer lo que usted necesita.

+3

Por el contrario - Uniscribe hace que sea más difícil * * para hacer lo que quiere el OP, ya que Uniscribe está diseñado para hacer que el proceso de encontrar el glifo transparente. UniScribe, por ejemplo, usar Font Fallback para seleccionar una fuente diferente que realmente * contiene * un glifo faltante. – bzlm

10

Aquí hay un pase utilizando C# y la API de Windows.

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

Entonces, dado un tocheck carbón que desea comprobar y una fuente theFont para probarlo en contra ...

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

mismo código utilizando 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

Si desea llamar a esto con frecuencia, probablemente desee almacenar en caché los rangos que ha obtenido, tal vez encapsularlo en una clase CharInFontChecker o lo que sea. – jfs

0

El El código publicado por Scott Nichols es excelente, excepto por un error: si el id. del glifo es mayor que Int16.MaxValue, arroja una OverflowException. Para solucionarlo, añadí la siguiente función:

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 

Y entonces cambió el principal de bucle en el GetUnicodeRangesForFont función para tener este aspecto:

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

respuesta de Scott es buena. Aquí hay otro enfoque que probablemente sea más rápido si se comprueban solo un par de cadenas por fuente (en nuestro caso 1 cadena por fuente). Pero probablemente sea más lento si está usando una fuente para verificar toneladas de texto.

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