有没有办法以编程方式确定字体文件是否具有特定的 Unicode 字形?

发布于 2024-07-05 06:19:28 字数 402 浏览 5 评论 0 原文

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

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

编辑:win32 解决方案看起来很棒,但我试图解决的具体情况是在 ASP.Net Web 系统中。 有没有办法在不将 Windows API DLL 包含到我的网站中的情况下执行此操作?

I'm working on a project that generates PDFs that can contain fairly complex math and science formulas. The text is rendered in Times New Roman, which has pretty good Unicode coverage, but not complete. We have a system in place to swap in a more Unicode complete font for code points that don't have a glyph in TNR (like most of the "stranger" math symbols,) but I can't seem to find a way to query the *.ttf file to see if a given glyph is present. So far, I've just hard-coded a lookup table of which code points are present, but I'd much prefer an automatic solution.

I'm using VB.Net in a web system under ASP.net, but solutions in any programming language/environment would be appreciated.

Edit: The win32 solution looks excellent, but the specific case I'm trying to solve is in an ASP.Net web system. Is there a way to do this without including the windows API DLLs into my web site?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

彼岸花ソ最美的依靠 2024-07-12 06:19:28

这是使用 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 和一个 Font theFont 来测试它...

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

使用 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  

Here's a pass at it using c# and the 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;
}

Then, given a char toCheck that you want to check and a Font theFont to test it against...

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

Same code using 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  
凉墨 2024-07-12 06:19:28

我仅通过 VB.Net 单元测试完成此操作,没有进行 WIN32 API 调用。 它包括检查特定字符 U+2026(省略号)和 U+2409 (HTab),还返回具有字形的字符数(以及低值和高值)。 我只对等宽字体感兴趣,但很容易改变......

    Dim fnt As System.Drawing.Font, size_M As Drawing.Size, size_i As Drawing.Size, size_HTab As Drawing.Size, isMonospace As Boolean
    Dim ifc = New Drawing.Text.InstalledFontCollection
    Dim bm As Drawing.Bitmap = New Drawing.Bitmap(640, 64), gr = Drawing.Graphics.FromImage(bm)
    Dim tf As Windows.Media.Typeface, gtf As Windows.Media.GlyphTypeface = Nothing, ok As Boolean, gtfName = ""

    For Each item In ifc.Families
        'TestContext_WriteTimedLine($"N={item.Name}.")
        fnt = New Drawing.Font(item.Name, 24.0)
        Assert.IsNotNull(fnt)

        tf = New Windows.Media.Typeface(item.Name)
        Assert.IsNotNull(tf, $"item.Name={item.Name}")

        size_M = System.Windows.Forms.TextRenderer.MeasureText("M", fnt)
        size_i = System.Windows.Forms.TextRenderer.MeasureText("i", fnt)
        size_HTab = System.Windows.Forms.TextRenderer.MeasureText(ChrW(&H2409), fnt)
        isMonospace = size_M.Width = size_i.Width
        Assert.AreEqual(size_M.Height, size_i.Height, $"fnt={fnt.Name}")

        If isMonospace Then

            gtfName = "-"
            ok = tf.TryGetGlyphTypeface(gtf)
            If ok Then
                Assert.AreEqual(True, ok, $"item.Name={item.Name}")
                Assert.IsNotNull(gtf, $"item.Name={item.Name}")
                gtfName = $"{gtf.FamilyNames(Globalization.CultureInfo.CurrentUICulture)}"

                Assert.AreEqual(True, gtf.CharacterToGlyphMap().ContainsKey(AscW("M")), $"item.Name={item.Name}")
                Assert.AreEqual(True, gtf.CharacterToGlyphMap().ContainsKey(AscW("i")), $"item.Name={item.Name}")

                Dim t = 0, nMin = &HFFFF, nMax = 0
                For n = 0 To &HFFFF
                    If gtf.CharacterToGlyphMap().ContainsKey(n) Then
                        If n < nMin Then nMin = n
                        If n > nMax Then nMax = n
                        t += 1
                    End If
                Next
                gtfName &= $",[x{nMin:X}-x{nMax:X}]#{t}"

                ok = gtf.CharacterToGlyphMap().ContainsKey(AscW(ChrW(&H2409)))
                If ok Then
                    gtfName &= ",U+2409"
                End If
                ok = gtf.CharacterToGlyphMap().ContainsKey(AscW(ChrW(&H2026)))
                If ok Then
                    gtfName &= ",U+2026"
                End If
            End If

            Debug.WriteLine($"{IIf(isMonospace, "*M*", "")} N={fnt.Name}, gtf={gtfName}.")
            gr.Clear(Drawing.Color.White)
            gr.DrawString($"Mi{ChrW(&H2409)} {fnt.Name}", fnt, New Drawing.SolidBrush(Drawing.Color.Black), 10, 10)
            bm.Save($"{fnt.Name}_MiHT.bmp")
        End If
    Next

