Nano Hash - криптовалюты, майнинг, программирование

Есть ли способ программно определить, имеет ли файл шрифта определенный символ Unicode?

Я работаю над проектом, который генерирует PDF-файлы, которые могут содержать довольно сложные математические и научные формулы. Текст отображается шрифтом Times New Roman, который имеет довольно хорошее, но не полное покрытие Unicode. У нас есть система для замены более полного шрифта Unicode для кодовых точек, которые не имеют глифа в TNR (например, большинство «чужих» математических символов), но я, похоже, не могу найти способ запроса файл * .ttf, чтобы узнать, присутствует ли данный глиф. До сих пор я только что жестко запрограммировал таблицу поиска, в которой присутствуют кодовые точки, но я бы предпочел автоматическое решение.

Я использую VB.Net в веб-системе под ASP.net, но я буду признателен за решения на любом языке программирования / среде.

Изменить: решение win32 выглядит превосходно, но конкретный случай, который я пытаюсь решить, находится в веб-системе ASP.Net. Есть ли способ сделать это без включения библиотек Windows API на мой веб-сайт?


Ответы:


1

Вот пример использования 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  
19.09.2008
  • Если вы хотите вызывать это часто, вы, вероятно, захотите кэшировать полученные диапазоны, возможно, инкапсулировать их в класс CharInFontChecker или что-то еще. 20.09.2008
  • Что меня беспокоит в этом, так это то, что несколько протестированных шрифтов, которые отображают правильный глиф, возвращаются как отсутствующие в шрифте. 23.03.2020

  • 2

    Ответ Скотта хорош. Вот еще один подход, который, вероятно, будет быстрее, если проверять всего пару строк на шрифт (в нашем случае 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;
        }
    
    04.07.2012
  • Какое значение 0xffff передается в GetGlyphIndices? Кажется, не задокументировано (больше?) Microsoft - упоминается только GGI_MARK_NONEXISTING_GLYPHS, который имеет значение 0x0001. (здесь версия ANSI, но версия Unicode в этом отношении не отличается). 21.06.2021

  • 3

    FreeType - это библиотека, которая может читать файлы шрифтов TrueType (среди прочего) и может использоваться для запроса шрифта для конкретный глиф. Однако FreeType разработан для рендеринга, поэтому при его использовании может потребоваться больше кода, чем требуется для этого решения.

    К сожалению, даже в мире шрифтов OpenType / TrueType нет однозначного решения; преобразование символа в глиф имеет около десятка различных определений в зависимости от типа шрифта и платформы, для которой оно было изначально разработано. Вы можете попробовать посмотреть на определение таблицы cmap в копии спецификация OpenType, но читать ее непросто.

    19.09.2008

    4

    Эта статья базы знаний Microsoft может помочь: http://support.microsoft.com/kb/241020

    Он немного устарел (изначально был написан для Windows 95), но общий принцип все еще может применяться. Пример кода написан на C ++, но поскольку он просто вызывает стандартные Windows API, он, скорее всего, будет работать и на языках .NET с небольшой смазкой локтя.

    -Edit- Кажется, что старые API 95-й эры были устаревшими из-за нового API, который Microsoft называет "Uniscribe", который должен иметь возможность делать то, что вам нужно.

    19.09.2008
  • Напротив, UniScribe делает труднее делать то, что хочет OP, поскольку UniScribe разработан, чтобы сделать процесс поиска глифа прозрачным. UniScribe, например, будет использовать Font Fallback для выбора другого шрифта, который на самом деле действительно содержит отсутствующий глиф. 25.09.2008

  • 5

    Код, опубликованный Скоттом Николсом, великолепен, за исключением одной ошибки: если идентификатор глифа больше 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
    

    А затем изменил основной цикл for в функции 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
    
    14.08.2009

    6

    Я сделал это с помощью только модульного теста VB.Net и без вызовов API WIN32. Он включает код для проверки определенных символов 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 = 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, 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 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.

    26.03.2019
    Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..