Chapter 14  Text and Fonts
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

Writing text to the display was one of the first jobs we tackled in
programming for Windows. Now it's time to explore the use of different font
sizes and learn how to justify text. This chapter is largely restricted to
writing text on the video display. The subject becomes more complex when you
need to write text using the printer. The printer may be capable of using a
greater variety of fonts than the video display, and the characteristics of
these fonts can be quite different from the characteristics of the video
fonts. Therefore, the subject of text and fonts is continued in the next
chapter, "Using the Printer."

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 one we've used in almost all sample
programs:

TextOut (hdc, xStart, yStart, lpString, nCount) ;

The xStart and yStart parameters 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 far
pointer to the character string and the length of the string. The function
knows nothing about NULL-terminated character strings.

The meaning of the xStart and yStart parameters to TextOut can be altered by
the SetTextAlign function. The TA_LEFT, TA_RIGHT, and TA_CENTER flags affect
the horizontal positioning of the character string. For example, if you call
SetTextAlign with the TA_CENTER flag, subsequent TextOut calls position the
center of the string at xStart. Similarly, the TA_TOP, TA_BOTTOM, and
TA_BASELINE flags affect the vertical positioning. If you call SetTextAlign
with the TA_UPDATECP flag, then Windows ignores the xStart and yStart
parameters to TextOut and instead uses the current position previously set
by MoveTo or LineTo. 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). When the horizontal positioning is
TA_CENTER, the current position remains the same after a TextOut call.

You'll recall that displaying columnar-aligned text in the series of SYSMETS
programs in Chapter 2 required that one TextOut call be used for each
column. An alternative is the TabbedTextOut function. 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 ExtTextOut function gives you much more control over the spacing of
individual characters in the text string. The function also lets you specify
a clipping rectangle.

A higher-level function for writing text is DrawText, which we first
encountered in the HELLOWIN program in Chapter 1. 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, lpString, nCount, &rect, wFormat) ;

DrawText also requires a far pointer to the character string and the length
of the string. If you use DrawText with NULL-terminated strings, however,
you can set nCount to -1, and Windows will calculate the length of the
string.

When wFormat is set to 0, Windows interprets the text as a series of lines
that are separated by carriage-return characters (ASCII number 13) or
linefeed characters (ASCII number 10). The text begins at the upper left
corner of the rectangle. A carriage return or linefeed is interpreted as a
"newline" character; 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 a wFormat
parameter, which consists of one or more flags defined in WINDOWS.H and
separated by the C bitwise OR operator. 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 those ASCII numbers as displayable characters rather than as
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). DT_TOP, like DT_LEFT, has a value of 0, so you don't
need to explicitly include the flag.

When displaying multiple lines of text, Windows normally breaks the lines
only at carriage returns or linefeeds. If the lines are too long to fit in
the rectangle, however, you can use the DT_WORDBREAK flag, which causes
Windows to make breaks at the ends 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
Win- dows spaces multiple lines of text, it 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 (ASCII number 9), 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 wFormat contains the
character-position number of the new tab stops. I recommend that you avoid
using DT_TABSTOP, however, because the upper byte of wFormat is also used
for some other flags.


Device Context Attributes for Text

Several device context attributes affect text. In the default device
context, the text color is black, but you can change that:

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.

The spaces between the character strokes are colored in, based on the
setting of the background mode and the background color. You can change the
background mode using:

SetBkMode (hdc, nMode) ;

where nMode is either OPAQUE or TRANSPARENT. The default background mode is
OPAQUE, which means that Windows uses the background color to fill in the
area between the character strokes. 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 the background mode is set to TRANSPARENT,
Windows ignores the background color and doesn't color the area between the
character strokes. 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 you saw in Chapter 12.

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 program. In that case, you would specify the background color this
way:

wndclass.hbrBackground = COLOR_WINDOW + 1 ;

When you want to write text to the client area, you can 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, then 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, nExtra) ;

