PDRREF:Design Considerations for Hardcopy Drivers

For information on hardcopy drivers written to the graphics engine dispatch table, see the following chapters:
 * Graphics Engine Hardcopy Drivers
 * Presentation Manager Function Categories
 * Exported Driver Function Reference
 * Mandatory and Simulated Graphics Engine Function Reference

Design Considerations
The following design considerations only apply to hardcopy drivers:
 * Banding
 * Document processing
 * Dynamic Job Properties
 * Extended attributes
 * Hardcopy device names
 * Hardcopy driver migration
 * Hardcopy driver output to file
 * Help
 * Job error dialog
 * Output threads

Banding
Banding is another technique that is available to presentation drivers for raster technology hardcopy devices. Banding is the process of dividing a whole page into two or more bands (or strips) of raster data, which is recorded in memory as a bit map, and is then sent to the physical device or spooler. It is used to reach a balance between memory requirements and performance. This technique uses the graphics engine's journaling functions to save and replay a journal file of the calls to the Grexxx functions for a whole page.

The hardcopy driver handles the output page as a number of bands and creates a bit map large enough for one band at a time (see illustration below). ┌────────────────────┐ │      Band 1       │ ├────────────────────┤ │      Band 2       │ ├────────────────────┤ │        "          │ │         "          │ │         "          │ ├────────────────────┤ │       Band N       │ └────────────────────┘ The device context origin of the bit map is manipulated so that it relates to each band in turn. The hardcopy driver replays the journal file as many times as necessary to write into each band. Note that Band 1 cannot be written into while the calls to the Grexxx functions are being journaled. This is because the Command flag, COM_DRAW, is turned OFF between calls to GreStartJournalFile and GreStopJournalFile unless the JNL_DRAW_OPTIMIZATION flag is passed in on the call to function GreCreateJournalFile. The hardcopy driver is told not to perform any output while the Grexxx calls are being journaled unless the JNL_DRAW_OPTIMIZATION bit flag is set.

The size (width and height) of each band is determined by each hardcopy driver, dependent upon the type of physical device to which the output is to be sent and the amount of memory the hardcopy driver can use to build its bit map. As an example, a color laser printer might need the full 24 bits per pel, in which case, several bands might be needed to make a page. A simple dot-matrix printer that uses one bit per pel could treat the whole page as a single band.

The data for each band is sent as a single band to the physical device for an OD_DIRECT device context, or to the spooler for an OD_QUEUED device context with a data type of PM_Q_RAW. Note that the number of bytes of data that are sent might not be the same as the number of bytes required to create the bit map for a given band. This can be due to compression algorithms, which might be implemented in a given hardcopy driver and supported by a given physical device.

The technique of banding is performed by recording all of the graphics for a whole page in a journal file. When the journal file is complete (that is, the hardcopy driver has received a DEVESC_NEXTBAND escape, a DEVESC_NEWFRAME escape, or a DEVESC_ENDDOC escape), the hardcopy driver plays the journal file, reprocesses the calls recorded to produce each band in turn, and clips the graphics recorded in the journal file to each band output. After all bands are output, the journal file is deleted. This banding technique uses the graphics engine journaling functions to save and replay a journal file of the Grexxx calls. These graphics engine journaling functions are documented in Journal Functions (Hardcopy Drivers Only).

Each page of output is handled as a separate entity. The GreEscape routine for DEVESC_STARTDOC opens a journal file for the first page and registers it in the device context instance data. When GreEscape DEVESC_NEWFRAME or DEVESC_ENDDOC is received, the hardcopy driver writes the bands and closes the journal. If the escape code was DEVESC_NEWFRAME, the GreEscape routine opens and registers the journal file for the next page.

The following figure gives an overview of how the presentation driver performs.

Document Processing
The following figure diagrams the state transitions for the GreEscape function calls used during document processing. The GreEscape function names are abbreviated for ease of reading. For example, GreEscape (DEVESC_STARTDOC) is shown as STARTDOC. See Spool File Creation for details on each of the output actions.

