Training
Certifications
Books
Special Offers
Community




 
Programming Windows®, Fifth Edition
Author Charles Petzold
Pages 1520
Disk 1 Companion CD(s)
Level All Levels
Published 11/11/1998
ISBN 9781572319950
Price $59.99
To see this book's discounted price, select a reseller below.
 

More Information

About the Book
Table of Contents
Sample Chapter
Related Series
Related Books
About the Author

Support: Book & CD

Rate this book
Barnes Noble Amazon Quantum Books

 


Programming Windows, Fifth Ed.

Charles Petzold

ISBN: 1-57231-995-x


Chapter 17: Text and Fonts



Displaying text was one of the first jobs we tackled in this book. Now it's time to explore the use of different fonts and font sizes available in Microsoft Windows and to learn how to justify text.

The introduction of TrueType in Windows 3.1 greatly enhanced the ability of programmers and users to work with text in a flexible manner. TrueType is an outline font technology that was developed by Apple Computer, Inc., and Microsoft Corporation and is supported by many font manufacturers. Because TrueType fonts are continuously scalable and can be used on both video displays and printers, true WYSIWYG (what-you-see-is-what-you-get) is now possible under Windows. TrueType also lends itself well to doing "fancy" font manipulation, such as rotating characters, filling the interiors with patterns, or using them for clipping regions, all of which I'll demonstrate in this chapter.

Simple Text Output

Let's begin by looking at the different functions Windows provides for text output, the device context attributes that affect text, and the use of stock fonts.

The Text Drawing Functions

The most common text output function is the one I've used in very many sample programs so far:

TextOut (hdc, xStart, yStart, pString, iCount) ;

The xStart and yStart arguments are the starting position of the string in logical coordinates. Normally, this is the point at which Windows begins drawing the upper left corner of the first character. TextOut requires a pointer to the character string and the length of the string. The function does not recognize NULL-terminated character strings.

The meaning of the xStart and yStart arguments to TextOut can be altered by the SetTextAlign function. The TA_LEFT, TA_RIGHT, and TA_CENTER flags affect how xStart is used to position the string horizontally. The default is TA_LEFT. If you specify TA_RIGHT in the SetTextAlign function, subsequent TextOut calls position the right side of the last character in the string at xStart. For TA_CENTER, the center of the string is positioned at xStart.

Similarly, the TA_TOP, TA_BOTTOM, and TA_BASELINE flags affect the vertical positioning. TA_TOP is the default, which means that the string is positioned so that yStart specifies the top of the characters in the string. Using TA_BOTTOM means that the string is positioned above yStart. You can use TA_BASELINE to position a string so that the baseline is at yStart. The baseline is the line below which descenders, such as those on the lowercase p, q, and y, hang.

If you call SetTextAlign with the TA_UPDATECP flag, Windows ignores the xStart and yStart arguments to TextOut and instead uses the current position previously set by MoveToEx or LineTo, or another other function that changes the current position. The TA_UPDATECP flag also causes the TextOut function to update the current position to the end of the string (for TA_LEFT) or the beginning of the string (for TA_RIGHT). This is useful for displaying a line of text with multiple TextOut calls. When the horizontal positioning is TA_CENTER, the current position remains the same after a TextOut call.

You'll recall that displaying columnar text in the series of SYSMETS programs in Chapter 4 required that one TextOut call be used for each column. An alternative is the TabbedTextOut function:

TabbedTextOut (hdc, xStart, yStart, pString, iCount,
               iNumTabs, piTabStops, xTabOrigin) ;