The nExtra parameter is in logical units. Windows converts it to the nearest
pixel, which can be 0. If you use a negative value for nExtra (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, or DrawText to write text,
Windows uses the font currently selected in the device context. The font
defines a particular typeface and size. The easiest way to write text in a
choice of fonts is to use the six stock fonts that Windows provides. You can
first obtain a handle to a stock font by calling:

hFont = GetStockObject (nFont) ;

where nFont is one of the six identifiers discussed below. You can then
select that font into the device context:

SelectObject (hdc, hFont) ;

Or you can do it in one step:

SelectObject (hdc, GetStockObject (nFont)) ;

GetStockObject is the same function that we used in Chapter 12 to obtain
stock pens and brushes; SelectObject we used in Chapters 12 and 13 to select
pens, brushes, bitmaps, and regions into the device context.

The font selected in the default device context is called the system font
and is identified by the GetStockObject parameter SYSTEM_FONT. This is the
proportional ANSI character set font that Windows uses for text in menus,
dialog boxes, message boxes, and window caption bars. Specifying
SYSTEM_FIXED_FONT in GetStockObject gives you a handle to a fixed-pitch ANSI
font compatible with the system font used in versions of Windows prior to
version 3. We've frequently encountered this font in sample programs in this
book when using a fixed-pitch font seemed to be easier than using a
proportional font. The OEM_FIXED_FONT identifier gives you a handle to a
font that is often called the terminal font. This is the font that Windows
uses for windowed DOS character-mode programs. On most devices, the terminal
font is similar to the fixed-pitch system font but uses the OEM rather than
the ANSI character set. (The ANSI and OEM character sets are discussed in
Chapter 4.)

The identifier ANSI_FIXED_FONT gives you a handle to a Courier font that is
usually smaller than the system or terminal font. You can obtain a handle to
a font with variable character widths by using the identifier ANSI_VAR_FONT.
This returns a handle to a Helvetica or Times Roman font, either of which is
usually smaller than the system font.

Finally, the identifier DEVICE_DEFAULT_FONT is designed to return a handle
to a font that is built into the output device and that is most suitable for
the device. For most graphics-based video displays, no font meets this
condition, so the identifier returns a handle to the system font. For a
dot-matrix printer, however, this identifier returns a handle to a font that
is specific to the printer and that in some cases does not require Windows
to operate the printer in a graphics mode.

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 a given character can have a smaller or
larger width. Later in this chapter you'll learn how to use GetTextExtent to
calculate 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 what font Windows gives you. You'll
see shortly how you can be very specific about the typeface and type size
that you want.


Graying Character Strings

Although TextOut and DrawText are the functions used most often to write
character strings, Windows has another text output function called
GrayString that exists for the express purpose of displaying grayed text.
Windows uses grayed text most often to show disabled items in menus and
dialog boxes. However, you might also want to use some grayed text within
your client area. GrayString is complex and has some limitations. I'll begin
by explaining how GrayString generally works and then show you an easier way
to use it. Here's the general syntax:

GrayString (hdc, hBrush, lpfnOutputFunction, dwData,
     nCount, xStart, yStart, xWidth, yHeight) ;

GrayString uses a call-back function of the form:

BOOL FAR PASCAL OutputFunction (hdc, dwData, nCount)
     HDC       hdc ;
     DWORD     dwData ;
     short     nCount ;
     {
[other program lines]
     return 1 ;
     }

The name of this function must be included in the EXPORTS section of your
mod- ule definition (.DEF) file. You must obtain a far pointer to the
function by calling MakeProcInstance:

lpfnOutputFunction = MakeProcInstance (hInstance, OutputFunction) ;

This far pointer is the third parameter to GrayString. The dwData and nCount
parameters to GrayString are passed as parameters to this call-back
function. Most often, dwData is a far pointer (converted to a doubleword) to
the text you want to gray, and nCount is set to the number of characters.

Here's what happens when you call GrayString. Windows creates a memory
device context and a monochrome bitmap using the xWidth and yHeight
parameters to GrayString. It selects the bitmap into the memory device
context and passes to the call-back function the memory device context
handle and the dwData and nCount parameters.

Within the call-back function, you draw on this memory device context. The
text color has been set to black and the background color to white. The
drawing you do is relative to the upper left corner of this memory device
context rather than the upper left corner of your original device context.
The output function returns a 1 if all goes well. Windows then uses the
PatBlt function to perform a bitwise OR operation on this memory device
context and the 50-percent gray brush. (The ROP code is 0xFA0089.) As a
result, the white pixels in the memory device context are unchanged, and
half the black pixels become white pixels.

Windows then performs a BitBlt operation on this memory device context and
the device context you specified in the GrayString call (probably your
client-area device context), starting at the point (xStart, yStart). The ROP
code is 0xB8074A, which corresponds to a Boolean operation of:

  ((Destination ^ Pattern) & Source) ^ Pattern

You may recall this particular ROP code from Chapter 13. The pixels in your
original device context (the destination) that correspond to white pixels in
the memory device context (the source) are unchanged. The pixels in your
original device context that correspond to black pixels in the memory device
context are colored with the brush specified in the GrayString call. In
other words, every other pixel that you color black in the memory device
context is colored with the brush in your original device context.

The GrayString function has certain limitations. Here are the most important
ones. GrayString requires the MM_TEXT mapping mode. This is because the
xWidth and yHeight parameters to GrayString are treated as device units when
Windows creates the bitmap, but they are treated as logical units when
PatBlt and BitBlt are called.

The memory device context has default device context attributes when Windows
calls the output function. Therefore, if you've selected a non\-default font
into your device context before calling GrayString, this font will not be
selected in the memory device context. You can, however, select the same
font into the memory device context within the output function.

Normally, the dwData parameter points to a character string, and nCount is
the number of characters, but dwData and nCount can really be anything you
want. Be aware of the following:

  ž   If nCount is 0, then Windows assumes that dwData is a zero-terminated
      character string and calculates the number of characters. That
      calculated value is passed to the call-back function.

  ž   If you specify an xWidth or yHeight value of 0, then Windows computes
      the height and width of the string pointed to by dwData. It uses the
      memory device context for this calculation, so these heights and
      widths are based on the height and width of the system font,
      regardless of the font you eventually use in the call-back function.

  ž   If the output function returns 0, then Windows assumes that an error
      has occurred and doesn't draw anything on your original device
      context. However, if you set the nCount parameter to -1 and the output
      function returns 0, then Windows simply transfers the memory device
      context intact to your original device context without first graying
      it. In this case, you have to determine the number of characters in
      the character string yourself, within the call-back function.

If you have a good feel for bitmaps, memory device contexts, and the raster
operations, you might want to sidestep these limitations by writing your own
text-graying function. Or you might prefer to use the easy GrayString syntax
presented below.


The Easy Use of GrayString

In the general syntax to GrayString, the parameter called lpfnOutputFunction
is a long pointer to a call-back function. If you set that parameter to
NULL, Windows uses the TextOut function. You can also set the nWidth and
nHeight parameters to 0. Here's the syntax you'll want to use for writing
grayed text to the display:

GrayString (hdc, GetStockObject (BLACK_BRUSH), NULL,
               (DWORD) lpString, nCount, xStart, yStart, 0, 0) ;

If lpString is a pointer to a NULL-terminated text string, then nCount can
also be set to 0, and Windows will calculate the length. GrayString uses the
system font regardless of the font currently selected in the device context.
The function ignores the device context settings for the text color,
background mode, and background color, and it requires the MM_TEXT mapping
mode.

On color displays, you can also pass to GrayString a brush handle from
CreateSolid-Brush, if you've created a brush of a pure color. On monochrome
displays, however, this brush would become a black-and-white dithered brush,
and you would encounter the same problems as with a gray brush.


Gray Strings Without GrayString

In versions of Windows prior to 3, the display drivers for the EGA and VGA
supported only eight pure colors (black, red, green, blue, yellow, magenta,
cyan, and white) on color displays. Grays had to be simulated using a
dithered pattern of black and white pixels.

Beginning in Windows 3, the EGA and VGA display drivers support 16 colors,
including two shades of gray. This means that you can display gray text
without using the GrayString function.

To do this, first call GetSysColor with a parameter of COLOR_GRAYTEXT:

rgbGrayText = GetSysColor (COLOR_GRAYTEXT) ;

If rgbGrayText is equal to 0L, then your program should use the GrayString
function to draw a grayed string. Otherwise, you can simply set the text
color to rgbGrayText:

SetTextColor (hdc, rgbGrayText) ;



BACKGROUND ON FONTS

Much of the remainder of this chapter concerns working with different fonts.
Before you get involved with specific code, however, you'll benefit from
having a firm grasp of the subject's basics.

The Types of Fonts

Windows supports two broad categories of fonts, called "GDI fonts" and
"device fonts."

The GDI-based fonts are stored in files with the extension .FON. These files
are sometimes called "font resource files," and they are stored in the
SYSTEM subdirectory of your Windows directory. Each file contains one or
more complete fonts. These files are in New Executable format, which you can
verify by running EXEHDR on them. They are library modules, although
somewhat unusual ones in that they contain no code or data. All they contain
are two types of resources: a font directory and the fonts themselves.

Device fonts are internal to the graphics output device. For video display
adapters, device fonts are currently rare. Windows uses the video adapter in
graphics mode, so it must use the GDI fonts and write the pixels to the
video display.

For printers, however, device-based fonts are common. For instance, Windows
can write text to a dot-matrix printer using either the printer's normal
text mode or the printer's graphics mode. With the text mode, Windows uses a
device font and needs to send only the ASCII numbers of the characters out
to the printer. With the graphics mode, Windows uses a GDI font and must
send the pixel patterns to the printer. For laser printers, device fonts can
be stored in ROM within the printer or in ROM cartridges. If the printer
requires a downloadable font that originates from a disk file, this font is
also classified as a device font, because it is specific to the particular
device.

GDI fonts come in two flavors--"raster," or bitmap, fonts (the more common
variety) and "stroke" fonts. In a raster font file, each character is stored
as a bitmap pixel pattern. Figure 14-1 shows a character from a GDI raster
font, blown up so that you can see the pixel formation.

  (Figure 14-1. may be found in the printed book.)

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 only in
integral multiples and within certain limits. Right away, you can probably
perceive one major difference between drawing graphics on the display and
writing text to the display using the GDI raster fonts. Although you can
draw a rectangle of virtually any size, GDI raster fonts are available only
in discrete sizes. You can't write text using a font smaller than the
smallest font. If you want a GDI raster font of a specific size, that size
may not be available.

The GDI stroke fonts partly solve this problem. 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
all aspect ratios and can be increased or decreased to any size. In general,
GDI raster fonts look better than stroke fonts at small sizes because raster
font designs were based on small sizes. At very large sizes, however, the
raster fonts look grainy--as you can see in Figure 14-1--because Windows has
to start doubling rows or columns of pixels. Thus, the stroke fonts are
usually preferable for large sizes, although the characters look somewhat
weak because their strokes are single lines. Figure 14-2 shows a character
from a blown-up GDI stroke font.

  (Figure 14-2. may be found in the printed book.)

For both GDI raster fonts and stroke fonts, Windows can "synthesize"
boldface, italics, underlining, and strikethrough without storing separate
fonts for each attribute. For italics, for instance, Windows simply shifts
the upper part of the character to the right. Because device fonts are
stored and used in a device-specific manner, it is impossible to discuss
them in the same detail as GDI fonts. Sometimes the device can italicize or
boldface a device font, and sometimes it can't. You can obtain such
information from the GetDeviceCaps function using the TEXTCAPS index. If you
want to obtain this information for particular printers, you can use the
GetDeviceCaps function as illustrated in the DEVCAPS1 program in Chapter 11.


Type Talk I: Families and Faces

I've been using the word font rather loosely until now. The more rigorous
definition of font that is preferred by typographers is this: A font is a
complete collection of characters of a particular typeface and a particular
size.

To a typographer, the term typeface denotes not only the style of type (such
as Courier or Helvetica) but also whether the characters are italic or
boldface, for example. When working with Windows, we'll use the word
typeface to denote simply the style of the type. Common typefaces, in
addition to Courier and Helvetica, are Times Roman, Gothic, and Palatino.

Windows groups typefaces into five "families," based on the general
appearance of the type. These families are called Modern, Swiss, Roman,
Script, and Decorative. The most common typefaces are categorized as Modern,
Swiss, or Roman, depending on two characteristics. The first characteristic
involves "stroke width"--the width of the lines that make up the
characters--which can be constant or variable. Typefaces in the Modern
family have constant stroke widths. Typefaces in the Swiss and Roman
families have variable stroke widths. (Typefaces with variable stroke widths
are generally of "variable pitch," which means that the characters have
variable widths. However, it is the stroke width rather than the use of
fixed or variable pitch that determines the family of a particular
typeface.) The second characteristic involves "serifs," which are small
lines that finish off the character strokes. The Swiss family comprises
"sans serif" typefaces (typefaces with no serifs); the Roman family
comprises serif typefaces.

The Script family comprises typefaces that resemble script handwriting. The
Decorative family includes typefaces that comprise "symbols" (sometimes also
called "ornaments" or "dingbats") and can also contain typefaces of
elaborate design such as Old English.

The table below summarizes the grouping of typefaces into families and shows
the identifiers (defined in WINDOWS.H) that programs can use to specify the
font family:

Font Family           Stroke    Usual Pitch  Serifs  Typical Typefaces
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
FF_MODERN             Fixed     Fixed        --      Courier, Elite, Pica

FF_SWISS              Variable  Variable     No      Helvetica, Avant
                                                     Garde

FF_ROMAN              Variable  Variable     Yes     Times Roman, Palatino,
                                                     New Century
                                                     Schoolbook

FF_SCRIPT             --        --           --      Cursive, Zapf
                                                     Chancery

FF_DECORATIVE         --        --           --      Old Symbol, English,
                                                     Zapf Dingbats


WINDOWS.H also includes a sixth font-family identifier, FF_DONTCARE, which a
program can use when it wants to create a font but doesn't care what family
it comes from.

The typeface names shown in this table are the common names by which these
typefaces are known, but when a Windows program specifies a typeface, in
most cases it uses an abbreviation (such as "Helv" for Helvetica and "Tms
Rmn" for Times Roman) rather than the full name. (The reason for this is
that the names Times Roman and Helvetica are copyrighted and cannot be used
to identify fonts not licensed from the copyright holder.) As you'll see,
Windows provides functions for programs to determine the names of the
typefaces available on a particular device. These functions become
particularly important when the device is a printer, because printers can
include many device fonts with names that can be determined only when the
program interrogates the device. Typefaces available for the video display
are shown in Figure 14-3.

  (Figure 14-3. may be found in the printed book.)

Courier, Helv, and Tms Rmn are the raster fonts; the stroke fonts (Modern,
Roman, and Script) have typeface names that are the same as the names of
three font families. The reason that the stroke fonts are not assigned true
typeface names is that they are not really fonts in any traditional
typographical sense.


The Font Resource Files

The SYSTEM subdirectory of the Windows directory contains several files with
the extension .FON. Some of the filenames begin with the letters COUR
(Courier), HELV (Helvetica), TMSR (Times Roman) and SYMBOL (Symbol) followed
by another letter: A, B, C, D, E, or F; these are the font resource files
containing the GDI raster fonts. Each font resource file contains one or
more sizes of a particular typeface. The terminating letter indicates the
resolution and aspect ratio for which the font was designed. The GDI stroke
fonts are stored in the MODERN.FON, ROMAN.FON, and SCRIPT.FON files. Because
the GDI stroke fonts are continuously scalable, they aren't based on a
particular aspect ratio or device resolution.

The system and terminal fonts are stored in files that begin with the name
of the device for which they have been designed (such as CGA, EGA, VGA, and
8514/A) and ending with the words SYS (proportional system font), FIX
(fixed-pitch system font), and OEM (terminal font).

The following table lists the GDI font resource files, the font-family
identifiers, the typefaces, and the character sets:


Filename     Type    Family         Typeface  Character Set
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
___SYS.FON   Raster  FF_SWISS       System    ANSI
___FIX.FON   Raster  FF_DONTCARE    System    ANSI
___OEM.FON   Raster  FF_MODERN      Terminal  OEM
COURx.FON    Raster  FF_MODERN      Courier   ANSI
HELVx.FON    Raster  FF_SWISS       Helv      ANSI
TMSRx.FON    Raster  FF_ROMAN       Tms Rmn   ANSI
SYMBOLx.FON  Raster  FF_DECORATIVE  Symbol    N/A
MODERN.FON   Stroke  FF_MODERN      Modern    OEM
ROMAN.FON    Stroke  FF_ROMAN       Roman     OEM
SCRIPT.FON   Stroke  FF_SCRIPT      Script    OEM


The GDI stroke font files are sometimes referred to as "Set #1." There are
six other sets corresponding to the terminating letter of the COURx.FON,
HELVx.FON, TMSRx.FON, and SYMBOLx.FONT filenames:


                            Pixels per
                            Logical Inch
Set  Letter  Aspect Ratio   X (Horz)       Y (Vert)         Device
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
#2   A       200            96             48               CGA

#3   B       133            96             72               EGA

#4   C       83             60             72               Okidata
                                                            printers

#5   D       167            120            72               IBM, Epson
                                                            printers

#6   E       100            96             96               VGA

#7   F       100            120            120              8514/A



Depending on what printers you've installed and whether you've used the
Windows Setup program to change the video display driver, some of these
files may or may not be present on your hard disk. Also, some users may have
other Windows font files present.

The aspect ratio in the above table is calculated as:


  100  *   horizontal pixels per logical inch


You might recall encountering this peculiar "logical inch" measurement in
Chapter 11, when we explored the information available from GetDeviceCaps
with the LOGPIXELSX and LOGPIXELSY parameters. We quickly established that a
logical inch is different from a real inch.

For an EGA, GetDeviceCaps reports that the device has 96 pixels horizontally
per logical inch and 72 pixels vertically per logical inch. This means that
the raster fonts stored in the COURB.FON, HELVB.FON, TMSRB.FON, and
SYMBOLB.FON files are appropriate for display on an EGA. The fonts in the
Set #2 files are too short for the EGA because they are based on a lower
vertical resolution, and the fonts in Set #4 are too wide because they are
based on a lower horizontal resolution.


Type Talk II: Getting the Point

Type size is expressed in units called "points." A point is very close to
1/72 inch, so close that it's often defined as exactly 1/72 inch. The point
size indicates the height of the characters. For instance, when we speak of
12-point type, we're referring to characters that are 12/72 (1/6) inch high
from the top of the ascenders to the bottom of the descenders.

In Windows, another convenient measurement is the "twip"; this fabricated
word stands for "twentieth of a point," which equals 1/1440 inch. Note that
the pixels-per-logical-inch measurements associated with each of the five
sets of GDI raster fonts are such that the size of each pixel is an integral
number of twips. Or rather, each pixel is an integral number of a
measurement we can call "logical twips":

Pixels per Logical Inch          Logical Twips per Pixel
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
48                               30
60                               24
72                               20
96                               15
120                              12

If you run the EXEHDR utility on the COURx.FON, HELVx.FON, TMSRx.FON, and
SYMBOL.FON font resource files, you'll see in the module description that
the files contain fonts of particular point sizes. The COURx.FON files
comprise 8-point, 10-point, and 12-point fonts. The HELVx.FON and TMSRx.FON
files include those sizes and add 14-point, 18-point, and 24-point fonts.
But here's the catch: These point sizes are dependent on the resolution
given by the number of pixels per logical inch. For instance, the 24-point
Times Roman font in the TMSRB.FON file has characters that are 24 pixels
high. Only on a display that has 72 pixels per vertical inch will the 24
pixels correspond to a 24-point font.


Why Logical Inches?

The fact remains that the EGA actually displays 68 pixels per horizontal
inch and 51 pixels per vertical inch. The logical inch is some 40 percent
larger than the real inch. Why not simply base the fonts on the real
dimensions of the EGA display and forget about this logical-inch business?

On paper, 8-point type with about 14 characters per horizontal inch is
perfectly readable. If you were programming a word-processing or
page-composition application for Windows, 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, each character would be about 6 pixels high and 5
pixels wide. Such characters would not be legible. 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 paper is generally about a foot, but a video display is
commonly viewed from a distance of 2 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-point. You can see this magnification effect
in Windows WRITE when you display the ruler at the top of the client area.

Note also that having 96 pixels per logical inch horizontally makes the
640-pixel-wide display of the CGA and EGA equal to about 6.5 logical inches.
This is precisely the width of text that you'll print on 8.5-inch-wide paper
when you use margins of an inch on each side. So the logical inch also takes
advantage of the width of the screen to allow text to be displayed as large
as possible.

This whole subject of logical inches is relevant only for the video display.
For printers, a logical inch is the same as a real inch.


Type Talk III: Leading and Spacing

When we wrote text to the display in Chapter 2, we obtained information from
GetTextMetrics that allowed us to space the text properly. The five values
from the TEXTMETRIC structure that describe the size of a character were
shown in a diagram (Figure 2-3). Those five character-height values are
shown again in Figure 14-4 on the following page.

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 is the space for diacritics. (For
the terminal font, tmInternalLeading is 0, and characters with diacritics
are simply reduced in size to make room for the diacritics.) The
tmExternalLeading "suggests" an additional space to leave between lines of
characters. Programmers can use or ignore the external leading value. The
Courier, Helvetica, and Times Roman fonts usually have tmExternalLeading
values of 0 and have positive tmInternalLeading values (except in the very
smallest sizes) to more closely approximate how fonts are used in printed
material.

When we refer to a font as being 8-point or 12-point, we're actually talking
about the height of the font less the internal leading. The diacritics on
certain capitals are considered to occupy the space that normally separates
lines of type. The tmHeight value refers to the line spacing. In the case of
the 24-point Times Roman font for the EGA (which conveniently has 72 pixels
per logical inch vertically, or 1 pixel per logical point), the tmHeight
value is 26 and the tmInternalLeading is 2. The line spacing is 28 points.
The size of the font is 26 minus 2, or 24 points. We speak of this as a
24-point font on a 26-point line spacing, which is often abbreviated as
24/26 (and pronounced "twenty-four on twenty-six"). The 10-point Courier,
Helvetica, and Times Roman fonts designed for the EGA all have a tmHeight
value of 12 and a tmInternalLeading value of 2. The line spacing is 12
points, or 1/6 logical inch, which is the normal line spacing of a printer
or typewriter.

  (Figure 14-4. may be found in the printed book.)


The "Logical Twips" Mapping Mode

When I discussed mapping modes in Chapter 11, you might have thought the
MM_TWIPS mapping mode would be used by programs that make heavy use of
formatted text. In this mapping mode, logical units are in terms of 1/20
point. However, you probably won't want to use MM_TWIPS for the video
display, because the mapping mode is based on real inches rather than
logical inches. As a result, your program won't be able to equate the
correct point sizes (8, 10, 12, 14, 18, and 24) of the available screen
fonts to their heights in MM_TWIPS units.

You'll be better off if you define your mapping mode based on the
logical-pixels-per-inch dimensions available from GetDeviceCaps. I call this
the "Logical Twips" mapping mode; here's all you need to set it:

SetMapMode (hdc, MM_ANISOTROPIC) ;
SetWindowExt (hdc, 1440, 1440) ;
SetViewportExt (hdc, GetDeviceCaps (hdc, LOGPIXELSX),
                     GetDeviceCaps (hdc, LOGPIXELSY)) ;

Because the pixels-per-logical-inch values are always divisors of 1440, the
scaling factor for this mapping mode is an integer. With this mapping mode
set, if you want to request a font with 12-point line spacing (as we'll do
shortly), you can specify the height of the font as 240 (12 times 20)
logical units.

If you select a font into your device context and call GetTextMetrics to
obtain the dimensions of the font, you can calculate the type size in points
by using the formula:

  (tm.tmHeight - tm.tmInternalLeading) / 20

The line spacing in points is equal to:

  tm.tmHeight / 20

For some smaller fonts on low-resolution devices, the size and spacing of
the type might actually involve a fraction of a point--for example, 8-point
type with 8.5-point line spacing. To round to the nearest integer point
size, you might instead want to use the formulas:

  (tm.tmHeight - tm.tmInternalLeading + 10) / 20

and:

  (tm.tmHeight + 10) / 20

We'll use the "Logical Twips" mapping mode in the JUSTIFY program toward the
end of this chapter.

Once again, remember that the discrepancy between logical inches and real
inches occurs only for the display. If you use the "Logical Twips" mapping
mode with a printer, you'll simply duplicate the MM_TWIPS mapping mode.



CREATING, SELECTING,AND DELETING LOGICAL FONTS

Now that we've nailed down the concept of logical inches, it's time to talk
about logical fonts. The logical font is the sixth and final type of GDI
object.

A logical font is the description of a font. Like the logical pen and
logical brush, it is an abstract item that becomes real only when it is
selected into a device context. For logical pens (for instance), you can
specify any color you want for the pen, but Windows converts that to a pure
color when you select the pen into the device context. Only then does
Windows know about the color capabilities of the device.

With fonts, this distinction between the logical font that you request and
the real font that you get is much more important, because the logical font
and the real font can be very different. For example, suppose you request a
32-point Zapf Chancery font. Windows returns to your program a handle to a
logical font. Now you select that font into a device context. What happens?
It depends. If the device context is a printer device context for an Apple
LaserWriter Plus, you will indeed be able to write text to the printer using
a 32-point Zapf Chancery font. But if you select this logical font into your
screen device context, you'll get something that only approximates this
font.

This is the process for creating, selecting, and deleting logical fonts:

  1.  Create a logical font by calling CreateFont or CreateFontIndirect.
      These functions return a handle to a font of type HFONT.

  2.  Select the logical font into the device context using SelectObject.
      Windows chooses a real font that matches most closely the logical
      font.

  3.  Determine the size and characteristics of the real font with
      GetTextMetrics. (You can also get the name of the font with
      GetTextFace.) The information lets you properly space the text that
      you write when this font is selected into the device context.

  4.  Delete the logical font by calling DeleteObject. Don't delete the font
      while it is selected in a valid device context, however, and never
      delete stock fonts.

Windows has two functions for creating logical fonts. The first is:

hFont = CreateFont (nHeight  . . .  lpszFaceName) ;

The CreateFont function has more parameters than any other Windows
function--14 of them. The 14 parameters to CreateFont correspond directly to
the 14 fields of the LOGFONT structure. You can also create a logical font
using the LOGFONT structure and the CreateFontIndirect function:

LOGFONT logfont ;
[other program lines]
hFont = CreateFontIndirect (&logfont) ;

In most cases, using CreateFontIndirect and the logical font structure is
neater and more efficient than specifying the 14 parameters to CreateFont.

After you create a logical font, you select it into your device context with
SelectObject:

hFontOld = SelectObject (hdc, hFont) ;

Windows then matches the logical font with a real font. You can determine
the name of the typeface by using the function:

GetTextFace (hdc, sizeof szFaceName, szFaceName) ;

where szFaceName is a character array to receive the name. You can have
Windows copy the various sizes of the font into a structure of type
TEXTMETRIC using the familiar:

GetTextMetrics (hdc, &tm) ;

The GetObject function, which you can use to obtain information about a
logical pen, brush, or bitmap, can also be used for logical fonts:

GetObject (hFont, sizeof (LOGFONT), &logfont) ;

But this function returns only the information that you put into logfont to
create the font in the first place.

You can delete a logical font (but not while it is selected in a device
context) with DeleteObject:

DeleteObject (hFont) ;

The PICKFONT Program

With the PICKFONT program, shown in Figure 14-5, you can create a logical
font and see the characteristics of the real font after the logical font is
selected into the screen device context.

 PICKFONT.MAK

#------------------------
# PICKFONT.MAK make file
#------------------------

pickfont.exe : pickfont.obj pickfont.def pickfont.res
     link pickfont, /align:16, NUL, /nod slibcew libw, pickfont
     rc pickfont.res

pickfont.obj : pickfont.c pickfont.h
     cl -c -Gsw -Ow -W2 -Zp pickfont.c

pickfont.res : pickfont.rc pickfont.h
     rc -r pickfont.rc

 PICKFONT.C

/*-----------------------------------------
   PICKFONT.C -- Font Picker Program
                 (c) Charles Petzold, 1990
  -----------------------------------------*/

#include 
#include "pickfont.h"

long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;
BOOL FAR PASCAL DlgProc (HWND, WORD, WORD, LONG) ;

char    szAppName [] = "PickFont" ;
DWORD   dwAspectMatch = 0L ;
HWND    hDlg ;
LOGFONT lf ;
short   nMapMode = IDD_TEXT ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     HWND     hwnd ;
     MSG      msg ;
     WNDCLASS wndclass ;

     if (!hPrevInstance)
          {
          wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
          wndclass.lpfnWndProc   = WndProc ;
          wndclass.cbClsExtra    = 0 ;
          wndclass.cbWndExtra    = 0 ;
          wndclass.hInstance     = hInstance ;
          wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
          wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
          wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
          wndclass.lpszMenuName  = NULL ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;
          }

     hwnd = CreateWindow (szAppName, "Font Picker",
                          WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, nCmdShow) ;
     UpdateWindow (hwnd) ;
     while (GetMessage (&msg, NULL, 0, 0))
          {
          if (hDlg == 0 || !IsDialogMessage (hDlg, &msg))
               {
               TranslateMessage (&msg) ;
               DispatchMessage  (&msg) ;
               }
          }
     return msg.wParam ;
     }

