32

私は、かなり複雑な数学と科学の数式を含むことができる PDF を生成するプロジェクトに取り組んでいます。テキストは Times New Roman でレンダリングされており、Unicode を十分にカバーしていますが、完全ではありません。TNR にグリフを持たないコード ポイント (ほとんどの「見知らぬ」数学記号のように) を、より Unicode の完全なフォントに交換するシステムを用意していますが、クエリを実行する方法が見つからないようです。 *.ttf ファイルを調べて、特定のグリフが存在するかどうかを確認します。これまでのところ、存在するコード ポイントのルックアップ テーブルをハードコーディングしただけですが、自動ソリューションの方がはるかに望ましいと思います。

ASP.net の下の Web システムで VB.Net を使用していますが、任意のプログラミング言語/環境でのソリューションを歓迎します。

編集: win32 ソリューションは優れているように見えますが、解決しようとしている特定のケースは ASP.Net Web システムにあります。Windows API DLL を Web サイトに含めずにこれを行う方法はありますか?

4

6 に答える 6

11

これは、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  
于 2008-09-19T17:22:52.163 に答える
3

スコットの答えは良いです。これは、フォントごとに2、3の文字列(この場合はフォントごとに1つの文字列)をチェックする場合におそらく高速になる別のアプローチです。ただし、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;
    }
于 2012-07-04T18:14:53.000 に答える
1

FreeTypeは、(とりわけ) TrueType フォント ファイルを読み取ることができるライブラリであり、特定のグリフのフォントをクエリするために使用できます。ただし、FreeType はレンダリング用に設計されているため、これを使用すると、このソリューションに必要な数よりも多くのコードを取り込む可能性があります。

残念ながら、OpenType / TrueType フォントの世界でも明確な解決策はありません。文字からグリフへのマッピングには、フォントの種類や元々設計されたプラットフォームに応じて、約 12 の異なる定義があります。Microsoft のOpenType 仕様のコピーにあるcmap テーブル定義を見ようとするかもしれませんが、正確に読むのは簡単ではありません。

于 2008-09-19T17:14:29.640 に答える
0

Scott Nichols によって投稿されたコードは素晴らしいものですが、1 つのバグを除いては、グリフ 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
于 2009-08-14T14:20:52.013 に答える
0

この Microsoft KB 記事が役立つ場合があります: http://support.microsoft.com/kb/241020

これは少し時代遅れです (元々は Windows 95 用に書かれました) が、一般的な原則はまだ適用される可能性があります。サンプル コードは C++ ですが、標準の Windows API を呼び出しているだけなので、少し手を加えれば .NET 言語でも動作する可能性が高くなります。

-編集-古い 95 時代の API は、Microsoft が「 Uniscribe 」と呼ぶ新しい API によって廃止されたようです。これは、必要なことを実行できるはずです。

于 2008-09-19T17:15:55.300 に答える