Programming Windows, Fifth Ed. - Chapter 17

Archived content. No warranty is made as to technical accuracy. Content may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

By Charles Petzold

ISBN: 1-57231-995-x

Chapter 17 and the Table of Contents of Programming Windows, Fifth Ed. reprinted with permission from Microsoft Press.
https://www.microsoft.com/mspress/

On This Page

Table of Contents

Table of Contents

Section I The Basics

Chapter 1 Getting Started

The Windows Environment
A History of Windows
Aspects of Windows
Dynamic Linking
Windows Programming Options
APIs and Memory Models
Language Options
The Programming Environment
API Documentation
Your First Windows Program
A Character-Mode Model
The Windows Equivalent
The Header Files
Program Entry Point
The MessageBox Function
Compile, Link, and Run

Chapter 2 An Introduction to Unicode

A Brief History of Character Sets
American Standards
The World Beyond
Extending ASCII
Double-Byte Character Sets
Unicode to the Rescue
Wide Characters and C
The char Data Type
Wider Characters
Wide-Character Library Functions
Maintaining a Single Source
Wide Characters and Windows
Windows Header File Types
The Windows Function Calls
Windows' String Functions
Using printf in Windows
A Formatting Message Box
Internationalization and This Book

Chapter 3 Windows and Messages

A Window of One's Own
An Architectural Overview
The HELLOWIN Program
Thinking Globally
Registering the Window Class
Creating the Window
Displaying the Window
The Message Loop
The Window Procedure
Processing the Messages
Playing a Sound File
The WM_PAINT Message
The WM_DESTROY Message
The Windows Programming Hurdles
Don't Call Me, I'll Call You
Queued and Nonqueued Messages
Get In and Out Fast

Chapter 4 An Exercise in Text Output

Painting and Repainting
The WM_PAINT Message
Valid and Invalid Rectangles
An Introduction to GDI
The Device Context
Getting a Device Context Handle: Method One
The Paint Information Structure
Getting a Device Context Handle: Method Two
TextOut: The Details
The System Font
The Size of a Character
Text Metrics: The Details
Formatting Text
Putting It All Together
The SYSMETS1.C Window Procedure
Not Enough Room
The Size of the Client Area
Scroll Bars
Scroll Bar Range and Position
Scroll Bar Messages
Scrolling SYSMETS
Structuring Your Program for Painting
Building a Better Scroll
The Scroll Bar Information Functions
How Low Can You Scroll?
The New SYSMETS
But I Don't Like to Use the Mouse

Chapter 5 Basic Drawing

The Structure of GDI
The GDI Philosophy
The GDI Function Calls
The GDI Primitives
Other Stuff
The Device Context
Getting a Device Context Handle
Getting Device Context Information
The DEVCAPS1 Program
The Size of the Device
Finding Out About Color
The Device Context Attributes
Saving Device Contexts
Drawing Dots and Lines
Setting Pixels
Straight Lines
The Bounding Box Functions
Bezier Splines
Using Stock Pens
Creating, Selecting, and Deleting Pens
Filling in the Gaps
Drawing Modes
Drawing Filled Areas
The Polygon Function and the Polygon-Filling Mode
Brushing the Interior
The GDI Mapping Mode
Device Coordinates and Logical Coordinates
The Device Coordinate Systems
The Viewport and the Window
Working with MM_TEXT
The Metric Mapping Modes
The "Roll Your Own" Mapping Modes
The WHATSIZE Program
Rectangles, Regions, and Clipping
Working with Rectangles
Random Rectangles
Creating and Painting Regions
Clipping with Rectangles and Regions
The CLOVER Program

Chapter 6 The Keyboard

Keyboard Basics
Ignoring the Keyboard
Who's Got the Focus?
Queues and Synchronization
Keystrokes and Characters
Keystroke Messages
System and Nonsystem Keystrokes
Virtual Key Codes
The lParam Information
Shift States
Using Keystroke Messages
Enhancing SYSMETS for the Keyboard
Character Messages
The Four Character Messages
Message Ordering
Control Character Processing
Dead-Character Messages
Keyboard Messages and Character Sets
The KEYVIEW1 Program
The Foreign-Language Keyboard Problem
Character Sets and Fonts
What About Unicode?
TrueType and Big Fonts
The Caret (Not the Cursor)
The Caret Functions
The TYPER Program

Chapter 7 The Mouse

Mouse Basics
Some Quick Definitions
The Plural of Mouse Is…
Client-Area Mouse Messages
Simple Mouse Processing: An Example
Processing Shift Keys
Mouse Double-Clicks
Nonclient-Area Mouse Messages
The Hit-Test Message
Messages Beget Messages
Hit-Testing in Your Programs
A Hypothetical Example
A Sample Program
Emulating the Mouse with the Keyboard
Add a Keyboard Interface to CHECKER
Using Child Windows for Hit-Testing
Child Windows in CHECKER
Child Windows and the Keyboard
Capturing the Mouse
Blocking Out a Rectangle
The Capture Solution
The BLOKOUT2 Program
The Mouse Wheel
Still to Come

Chapter 8 The Timer

Timer Basics
The System and the Timer
Timer Messages Are Not Asynchronous
Using the Timer: Three Methods
Method One
Method Two
Method Three
Using the Timer for a Clock
Building a Digital Clock
Getting the Current Time
Displaying Digits and Colons
Going International
Building an Analog Clock
Using the Timer for a Status Report

Chapter 9 Child Window Controls

The Button Class
Creating the Child Windows
The Child Talks to Its Parent
The Parent Talks to Its Child
Push Buttons
Check Boxes
Radio Buttons
Group Boxes
Changing the Button Text
Visible and Enabled Buttons
Buttons and Input Focus
Controls and Colors
System Colors
The Button Colors
The WM_CTLCOLORBTN Message
Owner-Draw Buttons
The Static Class
The Scroll Bar Class
The COLORS1 Program
The Automatic Keyboard Interface
Window Subclassing
Coloring the Background
Coloring the Scroll Bars and Static Text
The Edit Class
The Edit Class Styles
Edit Control Notification
Using the Edit Controls
Messages to an Edit Control
The Listbox Class
List Box Styles
Putting Strings in the List Box
Selecting and Extracting Entries
Receiving Messages from List Boxes
A Simple List Box Application
Listing Files
A head for Windows

Chapter 10 Menus and Other Resources

Icons, Cursors, Strings, and Custom Resources
Adding an Icon to a Program
Getting a Handle on Icons
Using Icons in Your Program
Using Customized Cursors
Character String Resources
Custom Resources
Menus
Menu Concepts
Menu Structure
Defining the Menu
Referencing the Menu in Your Program
Menus and Messages
A Sample Program
Menu Etiquette
Defining a Menu the Hard Way
Floating Popup Menus
Using the System Menu
Changing the Menu
Other Menu Commands
An Unorthodox Approach to Menus
Keyboard Accelerators
Why You Should Use Keyboard Accelerators
Some Rules on Assigning Accelerators
The Accelerator Table
Loading the Accelerator Table
Translating the Keystrokes
Receiving the Accelerator Messages
POPPAD with a Menu and Accelerators
Enabling Menu Items
Processing the Menu Options

Chapter 11 Dialog Boxes

Modal Dialog Boxes
Creating an "About" Dialog Box
The Dialog Box and Its Template
The Dialog Box Procedure
Invoking the Dialog Box
Variations on a Theme
A More Complex Dialog Box
Working with Dialog Box Controls
The OK and Cancel Buttons
Avoiding Global Variables
Tab Stops and Groups
Painting on the Dialog Box
Using Other Functions with Dialog Boxes
Defining Your Own Controls
Modeless Dialog Boxes
Differences Between Modal and Modeless Dialog Boxes
The New COLORS Program
HEXCALC: Window or Dialog Box?
The Common Dialog Boxes
POPPAD Revisited
Unicode File I/O
Changing the Font
Search and Replace
The One-Function-Call Windows Program

Chapter 12 The Clipboard

Simple Use of the Clipboard
The Standard Clipboard Data Formats
Memory Allocation
Transferring Text to the Clipboard
Getting Text from the Clipboard
Opening and Closing the Clipboard
The Clipboard and Unicode
Beyond Simple Clipboard Use
Using Multiple Data Items
Delayed Rendering
Private Data Formats
Becoming a Clipboard Viewer
The Clipboard Viewer Chain
Clipboard Viewer Functions and Messages
A Simple Clipboard Viewer

Section II More Graphics

Chapter 13 Using the Printer

