11
votes

I have searched a lot and tried much but I can not find the proper solution.

I wonder is there any approach for determining exact glyph height in specified font?

I mean here when I want to determine the height of DOT glyph I should receive small height but not height with paddings or the font size.

I have found the solution for determining exact glyph width here (I have used the second approach) but it does not work for height.

UPDATE: I need solution for .NET 1.1

3
[If this is of some help to you][1] [1]: stackoverflow.com/questions/1439551/…Nikhil Agrawal
if you like the solution on codeproject (on which you gave a link) you may draw a glyph and rotate graphics object, so height will become width and use the same method.cookieMonster
Hmmm.. if it is possible that would be nice. But how do you suggest to "draw and rotate" glyphs? Is that possible? Could you provide your solution with examples by answering this question?Michael Z
it seems a bit more tricky than i expected, but i guess we can handle this one - what method from linked article do you use?cookieMonster
i tried this (first method from linked article): int width = MeasureDisplayStringWidth(g, ".", new Font("Arial", 12.0f)); //it gave me 8, maybe i'm missing something, but it's not seems to be rightcookieMonster

3 Answers

7
votes

It's not that hard to get the character metrics. GDI contains a function GetGlyphOutline that you can call with the GGO_METRICS constant to get the height and width of the smallest enclosing rectangle required to contain the glyph when rendered. I.e, a 10 point glyph for a dot in font Arial will give a rectangle of 1x1 pixels, and for the letter I 95x.14 if the font is 100 points in size.

These are the declaration for the P/Invoke calls:

// 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);

The actual code, rather trivial, if you don't consider the P/Invoke redundancies. I tested the code, it works (you can adjust for getting the width as well from GLYPHMETRICS).

Note: this is ad-hoc code, in the real world, you should clean up the HDC's and objects with ReleaseHandle and DeleteObject. Thanks to a comment by user2173353 to point this out.

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

Can you update the question to include what you have tried ?

By dot glyph I assume you mean the punctuation mark detailed here ?

Is this Glyph height displayed on screen or a printed page ?

I managed to modify the first method in the link you posted in order to count the matching vertical pixels, however identifying the largest height of the glyph is fiddly to do unless you are willing to draw character by character, so this wasn't really a general working solution like the article.

In order to have a general working solution would need identify the largest single pixel vertical region of the character / glyph, then count the number of pixels in that region.

I also managed to verify that Graphics.MeasureString, TextRenderer.MeasureText and Graphics.MeasureCharacterRanges all returned the bounding box which gave a number similar to the font height.

The alternative to this is to Glyph.ActualHeight property which gets the rendered height of the framework element. This part of WPF and the related GlyphTypeface and GlyphRun classes. I wasn't able to test them at this time having only Mono.

The steps for getting Glyph.ActualHeight are as follows

  1. Initialise the arguments for GlyphRun

  2. Initialise GlyphRun object

  3. Access relevant Glyph using glyphTypeface.CharacterToGlyphMap[text[n]] or more correctly glyphTypeface.GlyphIndices[n], where glyphTypeface is your GlyphTypeface, which is created from the Typeface object you make in Step 1.

Relevant resources on using them include

Futher references on GDI (What these classes use under the hood is GDI or GDI+) and Fonts in Windows include

2
votes

Here's a solution involving WPF. We create an intermediate Geometry object in order to retrieve the accurate bounding box of our text. The advantage of this solution is that it does not actually render anything. Even if you don't use WPF for your interface, you may use this piece of code to do your measurements only, assuming the font rendering size would be the same in GDI, or close enough.

    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;

Here, "Hello world" measurements are about 77 x 11 units. A single dot gives 1.5 x 1.5.

As an alternative solution, still in WPF, you could use GlyphRun and ComputeInkBoundingBox(). It's a bit more complex and won't support automatic font substitution, though. It would look like this:

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

Rect glyphInkBox = glyphRun.ComputeInkBoundingBox();