输出是

M N=Consolas,gtf=Consolas,[x0-xFFFC]#2488,U+2026。

M N=新快递,gtf=新快递,[x20-xFFFC]#3177,U+2026。

M N=Lucida 控制台,gtf=Lucida 控制台,[x20-xFB02]#644,U+2026。

M N=Lucida Sans 打字机,gtf=Lucida Sans 打字机,[x20-xF002]#240,U+2026。

M N=MingLiU-ExtB,gtf=MingLiU-ExtB,[x0-x2122]#212。

M N=MingLiU_HKSCS-ExtB,gtf=MingLiU_HKSCS-ExtB,[x0-x2122]#212。

M N=MS 哥特式,gtf=MS 哥特式,[x0-xFFEE]#15760,U+2026。

M N=NSimSun,gtf=NSimSun,[x20-xFFE5]#28737,U+2026。

M N=OCR A 扩展,gtf=OCR A 扩展,[x20-xF003]#248,U+2026。

M N=SimSun,gtf=SimSun,[x20-xFFE5]#28737,U+2026。

M N=SimSun-ExtB,gtf=SimSun-ExtB,[x20-x7F]#96。

M N=Webdings,gtf=Webdings,[x20-xF0FF]#446。

I have done this with just a VB.Net Unit Test and no WIN32 API calls. It includes code to check specific characters U+2026 (ellipsis) & U+2409 (HTab), and also returns # of characters (and low and high values) that have glyphs. I was only interested in Monospace fonts, but easy enough to change ...

    Dim fnt As System.Drawing.Font, size_M As Drawing.Size, size_i As Drawing.Size, size_HTab As Drawing.Size, isMonospace As Boolean
    Dim ifc = New Drawing.Text.InstalledFontCollection
    Dim bm As Drawing.Bitmap = New Drawing.Bitmap(640, 64), gr = Drawing.Graphics.FromImage(bm)
    Dim tf As Windows.Media.Typeface, gtf As Windows.Media.GlyphTypeface = Nothing, ok As Boolean, gtfName = ""

    For Each item In ifc.Families
        'TestContext_WriteTimedLine(