Printing Fundamentals
Printing and Spooling
The Printer Device Context
The Revised DEVCAPS Program
The PrinterProperties Call
Checking for BitBlt Capability
The Simplest Printing Program
Printing Graphics and Text
Bare-Bones Printing
Canceling Printing with an Abort Procedure
How Windows Uses AbortProc
Implementing an Abort Procedure
Adding a Printing Dialog Box
Adding Printing to POPPAD

Chapter 14 Bitmaps and Bitblts

Bitmap Basics
Where Do Bitmaps Come From?
Bitmap Dimensions
Color and Bitmaps
Real-World Devices
Bitmap Support in GDI
The Bit-Block Transfer
A Simple BitBlt
Stretching the Bitmap
The StretchBlt Mode
The Raster Operations
The Pattern Blt
The GDI Bitmap Object
Creating a DDB
The Bitmap Bits
The Memory Device Context
Loading Bitmap Resources
The Monochrome Bitmap Format
Brushes from Bitmaps
Drawing on Bitmaps
The Shadow Bitmap
Using Bitmaps in Menus
Nonrectangular Bitmap Images
Some Simple Animation
Bitmaps Outside the Window

Chapter 15 The Device-Independent Bitmap

The DIB File Format
The OS/2-Style DIB
Bottoms Up!
The DIB Pixel Bits
The Expanded Windows DIB
Reality Check
DIB Compression
Color Masking
The Version 4 Header
The Version 5 Header
Displaying DIB Information
Displaying and Printing
Digging Into the DIB
Pixel to Pixel
The Topsy-Turvy World of DIBs
Sequential Display
Stretching to Fit
Color Conversion, Palettes, and Performance
The Union of DIBs and DDBs
Creating a DDB from a DIB
From DDB to DIB
The DIB Section
More DIB Section Differences
The File-Mapping Option
In Summary

Chapter 16 The Palette Manager

Using Palettes
Video Hardware
Displaying Gray Shades
The Palette Messages
The Palette Index Approach
Querying the Palette Support
The System Palette
Other Palette Functions
The Raster-Op Problem
Looking at the System Palette
Palette Animation
The Bouncing Ball
One-Entry Palette Animation
Engineering Applications
Palettes and Real-World Images
Palettes and Packed DIBs
The All-Purpose Palette
The Halftone Palette
Indexing Palette Colors
Palettes and Bitmap Objects
Palettes and DIB Sections
A Library for DIBs
The DIBSTRUCT Structure
The Information Functions
Reading and Writing Pixels
Creating and Converting
The DIBHELP Header File and Macros
The DIBBLE Program
Simple Palettes; Optimized Palettes
Converting Formats

Chapter 17 Text and Fonts

Simple Text Output
The Text Drawing Functions
Device Context Attributes for Text
Using Stock Fonts
Background on Fonts
The Types of Fonts
TrueType Fonts
Attributes or Styles?
The Point Size
Leading and Spacing
The Logical Inch Problem
The Logical Font
Logical Font Creation and Selection
The PICKFONT Program
The Logical Font Structure
The Font-Mapping Algorithm
Finding Out About the Font
Character Sets and Unicode
The EZFONT System
Font Rotation
Font Enumeration
The Enumeration Functions
The ChooseFont Dialog
Paragraph Formatting
Simple Text Formatting
Working with Paragraphs
Previewing Printer Output
The Fun and Fancy Stuff
The GDI Path
Extended Pens
Four Sample Programs

Chapter 18 Metafiles

The Old Metafile Format
Simple Use of Memory Metafiles
Storing Metafiles on Disk
Old Metafiles and the Clipboard
Enhanced Metafiles
The Basic Procedure
Looking Inside
Metafiles and GDI Objects
Metafiles and Bitmaps
Enumerating the Metafile
Embedding Images
An Enhanced Metafile Viewer and Printer
Displaying Accurate Metafile Images
Scaling and Aspect Ratios
Mapping Modes in Metafiles
Mapping and Playing

Section III Advanced Topics

Chapter 19 The Multiple-Document Interface

MDI Concepts
The Elements of MDI
MDI Support
A Sample MDI Implementation
Three Menus
Program Initialization
Creating the Children
More Frame Window Message Processing
The Child Document Windows
Cleaning Up

Chapter 20 Multitasking and Multithreading

Modes of Multitasking
Multitasking Under DOS?
Nonpreemptive Multitasking
PM and the Serialized Message Queue
The Multithreading Solution
Multithreaded Architecture
Thread Hassles
The Windows Advantage
New! Improved! Now With Threads!
Windows Multithreading
Random Rectangles Revisited
Any Problems?
The Benefits of Sleep
Thread Synchronization
The Critical Section
Event Signaling
The BIGJOB1 Program
The Event Object
Thread Local Storage

Chapter 21 Dynamic-Link Libraries

Library Basics
Library: One Word, Many Meanings
A Simple DLL
Shared Memory in DLLs
The Library Entry and Exit Point
The STRPROG Program
Sharing Data Among STRPROG Instances
Miscellaneous DLL Topics
Dynamic Linking Without Imports
Resource-Only Libraries

Chapter 22 Sound and Music

Windows and Multimedia
Multimedia Hardware
An API Overview
Exploring MCI with TESTMCI
MCITEXT and CD Audio
Waveform Audio
Sound and Waveforms
Pulse Code Modulation
The Sampling Rate
The Sample Size
Generating Sine Waves in Software
A Digital Sound Recorder
The MCI Alternative
The MCI Command String Approach
The Waveform Audio File Format
Experimenting with Additive Synthesis
Waking Up to Waveform Audio
MIDI and Music
The Workings of MIDI
The Program Change
The MIDI Channel
MIDI Messages
An Introduction to MIDI Sequencing
Playing a MIDI Synthesizer from the PC Keyboard
A MIDI Drum Machine
The Multimedia time Functions
RIFF File I/O

Chapter 23 A Taste of the Internet

Windows Sockets
Sockets and TCP/IP
Network Time Services
The NETTIME Program
WinInet and FTP
Overview of the FTP API
The Update Demo

Chapter 17 and the Table of Contents of Programming Windows reprinted with permission from Microsoft Press.
https://www.microsoft.com/mspress/

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

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

Simple Text Output

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

The Text Drawing Functions

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Device Context Attributes for Text

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

SetTextColor (hdc, rgbColor) ;

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

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

SetBkMode (hdc, iMode) ;

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

SetBkColor (hdc, rgbColor) ;

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

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

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

wndclass.hbrBackground = COLOR_WINDOW + 1 ;

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

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

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

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

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

SetTextCharacterExtra (hdc, iExtra) ;

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

Using Stock Fonts

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

You can obtain a handle to a stock font by calling

hFont = GetStockObject (iFont) ;

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

SelectObject (hdc, hFont) ;

Or you can accomplish this in one step:

SelectObject (hdc, GetStockObject (iFont)) ;

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

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

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

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

Background on Fonts

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

The Types of Fonts

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

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

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

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

System

(used for SYSTEM_FONT)

FixedSys

(used for SYSTEM_FIXED_FONT)

Terminal

(used for OEM_FIXED_FONT)

Courier

 

MS Serif

 

MS Sans Serif

(used for DEFAULT_GUI_FONT)

Small Fonts

 

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

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

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

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

TrueType Fonts

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

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

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

Courier New

Courier New Bold

Courier New Italic

Courier New Bold Italic

Times New Roman

Times New Roman Bold

Times New Roman Italic

Times New Roman Bold Italic

Arial

Arial Bold

Arial Italic

Arial Bold Italic

Symbol

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

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

Attributes or Styles?

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

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

The Point Size

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

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

Leading and Spacing

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

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

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

The Logical Inch Problem

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

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

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

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

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

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

SetMapMode (hdc, MM_ANISOTROPIC) ;

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

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

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

The Logical Font

Now that we've nailed down the concept of logical inches and logical twips, it's time to talk about logical fonts.

A logical font is a GDI object whose handle is stored in a variable of type HFONT. A logical font is a description of a font. Like the logical pen and logical brush, it is an abstract object that becomes real only when it is a selected into a device context when an application calls SelectObject. For logical pens, for instance, you can specify any color you want for the pen, but Windows converts that to a pure color available on the device when you select the pen into the device context. Only then does Windows know about the color capabilities of the device.

Logical Font Creation and Selection

You create a logical font by calling CreateFont or CreateFontIndirect. The CreateFontIndirect function takes a pointer to a LOGFONT structure, which has 14 fields. The CreateFont function takes 14 arguments, which are identical to the 14 fields of the LOGFONT structure. These are the only two functions that create a logical font. (I mention this because there are multiple functions in Windows for some other font jobs.) Because the 14 fields are difficult to remember, CreateFont is rarely used, so I'll focus on CreateFontIndirect.

