January 1998

Rotated fonts

by Kent Reisdorph

Have you ever needed to print text at an angle? Maybe you wanted a sideways caption for a chart's X-axis, or you needed to print text upside down. If so, you probably noticed right away that the standard Label component can only display text normally--no rotation allowed. In this article, we'll demonstrate how to print rotated text. We'll show you how to use the Windows API to rotate TrueType fonts. By combining the information in this article with the information in our earlier series on creating components, you can easily create your own rotated label component. (See "Building Components" parts 1, 2, and 3 in the September, October, and November 1997 issues of C++Builder Developer's Journal.) If you'd like to review some text-drawing basics before tackling rotated text, see "Text Drawing 101."

 

The LOGFONT structure

One of the great things about the VCL is that it shields you from the API. The Font property is a perfect example. At the API level, you create fonts in Windows by filling out a LOGFONT structure and then calling the CreateFontIndirect() API function. The LOGFONT structure looks like this:
typedef struct tagLOGFONTA
{
    LONG      lfHeight;
    LONG      lfWidth;
    LONG      lfEscapement;
    LONG      lfOrientation;
    LONG      lfWeight;
    BYTE      lfItalic;
    BYTE      lfUnderline;
    BYTE      lfStrikeOut;
    BYTE      lfCharSet;
    BYTE      lfOutPrecision;
    BYTE      lfClipPrecision;
    BYTE      lfQuality;
    BYTE      lfPitchAndFamily;
    CHAR      lfFaceName[LF_FACESIZE];
} LOGFONTA;
The various members of this function specify the font's name, point size, style (bold, italic, underline, strikeout), and other attributes. One of the special attributes is, of course, the font's angle. Most of the time you don't have to worry about all this, because the VCL fills in this structure behind the scenes and creates the font for you. All you have to do is set the Font property's Name, Size, or Style, and VCL takes care of the nitty-gritty details--this is great, because you can deal with fonts at the component level. However, when you get to specialized font operations--such as setting the rotation angle--you need to go to the API and temporarily work around the VCL.

We're concerned with two members of the LOGFONT structure: lfEscapement and lfOrientation. The lfEscapement member controls the font's angle, specified in tenths of degrees (for example, a value of 900 would mean 90 degrees). The lfOrientation member controls the rotation of the individual letters within the text string. This feature is supported only under Windows NT 4.0, so it's probably not something you'll use regularly. If you want to know what each member of the LOGFONT structure does, look up LOGFONT in the Win32 online Help.

Modifying a VCL Font object

To change the escapement and orientation for a VCL Font object, you need to perform the following steps:
  1. Extract the LOGFONT information for the currently selected font.
  2. Modify the lfEscapement and lfOrientation members.
  3. Create a new font from the LOGFONT structure.
  4. Assign the new font to the VCL Font object's Handle property.
You'll extract the LOGFONT settings from the VCL Font object by calling the GetObject() API function. Given a handle to a font object, this function will fill in a LOGFONT structure. The TFont class's Handle property contains the font's handle (HFONT). It looks like this:
LOGFONT lf;
GetObject(Canvas->Font->Handle, 
  sizeof(LOGFONT), &lf);
At this point, the LOGFONT structure has been filled in with information obtained from the form's Font property. The next step is to change the escapement and orientation. Both of these parameters are specified in tenths of degrees. For example, to rotate the font 45 degrees, you'd set lfEscapement and lfOrientation to 450:
lf.lfEscapement = 450;
lf.lfOrientation = 450;
That's easy enough. Next, you need to create a new font from the modified LOGFONT structure and assign that font to the Font object's Handle property. You can do so in one step:
Canvas->Font->Handle = CreateFontIndirect(&lf);
The CreateFontIndirect() function returns a handle to the new font, which you can assign directly to the Font object's Handle property. Now that the new font is ready to go, you can call TextOut() or DrawText() to display the rotated text. The complete code from all the steps is as follows:
Canvas->Font->Name = "Arial";
Canvas->Font->Size = 20;
LOGFONT lf;
GetObject(Canvas->Font->Handle,
  sizeof(LOGFONT), &lf);
lf.lfEscapement = 450;
lf.lfOrientation = 450;
Canvas->Font->Handle = CreateFontIndirect(&lf);
Canvas->Brush->Style = bsClear;
Canvas->TextOut(20, 120, "Hey, it works!!");
To test this code, create a new project and place a button on the form. Enter the code in the event handler for the button's OnClick event. When you click the button, the text will be displayed at a 45-degree angle, as shown in
Figure A.

