PART IV  THE GRAPHICS DEVICE INTERFACE
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

Chapter 11  An Introduction to GDI
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ

We have been using Graphics Device Interface (GDI) functions since Chapter
1, when we first started writing to the client area of our windows. Now it's
time for a more formal coverage of the subject. This chapter discusses the
preliminaries of GDI but stops short of drawing graphics, which is the
subject of Chapter 12. Chapter 13 covers bitmaps and metafiles, which are
means of storing graphical information; Chapter 14 discusses text and fonts;
and Chapter 15 deals with printing.

THE GDI PHILOSOPHY

Graphics in Windows are handled primarily by functions exported from the
GDI.EXE module (although some drawing functions actually have entry points
in the USER.EXE file). The GDI.EXE module calls routines in the various
driver files--a .DRV for the video display screen and possibly one or more
other .DRV driver files that control printers or plotters. The video driver
accesses the hardware of the video display. Different video display adapters
and printers require different driver files.

The GDI system is constructed so that Windows can determine from the driver
what the driver can handle itself and what it needs assistance with. For
instance, if the video hardware includes a graphics coprocessor that can
draw ellipses, then GDI can take advantage of that; otherwise, the GDI
module must itself calculate the points of the ellipse and pass the points
to the driver.

Because a large number of different display devices can be attached to the
IBM PC and compatibles, one of the primary goals of GDI is to support
device-independent graphics on output devices such as video displays,
printers, and plotters. Windows programs should be able to run without
problems on any graphics output device that Windows supports. GDI
accomplishes this goal by providing facilities to insulate your programs
from the particular characteristics of different output devices. In this way
it is like other device-independent graphics programming languages. But
where Windows GDI is different is in its strong support of pixel-level
manipulation.

The world of graphics output devices is divided into two broad groups:
raster devices and vector devices. Most PC output devices are raster
devices, which means that they represent images as a pattern of dots. This
category includes video display adapters, dot-matrix printers, and laser
printers. Vector devices, which draw images using lines, are generally
limited to plotters.

Although most video display adapters and printers are raster devices, most
graphics interface languages are based solely on vectors. This means that a
program using one of these graphics languages is a level of abstraction away
from the hardware. The output device is using pixels for a graphics
representation, but the program is not talking to the interface in terms of
pixels. While you can certainly use Windows GDI as a high-level vector
drawing system, you can also use it for relatively low-level pixel
manipulation.

In this respect, Windows GDI is to other graphics interface languages what C
is to other programming languages. C is well known for its high degree of
portability among different operating systems and environments. Yet C is
also well known for allowing a programmer to perform low-level system
functions that are often impossible in other high-level languages. Just as C
is sometimes thought of as a "high-level assembly language," you can think
of GDI as a high-level interface to the hardware of the graphics device.

As you've seen, by default Windows uses a coordinate system based on pixels.
Most other graphics languages use a "virtual" coordinate system with
horizontal and vertical axes that range (for instance) from 0 to 32,767.
Although some graphics languages don't let you use pixel coordinates,
Windows GDI lets you use either system (as well as additional coordinate
systems based on physical measurements). You can use a virtual coordinate
system and keep your program distanced from the hardware, or you can use the
device coordinate system and snuggle right up to the hardware.

Some programmers think that after you start working in terms of pixels,
you've abandoned device independence. We've already seen that this is not
necessarily true. The trick  is to use the pixels in a device-independent
fashion. This requires that the graphics interface language provide
facilities for a program to determine the hardware characteristics of the
device and make appropriate adjustments. For instance, we've frequently used
the pixel size of a standard system font character to space text on the
screen. This approach allows our programs to adjust to different display
adapters with different resolutions, text sizes, and aspect ratios. You'll
see other methods in this chapter for determining display sizes.

Windows can run on either a monochrome display or a color display. If you
choose, you can write a program without worrying very much about color. If
you use color in your program and the program later runs on a monochrome
display adapter, Windows will use a shade of gray to represent the color.
However, you can also determine from your program how many colors are
available on the particular display device and take best advantage of the
hardware.

Of course, just as you can write C programs that have subtle portability
problems when they run on other computers, you can also inadvertently let
device dependencies creep into your Windows programs. That's part of the
price of not being fully insulated from the hardware. We'll examine many of
the device-dependent traps in the next few chapters.

You should also be aware of the limitations of Windows GDI. GDI is not (at
this time) capable of doing everything you may want a graphics interface to
do. Although you can move graphics objects around the display, GDI is
generally a static display system with no real animation support. GDI
provides no direct support for three-dimensional representations or for
rotations of objects. For instance, when you draw an ellipse, the ellipse
axes must be parallel to the horizontal and vertical coordinates. Although
some graphics languages use floating-point numbers for virtual coordinates,
Windows--for performance reasons--always uses 16-bit signed integers.


THE DEVICE CONTEXT

When you want to draw on a graphics output device (such as the screen or a
printer), you must first obtain a handle to a device context (or DC). In
giving your program this handle, Windows is giving you permission to use the
device. You then include the handle as a parameter in the GDI functions to
identify to Windows the device you want to draw on.

The device context contains many current "attributes" that determine how the
GDI functions work on the device. These attributes allow the parameters to
the GDI functions to include only starting coordinates or sizes and not
everything else that Windows needs to display the object on the device. For
example, when you call TextOut, you need specify in the function only the
device context handle, the starting coordinates, the text, and the length of
the text. You don't need to specify the font, the color of the text, the
color of the background behind the text, and the intercharacter spacing,
because these attributes are  part of the device context. When you want to
change one of these attributes, you call a function that changes the
attribute in the device context. Subsequent TextOut calls use the changed
attribute.

Getting the Handle to the Device Context

Windows provides several methods for obtaining a device context handle. If
you obtain a device context handle while processing a message, you should
release it (or delete it) before exiting the window function. After you
release the handle, it is no longer valid.

The most common method for obtaining and then releasing a device context
handle involves using the BeginPaint and EndPaint calls when processing the
WM_PAINT message:

hdc = BeginPaint (hwnd, &ps) ;
[other program lines]
EndPaint (hwnd, &ps) ;

The variable ps is a structure of type PAINTSTRUCT. The hdc field of this
structure is the handle to the device context that BeginPaint returns. The
PAINTSTRUCT structure also contains a RECT (rectangle) structure named
rcPaint that contains a clipping rectangle indicating the invalid region of
the window's client area. With the device context handle obtained from
BeginPaint, you can draw only within this rectangle. The EndPaint call
validates this region.

Windows programs can also obtain a handle to a device context during
processing of messages other than WM_PAINT:

hdc = GetDC (hwnd) ;
[other program lines]
ReleaseDC (hwnd, hdc) ;

This device context applies to the client area of the window whose handle is
hwnd. The primary difference between the use of these calls and of the
BeginPaint and EndPaint combination is that you can draw on your entire
client area with the handle returned from GetDC. However, ReleaseDC doesn't
validate any possibly invalid regions of the client area.

A Windows program can also obtain a handle to a device context that applies
to the entire window and not only to the window's client area:

hdc = GetWindowDC (hwnd) ;
[other program lines]
ReleaseDC (hwnd, hdc) ;

This device context includes the window caption bar, menu, scroll bars, and
frame in addition to the client area. The GetWindowDC function is rarely
used. If you want to experiment with it, you should trap WM_NCPAINT
("nonclient paint") messages, which prevent Windows from drawing on the
nonclient area of the window.

The BeginPaint, GetDC, and GetWindowDC calls obtain a device context
associated with a particular window. You can also obtain a device context
for the entire display by calling CreateDC:

hdc = CreateDC (lpszDriver, lpszDevice, lpszOutput, lpData) ;
[other program lines]
DeleteDC (hdc) ;

In the BLOWUP1 program in Chapter 4 we used this function to obtain a device
context handle that allowed us to write outside our window's client area:

hdc = CreateDC ("DISPLAY", NULL, NULL, NULL) ;

Writing outside your windows is generally impolite, but it's convenient for
some unusual applications. (Although this fact is undocumented, you can also
retrieve a device context for the entire screen by calling GetDC with a NULL
parameter.)

In Chapter 15 we'll use the CreateDC function to obtain a handle to a
printer device context:

hdcPrinter = CreateDC ("IBMGRX", "IBM Graphics", "LPT1:", NULL) ;

Of course, we won't include the names of specific printers in our programs.
Programs can instead obtain this information from WIN.INI.

Sometimes you need only to obtain some information about a device context
and not to do any drawing. In these cases, you can obtain a handle to an
"information context" using CreateIC. The parameters are the same as for the
CreateDC function:

hdcInfo = CreateIC (lpszDriver, lpszDevice, lpszOutput, lpData) ;
[other program lines]
DeleteDC (hdcInfo) ;

You can't write to the device using this information context handle. We'll
use this function in the DEVCAPS1 program shown later in this chapter to
obtain an information context for the display and the printer.

In the GRAFMENU program in Chapter 9, we obtained a memory device context to
manipulate some bitmaps. A memory device context is always created to be
compatible with an existing device context:

hdcMem = CreateCompatibleDC (hdc) ;
[other program lines]
DeleteDC (hdcMem) ;

When you first obtain a memory device context, the display surface that it
represents contains exactly 1 pixel. We'll work more with memory device
contexts in Chapter 13.

In Chapter 13 we'll also work with "metafiles." A metafile is a collection
of GDI calls encoded in binary form. You can create a metafile by obtaining
a metafile device context:

hdcMeta = CreateMetaFile (lpszFilename) ;
[other program lines]
hmf = CloseMetaFile (hdcMeta) ;

During the time that the metafile device context is valid, any GDI calls you
make using hdcMeta become part of the metafile. When you call CloseMetaFile,
the device context handle becomes invalid. The function returns a handle to
the metafile (hmf).