There are three basic ways to define the fields of a LOGFONT structure in preparation for calling CreateFontIndirect:

  • You can simply set the fields of the LOGFONT structure to the characteristics of the font that you want. In this case, when you call SelectObject, Windows uses a "font mapping" algorithm to attempt to give you the font available on the device that best matches these characteristics. Depending on the fonts available on the video display or printer, the result might differ considerably from what you request.

  • You can enumerate all the fonts on the device and choose from those or even present them to the user with a dialog box. I'll discuss the font enumeration functions later in this chapter. These are not used much these days because the third method does the enumeration for you.

  • You can take the simple approach and call the ChooseFont function, which I discussed a little in Chapter 11. You get back a LOGFONT structure that you can use directly for creating the font.

In this chapter, I'll use the first and third approaches.

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

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

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

  • Determine the size and characteristics of the real font with GetTextMetrics (and possibly some other functions). You can use this information to properly space the text that you write when this font is selected into the device context.

  • After you've finished using the font, delete the logical font by calling DeleteObject. Don't delete the font while it is selected in a valid device context, and don't delete stock fonts.

The GetTextFace function lets a program determine the face name of the font currently selected in the device context:

GetTextFace (hdc, sizeof (szFaceName) / sizeof (TCHAR), szFaceName) ;

The detailed font information is available from GetTextMetrics:

GetTextMetrics (hdc, &textmetric) ;

where textmetric is a variable of type TEXTMETRIC, a structure with 20 fields.

I'll discuss the fields of the LOGFONT and TEXTMETRIC structures in detail shortly. The structures have some similar fields, so they can be confusing. For now, just keep in mind that LOGFONT is for defining a logical font and TEXTMETRIC is for obtaining information about the font currently selected in the device context.

The PICKFONT Program

With the PICKFONT program shown in Figure 17-1, you can define many of the fields of a LOGFONT structure. The program creates a logical font and displays the characteristics of the real font after the logical font has been selected in the screen device context. This is a handy program for understanding how logical fonts are mapped to real fonts.

PICKFONT.C

/*-----------------------------------------
PICKFONT.C -- Create Logical Font
(c) Charles Petzold, 1998
-----------------------------------------*/

#include <windows.h>
#include "resource.h"

// Structure shared between main window and dialog box

typedef struct
            {
                    int        iDevice, iMapMode ;
                    BOOL       fMatchAspect ;
                    BOOL       fAdvGraphics ;
                    LOGFONT    lf ;
                    TEXTMETRIC tm ;
                    TCHAR      szFaceName [LF_FULLFACESIZE] ;
             }
DLGPARAMS ;

// Formatting for BCHAR fields of TEXTMETRIC structure

#ifdef UNICODE
#define BCHARFORM TEXT ("0x%04X")
#else
#define BCHARFORM TEXT ("0x%02X")
#endif

// Global variables

HWND  hdlg ;
TCHAR szAppName[] = TEXT ("PickFont") ;

// Forward declarations of functions

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
BOOL    CALLBACK DlgProc (HWND, UINT, WPARAM, LPARAM) ;
void SetLogFontFromFields    (HWND hdlg, DLGPARAMS * pdp) ;
void SetFieldsFromTextMetric (HWND hdlg, DLGPARAMS * pdp) ;
void MySetMapMode (HDC hdc, int iMapMode) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
             {
                    HWND     hwnd ;
                    MSG      msg ;
                    WNDCLASS wndclass ;

                    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 = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
                    wndclass.lpszMenuName  = szAppName ; 
                    wndclass.lpszClassName = szAppName ;

if (!RegisterClass (&wndclass))
            {
                    MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                    szAppName, MB_ICONERROR) ;
                    return 0 ;
             }
hwnd = CreateWindow (szAppName, TEXT ("PickFont: Create Logical Font"),
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL) ;
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
            {
                    if (hdlg == 0 || !IsDialogMessage (hdlg, &msg))
            {
                    TranslateMessage (&msg) ;
                    DispatchMessage (&msg) ;
            } 
            }
                    return msg.wParam ;
            }
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
           {
                    static DLGPARAMS dp ;
                    static TCHAR     szText[] = TEXT ("\x41\x42\x43\x44\x45 ")
                    TEXT ("\x61\x62\x63\x64\x65 ")
                    TEXT ("\xC0\xC1\xC2\xC3\xC4\xC5 ")
                    TEXT ("\xE0\xE1\xE2\xE3\xE4\xE5 ") 
                    #ifdef UNICODE
                    TEXT ("\x0390\x0391\x0392\x0393\x0394\x0395 ")
                    TEXT ("\x03B0\x03B1\x03B2\x03B3\x03B4\x03B5 ")
                    TEXT ("\x5000\x5001\x5002\x5003\x5004") 
                    #endif
                    ;
                    HDC              hdc ;
                    PAINTSTRUCT      ps ;
                    RECT             rect ;
                    switch (message)
          {
                    case WM_CREATE:
                    dp.iDevice = IDM_DEVICE_SCREEN ;
                    hdlg = CreateDialogParam (((LPCREATESTRUCT) lParam)->hInstance, 
                    szAppName, hwnd, DlgProc, (LPARAM) &dp) ;
                    return 0 ;
                    case WM_SETFOCUS:
                    SetFocus (hdlg) ;
                    return 0 ;
                    case WM_COMMAND:
                    switch (LOWORD (wParam))
          {
                    case IDM_DEVICE_SCREEN:
                    case IDM_DEVICE_PRINTER:
                    CheckMenuItem (GetMenu (hwnd), dp.iDevice, MF_UNCHECKED) ;
                    dp.iDevice = LOWORD (wParam) ;
                    CheckMenuItem (GetMenu (hwnd), dp.iDevice, MF_CHECKED) ;
                    SendMessage (hwnd, WM_COMMAND, IDOK, 0) ;
                    return 0 ;
          }
                    break ;
                    case WM_PAINT:
                    hdc = BeginPaint (hwnd, &ps) ;
// Set graphics mode so escapement works in Windows NT
                    SetGraphicsMode (hdc, dp.fAdvGraphics ? GM_ADVANCED : GM_COMPATIBLE) ;
// Set the mapping mode and the mapper flag
                    MySetMapMode (hdc, dp.iMapMode) ;
                    SetMapperFlags (hdc, dp.fMatchAspect) ;
// Find the point to begin drawing text
                    GetClientRect (hdlg, &rect) ;
                    rect.bottom += 1 ;
                    DPtoLP (hdc, (PPOINT) &rect, 2) ;
// Create and select the font; display the text
                    SelectObject (hdc, CreateFontIndirect (&dp.lf)) ;
                    TextOut (hdc, rect.left, rect.bottom, szText, lstrlen (szText)) ;
                    DeleteObject (SelectObject (hdc, GetStockObject (SYSTEM_FONT))) ;
                    EndPaint (hwnd, &ps) ;
                    return 0 ;
                    case WM_DESTROY:
                    PostQuitMessage (0) ;
                    return 0 ;
         }
                    return DefWindowProc (hwnd, message, wParam, lParam) ;
         }