void MySetMapMode (HDC hdc)
     {
     if (nMapMode == IDD_LTWPS)
          {
          SetMapMode (hdc, MM_ANISOTROPIC) ;
          SetWindowExt (hdc, 1440, 1440) ;
          SetViewportExt (hdc, GetDeviceCaps (hdc, LOGPIXELSX),
                               GetDeviceCaps (hdc, LOGPIXELSY)) ;
          }
     else
          SetMapMode (hdc, MM_TEXT + nMapMode - IDD_TEXT) ;
     }

void ShowMetrics (HWND hDlg)
     {
     static TEXTMETRIC tm ;
     static struct
          {
          short nDlgID ;
          short *pData ;
          }
          shorts [] =
          {
          TM_HEIGHT,     &tm.tmHeight,
          TM_ASCENT,     &tm.tmAscent,
          TM_DESCENT,    &tm.tmDescent,
          TM_INTLEAD,    &tm.tmInternalLeading,
          TM_EXTLEAD,    &tm.tmExternalLeading,
          TM_AVEWIDTH,   &tm.tmAveCharWidth,
          TM_MAXWIDTH,   &tm.tmMaxCharWidth,
          TM_WEIGHT,     &tm.tmWeight,
          TM_OVER,       &tm.tmOverhang,
          TM_DIGX,       &tm.tmDigitizedAspectX,
          TM_DIGY,       &tm.tmDigitizedAspectY
          } ;
     static char    *szFamily [] = { "Don't Care", "Roman",  "Swiss",
                                     "Modern",     "Script", "Decorative" }
;
     BOOL           bTrans ;
     char           szFaceName [LF_FACESIZE] ;
     HDC            hdc ;
     HFONT          hFont ;
     short          i ;

     lf.lfHeight    = GetDlgItemInt (hDlg, IDD_HEIGHT, &bTrans, TRUE) ;
     lf.lfWidth     = GetDlgItemInt (hDlg, IDD_WIDTH,  &bTrans, FALSE) ;
     lf.lfWeight    = GetDlgItemInt (hDlg, IDD_WEIGHT, &bTrans, FALSE) ;

     lf.lfItalic    = (BYTE) (IsDlgButtonChecked (hDlg, IDD_ITALIC) ? 1 : 0)
                    ;
     lf.lfUnderline = (BYTE) (IsDlgButtonChecked (hDlg, IDD_UNDER)  ? 1 : 0)
                    ;
     lf.lfStrikeOut = (BYTE) (IsDlgButtonChecked (hDlg, IDD_STRIKE) ? 1 : 0)
                    ;

     GetDlgItemText (hDlg, IDD_FACE, lf.lfFaceName, LF_FACESIZE) ;

     dwAspectMatch = IsDlgButtonChecked (hDlg, IDD_ASPECT) ? 1L : 0L ;

     hdc = GetDC (hDlg) ;
     MySetMapMode (hdc) ;
     SetMapperFlags (hdc, dwAspectMatch) ;

     hFont = SelectObject (hdc, CreateFontIndirect (&lf)) ;
     GetTextMetrics (hdc, &tm) ;
     GetTextFace (hdc, sizeof szFaceName, szFaceName) ;

     DeleteObject (SelectObject (hdc, hFont)) ;
     ReleaseDC (hDlg, hdc) ;

     for (i = 0 ; i < sizeof shorts / sizeof shorts [0] ; i++)
          SetDlgItemInt (hDlg, shorts[i].nDlgID, *shorts[i].pData, TRUE) ;

     SetDlgItemText (hDlg, TM_PITCH, tm.tmPitchAndFamily & 1 ?
                                                      "VARIABLE":"FIXED") ;

     SetDlgItemText (hDlg, TM_FAMILY, szFamily [tm.tmPitchAndFamily >> 4]) ;
     SetDlgItemText (hDlg, TM_CHARSET, tm.tmCharSet ? "OEM" : "ANSI") ;
     SetDlgItemText (hDlg, TF_NAME, szFaceName) ;
     }

BOOL FAR PASCAL DlgProc (HWND hDlg, WORD message, WORD wParam, LONG lParam)
     {
     switch (message)
          {
          case WM_INITDIALOG :
               CheckRadioButton (hDlg, IDD_TEXT,   IDD_LTWPS,  IDD_TEXT) ;
               CheckRadioButton (hDlg, IDD_ANSI,   IDD_OEM,    IDD_ANSI) ;
               CheckRadioButton (hDlg, IDD_QDRAFT, IDD_QPROOF, IDD_QDRAFT) ;
               CheckRadioButton (hDlg, IDD_PDEF,   IDD_PVAR,   IDD_PDEF) ;
               CheckRadioButton (hDlg, IDD_DONT,   IDD_DEC,    IDD_DONT) ;

               lf.lfEscapement    = 0 ;
               lf.lfOrientation   = 0 ;
               lf.lfOutPrecision  = OUT_DEFAULT_PRECIS ;
               lf.lfClipPrecision = CLIP_DEFAULT_PRECIS ;

               ShowMetrics (hDlg) ;
                                        /* fall through */
          case WM_SETFOCUS :
               SetFocus (GetDlgItem (hDlg, IDD_HEIGHT)) ;
               return FALSE ;

          case WM_COMMAND :
               switch (wParam)
                    {
                    case IDD_TEXT :
                    case IDD_LOMET :
                    case IDD_HIMET :
                    case IDD_LOENG :
                    case IDD_HIENG :
                    case IDD_TWIPS :
                    case IDD_LTWPS :
                         CheckRadioButton (hDlg, IDD_TEXT, IDD_LTWPS,
wParam) ;
                         nMapMode = wParam ;
                         break ;

                    case IDD_ASPECT :
                    case IDD_ITALIC :
                    case IDD_UNDER :
                    case IDD_STRIKE :
                         CheckDlgButton (hDlg, wParam,
                              IsDlgButtonChecked (hDlg, wParam) ? 0 : 1) ;
                         break ;

                    case IDD_ANSI :
                    case IDD_OEM :
                         CheckRadioButton (hDlg, IDD_ANSI, IDD_OEM, wParam)
;
                         lf.lfCharSet = (BYTE) (wParam == IDD_ANSI ? 0 :
                                      255) ;
                         break ;

                    case IDD_QDRAFT :
                    case IDD_QDEF :
                    case IDD_QPROOF :
                         CheckRadioButton (hDlg, IDD_QDRAFT, IDD_QPROOF,
                                                                     wParam)
;
                         lf.lfQuality = (BYTE) (wParam - IDD_QDRAFT) ;
                         break ;

                    case IDD_PDEF :
                    case IDD_PFIXED :
                    case IDD_PVAR :
                         CheckRadioButton (hDlg, IDD_PDEF, IDD_PVAR, wParam)
;
                         lf.lfPitchAndFamily &= 0xF0 ;
                         lf.lfPitchAndFamily |= (BYTE) (wParam - IDD_PDEF) ;
                         break ;
                    case IDD_DONT :
                    case IDD_ROMAN :
                    case IDD_SWISS :
                    case IDD_MODERN :
                    case IDD_SCRIPT :
                    case IDD_DEC :
                         CheckRadioButton (hDlg, IDD_DONT, IDD_DEC, wParam)
                         ;
                         lf.lfPitchAndFamily &= 0x0F ;
                         lf.lfPitchAndFamily |= (BYTE) (wParam-IDD_DONT <<
                                             4) ;
                         break ;

                    case IDD_OK :
                         ShowMetrics (hDlg) ;
                         InvalidateRect (GetParent (hDlg), NULL, TRUE) ;
                         break ;
                    }
               break ;

          default :
               return FALSE ;
          }
     return TRUE ;
     }

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     static char  szText [] =
                      "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPqQqRrSsTtUuVvWwXxYyZz"
;
     static short cxClient, cyClient ;
     HANDLE       hInstance ;
     HDC          hdc ;
     HFONT        hFont ;
     FARPROC      lpfnDlgProc ;
     PAINTSTRUCT  ps ;
     RECT         rect ;

     switch (message)
          {
          case WM_CREATE :
               hInstance = ((LPCREATESTRUCT) lParam)->hInstance ;
               lpfnDlgProc = MakeProcInstance (DlgProc, hInstance) ;
               hDlg = CreateDialog (hInstance, szAppName, hwnd, lpfnDlgProc)
;
               return 0 ;

          case WM_SETFOCUS :
               SetFocus (hDlg) ;
               return 0 ;
          case WM_PAINT :
               hdc = BeginPaint (hwnd, &ps) ;
               MySetMapMode (hdc) ;
               SetMapperFlags (hdc, dwAspectMatch) ;
               GetClientRect (hDlg, &rect) ;
               rect.bottom += 1 ;
               DPtoLP (hdc, (LPPOINT) &rect, 2) ;

               hFont = SelectObject (hdc, CreateFontIndirect (&lf)) ;

               TextOut (hdc, rect.left, rect.bottom, szText, 52) ;

               DeleteObject (SelectObject (hdc, hFont)) ;
               EndPaint (hwnd, &ps) ;
               return 0 ;

          case WM_DESTROY :
               PostQuitMessage (0) ;
               return 0 ;
          }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }

 PICKFONT.RC