Getting Device Context Information

A device context usually refers to a physical display device such as a video
display or a printer. Often, you need to obtain information about this
device, including the size of the display (in terms of both pixels and
physical dimensions) and its color capabilities. You can get this
information by calling the GetDeviceCaps ("get device capabilities")
function:

nValue = GetDeviceCaps (hdc, nIndex) ;

The nIndex parameter is 1 of 28 identifiers defined in WINDOWS.H. For
instance, the nIndex HORZRES causes GetDeviceCaps to return the width of the
device in pixels; a VERTRES parameter returns the height of the device in
pixels. If hdc is a handle to a screen device context, that's the same
information you can get from GetSystemMetrics. If hdc is a handle to a
printer device context, then GetDeviceCaps returns the height and width of
the printer display area in pixels.

You can also use GetDeviceCaps to determine the device's capabilities of
processing various types of graphics. This is unimportant for the video
display, but it becomes very important when working with printers. For
instance, most plotters can't draw bitmapped images--and GetDeviceCaps can
tell you that.





The DEVCAPS1 Program

The DEVCAPS1 program, shown in Figure 11-1, displays all the information
available from the GetDeviceCaps function for either the video display or
the selected printer. (A second version of this program, called DEVCAPS2,
will be presented in Chapter 15.) If you change the current printer using
the Windows Control Panel program, DEVCAPS1 updates the printer information.

 DEVCAPS1.MAK

#------------------------
# DEVCAPS1.MAK make file
#------------------------

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

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

devcaps.obj : devcaps.c
     cl -c -Gsw -Ow -W2 -Zp devcaps.c

devcaps1.res : devcaps1.rc devcaps1.h
     rc -r devcaps1.rc

 DEVCAPS1.C

/*------------------------------------------------------
   DEVCAPS1.C -- Displays Device Capability Information
                 (c) Charles Petzold, 1990
  ------------------------------------------------------*/

#include 
#include 
#include "devcaps1.h"

void DoBasicInfo (HDC, HDC, short, short) ;       // in DEVCAPS.C
void DoOtherInfo (HDC, HDC, short, short) ;
void DoBitCodedCaps (HDC, HDC, short, short, short) ;

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

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



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

          RegisterClass (&wndclass) ;
          }

     hwnd = CreateWindow (szAppName, "Device Capabilities",
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

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

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

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

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

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

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

     return NULL ;
     }