BOOL CALLBACK DlgProc (HWND hdlg, UINT message, WPARAM wParam, LPARAM lParam)
         {
                    static DLGPARAMS * pdp ;
                    static PRINTDLG    pd = { sizeof (PRINTDLG) } ;
                    HDC                hdcDevice ;
                    HFONT              hFont ;
                    switch (message)
         {
                    case WM_INITDIALOG:
// Save pointer to dialog-parameters structure in WndProc
                    pdp = (DLGPARAMS *) lParam ;
                    SendDlgItemMessage (hdlg, IDC_LF_FACENAME, EM_LIMITTEXT, 
                    LF_FACESIZE - 1, 0) ;
                    CheckRadioButton (hdlg, IDC_OUT_DEFAULT, IDC_OUT_OUTLINE,
                    IDC_OUT_DEFAULT) ;
                    CheckRadioButton (hdlg, IDC_DEFAULT_QUALITY, IDC_PROOF_QUALITY,
                    IDC_DEFAULT_QUALITY) ;
                    CheckRadioButton (hdlg, IDC_DEFAULT_PITCH, IDC_VARIABLE_PITCH,
                    IDC_DEFAULT_PITCH) ;
                    CheckRadioButton (hdlg, IDC_FF_DONTCARE, IDC_FF_DECORATIVE,
                    IDC_FF_DONTCARE) ;
                    CheckRadioButton (hdlg, IDC_MM_TEXT, IDC_MM_LOGTWIPS,
                    IDC_MM_TEXT) ;
                    SendMessage (hdlg, WM_COMMAND, IDOK, 0) ;
// fall through
     case WM_SETFOCUS:
          SetFocus (GetDlgItem (hdlg, IDC_LF_HEIGHT)) ;
          return FALSE ;
     case WM_COMMAND:
          switch (LOWORD (wParam))
          {
          case IDC_CHARSET_HELP:
               MessageBox (hdlg, 
                           TEXT ("0 = Ansi\n")
                           TEXT ("1 = Default\n")
                           TEXT ("2 = Symbol\n")
                           TEXT ("128 = Shift JIS (Japanese)\n")
                           TEXT ("129 = Hangul (Korean)\n")
                           TEXT ("130 = Johab (Korean)\n")
                           TEXT ("134 = GB 2312 (Simplified Chinese)\n")
                           TEXT ("136 = Chinese Big 5 (Tradtional Chinese)\n")
                           TEXT ("177 = Hebrew\n")
                           TEXT ("178 = Arabic\n")
                           TEXT ("161 = Greek\n")
                           TEXT ("162 = Turkish\n")
                           TEXT ("163 = Vietnamese\n")
                           TEXT ("204 = Russian\n")
                           TEXT ("222 = Thai\n")
                           TEXT ("238 = East European\n")
                           TEXT ("255 = OEM"),
                           szAppName, MB_OK | MB_ICONINFORMATION) ;
               return TRUE ;
               // These radio buttons set the lfOutPrecision field
          case IDC_OUT_DEFAULT:   
               pdp->lf.lfOutPrecision = OUT_DEFAULT_PRECIS ;  
               return TRUE ;
          case IDC_OUT_STRING:
               pdp->lf.lfOutPrecision = OUT_STRING_PRECIS ;  
               return TRUE ;
          case IDC_OUT_CHARACTER:
               pdp->lf.lfOutPrecision = OUT_CHARACTER_PRECIS ;  
               return TRUE ;
          case IDC_OUT_STROKE:
               pdp->lf.lfOutPrecision = OUT_STROKE_PRECIS ;  
               return TRUE ;
          case IDC_OUT_TT:
               pdp->lf.lfOutPrecision = OUT_TT_PRECIS ;  
               return TRUE ;
          case IDC_OUT_DEVICE:
               pdp->lf.lfOutPrecision = OUT_DEVICE_PRECIS ;  
               return TRUE ;
          case IDC_OUT_RASTER:
               pdp->lf.lfOutPrecision = OUT_RASTER_PRECIS ;  
               return TRUE ;
          case IDC_OUT_TT_ONLY:
               pdp->lf.lfOutPrecision = OUT_TT_ONLY_PRECIS ;  
               return TRUE ;
          case IDC_OUT_OUTLINE:
               pdp->lf.lfOutPrecision = OUT_OUTLINE_PRECIS ;  
               return TRUE ;
               // These three radio buttons set the lfQuality field
          case IDC_DEFAULT_QUALITY:
               pdp->lf.lfQuality = DEFAULT_QUALITY ;   
               return TRUE ;
          case IDC_DRAFT_QUALITY:
               pdp->lf.lfQuality = DRAFT_QUALITY ;  
               return TRUE ;
          case IDC_PROOF_QUALITY:
               pdp->lf.lfQuality = PROOF_QUALITY ;  
               return TRUE ;
               // These three radio buttons set the lower nibble
               //   of the lfPitchAndFamily field
          case IDC_DEFAULT_PITCH:
               pdp->lf.lfPitchAndFamily = 
                    (0xF0 & pdp->lf.lfPitchAndFamily) | DEFAULT_PITCH ; 
               return TRUE ;
          case IDC_FIXED_PITCH:
               pdp->lf.lfPitchAndFamily = 
                    (0xF0 & pdp->lf.lfPitchAndFamily) | FIXED_PITCH ; 
               return TRUE ;
          case IDC_VARIABLE_PITCH:
               pdp->lf.lfPitchAndFamily = 
                    (0xF0 & pdp->lf.lfPitchAndFamily) | VARIABLE_PITCH ;  
               return TRUE ;
               // These six radio buttons set the upper nibble
               //   of the lfPitchAndFamily field
          case IDC_FF_DONTCARE:
               pdp->lf.lfPitchAndFamily =
                    (0x0F & pdp->lf.lfPitchAndFamily) | FF_DONTCARE ;  
               return TRUE ;
          case IDC_FF_ROMAN:
               pdp->lf.lfPitchAndFamily =
                    (0x0F & pdp->lf.lfPitchAndFamily) | FF_ROMAN;  
               return TRUE ;
          case IDC_FF_SWISS:
               pdp->lf.lfPitchAndFamily =
                    (0x0F & pdp->lf.lfPitchAndFamily) | FF_SWISS ;  
               return TRUE ;
          case IDC_FF_MODERN:
               pdp->lf.lfPitchAndFamily =
                    (0x0F & pdp->lf.lfPitchAndFamily) | FF_MODERN ;  
               return TRUE ;
          case IDC_FF_SCRIPT:
               pdp->lf.lfPitchAndFamily =
                    (0x0F & pdp->lf.lfPitchAndFamily) | FF_SCRIPT ;  
               return TRUE ;
          case IDC_FF_DECORATIVE:
               pdp->lf.lfPitchAndFamily =
                    (0x0F & pdp->lf.lfPitchAndFamily) | FF_DECORATIVE ;  
               return TRUE ;
               // Mapping mode:
          case IDC_MM_TEXT:
          case IDC_MM_LOMETRIC:
          case IDC_MM_HIMETRIC:
          case IDC_MM_LOENGLISH:
          case IDC_MM_HIENGLISH:
          case IDC_MM_TWIPS:
          case IDC_MM_LOGTWIPS:
               pdp->iMapMode = LOWORD (wParam) ;
               return TRUE ;
               // OK button pressed
               // -----------------
          case IDOK:
                    // Get LOGFONT structure
               SetLogFontFromFields (hdlg, pdp) ;
                    // Set Match-Aspect and Advanced Graphics flags
               pdp->fMatchAspect = IsDlgButtonChecked (hdlg, IDC_MATCH_ASPECT) ;
               pdp->fAdvGraphics = IsDlgButtonChecked (hdlg, IDC_ADV_GRAPHICS) ;
                    // Get Information Context
               if (pdp->iDevice == IDM_DEVICE_SCREEN)
               {
                    hdcDevice = CreateIC (TEXT ("DISPLAY"), NULL, NULL, NULL) ;
               }
               else
               {
                    pd.hwndOwner = hdlg ;
                    pd.Flags = PD_RETURNDEFAULT | PD_RETURNIC ;
                    pd.hDevNames = NULL ;
                    pd.hDevMode = NULL ;
                    PrintDlg (&pd) ;
                    hdcDevice = pd.hDC ;
               }
                    // Set the mapping mode and the mapper flag
               MySetMapMode (hdcDevice, pdp->iMapMode) ;
               SetMapperFlags (hdcDevice, pdp->fMatchAspect) ;
                    // Create font and select it into IC
               hFont = CreateFontIndirect (&pdp->lf) ;
               SelectObject (hdcDevice, hFont) ;
                    // Get the text metrics and face name
               GetTextMetrics (hdcDevice, &pdp->tm) ;
               GetTextFace (hdcDevice, LF_FULLFACESIZE, pdp->szFaceName) ;
               DeleteDC (hdcDevice) ;
               DeleteObject (hFont) ;
                    // Update dialog fields and invalidate main window
               SetFieldsFromTextMetric (hdlg, pdp) ;
               InvalidateRect (GetParent (hdlg), NULL, TRUE) ;
               return TRUE ;
          }
          break ;
     }
     return FALSE ;
}
void SetLogFontFromFields (HWND hdlg, DLGPARAMS * pdp)
{
     pdp->lf.lfHeight      = GetDlgItemInt (hdlg, IDC_LF_HEIGHT,  NULL, TRUE) ;
     pdp->lf.lfWidth       = GetDlgItemInt (hdlg, IDC_LF_WIDTH,   NULL, TRUE) ;
     pdp->lf.lfEscapement  = GetDlgItemInt (hdlg, IDC_LF_ESCAPE,  NULL, TRUE) ;
     pdp->lf.lfOrientation = GetDlgItemInt (hdlg, IDC_LF_ORIENT,  NULL, TRUE) ;
     pdp->lf.lfWeight      = GetDlgItemInt (hdlg, IDC_LF_WEIGHT,  NULL, TRUE) ;
     pdp->lf.lfCharSet     = GetDlgItemInt (hdlg, IDC_LF_CHARSET, NULL, FALSE) ;
     pdp->lf.lfItalic = 
                    IsDlgButtonChecked (hdlg, IDC_LF_ITALIC) == BST_CHECKED ;
     pdp->lf.lfUnderline = 
                    IsDlgButtonChecked (hdlg, IDC_LF_UNDER)  == BST_CHECKED ;
     pdp->lf.lfStrikeOut = 
                    IsDlgButtonChecked (hdlg, IDC_LF_STRIKE) == BST_CHECKED ;
     GetDlgItemText (hdlg, IDC_LF_FACENAME, pdp->lf.lfFaceName, LF_FACESIZE) ;
}
void SetFieldsFromTextMetric (HWND hdlg, DLGPARAMS * pdp) 
{
     TCHAR   szBuffer [10] ;
     TCHAR * szYes = TEXT ("Yes") ; 
     TCHAR * szNo  = TEXT ("No") ;
     TCHAR * szFamily [] = { TEXT ("Don't Know"), TEXT ("Roman"),
                             TEXT ("Swiss"),      TEXT ("Modern"),
                             TEXT ("Script"),     TEXT ("Decorative"), 
                             TEXT ("Undefined") } ;
     SetDlgItemInt (hdlg, IDC_TM_HEIGHT,   pdp->tm.tmHeight,           TRUE) ;
     SetDlgItemInt (hdlg, IDC_TM_ASCENT,   pdp->tm.tmAscent,           TRUE) ;
     SetDlgItemInt (hdlg, IDC_TM_DESCENT,  pdp->tm.tmDescent,          TRUE) ;
     SetDlgItemInt (hdlg, IDC_TM_INTLEAD,  pdp->tm.tmInternalLeading,  TRUE) ;
     SetDlgItemInt (hdlg, IDC_TM_EXTLEAD,  pdp->tm.tmExternalLeading,  TRUE) ;
     SetDlgItemInt (hdlg, IDC_TM_AVECHAR,  pdp->tm.tmAveCharWidth,     TRUE) ;
     SetDlgItemInt (hdlg, IDC_TM_MAXCHAR,  pdp->tm.tmMaxCharWidth,     TRUE) ;
     SetDlgItemInt (hdlg, IDC_TM_WEIGHT,   pdp->tm.tmWeight,           TRUE) ;
     SetDlgItemInt (hdlg, IDC_TM_OVERHANG, pdp->tm.tmOverhang,         TRUE) ;
     SetDlgItemInt (hdlg, IDC_TM_DIGASPX,  pdp->tm.tmDigitizedAspectX, TRUE) ;
     SetDlgItemInt (hdlg, IDC_TM_DIGASPY,  pdp->tm.tmDigitizedAspectY, TRUE) ;
     wsprintf (szBuffer, BCHARFORM, pdp->tm.tmFirstChar) ;
     SetDlgItemText (hdlg, IDC_TM_FIRSTCHAR, szBuffer) ;
     wsprintf (szBuffer, BCHARFORM, pdp->tm.tmLastChar) ;
     SetDlgItemText (hdlg, IDC_TM_LASTCHAR, szBuffer) ;
     wsprintf (szBuffer, BCHARFORM, pdp->tm.tmDefaultChar) ;
     SetDlgItemText (hdlg, IDC_TM_DEFCHAR, szBuffer) ;
     wsprintf (szBuffer, BCHARFORM, pdp->tm.tmBreakChar) ;
     SetDlgItemText (hdlg, IDC_TM_BREAKCHAR, szBuffer) ;
     SetDlgItemText (hdlg, IDC_TM_ITALIC, pdp->tm.tmItalic     ? szYes : szNo) ;
     SetDlgItemText (hdlg, IDC_TM_UNDER,  pdp->tm.tmUnderlined ? szYes : szNo) ;
     SetDlgItemText (hdlg, IDC_TM_STRUCK, pdp->tm.tmStruckOut  ? szYes : szNo) ;
     SetDlgItemText (hdlg, IDC_TM_VARIABLE, 
               TMPF_FIXED_PITCH & pdp->tm.tmPitchAndFamily ? szYes : szNo) ;
     SetDlgItemText (hdlg, IDC_TM_VECTOR, 
               TMPF_VECTOR & pdp->tm.tmPitchAndFamily ? szYes : szNo) ;
     SetDlgItemText (hdlg, IDC_TM_TRUETYPE, 
               TMPF_TRUETYPE & pdp->tm.tmPitchAndFamily ? szYes : szNo) ;
     SetDlgItemText (hdlg, IDC_TM_DEVICE, 
               TMPF_DEVICE & pdp->tm.tmPitchAndFamily ? szYes : szNo) ;
     SetDlgItemText (hdlg, IDC_TM_FAMILY, 
               szFamily [min (6, pdp->tm.tmPitchAndFamily >> 4)]) ;
     SetDlgItemInt  (hdlg, IDC_TM_CHARSET,   pdp->tm.tmCharSet, FALSE) ;
     SetDlgItemText (hdlg, IDC_TM_FACENAME, pdp->szFaceName) ;
}
void MySetMapMode (HDC hdc, int iMapMode)
{
     switch (iMapMode)
     {
     case IDC_MM_TEXT:       SetMapMode (hdc, MM_TEXT) ;       break ;
     case IDC_MM_LOMETRIC:   SetMapMode (hdc, MM_LOMETRIC) ;   break ;
     case IDC_MM_HIMETRIC:   SetMapMode (hdc, MM_HIMETRIC) ;   break ;
     case IDC_MM_LOENGLISH:  SetMapMode (hdc, MM_LOENGLISH) ;  break ;
     case IDC_MM_HIENGLISH:  SetMapMode (hdc, MM_HIENGLISH) ;  break ;
     case IDC_MM_TWIPS:      SetMapMode (hdc, MM_TWIPS) ;      break ;
     case IDC_MM_LOGTWIPS:
          SetMapMode (hdc, MM_ANISOTROPIC) ;
          SetWindowExtEx (hdc, 1440, 1440, NULL) ;
          SetViewportExtEx (hdc, GetDeviceCaps (hdc, LOGPIXELSX),
                                 GetDeviceCaps (hdc, LOGPIXELSY), NULL) ;
          break ;
     }
}