Dynamic Job Properties
Dynamic Job Properties (DJP) provides two new printing enhancements. The first enhancement makes it possible to set up a print job without relying on user intervention. This is an improvement over the current "black box" configuration, which requires that the printer driver's job properties be displayed in a dialog box for a user to edit.

The second enhancement makes it possible to change the print format within the same job. Currently, a four page legal document that includes a high resolution color bitmap and a letter envelope would have to be printed out as three separate jobs, requiring the user to set the job properties of each job to match the specified format (monochrome legal text; high-resolution, 24-bit per pel bitmap; and monochrome envelope). With the DJP enhancement, a program can pass in different job properties with each page, rather than bringing every page up to the highest format in the job.

Refer to Mandatory and Simulated Graphics Engine Function Reference for a detailed description of each of the following functions: GreEscape DEVESC_DEFAULTJOBPROPERTIES GreEscape DEVESC_NEWFRAME_WPROP GreEscape DEVESC_QUERYESCSUPPORT GreEscape DEVESC_QUERYJOBPROPERTIES GreEscape DEVESC_QUERYSIZE GreEscape DEVESC_SETJOBPROPERTIES GreEscape DEVESC_STARTDOC_WPROP

Design Overview of DJP
DJP can be applied to two different scenarios. The first scenario applies to applications that query or modify DJP_ITEMs; these events are usually performed asynchronously. In this scenario, the application opens up a OD_INFO DC, sets up its job property information, and then turns around and opens up a OD_QUEUED DC to print. Setting or querying data uses only the printer property information in the device context (DC) and does not affect the print job in any other way.

In the other scenario for using DJPs, the DC characteristics provided in the DevOpenDC information are changed. This is either accomplished at the start of a document with GreEscape DEVESC_STARTDOC_WPROP or at the beginning of a new page with GreEscape DEVESC_NEWFRAME_WPROP. In this scenario, the application must query the printer driver again and again for DevQueryCaps, DevQueryHardcopyCaps, list of devices fonts, and any other DC information. The driver must use the same code path that DevOpenDC uses (FillLogicalDeviceBlock, FillPhysicalDeviceBlock, EnableDeviceContext, CompleteOpenDC). After the printer driver resets all of its information, it calls the new Gre call, GreResetDC2 (hdc, 0). This call instructs the graphics engine to query the printer driver again and recalculate its internal state data. This has the side effect of performing a ResetDC.