long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     static short cxChar, cyChar, nCurrentDevice = IDM_SCREEN,
                                  nCurrentInfo   = IDM_BASIC ;
     HDC          hdc, hdcInfo ;
     HMENU        hMenu ;
     PAINTSTRUCT  ps ;
     TEXTMETRIC   tm ;

     switch (message)
          {
          case WM_CREATE :
               hdc = GetDC (hwnd) ;
               SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;

               GetTextMetrics (hdc, &tm) ;
               cxChar = tm.tmAveCharWidth ;
               cyChar = tm.tmHeight + tm.tmExternalLeading ;
               ReleaseDC (hwnd, hdc) ;
               return 0 ;

          case WM_COMMAND :
               hMenu = GetMenu (hwnd) ;

               switch (wParam)
                    {
                    case IDM_SCREEN :
                    case IDM_PRINTER :
                         CheckMenuItem (hMenu, nCurrentDevice, MF_UNCHECKED)
;
                         nCurrentDevice = wParam ;
                         CheckMenuItem (hMenu, nCurrentDevice, MF_CHECKED) ;
                         InvalidateRect (hwnd, NULL, TRUE) ;
                         return 0 ;

                    case IDM_BASIC :
                    case IDM_OTHER :
                    case IDM_CURVE :
                    case IDM_LINE :
                    case IDM_POLY :
                    case IDM_TEXT :
                         CheckMenuItem (hMenu, nCurrentInfo, MF_UNCHECKED) ;
                         nCurrentInfo = wParam ;
                         CheckMenuItem (hMenu, nCurrentInfo, MF_CHECKED) ;
                         InvalidateRect (hwnd, NULL, TRUE) ;
                         return 0 ;
                    }
               break ;
          case WM_DEVMODECHANGE :
               InvalidateRect (hwnd, NULL, TRUE) ;
               return 0 ;

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

               if (nCurrentDevice == IDM_SCREEN)
                    hdcInfo = CreateIC ("DISPLAY", NULL, NULL, NULL) ;
               else
                    hdcInfo = GetPrinterIC () ;

               if (hdcInfo)
                    {
                    switch (nCurrentInfo)
                         {
                         case IDM_BASIC :
                              DoBasicInfo (hdc, hdcInfo, cxChar, cyChar) ;
                              break ;

                         case IDM_OTHER :
                              DoOtherInfo (hdc, hdcInfo, cxChar, cyChar) ;
                              break ;

                         case IDM_CURVE :
                         case IDM_LINE :
                         case IDM_POLY :
                         case IDM_TEXT :
                              DoBitCodedCaps (hdc, hdcInfo, cxChar, cyChar,
                                              nCurrentInfo - IDM_CURVE) ;
                              break ;
                         }

                    DeleteDC (hdcInfo) ;
                    }

               EndPaint (hwnd, &ps) ;
               return 0 ;

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

 DEVCAPS.C

/*---------------------------------------------------------
   DEVCAPS.C -- Display routines for DEVCAPS1 and DEVCAPS2
                (c) Charles Petzold, 1990
  ---------------------------------------------------------*/

#include 
#include 
#include 

typedef struct
     {
     short nMask ;
     char  *szMask ;
     char  *szDesc ;
     }
     BITS ;

void DoBasicInfo (HDC hdc, HDC hdcInfo, short cxChar, short cyChar)
     {
     static struct
          {
          short nIndex ;
          char  *szDesc ;
          }
          info [] =
          {
          HORZSIZE,      "HORZSIZE     Width in millimeters:",
          VERTSIZE,      "VERTSIZE     Height in millimeters:",
          HORZRES,       "HORZRES      Width in pixels:",
          VERTRES,       "VERTRES      Height in raster lines:",
          BITSPIXEL,     "BITSPIXEL    Color bits per pixel:",
          PLANES,        "PLANES       Number of color planes:",
          NUMBRUSHES,    "NUMBRUSHES   Number of device brushes:",
          NUMPENS,       "NUMPENS      Number of device pens:",
          NUMMARKERS,    "NUMMARKERS   Number of device markers:",
          NUMFONTS,      "NUMFONTS     Number of device fonts:",
          NUMCOLORS,     "NUMCOLORS    Number of device colors:",
          PDEVICESIZE,   "PDEVICESIZE  Size of device structure:",
          ASPECTX,       "ASPECTX      Relative width of pixel:",
          ASPECTY,       "ASPECTY      Relative height of pixel:",
          ASPECTXY,      "ASPECTXY     Relative diagonal of pixel:",
          LOGPIXELSX,    "LOGPIXELSX   Horizontal dots per inch:",
          LOGPIXELSY,    "LOGPIXELSY   Vertical dots per inch:",
          SIZEPALETTE,   "SIZEPALETTE  Number of palette entries:",
          NUMRESERVED,   "NUMRESERVED  Reserved palette entries:",
          COLORRES,      "COLORRES     Actual color resolution:"
          } ;
     char   szBuffer [80] ;
     short  i, nLine ;

     for (i = 0 ; i < sizeof info / sizeof info [0] ; i++)
          TextOut (hdc, cxChar, (i + 1) * cyChar, szBuffer,
               sprintf (szBuffer, "%-40s%8d", info[i].szDesc,
                    GetDeviceCaps (hdcInfo, info[i].nIndex))) ;
     }

void DoOtherInfo (HDC hdc, HDC hdcInfo, short cxChar, short cyChar)
     {
     static BITS clip [] =
          {
          CP_RECTANGLE,  "CP_RECTANGLE",     "Can clip to rectangle:"
          } ;

     static BITS raster [] =
          {
          RC_BITBLT,       "RC_BITBLT",       "Capable of simple BitBlt:",
          RC_BANDING,      "RC_BANDING",      "Requires banding support:",
          RC_SCALING,      "RC_SCALING",      "Requires scaling support:",
          RC_BITMAP64,     "RC_BITMAP64",     "Supports bitmaps >64K:",
          RC_GDI20_OUTPUT, "RC_GDI20_OUTPUT", "Has 2.0 output calls:",
          RC_DI_BITMAP,    "RC_DI_BITMAP",    "Supports DIB to memory:",
          RC_PALETTE,      "RC_PALETTE",      "Supports a palette:",
          RC_DIBTODEV,     "RC_DIBTODEV",     "Supports bitmap conversion:",
          RC_BIGFONT,      "RC_BIGFONT",      "Supports fonts >64K:",
          RC_STRETCHBLT,   "RC_STRETCHBLT",   "Supports StretchBlt:",
          RC_FLOODFILL,    "RC_FLOODFILL",    "Supports FloodFill:"
          } ;

     static char *szTech [] = { "DT_PLOTTER (Vector plotter)",
                                "DT_RASDISPLAY (Raster display)",
                                "DT_RASPRINTER (Raster printer)",
                                "DT_RASCAMERA (Raster camera)",
                                "DT_CHARSTREAM (Character-stream, PLP)",
                                "DT_METAFILE (Metafile, VDM)",
                                "DT_DISPFILE (Display-file)" } ;
     char        szBuffer [80] ;
     short       i ;

     TextOut (hdc, cxChar, cyChar, szBuffer,
          sprintf (szBuffer, "%-24s%04XH",
               "DRIVERVERSION:", GetDeviceCaps (hdcInfo, DRIVERVERSION))) ;

     TextOut (hdc, cxChar, 2 * cyChar, szBuffer,
          sprintf (szBuffer, "%-24s%-40s",
               "TECHNOLOGY:", szTech [GetDeviceCaps (hdcInfo, TECHNOLOGY)]))
;
     TextOut (hdc, cxChar, 4 * cyChar, szBuffer,
          sprintf (szBuffer, "CLIPCAPS (Clipping capabilities)")) ;

     for (i = 0 ; i < sizeof clip / sizeof clip [0] ; i++)
          TextOut (hdc, 9 * cxChar, (i + 6) * cyChar, szBuffer,
               sprintf (szBuffer, "%-16s%-28s %3s",
                    clip[i].szMask, clip[i].szDesc,
                    GetDeviceCaps (hdcInfo, CLIPCAPS) & clip[i].nMask ?
                         "Yes" : "No")) ;

     TextOut (hdc, cxChar, 8 * cyChar, szBuffer,
          sprintf (szBuffer, "RASTERCAPS (Raster capabilities)")) ;

     for (i = 0 ; i < sizeof raster / sizeof raster [0] ; i++)
          TextOut (hdc, 9 * cxChar, (i + 10) * cyChar, szBuffer,
               sprintf (szBuffer, "%-16s%-28s %3s",
                    raster[i].szMask, raster[i].szDesc,
                    GetDeviceCaps (hdcInfo, RASTERCAPS) & raster[i].nMask ?
                         "Yes" : "No")) ;
     }

void DoBitCodedCaps (HDC hdc, HDC hdcInfo, short cxChar, short cyChar,
                     short nType)
     {
     static BITS curves [] =
          {
          CC_CIRCLES,    "CC_CIRCLES",    "circles:",
          CC_PIE,        "CC_PIE",        "pie wedges:",
          CC_CHORD,      "CC_CHORD",      "chord arcs:",
          CC_ELLIPSES,   "CC_ELLIPSES",   "ellipses:",
          CC_WIDE,       "CC_WIDE",       "wide borders:",
          CC_STYLED,     "CC_STYLED",     "styled borders:",
          CC_WIDESTYLED, "CC_WIDESTYLED", "wide and styled borders:",
          CC_INTERIORS,  "CC_INTERIORS",  "interiors:"
          } ;

     static BITS lines [] =
          {
          LC_POLYLINE,   "LC_POLYLINE",   "polylines:",
          LC_MARKER,     "LC_MARKER",     "markers:",
          LC_POLYMARKER, "LC_POLYMARKER", "polymarkers",
          LC_WIDE,       "LC_WIDE",       "wide lines:",
          LC_STYLED,     "LC_STYLED",     "styled lines:",
          LC_WIDESTYLED, "LC_WIDESTYLED", "wide and styled lines:",
          LC_INTERIORS,  "LC_INTERIORS",  "interiors:"
          } ;
     static BITS poly [] =
          {
          PC_POLYGON,    "PC_POLYGON",    "alternate fill polygon:",
          PC_RECTANGLE,  "PC_RECTANGLE",  "rectangle:",
          PC_TRAPEZOID,  "PC_TRAPEZOID",  "winding number fill polygon:",
          PC_SCANLINE,   "PC_SCANLINE",   "scanlines:",
          PC_WIDE,       "PC_WIDE",       "wide borders:",
          PC_STYLED,     "PC_STYLED",     "styled borders:",
          PC_WIDESTYLED, "PC_WIDESTYLED", "wide and styled borders:",
          PC_INTERIORS,  "PC_INTERIORS",  "interiors:"
          } ;

     static BITS text [] =
          {
          TC_OP_CHARACTER, "TC_OP_CHARACTER", "character output precision:",
          TC_OP_STROKE,    "TC_OP_STROKE",    "stroke output precision:",
          TC_CP_STROKE,    "TC_CP_STROKE",    "stroke clip precision:",
          TC_CR_90,        "TC_CP_90",        "90-degree character
rotation:",
          TC_CR_ANY,       "TC_CR_ANY",       "any character rotation:",
          TC_SF_X_YINDEP,  "TC_SF_X_YINDEP",  "scaling independent of x and
y:",
          TC_SA_DOUBLE,    "TC_SA_DOUBLE",    "doubled character for
scaling:",
          TC_SA_INTEGER,   "TC_SA_INTEGER",   "integer multiples for
scaling:",
          TC_SA_CONTIN,    "TC_SA_CONTIN",    "any multiples for exact
scaling:",
          TC_EA_DOUBLE,    "TC_EA_DOUBLE",    "double-weight characters:",
          TC_IA_ABLE,      "TC_IA_ABLE",      "italicizing:",
          TC_UA_ABLE,      "TC_UA_ABLE",      "underlining:",
          TC_SO_ABLE,      "TC_SO_ABLE",      "strikeouts:",
          TC_RA_ABLE,      "TC_RA_ABLE",      "raster fonts:",
          TC_VA_ABLE,      "TC_VA_ABLE",      "vector fonts:"
          } ;

     static struct
          {
          short nIndex ;
          char  *szTitle ;
          BITS  (*pbits) [] ;
          short nSize ;
          }
          bitinfo [] =
          {
          CURVECAPS,  "CURVCAPS (Curve capabilities)",
                      (BITS (*)[]) curves, sizeof curves / sizeof curves
[0],
          LINECAPS,   "LINECAPS (Line capabilities)",
                      (BITS (*)[]) lines, sizeof lines / sizeof lines [0],
          POLYGONALCAPS, "POLYGONALCAPS (Polygonal capabilities)",
                      (BITS (*)[]) poly, sizeof poly / sizeof poly [0],
TEXTCAPS,   "TEXTCAPS (Text capabilities)",
                      (BITS (*)[]) text, sizeof text / sizeof text [0]
          } ;

     static char szBuffer [80] ;
     BITS        (*pbits) [] = bitinfo [nType].pbits ;
     short       nDevCaps = GetDeviceCaps (hdcInfo, bitinfo [nType].nIndex)
;
     short       i ;

     TextOut (hdc, cxChar, cyChar, bitinfo [nType].szTitle,
                    strlen (bitinfo [nType].szTitle)) ;

     for (i = 0 ; i < bitinfo [nType].nSize ; i++)
          TextOut (hdc, cxChar, (i + 3) * cyChar, szBuffer,
               sprintf (szBuffer, "%-16s %s %-32s %3s",
                    (*pbits)[i].szMask, "Can do", (*pbits)[i].szDesc,
                    nDevCaps & (*pbits)[i].nMask ? "Yes" : "No")) ;
     }

 DEVCAPS1.RC

/*-----------------------------
   DEVCAPS1.RC resource script
  -----------------------------*/

#include "devcaps1.h"

DevCaps MENU
     {
     POPUP "&Device"
          {
          MENUITEM "&Screen",                IDM_SCREEN, CHECKED
          MENUITEM "&Printer",               IDM_PRINTER
          }
     POPUP "&Capabilities"
          {
          MENUITEM "&Basic Information",     IDM_BASIC, CHECKED
          MENUITEM "&Other Information",     IDM_OTHER
          MENUITEM "&Curve Capabilities",    IDM_CURVE
          MENUITEM "&Line Capabilities",     IDM_LINE
          MENUITEM "&Polygonal Capabilities",IDM_POLY
          MENUITEM "&Text Capabilities",     IDM_TEXT
          }
     }

 DEVCAPS1.H

/*------------------------
   DEVCAPS1.H header file
  ------------------------*/

#define IDM_SCREEN  1
#define IDM_PRINTER 2

#define IDM_BASIC   3
#define IDM_OTHER   4
#define IDM_CURVE   5
#define IDM_LINE    6
#define IDM_POLY    7
#define IDM_TEXT    8

 DEVCAPS1.DEF

;-------------------------------------
; DEVCAPS1.DEF module definition file
;-------------------------------------

NAME           DEVCAPS1

DESCRIPTION    'Displays Device Capability Info (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc

The DEVCAPS1 Device menu lets you select either the screen or the printer.
Because DEVCAPS1 needs only to obtain information about this device, it gets
a handle to an information context (using the CreateIC function) rather than
to a device context. Getting an information context handle for the video
device is easy:

HIC = CreateIC ("DISPLAY", NULL, NULL, NULL) ;

However, an information context handle for the printer requires more complex
code:

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

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

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

     return NULL ;
     }

The selected printer is listed in the [windows] section of the WIN.INI file
in the following format:

device=device name,driver filename,port

For an IBM Graphics printer connected to the LPT1 printer port, the WIN.INI
line is:

device=IBM Graphics,IBMGRX,LPT1:

IBM Graphics is the name of the printer, and IBMGRX.DRV is the name of the
driver file.

To get an information context (or device context) for the current printer,
you must first obtain the character string following device= in WIN.INI by
using the GetProfileString function. You must then parse this string into
the three components: the device name, the driver filename, and the port.
You can do this in various ways. I happened to use the C strtok function,
which is designed for parsing character strings separated by delimiters such
as commas and spaces. Note that the device name itself can have embedded
blanks.

If you'd like to use DEVCAPS1 to examine the device capabilities of other
printers, you can add printer driver files to your Windows subdirectory
using the Control Panel program and then select each of these printers, one
by one, as the current printer. Specify that the port the printer is
connected to is "NONE." An advantage of CreateIC over CreateDC is that
CreateDC returns a device context handle only if the printer is attached to
a port, whereas CreateIC doesn't care whether the printer is attached. To
get an idea of the range of devices you'll be dealing with, you might want
to obtain the GetDeviceCaps information for a few different types of
printers, such as a simple nongraphics printer (the "Generic/ Text Only"
printer), a sophisticated laser printer (the Apple LaserWriter Plus), and a
plotter (the Hewlett-Packard ColorPro). DEVCAPS1 intercepts the
WM_DEVMODECHANGE message that Control Panel sends to all applications to
signal that the current printer has been changed.