PICKFONT.RC

//Microsoft Developer Studio generated resource script.
#include "resource.h"
#include "afxres.h"
/////////////////////////////////////////////////////////////////////////////
// Dialog
PICKFONT DIALOG DISCARDABLE  0, 0, 348, 308
STYLE WS_CHILD | WS_VISIBLE | WS_BORDER
FONT 8, "MS Sans Serif"
BEGIN
    LTEXT           "&Height:",IDC_STATIC,8,10,44,8
    EDITTEXT        IDC_LF_HEIGHT,64,8,24,12,ES_AUTOHSCROLL
    LTEXT           "&Width",IDC_STATIC,8,26,44,8
    EDITTEXT        IDC_LF_WIDTH,64,24,24,12,ES_AUTOHSCROLL
    LTEXT           "Escapement:",IDC_STATIC,8,42,44,8
    EDITTEXT        IDC_LF_ESCAPE,64,40,24,12,ES_AUTOHSCROLL
    LTEXT           "Orientation:",IDC_STATIC,8,58,44,8
    EDITTEXT        IDC_LF_ORIENT,64,56,24,12,ES_AUTOHSCROLL
    LTEXT           "Weight:",IDC_STATIC,8,74,44,8
    EDITTEXT        IDC_LF_WEIGHT,64,74,24,12,ES_AUTOHSCROLL
    GROUPBOX        "Mapping Mode",IDC_STATIC,97,3,96,90,WS_GROUP
    CONTROL         "Text",IDC_MM_TEXT,"Button",BS_AUTORADIOBUTTON,104,13,56,
                    8
    CONTROL         "Low Metric",IDC_MM_LOMETRIC,"Button",BS_AUTORADIOBUTTON,
                    104,24,56,8
    CONTROL         "High Metric",IDC_MM_HIMETRIC,"Button",
                    BS_AUTORADIOBUTTON,104,35,56,8
    CONTROL         "Low English",IDC_MM_LOENGLISH,"Button",
                    BS_AUTORADIOBUTTON,104,46,56,8
    CONTROL         "High English",IDC_MM_HIENGLISH,"Button",
                    BS_AUTORADIOBUTTON,104,57,56,8
    CONTROL         "Twips",IDC_MM_TWIPS,"Button",BS_AUTORADIOBUTTON,104,68,
                    56,8
    CONTROL         "Logical Twips",IDC_MM_LOGTWIPS,"Button",
                    BS_AUTORADIOBUTTON,104,79,64,8
    CONTROL         "Italic",IDC_LF_ITALIC,"Button",BS_AUTOCHECKBOX | 
                    WS_TABSTOP,8,90,48,12
    CONTROL         "Underline",IDC_LF_UNDER,"Button",BS_AUTOCHECKBOX | 
                    WS_TABSTOP,8,104,48,12
    CONTROL         "Strike Out",IDC_LF_STRIKE,"Button",BS_AUTOCHECKBOX | 
                    WS_TABSTOP,8,118,48,12
    CONTROL         "Match Aspect",IDC_MATCH_ASPECT,"Button",BS_AUTOCHECKBOX | 
                    WS_TABSTOP,60,104,62,8
    CONTROL         "Adv Grfx Mode",IDC_ADV_GRAPHICS,"Button",
                    BS_AUTOCHECKBOX | WS_TABSTOP,60,118,62,8
    LTEXT           "Character Set:",IDC_STATIC,8,137,46,8
    EDITTEXT        IDC_LF_CHARSET,58,135,24,12,ES_AUTOHSCROLL
    PUSHBUTTON      "?",IDC_CHARSET_HELP,90,135,14,14
    GROUPBOX        "Quality",IDC_STATIC,132,98,62,48,WS_GROUP
    CONTROL         "Default",IDC_DEFAULT_QUALITY,"Button",
                    BS_AUTORADIOBUTTON,136,110,40,8
    CONTROL         "Draft",IDC_DRAFT_QUALITY,"Button",BS_AUTORADIOBUTTON,
                    136,122,40,8
    CONTROL         "Proof",IDC_PROOF_QUALITY,"Button",BS_AUTORADIOBUTTON,
                    136,134,40,8
    LTEXT           "Face Name:",IDC_STATIC,8,154,44,8
    EDITTEXT        IDC_LF_FACENAME,58,152,136,12,ES_AUTOHSCROLL
    GROUPBOX        "Output Precision",IDC_STATIC,8,166,118,133,WS_GROUP
    CONTROL         "OUT_DEFAULT_PRECIS",IDC_OUT_DEFAULT,"Button",
                    BS_AUTORADIOBUTTON,12,178,112,8
    CONTROL         "OUT_STRING_PRECIS",IDC_OUT_STRING,"Button",
                    BS_AUTORADIOBUTTON,12,191,112,8
    CONTROL         "OUT_CHARACTER_PRECIS",IDC_OUT_CHARACTER,"Button",
                    BS_AUTORADIOBUTTON,12,204,112,8
    CONTROL         "OUT_STROKE_PRECIS",IDC_OUT_STROKE,"Button",
                    BS_AUTORADIOBUTTON,12,217,112,8
    CONTROL         "OUT_TT_PRECIS",IDC_OUT_TT,"Button",BS_AUTORADIOBUTTON,
                    12,230,112,8
    CONTROL         "OUT_DEVICE_PRECIS",IDC_OUT_DEVICE,"Button",
                    BS_AUTORADIOBUTTON,12,243,112,8
    CONTROL         "OUT_RASTER_PRECIS",IDC_OUT_RASTER,"Button",
                    BS_AUTORADIOBUTTON,12,256,112,8
    CONTROL         "OUT_TT_ONLY_PRECIS",IDC_OUT_TT_ONLY,"Button",
                    BS_AUTORADIOBUTTON,12,269,112,8
    CONTROL         "OUT_OUTLINE_PRECIS",IDC_OUT_OUTLINE,"Button",
                    BS_AUTORADIOBUTTON,12,282,112,8
    GROUPBOX        "Pitch",IDC_STATIC,132,166,62,50,WS_GROUP
    CONTROL         "Default",IDC_DEFAULT_PITCH,"Button",BS_AUTORADIOBUTTON,
                    137,176,52,8
    CONTROL         "Fixed",IDC_FIXED_PITCH,"Button",BS_AUTORADIOBUTTON,137,
                    189,52,8
    CONTROL         "Variable",IDC_VARIABLE_PITCH,"Button",
                    BS_AUTORADIOBUTTON,137,203,52,8
    GROUPBOX        "Family",IDC_STATIC,132,218,62,82,WS_GROUP
    CONTROL         "Don't Care",IDC_FF_DONTCARE,"Button",BS_AUTORADIOBUTTON,
                    137,229,52,8
    CONTROL         "Roman",IDC_FF_ROMAN,"Button",BS_AUTORADIOBUTTON,137,241,
                    52,8
    CONTROL         "Swiss",IDC_FF_SWISS,"Button",BS_AUTORADIOBUTTON,137,253,
                    52,8
    CONTROL         "Modern",IDC_FF_MODERN,"Button",BS_AUTORADIOBUTTON,137,
                    265,52,8
    CONTROL         "Script",IDC_FF_SCRIPT,"Button",BS_AUTORADIOBUTTON,137,
                    277,52,8
    CONTROL         "Decorative",IDC_FF_DECORATIVE,"Button",
                    BS_AUTORADIOBUTTON,137,289,52,8
    DEFPUSHBUTTON   "OK",IDOK,247,286,50,14
    GROUPBOX        "Text Metrics",IDC_STATIC,201,2,140,272,WS_GROUP
    LTEXT           "Height:",IDC_STATIC,207,12,64,8
    LTEXT           "0",IDC_TM_HEIGHT,281,12,44,8
    LTEXT           "Ascent:",IDC_STATIC,207,22,64,8
    LTEXT           "0",IDC_TM_ASCENT,281,22,44,8
    LTEXT           "Descent:",IDC_STATIC,207,32,64,8
    LTEXT           "0",IDC_TM_DESCENT,281,32,44,8
    LTEXT           "Internal Leading:",IDC_STATIC,207,42,64,8
    LTEXT           "0",IDC_TM_INTLEAD,281,42,44,8
    LTEXT           "External Leading:",IDC_STATIC,207,52,64,8
    LTEXT           "0",IDC_TM_EXTLEAD,281,52,44,8
    LTEXT           "Ave Char Width:",IDC_STATIC,207,62,64,8
    LTEXT           "0",IDC_TM_AVECHAR,281,62,44,8
    LTEXT           "Max Char Width:",IDC_STATIC,207,72,64,8
    LTEXT           "0",IDC_TM_MAXCHAR,281,72,44,8
    LTEXT           "Weight:",IDC_STATIC,207,82,64,8
    LTEXT           "0",IDC_TM_WEIGHT,281,82,44,8
    LTEXT           "Overhang:",IDC_STATIC,207,92,64,8
    LTEXT           "0",IDC_TM_OVERHANG,281,92,44,8
    LTEXT           "Digitized Aspect X:",IDC_STATIC,207,102,64,8
    LTEXT           "0",IDC_TM_DIGASPX,281,102,44,8
    LTEXT           "Digitized Aspect Y:",IDC_STATIC,207,112,64,8
    LTEXT           "0",IDC_TM_DIGASPY,281,112,44,8
    LTEXT           "First Char:",IDC_STATIC,207,122,64,8
    LTEXT           "0",IDC_TM_FIRSTCHAR,281,122,44,8
    LTEXT           "Last Char:",IDC_STATIC,207,132,64,8
    LTEXT           "0",IDC_TM_LASTCHAR,281,132,44,8
    LTEXT           "Default Char:",IDC_STATIC,207,142,64,8
    LTEXT           "0",IDC_TM_DEFCHAR,281,142,44,8
    LTEXT           "Break Char:",IDC_STATIC,207,152,64,8
    LTEXT           "0",IDC_TM_BREAKCHAR,281,152,44,8
    LTEXT           "Italic?",IDC_STATIC,207,162,64,8
    LTEXT           "0",IDC_TM_ITALIC,281,162,44,8
    LTEXT           "Underlined?",IDC_STATIC,207,172,64,8
    LTEXT           "0",IDC_TM_UNDER,281,172,44,8
    LTEXT           "Struck Out?",IDC_STATIC,207,182,64,8
    LTEXT           "0",IDC_TM_STRUCK,281,182,44,8
    LTEXT           "Variable Pitch?",IDC_STATIC,207,192,64,8
    LTEXT           "0",IDC_TM_VARIABLE,281,192,44,8
    LTEXT           "Vector Font?",IDC_STATIC,207,202,64,8
    LTEXT           "0",IDC_TM_VECTOR,281,202,44,8
    LTEXT           "TrueType Font?",IDC_STATIC,207,212,64,8
    LTEXT           "0",IDC_TM_TRUETYPE,281,212,44,8
    LTEXT           "Device Font?",IDC_STATIC,207,222,64,8
    LTEXT           "0",IDC_TM_DEVICE,281,222,44,8
    LTEXT           "Family:",IDC_STATIC,207,232,64,8
    LTEXT           "0",IDC_TM_FAMILY,281,232,44,8
    LTEXT           "Charecter Set:",IDC_STATIC,207,242,64,8
    LTEXT           "0",IDC_TM_CHARSET,281,242,44,8
    LTEXT           "0",IDC_TM_FACENAME,207,262,128,8