The DJP_ITEM is the new structure that contains DJP information. The structure DJP_ITEM is used by both applications and device drivers. The format of the structure follows.  /* Dynamic Job Property Item typedef struct _djpItem { ULONG      cb;             // size of this structure ULONG      ulProperty;     // Type of Property LONG       iType;          // INPUT : DJP_ALL - Returns all possible //        values (placed at ulValue). //        DJP_ALL is not valid if                               //         setting properties. //                              //         DJP_CURRENT - returns current //        value //                              // OUTPUT: DJP_ERROR_XXX if there was an                               //         error ULONG      ulNumReturned;  // How many elements are returned in                               // ulValue on a query. This should be                              // 1 for DEVESC_SETJOBPROPERTIES. ULONG      ulValue;        // The start of the types supported by                               // this property. } DJP_ITEM, *PDJP_ITEM;  Because the individual properties have variable lengths, the properties are broken up into two different categories: simple and complex. The names of these properties are identified by either "_Sx_" for simple or "_Cx_" for complex. In addition, they are sorted into different subcategories such as "_xJ_" for job properties or "_xP_" for printer properties. Simple types are ULONG sized. This is the minimum size for the data. The complex types can be larger and have structures associated with them. All property types, simple and complex, are associated with a type; for example, DJP_SJ_ORIENTATION is a simple type of type PDJPT_ORIENTATION. The values for DJP_SJ_ORIENTATION can be DJP_ORI_PORTRAIT or DJP_ORI_LANDSCAPE. DJP_CJ_RESOLUTION is a complex type of type PDJPT_RESOLUTION; this is a structure composed of two members: x and y resolutions.

The following helper macros have been created to work with DJP_ITEMs: DJP_NEXT_STRUCTP, DJP_ELEMENTP, and DJP_SET_ELEMENT.

This macro is used for walking the list of item structures. It takes a pointer to the current item and returns a pointer to the next item and depends on the size of the structure set in the variable cb.
 * DJP_NEXT_STRUCTP:

// Bump to the next DJP_ITEM structure pDJP = DJP_NEXT_STRUCTP (pDJP);

This macro takes a pointer to the current item and returns a pointer to the element type passed in. Each job property type has a corresponding element type.
 * DJP_ELEMENTP:

// Get the pointer to the resolution value PDJPT_RESOLUTION   pElm; pElm = DJP_ELEMENTP (pDJP, DJPT_RESOLUTION);

This macro will take three parameters: The first is a DJP_ITEM structure, the second is the element type, and the third is the DJP element. This function helps to deal with complex structures, but is not useful for dealing with simple structures. The following example shows how to set DJP_CJ_RESOLUTION (complex property) to use a 720x720 resolution:
 * DJP_SET_ELEMENT:

// Structure (from header file DJP.H)   typedef struct _djpResolution { USHORT    usXResolution; // X resolution (in dots per inch) USHORT    usYResolution; // Y resolution (in dots per inch) } DJPT_RESOLUTION, *PDJPT_RESOLUTION; // Set the # of bytes in the structure pDJP->cb = DJP_HEADER_SIZE + sizeof (DJPT_RESOLUTION); // Set the current property and type pDJP->ulProperty = DJP_CJ_RESOLUTION; pDJP->iType     = DJP_CURRENT; // Set up the resolution structure djpResolution.usXResolution = 720; djpResolution.usYResolution = 720; // Put the data into the DJP_ITEM structure. DJP_SET_ELEMENT (*pDJP, DJPT_RESOLUTION, djpResolution);

To set a simple type such as the color mode use: pDJP->cb        = sizeof (DJP_ITEM); pDJP->ulProperty = DJP_SJ_COLOR; pDJP->lType     = DJP_CURRENT; pDJP->ulValue   = DJP_CLR_COLOR; /* Move to next element. Either way, you use the same macro. ** Dependent on cb having the correct value. */ pDJP = DJP_NEXT_STRUCTP (pDJP);

The parts of the DJP property defines identified by _Sx_ and _Cx_ indicate whether the specified item is a simple item or a complex item. This information helps you to determine the correct strategy for setting the structure size and the data value.

DJP Design Restrictions
DevEscape is a preexisting API. This means that is a metafile. Metafile programs can work with old drivers (returning error codes), or both the new programs and new printer drivers can work on old operating system levels.

Unfortunately, DevEscape has only two pointers to data blocks, each with two corresponding sizes. This limits what can be passed through the API. If a DJP is changed, you must re-query all of the HDC (the device context handle) characteristics. This includes font information, DevQueryCaps, DevQueryHardcopyCaps. In addition, previously opened memory HDCs are no longer valid or compatible with this DC.

Engine Interfaces to the Printer Driver
DJP uses two paths to communicate with the driver. The first path is the preexisting DevEscape. This is hooked out in the dispatch table at location NGreEscape & 0x01FF. The second is a new API DevPostEscape. This is an exported entry point in the .DEF file (OS2_PM_DRV_POSTESCAPE @206).