The Capabilities menu in DEVCAPS1 lets you display one of six screens that
show the GetDeviceCaps information. Much of the DEVCAPS1 code is dedicated
to formatting this information. When you choose the Basic Information option
from the Capabilities menu, the most important information is displayed,
including the size of the display, the number of pure colors it can display,
and the organization of display memory into color planes and color bits per
pixel. Figure 11-2 shows this basic information for a VGA; Figure 11-3 shows
the information for an Apple LaserWriter Plus.

When you choose the Other Information option from the Capabilities menu, the
program displays the type of device (usually "Raster device," "Raster
printer," or "Vector plotter") and gives some information that is crucial
for using printers, as you'll discover when you come to Chapter 15. Figure
11-4 shows this display for an Apple LaserWriter Plus. The RC_BITBLT
identifier indicates that this printer can accept bitmaps; text-only
printers and plotters cannot, however, which means that some GDI functions
won't work on them. The RC_BANDING identifier indicates that this IBM
printer, like many printers, requires "banding" support, which means that
the GDI module must print to the printer in segments, each occupying a small
section of the page. Again, we'll explore these issues further in Chapter
16.

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

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

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

The other menu options in DEVCAPS display curve, line, polygon, and text
capabilities. The information displayed indicates the type of graphics and
text manipulation that the device driver can handle. However, this
information is much more important to Windows itself than to application
programs--if the driver lacks one of these capabilities and a program
demands it, then the GDI module must assume the task. For instance, you
don't have to check to see if your printer is capable of drawing ellipses
before you draw an ellipse.

The information that you can obtain from GetDeviceCaps using the CURVECAPS,
LINECAPS, POLYGONALCAPS, and TEXTCAPS parameters is encoded in bits in the
return value. Although the Programmer's Reference doesn't indicate it,
WINDOWS.H includes identifiers beginning with the letters CC, LC, PC, and TC
to help you mask out the bits you want.


The Size of the Device

The most important information that your Windows program can obtain about
the video device from GetDeviceCaps is the size of the display (in both
millimeters and pixels) and the display's pixel aspect ratio. These
dimensions can help in scaling images to be displayed. To give you some idea
of what these numbers look like, the table below presents information from
GetDeviceCaps for four common IBM video adapters: the Color/Graphics Adapter
(CGA), Enhanced Graphics Adapter (EGA), the Video Graphics Array (VGA), and
the 8514/A:


GetDeviceCaps Index         CGA  EGA  VGA  8514/A
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
HORZSIZE (width in mm)      240  240  208  280
VERTSIZE (height in mm)     180  175  156  210
HORZRES (pixel width)       640  640  640  1024
VERTRES (pixel height)      200  350  480  760
ASPECTX (horizontal)        5    38   36   10
ASPECTY (vertical)          12   48   36   14
ASPECTXY (diagonal)         13   61   51   14
LOGPIXELSX (x pixels/inch)  96   96   96   120
LOGPIXELSY (y pixels/inch)  48   72   96   120


The HORZSIZE and VERTSIZE values are the width and height of the display
area in millimeters. Of course, the Windows driver doesn't really know the
size of the display you have attached to your video adapter. These
dimensions are based on standard display sizes for the adapters.

The HORZRES and VERTRES values are the width and height of the display area
in pixels. For a device context for a video display, these are the same
values as those returned from GetSystemMetrics.

The ASPECTX, ASPECTY, and ASPECTXY values are the relative width, height,
and diagonal size of each pixel. ASPECTXY equals the square root of the sum
of ASPECTX squared and ASPECTY squared.

The LOGPIXELSX and LOGPIXELSY values are the number of pixels per a
horizontal and a vertical "logical inch." A logical inch is not a real inch
(25.4 mm), as you can easily determine by performing a few calculations
using the HORZSIZE, VERTSIZE, HORZRES, and VERTRES values. These LOGPIXELSX
and LOGPIXELSY values require a little explanation. You may have noticed
that the WRITE program and some other Windows programs display a ruler that
isn't quite right: If you measure the ruler as displayed on an VGA, you'll
find that what it declares as 1 inch is really more like 1-1/2 inches. These
programs are using the LOGPIXELSX and LOGPIXELSY values for the ruler. If
WRITE used actual physical dimensions, normal 10-point or 12-point text
would be so small as to be nearly illegible. These logical dimensions in
effect blow up the display to allow an adequate size for displaying text.
When we start working with text and fonts in Chapter 14, we'll wrestle again
with this problem. It affects only video displays; for printers, all the
dimensions returned from GetDeviceCaps are consistent.


Finding Out About Color

During the discussion of bitmaps in Chapter 8, I noted the two ways in which
memory in a video display adapter can be organized for color. In some video
adapters, memory is organized into a number of color planes. Within a plane,
each bit corresponds to a single pixel and represents a particular primary
color (such as red, green, or blue). Other video adapters have a single
color plane, in which a number of adjacent bits represent the color of each
pixel.

GetDeviceCaps lets you determine the organization of memory in the video
adapter and the number of colors it can represent. This call returns the
number of color planes:

nPlanes = GetDeviceCaps (hdc, PLANES) ;

This call returns the number of color bits per pixel:

nBitsPixel = GetDeviceCaps (hdc, BITSPIXEL) ;

Most graphics display devices that are capable of color use either multiple
color planes or multiple color bits per pixel, but not both; in other words,
one of these calls will return a value of 1. The number of colors that can
be rendered on the video adapter can be calculated by the formula:

nColors = 1 << (nPlanes * nBitsPixel) ;

This value may or may not be the same as the number of colors obtainable
with the NUMCOLORS parameter:

nColors = GetDeviceCaps (hdc, NUMCOLORS) ;

These two numbers will be different for most plotters. For a plotter, both
the PLANES and BITSPIXEL values will equal 1, but the NUMCOLORS value will
reflect the number of colored pens that the plotter has. For monochrome
devices, GetDeviceCaps returns a 2 for the NUMCOLORS parameter.

The two values can also be different for video adapters that support
loadable color palettes under Windows 3 (such as the IBM 8514/A adapter).
The 8514/A has 1 plane and 8 bits per pixel, which means that 256 colors are
possible. GetDeviceCaps with the NUMCOLORS parameter returns the number of
colors reserved by Windows (20 in the case of the 8514/A). The remaining 236
colors can be set by a Windows program.

The number of colors returned from GetDeviceCaps is the number of pure
colors that the device can display. Windows can use dithering (which
involves a pixel pattern that combines pixels of different colors) to
represent colors in addition to the pure colors.

A color is usually represented by an unsigned long integer with 3 bytes, one
each for the intensity of red, green, and blue. (Chapters 5 and 6 discussed
this subject in greater detail.) You can determine the closest pure color of
a particular color value by calling GetNearestColor:

rgbPureColor = GetNearestColor (hdc, rgbColor) ;


The Device Context Attributes

As I noted above, Windows uses the device context to store "attributes" that
govern how the GDI functions operate on the display. For instance, when you
display some text using the TextOut function, you don't have to specify the
color of the text or the font. Windows uses the device context to obtain
this information.

When a program obtains a handle to a device context, Windows creates a
device context with default values for all the attributes. The device
context attributes are shown in the following table. A program can change or
obtain any of the attributes.


Device Context     Default             Function(s) to Change  Function(s) to Get
Attribute
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Mapping mode       MM_TEXT             SetMapMode             GetMapMode

Window origin      (0, 0)              SetWindowOrg           GetWindowOrg

OffsetWindowOrg

Viewport origin    (0, 0)              SetViewportOrg         GetViewportOrg

OffsetViewportOrg

Window extents     (1, 1)              SetWindowExt           GetWindowExt

SetMapMode

Viewport extents   (1, 1)              SetViewportExt         GetViewportExt

SetMapMode

Pen                BLACK_PE            SelectObject           SelectObject

Brush              WHITE_BRUSH         SelectObject           SelectObject

Font               SYSTEM_FONT         SelectObject           SelectObject

Bitmap             None                SelectObject           SelectObject

Current pen        (0, 0)              MoveTo
position

LineTo             GetCurrentPosition

Background mode    OPAQUE              SetBkMode              GetBkMode

Background color   White               SetBkColor             GetBkColor

Text color         Black               SetTextColor           GetTextColor

Drawing mode       R2_COPYPEN          SetROP2                GetROP2

Stretching mode    BLACKONWHITE        SetPolyFillMode        GetPolyFillMode

Polygon filling    ALTERNATE           SetPolyFillMode        GetPolyFillMode
mode

Intercharacter     0                   SetTextCharacterExtra  GetTextCharacterExtra
spacing

Brush origin       (0, 0) in screen    SetBrushOrg            GetBrushOrg
                   coordinates

Clipping region    None                SelectObject           SelectObject

                                       SelectClipRgn

IntersectClipRect

OffsetClipRgn

ExcludeClipRect    GetClipBox




Saving Device Contexts

Throughout Section IV, you'll encounter various functions to change the
device context attributes. Normally, Windows creates a new device context
with default values when you call GetDC or BeginPaint. All changes you make
to the attributes are lost when the device context is released with the
ReleaseDC or the EndPaint call. If your program needs to use nondefault
device context attributes, you'll have to initialize the device context
every time you obtain a device context handle:

WM_PAINT :
     hdc = BeginPaint (hwnd, &ps) ;
[initialize device context attributes]
[paint client area of window]
     EndPaint (hwnd, &ps) ;
     return 0 ;

Although this approach is generally satisfactory, you might prefer that
changes you make to the device context attributes be saved when you release
the device context, so they will be in effect the next time you call GetDC
or BeginPaint. You can accomplish this by including the CS_OWNDC flag as
part of the window class when you register the window class:

wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC ;

Now each window that you create based on this window class will have its own
private device context that exists until the window is destroyed. When you
use the CS_OWNDC style, you need to initialize the device context attributes
only once, perhaps during processing of the WM_CREATE message:

WM_CREATE :
     hdc = GetDC (hwnd) ;
[initialize device context attributes]
     ReleaseDC (hwnd, hdc) ;

The attributes continue to be valid until you change them.

The CS_OWNDC style affects only the device contexts retrieved from GetDC and
BeginPaint and not device contexts obtained from the other functions (such
as GetWindowDC). The CS_OWNDC style is not without its cost: Windows
requires about 800 bytes to store the device context for each window created
with this style. Even if you use CS_OWNDC, you must still release the device
context before exiting the window function.

You can also use the CS_CLASSDC style:

wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_CLASSDC ;

This causes each window class to have its own device context that is shared
by all windows created based on that class--even by windows created in other
instances of the same program. Now you can initialize the device context
attributes once in WinMain following creation of the first window based on
that window class:

if (!hPrevInstance)
     {
     hdc = GetDC (hwnd) ;
[initialize device context]
     ReleaseDC (hwnd, hdc) ;
     }

In general, the CS_CLASSDC type of device context is more difficult to use
than the CS_OWNDC type, because any changes you make to the device context
attributes affect all windows in all instances based on the same window
class. This could have some strange effects, particularly if you use a
customized mapping mode (a subject coming up shortly) based on the size of
the window.

In some cases you might want to change certain device context attributes, do
some painting using the changed attributes, and then revert to the original
device context. To simplify this process, you save the state of a device
context by calling:

nSavedID = SaveDC (hdc) ;

Now you change some attributes. When you want to return to the device
context as it existed before the SaveDC call, you use:

RestoreDC (hdc, nSavedID) ;

You can call SaveDC any number of times before calling RestoreDC. If you
want to revert to the device context as it existed before the last SaveDC
call, you call:

RestoreDC (hdc, -1) ;



THE MAPPING MODE

One device context attribute that affects virtually all the drawing you do
on the client area is the "mapping mode." Four other device context
attributes--the window origin, the viewport origin, the window extents, and
the viewport extents--are closely related to the mapping mode attribute.

Most of the GDI drawing functions require coordinate values or sizes. For
instance, this is the TextOut function:

TextOut (hdc, x, y, szBuffer, nLength) ;

The x and y parameters indicate the starting position of the text. The x
parameter is the position on the horizontal axis, and the y parameter is the
position on the vertical axis. Often the notation (x, y) is used to indicate
this point.

In TextOut, as in virtually all GDI functions, these coordinate values are
in terms of "logical units." Windows must translate the logical units into
"device units," or pixels. This translation is governed by the mapping mode,
the window and viewport origins, and the window and viewport extents. The
mapping mode also implies an origin and orientation of the x-axis and the
y-axis; that is, it determines whether values of x increase as you move
toward the left or right side of the display and whether values of y
increase as you move up or down the display.

Windows defines eight mapping modes. These are listed in the table on the
following page using the WINDOW.H identifiers.


                                    Increasing Values
Mapping Mode    Logical Unit        x-axis             y-axis
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
MM_TEXT         Pixel               Right              Down
MM_LOMETRIC     0.1 mm              Right              Up
MM_HIMETRIC     0.01 mm             Right              Up
MM_LOENGLISH    0.01 in.            Right              Up
MM_HIENGLISH    0.001 in.           Right              Up
MM_TWIPS*       1/1440 in.          Right              Up
MM_ISOTROPIC    Arbitrary (x = y)   Selectable         Selectable
MM_ANISOTROPIC  Arbitrary (x != y)  Selectable         Selectable


* Twip is a fabricated word meaning "twentieth of a point." A point, which
is a unit of measurement for type, is approximately 1/72 inch but often
assumed in graphics systems such as GDI to be exactly 1/72 inch. A twip is
1/20 point and hence 1/1440 inch.

You can set the mapping mode by:

SetMapMode (hdc, nMapMode) ;

where nMapMode is one of the eight mapping mode identifiers. You can obtain
the current mapping mode by calling:

nMapMode = GetMapMode (hdc) ;

The default mapping mode is MM_TEXT. In this mapping mode, logical units are
the same as physical units, which allows us (or, depending on your
perspective, forces us) to work directly in terms of pixels. In a TextOut
call that looks like this:

TextOut (hdc, 8, 16, szBuffer, nLength) ;

the text begins 8 pixels from the left of the client area and 16 pixels from
the top.

If the mapping mode is set to MM_LOENGLISH, then logical units are in terms
of hundredths of an inch:

SetMapMode (hdc, MM_LOENGLISH) ;

Now the TextOut function call might look like this:

TextOut (hdc, 50, -100, szBuffer, nLength) ;

The text begins 0.5 inch from the left and 1 inch from the top of the client
area. (The reason for the negative sign in front of the y-coordinate will
become clear later when I discuss the mapping modes in more detail.) Other
mapping modes allow programs to specify coordinates in terms of millimeters,
a printer's point size, or an arbitrarily scaled axis.

If you feel comfortable working in terms of pixels, you don't need to use
any mapping modes except the default MM_TEXT mode. If you need to display an
image in actual inch  or millimeter dimensions, you can obtain the
information you need from GetDeviceCaps and do your own scaling. The other
mapping modes are simply a convenient way to avoid doing your own scaling.

Regardless of the mapping mode, all coordinates you specify in Windows
functions must be signed short integers in the range 32,767 through -32,768.
Some Windows functions that use coordinates for the starting point and
ending point of a rectangle also require that the width and height of the
rectangle be 32,767 or less.

Device Coordinates and Logical Coordinates

You may ask: If I use the MM_LOENGLISH mapping mode, will I start getting
WM_SIZE messages in terms of hundredths of an inch? Absolutely not. Windows
continues to use device coordinates for all messages (such as WM_MOVE,
WM_SIZE, and WM_MOUSEMOVE), for all non-GDI functions, and even for some GDI
functions. Think of it this way: The mapping mode is an attribute of the
device context, so the only time the mapping mode comes into play is when
you use GDI functions that require a handle to the device context as one of
the parameters. GetSystemMetrics is not a GDI function, so it will continue
to return sizes in terms of device units, which are pixels. And although
GetDeviceCaps is a GDI function that requires a handle to a device context,
Windows continues to return device units for the HORZRES and VERTRES
indexes, because one of the purposes of this function is to provide a
program with the size of the device in pixels.

However, the values in the TEXTMETRIC structure that you obtain from the
GetTextMetrics call are in terms of logical units. If the mapping mode is
MM_LOENGLISH at the time the call is made, GetTextMetrics provides character
widths and heights in terms of hundredths of an inch. When you call
GetTextMetrics for information about the height and width of characters, the
mapping mode should be set to the same mapping mode that you'll be using
when you draw text based on these sizes. As I cover the various GDI
functions in this and subsequent chapters, I'll note whether they use device
coordinates or logical coordinates.


The Device Coordinate Systems

Windows maps logical coordinates specified in GDI functions to device
coordinates. Before we discuss the logical coordinate systems used with the
various mapping modes, let's examine the different device coordinate systems
that Windows defines for the video display area. Although we have been
working mostly within the client area of our window, Windows uses two other
device coordinate areas at various times. In all device coordinate systems,
units are in terms of pixels. Values on the horizontal, or x, axis increase
from left to right, and values on the vertical, or y, axis increase from top
to bottom.

When we use the entire screen, we are working in terms of "screen
coordinates." The upper left corner of the screen is the point (0, 0).
Screen coordinates are used in the  WM_MOVE message (for nonchild windows)
and in the following Windows functions: CreateWindow and MoveWindow (both
for nonchild windows), GetMessagePos, GetCursorPos, SetCursorPos,
GetWindowRect, WindowFromPoint, and SetBrushOrg. These are generally either
functions that don't have a window associated with them (such as the two
cursor functions) or functions that must move (or find) a window based on a
screen point. If you use CreateDC with a "DISPLAY" parameter to obtain a
device context for the entire screen, then logical coordinates specified in
GDI calls will be mapped to screen coordinates.

