11

我进行了很多搜索并尝试了很多,但我找不到合适的解决方案。

我想知道是否有任何方法可以确定指定字体的确切字形高度?

我的意思是当我想确定DOT字形的高度时,我应该收到小的高度,但不是带有填充或字体大小的高度。

我在这里找到了确定精确字形宽度的解决方案(我使用了第二种方法),但它不适用于高度。

更新:我需要 .NET 1.1 的解决方案

4

3 回答 3

7

获得角色指标并不难。GDI 包含一个函数GetGlyphOutline,您可以使用常量调用该函数,以获取渲染时包含字形所需的最小封闭矩形GGO_METRICS的高度和宽度。即,Arial 字体中一个点的 10 点字形将给出一个 1x1 像素的矩形,如果字体大小为 100 点,则字母 I 为 95x.14。

这些是 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)。

注意:这是临时代码,在现实世界中,您应该使用ReleaseHandle和清理 HDC 和对象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;
    }    
}
于 2012-04-07T18:43:15.580 回答
2

这是一个涉及 WPF 的解决方案。我们创建了一个中间几何对象,以检索我们文本的准确边界框。这种解决方案的优点是它实际上并不渲染任何东西。即使您的界面不使用 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 x 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();
于 2012-04-03T13:03:42.753 回答
2

您能否更新问题以包括您尝试过的内容?

通过点字形,我假设您的意思是此处详述的标点符号?

这个字形高度是显示在屏幕上还是打印页面上?

我设法修改了您发布的链接中的第一种方法,以便计算匹配的垂直像素,但是除非您愿意逐个字符地绘制,否则识别字形的最大高度是很繁琐的,所以这并不是真正的像文章一样的一般工作解决方案。

为了获得通用的工作解决方案,需要识别字符/字形的最大单像素垂直区域,然后计算该区域中的像素数。

我还设法验证了Graphics.MeasureStringTextRenderer.MeasureText并且Graphics.MeasureCharacterRanges都返回了边界框,它给出了一个类似于字体高度的数字。

对此的替代方法是Glyph.ActualHeight获取框架元素的渲染高度的属性。WPF 的这一部分以及相关的GlyphTypefaceGlyphRun类。我目前无法测试它们,只有 Mono。

获取步骤Glyph.ActualHeight如下

  1. 初始化参数GlyphRun

  2. 初始化GlyphRun对象

  3. Glyph使用glyphTypeface.CharacterToGlyphMap[text[n]]或更正确地访问相关glyphTypeface.GlyphIndices[n]glyphTypeface您的 GlyphTypeface 在哪里,它是从您在步骤 1 中创建的 Typeface 对象创建的。

使用它们的相关资源包括

关于 GDI 的进一步参考(这些类在后台使用的是 GDI 或 GDI+)和 Windows 中的字体包括

于 2012-04-01T14:28:21.840 回答