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."
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.
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: Our form displays text at a 45-degree angle.
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.
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!");
}