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.
- Get the device context (DC) from the graphics.
- Call the
CreateFont
function and create the GDI font.
- Use the font in the DC by calling
SelectObject
.
- Call the
DrawText
function and draw the text onto the specific region of the DC.
- 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) {
var h = new HandleRef (null, hdc.GetHdc ());
var p = new HandleRef (null, NativeMethods.CreateFont
(size, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 4, 0, fontFamily));
try {
NativeMethods.SelectObject (h, p.Handle);
NativeMethods.SetBkMode (h, 1);
NativeMethods.SetTextColor (h, color);
NativeMethods.DrawText (h, text, region, 0x0100);
}
finally {
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