One Call Does It All

From EDM2
Jump to: navigation, search

One call to SetupPrinter() prepares your PM application for printing

by Monte Copeland

One rule of Presentation Manager (PM) programming is that an application must be ready to paint the screen upon receipt of a paint message. Likewise, serious PM applications must be ready to print whenever the user says, Print!

A PM application should keep details about a certain printer attached to the system. It can perform screen drawing relative to this printer and be ready to print at the touch of a button. For this reason, most PM applications offer a Printer Setup option to get details about the user's preferred printer.

The C code provided with this article contains a function called SetupPrinter(). It provides a user interface to select a printer and change the print job settings (see Figure 1). Most importantly, it returns an info device context (DC) and presentation space (PS). The purpose of info DCs and PSs is to let the application query the printer for information about forms, colors, fonts, and character widths without creating an actual print job.

OneCallDoesItAll Fig-1.gif

Figure 1. Printer setup dialog presented by SetupPrinter(). The user may pick a printer from the list shown and configure job properties such as form and portrait/landscape orientation Selecting Print to file prompts the user for a file name.

The Printer "Couch"

For applications, the Printer Setup menu option provides a printer couch which governs font selection and what-you-see-is-what-you-get (WYSIWYG) screen painting.

For example, some applications prefer to use printer internal fonts because they print quickly However, you cannot realize printer device fonts on the screen, so these applications must simulate device fonts on the screen for WYSIWYG. The info presentation space is invaluable for this task For WYSIWYG, the application needs to know the precise placement of characters as they would be drawn on the printer. The application uses the following steps:

  • GpiCreateLogFont(hpsPrinterInfo, ) to realize the device font in the printer info PS
  • GpiCreateLogFont(hpsScreen, ) to realize a simulation font using the screen PS
  • GpiSetAttrs(hpsPrinterInfo, ) to set character set and box attributes in the printer info PS
  • GpiSetAttrs(hpsScreen, ) to set similar character set and box attributes in the screen PS
  • GpiQueryCharStringPosAt(hpsPrinterInfo, ) to get character positions for a string using the device font
  • GpiCharStringPosAt(hpsScreen, ) to draw the simulated font on the screen using device font positioning

Character positions are in world coordinates, so create both the printer info PS and the screen PS with the same world coordinate system. PU_TWIPS is a favorite GPI coordinate system for programming fonts because there are 20 PU_TWIPS units in a point.

Given a string, an application can query its text box using GpiQueryTextBox(). A text box is a parallelogram that bounds the string. Knowing the text box coordinates is crucial when positioning text near the edge of the page. For example, LaserJet printers in PCL will clip a whole character if a portion of the character lies outside the device clipping limits.

Every printer has different hardware margins for its various form sizes Using the info DC, an application calls DevQueryHardcopyCaps() to obtain these margins. This way, an application can simulate the entire form, not just the imageable area as reported by GpiQueryPS().

Sample Code: The SetupPrinter() Function

The Developer Connection for OS/2 CD-ROM contains sample C code for the function SetupPrinter(). SetupPrinter() has two parameters a pointer to a PRINTERSETUP structure (defined in SETUP H) and a Boolean variable to indicate if SetupPrinter() should display a dialog. An application should call SetupPrinter() once during WM_CREATE processing, then again whenever the user selects the Printer Setup option.

// Definition of PRINTERSETUP structure
typedef struct _PRINTERSETUP {
       LONG           lWorldCoordinates;
       HAB            hab;
       HWND           hwndFrame;
       HMODULE        hmod;
       CHAR           szPreferredQueue[ 64 ];
       PDRIVDATA      pDriverData;
       HDC            hdcPrinterInfo;
       HPS            hpsPrinterInfo;
       LONG           lDCType;
       PDEVOPENDATA   pDevOpenData;
       DEVOPENSTRUC   devopenstruc;
       LONG           cQueues;
       PPRQINFO3      pQueueInfo;
       BOOL           fToFile;
       CHAR           szFileName[ CCHMAXPATH ];
} PRINTERSETUP;
typedef PRINTERSETUP *PPRINTERSETUP;

// Function prototype for SetupPrinter()
ULONG APIENTRY SetupPrinter( PPRINTERSETUP pSetup, BOOL
                             fShowDialog );

Sample Code 1. Definition for PRINTERSETUP structure which is input to the SetupPrinter() function Before calling SetupPrinter(), the application initializes the structure to zero and assigns lWorldCoordinates, hab, and hwndFrame. If profiled, the application may also assign szPreferredQueue and pDriverData See SETUP H in the SOURCE DEVNEWS VOL7 ONECALL subdirectory on your Developer Connection for OS/2 CD-ROM, disc 1.

Before calling SetupPrinter(), an application must first prepare a PRINTERSETUP data structure (see Sample Code 1). The application should initialize the structure to zeroes, then assign lWorldCoordinates, hab, and hwndFrame. If the application saved the user s preferences from a previous run, it may also assign szPreferredQueue and pDriverData. If szPreferredQueue or pDriverData contain invalid or outdated data, SetupPrinter() will use the system default printer PRINTERSETUP is defined in SETUP.H.

SetupPrinter() uses SplEnumQueue() to enumerate printer objects and if users change, add, or delete printers, they will always see a current list of printers whenever they select Printer Setup.

After the call to SetupPrinter(), the application can use the hdcPrinterInfo and hpsPrinterInfo handles in the PRINTERSETUP structure to query the printer The Dev* APIs use hdcPrinterInfo, and the GPI* APIs use hpsPrinterInfo. If you use the system font dialog via WinFontDlg(), you can use hpsPrinterInfo in FONTDLG hpsPrinter.

