Определение точной высоты глифа в указанном шрифте

Я много искал и много пробовал, но я не могу найти подходящее решение.

Интересно, есть ли какой-либо подход для определения точного глифа высота в указанном шрифте?

Я имею в виду, когда я хочу определить высоту глифа DOT, я должен получать небольшую высоту, но не высоту с прокладками или размером шрифта.

Я нашел решение для определения ширины глифа точной здесь (я использовал второй подход), но это не работает для высоты.

UPDATE: Мне нужно решение для .NET 1.1

Ответ 1

Не так сложно получить метрики персонажа. GDI содержит функцию GetGlyphOutline, которую вы можете вызвать с константой GGO_METRICS, чтобы получить высоту и ширину самого маленького прямоугольника, который должен содержать глиф при визуализации. I.e, 10-точечный глиф для точки в шрифте Arial даст прямоугольник размером 1x1 пиксель, а для буквы я 95x.14, если шрифт имеет 100 точек размера.

Это объявление для вызовов P/Invoke:

// the declarations
public struct FIXED
{
    public short fract;
    public short value;
}

public struct MAT2
{
    [MarshalAs(UnmanagedType.Struct)] public FIXED eM11;
    [MarshalAs(UnmanagedType.Struct)] public FIXED eM12;
    [MarshalAs(UnmanagedType.Struct)] public FIXED eM21;
    [MarshalAs(UnmanagedType.Struct)] public FIXED eM22;
}

[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
    public int x;
    public int y;
}

[StructLayout(LayoutKind.Sequential)]
public struct POINTFX
{
    [MarshalAs(UnmanagedType.Struct)] public FIXED x;
    [MarshalAs(UnmanagedType.Struct)] public FIXED y;
}

[StructLayout(LayoutKind.Sequential)]
public struct GLYPHMETRICS
{

    public int gmBlackBoxX;
    public int gmBlackBoxY;
    [MarshalAs(UnmanagedType.Struct)] public POINT gmptGlyphOrigin;
    [MarshalAs(UnmanagedType.Struct)] public POINTFX gmptfxGlyphOrigin;
    public short gmCellIncX;
    public short gmCellIncY;

}

private const int GGO_METRICS = 0;
private const uint GDI_ERROR = 0xFFFFFFFF;

[DllImport("gdi32.dll")]
static extern uint GetGlyphOutline(IntPtr hdc, uint uChar, uint uFormat,
   out GLYPHMETRICS lpgm, uint cbBuffer, IntPtr lpvBuffer, ref MAT2 lpmat2);

[DllImport("gdi32.dll", ExactSpelling = true, PreserveSig = true, SetLastError = true)]
static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);

Фактический код, довольно тривиальный, если вы не считаете увольнения P/Invoke. Я тестировал код, он работает (вы также можете настроить ширину, начиная с GLYPHMETRICS).

Примечание: это ad-hoc-код, в реальном мире вы должны очистить HDC и объекты с помощью ReleaseHandle и DeleteObject. Спасибо комментатору user2173353, чтобы указать на это.

// if you want exact metrics, use a high font size and divide the result
// otherwise, the resulting rectangle is rounded to nearest int
private int GetGlyphHeight(char letter, string fontName, float fontPointSize)
{
    // init the font. Probably better to do this outside this function for performance
    Font font = new Font(new FontFamily(fontName), fontPointSize);
    GLYPHMETRICS metrics;

    // identity matrix, required
    MAT2 matrix = new MAT2
        {
            eM11 = {value = 1}, 
            eM12 = {value = 0}, 
            eM21 = {value = 0}, 
            eM22 = {value = 1}
        };

    // HDC needed, we use a bitmap
    using(Bitmap b = new Bitmap(1,1))
    using (Graphics g = Graphics.FromImage(b))
    {
        IntPtr hdc = g.GetHdc();
        IntPtr prev = SelectObject(hdc, font.ToHfont());
        uint retVal =  GetGlyphOutline(
             /* handle to DC   */ hdc, 
             /* the char/glyph */ letter, 
             /* format param   */ GGO_METRICS, 
             /* glyph-metrics  */ out metrics, 
             /* buffer, ignore */ 0, 
             /* buffer, ignore */ IntPtr.Zero, 
             /* trans-matrix   */ ref matrix);

        if(retVal == GDI_ERROR)
        {
            // something went wrong. Raise your own error here, 
            // or just silently ignore
            return 0;
        }


        // return the height of the smallest rectangle containing the glyph
        return metrics.gmBlackBoxY;
    }    
}