The prototype for DevEscape follows: LONG ENGENTRY Escape (HDC      hdc,                       LONG      lEscape,                       LONG      cInCount,                       PBYTE     pInData,                       PLONG     pcOutCount,                       PBYTE     pOutData,                       PDDC      pddc,                       ULONG     ulFunction) The prototype for DevPostEscape is: ULONG APIENTRY OS2_PM_DRV_POSTESCAPE (PSZ      pszDriverName,                                       PSZ       pszDeviceName,                                       PSZ       pszQueueName,                                       PSZ       pszSplPrinterName,                                       ULONG     ulFuncNum,                                       ULONG     cbParm1,                                       PBYTE     pbParm1,                                       ULONG     cbParm2,                                       PBYTE     pbParm2) The following information shows how these two functions are similar (note that most of the parameters are the same). LONG     lEscape     <===> ULONG     ulFuncNum     * LONG     cInCount    <===> ULONG     cbParm1 PBYTE    pInData     <===> PBYTE     pbParm1 PLONG    pcOutCount  <===> ULONG     cbParm2 PBYTE    pOutData    <===> PBYTE     pbParm2 The defines and values are different, but the concept is the same. DevEscapes                   DevPostEscapes GreEscape                    DEVPE_QUERYSUPPORT DEVESC_QUERYESCSUPPORT GreEscape DEVESC_QUERYSIZE   DEVPE_QUERYSIZE GreEscape                    DEVPE_QUERYJOBPROPERTIES DEVESC_QUERYJOBPROPERTIES The major difference between DevEscapes and DevPostEscapes is that DevEscape uses an already opened device context handle (HDC), and printer property information is already "hardcoded" at DevOpenDC time. If another device's job properties are passed in, or if another queue's job properties is used with different printer property information, misleading information will be returned.

To overcome this limitation with DevEscape, DevPostEscape passes in four strings with all the information required to find both the printer and job property data. However, DevPostEscape also has a limitation: DevPostEscape behaves like DevPostDeviceModes in that it loads and unloads the printer driver for each call to DevPostEscape. When DevPostEscape is used before a DevOpenDC is issued against a specified printer driver, the number of opened instances against the printer driver is 0 (after DevOpenDC, the number will be at least 2 or more). When the number of references is 0, the printer driver will behave as if it is being called for the first time and will load all of its global resources, such as string tables and device data structures. This process can be time consuming, and, if DevPostEscape is called a number of times before DevOpenDC, performance will suffer.

Minimum Support for DJP
To provide minimum support for DJP, the printer driver must accomplish the following: DevEscapes                   DevPostEscapes GreEscape                    DEVPE_QUERYSUPPORT DEVESC_QUERYESCSUPPORT GreEscape DEVESC_QUERYSIZE   DEVPE_QUERYSIZE GreEscape                    DEVPE_QUERYJOBPROPERTIES DEVESC_QUERYJOBPROPERTIES GreEscape                    DEVPE_SETJOBPROPERTIES DEVESC_SETJOBPROPERTIES GreEscape                    DEVPE_DEFAULTJOBPROPERTIES DEVESC_DEFAULTJOBPROPERTIES GreEscape                    GreEscape DEVESC_STARTDOC_WPROP        DEVESC_NEWFRAME_WPROP
 * Export OS2_PM_DRV_POSTESCAPE @206 for the new DevPostEscape API.
 * Support the following DevEscapes and their equivalent DevPostEscapes:

Support the following DJP_ITEMs:
 * DJP_NONE
 * DJP_SJ_ORIENTATION
 * DJP_CJ_RESOLUTION
 * DJP_SJ_BITSPERPEL
 * DJP_SJ_COLOR
 * DJP_SJ_PRINTQUALITY
 * DJP_SJ_PAPERSIZE
 * DJP_CJ_FORM
 * DJP_SJ_COPIES
 * Note:You must support the GreEscape DEVESC_QUERYSIZE/DEVPE_QUERYSIZE for all DJP_ITEMs (returning a count of 1).

Unsupported DJP Items
If your device does not have a DJP_ITEM in its job properties, then the following strategy should be implemented:  for DEVESC_QUERYSIZE return the sizeof (DJP_ITEM) pQuerySize->ulSizeNeeded += sizeof (DJP_ITEM);

for DEVESC_QUERYJOBPROPERTIES pIQuery->ulNumReturned = 0; pIQuery->ulValue      = DJP_NONE; pIQuery->lType        = DJP_ERROR_NOT_SUPPORTED;

and return DEV_WARNING

for DEVESC_SETJOBPROPERTIES pISet->lType = DJP_ERROR_NOT_SUPPORTED;

and return DEV_WARNING 

