Determining exact glyph height in specified font
Asked Answered
S

3

11

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

Saccharose answered 29/3, 2012 at 11:2 Comment(6)
[If this is of some help to you][1] [1]: #1440051Rampage
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.Ane
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?Saccharose
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?Ane
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 rightAne
I have used the second approach in the provided article. Yeh, I think 8 - is NOT correct height for such a pity glyph like DOT (.)Saccharose
H
7

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;
    }    
}
Hoofer answered 7/4, 2012 at 18:43 Comment(10)
Could you please explain this comment: "// should check Marshal.GetLastError here." ? Why that should be performed and how that should be done?Saccharose
@MichaelZ: that's a common practice, just like try/catch or checking result values in C# programming, when calling Win32 functions you check the "LastError". (EDIT: actually, this function does not call SetLastError, if it goes wrong it simply returns a constant, GDI_ERROR, with no additional info. This is similar to the dreaded "A generic error occurred in GDI+" in GDI+). I'll update the code to reflect this.Hoofer
Why I receive metrics.gmBlackBoxY = 0 for some fonts? Is there any limitations in your solution?Saccharose
@MichaelZ: it's limited to TrueType and OpenType fonts. Raster fonts won't work, because they are not scalable. Also, do test the return value for GDI_ERROR. Can you be more specific about "some fonts"? Oh, and if the font height is too low, it can be rounded down to zero, in which case you should try a high font height, like 100 points, to begin with.Hoofer
I have resolved the problem! I had two fonts in my system Font folder that were derived from Computer Modern Roman family, i.e. I had cmr10.ttf and cmr10.otf. When I have been used your approach with "cmr10" as Font Family Name I had zeros for glyphs metrics. After deleting cmr10.otf the problem was gone.Saccharose
Are you sure the results are in pixels when input font has size in points? I think result also is in points. Don't? If so ALL results are rounded by integer but as you understand that is NOT very precise - I would like to have floating point numbers!Saccharose
@MichaelZ: I didn't invent how Windows handles fonts, but: the method is meant for calculating how fonts are drawn to a device, here, a screen. It takes DPI into account and obviously must use pixel size. If you use a printer device you'll get higher numbers. In my answer I already mentioned that you should use a high font size and divide the result if you want fractional precisioned metrics. Just feed it a font of 1000pt and divide the result by 100, and you have pixel height with two digits precision (even though fractional pixels don't exist).Hoofer
PS: don't get me wrong, this is a limitation of the Windows GDI API. I think your question is genuine and if you want to go the extra mile have a look into using the DirectWrite API. It has far better ways of dealing with fonts, anti-aliasing, design, drawing etc. It's a deep dive so be willing to spend a few days investigating. Here's a starting point with GetDesignGlyphMetrics that gets you all the relative metrics for an array of character indices. Working from .NET 1.1 with COM can be a pain though.Hoofer
Although this answers the question, the code is problematic. Quotes from MSDN: "Calls to the GetHdc and ReleaseHdc methods must appear in pairs." and "When using this method (ToHfont), you must dispose of the resulting Hfont using the GDI DeleteObject method to ensure the resources are released."Aalst
@user2173353: it's been a while that I created that post, but glancing over it now, I would assume that the using{...} around the Graphics object would take care of this. If that is not the case, you are right and at the end of the using-block, a ReleaseHdc and DeleteObject ought to be called. Hmm, thinking a bit more of this: I think it is probably better to use a SafeHandle derivative, instead of IntPtr, which would then automatically take care of cleaning up. I added a warning note to the post with regards to this.Hoofer
N
2

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

Nae answered 1/4, 2012 at 14:28 Comment(1)
Sorry, I can not use this solution because my App should be compiled with .NET 1.1. BTW I have used the second approach from the provided link in the question.Saccharose
A
2

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();
Agonizing answered 3/4, 2012 at 13:3 Comment(3)
Sorry, I can not use this solution because my App should be compiled with .NET 1.1Saccharose
@Michael Z Just curious, why such a requirement ? Do you need to deploy your software on old systems ? .NET 2.0 came out in 2005. Also, if you only have to support a few fonts, you may pre-calculate the glyph sizes locally with a recent .NET version, and embed those pre-calculated tables in your 1.1 release.Agonizing
Unfortunately I need to support NOT few fonts and yes we have to support our products for .NET 1.1Saccharose

© 2022 - 2024 — McMap. All rights reserved.