"Whole-window coordinates" refer to a program's entire window, including the
caption bar, menu, scroll bars, and window frame. For a normal window, the
point (0, 0) is the upper left corner of the sizing border. Whole-window
coordinates are rare in Windows, but if you obtain a device context from
GetWindowDC, logical coordinates in GDI functions will be mapped to
whole-window coordinates.

The third device coordinate system--the one we've been working with the
most_ uses "client-area coordinates." The point (0, 0) is the upper left
corner of the client area. When you obtain a device context using GetDC or
BeginPaint, logical coordinates in GDI functions are translated to
client-area coordinates.

You can convert client-area coordinates to screen coordinates and vice versa
using the functions ClientToScreen and ScreenToClient. You can also obtain
the position and size of the whole window in terms of screen coordinates
using the GetWindowRect function. These three functions provide enough
information to translate from any one device coordinate system to any other.


The Viewport and the Window

The mapping mode defines how Windows maps logical coordinates that are
specified in GDI functions to device coordinates, where the particular
device coordinate system depends on the function you use to obtain the
device context. To continue our discussion of the mapping mode, we need some
additional terminology: The mapping mode is said to define the mapping of
the "window" (logical coordinates) to the "viewport" (device coordinates).

The use of the words window and viewport is unfortunate. In other graphics
interface languages, viewport often implies a clipping region. We've been
using the word window to talk about the area that a program occupies on the
screen. We'll have to put aside our preconceptions about these words during
this discussion.

