Welcome to Atalasoft Community Sign in | Help

Measuring Text

I had the task of measuring the length of string rendered in a given font.  I'd like to be able to make this measurement in points, rather than pixels so that I know exactly what my units are at the outset.  This task is not straightforward in .NET, nor is it as straightforward as I'd like using Win32.

I want to start at the outset by saying the .NET text rendering is fundamentally broken.  In fact, Windows text rendering is pretty much broken.  To try to get your text exactly where you want it and in the right proportion is no small feat, and Graphics.DrawString doesn't really help you a lot.  At some future date, I'll probably write some replacement code that does the right thing and a full critique of text rendering, but not today.

At any rate, here is a chunk of code that measures the length of a string to the nearest 10th of a point:

public static double MeasureText(Font font, string text)
    // Assume no width
    double measure = 0.0;

    // we'll need a DC to do anything
    IntPtr hdc = CreateDC("DISPLAY", null, null, IntPtr.Zero);
    IntPtr gdiFont = IntPtr.Zero;

        // try to make the font - in this case, we're going
        // to use 10 * the point size - the negative is used
        // to get make the font mapper map onto character height
        // NOT cell height (which is usually the wrong thing)
        // that 2 squeaked in there is to get a TrueType font
        // I initially tried to do this scaled by 1000, rather than
        // 10 and discovered that GDI silently fails to make a usable
        // 12,000 point font.
        // It might be smarter to make the upscale dynamic, but that
        // would mean knowing what the upper limits of GDI+ are, which
        // is something I'd rather not depend on or measure empirically
        gdiFont = CreateFont((int)(-10 * font.SizeInPoints), 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, font.Name);
        if (gdiFont == IntPtr.Zero)
            throw new Exception("Unknown font " + font.Name);

        // once built, select the font
        SelectObject(hdc, gdiFont);
        SIZE size = new SIZE();
        GetTextExtentPoint32(hdc, text, text.Length, out size);
        // scale it back
        measure = size.cx  / 10.0;
        if (gdiFont != IntPtr.Zero)
        if (hdc != IntPtr.Zero)
    return measure;

// and finally, here is a set of P/Invokes and a struct to make
// this all happen
private static extern IntPtr CreateDC(string driverName, string deviceName, string output, IntPtr lpinitData);
private static extern bool DeleteDC(IntPtr DC);

private static extern IntPtr CreateFont(int size, int a, int b, int c, int d, int e, int f, int g,
            int charSet, int precision, int clipPrecision, int quality, int format, string name);

private static extern bool DeleteObject(IntPtr obj);

private struct SIZE
    public int cx;
    public int cy;

private static extern bool GetTextExtentPoint32(IntPtr hdc, string s, int len, out SIZE size);

When using this, keep in mind that the following will probably never be true:

private double MeasureStringByChars(Font font, string s)
     double measure = 0.0;
     for (int i=0; i < s.Length; i++) {
         measure += MeasureString(font, s.Substring(i, 1));
     return measure;

MeasureStringByChars(font, "foo") == MeasureString(font, "foo")

Fonts are funny and include things like spacing changes depending on which character follows the last.  This is called AutoKerning and for typography purposes is a very good thing.  For example, if you have a 'W' followed by an 'o', you want that 'o' a little further to the left than you would want with say, an 'R' followed by an 'o'.  This way, the 'o' nestles in a little closer in the open space under the W and looks a little more pleasing to the eye.

There was a phrase that I frequently saw around Adobe: "Type is to Read." which is quote from graphic designer William Golden.

Published Wednesday, November 15, 2006 11:31 AM by Steve Hawley


No Comments
Anonymous comments are disabled