Like Cartman, that 'W' letter isn't fat; it's big boned!

by Michael S. Kaplan, published on 2007/03/28 11:36 -04:00, original URI: http://blogs.msdn.com/b/michkap/archive/2007/03/28/1977465.aspx


Eli's question was simple enough:

Is there a way to tell if a System.Drawing.Font object that I’ve created is fixed width or not? Also, once I know whether or not my font is fixed width, is there a way to tell what the width is? The only approach I could find was to use the System.Drawing.Font.ToLogFont function, and pass in a managed LogFont class:

        [StructLayout( LayoutKind.Sequential, CharSet = CharSet.Auto )]
        class LOGFONT {
            public int lfHeight;
            public int lfWidth;
            public int lfEscapement;
            public int lfOrientation;
            public int lfWeight;
            public byte lfItalic;
            public byte lfUnderline;
            public byte lfStrikeOut;
            public byte lfCharSet;
            public byte lfOutPrecision;
            public byte lfClipPrecision;
            public byte lfQuality;
            public byte lfPitchAndFamily;
            [MarshalAs( UnmanagedType.ByValTStr, SizeConst = 32 )]
            public string lfFaceName;
        }

After I call ToLogFont, the lfFaceName string is filled in appropriately, with the name of my fixed width font: “Courier New”.  However, the lfPitchAndFamily is 0. I would have expected to see the low order bit to be set, indicating a FIXED_PITCH. Also, width is set to 0. Am I using this method incorrectly?

Thanks,
Eli

Eli is right -- the call to Font.ToLogFont produces a LOGFONT structure that is woefully incomplete if your goal is to be able to query this sort of information.

But let's not feel too bad, since even when this information is filled in, it is far from perfect. You may recall reading Fonts that are 'fixed-width' even if they do not claim to be if you have been around for a while, and it shows an example of a specific case that fails here. So let's put together something that will work in a wider variety of cases....

Now I am going to use TextRenderer.MeasureText in the sample rather than Graphics.MeasureString since a Graphics object is much harder to get if you don't already have one (if you do have one then you can safely substitute one method for the other):

using System;
using System.Drawing;
using System.Windows.Forms;

public class FixedWidthTest {
    public static void Main(string[] args) {
        Console.WriteLine(IsFixedWidth(args[0]));
    }

    public static  bool IsFixedWidth(string stFontName) {
        Font fnt = new Font(stFontName, 16);
        return (TextRenderer.MeasureText("W", fnt).Width - TextRenderer.MeasureText("i", fnt).Width) == 0;
    }
}

Then you can easily compile and test this sample with all manner of fonts:

e:\test>csc fnt.cs
Microsoft (R) Visual C# 2005 Compiler version 8.00.50727.42
for Microsoft (R) Windows (R) 2005 Framework version 2.0.50727
Copyright (C) Microsoft Corporation 2001-2005. All rights reserved.

e:\test>fnt.exe "Courier New"
True

e:\test>fnt.exe "Segoe UI"
False

e:\test>fnt.exe "Tahoma"
False

e:\test>fnt.exe "Consolas"
True

e:\test>fnt.exe "MS Mincho"
True

e:\test>fnt.exe "MS PMincho"
False

So in the end, I think this technique does a better job then trying to rely on a method that gives you a structure that even if it were complete would not really get the whole job done....

I think I'll post the completely unmanaged version of this method tomorrow (the managed one came first since that is where the question came from!).

 

This post brought to you by W (U+0057, a.k.a. LATIN CAPITAL LETTER W)


# Dirk on 28 Mar 2007 2:48 PM:

The tmAveCharWidth and tmMaxCharWidth members of the TEXTMETRIC structure for "Courier New" are different. I ran TextRenderer.MeasureText for the Chars from 0 to 2000 to see whether they are any differences. The maximum was returned for 0x713. What character is this?

The otmPanoseNumber of the OUTLINETEXTMETRIC structure has PAN_PROP_MONOSPACED for bProportion. This value seems to be set for the fonts that return true in your test. But I do not know if this is consistent and using IsFixedWidth is a lot simpler.

# Michael S. Kaplan on 28 Mar 2007 4:09 PM:

That is SYRIAC LETTER GAMAL, which is not in the font -- you are hitting font linking/fallback, which is not useful here (only characters in the font are)....

# Dirk on 29 Mar 2007 5:39 AM:

Thanks for the explanation. Looking at "Courier New" in charmap I could not find this code point and I suspected some substitution must take place. I just saw your link to the Unicode Character Search.

Is there an easy way in managed code to test whether a particular code point is supported by a font?

# Michael S. Kaplan on 29 Mar 2007 7:14 AM:

In managed code? Not so much. But you can p/invoke to GetGlyphOutline....

# Rosyna on 29 Mar 2007 8:37 AM:

Hmm, your last post was about the Yen, and this post is about a W which is the base of won (₩). I wonder if these are connected or if you just like making fun of chubby letters.

# Michael S. Kaplan on 29 Mar 2007 8:44 AM:

They aren't chubby; they're big-boned!


referenced by

2008/06/09 Did you lose some WIDTH or something?

go to newer or older post, or back to index or month or day