/*-----------------------------
   PICKFONT.RC resource script
  -----------------------------*/

#include 
#include "pickfont.h"

#define GT (WS_GROUP | WS_TABSTOP)
#define GR (WS_GROUP)
#define TA (WS_TABSTOP)

PickFont DIALOG 0, 0, 320, 170
     STYLE WS_CHILD | WS_BORDER | WS_VISIBLE | DS_ABSALIGN
     {
     LTEXT         "&Height"          -1,            6,   8,  30,   8
     EDITTEXT                         IDD_HEIGHT,   36,   6,  30,  12
     LTEXT         "&Width"           -1,            6,  24,  30,   8
     EDITTEXT                         IDD_WIDTH,    36,  22,  30,  12
     LTEXT         "Weigh&t"          -1,            6,  40,  30,   8
     EDITTEXT                         IDD_WEIGHT,   36,  38,  30,  12
     GROUPBOX      "&Mapping Mode"    -1,           70,   2, 116,  60
     RADIOBUTTON   "Text"             IDD_TEXT,     74,  12,  50,  12, GT
     RADIOBUTTON   "Lo Metric"        IDD_LOMET,    74,  24,  50,  12
     RADIOBUTTON   "Hi Metric"        IDD_HIMET,    74,  36,  50,  12
     RADIOBUTTON   "Lo English"       IDD_LOENG,   130,  12,  54,  12
     RADIOBUTTON   "Hi English"       IDD_HIENG,   130,  24,  52,  12
     RADIOBUTTON   "Twips"            IDD_TWIPS,   130,  36,  52,  12
     RADIOBUTTON   "'Logical Twips'", IDD_LTWPS,    74,  48,  80,  12
     CHECKBOX      "Match &Aspect"    IDD_ASPECT,    6,  52,  60,  12, GT
     CHECKBOX      "&Italic"          IDD_ITALIC,    6,  64,  60,  12, GT
     CHECKBOX      "&Underline"       IDD_UNDER,     6,  76,  60,  12, GT
     CHECKBOX      "&Strike-Out"      IDD_STRIKE,    6,  88,  60,  12, GT
     GROUPBOX      "&Char Set"        -1,           70,  66,  52,  36, GR
     RADIOBUTTON   "ANSI"             IDD_ANSI,     74,  76,  28,  12, GT
     RADIOBUTTON   "OEM"              IDD_OEM,      74,  88,  28,  12
     GROUPBOX      "&Quality"         -1,            4, 102,  62,  48, GR
     RADIOBUTTON   "Draft"            IDD_QDRAFT,    8, 112,  42,  12, GT
     RADIOBUTTON   "Default"          IDD_QDEF,      8, 124,  40,  12
     RADIOBUTTON   "Proof"            IDD_QPROOF,    8, 136,  42,  12
     GROUPBOX      "&Pitch"           -1,           70, 102,  52,  48, GR
     RADIOBUTTON   "Default"          IDD_PDEF,     74, 112,  46,  12, GT
     RADIOBUTTON   "Fixed"            IDD_PFIXED,   74, 124,  46,  12
     RADIOBUTTON   "Variable"         IDD_PVAR,     74, 136,  46,  12
     GROUPBOX      "&Family"          -1,          126,  66,  60,  84, GR
     RADIOBUTTON   "Don't Care"       IDD_DONT,    130,  76,  54,  12, GT
     RADIOBUTTON   "Roman"            IDD_ROMAN,   130,  88,  54,  12
     RADIOBUTTON   "Swiss"            IDD_SWISS,   130, 100,  54,  12
     RADIOBUTTON   "Modern"           IDD_MODERN,  130, 112,  54,  12
     RADIOBUTTON   "Script"           IDD_SCRIPT,  130, 124,  54,  12
     RADIOBUTTON   "Decorative"       IDD_DEC,     130, 136,  52,  12
     LTEXT         "Face &Name"       -1,            4, 154,  42,   8
     EDITTEXT                         IDD_FACE,     48, 152,  74,  14
     DEFPUSHBUTTON "&Ok"              IDD_OK,      126, 152,  60,  14, GT
     GROUPBOX      "Text Metrics"     -1,          192,   2, 110, 164, GR
     LTEXT         "Height:"          -1,          200,  14,  44,   8
     LTEXT         "Ascent:"          -1,          200,  24,  44,   8
     LTEXT         "Descent:"         -1,          200,  34,  46,   8
     LTEXT         "Int Lead:"        -1,          200,  44,  44,   8
     LTEXT         "Ext Lead:"        -1,          200,  54,  44,   8
     LTEXT         "Ave Width:"       -1,          200,  64,  44,   8
     LTEXT         "Max Width:"       -1,          200,  74,  44,   8
     LTEXT         "Weight:"          -1,          200,  84,  44,   8
     LTEXT         "Pitch:"           -1,          200,  94,  44,   8
     LTEXT         "Family:"          -1,          200, 104,  42,   8
     LTEXT         "Char Set:"        -1,          200, 114,  44,   8
     LTEXT         "Overhang:"        -1,          200, 124,  44,   8
     LTEXT         "X Aspect:"        -1,          200, 134,  44,   8
     LTEXT         "Y Aspect:"        -1,          200, 144,  44,   8
     LTEXT         "Face Name:"       -1,          200, 154,  44,   8
     RTEXT         "0"                TM_HEIGHT,   250,  14,  44,   8
     RTEXT         "0"                TM_ASCENT,   250,  24,  44,   8
     RTEXT         "0"                TM_DESCENT,  250,  34,  44,   8
     RTEXT         "0"                TM_EXTLEAD,  250,  54,  44,   8
     RTEXT         "0"                TM_INTLEAD,  250,  44,  44,   8
     RTEXT         "0"                TM_AVEWIDTH, 250,  64,  44,   8
     RTEXT         "0"                TM_MAXWIDTH, 250,  74,  44,   8
     RTEXT         "0"                TM_WEIGHT,   250,  84,  44,   8
     RTEXT         ""                 TM_PITCH,    250,  94,  44,   8
     RTEXT         ""                 TM_FAMILY,   250, 104,  44,   8
     RTEXT         ""                 TM_CHARSET,  250, 114,  44,   8
     RTEXT         "0"                TM_OVER,     250, 124,  44,   8
     RTEXT         "0"                TM_DIGX,     250, 134,  44,   8
     RTEXT         "0"                TM_DIGY,     250, 144,  44,   8
     RTEXT         ""                 TF_NAME,     250, 154,  44,   8
     }

 PICKFONT.H

/*------------------------
   PICKFONT.H header file
  ------------------------*/

#define IDD_OK         1
#define IDD_HEIGHT    10
#define IDD_WIDTH     11
#define IDD_WEIGHT    12
#define IDD_ITALIC    13
#define IDD_UNDER     14
#define IDD_STRIKE    15
#define IDD_ASPECT    16
#define IDD_TEXT      17
#define IDD_LOMET     18
#define IDD_HIMET     19
#define IDD_LOENG     20
#define IDD_HIENG     21
#define IDD_TWIPS     22
#define IDD_LTWPS     23
#define IDD_ANSI      24
#define IDD_OEM       25
#define IDD_QDRAFT    26
#define IDD_QDEF      27
#define IDD_QPROOF    28
#define IDD_PDEF      29
#define IDD_PFIXED    30
#define IDD_PVAR      31
#define IDD_DONT      32
#define IDD_ROMAN     33
#define IDD_SWISS     34
#define IDD_MODERN    35
#define IDD_SCRIPT    36
#define IDD_DEC       37
#define IDD_FACE      38
#define TM_HEIGHT     39
#define TM_ASCENT     40
#define TM_DESCENT    41
#define TM_INTLEAD    42
#define TM_EXTLEAD    43
#define TM_AVEWIDTH   44
#define TM_MAXWIDTH   45
#define TM_WEIGHT     46
#define TM_PITCH      47
#define TM_FAMILY     48
#define TM_CHARSET    49
#define TM_OVER       50
#define TM_DIGX       51
#define TM_DIGY       52
#define TF_NAME       53

 PICKFONT.DEF

;-------------------------------------
; PICKFONT.DEF module definition file
;-------------------------------------

NAME           PICKFONT

DESCRIPTION    'Font Picker Program (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc
               DlgProc

Figure 14-6 shows a typical PICKFONT screen. The left side of the PICKFONT
display is a modeless dialog box that allows you to select most of the
fields of the logical font structure. The right side shows the results of
GetTextMetrics after the font is selected into the device context. A sample
line of text using this font appears at the bottom of the screen.

The modeless dialog box also contains some options that are not part of the
logical font structure. These are the mapping mode (including my "Logical
Twips" mapping mode) and the "Match Aspect" option, which changes the way
Windows matches a logical font to a real font.

Much of the PICKFONT program contains the logic necessary to maintain the
dialog box, so I won't go into detail on the workings of the program.
Instead, I'll explain what you're doing when you create and select a logical
font.

  (Figure 14-6. may be found in the printed book.)


The Logical Font Structure

As I mentioned, the best way to create a logical font is to first define a
structure of type LOGFONT:

LOGFONT logfont ;

When you call CreateFontIndirect, you give Windows a pointer to this
structure:

hFont = CreateFontIndirect (&logfont) ;

The 14 fields of this structure are the same as the 14 parameters to
CreateFont.

You don't need to set each and every field of the LOGFONT structure. If your
logical font structure is defined as a static variable, it will be
initialized to 0. The 0 values are defaults. You can use that structure
directly without any changes, and CreateFontIndirect will return a handle to
a font. When you select that font into the device context, you'll get a
reasonable default font. You can be as specific or as vague as you want in
the LOGFONT structure, and Windows will attempt to match your requests with
a real font.

The first two fields of the LOGFONT structure are in logical units, so they
depend on the current setting of the mapping mode:

  ž   lfHeight (short integer)--This is the desired height of the characters
      (including internal leading but not external leading) in logical
      units. Because the point size of the font itself is the height of the
      font less internal leading, you're really specifying a line spacing
      here. You can set it to 0 for a default size. If you set lfHeight to a
      negative number, Windows treats the absolute value of that number as a
      desired ascent size rather than as a full height.

  ž   lfWidth (short integer)--This is the desired width of the characters
      in logical units. In most cases you'll want to set this to 0 and let
      Windows choose a font based on the height. If you use a nonzero value,
      Windows might be forced to use a font designed for an aspect ratio
      different from that of the device context into which you later select
      the font.

The next two fields specify the "escapement" and "orientation" of the text.
In theory, lfEscapement allows character strings to be written at an angle,
and lfOrientation allows characters to be tilted. These two fields are not
included in the PICKFONT program, however, because they currently don't work
well on the screen. Before you try to use these on a device, you should use
the TEXTCAPS index to GetDeviceCaps to check the device's ability to do
character rotation.

  ž   lfEscapement (short integer)--This is an angle in tenths of a degree,
      measured from the horizontal in a counterclockwise direction. It
      specifies the placement of the string when you write text with
      TextOut. Here are some examples:

      Value  Placement of Characters
      ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
      0      Run from left to right (default)
      900    Go up
      1800   Run from right to left
      2700   Go down

  ž   lfOrientation (short integer)--This is an angle in tenths of a degree,
      measured from the horizontal in a counterclockwise direction. It
      specifies the appearance of each character. Here are some examples:

      Value  Character Appearance
      ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
      0      Normal (default)
      900    Tipped 90 degrees to the left
      1800   Upside down
      2700   Tipped 90 degrees to the right

      The remaining 10 fields follow:

  ž   lfWeight (short integer)--This field allows you to specify boldface.
      Currently, there are only two recommended values:

      Value  Result
      ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
      400    Normal
      700    Boldface

      In actuality, any value from 0 to 550 is normal, and any value greater
      than 550 is boldface. If you like to plan for the future, WINDOWS.H
      has a collection of font weight identifiers:

      Value  Identifier
      ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
      0      FW_DONTCARE
      100    FW_THIN
      200    FW_EXTRALIGHT or FW_ULTRALIGHT
      300    FW_LIGHT
      400    FW_NORMAL or FW_REGULAR
      500    FW_MEDIUM
      600    FW_SEMIBOLD or FW_DEMIBOLD
      700    FW_BOLD
      800    FW_EXTRABOLD or FW_ULTRABOLD
      900    FW_HEAVY or FW_BLACK

  ž   lfItalic (BYTE)--When nonzero, this specifies italics. Windows can
      synthesize italics on GDI fonts. To determine what a particular device
      can do with a device font, check the TC_IA_ABLE bit of the TEXTCAPS
      value returned from GetDeviceCaps.

  ž   lfUnderline (BYTE)--When nonzero, this specifies underlining, which is
      synthesized on GDI fonts. For device fonts, check the TC_UA_ABLE bit
      from GetDeviceCaps.

  ž   lfStrikeOut (BYTE)--When nonzero, this specifies that the font should
      have a line drawn through the characters. This also is synthesized on
      GDI fonts. For device fonts, check the TC_SO_ABLE bit.

  ž   lfCharSet (BYTE)--This is the character set of the font. WINDOWS.H
      currently contains three identifiers for the character set:

      Value  Identifier
      ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
      0      ANSI_CHARSET
      2      SYMBOL_CHARSET
      128    SHIFTJIS_CHARSET (Japanese Kanji)
      255    OEM_CHARSET

      Note: The Kanji character sets are not, of course, included with
      American or Western European releases of Windows.

  ž   lfOutPrecision (BYTE)--This specifies how Windows should attempt to
      match the desired font sizes and characteristics with actual fonts.
      This field is not yet implemented and is not included in the PICKFONT
      program. WINDOWS.H contains four identifiers for the field:

      Value  Identifier
      ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
      0      OUT_DEFAULT_PRECIS
      1      OUT_STRING_PRECIS
      2      OUT_CHARACTER_PRECIS
      3      OUT_STROKE_PRECIS

  ž   lfClipPrecision (BYTE)--This specifies how to clip characters that are
      partly outside the clipping region. The field is not included in the
      PICKFONTS program. WINDOWS.H contains three identifiers:

      Value  Identifier
      ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
      0      CLIP_DEFAULT_PRECIS
      1      CLIP_CHARACTER_PRECIS
      2      CLIP_STROKE_PRECIS

  ž   lfQuality (BYTE)--This is actually an instruction to Windows regarding
      the matching of a desired font with a real font. You can use three
      identifiers:

      Value  Identifier
      ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
      0      DEFAULT_QUALITY
      1      DRAFT_QUALITY
      2      PROOF_QUALITY

      If you specify PROOF_QUALITY, you're telling Windows that you don't
      want a font to be increased to a larger size to match the character
      height or width that you request. The PROOF_QUALITY fonts are the most
      attractive, but they may be smaller than what you request.

  ž   lfPitchAndFamily (BYTE)--This byte is composed of two parts. You can
      use the C OR operator to combine two identifiers for this field. The
      lowest two bits specify the pitch of the font:

      Value  Identifier
      ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
      0      DEFAULT_PITCH
      1      FIXED_PITCH
      2      VARIABLE_PITCH

      If you specify FIXED_PITCH, Windows will pick a font that has a fixed
      pitch, because you're essentially telling Windows that your program
      can't deal with variable-pitch fonts.

      The upper half of this byte specifies the font family:

      Value  Identifier
      ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
      0x00   FF_DONTCARE
      0x10   FF_ROMAN
      0x20   FF_SWISS
      0x30   FF_MODERN
      0x40   FF_SCRIPT
      0x50   FF_DECORATIVE

  ž   lfFaceName (BYTE array)--This is the name of a typeface (such as
      Courier, Helv, or Tms Rmn). WINDOWS.H includes a LF_FACESIZE
      identifier that equals 32, which is the maximum number of characters
      allowed for the typeface name.


The Font-Mapping Algorithm

After you set up the logical font structure, you call CreateFontIndirect to
get a handle to the logical font. When you use SelectObject to select that
logical font into a device context, Windows finds the real font that most
closely matches the request. In doing so, it uses a "font-mapping
algorithm." Certain fields of the structure are more important than other
fields.

The best way to get a feel for font mapping is to spend an hour or so
experimenting with PICKFONT. Here are some general guidelines:

  ž   The lfCharSet (character set) field is very important. For the
      display, if you specify OEM_CHARSET, you'll get either one of the
      stroke fonts or the terminal font, because these are the only fonts
      that do not use the ANSI character set. You have to use OEM_CHARSET if
      you want a GDI stroke font. A value of ANSI_CHARSET always gives you a
      raster font.

  ž   A pitch value of FIXED_PITCH (in the lfPitchAndFamily field) is
      important, because you are in effect telling Windows that you don't
      want to deal with a variable-pitch font.

  ž   The lfFaceName field is important, because you're being specific about
      the typeface of the font that you want. If you leave lfFaceName set to
      NULL and set the lfFamily field to a value other than FF_DONTCARE,
      then the latter field becomes important, because you're being specific
      about the font family.

  ž   Windows will attempt to match the lfHeight value even if it needs to
      increase the size of a smaller font. The height of the actual font
      will always be less than or equal to that of the requested font unless
      there is no font small enough to satisfy your request.

  ž   You can prevent Windows from scaling a font by setting lfQuality to
      PROOF_QUALITY. By doing so, you're telling Windows that the requested
      height of the font is less important than the appearance of the font.

  ž   If you specify lfHeight and lfWidth values that are out of line for
      the particular aspect ratio of the display, Windows can map to a font
      that is designed for a display or other device of a different aspect
      ratio. You can use this trick to get a particularly thin or fat font.
      In general, however, you'll probably want to avoid this situation,
      which you do in PICKFONT by clicking the check box marked Match
      Aspect. PICKFONT makes a call to SetMapperFlags with a flag set to 1:

      SetMapperFlags (hdc, 1L) ;

      This specifies that Windows should only match fonts that have the same
      aspect ratio as the display. You can get fonts of other aspect ratios
      by setting the mapper flag back to the default value:

      SetMapperFlags (hdc, 0L) ;

      If you don't like the way that Windows weights these various
      characteristics of the logical font to match a real font, you can
      change them all using the SetFontMapperFlags function.


Finding Out About the Font

At the right side of its client area, PICKFONT shows you the type of
information you can obtain after you select the logical font into the device
context. GetTextMetrics tells you the real characteristics of the font, and
GetTextFace tells you the typeface name.

To obtain the typeface name, you first define a character array to receive
the name:

char szFaceName [LF_FACESIZE] ;

The LF_FACESIZE identifier is the maximum number of characters in the
typeface name. You then tell Windows to copy the typeface name into this
array:

GetTextFace (hdc, sizeof szFaceName, szFaceName) ;

The GetTextMetrics function retrieves information on the size and other
characteristics of the font currently selected in the device context:

TEXTMETRIC     tm ;
[other program lines]
GetTextMetrics (hdc, &tm) ;

All the size values that Windows copies into the TEXTMETRIC structure are in
logical units except for the digitized aspect ratios. The fields of the
TEXTMETRIC structure are as follows:

  ž   tmHeight (short integer)--The height of the character in logical
      units. This is the value that should approximate the lfHeight field
      specified in the LOGFONT structure. It is the sum of the tmAscent and
      tmDescent fields. Like the lfHeight field in the LOGFONT structure, it
      actually represents the line spacing of the font rather than the size,
      because it includes internal leading.

  ž   tmAscent (short integer)--The height of the character above the
      baseline in logical units. This should approximate the absolute value
      of the lfHeight field in the LOGFONT structure if lfHeight is set to a
      negative value.

  ž   tmDescent (short integer)--The height of the character below the
      baseline in logical units.

  ž   tmInternalLeading (short integer)--The area used for diacritics on
      some capital letters. As noted above, the actual internal leading is
      included in the tmHeight value. You can calculate the point size of
      the font by subtracting the tmInternalLeading value from the tmHeight
      value.

  ž   tmExternalLeading (short integer)--An additional amount of line
      spacing (beyond tmHeight) recommended by the designer of the font.

  ž   tmAveCharWidth (short integer)--The average width of the characters in
      logical units.

  ž   tmMaxCharWidth (short integer)--The width of the widest character in
      logical units. This value is the same as tmAveCharWidth for a
      fixed-pitch font.

  ž   tmWeight (short integer)--The weight of the font, ranging from 0 to
      999. Currently, it will be set to either 400 (normal) or 700
      (boldface).

  ž   tmItalic (BYTE)--Nonzero for an italic font.

  ž   tmUnderlined (BYTE)--Nonzero for an underlined font.

  ž   tmStruckOut (BYTE)--Nonzero for a strikethrough font.

  ž   tmPitchAndFamily (BYTE)--A value comprising the pitch in the lower two
      bits and the family in the higher four bits. This field is coded in
      the same way as the lfPitchAndFamily field in the LOGFONT structure,
      and you can use the same identifiers to extract the information.

  ž   tmCharSet (BYTE)--The character set. Under most circumstances, it will
      be either 0 (ANSI_CHARSET) or 255 (OEM_CHARSET).

  ž   tmOverhang (short integer)--The amount of extra width (in logical
      units) that Windows adds to a character when synthesizing italic or
      boldface. When a font is italicized, the tmAveCharWidth value remains
      unchanged, because a string of italicized characters has the same
      overall width as the same string of normal characters. For boldfacing,
      Windows must slightly expand the width of each character. For a
      boldfaced font, the tmAveCharWidth value less the tmOverhang value
      equals the tm- AveCharWidth value for the same font without
      boldfacing.

  ž   tmDigitizedAspectX and tmDigitizedAspectY (short integers)--The aspect
      ratio for which the font is appropriate. If you specify Proof (under
      Quality) and check Match Aspect in PICKFONT, then for most devices
      these values will be equivalent to the pixels-per-logical-inch values
      returned from GetDeviceCaps. Note, however, that these two TEXTMETRIC
      fields are switched around in relation to the corresponding
      GetDeviceCaps parameters: tmDigitizedAspectX is equivalent to the
      GetDeviceCaps value for the LOGPIXELSY parameter, and
      tmDigitizedAspectY is equivalent to the value for LOGPIXELSX. If the
      font is scaled to a larger size, then the tmDigitizedAspectX and
      tmDigitizedAspectY values increase accordingly.

Because of space restrictions, the following four fields were not included
in the display screen of the PICKFONTS program:

  ž   tmFirstChar (BYTE)--The character code of the first character in the
      font. For ANSI_CHARSET fonts, this is normally 32, the space
      character.

  ž   tmLastChar (BYTE)--The character code of the last character in the
      font. For ANSI_CHARSET fonts, this is normally 255.

  ž   tmDefaultChar (BYTE)--The character that Windows uses to display
      characters that are not in the font. For ANSI_CHARSET fonts, this is
      normally 128.

  ž   tmBreakChar (BYTE)--The character that Windows (and your programs)
      should use to determine word breaks when justifying text. For
      ANSI_CHARSET fonts, this is normally 32, the space character.



ENUMERATING THE FONTS

Earlier in the chapter, I covered the particular typefaces and sizes that
are available when you're writing a program to display text on the screen.
If you want to send this text to a printer, however, how do you know which
fonts the printer has? The Windows WRITE program knows. When you change the
current printer, Write often comes up with a different list of typeface
names and sizes that you can choose from. The function that lets WRITE do
this is EnumFonts. EnumFonts uses a call-back function that Windows calls
once for each typeface or font that is available on the device.

You use EnumFonts in one of two ways. The first requires that the second
parameter be NULL:

EnumFonts (hdc, NULL, lpfnEnumFunction, lpData) ;

The lpfnEnumFunction parameter is a pointer to the call-back function.
EnumFonts calls this call-back function once for each typeface name
available on the device indicated by the hdc parameter. Normally, you use
EnumFonts this way first to get a list of all typefaces supported by the
device.

After you have the list of typeface names, you can call EnumFonts once for
each typeface:

EnumFonts (hdc, szTypeFace, lpfnEnumFunction, lpData) ;

For each call you make to EnumFonts in this format, Windows calls
EnumFunction once for each available size of the particular typeface.

The call-back function has the format:

short FAR PASCAL EnumFunction (lplf, lptm, nFontType, lpData)
     LOGFONT    far *lf ;
     TEXTMETRIC far *lptm ;
     short          nFontType ;
     LPSTR          lpData ;
     {
[other program lines]
     return 1 ;
     }

The function must be listed in the EXPORTS section of the module definition
(.DEF) file, and you must obtain a pointer to the function with
MakeProcInstance:

FARPROC   lpfnEnumFunction ;
[other program lines]
lpfnEnumFunction = MakeProcInstance (EnumFunction, hInstance) ;

The call-back function receives a far pointer to the particular LOGFONT
structure that you can use to create this particular font, a far pointer to
the TEXTMETRIC structure that will be obtained when you select this font
into the device context, a short integer that indicates the type (raster or
stroke) of the font, and a pointer to programmer-supplied data specified in
the EnumFonts call. (This last parameter simply provides a clean way of
passing information to the call-back function without using global data.)

The lower two bits of the nFontType parameter to the EnumFunction call-back
function indicate the type of the font:

You can use the identifiers RASTER_FONTTYPE (which equals 1) to determine if
the font is raster or stroke and DEVICE_FONTTYPE (which equals 2) to
determine if the font is device or GDI. Windows will continue calling the
call-back function until all the fonts are enumerated or until the function
returns a 0.

The FONTLIST program, shown in Figure 14-7, uses EnumFonts to obtain the
fonts available on either the display or the current printer. If no printer
is available, then EnumFonts displays nothing when the Printer option is
selected from the menu.

 FONTLIST.MAK

#------------------------
# FONTLIST.MAK make file
#------------------------

fontlist.exe : fontlist.obj fontlist.def fontlist.res
     link fontlist, /align:16, NUL, /nod slibcew libw, fontlist
     rc fontlist.res

fontlist.obj : fontlist.c fontlist.h
     cl -c -Gsw -Ow -W2 -Zp fontlist.c

fontlist.res : fontlist.rc fontlist.h
     rc -r fontlist.rc

 FONTLIST.C

/*-----------------------------------------
   FONTLIST.C -- Font Enumeration Program
                 (c) Charles Petzold, 1990
  -----------------------------------------*/

#include 
#include 
#include "fontlist.h"

typedef struct
     {
     GLOBALHANDLE hGMem ;
     short        nCount ;
     }
     ENUMER ;

typedef struct
     {
     short        nFontType ;
     LOGFONT      lf ;
     TEXTMETRIC   tm ;
     }
     FONT ;


long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;
int  FAR PASCAL EnumAllFaces (LPLOGFONT, LPTEXTMETRIC, short, ENUMER FAR *)
;
int  FAR PASCAL EnumAllFonts (LPLOGFONT, LPTEXTMETRIC, short, ENUMER FAR *)
;

char szAppName[] = "FontList" ;

int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     HWND     hwnd ;
     MSG      msg ;
     WNDCLASS wndclass ;

     if (!hPrevInstance)
          {
          wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
          wndclass.lpfnWndProc   = WndProc ;
          wndclass.cbClsExtra    = 0 ;
          wndclass.cbWndExtra    = 0 ;
          wndclass.hInstance     = hInstance ;
          wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
          wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
          wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
          wndclass.lpszMenuName  = szAppName ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;
          }
     hwnd = CreateWindow (szAppName, "Font Enumeration",
                          WS_OVERLAPPEDWINDOW | WS_VSCROLL,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, nCmdShow) ;
     UpdateWindow (hwnd) ;

     while (GetMessage (&msg, NULL, 0, 0))
          {
          TranslateMessage (&msg) ;
          DispatchMessage  (&msg) ;
          }
     return msg.wParam ;
     }

int FAR PASCAL EnumAllFaces (LPLOGFONT lf, LPTEXTMETRIC tm,
                             short nFontType, ENUMER FAR *enumer)
     {
     LPSTR lpFaces ;
     if (NULL == GlobalReAlloc (enumer->hGMem,
                         (DWORD) LF_FACESIZE * (1 + enumer->nCount),
                         GMEM_MOVEABLE))
          return 0 ;

     lpFaces = GlobalLock (enumer->hGMem) ;
     lstrcpy (lpFaces + enumer->nCount * LF_FACESIZE, lf->lfFaceName) ;
     GlobalUnlock (enumer->hGMem) ;
     enumer->nCount ++ ;
     return 1 ;
     }

int FAR PASCAL EnumAllFonts (LPLOGFONT lf, LPTEXTMETRIC tm,
                             short nFontType, ENUMER FAR *enumer)
     {
     FONT FAR *font ;

     if (NULL == GlobalReAlloc (enumer->hGMem,
                         (DWORD) sizeof (FONT) * (1 + enumer->nCount),
                         GMEM_MOVEABLE))
          return 0 ;

     font = (FONT FAR *) GlobalLock (enumer->hGMem) + enumer->nCount ;
     font->nFontType = nFontType ;
     font->lf = *lf ;
     font->tm = *tm ;

     GlobalUnlock (enumer->hGMem) ;
     enumer->nCount ++ ;
     return 1 ;
     }

void Display (HDC hdc, short cxChar, short cyChar, FONT FAR *font)
     {
     static FONT f ;
     static char *szYN [] = { "No",         "Yes" } ;
     static char *szCS [] = { "ANSI",       "?????",   "Kanji",    "OEM" } ;
     static char *szOP [] = { "Default",    "String",  "Char",    "Stroke" }
;
     static char *szCP [] = { "Default",    "Char",    "Stroke",   "?????" }
;
     static char *szQU [] = { "Draft",      "Default", "Proof",    "?????" }
;
     static char *szP1 [] = { "Default",    "Fixed",   "Variable", "?????" }
;
     static char *szP2 [] = { "Fixed",      "Variable" } ;
     static char *szFA [] = { "Don't Care", "Roman",      "Swiss", "Modern",
                              "Script",     "Decorative", "?????", "?????" }
;
     static char *szVR [] = { "Stroke",     "Raster" } ;
     static char *szGD [] = { "GDI",        "Device" } ;
     static struct
          {
          short x ;
          short y ;
          char  *szFmt ;
          short *pData ;
          }
          shorts [] =
          {
           1,  1, "LOGFONT",            NULL,
           1,  2, "-------",            NULL,
           1,  3, "Height:      %10d",  &f.lf.lfHeight,
           1,  4, "Width:       %10d",  &f.lf.lfWidth,
           1,  5, "Escapement:  %10d",  &f.lf.lfEscapement,
           1,  6, "Orientation: %10d",  &f.lf.lfOrientation,
           1,  7, "Weight:      %10d",  &f.lf.lfWeight,
          28,  1, "TEXTMETRIC",         NULL,
          28,  2, "----------",         NULL,
          28,  3, "Height:       %5d",  &f.tm.tmHeight,
          28,  4, "Ascent:       %5d",  &f.tm.tmAscent,
          28,  5, "Descent:      %5d",  &f.tm.tmDescent,
          28,  6, "Int. Leading: %5d",  &f.tm.tmInternalLeading,
          28,  7, "Ext. Leading: %5d",  &f.tm.tmExternalLeading,
          28,  8, "Ave. Width:   %5d",  &f.tm.tmAveCharWidth,
          28,  9, "Max. Width:   %5d",  &f.tm.tmMaxCharWidth,
          28, 10, "Weight:       %5d",  &f.tm.tmWeight,
          51, 10, "Overhang:     %10d", &f.tm.tmOverhang,
          51, 11, "Digitized X:  %10d", &f.tm.tmDigitizedAspectX,
          51, 12, "Digitized Y:  %10d", &f.tm.tmDigitizedAspectY
          } ;

     static struct
          {
          short x ;
          short y ;
          char  *szFmt ;
          BYTE  *pData ;
          }
          bytes [] =
          {
          51,  3, "First Char:   %10d", &f.tm.tmFirstChar,
          51,  4, "Last Char:    %10d", &f.tm.tmLastChar,
          51,  5, "Default Char: %10d", &f.tm.tmDefaultChar,
          51,  6, "Break Char:   %10d", &f.tm.tmBreakChar
          } ;

     static struct
          {
          short x ;
          short y ;
          char  *szFmt ;
          BYTE  *pData ;
          char  **szArray ;
          short sAnd ;
          short sShift ;
          }
          strings [] =
          {
           1,  8, "Italic:      %10s",  &f.lf.lfItalic,         szYN, 1,
0,
           1,  9, "Underline:   %10s",  &f.lf.lfUnderline,      szYN, 1,
0,
           1, 10, "Strike-Out:  %10s",  &f.lf.lfStrikeOut,      szYN, 1,
0,
           1, 11, "Char Set:    %10s",  &f.lf.lfCharSet,        szCS, 0xC0,
6,
           1, 12, "Out  Prec:   %10s",  &f.lf.lfOutPrecision,   szOP, 3,
0,
           1, 13, "Clip Prec:   %10s",  &f.lf.lfClipPrecision,  szCP, 3,
0,
           1, 14, "Quality:     %10s",  &f.lf.lfQuality,        szQU, 3,
0,
           1, 15, "Pitch:       %10s",  &f.lf.lfPitchAndFamily, szP1, 3,
0,
           1, 16, "Family:      %10s",  &f.lf.lfPitchAndFamily, szFA, 0x70,
4,
          28, 11, "Italic:       %5s",  &f.tm.tmItalic,         szYN, 1,
0,
          28, 12, "Underline:    %5s",  &f.tm.tmUnderlined,     szYN, 1,
0,
          28, 13, "Strike-Out:   %5s",  &f.tm.tmStruckOut,      szYN, 1,
0,
          51,  7, "Pitch:        %10s", &f.tm.tmPitchAndFamily, szP2, 1,
0,
          51,  8, "Family:       %10s", &f.tm.tmPitchAndFamily, szFA, 0x70,
4,
          51,  9, "Char Set:     %10s", &f.tm.tmCharSet,        szCS, 0xC0,
6,
          36, 15, "Font Type:  %6s",    (BYTE *) &f.nFontType,  szVR, 1,
0,
          55, 15, "%s",                 (BYTE *) &f.nFontType,  szGD, 2,
1
          } ;

     char szBuffer [80] ;
     int  i ;

     f = *font ;

     for (i = 0 ; i < sizeof shorts / sizeof shorts [0] ; i++)
          TextOut (hdc, cxChar * shorts[i].x, cyChar * shorts[i].y,
szBuffer,
                   wsprintf (szBuffer, shorts[i].szFmt,
                             *shorts[i].pData)) ;

     for (i = 0 ; i < sizeof bytes / sizeof bytes [0] ; i++)
          TextOut (hdc, cxChar * bytes[i].x, cyChar * bytes[i].y, szBuffer,
                   wsprintf (szBuffer, bytes[i].szFmt,
                             *bytes[i].pData)) ;

     for (i = 0 ; i < sizeof strings / sizeof strings [0] ; i++)
          TextOut (hdc, cxChar * strings[i].x, cyChar * strings[i].y,
szBuffer,
                   wsprintf (szBuffer, strings[i].szFmt,
                             (LPSTR) ((strings[i].szArray)
                                  [(*strings[i].pData & strings[i].sAnd) >>
                                        strings[i].sShift]))) ;
     TextOut (hdc, cxChar, cyChar * 17, szBuffer,
              wsprintf (szBuffer, "Face Name:   %10s",
                        (LPSTR) f.lf.lfFaceName)) ;
     }

HDC GetPrinterIC ()
     {
     char szPrinter [64] ;
     char *szDevice, *szDriver, *szOutput ;

     GetProfileString ("windows", "device", "", szPrinter, 64) ;

     if ((szDevice = strtok (szPrinter, "," )) &&
         (szDriver = strtok (NULL,      ", ")) &&
         (szOutput = strtok (NULL,      ", ")))

               return CreateIC (szDriver, szDevice, szOutput, NULL) ;

     return NULL ;
     }

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     static BOOL    bHaveInfo = FALSE ;
     static ENUMER  enumer1, enumer2 ;
     static FARPROC lpfnEnumAllFaces, lpfnEnumAllFonts ;
     static short   cxChar, cyChar, nCurrent ;
     static WORD    wCurrentDC = IDM_SCREEN ;
     HANDLE         hInstance ;
     HDC            hdc ;
     HFONT          hFont ;
     HMENU          hMenu ;
     FONT FAR       *font ;
     LPSTR          lpFaces ;
     PAINTSTRUCT    ps ;
     short          i ;
     TEXTMETRIC     tm ;

     switch (message)
          {
          case WM_CREATE :
               hInstance = ((LPCREATESTRUCT) lParam)-> hInstance ;
               lpfnEnumAllFaces = MakeProcInstance (EnumAllFaces, hInstance)
;
               lpfnEnumAllFonts = MakeProcInstance (EnumAllFonts, hInstance)
;

               hdc = GetDC (hwnd) ;
               SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;
               GetTextMetrics (hdc, (LPTEXTMETRIC) &tm) ;
               cxChar = tm.tmAveCharWidth ;
               cyChar = tm.tmHeight + tm.tmExternalLeading ;

               ReleaseDC (hwnd, hdc) ;
               return 0 ;

          case WM_COMMAND :
               if (wParam == IDM_EXIT)
                    {
                    SendMessage (hwnd, WM_CLOSE, 0, 0L) ;
                    return 0 ;
                    }
               else if (wParam == wCurrentDC)
                    return 0 ;

               hMenu = GetMenu (hwnd) ;
               CheckMenuItem (hMenu, wCurrentDC, MF_UNCHECKED) ;
               CheckMenuItem (hMenu, wCurrentDC = wParam, MF_CHECKED) ;

                                        // fall through

          case WM_DEVMODECHANGE :
          case WM_FONTCHANGE :
               bHaveInfo = FALSE ;
               InvalidateRect (hwnd, NULL, TRUE) ;
               return 0 ;

          case WM_PAINT :
               if (!bHaveInfo)
                    {
                    if (enumer2.hGMem)
                         GlobalFree (enumer2.hGMem) ;

                    enumer1.hGMem  = GlobalAlloc (GHND, 1L) ;
                    enumer1.nCount = 0 ;

                    enumer2.hGMem  = GlobalAlloc (GHND, 1L) ;
                    enumer2.nCount = 0 ;

                    if (NULL == enumer1.hGMem || NULL == enumer2.hGMem)
                         goto MEMORY_ERROR ;

                    if (wCurrentDC == IDM_SCREEN)
                         hdc = CreateIC ("DISPLAY", NULL, NULL, NULL) ;
                    else
                         hdc = GetPrinterIC () ;
                    if (hdc)
                         {
                         if (0 == EnumFonts (hdc, NULL, lpfnEnumAllFaces,
                                                  (LPSTR) &enumer1))
                              goto MEMORY_ERROR ;

                         lpFaces = GlobalLock (enumer1.hGMem) ;

                         for (i = 0 ; i < enumer1.nCount ; i++)
                              if (0 == EnumFonts (hdc,
                                             lpFaces + i * LF_FACESIZE,
                                             lpfnEnumAllFonts,
                                             (LPSTR) &enumer2))
                                   goto MEMORY_ERROR ;

                         GlobalUnlock (enumer1.hGMem) ;
                         enumer2.nCount-- ;

                         DeleteDC (hdc) ;
                         bHaveInfo = TRUE ;
                         }
                    GlobalFree (enumer1.hGMem) ;
                    SetScrollRange (hwnd, SB_VERT, 0, enumer2.nCount, FALSE)
;
                    SetScrollPos   (hwnd, SB_VERT, nCurrent = 0, TRUE) ;
                    }

               hdc = BeginPaint (hwnd, &ps) ;

               if (bHaveInfo)
                    {
                    SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;

                    font = (FONT FAR *) GlobalLock (enumer2.hGMem) +
nCurrent ;
                    Display (hdc, cxChar, cyChar, font) ;

                    hFont = SelectObject (hdc, CreateFontIndirect
(&font->lf)) ;

                    TextOut (hdc, 1 * cxChar, 19 * cyChar,
                         52) ;

                    GlobalUnlock (enumer2.hGMem) ;
                    DeleteObject (SelectObject (hdc, hFont)) ;
                    }

               EndPaint (hwnd, &ps) ;
               return 0 ;
          case WM_KEYDOWN :
               switch (wParam)
                    {
                    case VK_HOME :
                         SendMessage (hwnd, WM_VSCROLL, SB_TOP, 0L) ;
                         break ;
                    case VK_END :
                         SendMessage (hwnd, WM_VSCROLL, SB_BOTTOM, 0L) ;
                         break ;
                    case VK_LEFT :
                    case VK_UP :
                    case VK_PRIOR :
                         SendMessage (hwnd, WM_VSCROLL, SB_LINEUP, 0L) ;
                         break ;
                    case VK_RIGHT :
                    case VK_DOWN :
                    case VK_NEXT :
                         SendMessage (hwnd, WM_VSCROLL, SB_LINEDOWN, 0L) ;
                         break ;
                    default :
                         return 0 ;
                    }
               return 0 ;

          case WM_VSCROLL :
               switch (wParam)
                    {
                    case SB_TOP :
                         nCurrent = 0 ;
                         break ;
                    case SB_BOTTOM :
                         nCurrent = enumer2.nCount ;
                         break ;
                    case SB_LINEUP :
                    case SB_PAGEUP :
                         nCurrent -- ;
                         break ;
                    case SB_LINEDOWN :
                    case SB_PAGEDOWN :
                         nCurrent ++ ;
                         break ;
                    case SB_THUMBPOSITION :
                         nCurrent = LOWORD (lParam) ;
                         break ;
                    default :
                         return 0 ;
                    }
               nCurrent = min (max (0, nCurrent), enumer2.nCount) ;
               SetScrollPos (hwnd, SB_VERT, nCurrent, TRUE) ;
               InvalidateRect (hwnd, NULL, TRUE) ;
               return 0 ;

          MEMORY_ERROR :
               MessageBox (hwnd, "Cannot allocate memory, must end.",
                    szAppName, MB_OK | MB_ICONHAND | MB_SYSTEMMODAL) ;

                                             // fall through
          case WM_CLOSE :
               DestroyWindow (hwnd) ;
               return 0 ;

          case WM_DESTROY :
               PostQuitMessage (0) ;
               return 0 ;
          }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }

 FONTLIST.RC

