Home > C#, WPF > GlyphRun and So Forth

GlyphRun and So Forth

In WPF there are a number of ways to get text painted, ranging from Label at the simple end, all the way down to GlyphRun if you want to get your hands dirty.

The latter stuff seems very thinly documented. Want to know what the bidiLevel parameter to the constructor of GlyphRun means? According to the documentation, it is "a value of type Int32". Very helpful.

This is a problem because it is your only option if you want to be able to control the positions of individual characters. The layer above is based around the FormattedText class, but it adds wrapping and rich text support, which you may not need, and exposes no ability to individually adjust the character positions.

After fooling around for three hours and (this was the crucial part) stepping through the WPF source code to see how FormattedText does it, I came up with this, which is basically the sample I wish I’d had in the first place. It demonstrates that in fact working directly with the Glyph-related classes isn’t too complicated after all.

It draws the text "Hello, world!" to a DrawingContext, with the standard character spacing.

Typeface typeface = new Typeface(new FontFamily("Arial"), 
                                FontStyles.Italic, 
                                FontWeights.Normal, 
                                FontStretches.Normal);

GlyphTypeface glyphTypeface;
if (!typeface.TryGetGlyphTypeface(out glyphTypeface))
    throw new InvalidOperationException("No glyphtypeface found");

string text = "Hello, world!";
double size = 40;

ushort[] glyphIndexes = new ushort[text.Length];
double[] advanceWidths = new double[text.Length];

double totalWidth = 0;

for (int n = 0; n < text.Length; n++)
{
    ushort glyphIndex = glyphTypeface.CharacterToGlyphMap[text[n]];
    glyphIndexes[n] = glyphIndex;

    double width = glyphTypeface.AdvanceWidths[glyphIndex] * size;
    advanceWidths[n] = width;

    totalWidth += width;
}

Point origin = new Point(50, 50);

GlyphRun glyphRun = new GlyphRun(glyphTypeface, 0, false, size, 
    glyphIndexes, origin, advanceWidths, null, null, null, null, 
    null, null);

dc.DrawGlyphRun(Brushes.Black, glyphRun);

double y = origin.Y;
dc.DrawLine(new Pen(Brushes.Red, 1), new Point(origin.X, y), 
    new Point(origin.X + totalWidth, y));

y -= (glyphTypeface.Baseline * size);
dc.DrawLine(new Pen(Brushes.Green, 1), new Point(origin.X, y), 
    new Point(origin.X + totalWidth, y));

y += (glyphTypeface.Height * size);
dc.DrawLine(new Pen(Brushes.Blue, 1), new Point(origin.X, y), 
    new Point(origin.X + totalWidth, y));
 
Among the major points of interest:
  • It seems to be a widespread believe that to get a GlyphTypeface, you have to pass the actual file path of the font file on your computer to the constructor. But in fact you can get a GlyphTypeface from a Typeface, if you use a method call that, if you search for it on Google, appears to have only ever been used directly by approximately four people. But it works, because FormattedText uses it.
  • You have to turn each character of the string into a different number called a glyph index. But this is just a matter of looking them up in the typeface’s CharacterToGlyphMap property.
  • It is mandatory for you to specify the width of each character. I do this above by filling in the array of widths and just taking the standard width from the GlyphTypeface, but at this point you could do anything you like to the widths.
  • If you want to align the text, you have to figure it out yourself. The origin given to the GlyphRun constructor is the position of the left end of the text and the baseline. The GlyphTypeface tells you about the significant vertical lines (I draw three of them at the end). And you can figure out the total width as you set up the array of individual widths, like I do.
  • There is no built-in support for underline or strikethrough. If you want these, you have to paint them as a very thin rectangle (seriously – it’s what FormattedText does). The typeface contains the suggested place to draw these decorations in properties such as UnderlinePosition and UnderlineThickness.
About these ads
Categories: C#, WPF Tags: ,
  1. Tim
    July 31, 2008 at 7:15 am | #1

    Fabulous post Daniel; so useful for the code I’m writing at the moment. Thank you. When you say “In WPF there are a number of ways to get text painted, ranging from Label at the simple end, all the way down to GlyphRun” would you enumerate all the techniques inbetween?

  2. Daniel Earwicker
    August 11, 2008 at 8:40 am | #2

    What I had in mind was: Label, TextBlock, FlowDocument and its associated viewers, FormattedText, GlyphRun. Arranged in an approximate order where you get more power, but more responsibility, like gradually turning into Spider-Man.

  3. Abdolhosein Vakilzadeh Ebrahimi
    August 21, 2008 at 4:29 am | #3

    Great document about undocumented fact!
    I don’t know it supports Arabic RTL and GPOS table of font, but worth a try.

  4. Abdolhosein Vakilzadeh Ebrahimi
    August 21, 2008 at 5:33 am | #4

    Just now I checked DrawGlyphRun with Arabic text, seems this routine is just a Glyph Renderer and it doesn’t look at special tables in OpenType font, e.g. GPOS, GSUB…

    And about the performance, the FlowDocument is still much faster than DrawText (and it’s friends).

    I wish I had access to FlowDocumentScrollViewer source, so I could tune it to my special needs!

  5. electroglyph
    January 27, 2009 at 10:21 am | #5

    what’s the “dc” in your code sample?

  6. Daniel Earwicker
    January 27, 2009 at 11:49 am | #6

    @electroglyph – sorry I didn’t make that clearer. Above the sample it says I’m rendering to a DrawingContext – that’s what dc is. You can get one passed to you by deriving your own class Foo from FrameworkElement and overriding OnRender. Then reference your Foo in your XAML to get it to appear on the screen.

  7. electroglyph
    January 27, 2009 at 6:45 pm | #7

    Great, thanks for the explanation Daniel. I had an idea for syntax highlighting by drawing a new GlyphRun over an existing one, do you think that’s a terrible idea?

  8. wanlei
    February 5, 2009 at 7:10 am | #8

    thank your help.i want to calculate the width of a character,the GlyphTypeface can get it.

  9. AgeKay
    March 11, 2009 at 12:04 pm | #9

    This is great. I adapted your code and it’s 30x faster than the build-in DrawText method. But now I want to draw the text at a 90 degree angle. Do you have an idea on how to do that?

  10. Daniel Earwicker
    March 11, 2009 at 1:27 pm | #10

    To rotate the text you would push a RotateTransform onto the DrawingContext by calling PushTransform, before doing the drawing. Afterwards, call Pop on the DrawingContext so the rotation won’t apply to any subsequent drawing.

  11. AgeKay
    March 11, 2009 at 1:30 pm | #11

    Oh right. That is very smart! Thanks so much!

  12. January 23, 2012 at 11:58 am | #12

    Great…

  13. xjem
    April 19, 2012 at 3:48 pm | #13

    Unfortunately, GlyphRun does not support automatic font substitution (for example, to display chineese or japanese characters). Any idea how to do it by hand ?

  1. May 29, 2012 at 12:38 am | #1
  2. December 13, 2012 at 11:02 pm | #2

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: