home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 14 Text
/
14-Text.zip
/
SEMINAR1.ZIP
/
SEMINAR1.DOC
Wrap
Text File
|
1991-11-27
|
23KB
|
501 lines
Printing from Within an Application
-----------------------------------
Written by:
Larry Salomon, Jr. (aka 'Q')
OS/2 Applications and Tools
IBM T.J. Watson Research Center
Yorktown Heights, NY
(LARRYS@YKTVMV.BITNET)
(larrys@watson.ibm.com)
(larrys@ibmman.watson.ibm.com)
(larrys@ibmman2.watson.ibm.com)
This seminar may be distributed in any form, providing that no
modifications are made to the text, unless done so by the author.
This, and other seminars, can be received via anonymous ftp from the
following sites:
Site IP Address Directory
---- ---------- ---------
cs.unc.edu 129.109.136.138 /pub/os2.seminars
network.ucsd.edu 128.54.16.3 /pub/os2.seminars
Abstract: one of the most-common functions desired from an application
is the ability to produce some form of hardcopy of the work done with
that application. Yet coding to print under OS/2 is one of the hardest
things to understand, due to the lack of good documentation. This
seminar will attempt to address this subject.
Disclaimer: I do not claim to know everything about this subject. I
simply know enough about this subject to warrant the writing of this
seminar with the intent of making it easier on those who want to know
more.
Audience: this seminar is directed at C programmers with good PM
programming experience. Function prototypes will be taken from the OS/2
1.3 toolkit.
Contact: any questions, comments, etc. can be sent directly to me at one
of the addresses listed above (in the order of preference and highest
rate of successful delivery).
Device Independence
-------------------
OS/2 dubiously touts a feature called device independence, which many
have heard of but few know much about. In a sentence, device
independence means that what your application does on one output device
can be done on any other output device, provided that the new device has
the physical capabilities required to perform the actions requested (I
don't want to get any notes asking why you can't get color output from a
black-and-white printer!!!) and provided that you do the proper steps to
setup any output device that isn't the display. To put this in practical
terms, GpiCharStringAt on a display will work the same on a printer,
plotter, etc. (also known as the "a rose is a rose is a rose etc."
syndrome :).
Of course, you'll notice the phrase "proper steps to setup...". What
does this mean? On a printer, it means that you told the spooler (here,
I am assuming that printing will go through the spooler) what printer to
use and any special job properties that are to be associated with this
output job.
Device Contexts
---------------
The method used to setup a printer for output is by using device
contexts. A device context is simply a data structure describing, among
other things, the characteristics and capabilities of the device in
question. Device contexts can be manipulated using the Dev API group.
The first step, however, is to acquire a device context handle. This is
done using the DevOpenDC call:
HDC APIENTRY DevOpenDC(HAB hab,
LONG lType,
PSZ pszToken,
LONG lCount,
PDEVOPENDATA pdopData,
HDC hdcComp);
hab - the anchor block handle of the calling thread
lType - one of the OD_* constants. We will be using OD_QUEUED and
OD_MEMORY
pszToken - a token into the OS2.INI file used for obtaining the default
job specific data. "*" means that no data is to be taken from OS2.INI
lCount - the number of items initialized in the structure pointed
to by pdopData.
pdopData - a pointer to a DEVOPENSTRUC who's first 'lCount' items are
properly initialized.
hdcComp - for OD_MEMORY device contexts, the device context handle of a
compatible device. If NULL, OS/2 assumes compatibility with the
display.
DevOpenDC returns a device context handle if successful, DEV_ERROR
otherwise. We will use, for the sake of example, the following call:
hdcPrinter=DevOpenDC(hab,OD_QUEUED,"*",9L,&dosPrinter,NULL);
For now, we will only use OD_QUEUED HDC's, meaning that hdcComp will
always be NULL. Later, when we discuss bitmaps, we will use OD_MEMORY
contexts. We will see later how to initialize the DEVOPENSTRUC
(dosPrinter) structure properly.
So now we have a device context handle. Big deal, right? The fun
actually begins after we create a presentation space using this device
context handle as the second parameter:
szlPage.cx=0;
szlPage.cy=0;
hpsPrinter=GpiCreatePS(hab,
hdcPrinter,
&szlPage,
PU_TWIPS|GPIT_MICRO|GPIA_ASSOC);
Assuming, of course, that both DevOpenDC and GpiCreatePS succeed, we are
ready to start drawing in hpsPrinter. Any of the Gpi calls can be used
(if GPIT_MICRO is used on GpiCreatePS, then the Gpi calls are restricted
to those that work with micro-PS's.) and we will see the corresponding
output from the printer...
...almost. The painted picture isn't quite complete. In fact, I left a
lot out for simplicity's sake. The first of the two non-existant steps
is...
The DEVOPENSTRUC Structure
--------------------------
Okay, before I said I would explain how to initialize the DEVOPENSTRUC
structure passed to the DevOpenDC call:
typedef struct _DEVOPENSTRUC { /* dop */
PSZ pszLogAddress;
PSZ pszDriverName;
PDRIVDATA pdriv;
PSZ pszDataType;
PSZ pszComment;
PSZ pszQueueProcName;
PSZ pszQueueProcParams;
PSZ pszSpoolerParams;
PSZ pszNetworkParams;
} DEVOPENSTRUC;
typedef DEVOPENSTRUC FAR *PDEVOPENSTRUC;
pszLogAddress - points to the queue name.
pszDriverName - points to the printer driver name.
pdriv - points to the printer driver-specific data to be used for this
job (see below).
pszDataType - points to the device driver data type (Details whether or
not to use "raw" data or "standard" data, the former being ASCII
characters and printer-control codes. This is not desirable when
programming for a device independent environment, so I always use
"PM_Q_STD")
pszComment - points to a comment string to be included as part of the job
details
pszQueueProcName - points to the name of the queue processor to be used.
If NULL, the system default queue processor is used (I always put NULL
here).
pszQueueProcParams - points to the queue processor parameter string if
OD_QUEUED is used (see below).
pszSpoolerParams - points to the spooler parameter string if OD_QUEUED is
used (see below).
pszNetworkParams - points to a network parameter string if OD_QUEUED is
used (I always put NULL here).
Starting with the easiest "see below"'s...
pszSpoolerParams is a string of parameters separated by blanks to be
passed to the spooler. Currently, only the following parameter is
defined:
PRTY=n: - Sets the job priority to 'n'. It must be in
the range 0 (lowest) to 99 (highest).
pszQueueProcParams is a string of parameters separated by blanks to be
passed to the queue print processor. For the PMPRINT processor (PMPLOT
also exists, but I have no experience with it), the following parameters
are defined (taken from the Programming Guide, chapter 37):
COP=n - The number of copies to print. The default is
COP=1.
ARE=C | w,h,l,t - The size and position of the output area.
ARE=C means that the output area is the whole
page, while w,h,l,t can be specified to
restrict this. 'w' is the width, 'h' is the
height, 'l' is the offset from the left of the
maximum area to the left of the output area,
and 't' is the offset from the top of the
maximum area to the top of the output area.
The default is ARE=C.
FIT=S | l,t - The part of the picture that is to be printed
in the output area. FIT=S causes the output to
be scaled until either the width or height
reaches the boundary of the output area,
maintaining the aspect ratio of the output,
while l,t can be specified to print in "real
size". 'l' is the percentage of the output in
the horizontal direction (from the left edge)
that is to be centered (horizontally) in the
output area. 't' is the percentage of the
output in the vertical direction (from the top
edge) that is to be centered (vertically) in
the output area. Thus, "FIT=50,50" means that
the picture is to be centered in the output
area (50 percent from the left and 50 percent
from the top is the center), while "FIT=0,100"
means that the lower left corner is the center
of the output area. The default is "FIT=S".
XFM=0 | 1 - Specifies whether to override the picture
positioning and clipping instructions given by
the ARE and FIT parameters. 0 means use the
output as specified in the picture file while 1
means use the ARE and FIT settings. The
default is "XFM=1" (Author's note: don't
worry, I don't understand what the heck they
are saying here either!).
COL=M | C - Specifies whether or not to use color. M means
create monochrome output and is supported by
all devices. C means create color output. If
the device supports color output, this can be
nice, but if the device does not support color
output, this causes problems since the only
color is black (meaning red lines on a blue
background will become black line on black
background!). The default is "COL=M" if using
a monochrome device, "COL=C" if using a color
device.
MAP=N | A - Specifies how to map neutral colors. N means
create a "normal" reprentation (black on
white), while A means create an "inverted"
representation (white on black). The default
is "MAP=N".
Finally, pdriv points to the printer driver specific data to be used for
this job. This data is obtained using the DevPostDeviceModes call:
LONG APIENTRY DevPostDeviceModes(HAB hab,
PDRIVDATA pdrivDriverData,
PSZ pszDriverName,
PSZ pszDeviceName,
PSZ pszName,
ULONG flOptions);
hab - the anchor block handle of the calling thread
pdrivDriverData - pointer to a buffer to receive the printer driver
specific data (see below).
pszDriverName - points to the name of the printer driver
pszDeviceName - points to the name of the device. For printer drivers
that support only one printer, this is not required.
pszName - points to the printer name.
flOptions - one of the DPDM_* constants.
The action taken depends on both pdrivDriverData and flOptions. If
pdrivDriverData is NULL, then the size of the buffer needed to hold the
driver data is returned. Otherwise, the value of flOptions dictates the
action taken:
DPDM_QUERYJOBPROP - initializes pdrivDriverData with the
current job properties.
DPDM_POSTJOBPROP - displays a dialog box allowing the user
to change the job properties. If
pszName is not NULL, pdrivDriverData is
initialized first with the data from
OS2.INI. Otherwise, the values
contained in pdrivDriverData are used.
DPDM_CHANGEJOBPROP - displays two dialog boxes, the first
allowing the user to change the job
properties and the second allowing the
user to change the printer properties.
pszName cannot be NULL.
Doing this is ***most*** important, especially since printer jobs going
to LAN printers will not work unless pdrivData points to valid data.
We'll use the following code to initialize the data and give the user the
opportunity to change the properties:
ULONG ulSzBuf;
PDRIVDATA pddData;
ulSzBuf=DevPostDeviceModes(hab,
NULL,
"PSCRIPT",
"IBM 4019 v_52 (39 Fonts)",
"LANPRN",
DPDM_QUERYJOBPROP);
DosAllocSeg((SHORT)ulSzBuf,&SELECTOROF(pddData),0);
OFFSETOF(pddData)=0;
DevPostDeviceModes(hab,
pddData,
"PSCRIPT",
"IBM 4019 v_52 (39 Fonts)",
"LANPRN",
DPDM_QUERYJOBPROP);
DevPostDeviceModes(hab,
pddData,
"PSCRIPT",
"IBM 4019 v_52 (39 Fonts)",
"LANPRN",
DPDM_POSTJOBPROP);
Device Independence (Revisited)
-------------------------------
WHOA!!! If OS/2 is so device independent, where do the hard-coded values
come from? Those hard coded values should ideally come from OS2.INI,
since the user can define, change, and delete printer definitions at
will. Given that we can obtain all of the printer names defined to the
system using the PrfQueryProfileString call with
pszAppName=SPL_INI_PRINTER and pszKeyName=NULL (which means enumerate all
keys defined for the application specified), we can obtain the rest of
the information using the following code:
typedef struct {
CHAR achName[64];
CHAR achQueue[64];
CHAR achDriver[64];
CHAR achDevice[64];
CHAR achPort[8];
} PRINTERINFO, FAR *PPRINTERINFO;
BOOL QueryPrinterInfo(PPRINTERINFO ppiInfo)
//-------------------------------------------------------
// This function queries OS2.INI for information about the
// printer who's name is given in the achName field of the
// PRINTERINFO structure pointed to by ppiInfo.
//
// Input: ppiInfo - points to a PRINTERINFO structure with
// achName initialized to the printer
// name.
// Output: ppiInfo - points to a completely initialized
// PRINTERINFO structure
// Returns: TRUE if successful, FALSE otherwise
//
// Written by: Larry Salomon, Jr.
//-------------------------------------------------------
{
ULONG ulSzBuf;
PCHAR pchData;
PCHAR pchPos;
if ((!PrfQueryProfileSize(HINI_PROFILE,
SPL_INI_PRINTER,
ppiInfo->achName,
&ulSzBuf)) || (ulSzBuf==0)) {
return FALSE;
} /* endif */
pchData=(PCHAR)malloc((SHORT)ulSzBuf);
if (pchData==NULL) {
return FALSE;
} /* endif */
ppiInfo->achQueue[0]=0;
ppiInfo->achDriver[0]=0;
ppiInfo->achDevice[0]=0;
ppiInfo->achPort[0]=0;
PrfQueryProfileString(HINI_PROFILE,
SPL_INI_PRINTER,
ppiInfo->achName,
"",
pchData,
ulSzBuf);
pchPos=strchr(pchData,';')+1;
pchPos=strchr(pchPos,';')+1;
*(pchPos+strcspn(pchPos,".,;"))=0;
strcpy(ppiInfo->achQueue,pchPos);
pchPos=strchr(pchData,';')+1;
pchPos+=strcspn(pchPos,".;");
if (*pchPos=='.') {
pchPos++;
*(pchPos+strcspn(pchPos,",;"))=0;
strcpy(ppiInfo->achDevice,pchPos);
} else {
ppiInfo->achDevice[0]=0;
} /* endif */
pchPos=strchr(pchData,';')+1;
*(pchPos+strcspn(pchPos,".,;"))=0;
strcpy(ppiInfo->achDriver,pchPos);
*strchr(pchData,';')=0;
strcpy(ppiInfo->achPort,pchData);
free(pchData);
return TRUE;
}
The achName, achDriver and achDevice fields can then be used in the
DevPostDeviceModes call(s) to obtain the printer driver data to be used
in the DEVOPENSTRUC structure for the DevOpenDC call.
It really is easy, though. Honest.
The Second Non-Existant Step
----------------------------
or
How to Start the Print Job
--------------------------
The final step that I left out way back yonder is actually telling the
spooler that it should ready itself to create the job output. This is
comparitively easy (compared to the DevPostDeviceModes call and
associated stuff) using the DevEscape call.
LONG APIENTRY DevEscape(HDC hdc,
LONG lCode,
LONG lInCount,
PBYTE pbInData,
PLONG plOutCount,
PBYTE pbOutData);
hdc - the device context handle to send the escape code to.
lCode - one of the DEVESC_* constants
lInCount - the size of the data pointed to by pbInData
pbInData - input data which varies according to the DEVESC code
plOutCount - points to the size of the data pointed to by pbOutData.
This is updated with the actual size of the data returned.
pbOutData - output data which varies according to the DEVESC code
While there are many DEVESC_* codes, we are interested in the following
ones only:
DEVESC_STARTDOC - Starts the print job. pbInData points to the
name of the print job and lInCount contains the
and lInCount contains the length of the print
job name.
DEVESC_ENDDOC - Ends the print job. pbOutData points to a
16-bit integer which will contain the job id.
DEVESC_ABORTDOC - Aborts the print job. None of the other
parameters are used.
DEVESC_NEWFRAME - Starts a new page. None of the other
parameters are used.
Miscellany
----------
The final item that I wish to mention is the DevQueryCaps call. This API
allows you to actually query the device capabilities.
BOOL APIENTRY DevQueryCaps(HDC hdc,
LONG lStart,
LONG lCount,
PLONG alArray);
hdc - the device context handle to query.
lStart - the index of the starting capability to query (one of the CAPS_*
constants).
lCount - the number of capabilities to query.
alArray - points to an array of lCount LONGs, to receive the query
results.
There are many CAPS_* constants, the ones I am usually interested in
being:
CAPS_HEIGHT - returns the maximum output height in
pels
CAPS_WIDTH - returns the maximum output width in
pels
CAPS_COLORS - returns the maximum number of colors
the device supports
CAPS_COLOR_PLANES - returns the number of color planes
CAPS_COLOR_BITCOUNT - returns the number of bits per pel used
for color
The information returned for the first three is intuitively obvious, and
the last two we'll see again in part 2.
Wrapping Up
--------------------
After this is all done, we can use the Gpi calls to draw lines, points,
text, and even bit-blit to the presentation space associated with the
printer and see the results with our very own eyes. The order of calls
is:
DevPostDeviceModes(NULL)
DevPostDeviceModes(pddData)
DevOpenDC(...)
GpiCreatePS(...)
DevEscape(DEVESC_STARTDOC);
:
: /* Do your drawing here */
:
if (bError) {
DevEscape(DEVESC_ABORTDOC);
} /* endif */
DevEscape(DEVESC_ENDDOC);
GpiDestroyPS(...)
DevCloseDC(...)
Note that between the start/stop document escapes, you can create/set
logical fonts, change colors (although you might not see any change),
etc. and it works!!!