If the text string contains embedded tab characters (`\t' or 0x09), TabbedTextOut will expand the tabs into spaces based on an array of integers you pass to the function.

The first five arguments to TabbedTextOut are the same as those to TextOut. The sixth argument is the number of tab stops, and the seventh argument is an array of tab stops in units of pixels. For example, if the average character width is 8 pixels and you want a tab stop every 5 characters, then this array would contain the numbers 40, 80, 120, and so forth, in ascending order.

If the sixth and seventh arguments are 0 or NULL, tab stops are set at every eight average character widths. If the sixth argument is 1, the seventh argument points to a single integer, which is repeated incrementally for multiple tab stops. (For example, if the sixth argument is 1 and the seventh argument points to a variable containing the number 30, tab stops are set at 30, 60, 90… pixels.) The last argument gives the logical x-coordinate of the starting position from which tab stops are measured. This might or might not be the same as the starting position of the string.

Another advanced text output function is ExtTextOut (the Ext prefix stands for extended):

ExtTextOut (hdc, xStart, yStart, iOptions, &rect,
               pString, iCount, pxDistance) ;

The fifth argument is a pointer to a rectangle structure. This is either a clipping rectangle, (if iOptions is set to ETO_CLIPPED, or a background rectangle to be filled with the current background color, if iOptions is set to ETO_OPAQUE. You can specify both options or neither.

The last argument is an array of integers that specify the spacing between consecutive characters in the string. This allows a program to tighten or loosen intercharacter spacing, which is sometimes required for justifying a single word of text in a narrow column. The argument can be set to NULL for default character spacing.

A higher-level function for writing text is DrawText, which we first encountered in the HELLOWIN program in Chapter 3. Rather than specifying a coordinate starting position, you provide a structure of type RECT that defines a rectangle in which you want the text to appear:

DrawText (hdc, pString, iCount, &rect, iFormat) ;

As with the other text output functions, DrawText requires a pointer to the character string and the length of the string. However, if you use DrawText with NULL-terminated strings, you can set iCount to -1 and Windows will calculate the length of the string for you.

When iFormat is set to 0, Windows interprets the text as a series of lines that are separated by carriage-return characters (`\r' or 0x0D) or linefeed characters (`\n' or 0x0A). The text begins at the upper left corner of the rectangle. A carriage return or linefeed is interpreted as a "newline" character, so Windows breaks the current line and starts a new one. The new line begins at the left side of the rectangle, spaced one character height (without external leading) below the previous line. Any text, including parts of letters, that would be displayed to the right or below the bottom of the rectangle is clipped.

You can change the default operation of DrawText by including an iFormat argument, which consists of one or more flags. The DT_LEFT flag (the default) specifies a left-justified line, DT_RIGHT specifies a right-justified line, and DT_CENTER specifies a line centered between the left and right sides of the rectangle. Because the value of DT_LEFT is 0, you needn't include the identifier if you want text to be left-justified only.

If you don't want carriage returns or linefeeds to be interpreted as newline characters, you can include the identifier DT_SINGLELINE. Windows then interprets carriage returns and linefeeds as displayable characters rather than control characters. When using DT_SINGLELINE, you can also specify whether the line is to be placed at the top of the rectangle (DT_TOP, the default), at the bottom of the rectangle (DT_BOTTOM), or halfway between the top and bottom (DT_VCENTER, the V standing for vertical).

When displaying multiple lines of text, Windows normally breaks the lines at carriage returns or linefeeds only. If the lines are too long to fit in the rectangle, however, you can use the DT_WORDBREAK flag, which causes Windows to create breaks at the end of words within lines. For both single-line and multiple-line displays, Windows truncates any part of the text that falls outside the rectangle. You can override this by including the flag DT_NOCLIP, which also speeds up the operation of the function. When Windows spaces multiple lines of text, it normally uses the character height without external leading. If you prefer that external leading be included in the line spacing, use the flag DT_EXTERNALLEADING.