Using DEVESC_STARTDOC_WPROP and DEVESC_NEWFRAME_WPROP
GreEscape DEVESC_STARTDOC_WPROP and GreEscape DEVESC_NEWFRAME_WPROP are used to change your device characteristics. When an application calls DevOpenDC, it passes your job property information. DJP allows an application to give new job property information on a page-by-page basis. The printer driver must reinitialize its internal structures based on the new job property information. If the device is placed into one mode and a new job property forces the device into a different mode, then it is acceptable for the printer driver to terminate the job and reinitialize the device. To keep the spooler from terminating and reinitializing, avoid using PrtClose to close the job. (Because the device context is essentially different than it was at DevOpenDC time, all memory compatible DCs, any information returned from DevQueryCaps or DevQueryHardcopyCaps, and any font related information will have to be requeried or recreated by the application.)

Extended Attributes
Extended attributes (EA) are used for correct installation and operation of multifile drivers. They are also used for improved performance during installation.

Extended attributes serve three purposes: Performance is improved because the Workplace Shell no longer needs to open a Device Context to the driver, which causes the driver to be loaded each time it needs Icon and Device Name information.
 * Installation of multifile drivers
 * Version numbering
 * Improved performance of drivers in the Workplace Shell*

Names are separated by commas when more than one name is defined in an extended attribute. The following examples of extended attributes are for the HP LaserJet** hardcopy driver.

Installation of Multifile Drivers

 * VENDORNAME (Optional) : Vendor name of the supported printers, such as VENDORNAME=HP.
 * REQUIREDVENDORFILES (Optional unless the driver requires other files) : Names of extra files required for correct operation by the driver files in the VENDORNAME directory, such as REQUIREDVENDORFILES=HP_ADDF.DLL.
 * OPTIONALVENDORFILES : Names of extra files that are optional, that is, files not required for correct operation, such as OPTIONALVENDORFILES=PCLHELP.HLP, HP_ADDF.SYM, HP_ADDF.MAP.
 * CLASSNAME (Optional) : Name of the output stream created by the hardcopy driver, such as CLASSNAME=PCL.
 * REQUIREDCLASSFILES (Optional unless the driver requires other files) : Names of extra files required for correct operation by the driver files in the CLASSNAME directory, such as REQUIREDCLASSFILES=GENERIC.DLL.
 * OPTIONALCLASSFILES : Names of extra files that are optional, that is, files not required for correct operation, such as OPTIONALCLASSFILES=*.FNT, GENERIC.SYM, GENERIC.MAP.
 * REQUIREDDRIVERFILES : Names of extra files required by the driver for correct operation, such as REQUIREDDRIVERFILES=LASERJET.DRV.
 * OPTIONALDRIVERFILES : Names of extra files that are optional, that is, files not required for correct operation, such as OPTIONALDRIVERFILES=LASERJET.SYM, LASERJET.MAP.

Subdirectory Structure
The subdirectory structure on the startup driver created by the hardcopy driver installation is as follows: OS2\DLL\VENDORNAME\CLASSNAME\DRVNAME An example of this subdirectory structure might be OS2\DLL\HP\PCL\LASERJET. Most hardcopy drivers need to be installed only in one subdirectory. In this case, omit CLASSNAME and ensure that VENDORNAME is equal to the name of the DRV file.

For example, to install a Postscript** driver in a subdirectory, you might use the following extended attributes: VENDORNAME = PSCRIPT REQUIREDDRIVERFILES = PSCRIPT.DRV OPTIONALDRIVERFILES = PSCRIPT.HLP This will install the PostScript driver into a subdirectory named PSCRIPT.

Version Numbering

 * VERSION (Optional) : Indicates the version number of a hardcopy driver. For example to indicate a hardcopy version number of 13.328, the extended attribute might be .VERSION=13.328. Hardcopy drivers can load this attribute and display it in OS2_PM_DRV_DEVMODE dialogs. See Hardcopy Driver Migration for more information.