The "viewport" is in terms of device coordinates (pixels). Most often, the
viewport is the same as the client area, but it can also refer to
whole-window coordinates or screen coordinates if you've obtained a device
context from GetWindowDC or CreateDC. The point (0, 0) is the upper left
corner of the client area (or the whole window or the screen). Values of x
increase to the right, and values of y increase going down.

The "window" is in terms of logical coordinates, which may be pixels,
millimeters, inches, or any other unit you want. You specify logical window
coordinates in the GDI functions.

For all mapping modes, Windows translates window (logical) coordinates to
viewport (device) coordinates by the use of two formulas:

                                    xViewExt
  xViewport = (xWindow - xWinOrg) * ÄÄÄÄÄÄÄÄ + xViewOrg
                                    xWinExt

                                    yViewExt
  yViewport = (yWindow - yWinOrg) * ÄÄÄÄÄÄÄÄ + yViewOrg
                                    yWinExt

where (xWindow, yWindow) is a logical point to be translated, and
(xViewport, yViewport) is the translated point in device coordinates. If the
device coordinates are client-area coordinates or whole-window coordinates,
then Windows must also translate these device coordinates to screen
coordinates before drawing an object.

These formulas use two points that specify an "origin" of the window and the
viewport: (xWinOrg, yWinOrg) is the window origin in logical coordinates;
(xViewOrg, yViewOrg) is the viewport origin in device coordinates. In the
default device context, these two points are set to (0, 0), but they can be
changed. The formulas imply that the logical point (xWinOrg, yWinOrg) is
always mapped to the device point (xViewOrg, yViewOrg).

The formulas also use two points that specify "extents": (xWinExt, yWinExt)
is the window extent in logical coordinates; (xViewExt, yViewExt) is the
viewport extent in device coordinates. In most mapping modes, the extents
are implied by the mapping mode and cannot be changed. Each extent means
nothing by itself, but the ratio of the viewport extent to the window extent
is a scaling factor for converting logical units to device units. The
extents can be negative: This implies that values on the logical x-axis
don't necessarily have to increase to the right and that values on the
logical y-axis don't necessarily have to increase going down.

Windows can also translate from viewport (device) coordinates to window
(logical) coordinates:

                                     xWinExt
  xWindow = (xViewport - xViewOrg) * ÄÄÄÄÄÄÄ + xWinOrg
                                     xViewExt

                                     yWinExt
  yWindow = (yViewport - yViewOrg) * ÄÄÄÄÄÄÄ + yWinOrg
                                     yViewExt

Windows provides two functions that let you convert device points to logical
points and vice versa within a program. The following function converts
device points to logical points:

DPtoLP (hdc, lpPoints, nNumber) ;

The variable lpPoints is a long pointer to an array of POINT structures, and
nNumber is the number of points to be converted. You'll find this function
useful for converting the size of the client area obtained from
GetClientRect (which is always in terms of device units) to logical
coordinates:

GetClientRect (hwnd, &rect) ;
DPtoLP (hdc, (LPPOINT) &rect, 2) ;

This function converts logical points to device points:

LPtoDP (hdc, lpPoints, nNumber) ;


Working with MM_TEXT

For the MM_TEXT mapping mode, the default origins and extents are shown
below:

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Window origin:    (0, 0)  Can be changed
Viewport origin:  (0, 0)  Can be changed
Window extent:    (1, 1)  Cannot be changed
Viewport extent:  (1, 1)  Cannot be changed

The ratio of the viewport extent to the window extent is 1, so no scaling is
performed between logical coordinates and device coordinates. The formulas
shown on the preceding page reduce to these:


  xViewport = xWindow - xWinOrg + xViewOrg

                                          This mapping mode is called a
"text" mapping mode not because it's most suitable for text but because of
the orientation of the axes. We read text from left to right and top to
bottom, and MM_TEXT defines values on the axes to increase the same way:

Windows provides the functions SetViewportOrg and SetWindowOrg for changing
the viewport and window origins. These functions have the effect of shifting
the axis so that the logical point (0, 0) no longer refers to the upper left
corner. Generally, you'll use either SetViewportOrg or SetWindowOrg but not
both.

Here's how these functions work: If you change the viewport origin to
(xViewOrg, yViewOrg), then the logical point (0, 0) will be mapped to the
device point (xViewOrg, yViewOrg). If you change the window origin to
(xWinOrg, yWinOrg), then the logical point (xWinOrg, yWinOrg) will be mapped
to the device point (0, 0), which is the upper left corner. Regardless of
any changes you make to the window and viewport origins, the device point
(0, 0) is always the upper left corner of the client area.

For instance, suppose your client area is cxClient pixels wide and cyClient
pixels high. If you want to define the logical point (0, 0) to be the center
of the client area, you can do so by calling:

SetViewportOrg (hdc, cxClient / 2, cyClient / 2) ;

The arguments to SetViewportOrg are always in terms of device units. The
logical point (0, 0) will now be mapped to the device point (cxClient / 2,
cyClient / 2). Now you use your client area as if it had the following
coordinate system:

The logical x-axis ranges from -cxClient / 2 to +cxClient / 2, and the
logical y-axis ranges from -cyClient / 2 to +cyClient / 2. The lower right
corner of the client area is the logical point (cxClient / 2, cyClient / 2).
If you want to display text starting at the upper left corner of the client
area, which is the device point (0, 0), you need to use negative
coordinates:

TextOut (hdc, -cxClient / 2, -cyClient / 2, "Hello", 5) ;

You can achieve the same result with SetWindowOrg as you did with
SetViewportOrg:

SetWindowOrg (hdc, -cxClient / 2, -cyClient / 2) ;

The arguments to SetWindowOrg are always in terms of logical units. After
this call, the logical point (-cxClient / 2, -cyClient / 2) is mapped to the
device point (0, 0), the upper left corner of the client area.

What you probably don't want to do (unless you know what's going to happen)
is to use both functions together:

SetViewportOrg (hdc, cxClient / 2, cyClient / 2) ;
SetWindowOrg (hdc, -cxClient / 2, -cyClient / 2) ;

This means that the logical point (-cxClient / 2, -cyClient / 2) is mapped
to the device point (cxClient / 2, cyClient / 2), giving you a coordinate
system that looks like this:

You can obtain the current viewport and window origins from these functions:

dwViewOrigin = GetViewportOrg (hdc) ;

dwWindowOrigin = GetWindowOrg (hdc) ;

Both functions return DWORDs (unsigned longs). The x-origin is in the low
word and the y-origin is in the high word. You can use the LOWORD and HIWORD
macros to extract these two values from the DWORD. The values returned from
GetViewportOrg are in device coordinates; the values returned from
GetWindowOrg are in logical coordinates.

You might want to change the viewport or window origin to shift display
output within the client area of your window--for instance, in response to
scroll bar input from the user. Changing the viewport or window origin
doesn't shift the display output immediately, of course. You change the
origin and then repaint the display. For instance, in the SYSMETS2 program
in Chapter 2, we used the nVscrollPos value (the current position of the
vertical scroll bar) to adjust the y-coordinates of the display output:

case WM_PAINT:
     BeginPaint (hwnd, &ps) ;

     for (i = 0 ; i < NUMLINES ; i++)
          {
          y = cyChar * (1 - nVscrollPos + i) ;
[display text]
          }

     EndPaint (hwnd, &ps) ;
     return 0 ;

We can achieve the same result using SetWindowOrg:

case WM_PAINT:
     BeginPaint (hwnd, &ps) ;

     SetWindowOrg (ps.hdc, 0, cyChar * nVscrollPos) ;

     for (i = 0 ; i < NUMLINES ; i++)
          {
          y = cyChar * (1 + i) ;
[display text]
          }

     EndPaint (hwnd, &ps) ;
     return 0 ;

Now the calculation of the y-coordinate for the TextOut functions doesn't
require the nVscrollPos value. This means you can put the text output
functions in a subroutine and not have to pass the nVscrollPos value to the
subroutine, because we adjust the display of the text by changing the window
origin.

If you have some experience working with rectangular (or Cartesian)
coordinate systems, moving the logical point (0, 0) to the center of the
client area as we did earlier may have seemed a reasonable action. However,
there's a slight problem with the MM_TEXT mapping mode: A Cartesian
coordinate system defines values on the y-axis to increase as you move up
the axis, whereas MM_TEXT defines the values to increase as you move down.
In this sense, MM_TEXT is an oddity, and these next five mapping modes do it
correctly.


The "Metric" Mapping Modes

Windows includes five mapping modes that express logical coordinates in
physical measurements. Because logical coordinates on the x-axis and y-axis
are mapped to identical physical units, these mapping modes help you to draw
round circles and square squares.

The five "metric" mapping modes are arranged below in order of lowest
precision to highest precision. The two columns at the right show the size
of the logical units in terms of inches (in.) and millimeters (mm) for
comparison:

Mapping Mode  Logical Unit  Inch      Millimeter
ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
MM_LOENGLISH  0.01 in.      0.01      0.254
MM_LOMETRIC   0.1 mm        0.00394   0.1
MM_HIENGLISH  0.001 in.     0.001     0.0254
MM_TWIPS*     1/1440 in.    0.000694  0.0176
MM_HIMETRIC   0.01 mm       0.000394  0.01

< IF0>*A twip equals 1/20 of a point, which itself equals 1/72 inch.

To give you an idea of how the MM_TEXT mode fits in with these resolutions,
on a standard EGA display each pixel is 0.375 mm wide and 0.5 mm tall, so
EGA device coordinates are coarser than the logical coordinates for any of
the metric mapping modes.