quot;N={item.Name}.")
        fnt = New Drawing.Font(item.Name, 24.0)
        Assert.IsNotNull(fnt)

        tf = New Windows.Media.Typeface(item.Name)
        Assert.IsNotNull(tf, 
quot;item.Name={item.Name}")

        size_M = System.Windows.Forms.TextRenderer.MeasureText("M", fnt)
        size_i = System.Windows.Forms.TextRenderer.MeasureText("i", fnt)
        size_HTab = System.Windows.Forms.TextRenderer.MeasureText(ChrW(&H2409), fnt)
        isMonospace = size_M.Width = size_i.Width
        Assert.AreEqual(size_M.Height, size_i.Height, 
quot;fnt={fnt.Name}")

        If isMonospace Then

            gtfName = "-"
            ok = tf.TryGetGlyphTypeface(gtf)
            If ok Then
                Assert.AreEqual(True, ok, 
quot;item.Name={item.Name}")
                Assert.IsNotNull(gtf, 
quot;item.Name={item.Name}")
                gtfName = 
quot;{gtf.FamilyNames(Globalization.CultureInfo.CurrentUICulture)}"

                Assert.AreEqual(True, gtf.CharacterToGlyphMap().ContainsKey(AscW("M")), 
quot;item.Name={item.Name}")
                Assert.AreEqual(True, gtf.CharacterToGlyphMap().ContainsKey(AscW("i")), 
quot;item.Name={item.Name}")

                Dim t = 0, nMin = &HFFFF, nMax = 0
                For n = 0 To &HFFFF
                    If gtf.CharacterToGlyphMap().ContainsKey(n) Then
                        If n < nMin Then nMin = n
                        If n > nMax Then nMax = n
                        t += 1
                    End If
                Next
                gtfName &= 
quot;,[x{nMin:X}-x{nMax:X}]#{t}"

                ok = gtf.CharacterToGlyphMap().ContainsKey(AscW(ChrW(&H2409)))
                If ok Then
                    gtfName &= ",U+2409"
                End If
                ok = gtf.CharacterToGlyphMap().ContainsKey(AscW(ChrW(&H2026)))
                If ok Then
                    gtfName &= ",U+2026"
                End If
            End If

            Debug.WriteLine(
quot;{IIf(isMonospace, "*M*", "")} N={fnt.Name}, gtf={gtfName}.")
            gr.Clear(Drawing.Color.White)
            gr.DrawString(
quot;Mi{ChrW(&H2409)} {fnt.Name}", fnt, New Drawing.SolidBrush(Drawing.Color.Black), 10, 10)
            bm.Save(
quot;{fnt.Name}_MiHT.bmp")
        End If
    Next

The output was

M N=Consolas, gtf=Consolas,[x0-xFFFC]#2488,U+2026.

M N=Courier New, gtf=Courier New,[x20-xFFFC]#3177,U+2026.

M N=Lucida Console, gtf=Lucida Console,[x20-xFB02]#644,U+2026.

M N=Lucida Sans Typewriter, gtf=Lucida Sans Typewriter,[x20-xF002]#240,U+2026.

M N=MingLiU-ExtB, gtf=MingLiU-ExtB,[x0-x2122]#212.

M N=MingLiU_HKSCS-ExtB, gtf=MingLiU_HKSCS-ExtB,[x0-x2122]#212.

M N=MS Gothic, gtf=MS Gothic,[x0-xFFEE]#15760,U+2026.

M N=NSimSun, gtf=NSimSun,[x20-xFFE5]#28737,U+2026.

M N=OCR A Extended, gtf=OCR A Extended,[x20-xF003]#248,U+2026.

M N=SimSun, gtf=SimSun,[x20-xFFE5]#28737,U+2026.

M N=SimSun-ExtB, gtf=SimSun-ExtB,[x20-x7F]#96.

M N=Webdings, gtf=Webdings,[x20-xF0FF]#446.

高跟鞋的旋律 2024-07-12 06:19:28

Scott Nichols 发布的代码很棒,但有一个错误:如果字形 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 循环更改为如下所示:

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

The code posted by Scott Nichols is great, except for one bug: if the glyph id is greater than Int16.MaxValue, it throws an OverflowException. To fix it, I added the following function:

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

And then changed the main for loop in the function GetUnicodeRangesForFont to look like this:

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
み青杉依旧 2024-07-12 06:19:28

这篇 Microsoft 知识库文章可能会有所帮助:
http://support.microsoft.com/kb/241020

它有点过时了(最初是为 Windows 95 编写),但一般原则可能仍然适用。 示例代码是 C++,但由于它只是调用标准 Windows API,因此它很可能也可以在 .NET 语言中工作,只需一点点努力。

-编辑-
看来旧的 95 时代的 API 已经被微软称为“Uniscribe",它应该能够完成您需要的操作。

This Microsoft KB article may help:
http://support.microsoft.com/kb/241020

It's a bit dated (was originally written for Windows 95), but the general principle may still apply. The sample code is C++, but since it's just calling standard Windows APIs, it'll more than likely work in .NET languages as well with a little elbow grease.

-Edit-
It seems that the old 95-era APIs have been obsoleted by a new API Microsoft calls "Uniscribe", which should be able to do what you need it to.

蓝眸 2024-07-12 06:19:28

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

不幸的是,即使在 OpenType / TrueType 字体领域,也没有真正明确的解决方案; 字符到字形的映射有大约十几种不同的定义,具体取决于字体的类型及其最初设计的平台。 您可以尝试查看 Microsoft 的 cmap 表定义 a href="http://www.microsoft.com/typography/otspec/otff.htm" rel="nofollow noreferrer">OpenType 规范,但它并不容易阅读。

FreeType is a library that can read TrueType font files (among others) and can be used to query the font for a specific glyph. However, FreeType is designed for rendering, so using it might cause you to pull in more code than you need for this solution.

Unfortunately, there's not really a clear solution even within the world of OpenType / TrueType fonts; the character-to-glyph mapping has about a dozen different definitions depending on the type of font and what platform it was originally designed for. You might try to look at the cmap table definition in Microsoft's copy of the OpenType spec, but it's not exactly easy reading.

夜还是长夜 2024-07-12 06:19:28

斯科特的回答很好。 这是另一种方法,如果每个字体只检查几个字符串(在我们的例子中每个字体 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;
    }

Scott's answer is good. Here is another approach that is probably faster if checking just a couple of strings per font (in our case 1 string per font). But probably slower if you are using one font to check a ton of text.

    [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;
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文