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[64], *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.