CS-360 Fall, 2000 Class 11 R. Eckert PRINTING Under DOS, programs must send printer commands. So each DOS program must have its set of printer drivers, one for each type of printer. Under WINDOWS, the environment supports different types of printers using drivers stored in a system directory. -The user selects a printer from the Control Panel Application (once). -The driver for that printer is then used for printing from ALL applications that are running (and that do printing). -To print, an Application must create a printer DC that is compatible with the printing device, its driver, and that uses the correct output port. -Calls to standard GDI output functions initiate the printing. -The print driver translates these output functions into printer commands. In other words, the Windows Environment (not the Windows Application program) supports the printer. But there are differences between printers and screens-- -Is the printer on line? -Does the printer have paper? -Is there color support? -Is there graphics support? -Printers are slower than video displays. -Programs reuse the video display surface; a printer must eject completed pages and go on to others. -There should be the facility to quit in middle of printing (paper jam). -Lots of others. All of these issues should be addressed without having to use printer- specific commands. Selecting a printer with Control Panel-- When the user selects from the desktop task bar: "Start | Settings | Printers" or "Start | Settings | Control Panel | Printers", selects a printer, and, from the "File" menu chooses: "Set as Default", Windows updates the WIN.INI file in the system's \Windows directory. Windows reads this file whenever it needs information on the type of equipment installed and the drivers to be used. Specifically, with regard to printers, the [windows] section of the WIN.INI file is the relevant one. Printer information stored in the [windows] section of WIN.INI (example)-- [windows] ... device=Panasonic KX-P2130/2135,PAN24_15,LPT1: ... In this example, Windows is informed of the following: -a device named Panasonic KX-P2130/2135 is installed; -it uses the device driver file named PAN24_15.DRV; -output will go to port LPT1; PRINTER DEVICE CONTEXTS-- Just as with a display, to output to a printer, an application must obtain a printer device context and send GDI commands to it. As you will recall a handle to a display device context is obtained with GetDC() or BeginPaint(). A printer is different. A handle to a printer DC is "created" with CreateDC()-- hDC = CreateDC (drvName, devName, outPort, initData); Each parameter is a pointer to a character string-- drvName: printer driver file name (e.g., "PAN24_15") devName: device name (e.g., "KX-P2130/2135") outPort: output device port name (e.g., "LPT1") initData: initialization data, usually set to NULL This is exactly the information contained in "device=" line of the [windows] section of WIN.INI. We can use GetProfileString() to read a given entry in a given section of WIN.INI. n = GetProfileString(section, entry, default, cBuf, maxcnt); EXAMPLE: GetProfileString("windows", "device", "", lpszPrinter, 64); The first four parameters are long pointers to character strings: section: the section of WIN.INI containing the entry ("windows"). entry: the entry name ("device"). default: the default entry name, if not found (""). cBuf: a buffer to contain the string read from WIN.INI. Last parameter: the maximum number of characters to be read. The function returns the number of characters actually read into the buffer. After reading the file, we must extract the individual fields from the buffer using the C library function strtok(): char * strtok (char * s1, const char * s2); -Parses string s1 and breaks it into tokens separated by string s2. -The first call returns a pointer to the first character in the first token of s1, then inserts a NULL after the token. -Subsequent calls (first parameter = NULL) work through string s1, returning pointers to the first character of subsequent tokens. SAMPLE CODE TO CREATE A DC FOR THE DEFAULT PRINTER-- HDC hDC; char szPrt, *szDrv, *szDev, *szPort; GetProfileString ("windows", "device", "", szPrt, 64); szDev = strtok (szPrt, ","); /* parsing for commas */ szDrv = strtok (NULL, ","); szPort = strtok (NULL, ","); hDC = CreateDC (szDrv, szDev, szPort, NULL); Note that after this code executes, each pointer points to the correct section of szPrt. Now we can output to the printer using standard GDI output functions. When output is completed, we must get rid of the DC with: DeleteDC (hDC); But before that we need to do other things besides sending text/graphics to the printer. In other words, we need special printing functions. SPECIAL PRINTING FUNCTIONS (the most important ones)-- StartDoc (hDC, lpDocinfo); --Starts a print job. Returns a positive integer if successful. The second parameter is a pointer to a DOCINFO structure with members: size in bytes; name of document (lpsz); name of output file (lpsz)-- (It could be a file for redirected output) NULL==>use the printer specified in the DC. StartPage (hDC); --Prepares the printer driver to accept data. Returns a positive integer if successful. EndPage (hDC); --Signals the device that writing to the page is done. Directs the driver to advance to a new page. Returns a positive integer if successful. EndDoc (hDC); --Ends a print job. Returns a positive integer if successful. The PRINT1 Example Program-- This example demonstrates minimum support necessary to output a line of text and a rectangle to the default printer and illustrates the use of the most important special printing functions. In response to the user selecting the "Print" menu item (WM_COMMAND, ID=IDM_PRINT), the program uses GetProfileString() and strtok(), as described above, to extract the information necessary to create the printer device context, which is then obtained with a call to CreateDC(). Finally StartDoc() and StartPage() are invoked and a call is made to a helper function OutputStuff() that does the GDI output. (This same function is also called in response to a WM_PAINT message, so that the same output should appear on the screen.) After OutputStuff() does its job, the printer functions EndPage() and EndDoc() are called to terminate the printing, and the device context is deleted with DeleteDC(). OutputStuff() (defined at end of program)-- This helper function loads a string from the program resources, and outputs it to location (0,0) on the device context it is sent in the first parameter. It also draws a rectangle between (0,40) and (200,100) on the device context. But the printed output looks different from what is displayed on the screen, even though the same output functions are used for each. The reason is that the default mapping mode (MM_TEXT) was used. The size of the device unit for the printer (a dot) and that for the screen (a pixel) are not the same. Also, text is not affected by the mapping mode in place; its size is based on the font selected into the DC. Windows keeps the default system font about the same size on both the screen and the printer. Getting the Printer Output "Right"-- To do this, use a mapping mode with fixed sizing: MM_LOMETRIC, MM_LOENGLISH, MM_HIMETRIC, MM_HIENGLISH, or MM_TWIPS. Use of a fixed logical coordinate unit size (e.g., MM_LOMETRIC = .1 mm) means that output to the screen and to the printed page will be very similar. The PRINT2 Example Program-- The only change here is in the helper function OutputStuff(). The mapping mode is set to MM_LOMETRIC with SetMapMode() and the paper/screen client area is "moved up" 15 mm with SetWindowOrgEx(hDC,0,150,NULL). Then the text reference point for positioning the string is set to the lower lefthand corner of its embedding rectangle with a call to SetTextAlign(). Calls to MoveToEx() and LineTo() are used to make a cross indicating the position of the origin on the paper/screen. The text is obtained from the resources and output. Next a rectangle that surrounds the text is drawn. To do that, a call to GetTextExtentPoint() is made. The x,y extent of the text string in logical units is returned in the SIZE structure pointed to by the last parameter. A call Rectangle() that uses this information then draws the rectangle around the string. Finally a black brush is obtained and an ellipse is drawn. The paper output should be very similar to what appears on the screen. GETTING INFORMATION ABOUT A PHYSICAL DEVICE (e.g., PRINTER)-- One way of obtaining information about a device such as a printer is to query the device context of the device. After creating the DC for the device, we can read data associated with that device that was stored there by Windows for that device by calling GetDeviceCaps(). This accesses the specified DC and returns the requested capability value. GetDeviceCaps (hDC, capability_index); (See online help on GetDeviceCaps() for possible capabilities). The DEVCAP Example Program-- Displays several capabilities of the video display and default printer. Uses a string table to store the various capability entries. Two helper functions do the display-- OutStringTable(hDC, hInst, nStringID, nx, ny); which retrieves the string whose ID is nStringID from the resource string table and displays it at (nx,ny) on the device context whose handle is hDC. And: OutDevCapValue(hDCOut, hDCData, nCapIndex, nx, ny); which retrieves from the device context whose handle is hDCData the value (nValue) of the capability whose index is nCapIndex using: nValue = GetDeviceCaps (hDCdata, nIndex); and then formats and displays that value on the device context whose handle is hDCOut using: TextOut (hDCout, nX, nY, cBuf, wsprintf (cBuf, "%d", nValue)); Note that in this call to TextOut(), the last parameter is the value returned by wsprintf(), which is the length of the string cBuf. In that call to wsprintf(), cBuf will be given the string equivalent of the integer nValue (i.e., formatted). A more complete program would use GetTextMetrics() to determine the character font height/width and calculate the text locations accordingly--so that the output would be properly spaced in the window client area.