/*-----------------------------
   FONTLIST.RC resource script
  -----------------------------*/

#include "fontlist.h"

FontList MENU
     {
     POPUP "&Device"
          {
          MENUITEM "&Screen",  IDM_SCREEN, CHECKED
          MENUITEM "&Printer", IDM_PRINTER
          MENUITEM SEPARATOR
          MENUITEM "E&xit",    IDM_EXIT
          }
     }

 FONTLIST.H

/*------------------------
   FONTLIST.H header file
  ------------------------*/

#define IDM_SCREEN  1
#define IDM_PRINTER 2
#define IDM_EXIT    3

 FONTLIST.DEF

;-------------------------------------
; FONTLIST.DEF module definition file
;-------------------------------------

NAME           FONTLIST

DESCRIPTION    'Font Enumeration Program (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc
               EnumAllFaces
               EnumAllFonts

FONTLIST uses two separate call-back functions to enumerate the fonts. The
first, called EnumAllFaces, accumulates the typeface names. The second,
EnumAllFonts, accumulates all the sizes for each typeface name. The program
uses global memory blocks to store this information. If FONTLIST runs out of
memory space, the program aborts after displaying a message box.

After FONTLIST is finished getting the fonts, it displays both the logical
font structure and the text metrics structure for each font, one font per
screen, with a sample line of text at the bottom of the screen. You can move
through the fonts using the vertical scroll bar or the cursor keys. A large
part of the program (the Display function) is devoted to formatting the
information for display. Figure 14-8 on the following page shows a typical
FONTLIST screen.

FONTLIST will list all the fonts in any COURx.FON, HELVx.FON, TMSRx.FON, and
SYMBOLx.FON files in the SYSTEM subdirectory of your Windows directory.
You'll note from the "Digitized X" and "Digitized Y" fields that some of
these fonts may not match the aspect ratio of the video display and may look
a little funny. When using the EnumFonts function in a real application to
obtain screen fonts, you should first obtain the resolution of the device in
logical pixels per inch by using the GetDeviceCaps function with the
LOGPIXELSX and LOGPIXELSY parameters. You should then reject any font in
which the tmDigitizedAspectX and tmDigitizedAspectY values of the
TEXTMETRICS structure does not match these values. (The JUSTIFY program
shown towards the end of this chapter does this.)

  (Figure 14-8. may be found in the printed book.)

FONTLIST intercepts the WM_DEVMODECHANGE message (which indicates a change
of the printer) and the WM_FONTCHANGE message (which indicates a change in
the font resources). You can use FONTLIST to examine the fonts available on
different printers by changing the current printer using the Control Panel.
If you choose to list the fonts for the current printer, you may wonder how
FONTLIST is able to display a device printer font at the bottom of the
client area. Although FONTLIST shows the logical font and text metrics
structures for device printer fonts, the sample text is a normal GDI font
that Windows picks based on the logical font structure for the printer font.
It's the closest approximation of the printer font that Windows can display
on the screen.


FORMATTING TEXT

Now that you know how to determine the fonts available on a particular
device, how to create a font and select it into the device context, and how
to determine the sizes and characteristics of the fonts, it's time to try
your hand at text formatting. The process involves placing each line of text
within margins in one of four ways: aligned on the left margin, aligned on
the right margin, centered between the margins, or justified--that is,
running from one margin to the other, with equal spaces between the words.
For the first three jobs, you can use the DrawText function with the
DT_WORDBREAK parameter, but this approach has limitations. For instance, you
can't determine what part of the text DrawText was able to fit within the
rectangle. DrawText is convenient for some simple jobs, but for more complex
formatting tasks, you'll probably want to employ TextOut.

One of the most useful functions for working with text is GetTextExtent.
This function tells you the width and height of a character string based on
the current font selected in the device context:

dwExtent = GetTextExtent (hdc, lpString, nCount) ;

The width of the text in logical units is in the low word of dwExtent, and
the height of the text in logical units is in the high word. Although you
can also obtain the text height from the tmHeight field of the TEXTMETRIC
structure, the TEXTMETRIC width is inadequate when you're working with
variable-pitch fonts or with italic or boldface text.

Breaking text into lines involves searching for break characters. In theory,
you should determine the font's break character from the tmBreakChar field
of the TEXTMETRIC structure, but you can also simply assume that it's the
space character (ASCII number 32). In theory, you should also use the
AnsiNext and AnsiPrev functions to step through the string, but you'll get
better performance if you use normal C pointer arithmetic. (Of course, if
you hope eventually to convert your programs to languages that use other
character sets, which might have 2 or more bytes per character, then you had
best follow these "in theory" rules right from the start.)

One-Line Text Alignment

I'll begin with an example using one line of text. Let's say that you have
selected a font into your device context and now want to write the text:

char *szText [] = "Hello, how are you?" ;

You want the text to start at the vertical coordinate yStart, within margins
set by the coordinates xLeft and xRight. Your job is to calculate the xStart
value for the horizontal coordinate where the text begins. This job would be
considerably easier if the text were displayed using a fixed-pitch font, but
you can't assume a fixed-pitch font in general.

First, you get the text extents of the string:

dwExtent = GetTextExtent (hdc, szText, strlen (szText)) ;

If the low word of dwExtent is larger than (xRight - xLeft), then the line
is too long to fit within the margins. Let's assume it can fit.

To align the text on the left margin, you simply set xStart equal to xLeft
and then write the text:

TextOut (hdc, xStart, yStart, szText, strlen (szText)) ;

This is easy. You can now add the high word of dwExtent to yStart, and
you're ready to write the next line of text.

To align the text on the right margin, you use this formula for xStart:

xStart = xRight - LOWORD (dwExtent) ;

To center the text between the left and right margins, use this formula:

xStart = (xLeft + xRight - LOWORD (dwExtent)) / 2 ;

Now here's the tough job--to justify the text within the left and right
margins. The distance between the margins is (xRight - xLeft). Without
justification, the text is LOWORD (dwExtent) wide. The difference between
these two values, which is:

xRight - xLeft - LOWORD (dwExtent)

must be equally distributed among the three space characters in the
character string. It sounds like a terrible job, but it's not too bad. To do
it, you call:

SetTextJustification (hdc, xRight - xLeft - LOWORD (dwExtent), 3) ;

The second parameter is the amount of space that must be distributed among
the space characters in the character string. The third parameter is the
number of space characters_ in this case, 3.

Now set xStart equal to xLeft and write the text with TextOut:

TextOut (hdc, xStart, yStart, szText, strlen (szText)) ;

The text will be justified between the xLeft and xRight margins.

Whenever you call SetTextJustification, it accumulates an error term if the
amount of space doesn't distribute evenly among the space characters. This
error term will affect subsequent GetTextExtent calls. Each time you start a
new line, you should clear out the error term by calling:

SetTextJustification (hdc, 0, 0) ;


Working with Paragraphs

If you're working with a whole paragraph, you have to start at the beginning
and scan through the string looking for blanks. Every time you encounter a
blank, you call GetTextExtent to determine if the text still fits between
the left and right margins. When the text exceeds the space allowed for it,
then you backtrack to the previous blank. Now you have determined the
character string for the line. If you want to justify the line, call
SetTextJustification and TextOut, clear out the error term, and proceed to
the next line.

The JUSTIFY program, shown in Figure 14-9, does this job for the first
paragraph of Herman Melville's Moby Dick. You choose a screen font, and the
Alignment menu lets you align the text on the left or right, center it, or
justify it. Figure 14-10 on page 713 shows a typical JUSTIFY screen.

 JUSTIFY.MAK

#-----------------------
# JUSTIFY.MAK make file
#-----------------------

justify.exe : justify.obj justify.def justify.res
     link justify, /align:16, NUL, /nod slibcew libw, justify
     rc justify.res

justify.obj : justify.c justify.h
     cl -c -Gsw -Ow -W2 -Zp justify.c

justify.res : justify.rc justify.asc justify.h
     rc -r justify.rc

 JUSTIFY.C

/*----------------------------------------
   JUSTIFY.C -- Justified Type Program
                (c) Charles Petzold, 1990
  ----------------------------------------*/

#include 
#include 
#include "justify.h"

typedef struct
     {
     short nNumFaces ;
     char  szFaceNames [MAX_FACES] [LF_FACESIZE] ;
     }
     ENUMFACE ;

typedef struct
     {
     short      nNumSizes ;
     short      xLogPixPerInch ;
     short      yLogPixPerInch ;
     LOGFONT    lf [MAX_SIZES] ;
     TEXTMETRIC tm [MAX_SIZES] ;
     }
     ENUMSIZE ;

long FAR PASCAL WndProc (HWND, WORD, WORD, LONG) ;
int  FAR PASCAL EnumAllFaces (LPLOGFONT, LPTEXTMETRIC, short, ENUMFACE FAR
*) ;
int  FAR PASCAL EnumAllSizes (LPLOGFONT, LPTEXTMETRIC, short, ENUMSIZE FAR
*) ;


int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
                    LPSTR lpszCmdLine, int nCmdShow)
     {
     static   char szAppName[] = "Justify" ;
     HWND     hwnd ;
     MSG      msg ;
     WNDCLASS wndclass ;

     if (!hPrevInstance)
          {
          wndclass.style         = CS_HREDRAW | CS_VREDRAW | CS_OWNDC ;
          wndclass.lpfnWndProc   = WndProc ;
          wndclass.cbClsExtra    = 0 ;
          wndclass.cbWndExtra    = 0 ;
          wndclass.hInstance     = hInstance ;
          wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
          wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
          wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
          wndclass.lpszMenuName  = szAppName ;
          wndclass.lpszClassName = szAppName ;

          RegisterClass (&wndclass) ;
          }
     hwnd = CreateWindow (szAppName, "Justified Type",
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, nCmdShow) ;
     UpdateWindow (hwnd) ;

     while (GetMessage (&msg, NULL, 0, 0))
          {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
          }
     return msg.wParam ;
     }

int FAR PASCAL EnumAllFaces (LPLOGFONT lplf, LPTEXTMETRIC lptm,
                             short nFontType, ENUMFACE FAR *lpef)
     {
     if (nFontType & RASTER_FONTTYPE)
          {
          lstrcpy (lpef->szFaceNames[lpef->nNumFaces], lplf->lfFaceName) ;
          if (++lpef->nNumFaces == MAX_FACES)
               return 0 ;
          }
     return 1 ;
     }
int FAR PASCAL EnumAllSizes (LPLOGFONT lplf, LPTEXTMETRIC lptm,
                             short nFontType, ENUMSIZE FAR *lpes)
     {
     if (lpes->xLogPixPerInch == lptm->tmDigitizedAspectX &&
         lpes->yLogPixPerInch == lptm->tmDigitizedAspectY)
          {
          lpes->lf [lpes->nNumSizes] = *lplf ;
          lpes->tm [lpes->nNumSizes] = *lptm ;
          if (++lpes->nNumSizes == MAX_SIZES)
               return 0 ;
          }
     return 1 ;
     }

short MakeSizeMenu (HWND hwnd, FARPROC lpfnEnumAllSizes,
                    ENUMSIZE *pes, char *szFaceName)
     {
     static LOGFONT lfBlank ;
     char           szBuffer[20] ;
     HDC            hdc ;
     HMENU          hPopup ;
     short          i ;

     hdc = GetDC (hwnd) ;
     hPopup = GetSubMenu (GetMenu (hwnd), SIZE_MENU) ;

     pes->nNumSizes = 0 ;
     EnumFonts (hdc, szFaceName, lpfnEnumAllSizes, (LPSTR) pes) ;
     ReleaseDC (hwnd, hdc) ;

     while (GetMenuItemCount (hPopup) > 0)
          DeleteMenu (hPopup, 0, MF_BYPOSITION) ;

     if (pes->nNumSizes)
          for (i = 0 ; i < pes->nNumSizes ; i++)
               {
               wsprintf (szBuffer, "%i  %2d / %2d", i + 1,
                    (pes->tm[i].tmHeight - pes->tm[i].tmInternalLeading +
10)
                                                                        /
20,
                    (pes->tm[i].tmHeight + 10) / 20) ;
               AppendMenu (hPopup, 0, IDM_ISIZE + i, szBuffer) ;
               }
     else           /* no fonts found that match aspect ratio of display */
          {
          pes->lf[0] = lfBlank ;
          strcpy (pes->lf[0].lfFaceName, szFaceName) ;
          AppendMenu (hPopup, 0, IDM_ISIZE, "Default") ;
          }
     CheckMenuItem (hPopup, IDM_ISIZE, MF_CHECKED) ;
     return 0 ;
     }

void DrawRuler (HDC hdc, POINT ptClient)
     {
     static short nRuleSize [16] = { 360, 72, 144, 72, 216, 72, 144, 72,
                                     288, 72, 144, 72, 216, 72, 144, 72 } ;
     short        i, j ;

     MoveTo (hdc, 0,          -360) ;
     LineTo (hdc, ptClient.x, -360) ;
     MoveTo (hdc, -360,          0) ;
     LineTo (hdc, -360, ptClient.y) ;

     for (i = 0, j = 0 ; i <= ptClient.x ; i += 1440 / 16, j++)
          {
          MoveTo (hdc, i, -360) ;
          LineTo (hdc, i, -360 - nRuleSize [j % 16]) ;
          }
     for (i = 0, j = 0 ; i <= ptClient.y ; i += 1440 / 16, j++)
          {
          MoveTo (hdc, -360, i) ;
          LineTo (hdc, -360 - nRuleSize [j % 16], i) ;
          }
     }