Ответ 2

Можете ли вы обновить вопрос, чтобы включить то, что вы пробовали?

Точечным глифом Я предполагаю, что вы имеете в виду знак пунктуации, подробно здесь?

Отображается ли эта высота глифа на экране или на распечатанной странице?

Мне удалось изменить первый метод в ссылке, которую вы опубликовали, чтобы подсчитать соответствующие вертикальные пиксели, однако идентифицируя наибольшую высоту глиф - это делать, если вы не хотите рисовать персонажа по своему характеру, поэтому это не было действительно рабочим решением, как статья.

Чтобы иметь общее рабочее решение, необходимо определить наибольшую вертикальную область одного пикселя символа/глифа, а затем подсчитать количество пикселей в этом регионе.

Мне также удалось убедиться, что Graphics.MeasureString, TextRenderer.MeasureText и Graphics.MeasureCharacterRanges все вернули ограничивающий прямоугольник, который дал число, подобное высоте шрифта.

Альтернативой этому является свойство Glyph.ActualHeight, которое получает отображаемую высоту элемента структуры. Эта часть WPF и связанные классы GlyphTypeface и GlyphRun. Я не смог проверить их в это время, имея только Моно.

Шаги для получения Glyph.ActualHeight следующие

  • Инициализировать аргументы для GlyphRun

  • Инициализировать объект GlyphRun

  • Доступ к реле Glyph с помощью glyphTypeface.CharacterToGlyphMap[text[n]] или более правильно glyphTypeface.GlyphIndices[n], где GlyphTypeface - ваш GlyphTypeface, который создается из объекта Typeface, который вы делаете на шаге 1.

Релевантные ресурсы по их использованию включают

Дальнейшие ссылки на GDI (Что эти классы используют под капотом GDI или GDI +), а шрифты в Windows включают

Ответ 3

Здесь есть решение с WPF. Мы создаем промежуточный объект Geometry, чтобы получить точную ограничительную рамку нашего текста. Преимущество этого решения заключается в том, что он ничего не делает. Даже если вы не используете WPF для своего интерфейса, вы можете использовать этот фрагмент кода только для своих измерений, предполагая, что размер рендеринга шрифта будет одинаковым в GDI или достаточно близко.

    var fontFamily = new FontFamily("Arial");
    var typeface = new Typeface(fontFamily, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal);
    var fontSize = 15;

    var formattedText = new FormattedText(
        "Hello World",
        CultureInfo.CurrentCulture,
        FlowDirection.LeftToRight,
        typeface,
        fontSize,
        Brushes.Black);

    var textGeometry = formattedText.BuildGeometry(new Point(0, 0));

    double x = textGeometry.Bounds.Left;
    double y = textGeometry.Bounds.Right;
    double width = textGeometry.Bounds.Width;
    double height = textGeometry.Bounds.Height;

Здесь измерения "Hello world" составляют около 77 x 11 единиц. Одна точка дает 1,5 х 1,5.

В качестве альтернативного решения, еще в WPF, вы можете использовать GlyphRun и ComputeInkBoundingBox(). Это немного сложнее и не поддерживает автоматическую замену шрифтов. Это будет выглядеть так:

var glyphRun = new GlyphRun(glyphTypeFace, 0, false, fontSize,
            glyphIndexList,
            new Point(0, 0),
            advanceWidths,
            null, null, null, null,
            null, null);

Rect glyphInkBox = glyphRun.ComputeInkBoundingBox();