Figure A: Our form displays text at a 45-degree angle.
[ Figure A ]

Additional considerations

There are a few things you need to know before putting rotated fonts to work. First, rotating fonts in this manner works only with TrueType fonts. Interestingly, the default font for VCL visual components is MS Sans Serif, which isn't a TrueType font. In the preceding example, we set the font name to Arial--if we hadn't done this, then the font rotation would have failed because the default font can't be rotated. You can get around this problem by forcing Windows to use a TrueType font regardless of the font selected. To do so, set the lfOutPrecision member to OUT_TT_ONLY_PRECIS when you set up the LOGFONT structure. For example, the line
lf.lfOutPrecision = OUT_TT_ONLY_PRECIS;
will cause Windows to pick the closest matching TrueType font in the event that the current font isn't TrueType. Another thing to keep in mind is that any time you access the Font property in the normal VCL way, the contents of the LOGFONT structure will be reset. Given that, the following code will fail:
lf.lfEscapement = 450;
lf.lfOutPrecision = OUT_TT_ONLY_PRECIS;
Canvas->Font->Handle = CreateFontIndirect(&lf);
Canvas->Font->Size = 20;
Canvas->TextOut(20, 120, "Test");
All your work setting up the LOGFONT structure is wiped out when you set the font's Size property. The moral here is to set any VCL Font object attributes before modifying the LOGFONT structure. The Win32 online Help states that the lfOrientation member isn't used under Win95 but is used under NT. It further states that you should set the orientation and escapement to the same value. You really don't need to set the orientation--but, just to be safe, you might heed Microsoft's instructions and set both the orientation and escapement.

Finally, under Windows NT, you can rotate both the line of text and the individual characters within the line. To do so, you must set the escapement to the desired angle for the text line and the orientation to the desired angle for the characters within the text. That's not the whole story, though--none of this will have any effect unless you set the graphics mode to use the advanced graphics routines. You do that by calling SetGraphicsMode() as follows:

SetGraphicsMode(Canvas->Handle, GM_ADVANCED);
Canvas->TextOut(20, 120, "Test");
Now both the orientation and escapement values will be taken into account. You may notice some odd spacing problems when rotating characters. Plus, since this is a Windows NT-only feature, you probably won't be able to take advantage of changing the orientation of the characters in most cases. Figure B shows the result of setting the escapement to 1350 and the orientation to 450 in Windows NT 4.0.

Figure B: Here we rotated both the font and the character.
[ Figure B ]

An example

Listings A and B contain a program that illustrates the concepts we've discussed in this article. (You can download our sample files from www.cobb.com/cpb as part of the file jan98.zip.) To create this program, start with a new project, create an event handler for the OnPaint event, and enter the code. Experiment with different escapement and orientation settings (if you're using Windows NT) and see what effects those changes have on the way the text is displayed.

Listing A: FONTTSTU.H

//---------------------------------------------
#ifndef FontTstUH
#define FontTstUH
//---------------------------------------------
#include <vcl\Classes.hpp>
#include <vcl\Controls.hpp>
#include <vcl\StdCtrls.hpp>
#include <vcl\Forms.hpp>
//---------------------------------------------
class TForm1 : public TForm
{
__published:  // IDE-managed Components
  void __fastcall FormPaint(TObject *Sender);
private:  // User declarations
public:  // User declarations
  __fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------
extern TForm1 *Form1;
//---------------------------------------------
#endif
Listing B: FONTTSTU.CPP
//---------------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop

#include "FontTstU.h"
//---------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
  : TForm(Owner)
{
}
//---------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
  // Start with a reasonable size.
  Canvas->Font->Size = 20;
  // Fill in the LOGFONT structure.
  LOGFONT lf;
  GetObject(Canvas->Font->Handle,
    sizeof(LOGFONT), &lf);
  // Change escapement, orientation, output precision
  lf.lfEscapement = 450;
  lf.lfOrientation = 450;
  lf.lfOutPrecision = OUT_TT_ONLY_PRECIS;
  // Create new font; assign to Canvas Font's Handle.
  Canvas->Font->Handle =
    CreateFontIndirect(&lf);
  // The following only works on NT!
  SetGraphicsMode(Canvas->Handle, GM_ADVANCED);
  // Set the brush style to clear.
  Canvas->Brush->Style = bsClear;
  // Display the text in the middle of the form.
  Canvas->TextOut(
    Width/2, Height/2, "Hello World!");
}