The PRINTERSETUP structure contains a PDEVOPENDATA intended for use with DevOpenDC() to open a DC for printing. It points to the same DEVOPENDATA used to open the info DC. This is nice! When it s time to print, use lDCType and pDevOpenData on the call to DevOpenDC() (See Sample Code 2).

// The PRINTERSETUP structure has DC type and DEVOPENSTRUC variables
// intended for use on DevOpenDC() 

PRINTERSETUP   Setup;
  
case WM_USER_PRINT 
       hdc = DevOpenDC( hab, Setup lDCType,  * , 9,
                        Setup pdevopendata, (HDC)0 );
       assert( hdc );

Sample Code 2 SetupPrinter() prepares variables lDCType and pDevOpenData suitable for use on DevOpenDC() Normally, DC type is OD_QUEUED, and the logical address is the name of a printer queue If the user selects Print to file, DC type will be OD_DIRECT, and pszLogAddress will be a disk file name.

At WM_CLOSE time, make a call to CleanupPrinter() and pass it the PRINTERSETUP structure. This closes the info DC and PS and frees heap allocations.

Code Integration Considerations

To integrate SetupPrinter(), you need SETUP.C, SETUP.H, SETUP.DLG, and PMASSERT.H. The code uses the PRQINFO3 structure, so define both INCL_SPL and INCL_SPLDOSPRINT before including SETUP.H in your C code Include SETUP.DLG in your RC file using rcinclude. Ensure that the definition for ID_PICKQ doesn't conflict with other dialog identifiers.

SetupPrinter() and CleanupPrinter(), as provided in SETUP.C, use malloc() and free() for memory management. If you use some other form of heap management, you will have to change the code in SETUP.C to match your style of heap management.

PMASSERT.H defines the debugging macro assert() used in SETUP.C. The parameter to assert() is a Boolean expression that. I expect to be true If it is ever false, the macro presents a message box with the line number and file name of the failed assertion. For non-debug builds, define the symbol NDEBUG to the compiler, and assert() disappears. See MAKEFILE targets all and debug.

To test for errors, check the return code from SetupPrinter(). It returns a non-zero result code if an error occurs (See SETUP.H).

Also on The Developer Connection for OS/2 CD-ROM is a test-drive program that uses SetupPrinter(). See DRIVER.C, OBJECT.C, DRIVER.H, DRIVER.RC, and MAKEFILE. Give TSTDRIVE.EXE a file name on the command line; TSTDRIVE.EXE prints the file using a 9-point Courier font TSTDRIVE EXE is a two-threaded PM application. For more information view Multithreading PM Applications in DOCS\DEVCON1.INF on The Developer Connection for OS/2 CD-ROM, disc 1.

Notes About the OD_QUEUED Print Job

Creating a perfect queued print job is an art A connoisseur of OD_QUEUED massages a metafile as a master chef suffers a souffl‚ Some tips for successful queuing

There are two kinds of OD_QUEUED print jobs standard and raw. To create a queued-standard print job, DEVOPENSTRUC pszDataType must be PM_Q_STD. To create a queued-raw print job, it must be PM_Q_RAW. A queued-standard job spools a metafile, while a queued-raw job spools printer-specific output. The SetupPrinter() function uses PM_Q_STD.

A metafile is to GPI as shorthand is to writing. Both forms have the same content, but metafiles and shorthand are denser representations, so they take up less space.

There are substantial performance reasons for creating queued-standard print jobs. First, a metafile takes up to 90% less disk space than printer-specific output. Second, OD_QUEUED spooling occurs on a thread in the application With 90% less data, it follows that the application thread will finish more quickly when spooling a metafile!

Try this yourself Create a queue for the LaserJet driver Change status to Hold On the Queue Options page of the printer settings, select Printer-specific format. Use TSTDRIVE.EXE to print DRIVER H Now deselect Printer-specific format and print again. Compare the sizes of the two *.SPL files under the SPOOL directory. The bigger one is full of HP PCL, and the smaller one is a GPI metafile viewable with PICVIEW.EXE.

When you change status to Release, a thread in PMSPOOL.EXE will print the jobs. For the printer-specific job, the spooler simply writes the HP PCL that was generated on the application thread. For the metafile job, PMSPOOL.EXE plays the metafile to the printer driver, which generates HP PCL and sends it directly to the parallel port. For the metafile case, large blocks of printer-specific output are never written to disk.

Some restrictions on the GPI exist when creating queued-standard print jobs. These are documented in PMGPI.INF with the warning, This function must not be used when creating SAA-conforming metafiles; see Metafile Restrictions. For example, GpiBitBlt() is an API that cannot be used when printing queued-standard print jobs; use GpiWCBitBlt() instead.

Keep font and bitmap handles for the duration of the print job After DEVESC_STARTDOC, create all bitmaps and fonts required for the job; delete them only when the drawing is done.

For best results, use zero for the FATTRS lMatch number on GpiCreateLogFont() when spooling to an IBM LAN Server print server. In absence of lMatch, be sure to fill in everything else that you can in FATTRS, including the precise face name from FONTMETRICS queried with GpiQueryFonts() Font lMatch numbers are not guaranteed to be the same from one OS/2 machine to another.

For a good discussion of queued-standard restrictions, consult Graham Winn's book, OS/2 Presentation Manager GPI (New York Van Nostrand Reinhold, 1991).

Try It Out

Check out the code for SetupPrinter(). It is in the SOURCE\DEVNEWS\VOL7\ONECALL subdirectory on The Developer Connection for OS/2 CD-ROM, disc 1. Try it out! You'll see that one call to SetupPrinter() prepares the printer for your PM application.

Reprint Courtesy of International Business Machines Corporation, © International Business Machines Corporation