void Justify (HDC hdc, HANDLE hResource, POINT ptClient, short nCurAlign)
     {
     DWORD  dwExtent ;
     LPSTR  lpText, lpBegin, lpEnd ;
     short  i, xStart, yStart, nBreakCount ;

     lpText = LockResource (hResource) ;

     yStart = 0 ;
     do                            // for each text line
          {
          nBreakCount = 0 ;
          while (*lpText == ' ')   // skip over leading blanks
               lpText++ ;
          lpBegin = lpText ;

          do                       // until the line is known
               {
               lpEnd = lpText ;

               while (*lpText != '\0' && *lpText++ != ' ') ;
               if (*lpText == '\0')
                    break ;
                                   // for each space, calculate extents
               nBreakCount++ ;
               SetTextJustification (hdc, 0, 0) ;
               dwExtent = GetTextExtent (hdc, lpBegin, lpText - lpBegin - 1)
;
               }
          while (LOWORD (dwExtent) < ptClient.x) ;

          nBreakCount-- ;
          while (*(lpEnd - 1) == ' ')   // eliminate trailing blanks
               {
               lpEnd-- ;
               nBreakCount-- ;
               }

          if (*lpText == '\0' || nBreakCount <= 0)
               lpEnd = lpText ;

          SetTextJustification (hdc, 0, 0) ;
          dwExtent = GetTextExtent (hdc, lpBegin, lpEnd - lpBegin) ;

          if (nCurAlign == IDM_LEFT)         // use alignment for xStart
               xStart = 0 ;

          else if (nCurAlign == IDM_RIGHT)
               xStart = ptClient.x - LOWORD (dwExtent) ;

          else if (nCurAlign == IDM_CENTER)
               xStart = (ptClient.x - LOWORD (dwExtent)) / 2 ;

          else
               {
               if (*lpText != '\0' && nBreakCount > 0)
                    SetTextJustification (hdc, ptClient.x - LOWORD
(dwExtent),
                                                  nBreakCount) ;
               xStart = 0 ;
               }

          TextOut (hdc, xStart, yStart, lpBegin, lpEnd - lpBegin) ;
          yStart += HIWORD (dwExtent) ;
          lpText = lpEnd ;
          }
     while (*lpText && yStart < ptClient.y) ;

     GlobalUnlock (hResource) ;
     }

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     static ENUMFACE ef ;
     static ENUMSIZE es ;
     static FARPROC  lpfnEnumAllFaces, lpfnEnumAllSizes ;
     static HANDLE   hResource ;
     static POINT    ptClient ;
     static short    nCurSize, nCurFace, nCurAttr, nCurAlign = IDM_LEFT ;
     HANDLE          hInstance ;
     HDC             hdc ;
     HFONT           hFont ;
     HMENU           hMenu, hPopup ;
     PAINTSTRUCT     ps ;
     short           i ;

     switch (message)
          {
          case WM_CREATE :
               hdc = GetDC (hwnd) ;
               es.xLogPixPerInch = GetDeviceCaps (hdc, LOGPIXELSX) ;
               es.yLogPixPerInch = GetDeviceCaps (hdc, LOGPIXELSY) ;

                              // Set Map Mode

               SetMapMode (hdc, MM_ANISOTROPIC) ;
               SetWindowExt (hdc, 1440, 1440) ;
               SetViewportExt (hdc, es.xLogPixPerInch, es.yLogPixPerInch) ;
               SetWindowOrg (hdc, -720, -720) ;

                              // MakeProcInstance for 2 routines

               hInstance = ((LPCREATESTRUCT) lParam)-> hInstance ;
               lpfnEnumAllFaces = MakeProcInstance (EnumAllFaces, hInstance)
;
               lpfnEnumAllSizes = MakeProcInstance (EnumAllSizes, hInstance)
;

                              // Enumerate the Font Faces

               EnumFonts (hdc, NULL, lpfnEnumAllFaces, (LPSTR) &ef) ;
               ReleaseDC (hwnd, hdc) ;

                              // Initialize the Menus

               hMenu  = GetMenu (hwnd) ;
               hPopup = CreateMenu () ;

               for (i = 0 ; i < ef.nNumFaces ; i++)
                    AppendMenu (hPopup, 0, IDM_IFACE + i, ef.szFaceNames[i])
;

               ModifyMenu (hMenu, IDM_FACE, MF_POPUP, hPopup, "&FaceName") ;
               CheckMenuItem (hMenu, IDM_IFACE, MF_CHECKED) ;

               nCurSize = MakeSizeMenu (hwnd, lpfnEnumAllSizes, &es,
                                   ef.szFaceNames [nCurFace]) ;

                              // Load the Text Resource
               hResource = LoadResource (hInstance,
                           FindResource (hInstance, "Ishmael", "TEXT")) ;
               return 0 ;

          case WM_SIZE :
               hdc = GetDC (hwnd) ;
               ptClient = MAKEPOINT (lParam) ;
               DPtoLP (hdc, &ptClient, 1) ;
               ptClient.x -= 360 ;
               ReleaseDC (hwnd, hdc) ;
               return 0 ;

          case WM_COMMAND :
               hMenu = GetMenu (hwnd) ;

               if (wParam >= IDM_IFACE && wParam < IDM_IFACE + MAX_FACES)
                    {
                    CheckMenuItem (hMenu, nCurFace + IDM_IFACE,
MF_UNCHECKED) ;
                    CheckMenuItem (hMenu, wParam, MF_CHECKED) ;
                    nCurFace = wParam - IDM_IFACE ;

                    nCurSize = MakeSizeMenu (hwnd, lpfnEnumAllSizes, &es,
                                        ef.szFaceNames [nCurFace]) ;
                    }

               else if (wParam >= IDM_ISIZE && wParam < IDM_ISIZE +
MAX_SIZES)
                    {
                    CheckMenuItem (hMenu, nCurSize + IDM_ISIZE,
MF_UNCHECKED) ;
                    CheckMenuItem (hMenu, wParam, MF_CHECKED) ;
                    nCurSize = wParam - IDM_ISIZE ;
                    }

               else switch (wParam)
                    {
                    case IDM_BOLD :
                    case IDM_ITALIC :
                    case IDM_STRIKE :
                    case IDM_UNDER :
                         CheckMenuItem (hMenu, wParam, MF_CHECKED &
                              GetMenuState (hMenu, wParam, MF_BYCOMMAND) ?
                                   MF_UNCHECKED : MF_CHECKED) ;
                         nCurAttr ^= wParam ;
                         break ;

                    case IDM_NORM :
                         nCurAttr = 0 ;
                         CheckMenuItem (hMenu, IDM_BOLD,   MF_UNCHECKED) ;
                         CheckMenuItem (hMenu, IDM_ITALIC, MF_UNCHECKED) ;
                         CheckMenuItem (hMenu, IDM_STRIKE, MF_UNCHECKED) ;
                         CheckMenuItem (hMenu, IDM_UNDER,  MF_UNCHECKED) ;
                         break ;
                    case IDM_LEFT :
                    case IDM_RIGHT :
                    case IDM_CENTER :
                    case IDM_JUST :
                         CheckMenuItem (hMenu, nCurAlign, MF_UNCHECKED) ;
                         nCurAlign = wParam ;
                         CheckMenuItem (hMenu, nCurAlign, MF_CHECKED) ;
                         break ;
                    }
               InvalidateRect (hwnd, NULL, TRUE) ;
               return 0 ;

          case WM_PAINT :
               hdc = BeginPaint (hwnd, &ps) ;

               es.lf[nCurSize].lfWeight    = nCurAttr & IDM_BOLD ? 700 : 400
                                           ;
               es.lf[nCurSize].lfItalic    = (BYTE) (nCurAttr & IDM_ITALIC)
                                           ;
               es.lf[nCurSize].lfUnderline = (BYTE) (nCurAttr & IDM_UNDER) ;
               es.lf[nCurSize].lfStrikeOut = (BYTE) (nCurAttr & IDM_STRIKE)
                                           ;

               hFont = CreateFontIndirect (&es.lf[nCurSize]) ;
               hFont = SelectObject (hdc, hFont) ;

               DrawRuler (hdc, ptClient) ;
               Justify (hdc, hResource, ptClient, nCurAlign) ;

               DeleteObject (SelectObject (hdc, hFont)) ;
               EndPaint (hwnd, &ps) ;
               return 0 ;

          case WM_DESTROY :
               FreeResource (hResource) ;
               PostQuitMessage (0) ;
               return 0 ;
          }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
     }

 JUSTIFY.RC

/*----------------------------
   JUSTIFY.RC resource script
  ----------------------------*/

#include "justify.h"

Ishmael TEXT justify.asc
Justify MENU
     {
     MENUITEM  "&FaceName",        IDM_FACE
     POPUP     "&PointSize"
          {
          MENUITEM "temp",         IDM_SIZE
          }
     POPUP     "&Attributes"
          {
          MENUITEM "&Bold",        IDM_BOLD
          MENUITEM "&Italic",      IDM_ITALIC
          MENUITEM "&StrikeOut",   IDM_STRIKE
          MENUITEM "&Underline",   IDM_UNDER
          MENUITEM SEPARATOR
          MENUITEM "&Normal",      IDM_NORM
          }
     POPUP     "A&lignment"
          {
          MENUITEM "&Left",        IDM_LEFT, CHECKED
          MENUITEM "&Right",       IDM_RIGHT
          MENUITEM "&Centered",    IDM_CENTER
          MENUITEM "&Justified",   IDM_JUST
          }
     }

 JUSTIFY.ASC

Call me Ishmael.  Some years ago -- never mind how long precisely -
- having little or no money in my purse, and nothing particular to
interest me on shore, I thought I would sail about a little and see
the watery part of the world.  It is a way I have of driving off the
spleen, and regulating the circulation.  Whenever I find myself
growing grim about the mouth; whenever it is a damp, drizzly November
in my soul; whenever I find myself involuntarily pausing before coffin
warehouses, and bringing up the rear of every funeral I meet; and
especially whenever my hypos get such an upper hand of me, that it
requires a strong moral principle to prevent me from deliberately
stepping into the street, and methodically knocking people's hats
off -- then, I account it high time to get to sea as soon as I can.
This is my substitute for pistol and ball.  With a philosophical
flourish Cato throws himself upon his sword; I quietly take to the
ship.  There is nothing surprising in this.  If they but knew it,
almost all men in their degree, some time or other, cherish very
nearly the same feelings towards the ocean with me.

 JUSTIFY.H

/*-----------------------
   JUSTIFY.H header file
  -----------------------*/

#define SIZE_MENU    1
#define MAX_FACES   16
#define MAX_SIZES   16

#define IDM_BOLD     1
#define IDM_ITALIC   2
#define IDM_STRIKE   4
#define IDM_UNDER    8

#define IDM_FACE    0x10
#define IDM_SIZE    0x11
#define IDM_NORM    0x12

#define IDM_LEFT    0x13
#define IDM_RIGHT   0x14
#define IDM_CENTER  0x15
#define IDM_JUST    0x16

#define IDM_IFACE   0x20
#define IDM_ISIZE   (IDM_IFACE + MAX_FACES)

 JUSTIFY.DEF

;------------------------------------
; JUSTIFY.DEF module definition file
;------------------------------------

NAME           JUSTIFY

DESCRIPTION    'Demonstration of Justified Text (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc
               EnumAllFaces
               EnumAllSizes

  (Figure 14-10. may be found in the printed book.)

Like the FONTLIST program, JUSTIFY uses the EnumFonts function to obtain the
fonts available for the screen. The two call-back functions are called
EnumAllFaces and EnumAllSizes. The program can store up to 16 typeface names
and up to 16 font sizes for a particular typeface. During the WM_CREATE
message, JUSTIFY calls EnumFonts with a NULL second parameter and puts the
typeface names into the FaceName menu.

When you select a typeface from the FaceName menu, JUSTIFY calls EnumFonts
again to get the available sizes of that typeface. JUSTIFY must then
reconstruct the PointSize menu, which displays the font sizes in this
format:

  24 / 28

which means a 24-point font on a 28-point line spacing. The Attributes menu
offers the options Bold, Italic, StrikeOut, and Underline, and the
aforementioned Alignment menu has the options Left, Right, Centered, and
Justified.

JUSTIFY uses the "Logical Twips" mapping mode to facilitate the translation
into point sizes of the information available from the TEXTMETRIC structure.
To avoid setting this mapping mode every time it obtains a device context,
JUSTIFY uses the CS_OWNDC class style.

JUSTIFY displays a ruler (in logical inches, of course) across the top and
down the left side of the client area. The DrawRuler function draws the
ruler. The window origin is adjusted to begin 1/2 inch from the left and top
of the client area. JUSTIFY also leaves a 1/4-inch margin at the right of
the client area.

The text is a user-defined resource. It is in a one-line-per-paragraph
format and is NULL-terminated. The bulk of the work involved with formatting
this text is in the Justify function. JUSTIFY starts searching for blanks at
the beginning of the text and uses GetTextExtent to measure each line. When
the length of the line exceeds the width of the display area, JUSTIFY
returns to the previous space and uses the line up to that point. Depending
on the Alignment choice, the line is left aligned, right aligned, centered,
or justified.

JUSTIFY isn't perfect. In particular, the justification logic falls apart
when there is only one word in each line. Even if we solve this problem
(which isn't a difficult one), the program still won't work properly when a
single word is too long to fit within the left and right margins. Of course,
matters can become even more complex when you start working with programs
that can use multiple fonts on the same line (as Windows WRITE can). But
nobody ever claimed this stuff was easy. It's just easier than if you were
doing all the work yourself.