END
/////////////////////////////////////////////////////////////////////////////
// Menu
PICKFONT MENU DISCARDABLE 
BEGIN
    POPUP "&Device"
    BEGIN
        MENUITEM "&Screen",                     IDM_DEVICE_SCREEN, CHECKED
        MENUITEM "&Printer",                    IDM_DEVICE_PRINTER
    END
END

RESOURCE.H

// Microsoft Developer Studio generated include file.
// Used by PickFont.rc
#define IDC_LF_HEIGHT                   1000
#define IDC_LF_WIDTH                    1001
#define IDC_LF_ESCAPE                   1002
#define IDC_LF_ORIENT                   1003
#define IDC_LF_WEIGHT                   1004
#define IDC_MM_TEXT                     1005
#define IDC_MM_LOMETRIC                 1006
#define IDC_MM_HIMETRIC                 1007
#define IDC_MM_LOENGLISH                1008
#define IDC_MM_HIENGLISH                1009
#define IDC_MM_TWIPS                    1010
#define IDC_MM_LOGTWIPS                 1011
#define IDC_LF_ITALIC                   1012
#define IDC_LF_UNDER                    1013
#define IDC_LF_STRIKE                   1014
#define IDC_MATCH_ASPECT                1015
#define IDC_ADV_GRAPHICS                1016
#define IDC_LF_CHARSET                  1017
#define IDC_CHARSET_HELP                1018
#define IDC_DEFAULT_QUALITY             1019
#define IDC_DRAFT_QUALITY               1020
#define IDC_PROOF_QUALITY               1021
#define IDC_LF_FACENAME                 1022
#define IDC_OUT_DEFAULT                 1023
#define IDC_OUT_STRING                  1024
#define IDC_OUT_CHARACTER               1025
#define IDC_OUT_STROKE                  1026
#define IDC_OUT_TT                      1027
#define IDC_OUT_DEVICE                  1028
#define IDC_OUT_RASTER                  1029
#define IDC_OUT_TT_ONLY                 1030
#define IDC_OUT_OUTLINE                 1031
#define IDC_DEFAULT_PITCH               1032
#define IDC_FIXED_PITCH                 1033
#define IDC_VARIABLE_PITCH              1034
#define IDC_FF_DONTCARE                 1035
#define IDC_FF_ROMAN                    1036
#define IDC_FF_SWISS                    1037
#define IDC_FF_MODERN                   1038
#define IDC_FF_SCRIPT                   1039
#define IDC_FF_DECORATIVE               1040
#define IDC_TM_HEIGHT                   1041
#define IDC_TM_ASCENT                   1042
#define IDC_TM_DESCENT                  1043
#define IDC_TM_INTLEAD                  1044
#define IDC_TM_EXTLEAD                  1045
#define IDC_TM_AVECHAR                  1046
#define IDC_TM_MAXCHAR                  1047
#define IDC_TM_WEIGHT                   1048
#define IDC_TM_OVERHANG                 1049
#define IDC_TM_DIGASPX                  1050
#define IDC_TM_DIGASPY                  1051
#define IDC_TM_FIRSTCHAR                1052
#define IDC_TM_LASTCHAR                 1053
#define IDC_TM_DEFCHAR                  1054
#define IDC_TM_BREAKCHAR                1055
#define IDC_TM_ITALIC                   1056
#define IDC_TM_UNDER                    1057
#define IDC_TM_STRUCK                   1058
#define IDC_TM_VARIABLE                 1059
#define IDC_TM_VECTOR                   1060
#define IDC_TM_TRUETYPE                 1061
#define IDC_TM_DEVICE                   1062
#define IDC_TM_FAMILY                   1063
#define IDC_TM_CHARSET                  1064
#define IDC_TM_FACENAME                 1065
#define IDM_DEVICE_SCREEN               40001
#define IDM_DEVICE_PRINTER              40002