On a 300-dots-per-inch laser printer, each pixel is 0.0033 inch--a higher
resolution than MM_LOENGLISH and MM_LOMETRIC but not as high as
MM_HIENGLISH,  MM_TWIPS, or MM_HIMETRIC.

The default origins and extents are shown below:

ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
Window origin:    (0, 0)  Can be changed
Viewport origin:  (0, 0)  Can be changed
Window extent:    (?, ?)  Cannot be changed
Viewport extent:  (?, ?)  Cannot be changed

The window and viewport extents depend on the mapping mode and the aspect
ratio of the device. As I mentioned earlier, the extents aren't important by
themselves but take on meaning only when expressed as ratios. Here are the
translation formulas again:

                                    xViewExt
  xViewport = (xWindow - xWinOrg) * ÄÄÄÄÄÄÄÄ  + xViewOrg
                                    xWinExt

                                    yViewExt
  yViewport = (yWindow - yWinOrg) * ÄÄÄÄÄÄÄÄ + yViewOrg
                                    yWinExt

For MM_LOENGLISH, for instance, Windows calculates the extents to be the
following:

  xViewExt
  ÄÄÄÄÄÄÄÄ = number of horizontal pixels in 0.01 in.
  xWinExt

    yViewExt
  -ÄÄÄÄÄÄÄÄ = number of vertical pixels in 0.01 in.
    yWinExt

For many display devices (such as the EGA), this ratio will be less than 1.
Because Windows works entirely with integers, the use of a ratio rather than
an absolute scaling factor is necessary to reduce loss of precision when
converting logical and device coordinates.

Notice the negative sign in front of the ratio of extents for the vertical
axis. This negative sign changes the orientation of the y-axis. For these
five mapping modes, y values increase as you move up the device. The default
window and viewport origins are (0, 0). This fact has an interesting
implication. When you first change to one of these five mapping modes, the
coordinate system looks like this:

The only way you can display anything in the client area is to use negative
values of y. For instance, this code:

SetMapMode (hdc, MM_LOENGLISH) ;
TextOut (hdc, 100, -100, "Hello", 5) ;

displays Hello 1 inch from the top and left edges of the client area.

To preserve your sanity, you'll probably want to avoid this. One solution is
to set the logical (0, 0) point to be the lower left corner of the client
area. Assuming that cyClient is the height of the client area in pixels, you
can do this by calling SetViewportOrg:

SetViewportOrg (hdc, 0, cyClient) ;

Now the coordinate system looks like this:

Alternatively, you can set the logical (0, 0) point to the center of the
client area:

SetViewportOrg (hdc, cxClient / 2, cyClient / 2) ;

The coordinate system looks like this:

Now we have a real four-quadrant Cartesian coordinate system with equal
logical units on the x-axis and y-axis in terms of inches, millimeters, or
twips.

You can also use the SetWindowOrg function to change the logical (0, 0)
point, but the task is a little more difficult because the parameters to
SetWindowOrg have to be in logical coordinates. You would first need to
convert cyClient to a logical coordinate using the DPtoLP function. Assuming
that the variable pt is a structure of type POINT, this code changes the
logical (0, 0) point to the center of the client area:

pt.x = cxClient ;
pt.y = cyClient ;
DPtoLP (hdc, &pt, 1) ;
SetWindowOrg (hdc, -pt.x, -pt.y) ;


The "Roll Your Own" Mapping Modes

The two remaining mapping modes are called MM_ISOTROPIC and MM_ANISOTROPIC.
These are the only two mapping modes for which Windows lets you change the
viewport and window extents, which means that you can change the scaling
factor that Windows uses to translate logical and device coordinates. The
word isotropic means "equal in all directions"; anisotropic is the
opposite--"not equal." Like the metric mapping modes shown earlier,
MM_ISOTROPIC uses equally scaled axes. Logical units on the x-axis have the
same physical dimensions as logical units on the y-axis. This helps when you
need to create images that retain the correct aspect ratio regardless of the
aspect ratio of the display device.

The difference between MM_ISOTROPIC and the metric mapping modes is that
with MM_ISOTROPIC you can control the physical size of the logical unit. If
you want, you can adjust the physical size of the logical unit based on the
size of the client area so that the images you draw are always contained
within the client area, shrinking and expanding appropriately. For instance,
the CLOCK and REVERSI programs included with Windows are examples of
isotropic images. The clock is always round, and the Reversi playing board
is  always square. As you size the window, the image is resized
appropriately. A Windows program can handle the resizing of an image
entirely through adjusting the window and viewport extents. The program can
then use the same logical units in the drawing functions regardless of the
size of the window.

Sometimes the MM_TEXT and the "metric" mapping modes are called "fully
constrained" mapping modes. This means that you cannot change the window and
viewport extents and the way that Windows scales logical coordinates to
device coordinates. MM_ISOTROPIC is a "partly constrained" mapping mode.
Windows allows you to change the window and viewport extents, but it adjusts
them so that x and y logical units represent the same physical dimensions.
The MM_ANISOTROPIC mapping mode is "unconstrained." You can change the
window and viewport extents, and Windows doesn't adjust the values.

The MM_ISOTROPIC mapping mode

The MM_ISOTROPIC mapping mode is ideal for using arbitrary axes while
preserving equal logical units on the two axes. Rectangles with equal
logical widths and heights are displayed as squares. Ellipses with equal
logical widths and heights are displayed as circles.

When you first set the mapping mode to MM_ISOTROPIC, Windows uses the same
window and viewport extents that it uses with MM_LOMETRIC. (Don't rely on
this fact, however.) The difference is that you can now change the extents
to suit your preferences by calling SetWindowExt and SetViewportExt. Windows
will then adjust the extents so that the logical units on both axes
represent equal physical distances.

Generally, you'll use parameters to SetWindowExt with the desired logical
size of the logical window, and parameters to SetViewportExt with the actual
height and width of the client area. When Windows adjusts these extents, it
has to fit the logical window within the physical viewport, which can result
in a section of the client area falling outside the logical window. You
should call SetWindowExt before you call SetViewportExt to make the most
efficient use of space in the client area.

For instance, suppose you want a "traditional" one-quadrant virtual
coordinate system where (0, 0) is at the lower left corner of the client
area and the width ranges from 0 to 32,767 and the height from 0 to 32,767.
You want the x and y units to have the same physical dimensions. Here's what
you need to do:

SetMapMode (hdc, MM_ISOTROPIC) ;
SetWindowExt (hdc, 32767, 32767) ;
SetViewportExt (hdc, cxClient, -cyClient) ;
SetViewportOrg (hdc, 0, cyClient) ;

If you then obtain the window and viewport extents using GetWindowExt and
GetViewportExt, you'll find that they are not the values you specified.
Windows adjusts the extents based on the aspect ratio of the display device
so that logical units on the two axes represent the same physical
dimensions.

If the client area is wider than it is high (in physical dimensions),
Windows adjusts the x extents so that the logical window is narrower than
the client-area viewport. The logical window will be positioned at the left
of the client area:

You can't display anything starting on the right side of the client area
beyond the range of the x-axis, because that requires a logical x-coordinate
greater than 32,767.

If the client area is higher than it is wide (in physical dimensions),
Windows adjusts the y extents. The logical window will be positioned at the
bottom of the client area:

Now you can't display anything at the top of the client area, because you
need a logical y-coordinate greater than 32,767.

If you prefer that the logical window always be positioned at the left and
top of the client area, you can change the code on the preceding page to the
following:

SetMapMode (hdc, MM_ISOTROPIC) ;
SetWindowExt (hdc, 32767, 32767) ;
SetViewportExt (hdc, cxClient, -cyClient) ;
SetWindowOrg (hdc, 0, 32767) ;

In the SetWindowOrg call we're saying that we want the logical point (0,
32,767) to be mapped to the device point (0, 0). Now if the client area is
higher than it is wide, the coordinates are arranged like this:

For a CLOCK-like image, you might want to use a four-quadrant Cartesian
coordinate system with arbitrarily scaled axes in four directions where the
logical point (0, 0) is in the center of the client area. If you want each
axis to range from 0 to 1000 (for instance), you use this code:

SetMapMode (hdc, MM_ISOTROPIC) ;
SetWindowExt (hdc, 1000, 1000) ;
SetViewportExt (hdc, cxClient / 2, -cyClient / 2) ;
SetViewportOrg (hdc, cxClient / 2, cyClient / 2) ;

The logical coordinates look like this if the client area is wider than it
is high:

The logical coordinates are also centered if the client area is higher than
it is wide:

Keep in mind that no clipping is implied in window or viewport extents. When
calling GDI functions, you are still free to use logical x and y values less
than -1000 and greater than +1000. Depending on the shape of the client
area, these points may or may not be visible.

With the MM_ISOTROPIC mapping mode, you can make logical units larger than
pixels. For instance, suppose you want a mapping mode with the point (0,0)
at the upper left corner of the display and values of y increasing as you
move down (like MM_TEXT) but with logical coordinates in sixteenths of an
inch. This mapping mode would let you draw a ruler starting at the top and
left side of the client area with divisions of sixteenths of an inch:

SetMapMode (hdc, MM_ISOTROPIC) ;

SetWindowExt (hdc,
     (short) (160L * GetDeviceCaps (hdc, HORZSIZE) / 254),
     (short) (160L * GetDeviceCaps (hdc, VERTSIZE) / 254)) ;

SetViewportExt (hdc, GetDeviceCaps (hdc, HORZRES),
                     GetDeviceCaps (hdc, VERTRES)) ;

