C’è un modo per determinare a livello di programmazione se un file di font ha un glifo Unicode specifico?

Sto lavorando a un progetto che genera PDF in grado di contenere formule matematiche e scientifiche piuttosto complesse. Il testo è reso in Times New Roman, che ha una copertura Unicode piuttosto buona, ma non completa. Abbiamo un sistema in atto per scambiare un carattere completo unicode completo per i punti di codice che non hanno un glifo in TNR (come la maggior parte dei simboli matematici “straniero”), ma non riesco a trovare un modo per interrogare il file * .ttf per vedere se un dato glifo è presente. Finora, ho appena codificato una tabella di ricerca di quali punti di codice sono presenti, ma preferirei molto una soluzione automatica.

Sto usando VB.Net in un sistema web sotto ASP.net, ma le soluzioni in qualsiasi linguaggio / ambiente di programmazione sarebbero apprezzate.

Modifica: la soluzione win32 sembra eccellente, ma il caso specifico che sto cercando di risolvere è in un sistema Web ASP.Net. C’è un modo per farlo senza includere le DLL di Windows API nel mio sito web?

Ecco un passaggio usando c # e 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 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 fontRanges = new List(); 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 ranges = GetUnicodeRangesForFont(font); bool isCharacterPresent = false; foreach (FontRange range in ranges) { if (intval >= range.Low && intval <= range.High) { isCharacterPresent = true; break; } } return isCharacterPresent; } 

Quindi, dato un segno di spunta per verificare che si desidera controllare e un Font theFont per testarlo contro ...

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

Stesso codice usando VB.Net

  _ Public Shared Function GetFontUnicodeRanges(ByVal hds As IntPtr, ByVal lpgs As IntPtr) As UInteger End Function  _ 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 è una libreria in grado di leggere file di font TrueType (tra gli altri) e può essere utilizzata per interrogare il carattere per un glifo specifico. Tuttavia, FreeType è progettato per il rendering, quindi utilizzarlo potrebbe farti inserire più codice di quello che ti serve per questa soluzione.

Sfortunatamente, non c’è una soluzione chiara nemmeno nel mondo dei font OpenType / TrueType; la mapping da carattere a glifo ha una dozzina di definizioni diverse a seconda del tipo di carattere e della piattaforma per cui è stata originariamente progettata. Potresti provare a guardare la definizione della tabella cmap nella copia Microsoft delle specifiche OpenType , ma non è esattamente una lettura facile.

La risposta di Scott è buona. Ecco un altro approccio che è probabilmente più veloce se si controllano solo un paio di stringhe per carattere (nel nostro caso 1 stringa per carattere). Ma probabilmente più lento se si sta usando un font per controllare una tonnellata di testo.

  [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); ///  /// 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. ///  /// The name of the font to check. /// The text to check for glyphs of. ///  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; } 

Questo articolo della Microsoft KB potrebbe aiutare: http://support.microsoft.com/kb/241020

È un po ‘datato (era stato originariamente scritto per Windows 95), ma il principio generale potrebbe ancora essere applicato. Il codice di esempio è C ++, ma dal momento che sta solo chiamando le API standard di Windows, sarà più che probabile che funzioni anche nei linguaggi .NET con un po ‘di gomito.

-Edit- Sembra che le vecchie API di 95 anni siano state rese obsolete da una nuova API Microsoft chiama ” Uniscribe “, che dovrebbe essere in grado di fare quello che ti serve.

Il codice pubblicato da Scott Nichols è ottimo, tranne che per un bug: se l’ID del glifo è maggiore di Int16.MaxValue, genera una OverflowException. Per risolvere il problema, ho aggiunto la seguente funzione:

 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 

E poi ha cambiato il ciclo principale per la funzione GetUnicodeRangesForFont per assomigliare a questo:

 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