Figure 17-1 The PICKFONT program.

Figure 17-2 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 of the dialog box shows the results of GetTextMetrics after the font is selected in the device context. Below the dialog box, the program displays a string of characters using this font. Because the modeless dialog box is so big, you're best off running this program on a display size of 1024 by 768 or larger.

Cc750988.progwin1(en-us,TechNet.10).gif

Figure 17-2: A typical PICKFONT display (Unicode version under Windows NT).

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 mode; the Match Aspect option, which changes the way Windows matches a logical font to a real font; and "Adv Grfx Mode," which sets the advanced graphics mode in Windows NT. I'll discuss these in more detail shortly.

From the Device menu you can select the default printer rather than the video display. In this case, PICKFONT selects the logical font into the printer device context and displays the TEXTMETRIC structure from the printer. The program then selects the logical font into the window device context for displaying the sample string. Thus, the text displayed by the program might use a different font (a screen font) than the font described by the list of the TEXTMETRIC fields (which is a printer 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.

The Logical Font Structure

To create a logical font, you can call CreateFont, a function that has 14 arguments. Generally, it's easier to define a structure of type LOGFONT,

LOGFONT lf ;

and then define the fields of this structure. When finish, you call CreateFontIndirect with a pointer to the structure:

hFont = CreatFontIndirect (&lf) ;

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, all the fields will be initialized to 0. The 0 values are generally defaults. You can then 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 the Windows will attempt to match your requests with a real font.

As I discuss each field of the LOGFONT structure, you may want to test them out using the PICKFONT program. Be sure to press Enter or the OK button when you want the program to use any fields you've entered.

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

  • lfHeight This is the desired height of the characters (including internal leading but not external leading) in logical units. You can set lfHeight to 0 for a default size, or you can set it to a positive or negative value depending on what you want the field to represent. If you set lfHeightW to a positive value, you're implying that you want this value to be a height that includes internal leading. In effect, you're really requesting a font that is appropriate for a line spacing of lfHeight. If you set lfHeight to a negative value, Windows treats the absolute value of that number as a desired font height compatible with the point size. This is an important distinction: If you want a font of a particular point size, convert that point size to logical units and set the lfHeight field to the negative of that value. If lfHeight is positive, the tmHeight field of the resultant TEXTMETRIC structure will be roughly that value. (It's sometimes a little off, probably because of rounding.) If lfHeight is negative, it will roughly match the tmHeight field of the TEXTMETRIC structure less the tmInternalLeading field.

  • lfWidth This is the desired width of the characters in logical units. In most cases, you'll want to set this value to 0 and let Windows choose a font based solely on the height. Using a nonzero value does not work well with raster fonts, but with TrueType fonts you can easily use this to get a font that has wider or slimmer characters than normal. This field corresponds to the tmAveCharWidth field of the TEXTMETRIC structure. To use the lfWidth field intelligently, first set up the LOGFONT structure with a lfWidth field set to zero, create the logical font, select it into a device context, and then call GetTextMetrics. Get the tmAveCharWidth field, adjust it up or down, probably by a percentage, and then create a second font using that adjusted tmAveCharWidth value for lfWidth.

The next two fields specify the "escapement" and "orientation" of the text. In theory, lfEscapement allows character strings to be written at an angle (but with the baseline of each character still parallel to the horizontal axis) and lfOrientation allows individual characters to be tilted. These fields have never quite worked as advertised, and even today they don't work as they should except in one case: you're using a TrueType font, you're running Windows NT, and you call SetGraphicsMode with the CM_ADVANCED flag set. You can accomplish the final requirement in PICKFONT by checking the "Adv Grfx Mode" check box.

To experiment with these fields in PICKFONT, be aware that the units are in tenths of a degree and indicate a counterclockwise rotation. It's easy to enter values that cause the sample text string to disappear! For this reason, use values between 0 and -600 (or so) or values between 3000 and 3600.

  • lfEscapement This is an angle in tenths of a degree, measured from the horizontal in a counterclockwise direction. It specifies how the successive characters of a string are placed when you write text. 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

    In Windows 98, this value sets both the escapement and orientation of TrueType text. In Windows NT, this value also normally sets both the escapement and orientation of TrueType text, except when you call SetGraphicsMode with the GM_ADVANCED argument, in which case it works as documented.

  • lfOrientation This is an angle in tenths of a degree, measured from the horizontal in a counterclockwise direction. It affects the appearance of each individual character. Here are some examples:

    Value

    Character Appearance

    0

    Normal (default)

    900

    Tipped 90 degrees to the right

    1800

    Upside down

    2700

    Tipped 90 degrees to the left

    This field has no effect except with a TrueType font under Windows NT with the graphics mode set to GM_ADVANCED, in which case it works as documented.

    The remaining 10 fields follow:

  • lfWeight This field allows you to specify boldface. The WINGDI.H header files defines a bunch of values to use with this field:

    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

    In reality, this table is much more ambitious than anything that was ever implemented. You can use 0 or 400 for normal and 700 for bold.

  • lfItalic When nonzero, this specifies italics. Windows can synthesize italics on GDI raster fonts. That is, Windows simply shifts some rows of the character bitmap to mimic italic. With TrueType fonts, Windows uses the actual italic or oblique versions of the fonts.

  • lfUnderline When nonzero, this specifies underlining, which is always synthesized on GDI fonts. That is, the Windows GDI simply draws a line under each character, including spaces.

  • lfStrikeOut When nonzero, this specifies that the font should have a line drawn through the characters. This is also synthesized on GDI fonts.

  • lfCharSet This is a byte value that specifies the character set of the font. I'll have more to say about this field in the upcoming section, "Character Sets and Unicode",. In PICKFONT, you can press the button with the question mark to obtain a list of the character set codes you can use.

    Notice that the lfCharSet field is the only field where a zero does not indicate a default value. A zero value is equivalent to ANSI_CHARSET, the ANSI character set used in the United States and Western Europe. The DEFAULT_CHARSET code, which equals 1, indicates the default character set for the machine on which the program is running.

  • lfOutPrecision This specifies how Windows should attempt to match the desired font sizes and characteristics with actual fonts. It's a rather complex field that you probably won't use much. Check the documentation of the LOGFONT structure for more detail. Note that you can use the OUT_TT_ONLY_PRECIS flag to ensure that you always get a TrueType font.

  • lfClipPrecision This field specifies how characters are to be clipped when they lie partially outside the clipping region. This field is not used much and is not implemented in the PICKFONT program.

  • lfQuality This is an instruction to Windows regarding the matching of a desired font with a real font. It really has meaning with raster fonts only and should not affect TrueType fonts. The DRAFT_QUALITY flag indicates that you want GDI to scale raster fonts to achieve the size you want; the PROOF_QUALITY flag indicates no scaling should be done. The PROOF_QUALITY fonts are the most attractive, but they might be smaller than what you request. You'll probably use DEFAULT_QUALITY (or 0) in this field.

  • lfPitchAndFamily This byte is composed of two parts. You can use the C bitwise OR operator to combine two identifiers for this field. The lowest two bits specify whether the font has a fixed pitch (that is, all characters are the same width) or a variable pitch:

    Value

    Identifier

    0

    DEFAULT_PITCH

    1

    FIXED_PITCH

    2

    VARIABLE_PITCH

    The upper half of this byte specifies the font family:

    Value

    Identifier

    0x00

    FW_DONTCARE

    0x10

    FF_ROMAN (variable widths, serifs)

    0x20

    FF_SWISS (variable widths, no serifs)

    0x30

    FF_MODERN (fixed pitch)

    0x40

    FF_SCRIPT (mimics handwriting)

    0x50

    FF_DECORATIVE

  • lfFaceName This is the actual text name of a typeface (such as Courier, Arial, or Times New Roman). This field is a byte array that is LF_FACESIZE (or 32 characters) wide. If you want a TrueType italic or boldface font, you can get it in one of two ways. You can use the complete typeface name (such as Times New Roman Italic) in the lfFaceName field, or you can use the base name (Times New Roman) and set the lfItalic field.

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 call 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 some time experimenting with PICKFONT. Here are some general guidelines:

  • The lfCharSet (character set) field is very important. It used to be that if you specified OEM_CHARSET (255), you'd get either one of the stroke fonts or the Terminal font because these are the only fonts that used the OEM character sets. However, with the advent of TrueType "Big Fonts", a single TrueType font can be mapped to different character sets, including the OEM character set. You'll need to use SYMBOL_CHARSET (2) to get the Symbol font or the Wingdings 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-width 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 family value in the lfPitchAndFamily field to a value other than FF_DONTCARE, that field becomes important because you're being specific about the font family.

  • For raster fonts, 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. For stroke or TrueType fonts, Windows will simply scale the font to the desired height.

  • You can prevent Windows from scaling a raster 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 lfWeight values that are out of line for the particular aspect ratio of the display, Windows can map to a raster font that is designed for a display or other device of a different aspect ratio. This used to be a trick to get to get a thin or thick font. (This is not really necessary with TrueType, of course.) In general, you'll probably want to avoid matching with a font for another device, which you can do in PICKFONT by clicking the check box marked Match Aspect. If this box is checked, PICKFONT makes a call to SetMapperFlags with a TRUE argument.

Finding Out About the Font

At the right side of the modeless dialog box in PICKFONT is the information obtained from the GetTextMetrics function after the font has been selected in a device context. (Notice that you can use PICKFONT's device menu to indicate whether you want this device context to be the screen or the default printer. The results might be different because different fonts might be available on the printer.) At the bottom of the list in PICKFONT is the typeface name available from GetTextFace.

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 The height of the character in logical units. This is the value that should approximate the lfHeight field specified in the LOGFONT structure, if that value was positive, in which case it represents the line spacing of the font rather than the point size. If the lfHeight field of the LOGFONT structure was negative, the tmHeight field minus the tmInternalLeading field should approximate the absolute value of the lfHeight field.

  • tmAscent The vertical size of the character above the baseline in logical units.

  • tmDescent The vertical size of the character below the baseline in logical units.

  • tmInternalLeading A vertical size included in the tmHeight value that is usually occupied by diacritics on some capital letters. Once again, you can calculate the point size of the font by subtracting the tmInternalLeading value from the tmHeight value.

  • tmExternalLeading An additional amount of line spacing beyond tmHeight recommended by the designer of the font for spacing successive lines of text.

  • tmAveCharWidth The average width of lowercase letters in the font.

  • tmMaxCharWidth The width of the widest character in logical units. For a fixed-pitch font, this value is the same as tmAveCharWidth.

  • tmWeight The weight of the font ranging from 0 through 999. In reality, the field will be 400 for a normal font and 700 for a boldface font.

  • tmOverhang The amount of extra width (in logical units) that Windows adds to a raster font character when synthesizing italic or boldface. When a raster 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 boldface font, the tmAveCharWidth value less the tmOverhang value equals the tmAveCharWidth value for the same font without boldfacing.

  • tmDigitizedAspectX and tmDigitizedAspectY The aspect ratio for which the font is appropriate. These are equivalent to values obtained from GetDeviceCaps with the LOGPIXELSX and LOGPIXELSY identifiers.

  • tmFirstChar The character code of the first character in the font.

  • tmLastChar The character code of the last character in the font. If the TEXTMETRIC structure is obtained by a call to GetTextMetricsW (the wide character version of the function), then this value might be greater than 255.

  • tmDefaultChar The character code that Windows uses to display characters that are not in the font.

  • tmBreakChar The character that Windows, and your programs, should use to determine word breaks when justifying text. Unless you're using something bizarre (such as an EBCDIC font), this will be 32—the space character.

  • tmItalic Nonzero for an italic font.

  • tmUnderlined Nonzero for an underlined font.

  • tmStruckOut Nonzero for a strikethrough font.

  • tmPitchAndFamily The four low-order bits are flags that indicate some characteristics about the font, indicated by the following identifiers defined in WINGDI.H:

    Value

    Identifier

    0x01

    TMPF_FIXED_PITCH

    0x02

    TMPF_VECTOR

    0x04

    TMPF_TRUETYPE

    0x08

    TMPF_DEVICE

    Despite the name of the TMPF_FIXED_PITCH flag, the lowest bit is 1 if the font characters have a variable pitch. The second lowest bit (TMPF_VECTOR) will be 1 for TrueType fonts and fonts that use other scalable outline technologies, such as PostScript. The TMPF_DEVICE flag indicates a device font (that is, a font built into a printer) rather than a GDI-based font.

    The top four bits of this field indicates the font family, which are the same values used in the LOGFONT lfPitchAndFamily field.

  • tmCharSet The character set identifier.