In this code, the viewport extents are set to the pixel dimensions of the
entire screen. The window extents must be set to the dimensions of the
entire screen in units of sixteenths of an inch. The HORZSIZE and VERTSIZE
indexes to GetDeviceCaps return the dimensions of the device in millimeters.
If we were working with floating-point numbers, we would convert the
millimeters to inches by dividing by 25.4 and then convert inches to
sixteenths of an inch by multiplying by 16. However, because we're working
with integers, we must multiply by 160 and divide by 254. The calculation is
done in long integers to prevent overflow.

For most output devices, this code makes the logical unit much larger than
the physical unit. Everything you draw on the device will have coordinate
values that map to an increment of 1/16 inch. You cannot draw two horizontal
lines that are 1/32 inch apart, however, because that would require a
fractional logical coordinate.


MM_ANISOTROPIC: Stretching the image to fit

When you set the viewport and window extents in the MM_ISOTROPIC mapping
mode, Windows adjusts the values so that logical units on the two axes have
the same physical dimensions. In the MM_ANISOTROPIC mapping mode, Windows
makes no adjustments to the values you set. This means that MM_ANISOTROPIC
does not necessarily maintain the correct aspect ratio.

One way you can use MM_ANISOTROPIC is to have arbitrary coordinates for the
client area, as we did with MM_ISOTROPIC. This code sets the point (0, 0) at
the lower left corner of the client area with both the x- and y-axes ranging
from 0 to 32,767:

SetMapMode (hdc, MM_ANISOTROPIC) ;
SetWindowExt (hdc, 32767, 32767) ;
SetViewportExt (hdc, cxClient, -cyClient) ;
SetViewportOrg (hdc, 0, cyClient) ;

With MM_ISOTROPIC, similar code caused part of the client area to be beyond
the range of the axes. With MM_ANISOTROPIC, the upper right corner of the
client area is always the point (32767, 32767) regardless of its dimensions.
If the client area is not square, then logical x and y units will be
different physical dimensions.

In the previous section on the MM_ISOTROPIC mapping mode, I discussed
drawing a CLOCK-like image in the client area where both the x- and y-axes
ranged from -1000 to 1000. You can do something similar with MM_ANISOTROPIC:

SetMapMode (hdc, MM_ANISOTROPIC) ;
SetWindowExt (hdc, 1000, 1000) ;
SetViewportExt (hdc, cxClient / 2, -cyClient / 2) ;
SetViewportOrg (hdc, cxClient / 2, cyClient / 2) ;

The difference with MM_ANISOTROPIC is that in general the clock would be
drawn as an ellipse rather than a circle.

Another way to use MM_ANISOTROPIC is to set x and y units to fixed but
unequal values. For instance, if you have a program that displays only text,
you may want to set coarse coordinates based on the height and width of a
single character:

SetMapMode (hdc, MM_ANISOTROPIC) ;
SetWindowExt (hdc, 1, 1) ;
SetViewportExt (hdc, cxChar, cyChar) ;

(This assumes that cxChar and cyChar are the width and height of a character
in pixels, for a fixed-pitch font.) Now you can specify character row and
column coordinates in the TextOut call rather than pixel coordinates. For
instance, the following statement displays the text Hello three character
spaces from the left and two character rows from the top:

TextOut (hdc, 3, 2, "Hello", 5) ;

This is almost like working in text mode in the non-Windows MS-DOS
environment!

When you first set the MM_ANISOTROPIC mapping mode, it always inherits the
extents of the previously set mapping mode, which can be very convenient.
One way of thinking about MM_ANISOTROPIC is that it "unlocks" the extents;
that is, it allows you to change the extents of an otherwise fully
constrained mapping mode. For instance, suppose you want to use the
MM_LOENGLISH mapping mode because you want logical units to be 0.01 inch.
But you don't want the values along the y-axis to increase as you move up
the screen--you prefer the MM_TEXT orientation, where y values increase
moving down. Here's the code:

DWORD dwExtent ;
[other program lines]
SetMapMode (hdc, MM_LOENGLISH) ;
SetMapMode (hdc, MM_ANISOTROPIC) ;

dwExtent = GetViewportExt (hdc) ;

SetViewportExt (hdc, LOWORD (dwExtent), -HIWORD (dwExtent)) ;

We first set the mapping mode to MM_LOENGLISH. Then we liberate the extents
by setting the mapping mode to MM_ANISOTROPIC. The GetViewportExt obtains
the viewport extents encoded in a DWORD variable. Then we call
SetViewportExt with the extents extracted from the DWORD using the LOWORD
and HIWORD macros, except that we make the y extent negative.



The WHATSIZE Program

We'll use various mapping modes as we explore the GDI functions in the next
four chapters. Right now, let's simply look at the size of a client area in
terms of inches and millimeters. The WHATSIZE program, shown in Figure 11-5,
displays the size of the client area in terms of units associated with the
six fully constrained mapping modes: MM_TEXT, MM_LOMETRIC, MM_HIMETRIC,
MM_LOENGLISH, MM_HIENGLISH, and MM_TWIPS.

 WHATSIZE.MAK

#------------------------
# WHATSIZE.MAK make file
#------------------------

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

whatsize.obj : whatsize.c
     cl -c -Gsw -Ow -W2 -Zp whatsize.c

 WHATSIZE.C

/*-----------------------------------------
   WHATSIZE.C -- What Size Is the Window?
                 (c) Charles Petzold, 1990
  -----------------------------------------*/

#include 
#include 

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

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

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

          RegisterClass (&wndclass) ;
          }

     hwnd = CreateWindow (szAppName, "What Size Is the Window?",
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          NULL, NULL, hInstance, NULL) ;

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

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

void Show (HWND hwnd, HDC hdc, short xText, short yText, short nMapMode,
           char *szMapMode)
     {
     char szBuffer [60] ;
     RECT rect ;

     SaveDC (hdc) ;

     SetMapMode (hdc, nMapMode) ;
     GetClientRect (hwnd, &rect) ;
     DPtoLP (hdc, (LPPOINT) &rect, 2) ;

     RestoreDC (hdc, -1) ;

     TextOut (hdc, xText, yText, szBuffer,
               sprintf (szBuffer, "%-20s %7d %7d %7d %7d", szMapMode,
                    rect.left, rect.right, rect.top, rect.bottom)) ;
     }

long FAR PASCAL WndProc (HWND hwnd, WORD message, WORD wParam, LONG lParam)
     {
     static char  szHeading [] =
                    "Mapping Mode            Left   Right     Top  Bottom" ;
     static char  szUndLine [] =
                    "------------            ----   -----     ---  ------" ;
     static short cxChar, cyChar ;
     HDC          hdc ;     PAINTSTRUCT  ps ;
     TEXTMETRIC   tm ;

     switch (message)
          {
          case WM_CREATE :
               hdc = GetDC (hwnd) ;
               SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;

               GetTextMetrics (hdc, &tm) ;
               cxChar = tm.tmAveCharWidth ;
               cyChar = tm.tmHeight + tm.tmExternalLeading ;

               ReleaseDC (hwnd, hdc) ;
               return 0 ;

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

               SetMapMode (hdc, MM_ANISOTROPIC) ;
               SetWindowExt (hdc, 1, 1) ;
               SetViewportExt (hdc, cxChar, cyChar) ;

               TextOut (hdc, 1, 1, szHeading, sizeof szHeading - 1) ;
               TextOut (hdc, 1, 2, szUndLine, sizeof szUndLine - 1) ;

               Show (hwnd, hdc, 1, 3, MM_TEXT,      "TEXT (pixels)") ;
               Show (hwnd, hdc, 1, 4, MM_LOMETRIC,  "LOMETRIC (.1 mm)") ;
               Show (hwnd, hdc, 1, 5, MM_HIMETRIC,  "HIMETRIC (.01 mm)") ;
               Show (hwnd, hdc, 1, 6, MM_LOENGLISH, "LOENGLISH (.01 in)") ;
               Show (hwnd, hdc, 1, 7, MM_HIENGLISH, "HIENGLISH (.001 in)") ;
               Show (hwnd, hdc, 1, 8, MM_TWIPS,     "TWIPS (1/1440 in)") ;

               EndPaint (hwnd, &ps) ;
               return 0 ;

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

 WHATSIZE.DEF

;-------------------------------------
; WHATSIZE.DEF module definition file
;-------------------------------------

NAME           WHATSIZE

DESCRIPTION    'What Size Is the Window? (c) Charles Petzold, 1990'
EXETYPE        WINDOWS
STUB           'WINSTUB.EXE'
CODE           PRELOAD MOVEABLE DISCARDABLE
DATA           PRELOAD MOVEABLE MULTIPLE
HEAPSIZE       1024
STACKSIZE      8192
EXPORTS        WndProc

For ease in displaying the information using the TextOut function, WHATSIZE
uses the MM_ANISOTROPIC mapping mode with logical units set to character
dimensions:

SetMapMode (hdc, MM_ANISOTROPIC) ;
SetWindowExt (hdc, 1, 1) ;
SetViewportExt (hdc, cxChar, cyChar) ;

The program can then specify logical coordinates to TextOut in character row
and character column coordinates for a fixed-pitch font.

When WHATSIZE needs to obtain the size of the client area for one of the six
mapping modes, it saves the current device context, sets a new mapping mode,
obtains the client-area coordinates, converts them to logical coordinates,
and then restores the original mapping mode before displaying the
information. This code is in WHATSIZE's Show function:

SaveDC (hdc) ;

SetMapMode (hdc, nMapMode) ;
GetClientRect (hwnd, &rect) ;
DPtoLP (hdc, (LPPOINT) &rect, 2) ;

RestoreDC (hdc, -1) ;

Figure 11-6 shows a typical display from WHATSIZE.

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

Now that we have the preliminaries down, we're ready to start drawing.