When you use a printer in Windows, you're initiating a complex interaction involving the GDI32 library module, the printer device driver library module (which has a .DRV extension), and the Windows print spooler, as well as some other modules that get into the act. Before we start programming for the printer, let's examine how this process works.
When an application program wants to begin using a printer, it first obtains a handle to the printer device context using CreateDC or PrintDlg. This causes the printer device driver library module to be loaded into memory (if it's not present already) and to initialize itself. The program then calls the StartDoc function, which signals the beginning of a new document. The StartDoc function is handled by the GDI module. The GDI module calls the Control function in the printer device driver, telling the device driver to prepare for printing.
The call to StartDoc begins the process of printing a document; the process ends when the program calls EndDoc. These two calls act as bookends for the normal GDI functions that display text or graphics to the document pages. Each page is itself delimited by a call to StartPage to begin a page and EndPage to end the page.
For example, if a program wants to draw an ellipse on the page, it first calls StartDoc to begin the print job, then StartPage to signal a new page. It then calls Ellipse, just as it does when drawing an ellipse on the screen. The GDI module generally stores any GDI call the program makes to the printer device context in a disk-based metafile, which has a filename that begins with the characters ~EMF ("enhanced metafile") and has a .TMP extension. However, as I'll discuss shortly, it's possible for the printer driver to skip this step.
When the application program is finished with the GDI calls that define the first page, the program calls EndPage. Now the real work begins. The printer driver must translate the various drawing commands stored in the metafile into output for the printer. The printer output required to define a page of graphics can be very large, particularly if the printer has no high-level page-composition language. For example, a 600-dots-per-inch laser printer using 8½-by-11-inch paper might require more than 4 megabytes of data to define just one page of graphics.
For this reason, printer drivers often implement a technique called "banding," which divides the page into rectangular bands. The GDI module obtains the dimensions of each band from the printer driver. It then sets a clipping region equal to this band and calls the printer device driver Output function for each of the drawing functions contained in the metafile. This process is called "playing the metafile into the device driver." The GDI module must play the entire metafile into the device driver for each band that the device driver defines on the page. After the process is completed, the metafile can be deleted.
For each band, the device driver translates these drawing functions into the output necessary to realize them on the printer. The format of this output will be specific to the printer. For dot-matrix printers, it will be a collection of control sequences, including graphics sequences. (For some assistance with constructing this output, the printer driver can call various "helper" routines also located in the GDI module.) For laser printers with a high-level page-composition language (such as PostScript), the printer output will be in that language.
The printer driver passes the printer output for each band to the GDI module, which then stores this printer output in another temporary file. This file begins with the characters ~SPL and has a .TMP extension. When the entire page is finished, the GDI module makes an interprocess call to the print spooler indicating that a new print job is ready. The application program then goes on to the next page. When the application is finished with all the pages it must print, it calls EndDoc to signal that the print job is completed. Figure 13-1 shows the interaction of the program, the GDI module, and the printer driver.
Figure 13-1. The interaction of the application program, the GDI module, the printer driver, and the spooler.
The Windows print spooler is actually a collection of several components:
Spooler Component | Description |
Print Request Spooler | Routes a data stream to the print provider |
Local Print Provider | Creates spool files destined for a local printer |
Network Print Provider | Creates spool files destined for a network printer |
Print Processor | Performs despooling, which is the conversion of spooled device-independent data into a form specific to the target printer |
Port Monitor | Controls the port to which the printer is connected |
Language Monitor | Controls printers capable of two-way communication to set device configuration and to monitor printer status |
The spooler relieves application programs of some of the work involved with printing. Windows loads the print spooler at startup, so it is already active when a program begins printing. When the program prints a document, the GDI module creates the files that contain printer output. The print spooler's job is to send these files to the printer. It is notified of a new print job by the GDI module. It then begins reading the file and transferring it directly to the printer. To transfer the files, the spooler uses various communications functions for the parallel or serial port to which the printer is connected. When the spooler is done sending a file to a printer, it deletes the temporary file holding the output. This process is shown in Figure 13-2.
Figure 13-2. The operation of the print spooler.
Most of this process is transparent to the application program. From the perspective of the application, "printing" occurs only during the time required for the GDI module to save all the printer output in disk files. After that—or even before, if printing is handled by a second thread—the program is freed up to do other things. The actual printing of the document becomes the print spooler's responsibility rather than the program's. The user is responsible for pausing print jobs, changing their priority, or canceling them if necessary. This arrangement allows programs to "print" faster than would be possible if they were printing in real time and had to wait for the printer to finish one page before proceeding to the next.
Although I've described how printing works in general, there are some variations on this theme. One variation is that the print spooler doesn't have to be present for Windows programs to use the printer. The user can usually turn off spooling for a printer from the printer's property sheet.
Why would a user want to bypass the Windows spooler? Well, perhaps the user has a hardware or software print spooler that works faster than the Windows spooler. Or perhaps the printer is on a network that has its own spooler. The general rule is that one spooler is faster than two. Removing the Windows spooler would speed up printing in these cases, because the printer output doesn't have to be stored on disk. It can go right out to the printer and be intercepted by the external hardware or software print spooler.
If the Windows spooler isn't active, the GDI module doesn't store the printer output from the device driver in a file. Instead, GDI itself sends the output directly to the parallel or serial printer port. Unlike the printing done by the spooler, the printing done by GDI has the potential of holding up the operation of application programs (particularly the program doing the printing) until the printing is completed.
Here's another variation: Normally, the GDI module stores all the functions necessary to define a page in a metafile and then plays this metafile into the printer driver once for each band defined by the driver. If the printer driver doesn't require banding, however, the metafile isn't created; GDI simply passes the drawing functions directly to the driver. In a further variation, it is also possible for an application to assume responsibility for dividing printer output into bands. This makes the printing code in the application program more complex, but it relieves the GDI module of creating the metafile. Once again, GDI simply passes the functions for each band to the printer driver.
Now perhaps you're starting to see how printing from a Windows program might involve a bit more overhead than that required for using the video display. Several problems can occur—particularly if the GDI module runs out of disk space while creating the metafile or the printer output files. Either you can get very involved in reporting these problems to the user and attempting to do something about them or you can remain relatively aloof.
For an application, the first step in printing is obtaining a printer device context.
Just as you must obtain a handle to a device context before you paint on the video display, you must obtain a printer device context handle before printing. Once you have this handle (and have called StartDoc to announce your intention of creating a new document and StartPage to begin a page), you can use this printer device context handle the same way you use the video display device context handle—as the first parameter to the various GDI drawing functions.
Many applications use a standard print dialog box invoked by calling the PrintDlg function. (I'll show how to use this function later in this chapter.) PrintDlg gives the user the opportunity to change printers or specify other job characteristics before printing. It then gives the application a printer device context handle. This function can save an application some work. However, some applications (such as Notepad) prefer instead to just obtain a printer device context without displaying a dialog box. This task requires a job to CreateDC.
In Chapter 5, we discovered that we can get a handle to a device context for the entire video display by calling
hdc = CreateDC (TEXT ("DISPLAY"), NULL, NULL, NULL) ;
You obtain a printer device context handle using this same function. However, for a printer device context, the general syntax of CreateDC is
hdc = CreateDC (NULL, szDeviceName, NULL, pInitializationData) ;
The pInitializationData argument is generally also set to NULL. The szDeviceName argument points to a character string that tells Windows the name of the printer device. Before you can set the device name, you must find out what printers are available.
A system can have more than one printer attached to it. It may even have other programs, such as fax software, masquerading as printers. Regardless of the number of attached printers, only one can be considered the "current" or "default" printer. This is the most recent printer that the user has chosen. Some small Windows programs use only this printer for printing.
Methods for obtaining the default printer device context have changed over the years. Currently, the standard method involves using the EnumPrinters function. This function fills an array of structures that contain information about each attached printer. You even have a choice of several structures to use with this function, depending on the level of detail you want. These structures have names of PRINTER_INFO_x, where x is a number.
Unfortunately, which structure you use also depends on whether your program is running under Windows 98 or Microsoft Windows NT. Figure 13-3 shows a GetPrinterDC function that will work under either operating system.
Figure 13-3. The GETPRNDC.C file.
GETPRNDC.C/*------------------------------------- GETPRNDC.C -- GetPrinterDC function -------------------------------------*/ #include <windows.h> HDC GetPrinterDC (void) { DWORD dwNeeded, dwReturned ; HDC hdc ; PRINTER_INFO_4 * pinfo4 ; PRINTER_INFO_5 * pinfo5 ; if (GetVersion () & 0x80000000) // Windows 98 { EnumPrinters (PRINTER_ENUM_DEFAULT, NULL, 5, NULL, 0, &dwNeeded, &dwReturned) ; pinfo5 = malloc (dwNeeded) ; EnumPrinters (PRINTER_ENUM_DEFAULT, NULL, 5, (PBYTE) pinfo5, dwNeeded, &dwNeeded, &dwReturned) ; hdc = CreateDC (NULL, pinfo5->pPrinterName, NULL, NULL) ; free (pinfo5) ; } else // Windows NT { EnumPrinters (PRINTER_ENUM_LOCAL, NULL, 4, NULL, 0, &dwNeeded, &dwReturned) ; pinfo4 = malloc (dwNeeded) ; EnumPrinters (PRINTER_ENUM_LOCAL, NULL, 4, (PBYTE) pinfo4, dwNeeded, &dwNeeded, &dwReturned) ; hdc = CreateDC (NULL, pinfo4->pPrinterName, NULL, NULL) ; free (pinfo4) ; } return hdc ; } |
This function uses the GetVersion function to determine whether the program is running under Windows 98 or Windows NT. Regardless of which is running, the function calls EnumPrinters twice—once to obtain the size of a structure it needs, and again to actually fill the structure. Under Windows 98, the function uses the PRINTER_INFO_5 structure; under Windows NT, it uses the PRINTER_INFO_4 structure. These structures are specifically indicated in the EnumPrinters documentation (/Platform SDK/Graphics and Multimedia Services/GDI/Printing and Print Spooler/Printing and Print Spooler Reference/Printing and Print Spooler Functions/EnumPrinters, right before the Examples section) as being "easy and extremely fast."
The original DEVCAPS1 program in Chapter 5 displayed basic information available from the GetDeviceCaps function for the video display. The new version, shown in Figure 13-4, shows more information for both the video display and all printers attached to the system.
Figure 13-4. The DEVCAPS2 program.
DEVCAPS2.C/*------------------------------------------------------------------ DEVCAPS2.C -- Displays Device Capability Information (Version 2) (c) Charles Petzold, 1998 ------------------------------------------------------------------*/ #include <windows.h> #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; void DoBasicInfo (HDC, HDC, int, int) ; void DoOtherInfo (HDC, HDC, int, int) ; void DoBitCodedCaps (HDC, HDC, int, int, int) ; typedef struct { int iMask ; TCHAR * szDesc ; } BITS ; #define IDM_DEVMODE 1000 int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("DevCaps2") ; 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, NULL, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static TCHAR szDevice[32], szWindowText[64] ; static int cxChar, cyChar, nCurrentDevice = IDM_SCREEN, nCurrentInfo = IDM_BASIC ; static DWORD dwNeeded, dwReturned ; static PRINTER_INFO_4 * pinfo4 ; static PRINTER_INFO_5 * pinfo5 ; DWORD i ; HDC hdc, hdcInfo ; HMENU hMenu ; HANDLE hPrint ; 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) ; // fall through case WM_SETTINGCHANGE: hMenu = GetSubMenu (GetMenu (hwnd), 0) ; while (GetMenuItemCount (hMenu) > 1) DeleteMenu (hMenu, 1, MF_BYPOSITION) ; // Get a list of all local and remote printers // // First, find out how large an array we need; this // call will fail, leaving the required size in dwNeeded // // Next, allocate space for the info array and fill it // // Put the printer names on the menu if (GetVersion () & 0x80000000) // Windows 98 { EnumPrinters (PRINTER_ENUM_LOCAL, NULL, 5, NULL, 0, &dwNeeded, &dwReturned) ; pinfo5 = malloc (dwNeeded) ; EnumPrinters (PRINTER_ENUM_LOCAL, NULL, 5, (PBYTE) pinfo5, dwNeeded, &dwNeeded, &dwReturned) ; for (i = 0 ; i < dwReturned ; i++) { AppendMenu (hMenu, (i+1) % 16 ? 0 : MF_MENUBARBREAK, i + 1, pinfo5[i].pPrinterName) ; } free (pinfo5) ; } else // Windows NT { EnumPrinters (PRINTER_ENUM_LOCAL, NULL, 4, NULL, 0, &dwNeeded, &dwReturned) ; pinfo4 = malloc (dwNeeded) ; EnumPrinters (PRINTER_ENUM_LOCAL, NULL, 4, (PBYTE) pinfo4, dwNeeded, &dwNeeded, &dwReturned) ; for (i = 0 ; i < dwReturned ; i++) { AppendMenu (hMenu, (i+1) % 16 ? 0 : MF_MENUBARBREAK, i + 1, pinfo4[i].pPrinterName) ; } free (pinfo4) ; } AppendMenu (hMenu, MF_SEPARATOR, 0, NULL) ; AppendMenu (hMenu, 0, IDM_DEVMODE, TEXT ("Properties")) ; wParam = IDM_SCREEN ; // fall through case WM_COMMAND : hMenu = GetMenu (hwnd) ; if (LOWORD (wParam) == IDM_SCREEN || // IDM_SCREEN & Printers LOWORD (wParam) < IDM_DEVMODE) { CheckMenuItem (hMenu, nCurrentDevice, MF_UNCHECKED) ; nCurrentDevice = LOWORD (wParam) ; CheckMenuItem (hMenu, nCurrentDevice, MF_CHECKED) ; } else if (LOWORD (wParam) == IDM_DEVMODE) // Properties selection { GetMenuString (hMenu, nCurrentDevice, szDevice, sizeof (szDevice) / sizeof (TCHAR), MF_BYCOMMAND); if (OpenPrinter (szDevice, &hPrint, NULL)) { PrinterProperties (hwnd, hPrint) ; ClosePrinter (hPrint) ; } } else // info menu items { CheckMenuItem (hMenu, nCurrentInfo, MF_UNCHECKED) ; nCurrentInfo = LOWORD (wParam) ; CheckMenuItem (hMenu, nCurrentInfo, MF_CHECKED) ; } InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; case WM_INITMENUPOPUP : if (lParam == 0) EnableMenuItem (GetMenu (hwnd), IDM_DEVMODE, nCurrentDevice == IDM_SCREEN ? MF_GRAYED : MF_ENABLED) ; return 0 ; case WM_PAINT : lstrcpy (szWindowText, TEXT ("Device Capabilities: ")) ; if (nCurrentDevice == IDM_SCREEN) { lstrcpy (szDevice, TEXT ("DISPLAY")) ; hdcInfo = CreateIC (szDevice, NULL, NULL, NULL) ; } else { hMenu = GetMenu (hwnd) ; GetMenuString (hMenu, nCurrentDevice, szDevice, sizeof (szDevice), MF_BYCOMMAND) ; hdcInfo = CreateIC (NULL, szDevice, NULL, NULL) ; } lstrcat (szWindowText, szDevice) ; SetWindowText (hwnd, szWindowText) ; hdc = BeginPaint (hwnd, &ps) ; SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ; 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) ; } void DoBasicInfo (HDC hdc, HDC hdcInfo, int cxChar, int cyChar) { static struct { int nIndex ; TCHAR * szDesc ; } info[] = { HORZSIZE, TEXT ("HORZSIZE Width in millimeters:"), VERTSIZE, TEXT ("VERTSIZE Height in millimeters:"), HORZRES, TEXT ("HORZRES Width in pixels:"), VERTRES, TEXT ("VERTRES Height in raster lines:"), BITSPIXEL, TEXT ("BITSPIXEL Color bits per pixel:"), PLANES, TEXT ("PLANES Number of color planes:"), NUMBRUSHES, TEXT ("NUMBRUSHES Number of device brushes:"), NUMPENS, TEXT ("NUMPENS Number of device pens:"), NUMMARKERS, TEXT ("NUMMARKERS Number of device markers:"), NUMFONTS, TEXT ("NUMFONTS Number of device fonts:"), NUMCOLORS, TEXT ("NUMCOLORS Number of device colors:"), PDEVICESIZE, TEXT ("PDEVICESIZE Size of device structure:"), ASPECTX, TEXT ("ASPECTX Relative width of pixel:"), ASPECTY, TEXT ("ASPECTY Relative height of pixel:"), ASPECTXY, TEXT ("ASPECTXY Relative diagonal of pixel:"), LOGPIXELSX, TEXT ("LOGPIXELSX Horizontal dots per inch:"), LOGPIXELSY, TEXT ("LOGPIXELSY Vertical dots per inch:"), SIZEPALETTE, TEXT ("SIZEPALETTE Number of palette entries:"), NUMRESERVED, TEXT ("NUMRESERVED Reserved palette entries:"), COLORRES, TEXT ("COLORRES Actual color resolution:"), PHYSICALWIDTH, TEXT ("PHYSICALWIDTH Printer page pixel width:"), PHYSICALHEIGHT, TEXT ("PHYSICALHEIGHT Printer page pixel height:"), PHYSICALOFFSETX, TEXT ("PHYSICALOFFSETX Printer page x offset:"), PHYSICALOFFSETY, TEXT ("PHYSICALOFFSETY Printer page y offset:") } ; int i ; TCHAR szBuffer[80] ; for (i = 0 ; i < sizeof (info) / sizeof (info[0]) ; i++) TextOut (hdc, cxChar, (i + 1) * cyChar, szBuffer, wsprintf (szBuffer, TEXT ("%-45s%8d"), info[i].szDesc, GetDeviceCaps (hdcInfo, info[i].nIndex))) ; } void DoOtherInfo (HDC hdc, HDC hdcInfo, int cxChar, int cyChar) { static BITS clip[] = { CP_RECTANGLE, TEXT ("CP_RECTANGLE Can Clip To Rectangle:") } ; static BITS raster[] = { RC_BITBLT, TEXT ("RC_BITBLT Capable of simple BitBlt:"), RC_BANDING, TEXT ("RC_BANDING Requires banding support:"), RC_SCALING, TEXT ("RC_SCALING Requires scaling support:"), RC_BITMAP64, TEXT ("RC_BITMAP64 Supports bitmaps >64K:"), RC_GDI20_OUTPUT, TEXT ("RC_GDI20_OUTPUT Has 2.0 output calls:"), RC_DI_BITMAP, TEXT ("RC_DI_BITMAP Supports DIB to memory:"), RC_PALETTE, TEXT ("RC_PALETTE Supports a palette:"), RC_DIBTODEV, TEXT ("RC_DIBTODEV Supports bitmap conversion:"), RC_BIGFONT, TEXT ("RC_BIGFONT Supports fonts >64K:"), RC_STRETCHBLT, TEXT ("RC_STRETCHBLT Supports StretchBlt:"), RC_FLOODFILL, TEXT ("RC_FLOODFILL Supports FloodFill:"), RC_STRETCHDIB, TEXT ("RC_STRETCHDIB Supports StretchDIBits:") } ; static TCHAR * szTech[] = { TEXT ("DT_PLOTTER (Vector plotter)"), TEXT ("DT_RASDISPLAY (Raster display)"), TEXT ("DT_RASPRINTER (Raster printer)"), TEXT ("DT_RASCAMERA (Raster camera)"), TEXT ("DT_CHARSTREAM (Character stream)"), TEXT ("DT_METAFILE (Metafile)"), TEXT ("DT_DISPFILE (Display file)") } ; int i ; TCHAR szBuffer[80] ; TextOut (hdc, cxChar, cyChar, szBuffer, wsprintf (szBuffer, TEXT ("%-24s%04XH"), TEXT ("DRIVERVERSION:"), GetDeviceCaps (hdcInfo, DRIVERVERSION))) ; TextOut (hdc, cxChar, 2 * cyChar, szBuffer, wsprintf (szBuffer, TEXT ("%-24s%-40s"), TEXT ("TECHNOLOGY:"), szTech[GetDeviceCaps (hdcInfo, TECHNOLOGY)])) ; TextOut (hdc, cxChar, 4 * cyChar, szBuffer, wsprintf (szBuffer, TEXT ("CLIPCAPS (Clipping capabilities)"))) ; for (i = 0 ; i < sizeof (clip) / sizeof (clip[0]) ; i++) TextOut (hdc, 9 * cxChar, (i + 6) * cyChar, szBuffer, wsprintf (szBuffer, TEXT ("%-45s %3s"), clip[i].szDesc, GetDeviceCaps (hdcInfo, CLIPCAPS) & clip[i].iMask ? TEXT ("Yes") : TEXT ("No"))) ; TextOut (hdc, cxChar, 8 * cyChar, szBuffer, wsprintf (szBuffer, TEXT ("RASTERCAPS (Raster capabilities)"))) ; for (i = 0 ; i < sizeof (raster) / sizeof (raster[0]) ; i++) TextOut (hdc, 9 * cxChar, (i + 10) * cyChar, szBuffer, wsprintf (szBuffer, TEXT ("%-45s %3s"), raster[i].szDesc, GetDeviceCaps (hdcInfo, RASTERCAPS) & raster[i].iMask ? TEXT ("Yes") : TEXT ("No"))) ; } void DoBitCodedCaps (HDC hdc, HDC hdcInfo, int cxChar, int cyChar, int iType) { static BITS curves[] = { CC_CIRCLES, TEXT ("CC_CIRCLES Can do circles:"), CC_PIE, TEXT ("CC_PIE Can do pie wedges:"), CC_CHORD, TEXT ("CC_CHORD Can do chord arcs:"), CC_ELLIPSES, TEXT ("CC_ELLIPSES Can do ellipses:"), CC_WIDE, TEXT ("CC_WIDE Can do wide borders:"), CC_STYLED, TEXT ("CC_STYLED Can do styled borders:"), CC_WIDESTYLED, TEXT ("CC_WIDESTYLED Can do wide and styled borders:"), CC_INTERIORS, TEXT ("CC_INTERIORS Can do interiors:") } ; static BITS lines[] = { LC_POLYLINE, TEXT ("LC_POLYLINE Can do polyline:"), LC_MARKER, TEXT ("LC_MARKER Can do markers:"), LC_POLYMARKER, TEXT ("LC_POLYMARKER Can do polymarkers"), LC_WIDE, TEXT ("LC_WIDE Can do wide lines:"), LC_STYLED, TEXT ("LC_STYLED Can do styled lines:"), LC_WIDESTYLED, TEXT ("LC_WIDESTYLED Can do wide and styled lines:"), LC_INTERIORS, TEXT ("LC_INTERIORS Can do interiors:") } ; static BITS poly[] = { PC_POLYGON, TEXT ("PC_POLYGON Can do alternate fill polygon:"), PC_RECTANGLE, TEXT ("PC_RECTANGLE Can do rectangle:"), PC_WINDPOLYGON, TEXT ("PC_WINDPOLYGON Can do winding number fill polygon:"), PC_SCANLINE, TEXT ("PC_SCANLINE Can do scanlines:"), PC_WIDE, TEXT ("PC_WIDE Can do wide borders:"), PC_STYLED, TEXT ("PC_STYLED Can do styled borders:"), PC_WIDESTYLED, TEXT ("PC_WIDESTYLED Can do wide and styled borders:"), PC_INTERIORS, TEXT ("PC_INTERIORS Can do interiors:") } ; static BITS text[] = { TC_OP_CHARACTER, TEXT ("TC_OP_CHARACTER Can do character output precision:"), TC_OP_STROKE, TEXT ("TC_OP_STROKE Can do stroke output precision:"), TC_CP_STROKE, TEXT ("TC_CP_STROKE Can do stroke clip precision:"), TC_CR_90, TEXT ("TC_CP_90 Can do 90 degree character rotation:"), TC_CR_ANY, TEXT ("TC_CR_ANY Can do any character rotation:"), TC_SF_X_YINDEP, TEXT ("TC_SF_X_YINDEP Can do scaling independent of X and Y:"), TC_SA_DOUBLE, TEXT ("TC_SA_DOUBLE Can do doubled character for scaling:"), TC_SA_INTEGER, TEXT ("TC_SA_INTEGER Can do integer multiples for scaling:"), TC_SA_CONTIN, TEXT ("TC_SA_CONTIN Can do any multiples for exact scaling:"), TC_EA_DOUBLE, TEXT ("TC_EA_DOUBLE Can do double weight characters:"), TC_IA_ABLE, TEXT ("TC_IA_ABLE Can do italicizing:"), TC_UA_ABLE, TEXT ("TC_UA_ABLE Can do underlining:"), TC_SO_ABLE, TEXT ("TC_SO_ABLE Can do strikeouts:"), TC_RA_ABLE, TEXT ("TC_RA_ABLE Can do raster fonts:"), TC_VA_ABLE, TEXT ("TC_VA_ABLE Can do vector fonts:") } ; static struct { int iIndex ; TCHAR * szTitle ; BITS (*pbits)[] ; int iSize ; } bitinfo[] = { CURVECAPS, TEXT ("CURVCAPS (Curve Capabilities)"), (BITS (*)[]) curves, sizeof (curves) / sizeof (curves[0]), LINECAPS, TEXT ("LINECAPS (Line Capabilities)"), (BITS (*)[]) lines, sizeof (lines) / sizeof (lines[0]), POLYGONALCAPS, TEXT ("POLYGONALCAPS (Polygonal Capabilities)"), (BITS (*)[]) poly, sizeof (poly) / sizeof (poly[0]), TEXTCAPS, TEXT ("TEXTCAPS (Text Capabilities)"), (BITS (*)[]) text, sizeof (text) / sizeof (text[0]) } ; static TCHAR szBuffer[80] ; BITS (*pbits)[] = bitinfo[iType].pbits ; int i, iDevCaps = GetDeviceCaps (hdcInfo, bitinfo[iType].iIndex) ; TextOut (hdc, cxChar, cyChar, bitinfo[iType].szTitle, lstrlen (bitinfo[iType].szTitle)) ; for (i = 0 ; i < bitinfo[iType].iSize ; i++) TextOut (hdc, cxChar, (i + 3) * cyChar, szBuffer, wsprintf (szBuffer, TEXT ("%-55s %3s"), (*pbits)[i].szDesc, iDevCaps & (*pbits)[i].iMask ? TEXT ("Yes") : TEXT ("No"))); } |
DEVCAPS2.RC (excerpts) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Menu DEVCAPS2 MENU DISCARDABLE BEGIN POPUP "&Device" BEGIN MENUITEM "&Screen", IDM_SCREEN, CHECKED END POPUP "&Capabilities" BEGIN MENUITEM "&Basic Information", IDM_BASIC 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 END END |
RESOURCE.H (excerpts)// Microsoft Developer Studio generated include file. // Used by DevCaps2.rc #define IDM_SCREEN 40001 #define IDM_BASIC 40002 #define IDM_OTHER 40003 #define IDM_CURVE 40004 #define IDM_LINE 40005 #define IDM_POLY 40006 #define IDM_TEXT 40007 |
Because DEVCAPS2 obtains only an information context for the printer, you can select printers from DEVCAPS2's menu, even though they may have an output port of "none." If you want to compare the capabilities of different printers, you can first use the Printers folder to add various printer drivers.
The Device menu of the DEVCAPS2 program includes an option called Properties. To use it, first select a printer from the Device menu and then select Properties. Up pops a dialog box. Where does the dialog box come from? It is invoked by the printer driver, and—at the very least—it requests that you make a choice of paper size. Most printer drivers also give you a choice of "portrait" or "landscape" mode. In portrait mode (often the default), the short side of the paper is the top; in landscape mode, the long side is the top. If you change this mode, the change is reflected in the information the DEVCAPS2 program obtains from the GetDeviceCaps function: the horizontal size and resolution are switched with the vertical size and resolution. Properties dialog boxes for color plotters can be quite extensive, requesting the colors of the pens installed in the plotter and the type of paper (or transparencies) being used.
All printer drivers contain an exported function called ExtDeviceMode that invokes this dialog box and saves the information that the user enters. Some printer drivers store this information in their own section of the Registry, and some don't. Those that store the information have access to it during the next Windows session.
Windows programs that allow the user a choice of printers generally just call PrintDlg, which I'll show you how to use later in this chapter. This useful function takes care of all the work of communicating with the user and handles any changes the user requests in preparation for printing. PrintDlg also invokes the property sheet dialog when the user clicks the Properties button.
A program can also display a printer's properties dialog by directly calling the printer driver's ExtDeviceMode or ExtDeveModePropSheet functions. However, I don't recommend this. It's far better to invoke the dialog indirectly by calling PrinterProperties, as DEVCAPS2 does.
PrinterProperties requires a handle to a printer object, which you get by calling the OpenPrinter function. When the user cancels a property sheet dialog, PrinterProperties returns. You can then close the printer handle by calling ClosePrinter. Here's how DEVCAPS2 does it:
The program first obtains the name of the printer currently selected in the Device menu and saves it in a character array named szDevice:
GetMenuString (hMenu, nCurrentDevice, szDevice, sizeof (szDevice) / sizeof (TCHAR), MF_BYCOMMAND) ;
Then it obtains the handle of this device by using OpenPrinter. If the call is successful, the program next calls PrinterProperties to invoke the dialog box and then ClosePrinter to delete the device handle:
if (OpenPrinter (szDevice, &hPrint, NULL)) { PrinterProperties (hwnd, hPrint) ; ClosePrinter (hPrint) ; }
You can use the GetDeviceCaps function to obtain the size and resolution of the printable area of the page. (In most cases, this area won't be the same as the full size of the paper.) You can also obtain the relative pixel width and height, if you want to do your own scaling.
Much of the information regarding various capabilities of the printer is for the purpose of GDI rather than applications. Often when a printer can't do something itself, GDI will simulate it. However, there is one capability that some applications should check.
This is the printer characteristic obtained from the RC_BITBLT bit of the value returned from GetDeviceCaps with a parameter of RASTERCAPS ("raster capabilities"). This bit indicates whether the device is capable of bit-block transfers. Most dot-matrix, laser, and ink-jet printers are capable of bit-block transfers, but plotters are not. Devices that can't handle bit-block transfers do not support the following GDI functions: CreateCompatibleDC, CreateCompatibleBitmap, PatBlt, BitBlt, StretchBlt, GrayString, DrawIcon, SetPixel, GetPixel, FloodFill, ExtFloodFill, FillRgn, FrameRgn, InvertRgn, PaintRgn, FillRect, FrameRect, and InvertRect. This is the single most important distinction between using GDI calls on a video display and using them on a printer.
We're now ready to print, and we're going to start as simply as possible. In fact, our first printing program does nothing but cause a printer form feed to eject the page. The FORMFEED program, shown in Figure 13-5, demonstrates the absolute minimum requirements for printing.
Figure 13-5. The FORMFEED program.
FORMFEED.C/*--------------------------------------------- FORMFEED.C -- Advances printer to next page (c) Charles Petzold, 1998 ---------------------------------------------*/ #include <windows.h> HDC GetPrinterDC (void) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int iCmdShow) { static DOCINFO di = { sizeof (DOCINFO), TEXT ("FormFeed") } ; HDC hdcPrint = GetPrinterDC () ; if (hdcPrint != NULL) { if (StartDoc (hdcPrint, &di) > 0) if (StartPage (hdcPrint) > 0 && EndPage (hdcPrint) > 0) EndDoc (hdcPrint) ; DeleteDC (hdcPrint) ; } return 0 ; } |
This program also requires the GETPRNDC.C file shown previously in Figure 13-3.
Other than obtaining the printer device context (and later deleting it), the program calls only the four print functions discussed earlier in this chapter. FORMFEED first calls StartDoc to start a new document. The program tests the return value from the function and proceeds only if the value is positive:
if (StartDoc (hdcPrint, &di) > 0)
The second argument to StartDoc is a pointer to a DOCINFO structure. This structure contains the size of the structure in the first field and the text string "FormFeed" in the second. As the document prints or while it is waiting to print, this string appears in the Document Name column of the printer's job queue. Generally the identification string includes the name of the application doing the printing and the file being printed.
If StartDoc is successful (indicated by a positive return value), FORMFEED calls StartPage, followed immediately by a call to EndPage. This sequence advances the printer to a new page. Once again, the return values are tested:
if (StartPage (hdcPrint) > 0 && EndPage (hdcPrint) > 0)
Finally, if everything has proceeded without error to this point, the document is ended:
EndDoc (hdcPrint) ;
Note that the EndDoc function is called only if no printing errors have been reported. If one of the other print functions returns an error code, GDI has already aborted the document. If the printer is not currently printing, such an error code often results in the printer being reset. Simply testing the return values from the print functions is the easiest way to check for errors. If you want to report a particular error to the user, you must call GetLastError to determine the error.
If you've ever written a simple form-feed program for MS-DOS, you know that ASCII code 12 (Ctrl-L) activates a form feed for most printers. Why not simply open the printer port using the C library function open and then output an ASCII code 12 using write? Well, nothing prevents you from doing this. You first have to determine the parallel port or the serial port the printer is attached to. You then have to determine whether another program (the print spooler, for instance) is currently using the printer. (You don't want the form feed to be output in the middle of some other program's document, do you?) Finally, you have to determine if ASCII code 12 is a form-feed character for the connected printer. It's not universal, you know. In fact, the form-feed command in PostScript isn't a 12; it's the word showpage.
In short, don't even think about going around Windows; stick with the Windows functions for printing.