Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Rendering Text with OpenType Fonts Using GDI

0.00/5 (No votes)
6 Mar 2013 1  
Rendering text with OpenType fonts by P/Invoking GDI API in WinForm applications.

Introduction

As you will probably encounter this problem in WinForm development--you can't create System.Drawing.Font instances from OpenType fonts. That's because the implementation of that class has used GDI+, which provides very limited support for OpenType fonts.

There are several way-outs. One is to switch to WPF from WinForm, which supports OpenType fonts, but I guess you won't do so easily, especially when the other parts of your code are fine and nice and the only missing feature is just the OpenType font files support. The second way is to use FreeType, a fabulous font rendering engine. It is viable but you have some good stuff to learn.

Hereby, this tip provides a method of rendering text with OpenType fonts by P/Invoking the system API.

Background

The GDI+ API used by the .NET platform does not support OpenType fonts well, but the system wide API, GDI (without the plus sign, but does more here), supports OpenType much better. We can use Platform Invoke to call functions exposed by the GDI and render texts with OpenType fonts.

The procedure is listed below. The major functions to use are the CreateFont function and the DrawText function.

  1. Get the device context (DC) from the graphics.
  2. Call the CreateFont function and create the GDI font.
  3. Use the font in the DC by calling SelectObject.
  4. Call the DrawText function and draw the text onto the specific region of the DC.
  5. Release the resources by calling DeleteObject.

The C# code is listed below, there are some other functions involved, but they are just listed for your reference.

public static void RenderText(IDeviceContext hdc, string text, 
                string fontFamily, Color color, Rectangle region, int size) {
    // create the handle of DC
    var h = new HandleRef (null, hdc.GetHdc ());
    // create the font
    var p = new HandleRef (null, NativeMethods.CreateFont 
        (size, 0, 0, 0, 0, 0, 0, 0, 1/*Ansi_encoding*/, 0, 0, 4, 0, fontFamily));
    try {
        // use the font in the DC
        NativeMethods.SelectObject (h, p.Handle);
        // set the background to transparent
        NativeMethods.SetBkMode (h, 1);
        // set the color of the text
        NativeMethods.SetTextColor (h, color);
        // draw the text to the region
        NativeMethods.DrawText (h, text, region, 0x0100);
    }
    finally {
        // release the resources
        NativeMethods.DeleteObject (p);
        hdc.ReleaseHdc ();
    }
}

We should firstly prepare our P/Invoke code for use. The protocol of the CreateFont function and DrawText function can be found out from MSDN.

HFONT CreateFont(
  __in  int nHeight,
  __in  int nWidth,
  __in  int nEscapement,
  __in  int nOrientation,
  __in  int fnWeight,
  __in  DWORD fdwItalic,
  __in  DWORD fdwUnderline,
  __in  DWORD fdwStrikeOut,
  __in  DWORD fdwCharSet,
  __in  DWORD fdwOutputPrecision,
  __in  DWORD fdwClipPrecision,
  __in  DWORD fdwQuality,
  __in  DWORD fdwPitchAndFamily,
  __in  LPCTSTR lpszFace
);

int DrawText(
  __in     HDC hDC,
  __inout  LPCTSTR lpchText,
  __in     int nCount,
  __inout  LPRECT lpRect,
  __in     UINT uFormat
);

And the corresponding P/Invoke code can be the following:

const string GDI32 = "gdi32.dll";
struct Rect
{
    public long Left, Top, Right, Bottom;
    public Rect (Rectangle rect) {
        this.Left = rect.Left;
        this.Top = rect.Top;
        this.Right = rect.Right;
        this.Bottom = rect.Bottom;
    }
}
[DllImport (GDI32)]
internal static extern IntPtr CreateFont (
    int nHeight,
    int nWidth,
    int nEscapement,
    int nOrientation,
    int fnWeight,
    uint fdwItalic,
    uint fdwUnderline,
    uint fdwStrikeOut,
    uint fdwCharSet,
    uint fdwOutputPrecision,
    uint fdwClipPrecision,
    uint fdwQuality,
    uint fdwPitchAndFamily,
    string lpszFace
    );
[DllImport (USER32, CharSet = CharSet.Auto)]
static extern int DrawText 
    (HandleRef hDC, string lpchText, int nCount, ref Rect lpRect, uint uFormat);
internal static int DrawText (HandleRef hDC, string text, Rectangle rect, uint format) {
    var r = new Rect (rect);
    return DrawText (hDC, text, text.Length, ref r, format);
}

Using the Code

With the above stuff ready, the rest is quite easy. You just need to gather the parameters and call the RenderText method, like the following code shows:

private void panel1_Paint (object sender, PaintEventArgs e) {
    int size;
    if (Int32.TryParse (comboBox1.Text, out size) == false) {
        size = (int)SystemFonts.DefaultFont.Size;
    }
    RenderText (e.Graphics, textBox1.Text, textBox2.Text, 
                Color.Red, panel1.ClientRectangle, size);
}

You can download the demo application and see how it works. The screen shot at the top of this tip shows an OpenType font LiheiPro is used to render some Chinese and Japanese fonts, which is impossible with the managed .NET API that calls GDI+.

Points of Interest

The GDI methods can return error codes. However, our demo application just assumes everything will go well. You shall refer to the documentation on MSDN and see how to deal with them.

There are quite a few other useful functions exposed by GDI but not covered in this short article. You can find more on MSDN as well.

History

  • Initial post: 2013-March-6

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here