Ron Gery
Microsoft Developer Network Technology Group
Created: March 20, 1992
Revised: June 2, 1992
ABSTRACT
This article discusses the basics of printing in the MicrosoftÒ WindowsÔ graphical environment. It does not cover setting up a printer and its device context (DC); it concentrates instead on operations needed to get output to the printer after it is set up. The article discusses the use of the main printing functions—StartDoc, EndDoc, StartPage, EndPage, AbortDoc, and SetAbortProc—and the principles of banding.
A printing operation in the MicrosoftÒ WindowsÔ graphical environment involves the following sequence of events:
1.Create the device context (DC) for the printer.
2.Set an AbortProc to handle possible abortive conditions (for example, out of disk space or user cancellation).
3.Start the document.
4.Output to the printer DC and advance through the pages as needed.
5.End the document.
6.Clean up (printer DC, AbortProc instance).
This article assumes that step 1 has already been accomplished. Using the PrintDlg common dialog greatly simplifies creating the DC.
Although printing methodology has not changed from Windows version 3.0 to Windows version 3.1, Windows version 3.1 provides new functions that simplify the application interface. In Windows version 3.0, the Escape function is the primary means of controlling the flow of a print job. Although this interface continues to function in Windows version 3.1, an added set of graphics device interface (GDI) functions separates the printing control operations from the device-specific escapes. The following table lists these functions, the escapes that they replace, and their functionality.
Windows version 3.1 function |
Corresponding escape |
Purpose |
StartDoc | STARTDOC | Start a document |
EndDoc | ENDDOC | End a document |
StartPage | Implicit part of NEWFRAME and STARTDOC | Start a new page |
EndPage | NEWFRAME | End current page |
SetAbortProc | SETABORTPROC | Set the AbortProc for print job |
AbortDoc | ABORTDOC or ABORTPIC | Abort a document |
The Windows version 3.1 functions and escapes map one-to-one with the exception of the NEWFRAME escape, which is replaced by two functions—StartPage and EndPage. The function of the NEWFRAME escape was to end the current page and start the next page; the STARTDOC escape indicated the start of the first page. The StartPage and EndPage functions define the interface better and are paired around every page being printed, including the first.
Although both approaches work in Windows version 3.1 for backward compatibility, developers who are writing applications for Windows version 3.1 and later should use the functions interface.
The following code demonstrates the bare essentials of printing using the Windows version 3.1 functions:
// For a print session this small, the AbortProc won't actually come
// into play, but this is an example of a nonaborting version.
NonAbort(HDC hPrintDC, short uCode)
{
return(1); // continue printing
}
// This routine prints a single 100 by 100 rectangle in the
// upper-left corner of the page.
BOOL PrintRect(HDC hPrintDC)
{
FARPROC lpAbortProc;
DOCINFO diInfo;
char DocName[5] = "hello";
lpAbortProc = MakeProcInstance((FARPROC)NonAbort, hInst);
SetAbortProc(hPrintDC, lpAbortProc);
diInfo.cbSize = sizeof(DOCINFO);
diInfo.lpszDocName = (LPSTR)DocName;
diInfo.lpszOutput = NULL;
StartDoc(hPrintDC, (LPDOCINFO)&diInfo);
StartPage(hPrintDC);
Rectangle(hPrintDC, 0, 0, 100, 100); // actual output to
// printer
EndPage(hPrintDC);
EndDoc(hPrintDC);
// Clean up AbortProc instance.
FreeProcInstance(lpAbortProc);
}
This example performs the bare minimum for a print job. The abort procedure is essentially empty and does not allow for user input, no explicit banding is supported, and only a single page is printed.
An application starts a print job using the StartDoc function (or the STARTDOC escape). The function accepts two parameters, the printer DC and a DOCINFO structure that identifies the document. The lpszDocName field specifies the name of the document, which is displayed in the Print Manager window. An application uses the lpszOutput field to specify an output file for the printer that could be different from the one in the Control Panel. For example, if a printer is set up to output to FILE:, an application can query the user for the output file name and use the actual name to start the print job. This avoids invoking the GDI dialog box to request the name of the output file. The state of the DC is saved at CreateDC time, and GDI restores it at the start of every page and band.
With the escape-only method, STARTDOC also defines the start of the document’s first page.
The EndDoc function (or the ENDDOC escape) marks the end of a print job. All necessary cleanup is done at this point.
The StartPage function marks the start of a new page of output. GDI does some bookkeeping to prepare the DC for the page, but nothing really exciting takes place. The Windows version 3.0 interface did not have the pure concept of a page start; it was implicitly defined by an output operation following a STARTDOC escape for the first page, a NEWFRAME escape for subsequent pages, or a NEXTBAND request after the previous page completed banding.
Predictably, the EndPage function denotes the end of a page. This functionality is the same as that of the NEWFRAME escape or of the last NEXTBAND escape on a page. At the printer level, this function causes a page eject; at the GDI level a virtual page is ejected by restoring the DC to its state at the time of the CreateDC function call. If GDI is handling banding for the application (more on that below), the page ends when the actual banding work is completed. The DC is now ready for another page of output.
Banding is the process in which a single page of output is generated using one or more separate rectangles, or bands. When all bands are placed on the page, a complete image results. This approach is often used by raster printers that do not have sufficient memory or ability to image a full page at one time. Banding devices include most dot matrix printers as well as some laser printers.
An application determines whether a printer is a banding printer by calling the GetDeviceCaps function with the RASTERCAPS index and then checking the RC_BANDING bit. When this bit is set, the device is a banding device. If the bit is not set, the driver outputs a full page at a time. Because GDI can transparently handle any necessary banding for banding printers, applications are not required to explicitly support banding regardless of how the RC_BANDING bit is set.
The advantages of using banding over allowing the GDI simulations are improved printing speed and possible reduction of disk space required for printing. Speed can be increased by selectively performing output based on the band rectangles, thereby eliminating unneeded calls. Disk space is saved by avoiding GDI’s banding support, which uses a disk metafile to represent the page image.
An application indicates that it intends to handle banding explicitly by calling Escape with NEXTBAND as its operation. This tells GDI that the application is doing its own banding as well as requesting the next band from the printer. In response to the NEXTBAND escape, the printer returns the bounding rectangle for the current band; all subsequent output to this band is automatically clipped to this rectangle. The dimensions of the bands depend on the driver and sometimes on the amount of memory available in the system. When there are no more bands to process on a page, the printer returns an empty rectangle for the band. Notice that the rectangles do not necessarily band in the y direction; because banding is usually based on the paper as it is situated in portrait mode, documents using landscape orientation may band in the x direction.
To explicitly use the device’s banding, an application loops through the bands on a page and outputs to each band as needed. Because output is clipped to the current band, the application need not limit its output to the band’s rectangle, but preclipping often speeds up printing. Once the application encounters the empty band, it is ready to move to the next page.
A nonbanding driver supports the NEXTBAND escape by defining a single band that encompasses the entire page. On these printers, an application gets two bands per page, the first being the full page band and the second being the empty band that marks the end of the page.
When the printer DC is created, GDI calls SaveDC to save the state of the printer DC. For every NEXTBAND escape, GDI calls RestoreDC so that any attributes changed during the band are undone, and the DC is restored to its state at the time the printer DC was created.
The following code uses the Windows version 3.1 printing functions in conjunction with the NEXTBAND escape to print multiple pages of a document while banding each page:
ret = 1; // assume nothing has gone wrong
for (i = 1; (ret >= 0) && (i < NumPages + 1); i++)
{
StartPage(hDC);
// For real bands, output the document.
// Once the empty band is encountered, end the page.
while ((ret = Escape(hDC, NEXTBAND, NULL, NULL, lpRect)) >= 0 &&
!IsRectEmpty(lpRect))
{
PageOutput(i, lpRect); // output to lpRect on page i
}
EndPage(hDC);
}
The escape-based printing model has a restriction that does not apply when the printing-specific functions are used. Basically, an application is not allowed to use both the NEXTBAND and the NEWFRAME escapes. An application that does its own banding cannot use the NEWFRAME escape; to proceed to the next page, the application calls the NEXTBAND escape again after the last band (the one with the empty rectangle) of the previous page. Continuous banding causes page breaks when appropriate. Similarly, an application using the NEWFRAME escape is assumed to be working in full-page mode and should not use the NEXTBAND escape. An application for Windows version 3.1 should call StartPage and EndPage for all pages, regardless of whether it is banding.
An application need not be aware of banding. By using only the StartPage/EndPage (or NEWFRAME escape) interface with no NEXTBAND escapes, an application indicates that GDI should handle all banding procedures. To accomplish this, GDI records all output calls in a metafile that represents that page. When the page is finished (when the application calls EndPage), GDI steps through the bands on the page (using the NEXTBAND escape) and plays the metafile into every band. When the page’s output is completed, the metafile is deleted, and a new one is created for the following page. All metafile processes are completely transparent to the application.
Using a metafile to simulate a full-page image may suggest that the limits of metafiles restrict output capabilities, but this is not the case. Because the metafile is associated with a real device, any operation that can be performed to a regular output DC can be performed to this metafiling DC. Query functions return values based on the printer, and functions such as DrawText can be used because they are simulated using queried values. Also, the scaling limitations of metafiles do not apply because the metafile is never scaled; it is played at its defined size.
GDI banding support does have one subtle limitation. If an application blts a color bitmap directly to a color printer, the metafile simulation does not produce the same results that manual banding produces. The metafile is built with a record containing a device-independent bitmap (DIB) representation of the bitmap; if the printer driver does not support the GetDIBits functionality, GDI simulates by building a monochrome DIB, and the color information is lost. An application that finds itself in need of blting a color bitmap to a color printer must do its own banding to achieve the best results. If color DIBs are used, this limitation does not exist.
The banding metafile is not used when an application does its own banding.
Certain printers, notably the HPÒ LaserJetÒ family under low memory conditions, separate output into two passes—one for text and one for graphics. After the text composed of built-in and downloaded fonts is placed on the page, a second pass outputs graphics primitives to the page. Although the difference between the two passes is meaningless for an application that draws a complete page to every band, applications that want to selectively draw to bands should be aware of this behavior. A text operation is any output performed using the TextOut or ExtTextOut function.
The BANDINFO escape gets information about the nature of the current band. When called after a new band has begun (after a NEXTBAND), this escape tells an application whether the printer is expecting text or graphics or both for this band. A driver does not have to support the BANDINFO escape. If the escape is not supported, every band handles both text and graphics. To determine whether a driver supports an escape, an application uses the QUERYESCSUPPORT escape. A driver that does not support this escape simply returns zero if the escape is attempted
The BANDINFO escape accepts two BANDINFOSTRUCT parameters, one for input and one for output. On input (the lpInData parameter), the application specifies what type of output it intends to put on this page and, if graphics are to be output, a graphics bounding rectangle. This input is meaningful only when used with the first band on the page. The driver uses it to optimize banding—graphics bands are not used unless they are needed. If the application passes a NULL lpInData, the driver expects a full page of text and graphics and bands accordingly.
The driver uses the output BANDINFOSTRUCT (the lpOutData parameter) to specify the nature of the current band. If only the text flag is set (fTextFlag set to TRUE), the band is a text-only band. All graphics output is ignored for this band. If, during the text band, text is encountered that requires glyphs to be generated in the graphics band (see below for more details), graphics bands are used even if the application specified that only text is being output to the page (see above). In bands that have the graphics flag set (fGraphicsFlag set to TRUE), the printer expects graphics operations. Text operations that involve fonts used in the text band are ignored. If both flags are set, the band is a graphics band, and the printer also expects text output that could not be performed in the text band.
The complication in the scheme is that, depending on the font being used, outputting text during the graphics band is possible. The example in Windows version 3.0 is vector fonts; in Windows version 3.1, TrueTypeÔ glyphs are output to the graphics band if they are rotated, if the font is larger than the threshold for downloading, or if the user selected Print TrueType As Graphics during printer setup. As a result of this expanded definition of text output, printer drivers often set both the text and the graphics bits when the current band is a graphics band.
Applications should not assume that a text band always exists. Given sufficient memory, the Windows version 3.1 driver for the LaserJet printers usually has a total of only one band that combines text and graphics.
By using the rectangle information that defines a band, an application can optimize its output to eliminate unnecessary work by the system. For example, if the current band covers the first 300 lines of the page, outputting a graphics primitive that appears between lines 600 and 900 results in the system doing significant work to get the primitive ready but ultimately clipping it out. An application that preclips its output to the bands can speed up its printing on a banding printer, especially if the output requires extensive simulations in GDI. On the system end of things, a graphics or text object is rarely clipped to the destination before final output time, long after any needed simulations have been performed.
Avoiding graphics operations during the text band is valid and efficient. On the other hand, an area that deserves caution is optimizing text output on printers that have separate text and graphics bands. Because text may appear in the graphics band depending on the font used, do not assume that text output can be ignored in a graphics band (unless all text was output in the text band and the fTextFlag was not set for the graphics band).
Printers that use text and graphics bands commonly allow the two bands to be at different resolutions (the text band remains at the highest possible resolution). For example, a LaserJet configured for 75 dpi outputs the text band at 300 dpi while outputting the graphics band at 75 dpi. This discrepancy allows printer fonts to always look their best while at the same time speeding up the printing of graphics by using less resolution. This effect is achieved by using a different scaling factor for the two types of bands.
The GETSCALINGFACTOR escape returns the scaling factor for the current band. The value is an exponent of 2 (so that when a value at text resolution is shifted right by the scaling factor, the result is the value at graphics resolution). If the escape is not supported or the scaling factor is 0, no scaling occurs. In the LaserJet at 75 dpi example, the first band (the text band) has a factor of 0, and all subsequent bands on the page (the graphics bands) have a factor of 2.
At the application level, all output is at full resolution at all times, and applications need not worry about the scaling factor. Coordinates are scaled when appropriate as part of the normal coordinate mapping that GDI does to convert logical units to device units. The only time the scaling becomes a concern is during pixel-specific processing, when 4 pixels at a logical 300 dpi could map to a single pixel at 75 dpi. Also, because clipping regions are specified in device units, a new clipping region designed for the scaled resolution needs to be built for the scaled bands.
A print job can be aborted in a variety of ways (discussed below), all with the same result. Any data that has not yet been sent to the printer is flushed out. Data that has already reached the printer cannot be recalled and is printed. The printer is notified of the abort and recovers as best it can. If the Print Manager is being used, the job is removed from the queue. If GDI was handling the banding, the banding metafile is deleted. The DC is returned to its state at creation. The application is still responsible for freeing its AbortProc function instance.
The SetAbortProc function (and the SETABORTPROC escape) sets up what is known as the AbortProc. This AbortProc function resides in the application; GDI calls it during a print job to inform the application of spooler errors and to allow the application to abort the job when desired. GDI calls the AbortProc function with information about why it is being called; this value is either an error code from the spooler or zero, which indicates that the function is being called simply to allow an abort.
The AbortProc function is called routinely during several steps of the printing process:
After every write to the printer port when printing directly to the printer (no spooling)
After every write to a file when printing directly to a file (no spooling)
After every write to the spooler file when spooling
Periodically when out of disk space for spooling as a result of other spool jobs
Before playing every metafile record when GDI is simulating banding
Occasionally from some older printer drivers
When GDI calls the AbortProc function, the application can continue the print job by returning a nonzero value or abort the print job by returning zero.
The only condition for calling the AbortProc function that actually results from an error condition during printing is insufficient disk space. In this situation, the spooler is indicating that sufficient disk space will exist for the print job to complete as soon as one or more of the other currently spooling print jobs are completed. By returning a nonzero value, the application indicates that it wishes to continue waiting for that disk space.
A big problem with the AbortProc function in the simple example at the beginning of this article is that it does not allow other applications to run during the print job. A more system-friendly AbortProc function processes pending messages before returning:
NonAbort(HDC hPrintDC, short uCode)
{
MSG msg;
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return(TRUE);
}
An application that decides to abort a print job on its own (that is, without using the AbortProc function) can do so by using the AbortDoc function or the ABORTPIC escape (also called ABORTDOC). Using either has the same effect as returning a zero from the AbortProc function. This approach to aborting a print job makes sense if the application decides to abort while performing intensive operations during which waiting for the AbortProc function to be called would be inconvenient.
Most applications give the user an opportunity to abort a print job by providing a dialog box with a cancel button or a variation on that theme. An application can use several time slices during a print job to check for a user cancellation of the printing. When GDI calls the AbortProc function, the printing process is yielding to the application for exactly such purposes; this is a good opportunity to check for user input. When the application itself is performing a time-intensive operation, it can yield to the abort-checking code when desired.
When the user indicates that the print job should be canceled, the application can use its AbortProc function or directly call the AbortDoc function. An application should not call the AbortDoc function from within its AbortProc function.
If GDI is handling the banding for an application, GDI can terminate the printing as a result of system errors. In these cases, GDI sends an ABORTPIC escape to the printer driver and cleans up the current banding metafile. The application is notified of the abort with an error return from the EndPage function (or NEWFRAME escape).