32 votes

Existe-t-il un moyen de déterminer par programme si un fichier de police a un glyphe Unicode spécifique?

Je suis en train de travailler sur un projet qui génère les fichiers Pdf qui peuvent contenir assez complexe de mathématiques et de sciences des formules. Le texte est traduit en Times New Roman, qui a assez bonne Unicode couverture, mais pas complet. Nous avons mis en place un système pour le remplacer par un plus unicode complet de la police de points de code qui n'ont pas de glyphe dans TNR (comme la plupart des "étranger" symboles mathématiques) mais je n'arrive pas à trouver un moyen d'interroger l' *.ttf fichier pour voir si un glyphe est présent. Pour l'instant, j'ai juste codé en dur, une table de recherche dont les points de code sont présents, mais je préfèrerais une solution automatique.

Je suis à l'aide d'VB.Net dans un système web sous ASP.net mais les solutions dans tout langage de programmation / environnement serait appréciée. Merci!

Edit: win32 solution semble excellent, mais le cas précis, je suis en train de résoudre est dans un ASP.Net système web. Est-il un moyen de le faire sans y compris les windows API dll dans mon site web?

11voto

Scott Nichols Points 3366

Voici un passage à l'aide de c # et de l'API 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;
}
 

Ensuite, étant donné un caractère toCheck que vous souhaitez vérifier et une police theFont pour le tester ...

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

Même code en utilisant 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
 

3voto

David Thielen Points 3176

La réponse de Scott est bonne. Voici une autre approche qui est probablement plus rapide si vous ne vérifiez que quelques chaînes par police (dans notre cas, 1 chaîne par police). Mais probablement plus lent si vous utilisez une police pour vérifier une tonne de texte.

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

1voto

Stephen Deken Points 2418

FreeType est une bibliothèque qui peut lire des fichiers de police TrueType (entre autres), et peut être utilisé pour interroger la police pour un glyphe. Cependant, FreeType est conçu pour le rendu, afin de l'utiliser peut vous amener à tirer dans plus de code que vous avez besoin pour cette solution.

Malheureusement, il n'y a pas vraiment de solution claire même dans le monde de la OpenType / TrueType polices de caractères, le caractère-à-glyphe de cartographie a environ une douzaine de définitions différentes selon le type de police et de quelle plate-forme il a été initialement conçu pour. Vous pourriez essayer de regarder la cmap définition de la table dans Microsoft copie de la OpenType spec, mais ce n'est pas exactement facile à lire.

0voto

Ryan Corradini Points 433

Cet article de base de connaissances Microsoft peut aider: http://support.microsoft.com/kb/241020

C'est un peu daté (a été écrit à l'origine pour Windows 95), mais le principe général peut encore s'appliquer. L'exemple de code est en C++, mais depuis qu'il est juste d'appeler des Api Windows standard, il sera plus que probablement travailler dans .NET languages aussi bien avec un peu d'huile de coude.

-Edit- Il semble que la vieille 95 de l'ère de l'Api ont été remplacés par une nouvelle API de Microsoft appelle "Uniscribe", qui devrait être capable de faire ce dont vous avez besoin.

0voto

FarmerDave Points 16

Le code publié par Scott Nichols est génial, à l'exception d'un bogue: si l'ID de glyphe est supérieur à Int16.MaxValue, il lève une OverflowException. Pour y remédier, j'ai ajouté la fonction suivante:

 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
 

Et puis changé la boucle for principale dans la fonction GetUnicodeRangesForFont pour ressembler à ceci:

 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
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X