If your text contains tab characters (`\t' or 0x09), you need to include the flag DT_EXPANDTABS. By default, the tab stops are set at every eighth character position. You can specify a different tab setting by using the flag DT_TABSTOP, in which case the upper byte of iFormat contains the character-position number of each new tab stop. I recommend that you avoid using DT_TABSTOP, however, because the upper byte of iFormat is also used for some other flags.

The problem with the DT_TABSTOP flag is solved by a newer DrawTextEx function that has an extra argument:

DrawTextEx (hdc, pString, iCount, &rect, iFormat, &drawtextparams) ;

The last argument is a pointer to a DRAWTEXTPARAMS structure, which is defined like so:

typedef struct tagDRAWTEXTPARAMS
{
    UINT cbSize ;         // size of structure
    int  iTabLength ;     // size of each tab stop
    int  iLeftMargin ;    // left margin
    int  iRightMargin ;   // right margin
    UINT uiLengthDrawn ;  // receives number of characters processed
} DRAWTEXTPARAMS, * LPDRAWTEXTPARAMS ;

The middle three fields are in units that are increments of the average character width.

Device Context Attributes for Text

Besides SetTextAlign discussed above, several other device context attributes affect text. In the default device context, the text color is black, but you can change that with

SetTextColor (hdc, rgbColor) ;

As with pen colors and hatch brush colors, Windows converts the value of rgbColor to a pure color. You can obtain the current text color by calling GetTextColor.

Windows displays text in a rectangular background area that it might or might not color based on the setting of the background mode. You can change the background mode using

SetBkMode (hdc, iMode) ;

where iMode is either OPAQUE or TRANSPARENT. The default background mode is OPAQUE, which means that Windows uses the background color to fill in the rectangular background. You can change the background color by using

SetBkColor (hdc, rgbColor) ;

The value of rgbColor is converted to that of a pure color. The default background color is white.

If two lines of text are too close to each other, the background rectangle of one can obscure the text of another. For this reason, I have often wished that the default background mode were TRANSPARENT. In the TRANSPARENT case, Windows ignores the background color and doesn't color the rectangular background area. Windows also uses the background mode and background color to color the spaces between dotted and dashed lines and the area between the hatches of hatched brushes, as I discussed in Chapter 5.

Many Windows programs specify WHITE_BRUSH as the brush that Windows uses to erase the background of a window. The brush is specified in the window class structure. However, you may want to make the background of your program's window consistent with the system colors that a user can set in the Control Panel program. In that case, you would specify the background color this way in the WNDCLASS structure:

wndclass.hbrBackground = COLOR_WINDOW + 1 ;

When you want to write text to the client area, you can then set the text color and background color using the current system colors:

SetTextColor (hdc, GetSysColor (COLOR_WINDOWTEXT)) ;
SetBkColor (hdc, GetSysColor (COLOR_WINDOW)) ;

If you do this, you'll want your program to be alerted if the system colors change:

case WM_SYSCOLORCHANGE :
    InvalidateRect (hwnd, NULL, TRUE) ;
    break ;

Another device context attribute that affects text is the intercharacter spacing. By default it's set to 0, which means that Windows doesn't add any space between characters. You can insert space by using the function

SetTextCharacterExtra (hdc, iExtra) ;

The iExtra argument is in logical units. Windows converts it to the nearest pixel, which can be 0. If you use a negative value for iExtra (perhaps in an attempt to squeeze characters closer together), Windows takes the absolute value of the number: You can't make the value less than 0. You can obtain the current intercharacter spacing by calling GetTextCharacterExtra. Windows converts the pixel spacing to logical units before returning the value.

Using Stock Fonts

When you call TextOut, TabbedTextOut, ExtTextOut, DrawText, or DrawTextEx to write text, Windows uses the font currently selected in the device context. The font defines a particular typeface and a size. The easiest way to display text with various fonts is to use the stock fonts that Windows provides. However, the range of these is quite limited.

You can obtain a handle to a stock font by calling

hFont = GetStockObject (iFont) ;

where iFont is one of several identifiers. You can then select that font into the device context:

SelectObject (hdc, hFont) ;

Or you can accomplish this in one step:

SelectObject (hdc, GetStockObject (iFont)) ;

The font selected in the default device context is called the system font and is identified by the GetStockObject argument SYSTEM_FONT. This is a proportional ANSI character set font. Specifying SYSTEM_FIXED_FONT in GetStockObject (which I did in a few programs earlier in this book) gives you a handle to a fixed-pitch font compatible with the system font used in versions of Windows prior to version 3. This is convenient when you need all the font characters to have the same width.

The stock OEM_FIXED_FONT, also called the Terminal font, is the font that Windows uses in MS-DOS Command Prompt windows. It incorporates a character set compatible with the original extended character set of the IBM PC. Windows uses DEFAULT_GUI_FONT for the text in window title bars, menus, and dialog boxes.

When you select a new font into a device context, you must calculate the font's character height and average character width using GetTextMetrics. If you've selected a proportional font, be aware that the average character width is really an average and that some characters have a lesser or greater width. Later in this chapter you'll learn how to determine the full width of a string made up of variable-width characters.

Although GetStockObject certainly offers the easiest access to different fonts, you don't have much control over which font Windows gives you. You'll see shortly how you can be very specific about the typeface and size that you want.

Background on Fonts

Much of the remainder of this chapter addresses working with different fonts. Before you get involved with specific code, however, you'll benefit from having a firm grasp of the basics of fonts as they are implemented in Windows.

The Types of Fonts

Windows supports two broad categories of fonts, called "GDI fonts" and "device fonts." The GDI fonts are stored in files on your hard disk. Device fonts are native to an output device. For example, it is common for printers to have a collection of built-in device fonts.

GDI fonts come in three flavors: raster fonts, stroke fonts, and TrueType fonts.

A raster font is sometimes also called a bitmap font, because each character is stored as a bitmap pixel pattern. Each raster font is designed for a specific aspect ratio and character size. Windows can create larger character sizes from GDI raster fonts by simply duplicating rows or columns of pixels. However, this can be done in integral multiples only and within certain limits. For this reason, GDI raster fonts are termed "nonscalable" fonts. They cannot be expanded or compressed to an arbitrary size. The primary advantages of raster fonts are performance (because they are very fast to display) and readability (because they have been hand-designed to be as legible as possible).

Fonts are identified by typeface names. The raster fonts have typeface names of

System (used for SYSTEM_FONT)
FixedSys (used for SYSTEM_FIXED_FONT)
Terminal (used for OEM_FIXED_FONT)
Courier  
MS Serif  
MS Sans Serif (used for DEFAULT_GUI_FONT)
Small Fonts  

Each raster font comes in just a few (no more than six) different sizes. The Courier font is a fixed-pitch font similar in appearance to the font used by a typewriter. The word "serif" refers to small turns that often finish the strokes of letters in a font such as the one used for this book. A "sans serif" font doesn't have serifs. In early versions of Windows, the MS (Microsoft) Serif and MS Sans Serif fonts were called Tms Rmn (meaning that it was a font similar to Times Roman) and Helv (similar to Helvetica). The Small Fonts are especially designed for displaying text in small sizes.

Prior to Windows 3.1, the only other GDI fonts supplied with Windows were the stroke fonts. The stroke fonts are defined as a series of line segments in a "connect-the-dots" format. Stroke fonts are continuously scalable, which means that the same font can be used for graphics output devices of any resolution and the fonts can be increased or decreased to any size. However, performance is poor, legibility suffers greatly at small sizes, and at large sizes the characters look decidedly weak because their strokes are single lines. Stroke fonts are now sometimes called plotter fonts because they are particularly suitable for plotters but not for anything else. The stroke fonts have typeface names of Modern, Roman, and Script.

For both GDI raster fonts and GDI stroke fonts, Windows can "synthesize" boldface, italics, underlining, and strikethroughs without storing separate fonts for each attribute. For italics, for instance, Windows simply shifts the upper part of the character to the right.

Then there is TrueType, to which I'll devote much of the remainder of this chapter.

TrueType Fonts

The individual characters of TrueType fonts are defined by filled outlines of straight lines and curves. Windows can scale these fonts by altering the coordinates that define the outlines.

When your program begins to use a TrueType font of a particular size, Windows "rasterizes" the font. This means that Windows scales the coordinates connecting the lines and curves of each character using "hints" that are included in the TrueType font file. These hints compensate for rounding errors that would otherwise cause a resultant character to be unsightly. (For example, in some fonts the two legs of a capital H should be the same width. A blind scaling of the font could result in one leg being a pixel wider than the other. The hints prevent this from happening.) The resultant outline of each character is then used to create a bitmap of the character. These bitmaps are cached in memory for future use.

Originally, Windows was equipped with 13 TrueType fonts, which have the following typeface names:

Courier New

Courier New Bold

Courier New Italic

Courier New Bold Italic

Times New Roman

Times New Roman Bold

Times New Roman Italic

Times New Roman Bold Italic

Arial

Arial Bold

Arial Italic

Arial Bold Italic

Symbol

In more recent versions of Windows, this list has been expanded. In particular, I'll be making use of the Lucida Sans Unicode font that includes some additional alphabets used around the world.

The three main font families are similar to the main raster fonts. Courier New is a fixed-pitch font designed to look like the output from that antique piece of hardware known as a typewriter. Times New Roman is a clone of the Times font originally designed for the Times of London and used in many printed material. It is considered to be highly readable. Arial is a clone of Helvetica, a sans serif font. The Symbol font contains a collection of handy symbols.

Attributes or Styles?

You'll notice in the list of TrueType fonts shown above that bold and italic styles of Courier, Times New Roman, and Arial seem to be separate fonts with their own typeface names. This naming is very much in accordance with traditional typography. However, computer users have come to think of bold and italic as particular "attributes" that are applied to existing fonts. Windows itself took the attribute approach early on when defining how the raster fonts were named, enumerated, and selected. With TrueType fonts, however, more traditional naming is preferred.

This conflict is not quite ever resolved in Windows. In short, as you'll see, you can select fonts by either naming them fully or by specifying attributes. The process of font enumeration, in which an application requests a list of fonts from the system, is—as you might expect—complicated somewhat by this dual approach.

The Point Size

In traditional typography, you specify a font by its typeface name and its size. The type size is expressed in units called points. A point is very close to 1/72 inch—so close in face that in computer typography it is often defined as exactly 1/72 inch. The text of this book is printed in 10-point type. The point size is usually described as the height of the characters from the top of the ascenders (without diacritics) to the bottom of the descenders, encompassing, for example, the full height of the letters "bq." That's a convenient way to think of the type size, but it's usually not metrically accurate.

The point size of a font is actually a typographical design concept rather than a metrical concept. The size of the characters in a particular font might be greater than or less than what the point size implies. In traditional typography you use a point size to specify the size of a font; in computer typography, there are other methods to determine the actual size of the characters.

Leading and Spacing

As you'll recall from as long ago as Chapter 4, you can obtain information about the font currently selected in the device context by calling GetTextMetrics, as we've also done frequently since then. Chapter 4 included a diagram shown in Figure 4-3 illustrating the vertical sizes of a font from the FONTMETRIC structure.

Another field of the TEXTMETRIC structure is named tmExternalLeading. The word leading (pronounced "ledding") is derived from the lead that typesetters insert between blocks of metal type to add white space between lines of text. The tmInternalLeading value corresponds to the space usually reserved for diacritics; tmExternalLeading suggests an additional space to leave between successive lines of characters. Programmers can use or ignore the external leading value.

When we refer to a font as being 8-point or 12-point, we're talking about the height of the font less internal leading. The diacritics on certain capital letters are considered to occupy the space that normally separates lines of type. The tmHeight value of the TEXTMETRIC structure thus actually refers to line spacing rather than the font point size. The point size can be derived from tmHeight minus tmInternalLeading.

The Logical Inch Problem

As I discussed in Chapter 5 (in the section entitled "The Size of the Device"), Windows 98 defines the system font as being a 10-point font with a 12-point line spacing. Depending on whether you choose Small Fonts or Large Fonts from the Display Properties dialog, this font could have a tmHeight value of 16 pixels or 20 pixels and a tmHeight minus tmInternalLeading value of 13 pixels or 16 pixels. Thus, the choice of the font implies a resolution of the device in dots per inch, namely 96 dpi when Small Fonts are selected and 120 dpi for Large Fonts.

You can obtain this implied resolution of the device by calling GetDeviceCaps with the LOGPIXELSX or LOGPIXELSY arguments. Thus, the metrical distance occupied by 96 or 120 pixels on the screen can be said to be a "logical inch." If you start measuring your screen with a ruler and counting pixels, you'll probably find that a logical inch is larger than an actual inch. Why is this?

On paper, 8-point type with about 14 characters horizontally per inch is perfectly readable. If you were programming a word processing or page-composition application, you would want to be able to show legible 8-point type on the display. But if you used the actual dimensions of the video display, there would probably not be enough pixels to show the character legibly. Even if the display had sufficient resolution, you might still have problems reading actual 8-point type on a screen. When people read print on paper, the distance between the eyes and the text is generally about a foot, but a video display is commonly viewed from a distance of two feet.

The logical inch in effect provides a magnification of the screen, allowing the display of legible fonts in a size as small as 8 points. Also, having 96 dots per logical inch makes the 640-pixel minimum display size equal to about 6.5 inches. This is precisely the width of text that prints on 8.5-inch-wide paper when you use the standard margins of an inch on each side. Thus, the logical inch also takes advantage of the width of the screen to allow text to be displayed as large as possible.

As you may also recall from Chapter 5, Windows NT does it a little differently. In Windows NT, the LOGPIXELSX (pixels per inch) value you obtain from GetDeviceCaps is not equal to the HORZRES value (in pixels) divided by the HORZSIZE value (in millimeters), multiplied by 25.4. Similarly, LOGPIXELSY, VERTRES, and VERTSIZE are not consistent. Windows uses the HORZRES, HORZSIZE, VERTRES, and VERTSIZE values when calculating window and offset extents for the various mapping modes; however, a program that displays text would be better off to use an assumed display resolution based on LOGPIXELSX and LOGPIXELSY. This is more consistent with Windows 98.

So, under Windows NT a program should probably not use the mapping modes provided by Windows when also displaying text in specific point sizes. The program should instead define its own mapping mode based on the logical-pixels-per-inch dimensions consistent with Windows 98. One such useful mapping mode for text I call the "Logical Twips" mapping mode. Here's how you set it:

SetMapMode (hdc, MM_ANISOTROPIC) ;

SetWindowExtEx (hdc, 1440, 1440, NULL) ;
SetViewportExt (hdc, GetDeviceCaps (hdc, LOGPIXELSX),
                     GetDeviceCaps (hdc, LOGPIXELSY), NULL) ;

With this mapping mode set, you can specify font dimensions in 20 times the point size, for example 240 for 12 points. Notice that unlike the MM_TWIPS mapping mode, the values of y increase going down the screen. This is easier when displaying successive lines of text.

Keep in mind that the discrepancy between logical inches and real inches occurs only for the display. On printer devices, there is total consistency with GDI and rulers.


TOP   |   NEXT PAGE

Visit Microsoft Press for more information on Programming Windows, Fifth Ed.

Top of Page


Last Updated: Friday, July 6, 2001