Improved Performance of Drivers in the Workplace Shell

 * EXPAND (Optional) : Lists the device names supported by a hardcopy driver. Using this extended attribute improves performance over use of OS2_PM_DRV_DEVICENAMES (especially from a diskette). Names (values) for this extended attribute are NULL-terminated strings. An example of this extended attribute is .EXPAND=HP LaserJet II\0HP LaserJet III\0\0.
 * ICON (Optional) : Gives the icon definition used by the Workplace Shell to display this hardcopy driver. This extended attribute is automatically created during the build process if the ICON keyword is used with a resource identifier of 1, or if the DEFAULTICON keyword is used in the RC file for a hardcopy driver. An example of this extended attribute would be .ICON 1 LASER.ICO.
 * HIDDEN (Optional) : Indicates which hardcopy driver is used by files associated with a printer driver. This attribute is used by the Workplace Shell to prevent display of these files in a directory folder. An example of this extended attribute is .HIDDEN=LASERJET.DRV.

Hardcopy Device Names
If a hardcopy driver supports multiple devices, this is indicated at the API and user level. There are three parts:
 * The hardcopy driver implements OS2_PM_DRV_DEVICENAMES.
 * The hardcopy driver accepts a device name in the szDeviceName field of pdriv. See FillPhysicalDeviceBlock.
 * The device name is accepted in OS2_PM_DRV_DEVMODE.

Hardcopy Driver Migration
Hardcopy drivers must be able to work with earlier levels as well as future levels of drivers across a network. Notice that there are several possible driver version numbers:
 * EA Version Number. Derived from a build number (such as 13.160)
 * Dialog Version Number. Derived from the build number. Service representatives can use this number to uniquely identify the build of the hardcopy driver and the fix level.
 * INI File Data Version Number. Identifies to the driver the version number of the data stored in the OS2SYS.INI file. The numbering system and format is defined by the hardcopy driver.
 * lVersion Number. Given in the DrivData structure of job or printer properties. The numbering scheme of this ULONG is defined by the hardcopy driver, but the following format of lVersion is suggested. It is a FIXED type of number.
 * High WORD. System level in BCD (such as 0020)
 * Low WORD. Build level in BCD (such as 0160).

The numbering scheme for drivers is as follows: New Driver        20.0000 Minor Update      20.0001 Major Release     21.0000 For drivers reading data from an earlier version of the driver, the data must be interpreted (by using the lVersion number as a guide to its format) and any missing data must set to the device defaults. For drivers reading data from a later version of the driver, as much data as possible should be read and the rest ignored. This implies that later versions of the driver only add new driver data fields at the end of the data.

Hardcopy Driver Output to File
Hardcopy drivers can print data to a disk file so that the print file can be sent to the printer later without any need for application intervention. The print file can be used as a file interchange format. Printing to file means the hardcopy driver converts the prespool input into a device-dependent format and stores the output directly to a file. File system errors must be reported.

There are two methods the hardcopy driver can implement to allow printing to disk. The system-provided method for printing to disk requires that the hardcopy driver handle a file name as a szLogAddress on a OD_DIRECT Enable Subfunction 02H - FillPhysicalDeviceBlock. The other method for printing to disk enables the hardcopy driver to change the job properties dialog to allow the user to input a fully-qualified file name. There are circumstances in which the hardcopy driver must know the format of the required output file. For example, a hardcopy driver can output raw PostScript or Encapsulated PostScript (EPS).

Help
All hardcopy drivers have a Help function that invokes contextual assistance.

Job Error Dialog
Use the SplMessageBox function to display error messages from hardcopy drivers. The following push buttons in a message box are presented to an end user: The hardcopy driver will respond to each of the returns in the following manner: The Job Error dialog box contains a Help push button and associated help.
 * RETRY : Retry sending print data
 * ABORT : Delete job
 * IGNORE : Cancel dialog
 * MBID_RETRY : Continue sending data to the output buffer (PrtWrite)
 * MBID_ABORT : Issue a PrtAbort to tell the spooler to delete the current job and set a flag that the job has been ended; then return from the write thread
 * MBID_IGNORE : Continue sending data to the output buffer (PrtWrite)

Output Threads
Printer drivers should buffer output into large blocks and call PrtWrite on a separate thread. Use DosCreateThread to start this separate thread when the first printer-specific commands are to be written.