your programing

글꼴 파일에 특정 유니코드 글리프가 있는지 프로그래밍 방식으로 확인할 수 있는 방법이 있습니까?

lovepro 2023. 5. 22. 23:12
반응형

글꼴 파일에 특정 유니코드 글리프가 있는지 프로그래밍 방식으로 확인할 수 있는 방법이 있습니까?

저는 상당히 복잡한 수학과 과학 공식을 포함할 수 있는 PDF를 생성하는 프로젝트를 진행하고 있습니다.텍스트는 유니코드 적용 범위가 꽤 좋지만 완전하지 않은 Times New Roman으로 렌더링됩니다.TNR(대부분의 "이상한" 수학 기호처럼)에 글리프가 없는 코드 포인트에 대해 유니코드 전체 글꼴로 스왑할 수 있는 시스템이 있지만 *.ttf 파일을 쿼리하여 주어진 글리프가 있는지 확인할 수 있는 방법을 찾을 수 없습니다.지금까지 코드 포인트가 있는 룩업 테이블을 하드 코딩했지만 자동 솔루션을 훨씬 선호합니다.

VB를 사용하고 있습니다.ASP.net 아래의 웹 시스템에 넷. 그러나 모든 프로그래밍 언어/환경의 솔루션은 감사할 것입니다.

편집: win32 솔루션은 훌륭해 보이지만, 제가 해결하고자 하는 구체적인 사례는 ASP.Net 웹 시스템에 있습니다.웹 사이트에 Windows API DLL을 포함하지 않고 이 작업을 수행할 수 있는 방법이 있습니까?

여기에 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;
}

그런 다음 확인할 문자와 이를 테스트할 글꼴을 지정합니다.

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

VB를 사용하는 동일한 코드.그물

<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  

스콧의 대답은 좋습니다.글꼴당 문자열 두 개만 확인하는 경우(우리의 경우 글꼴당 문자열 한 개) 더 빠를 수 있는 또 다른 접근 방식이 있습니다.그러나 한 글꼴을 사용하여 많은 텍스트를 확인하는 경우에는 속도가 느려질 수 있습니다.

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

FreeType은 TrueType 글꼴 파일을 읽을 수 있는 라이브러리이며 글꼴에서 특정 글리프를 쿼리하는 데 사용할 수 있습니다.그러나 FreeType은 렌더링을 위해 설계되었으므로 이 솔루션에 필요한 것보다 더 많은 코드를 입력할 수 있습니다.

안타깝게도 OpenType/TrueType 글꼴 세계에서도 명확한 솔루션은 없습니다. 문자 대 글리프 매핑은 글꼴 유형과 원래 어떤 플랫폼을 위해 설계되었는지에 따라 약 12가지의 다른 정의가 있습니다.Microsoft의 OpenType 사양 복사본에서 cmap 테이블 정의를 살펴보려 할 수도 있지만 쉽게 읽을 수 있는 것은 아닙니다.

다음 Microsoft KB 문서에서 도움이 될 수 있습니다. http://support.microsoft.com/kb/241020

(원래는 Windows 95용으로 작성되었지만) 일반적인 원리는 여전히 적용될 수 있습니다.샘플 코드는 C++이지만 표준 Windows API를 호출하는 것이기 때문에 약간의 윤활유만 있으면 .NET 언어로도 작동할 가능성이 높습니다.

-편집- 95년대의 오래된 API는 Microsoft에서 "Uniscript"라고 부르는 새로운 API에 의해 폐기된 것으로 보이며, 이 API는 필요한 작업을 수행할 수 있어야 합니다.

Scott Nichols가 게시한 코드는 한 가지 버그를 제외하면 훌륭합니다: 글리피드가 Int16보다 클 경우.MaxValue는 오버플로를 발생시킵니다.예외.이를 해결하기 위해 다음 기능을 추가했습니다.

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

나는 이것을 단지 VB로 했습니다.Net Unit Test 및 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

출력은.

MN=Matrix, gtf=Matrix, [x0-xFFFC]#2488, U+2026.

MN=Matrix New, gtf=Matrix New, [x20-xFFFC]#3177, U+2026.

MN=Malida Console, gtf=Malida Console, [x20-xFB02]#644, U+2026.

MN=Malida Sans 타자기, gtf=Malida Sans 타자기, [x20-xF002]#240, U+2026.

MN=MingLiU-ExtB, gtf=MingLiU-ExtB, [x0-x212]#212.

MN=MingLiU_HKSCS-ExtB, gtf=MingLiU_HKSCS-ExtB, [x0-x212]#212.

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

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

MN=OCRA 확장, gtf=OCRA 확장, [x20-xF003]#248,U+2026.

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

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

MN=웨딩, gtf=웨딩, [x20-xF0]FF]#446.

언급URL : https://stackoverflow.com/questions/103725/is-there-a-way-to-programmatically-determine-if-a-font-file-has-a-specific-unico

반응형