Amiga® RKM Devices: 10 Printer Device

The printer device offers a way of sending configuration-independent
output to a printer attached to the Amiga. It can be thought of as a
filter: it takes standard commands as input and translates them into
commands understood by the printer. The commands sent to the printer are
defined in a specific printer driver program. For each type of printer in
use, a driver (or the driver of a compatible printer) should be present in
the devs:printers directory.

           Printer Driver Source Code In This Chapter
           ------------------------------------------
   EpsonX          A YMCB, 8 pin, multi-density interleaved printer.
   HP_LaserJet     A black and white, multi-density, page-oriented printer.

 Printer Device Commands and Functions 
 Printer Device Access 
 Device Interface 
 Sending Printer Commands to a Printer 
 Obtaining Printer Specific Data 
 Reading and Changing the Printer Preferences Settings 
 Querying the Printer Device 
 Error Codes from the Printer Device 
 Dumping a Rastport to a Printer 
 Creating a Printer Driver 
 Example Printer Driver Source Code 
 Additional Information on the Printer Device 


10 Printer Device / Printer Device Commands and Functions

Command         Operation
-------         ----------
CMD_FLUSH       Remove all queued requests for the printer device.  Does
                not affect active requests.

CMD_RESET       Reset the printer device to its initialized state.  All
                active and queued I/O requests will be aborted.

CMD_START       Restart all paused I/O requests

CMD_STOP        Pause all active and queued I/O requests.

CMD_WRITE       Write out a stream of characters to the printer device.
                The number of characters can be specified or a
                NULL-terminated string can be sent.

PRD_DUMPRPORT   Dump the specified RastPort to a graphics printer.

PRD_PRTCOMMAND  Send a command to the printer.

PRD_QUERY       Return the status of the printer port's lines and
                registers.

PRD_RAWWRITE    Send unprocessed output to the the printer.


Exec Functions as Used in This Chapter
--------------------------------------
AbortIO()       Abort a command to the printer device.

CloseDevice()   Relinquish use of the printer device.  All requests must
                be complete before closing.

DoIO()          Start a command and wait for completion (synchronous
                request).

OpenDevice()    Obtain use of the printer device.

SendIO()        Start a command and return immediately (asynchronous
                request).

WaitIO()        Wait for the completion of an asynchronous request.  When
                the request is complete, the message will be removed from
                the printer message port.


Exec Support Functions as Used in This Chapter
----------------------------------------------
CreatePort()    Create a signal message port for reply messages from the
                audio device.  Exec will signal a task when a message
                arrives at the reply port.

CreateExtIO()   Create an I/O request structure of type printerIO.  This
                structure will be used to send commands to the printer
                device.

DeletePort()    Delete the message port created by CreatePort().

DeleteExtIO()   Delete an I/O request structure created by CreateExtIO().


10 Printer Device / Printer Device Access

The printer device is totally transparent to an application. It uses
information set up by the Workbench Preferences Printer and PrinterGfx
tools to identify the type of printer connection (serial or parallel),
type of dithering, etc.  It also offers the flexibility to send raw
information to the printer for special non-standard or unsupported
features. Raw data transfer is not recommended for conventional text and
graphics since it will result in applications that will only work with
certain printers. By using the standard printer device interface, an
application can perform device independent output to a printer.

   Don't Hog The Device.
   ---------------------
   The printer device is currently an exclusive access device.  Do not
   tie it up needlessly.

There are two ways of doing output to the printer device:

*  PRT:-the AmigaDOS printer device
   PRT: may be opened just like any other AmigaDOS file. You may send
   standard escape sequences to PRT: to specify the options you want as
   shown in the command table below. The escape sequences are
   interpreted by the printer driver, translated into printer-specific
   escape sequences and forwarded to the printer. When using PRT: the
   escape sequences and data must be sent as a character stream. Using
   PRT: is by far the easiest way of doing text output to a printer.

*  printer.device - to directly access the printer device itself
   By opening the printer device directly, you have full control over
   the printer.  You can either send standard escape sequences as shown
   in the command table below or send raw characters directly to the
   printer with no processing at all. Doing this would be similar to
   sending raw characters to SER: or PAR: from AmigaDOS. (Since this
   interferes with device-independence it is strongly discouraged).
   Direct access to the printer device also allows you to transmit
   device I/O commands, such as reset and flush, and do a raster dump on
   a graphics-capable printer.

   Use A Stream to Escape.
   -----------------------
   All "raw escape sequences" transmitted to the printer through the
   printer device must take the form of a character stream.

 Opening Prt:        Writing To Prt:        Closing Prt: 


10 / Printer Device Access / Opening Prt:

When using the printer device as PRT:, you can open it just as though it
were a normal AmigaDOS output file.

    struct FileHandle *file;

    file = Open( "PRT:", MODE_NEWFILE ); /* Open PRT: */
    if (file == 0)                       /* if the open was unsuccessful */
        exit(PRINTER_WONT_OPEN);


10 / Printer Device Access / Writing To Prt:

Once you've opened it, you can print by calling the AmigaDOS Write()
standard I/O routine.

    actual_length = Write(file, dataLocation, length);

    where

    file is a file handle.

dataLocation
   is a pointer to the first character in the output stream you wish to
   write.  This stream can contain the standard escape sequences as
   shown in the command table below. The printer command aRAW (see the
   Printer Device Command Functions table below) can be used in the
   stream if character translation is not desired.

length
   is the length of the output stream.

actual_length
   is the actual length of the write. For the printer device, if there
   are no errors, this will be the same as the length of write
   requested. The only exception is if you specify a value of -1 for
   length. In this case, -1 for length means that a null (0) terminated
   stream is being written to the printer device. The device returns the
   count of characters written prior to encountering the null.  If it
   returns a value of -1 in actual_length, there has been an error.

   -1 = STOP!
   ----------
   If a -1 is returned by Write(), do not do any additional printing.


10 / Printer Device Access / Closing Prt:

When the printer I/O is complete, you should close PRT:. Don't keep the
device open when you are not using it.  The user may have changed the
printer settings by using the Workbench Preferences tool.  There's also
the possibility the printer has been turned off and on again causing the
printer to switch to its own default settings. Every time the printer
device is opened, it reads the current Preferences settings. Hence, by
always opening the printer device just before printing and always closing
it afterwards, you ensure that your application is using the current
Preferences settings.

Close(file);

   In DOS, You Must Be A Process.
   ------------------------------
   Printer I/O through the DOS must be done by a process, not by a task.
   DOS utilizes information in the process control block and would
   become confused if a simple task attempted to perform these
   activities. Printer I/O using the printer device directly, however,
   can be performed by a task.

The remainder of this chapter will deal with using the printer device
directly.


10 Printer Device / Device Interface

The printer device operates like the other Amiga devices.  To use it, you
must first open the printer device, then send I/O requests to it, and then
close it when finished.  See the Introduction to Amiga Devices chapter
for general information on device usage.

There are three distinct kinds of data structures required by the printer
I/O routines. Some of the printer device I/O commands, such as CMD_START
and CMD_WRITE require only an IOStdReq data structure. Others, such as
PRD_DUMPRPORT and PRD_PRTCOMMAND, require an extended data structure
called IODRPReq (for "Dump a RastPort Request") or IOPrtCmdReq (for
"Printer Command Request").

For convenience, it is strongly recommended that you define a single data
structure called printerIO, that can be used to represent any of the three
pre-defined printer communications request blocks.

    union printerIO
    {
        struct IOStdReq    ios;
        struct IODRPReq    iodrp;
        struct IOPrtCmdReq iopc;
    };

    struct IODRPReq
    {
        struct  Message io_Message;
        struct  Device  *io_Device;     /* device node pointer  */
        struct  Unit    *io_Unit;       /* unit (driver private)*/
        UWORD   io_Command;             /* device command */
        UBYTE   io_Flags;
        BYTE    io_Error;               /* error or warning num */
        struct  RastPort *io_RastPort;  /* raster port */
        struct  ColorMap *io_ColorMap;  /* color map */
        ULONG   io_Modes;               /* graphics viewport modes */
        UWORD   io_SrcX;                /* source x origin */
        UWORD   io_SrcY;                /* source y origin */
        UWORD   io_SrcWidth;            /* source x width */
        UWORD   io_SrcHeight;           /* source x height */
        LONG    io_DestCols;            /* destination x width */
        LONG    io_DestRows;            /* destination y height */
        UWORD   io_Special;             /* option flags */
    };

    struct IOPrtCmdReq
    {
        struct  Message io_Message;
        struct  Device  *io_Device;     /* device node pointer  */
        struct  Unit    *io_Unit;       /* unit (driver private)*/
        UWORD   io_Command;             /* device command */
        UBYTE   io_Flags;
        BYTE    io_Error;               /* error or warning num */
        UWORD   io_PrtCommand;          /* printer command */
        UBYTE   io_Parm0;               /* first command parameter */
        UBYTE   io_Parm1;               /* second command parameter */
        UBYTE   io_Parm2;               /* third command parameter */
        UBYTE   io_Parm3;               /* fourth command parameter */
    };

See the include file exec/io.h for more information on IOStdReq and the
include file devices/printer.h for more information on IODRPReq and
IOPrtCmdReq.

 Opening The Printer Device 
 Writing Text To The Printer Device 
 Important Points About Print Requests 
 Closing The Printer Device 


10 / Device Interface / Opening The Printer Device

Three primary steps are required to open the printer device:

   *  Create a message port using CreatePort().  Reply messages from the
      device must be directed to a message port.

   *  Create an extended I/O request structure of type printerIO with the
      CreateExtIO() function.  This means that one memory area can be used
      to represent three distinct forms of memory layout for the three
      different types of data structures that must be used to pass commands
      to the printer device. By using CreateExtIO(), you automatically
      allocate enough memory to hold the largest structure in the union
      statement.

   *  Open the printer device.  Call OpenDevice(), passing the I/O request.

    union printerIO
    {
        struct IOStdReq    ios;
        struct IODRPReq    iodrp;
        struct IOPrtCmdReq iopc;
    };

    struct MsgPort  *PrintMP;         /* Message port pointer */
    union printerIO *PrintIO;         /* I/O request pointer */

    if (PrintMP=CreateMsgPort() )
      if (PrintIO=(union printerIO *)
                   CreateExtIO(PrintMP,sizeof(union printerIO)) )
         if (OpenDevice("printer.device",0L,(struct IORequest *)PrintIO,0))
              printf("printer.device did not open\n");

The printer device automatically fills in default settings for all printer
device parameters from Preferences.  In addition, information about the
printer itself is placed into the appropriate fields of printerIO. (See
the Obtaining Printer Specific Data section below.)

   Pre-V36 Tasks and OpenDevice().
   -------------------------------
   Tasks in pre-V36 versions of the operating system are not able to
   safely OpenDevice() the printer device because it may be necessary to
   load it in from disk, something only a process could do under
   pre-V36.  V36 and higher versions of the operating system do not have
   such a limitation.


10 / Device Interface / Writing Text To The Printer Device

Text written to a printer can be either processed text or unprocessed text.

Processed text is written to the device using the CMD_WRITE command. The
printer device accepts a character stream, translates any embedded escape
sequences into the proper sequences for the printer being used and then
sends it to the printer.  The escape sequence translation is based on the
printer driver selected either through Preferences or through your
application.  You may also send a NULL-terminated string as processed text.

Unprocessed text is written to the device using the PRD_RAWWRITE command.
The printer device accepts a character stream and sends it unchanged to
the printer.  This implies that you know the exact escape sequences
required by the printer you are using.  You may not send a NULL-terminated
string as unprocessed text.

One additional point to keep in mind when using PRD_RAWWRITE is that
Preference settings for the printer are ignored.  Unless the printer has
already been initialized by another command, the printer's own default
settings will be used when printing raw, not the user's Preferences
settings.

You write processed text to the printer device by passing an IOStdReq to
the device with CMD_WRITE set in io_Command, the number of bytes to be
written set in io_Length and the address of the write buffer set in
io_Data.

To write a NULL-terminated string, set the length to -1; the device will
output from your buffer until it encounters a value of zero (0x00).

    PrintIO->ios.io_Length  = -1;
    PrintIO->ios.io_Data    =
         (APTR)"I went to a fight and a hockey game broke out."
    PrintIO->ios.io_Command = CMD_WRITE;
    DoIO((struct IORequest *)PrintIO);

The length of the request is -1, meaning we are writing a NULL-terminated
string. The number of characters sent will be found in io_Actual after the
write request has completed.

You write unprocessed text to the printer device by passing an IOStdReq to
the device with PRD_RAWWRITE set in io_Command, the number of bytes to be
written set in io_Length and the address of the write buffer set in
io_Data.

    UBYTE *outbuffer;

    PrintIO->ios.io_Length   = strlen(outbuffer);
    PrintIO->ios.io_Data     = (APTR)outbuffer;
    PrintIO->ios.io_Command  = PRD_RAWWRITE;
    DoIO((struct IORequest *)PrintIO);

   IOStdReq Only.
   --------------
   I/O requests with CMD_WRITE and PRD_RAWWRITE must use the IOStdReq
   structure of the union printerIO.


10 / Device Interface / Important Points About Print Requests


*  Perform printer I/O from a separate task or process
   It is quite reasonable for a user to expect that printing will be
   performed as a background operation.  You should try to accommodate
   this expectation as much as possible.

*  Give the user a chance to stop
   Your application should always allow the user to stop a print request
   before it is finished.

*  Don't confuse aborting a print request with cancelling a page
   Some applications seem to offer the user the ability to abort a
   multi-page print request when in fact the abort is only for the
   current page being printed. This results in the next page being
   printed instead of the request being stopped. Do not do this!  It
   only confuses the user and takes away from your application.  There
   is nothing wrong with allowing the user to cancel a page and continue
   to the next page, but it should be explicit that this is the case. If
   you abort a print request, the entire request should be aborted.


10 / Device Interface / Closing The Printer Device

Each OpenDevice() must eventually be matched by a call to CloseDevice().

All I/O requests must be complete before CloseDevice().  If any requests
are still pending, abort them with AbortIO().

   AbortIO(PrintIO);  /* Ask device to abort request, if pending */
   WaitIO(PrintIO);   /* Wait for abort, then clean up */

   CloseDevice((struct IORequest *)PrintIO);

   Use AbortIO() / WaitIO() Intelligently.
   -------------------------------------
   Only call AbortIO()/WaitIO() for requests which have already been
   sent to the printer device.  Using the AbortIO()/WaitIO() sequence on
   requests which have not been sent results in a hung condition.


10 Printer Device / Sending Printer Commands to a Printer

As mentioned before, it is possible to include printer commands (escape
sequences) in the character stream and send them to the printer using the
CMD_WRITE device I/O command. It is also possible to use the printer
command names using the device I/O command PRD_PRTCOMMAND with the
IOPrtCmdReq data structure. This gives you a mnemonic way of setting the
printer to your program needs.

You send printer commands to the device by passing an IOPrtCmdReq to the
device with PRD_PRTCOMMAND set in io_Command, the printer command set in
io_PrtCommand and up to four parameters set in Parm0 through Parm3.

   #include 

   PrintIO->iopc.io_PrtCommand = aSLRM;  /* Set left & right margins */
   PrintIO->iopc.io_Parm0 = 1;           /* Set left margin = 1 */
   PrintIO->iopc.io_Parm1 = 79;          /* Set right margin = 79 */
   PrintIO->iopc.io_Parm2 = 0;
   PrintIO->iopc.io_Parm3 = 0;
   PrintIO->iopc.io_Command = PRD_PRTCOMMAND;
   DoIO((struct IORequest *)PrintIO);

Consult the command function table listed below for other printer commands.

 Printer Command Definitions 


10 / Sending Printer Commands to a Printer / Command Definitions

The following table describes the supported printer functions.

   Just Because We Have It Doesn't Mean You Do.
   --------------------------------------------
   Not all printers support every command.  Unsupported commands will
   either be ignored or simulated using available functions.

To transmit a command to the printer device, you can either formulate a
character stream containing the material shown in the "Escape Sequence"
column of the table below or send an PRD_PRTCOMMAND device I/O command
to the printer device with the "Name" of the function you wish to
perform.

                     PRINTER DEVICE COMMAND FUNCTIONS

           Cmd     Escape                                          Defined
   Name    No.     Sequence   Function                               by:
   ----    ---     --------   --------                             -------
   aRIS    0       ESCc       Reset                                ISO
   aRIN    1       ESC#1      Initialize                           +++
   aIND    2       ESCD       Linefeed                             ISO
   aNEL    3       ESCE       Return,linefeed                      ISO
   aRI     4       ESCM       Reverse linefeed                     ISO
   aSGR0   5       ESC[0m     Normal char set                      ISO
   aSGR3   6       ESC[3m     Italics on                           ISO
   aSGR23  7       ESC[23m    Italics off                          ISO
   aSGR4   8       ESC[4m     Underline on                         ISO
   aSGR24  9       ESC[24m    Underline off                        ISO
   aSGR1   10      ESC[1m     Boldface on                          ISO
   aSGR22  11      ESC[22m    Boldface off                         ISO
   aSFC    12      ESC[nm     Set foreground color where n         ISO
                              stands for a pair of ASCII digits,
                              3 followed by any number 0-9
                              (See ISOColor Table)

   aSBC    13      ESC[nm     Set background color where n         ISO
                              stands for a pair of ASCII digits,
                              4 followed by any number 0-9
                              (See ISO Color Table)

   aSHORP0 14      ESC[0w     Normal pitch                         DEC
   aSHORP2 15      ESC[2w     Elite on                             DEC
   aSHORP1 16      ESC[1w     Elite off                            DEC
   aSHORP4 17      ESC[4w     Condensed fine on                    DEC
   aSHORP3 18      ESC[3w     Condensed off                        DEC
   aSHORP6 19      ESC[6w     Enlarged on                          DEC
   aSHORP5 20      ESC[5w     Enlarged off                         DEC
   aDEN6   21      ESC[6"z    Shadow print on                      DEC
   aDEN5   22      ESC[5"z    Shadow print off            (sort of)DEC
   aDEN4   23      ESC[4"z    Doublestrike on                      DEC
   aDEN3   24      ESC[3"z    Doublestrike off                     DEC
   aDEN2   25      ESC[2"z    NLQ on                               DEC
   aDEN1   26      ESC[1"z    NLQ off                              DEC

   aSUS2   27      ESC[2v     Superscript on                       +++
   aSUS1   28      ESC[1v     Superscript off                      +++
   aSUS4   29      ESC[4v     Subscript on                         +++
   aSUS3   30      ESC[3v     Subscript off                        +++
   aSUS0   31      ESC[0v     Normalize the line                   +++
   aPLU    32      ESCL       Partial line up                      ISO
   aPLD    33      ESCK       Partial line down                    ISO

   aFNT0   34      ESC(B      US char set or Typeface 0            DEC
   aFNT1   35      ESC(R      French char set or Typeface 1        DEC
   aFNT2   36      ESC(K      German char set or Typeface 2        DEC
   aFNT3   37      ESC(A      UK char set or Typeface 3            DEC
   aFNT4   38      ESC(E      Danish I char set or Typeface 4      DEC
   aFNT5   39      ESC(H      Swedish char set or Typeface 5       DEC
   aFNT6   40      ESC(Y      Italian char set or Typeface 6       DEC
   aFNT7   41      ESC(Z      Spanish char set or Typeface 7       DEC
   aFNT8   42      ESC(J      Japanese char set or Typeface 8      +++
   aFNT9   43      ESC(6      Norwegian char set or Typeface 9     DEC
   aFNT10  44      ESC(C      Danish II char set or Typeface 10    +++
                              (See Suggested Typefaces Table)

   aPROP2  45      ESC[2p     Proportional on                      +++
   aPROP1  46      ESC[1p     Proportional off                     +++
   aPROP0  47      ESC[0p     Proportional clear                   +++
   aTSS    48      ESC[n E    Set proportional offset              ISO
   aJFY5   49      ESC[5 F    Auto left justify                    ISO
   aJFY7   50      ESC[7 F    Auto right justify                   ISO
   aJFY6   51      ESC[6 F    Auto full justify                    ISO
   aJFY0   52      ESC[0 F    Auto justify off                     ISO
   aJFY3   53      ESC[3 F    Letter space (justify)      (special)ISO
   aJFY1   54      ESC[1 F    Word fill(auto center)      (special)ISO

   aVERP0  55      ESC[0z     1/8" line spacing                    +++
   aVERP1  56      ESC[1z     1/6" line spacing                    +++
   aSLPP   57      ESC[nt     Set form length n                    DEC
   aPERF   58      ESC[nq     Perf skip n (n>0)                    +++
   aPERF0  59      ESC[0q     Perf skip off                        +++

   aLMS    60      ESC#9      Left margin set                      +++
   aRMS    61      ESC#0      Right margin set                     +++
   aTMS    62      ESC#8      Top margin set                       +++
   aBMS    63      ESC#2      Bottom margin set                    +++
   aSTBM   64      ESC[n;     nr Top and bottom margins            DEC
   aSLRM   65      ESC[n;     ns Left and right margins DEC
   aCAM    66      ESC#3      Clear margins +++

   aHTS    67      ESCH       Set horizontal tab                   ISO
   aVTS    68      ESCJ       Set vertical tabs                    ISO
   aTBC0   69      ESC[0g     Clear horizontal tab                 ISO
   aTBC3   70      ESC[3g     Clear all h. tabs                    ISO
   aTBC1   71      ESC[1g     Clear vertical tab                   ISO
   aTBC4   72      ESC[4g     Clear all v. tabs                    ISO
   aTBCALL 73      ESC#4      Clear all h. & v. tabs               +++
   aTBSALL 74      ESC#5      Set default tabs                     +++
   aEXTEND 75      ESC[n"x    Extended commands                    +++

   aRAW    76      ESC[n"r    Next n chars are raw                 +++

   Legend:
   ------
   ISO     indicates that the sequence has been defined by the
           International Standards Organization.  This is
           also very similar to ANSI x3.64.

   DEC     indicates a control sequence defined by Digital Equipment
           Corporation.

   +++     indicates a sequence unique to Amiga.

   n       stands for a decimal number expressed as a set of ASCII
           digits. In the aRAW string ESC[5"rHELLO, n is substituted by 5,
           the number of RAW characters you send to the printer.

           ISO Color Table            Suggested Typefaces
           ---------------            -------------------
           0  Black                   0   Default typeface
           1  Red                     1   Line Printer or equivalent
           2  Green                   2   Pica or equivalent
           3  Yellow                  3   Elite or equivalent
           4  Blue                    4   Helvetica or equivalent
           5  Magenta                 5   Times Roman or equivalent
           6  Cyan                    6   Gothic or equivalent
           7  White                   7   Script or equivalent
           8  NC                      8   Prestige or equivalent
           9  Default                 9   Caslon or equivalent
                                      10  Orator or equivalent


10 Printer Device / Obtaining Printer Specific Data

Information about the printer in use can be obtained by reading the
PrinterData and PrinterExtendedData structures.  The values found in these
structures are determined by the printer driver selected through
Preferences.  The data structures are defined in devices/prtbase.h.

Printer specific data is returned in printerIO when the printer device is
opened.  To read the structures, you must first set the PrinterData
structure to point to iodrp.io_Device of the printerIO used to open the
device and then set PrinterExtendedData to point to the extended data
portion of PrinterData.

     Printer_Data.c 


10 Printer Device / Reading and Changing Printer Preferences Settings

The user preferences can be read and changed without running the Workbench
Preferences tool.  Reading printer preferences can be done by referring to
PD->pd_Preferences.  Listed on the next page are the printer Preferences
fields and their valid ranges.

Text Preferences
----------------
    PrintPitch        -  PICA, ELITE, FINE
    PrintQuality      -  DRAFT, LETTER
    PrintSpacing      -  SIX_LPI, EIGHT_LPI
    PrintLeftMargin   -  1 to PrintRightMargin
    PrintRightMargin  -  PrintLeftMargin to 999
    PaperLength       -  1 to 999
    PaperSize         -  US_LETTER, US_LEGAL, N_TRACTOR, W_TRACTOR,CUSTOM
    PaperType         -  FANFOLD, SINGLE

Graphic Preferences
-------------------
    PrintImage        -  IMAGE_POSITIVE, IMAGE_NEGATIVE
    PrintAspect       -  ASPECT_HORIZ, ASPECT_VERT
    PrintShade        -  SHADE_BW, SHADE_GREYSCALE, SHADE_COLOR
    PrintThreshold    -  1 to 15
    PrintFlags        -  CORRECT_RED, CORRECT_GREEN, CORRECT_BLUE,
                         CENTER_IMAGE, IGNORE_DIMENSIONS,
                         BOUNDED_DIMENSIONS, ABSOLUTE_DIMENSIONS,
                         PIXEL_DIMENSIONS, MULTIPLY_DIMENSIONS,
                         INTEGER_SCALING, ORDERED_DITHERING,
                         HALFTONE_DITHERING, FLOYD_DITHERING,
                         ANTI_ALIAS, GREY_SCALE2
    PrintMaxWidth     -  0 to 65535
    PrintMaxHeight    -  0 to 65535
    PrintDensity      -  1 to 7
    PrintXOffset      -  0 to 255

This example program changes various settings in the printer device's
copy of preferences.

     Set_Prefs.c 

   Do Your Duty.
   -------------
   The application program is responsible for range checking if the user
   is able to change the preferences from within the application.


10 Printer Device / Querying the Printer Device

The status of the printer port and registers can be determined by querying
the printer device.  The information returned will vary depending on the
type of printer - parallel or serial - selected by the user.  If parallel,
the data returned will reflect the current state of the parallel port; if
serial, the data returned will reflect the current state of the serial
port.

You query the printer device by passing an IOStdReq to the device with
PRD_QUERY set in io_Command and a pointer to a structure to hold the

status set in io_Data.


   struct PStat
   {
       UBYTE LSB;       /* least significant byte of status */
       UBYTE MSB;       /* most significant byte of status */
   };

   union printerIO *PrintIO;

   struct PStat status;

   PrintIO->ios.io_Data = &status;      /* point to status structure */
   PrintIO->ios.io_Command = PRD_QUERY;
   DoIO((struct IORequest *)request);

The status is returned in the two UBYTES set in the io_Data field.  The
printer type, either serial or parallel, is returned in the io_Actual
field.

   io_Data         Bit     Active          Function (Serial Device)
   -------         ---     ------          ------------------------
     LSB           0       low             reserved
                   1       low             reserved
                   2       low             reserved
                   3       low             Data Set Ready
                   4       low             Clear To Send
                   5       low             Carrier Detect
                   6       low             Ready To Send
                   7       low             Data Terminal Ready
     MSB           8       high            read buffer overflow
                   9       high            break sent (most recent output)
                   10      high            break received (as latest input)
                   11      high            transmit x-OFFed
                   12      high            receive x-OFFed
                   13-15   high            reserved

   io_Data         Bit     Active          Function (Parallel Device)
   -------         ---     ------          --------------------------
     LSB           0       high            printer busy (offline)
                   1       high            paper out
                   2       high            printer selected
                   3        -              read=0; write=1
                   4-7                     reserved
     MSB           8-15                    reserved

   io_Actual                               1-parallel, 2-serial


10 Printer Device / Error Codes from the Printer Device

The printer device returns error codes whenever an operation is attempted.
There are two types of error codes that can be returned.  Printer device
error codes have positive values; Exec I/O error codes have negative
values. Therefore, an application should check for a non-zero return code
as evidence of an error, not simply a value greater than zero.

   PrintIO->ios.io_Length   = strlen(outbuffer);
   PrintIO->ios.io_Data     = (APTR)outbuffer;
   PrintIO->ios.io_Command  = PRD_RAWWRITE;
   if (DoIO((struct IORequest *)PrintIO))
       printf("RAW Write failed.  Error: %d ",PrintIO->ios.io_Error);

The error is found in io_Error.

                  PRINTER DEVICE ERROR CODES

   Error                   Value Explanation
   -----                   ----- -----------
   PDERR_NOERR               0   Operation successful
   PDERR_CANCEL              1   User canceled request
   PDERR_NOTGRAPHICS         2   Printer cannot output graphics
   PDERR_INVERTHAM           3   OBSOLETE
   PDERR_BADDIMENSION        4   Print dimensions are illegal
   PDERR_DIMENSIONOVERFLOW   5   OBSOLETE
   PDERR_INTERNALMEMORY      6   No memory available for internal variables
   PDERR_BUFFERMEMORY        7   No memory available for print buffer

                      EXEC ERROR CODES

   Error                   Value Explanation
   ----                    ----- -----------
   IOERR_OPENFAIL           -1   Device failed to open
   IOERR_ABORTED            -2   Request terminated early (after AbortIO())
   IOERR_NOCMD              -3   Command not supported by device
   IOERR_BADLENGTH          -4   Not a valid length


10 Printer Device / Dumping a Rastport to a Printer

You dump a RastPort (drawing area) to a graphics capable printer by
passing an IODRPReq to the device with PRD_DUMPRPORT set in io_Command
along with several parameters that define how the dump is to be rendered.

    union printerIO *PrintIO
    struct RastPort *rastPort;
    struct ColorMap *colorMap;
    ULONG modeid;
    UWORD sx, sy, sw, sh;
    LONG dc, dr;
    UWORD s;

    PrintIO->iodrp.io_RastPort = rastPort; /* pointer to RastPort */
    PrintIO->iodrp.io_ColorMap = colorMap; /* pointer to color map */
    PrintIO->iodrp.io_Modes = modeid;      /* ModeID of ViewPort */
    PrintIO->iodrp.io_SrcX = sx;           /* RastPort X offset */
    PrintIO->iodrp.io_SrcY = sy;           /* RastPort Y offset */
    PrintIO->iodrp.io_SrcWidth = sw;       /* print width from X offset */
    PrintIO->iodrp.io_SrcHeight = sh;      /* print height from Y offset */
    PrintIO->iodrp.io_DestCols = dc;       /* pixel width */
    PrintIO->iodrp.io_DestRows = dr;       /* pixel height */
    PrintIO->iodrp.io_Special = s;         /* flags */
    PrintIO->iodrp.io_Command = PRD_DUMPRPORT;
    SendIO((struct IORequest *)request);

The asynchronous SendIO() routine is used in this example instead of the
synchronous DoIO().  A call to DoIO() does not return until the I/O
request is finished. A call to SendIO() returns immediately.  This allows
your task to do other processing such as checking if the user wants to
abort the I/O request.  It should also be used when writing a lot of text
or raw data with CMD_WRITE and PRD_RAWWRITE.

Here is an overview of the possible arguments for the RastPort dump.

   io_RastPort     A pointer to a RastPort. The RastPort's bitmap could be
                   in Fast memory.

   io_ColorMap     A pointer to a ColorMap. This could be a custom one.
   io_Modes        The viewmode flags or the ModeID returned from
                   GetVPModeID() (V36).

   io_SrcX         X offset in the RastPort to start printing from.
   io_SrcY         Y offset in the RastPort to start printing from.
   io_SrcWidth     Width of the RastPort to print from io_SrcX.
   io_SrcHeight    Height of the RastPort to print from io_SrcY.
   io_DestCols     Width of the dump in printer pixels.
   io_DestRows     Height of the dump in printer pixels.
   io_Special      Flag bits (described below).

Looking at these arguments you can see the enormous flexibility the
printer device offers for dumping a RastPort. The RastPort pointed to
could be totally custom defined. This flexibility means it is possible to
build a BitMap with the resolution of the printer. This would result in
having one pixel of the BitMap correspond to one pixel of the printer. In
other words, only the resolution of the output device would limit the
final result. With 12 bit planes and a custom ColorMap, you could dump
4096 colors - without the HAM limitation - to a suitable printer. The
offset, width and height parameters allow dumps of any desired part of the
picture. Finally the ViewPort mode, io_DestCols, io_DestRows parameters,
together with the io_Special flags define how the dump will appear on
paper and aid in getting the correct aspect ratio.

 Printer Special Flags 
 Printing With Corrected Aspect Ratio 
 Strip Printing 
 Additional Notes About Graphic Dumps 


10 / Dumping a Rastport to a Printer / Printer Special Flags

The printer special flags (io_Flags) of the IODRPReq provide a high degree
of control over the printing of a RastPort.

   SPECIAL_ASPECT       Allows one of the dimensions to be
                        reduced/expanded to preserve the correct
                        aspect ratio of the printout.

   SPECIAL_CENTER       Centers the image between the left and right
                        edge of the paper.

   SPECIAL_NOFORMFEED   Prevents the page from being ejected after
                        a graphics dump.  Usually used to mix graphics and
                        text or multiple graphics dump on a page oriented
                        printer (normally a laser printer).

   SPECIAL_NOPRINT      The print size will be computed, and set
                        in io_DestCols and io_DestRows, but won't print.
                        This way the application can see what the actual
                        printsize in printerpixels would be.

   SPECIAL_TRUSTME      Instructs the printer not to send a reset
                        before and after the dump.  This flag is obsolete
                        for V1.3 (and higher) drivers.

   SPECIAL_DENSITY1-7   This flag bit is set by the user in Preferences.
                        Refer to "Reading and Changing the Printer
                        Preferences Settings" if you want to change to
                        density of the printout. (Or any other setting for
                        that matter.)

   SPECIAL_FULLCOLS     The width is set to the maximum possible,
                        as determined by the printer or the configuration
                        limits.

   SPECIAL_FULLROWS     The height is set to the maximum possible, as
                        determined by the printer or the configuration
                        limits.

   SPECIAL_FRACCOLS     Informs the printer device that the value in
                        io_DestCols is to be taken as a longword binary
                        fraction of the maximum for the dimension.  For
                        example, if io_DestCols is 0x8000, the width
                        would be 1/2 (0x8000 / 0xffff) of the width of
                        the paper.

   SPECIAL_FRACROWS     Informs the printer device that the value in
                        io_DestRows is to be taken as a longword binary
                        fraction for the dimension.

   SPECIAL_MILCOLS      Informs the printer device that the value in
                        io_DestCols is specified in thousandths of an inch.
                        For example, if io_DestCols is 8000, the width of
                        the printout would be 8.000 inches.

   SPECIAL_MILROWS      Informs the printer device that the value in
                        io_DestRows is specified in thousandths of an inch.

The flags are defined in the include file devices/printer.h.


10 / Dumping Rastport to a Printer / Printing Corrected Aspect Ratio

Using the special flags it is fairly easy to ensure a graphic dump will
have the correct aspect ratio on paper. There are some considerations
though when printing a non-displayed RastPort.  One way to get a corrected
aspect ratio dump is to calculate the printer's ratio from XDotsInch and
YDotsInch (taking into account that the printer may not have square
pixels) and then adjust the width and height parameters accordingly. You
then ask for a non-aspect-ratio-corrected dump since you already corrected
it yourself.

Another possibility is having the printer device do it for you. To get a
correct calculation you could build your RastPort dimensions in two ways:


   1. Using an integer multiple of one of the standard (NTSC) display
      resolutions and setting the io_Modes argument accordingly. For
      example if your RastPort dimensions were 1280 x 800 (an even multiple
      of 640 x 400) you would set io_Modes to LACE | HIRES. Setting the
      SPECIAL_ASPECT flag would enable the printer device to properly
      calculate the aspect ratio of the image.

   2. When using an arbitrary sized RastPort, you can supply the ModeID of
      a display mode which has the aspect ratio you would like for your
      RastPort. The aspect ratio of the various display modes are defined
      as ticks-per-pixel in the Resolution field of the DisplayInfo
      structure.  You can obtain this value from the graphics database.
      For example, the resolution of Productivity Mode is 22:22, in other
      words, 1:1, perfect for a RastPort sized to the limits of the output
      device.  See the "Graphics Library" chapters of the  Amiga ROM
      Kernel Reference Manual: Libraries for general information on the
      graphics system.

The following example will dump a RastPort to the printer and wait for
either the printer to finish or the user to cancel the dump and act
accordingly.

     Demo_Dump.c 


10 / Dumping a Rastport to a Printer / Strip Printing

Strip printing is a method which allows you to print a picture that
normally requires a large print buffer when there is not much memory
available. This would allow, for example, a RastPort to be printed at a
higher resolution than it was drawn in. Strip printing is done by creating
a temporary RastPort as wide as the source RastPort, but not as high. The
source RastPort is then rendered, a strip at a time, into the temporary
RastPort which is dumped to the printer.

The height of the strip to dump must be an integer multiple of the
printer's NumRows if a non-aspect-ratio-corrected image is to be printed.

For an aspect-ratio-corrected image, the SPECIAL_NOPRINT flag will have
to be used to find an io_DestRows that is an integer multiple of NumRows.
This can be done by varying the source height and asking for a
SPECIAL_NOPRINT dump until io_DestRows holds a number that is an integer
multiple of the printer's NumRows.

If smoothing is to work with strip printing, a raster line above and below
the actual area should be added. The line above should be the last line
from the previous strip, the line below should be the first line of the
next strip. Of course, the first strip should not have a line added above
and the last strip should not have a line added below.

The following is a strip printing procedure for a RastPort which is 200
lines high.

First strip

   * copy source line 0 through 50 (51 lines) to strip RastPort lines 0
     through 50 (51 lines).
   * io_SrcY = 0, io_Height = 50.
   * the printer device can see there is no line above the first line to
     dump (since SrcY = 0) and that there is a line below the last line to
     dump (since there is a 51 line RastPort and only 50 lines are dumped).

Second strip

   * copy source line 49 through 100 (52 lines) to strip RastPort lines 0
     through 51 (52 lines).
   * io_SrcY = 1, io_Height = 50.
   * the printer device can see there is a line above the first line to
     dump (since SrcY = 1) and that there is a line below the last line to
     dump (since there is a 52 line RastPort and only 50 lines are dumped).

Third strip

   * copy source line 99 through 150 (52 lines) to strip RastPort lines 0
     through 51 (52 lines).
   * io_SrcY = 1, io_Height = 50.
   * the printer device can see there is a line above the first line to
     dump (since SrcY = 1) and that there is a line below the last line to
     dump (since there is a 52 line RastPort and only 50 lines are dumped).

Fourth strip

   * copy source line 149 through 199 (51 lines) to strip RastPort lines 0
     through 50 (51 lines).
   * io_SrcY = 1, io_Height = 50.
   * the printer device can see there is a line above the first line to
     dump (since SrcY = 1) and that there is no line below the last line to
     dump (since there is a 51 line RastPort and only 50 lines are dumped).


10 / Dumping Rastport to a Printer / Additional Notes on Graphic Dumps

   1. When dumping a 1 bitplane image select the black and white mode in
      Preferences. This is much faster than a grey-scale or color dump.

   2. Horizontal dumps are much faster than vertical dumps.

   3. Smoothing doubles the print time.  Use it for final copy only.

   4. F-S dithering doubles the print time.  Ordered and half-tone
      dithering incur no extra overhead.

   5. The lower the density, the faster the printout.

   6. Friction-fed paper tends to be much more accurate than tractor-fed
      paper in terms of vertical dot placement (i.e., less horizontal
      strips or white lines).

   7. Densities which use more than one pass tend to produce muddy
      grey-scale or color printouts.  It is recommended not to choose these
      densities when doing a grey-scale or color dump.

   Keep This in Mind.
   ------------------
   It is possible that the printer has been instructed to receive a
   certain amount of data and is still in an "expecting" state if an
   I/O request has been aborted by the user. This means the printer
   would try to finish the job with the data the next I/O request might
   send. Currently the best way to overcome this problem is for the
   printer to be reset.


10 Printer Device / Creating a Printer Driver

Creating the printer-dependent modules for the printer device involves
writing the data structures and code, compiling and assembling them, and
linking to produce an Amiga binary object file. The modules a driver
contains varies depending on whether the printer is non-graphics or
graphics capable.

All drivers contain these modules:

   macros.i        -  include file for init.asm,contains printer device
                      macro definitions
   printertag.asm  -  printer specific capabilities such as density,
                      character sets and color
   init.asm        -  opens the various libraries required by the printer
                      driver.  This will be the same for all printers
   data.c          -  contains printer device RAW commands and the extended
                      character set supported by the printer
   dospecial.c     -  printer specific special processing required for
                      printer device commands like aSLRM and aSFC

Graphic printer drivers require these additional modules:

   render.c        -  printer specific processing to do graphics output
                      and fill the output buffer
   transfer.c      -  printer specific processing called by render.c
                      to output the buffer to the printer.  Code it in
                      assembly if speed is important
   density.c       -  printer specific processing to construct the proper
                      print density commands

The first piece of the printer driver is the PrinterSegment structure
described in devices/prtbase.h (this is pointed to by the BPTR returned by
the LoadSeg() of the object file). The PrinterSegment contains the
PrinterExtendedData (PED) structures (also described in devices/prtbase.h)
at the beginning of the object.  The PED structure contains data
describing the capabilities of the printer, as well as pointers to code
and other data. Here is the assembly code for a sample PrinterSegment,
which would be linked to the beginning of the sequence of files as
printertag.asm.

     printertag.asm 

The printer name should be the brand name of the printer that is available
for use by programs wishing to be specific about the printer name in any
diagnostic or instruction messages. The four functions at the top of the
structure are used to initialize this printer-dependent code:

   (*(PED->ped_Init))(PD) ;
      This is called when the printer-dependent code is loaded and provides
      a pointer to the printer device for use by the printer-dependent
      code. It can also be used to open up any libraries or devices needed
      by the printer-dependent code.

   (*(PED->ped_Expunge))() ;
      This is called immediately before the printer-dependent code is
      unloaded, to allow it to close any resources obtained at
      initialization time.

   (*(PED->ped_Open))(ior) ;
      This is called in the process of an OpenDevice() call, after the
      Preferences are read and the correct primitive I/O device (parallel
      or serial) is opened.  It must return zero if the open is successful,
      or non-zero to terminate the open and return an error to the user.

   (*(PED->ped_Close))(ior) ;
      This is called in the process of a CloseDevice() call to allow the
      printer-dependent code to close any resources obtained at open time.

The pd_ variable provided as a parameter to the initialization call is a
pointer to the PrinterData structure described in devices/prtbase.h. This
is also the same as the io_Device entry in printer I/O requests.

   pd_SegmentData
      This points back to the PrinterSegment, which contains the PED.

   pd_PrintBuf
      This is available for use by the printer-dependent code - it is not
      otherwise used by the printer device.

   (*pd_PWrite)(data, length);
      This is the interface routine to the primitive I/O device.  This
      routine uses two I/O requests to the primitive device, so writes are
      double-buffered. The data parameter points to the byte data to send,
      and the length is the number of bytes.

   (*pd_PBothReady)();
      This waits for both primitive I/O requests to complete.  This is
      useful if your code does not want to use double buffering.  If you
      want to use the same data buffer for successive pd_PWrites, you must
      separate them with a call to this routine.

   pd_Preferences
      This is the copy of Preferences in use by the printer device,
      obtained when the printer was opened.

The timeout field is the number of seconds that an I/O request from the
printer device to the primitive I/O device (parallel or serial) will
remain posted and unsatisfied before the timeout requester is presented to
the user. The timeout value should be long enough to avoid the requester
during normal printing.

The PrintMode field is a flag which indicates whether text has been
printed or not (1 means printed, 0 means not printed). This flag is used
in drivers for page oriented printers to indicate that there is no
alphanumeric data waiting for a formfeed.

 Writing Alphanumeric Printer Drivers 
 Writing A Graphics Printer Driver 
 Testing The Printer Driver 


10 / Creating a Printer Driver / Writing Alphanumeric Printer Drivers

The alphanumeric portion of the printer driver is designed to convert ANSI
x3.64 style commands into the specific escape codes required by each
individual printer.  For example, the ANSI code for underline-on is
ESC[4m.  The Commodore MPS-1250 printer would like a ESC[-1 to set
underline-on. The HP LaserJet accepts ESC[&dD as a start underline
command. By using the printer driver, all printers may be handled in a
similar manner.

There are two parts to the alphanumeric portion of the printer driver: the
Command Table data table and the DoSpecial() routine.

 Command Table 
 DoSpecial() 
 Printertag.asm 
 Extended Character Table 
 Character Conversion Routine 


10 / / Writing An Alphanumeric Printer Driver / Command Table

The CommandTable is used to convert all escape codes that can be handled
by simple substitution. It has one entry per ANSI command supported by the
printer driver. When you are creating a custom CommandTable, you must
maintain the order of the commands in the same sequence as that shown in
devices/printer.h. By placing the specific codes for your printer in the
proper positions, the conversion takes place automatically.

   Octal knows NULL.
   -----------------
   If the code for your printer requires a decimal 0 (an ASCII NULL
   character), you enter this NULL into the CommandTable as octal 376
   (decimal 254).

Placing an octal value of 377 (255 decimal) in a position in the command
table indicates to the printer device that no simple conversion is
available on this printer for this ANSI command.  For example, if a
daisy-wheel printer does not have a foreign character set, 377 octal (255
decimal) is placed in that position in the command table. However, 377 in
a position can also mean that the ANSI command is to be handled by code
located in the DoSpecial() function. For future compatibility all printer
commands should be present in the command table, and those not supported
by the printer filled with the dummy entry 377 octal.


10 / / Writing An Alphanumeric Printer Driver / DoSpecial()

The DoSpecial() function is meant to implement all the ANSI functions that
cannot be done by simple substitution, but can be handled by a more
complex sequence of control characters sent to the printer. These are
functions that need parameter conversion, read values from Preferences,
and so on. Complete routines can also be placed in dospecial.c. For
instance, in a driver for a page oriented-printer such as the HP LaserJet,
the dummy Close() routine from the init.asm file would be replaced by a
real Close() routine in dospecial.c. This close routine would handle
ejecting the paper after text has been sent to the printer and the printer
has been closed.

The DoSpecial() function is set up as follows:

   #include "exec/types.h"
   #include "devices/printer.h"
   #include "devices/prtbase.h"

   extern struct PrinterData *PD;

   DoSpecial(command,outputBuffer,vline,currentVMI,crlfFlag,Parms)
   UBYTE outputBuffer[];
   UWORD *command;
   BYTE *vline;
   BYTE *currentVMI;
   BYTE *crlfFlag;
   UBYTE Parms[];
   {                /* code begins here... */

   where

   command
      points to the command number. The devices/printer.h file contains the
      definitions for the routines to use (aRIN is initialize, and so on).

   vline
      points to the value for the current line position.

   currentVMI
      points to the value for the current line spacing.

   crlfFlag
      points to the setting of the "add line feed after carriage return"
      flag.

   Parms
      contain whatever parameters were given with the ANSI command.

   outputBuffer
      points to the memory buffer into which the converted command is
      returned.

Almost every printer will require an aRIN (initialize) command in
DoSpecial(). This command reads the printer settings from Preferences and
creates the proper control sequence for the specific printer.  It also
returns the character set to normal (not italicized, not bold, and so on).
Other functions depend on the printer.

Certain functions are implemented both in the CommandTable and in the
DoSpecial() routine. These are functions such as superscript, subscript,
PLU (partial line up), and PLD (partial line down), which can often be
handled by a simple conversion. However, some of these functions must also
adjust the printer device's line-position variable.

   Save the Data!
   --------------
   Some printers lose data when sent their own reset command. For this
   reason, it is recommended that if the printer's own reset command is
   going to be used, PD->pd_PWaitEnabled should be defined to be a
   character that the printer will not print. This character should be
   put in the reset string before and after the reset character(s) in
   the command table.

In the EpsonX[CBM_MPS-1250] DoSpecial() function you'll see

   if (*command == aRIS)
       {        /* reset command */
       PD->pd_PWaitEnabled = \375; /* preserve that data! */
   }

while in the command table the string for reset is defined as
"\375\033@\375". This means that when the printer device outputs the
reset string "\033@", it will first see the "\375", wait a second and
output the reset string. While the printer is resetting, the printer
device gets the second "\375" and waits another second. This ensures that
no data will be lost if a reset command is embedded in a string.


10 / / Writing An Alphanumeric Printer Driver / Printertag.asm

For an alphanumeric printer the printer-specific values that need to be
filled in printertag.asm are as follows:

   MaxColumns
      the maximum number of columns the printer can print across the page.

   NumCharSets
      the number of character sets which can be selected.

   8BitChars
      a pointer to an extended character table. If the field is null, the
      default table will be used.

   ConvFunc
      a pointer to a character conversion routine. If the field is null,no
      conversion routine will be used.


10 / / Writing An Alphanumeric Printer Driver / Extended Character Table

The 8BitChars field could contain a pointer to a table of characters for
the ASCII codes $A0 to $FF. The symbols for these codes are shown in the
IFF Appendix of this manual. If this field contains a NULL, it means
no specific table is provided for the driver, and the default table is to
be used instead.

Care should be taken when generating this table because of the way the
table is parsed by the printer device. Valid expressions in the table
include \011 where 011 is an octal number, \000 for null and \n
where n is a 1 to 3 digit decimal number. To enter an actual backslash in
the table requires the somewhat awkward \\. As an example, here is a
list of the first entries of the EpsonxX[CBM_MPS-1250] table:

   char *ExtendedCharTable[] =
        {
        " ",                       /* NBSP */
        "\033R\007[\033R\0",      /* i */
        "c\010|",                  /* c| */
        "\033R\003#\033R\0",      /* L- */
        "\033R\005$\033R\0",      /* o */
        "\033R\010\\\033R\0",   /* Y- */
        "|",                       /* | */
        "\033R\002@\033R\0",      /* SS */
        "\033R\001~\033R\0",      /* " */
        "c",                       /* copyright */
        "\033S\0a\010_\033T",     /* a_ */
        "<",                       /* << */
        "~",                       /* - */
        "-",                       /* SHY */
        "r",                       /* registered trademark */
        "-",                       /* - */
        /* more entries go here */
   };


10 / / Writing An Alphanumeric Printer Driver / Character Conversion

The ConvFunc field contains a pointer to a character conversion function
that allows you to selectively translate any character to a combination of
other characters. If no translation conversion is necessary (for most
printers it isn't), the field should contain a null.

ConvFunc() arguments are a pointer to a buffer, the character currently
processed, and a CR/LF flag. The ConvFunc() function should return a -1 if
no conversion has been done. If the character is not to be added to the
buffer, a 0 can be returned. If any translation is done, the number of
characters added to the buffer must be returned.

Besides simple character translation, the ConvFunc() function can be used
to add features like underlining to a printer which doesn't support them
automatically. A global flag could be introduced that could be set or
cleared by the DoSpecial() function. Depending on the status of the flag
the ConvFunc() routine could, for example, put the character, a backspace
and an underline character in the buffer and return 3, the number of
characters added to the buffer.

The ConvFunc() function for this could look like the following example:

   #define DO_UNDERLINE   0x01
   #define DO_BOLD        0x02
   /* etc */

   external short myflags;

   int ConvFunc(buffer, c, crlf_flag)
   char *buffer, c;
   int crlf_flag
   {
   int nr_of_chars_added = 0;

   /* for this example we only do this for chars in the 0x20-0x7e range */
   /* Conversion of ESC (0x1b) and CSI (0x9b) is NOT recommended */

   if (c > 0x1f && c < 0x7f)
       {             /* within space - ~ range ? */
       if (myflags & DO_UNDERLINE)
           {
           *buffer++ = c;                /* the character itself */
           *buffer++ = 0x08;             /* a backspace */
           *buffer++ = '_';              /* an underline char */
           nr_of_chars_added = 3;        /* added three chars to buffer */
           }
       if (myflags & DO_BOLD)
           {
           if (nr_of_chars_added)
               {      /* already have added something */
               *buffer++ = 0x08;         /* so we start with backspace */
               ++nr_of_chars_added;      /* and increment the counter */
               }
           *buffer++ = c;
           *buffer++ = 0x08;
           *buffer++ = c;
           ++nr_of_chars_added;
           if (myflags & DO_UNDERLINE)
               {      /* did we do underline too? */
               *buffer++ = 0x08;         /* then backspace again */
               *buffer++ = '_';          /* (printer goes crazy by now) */
               nr_of_chars_added += 2;   /* two more chars */
               }
           }
       }
   if (nr_of_chars_added)
       return(nr_of_chars_added);        /* total nr of chars we added */
   else
       return(-1);                       /* we didn't do anything */
   }

In DoSpecial() the flagbits could be set or cleared, with code like the
following:

   if (*command == aRIS)             /* reset  command */
       myflags = 0;                  /* clear all flags */

   if (*command == aRIN)             /* initialize command */
       myflags = 0;

   if (*command == aSGR0)            /* 'PLAIN' command */
       myflags = 0;

   if (*command == aSGR4)            /* underline on */
       myflags |= DO_UNDERLINE;      /* set underline bit */

   if (*command == aSGR24)           /* underline off */
       myflags &= ~DO_UNDERLINE;     /* clear underline bit */

   if (*command == aSGR1)            /* bold on */
       myflags |= DO_BOLD;           /* set bold bit */

   if (*command == aSGR22)           /* bold off */
       myflags &= ~DO_BOLD;          /* clear bold bit */

Try to keep the expansions to a minimum so that the throughput will not be
slowed down too much, and to reduce the possibility of data overrunning
the printer device buffer.


10 / Creating a Printer Driver / Writing A Graphics Printer Driver

Designing the graphics portion of a custom printer driver consists of two
steps: writing the printer-specific Render(), Transfer() and SetDensity()
functions, and replacing the printer-specific values in printertag.asm.
Render(), Transfer() and SetDensity() comprise render.c, transfer.c, and
density.c modules, respectively.

A printer that does not support graphics has a very simple form of
Render(); it returns an error. Here is sample code for Render() for a
non-graphics printer (such as an Alphacom or Diablo 630):

   #include "exec/types.h"
   #include "devices/printer.h"
   int Render()
   {
       return(PDERR_NOTGRAPHICS);
   }

The following section describes the contents of a typical driver for a
printer that does support graphics.

 Render()      Transfer()      SetDensity()      Printertag.asm 


10 / Writing A Graphics Printer Driver / Render()

This function is the main printer-specific code module and consists of
seven parts referred to here as cases:

   *  Pre-Master initialization (Case 5)

   *  Master initialization (Case 0)

   *  Putting the pixels in a buffer (Case 1)

   *  Dumping a pixel buffer to the printer (Case 2)

   *  Closing down (Case 4)

   *  Clearing and initializing the pixel buffer (Case 3)

   *  Switching to the next color(Case 6)  (special case for multi-color
      printers)

   State Your Case.
   ----------------
   The numbering of the cases reflects the value of each step as a case
   in a C-language switch statement.  It does not denote the order that
   the functions are executed; the order in which they are listed above
   denotes that.

For each case, Render() receives four long variables as parameters: {\tt
ct,} {\tt x,} {\tt y} and {\tt status}.  These parameters are described
below for each of the seven cases that Render() must handle.

Pre-Master initialization (Case 5)
----------------------------------
Parameters:

         ct - 0 or pointer to the IODRPReq structure passed to PCDumpRPort
         x  - io_Special flag from the IODRPReq structure
         y  - 0

When the printer device is first opened, Render() is called with ct set to
0, to give the driver a chance to set up the density values before the
actual graphic dump is called.

The parameter passed in x will be the io_Special flag which contains the
density and other SPECIAL flags. The only flags used at this point are the
DENSITY flags, all others should be ignored. Never call PWrite() during
this case.  When you are finished handling this case, return PDERR_NOERR.

Master initialization (Case 0).
-------------------------------
Parameters:

         ct - pointer to a IODRPReq structure
         x  - width (in pixels) of printed picture
         y  - height (in pixels) of printed picture

   Everything is A-OK.
   -------------------
   At this point the printer device has already checked that the values
   are within range for the printer.  This is done by checking values
   listed in printertag.asm.

The x and y value should be used to allocate enough memory for a command
and data buffer for the printer. If the allocation fails,
PDERR_BUFFERMEMORY should be returned. In general, the buffer needs to be
large enough for the commands and data required for one pass of the print
head. These typically take the following form:

         

The  should contain any special, one-time initializations
that the printer might require such as:

   *  Carriage Return - some printers start printing graphics without
      returning the printhead. Sending a CR assures that printing will
      start from the left edge.

   *  Unidirectional - some printers which have a bidirectional mode
      produce non-matching vertical lines during a graphics dump, giving a
      wavy result. To prevent this, your driver should set the printer to
      unidirectional mode.

   *  Clear margins - some printers force graphic dumps to be done within
      the text margins, thus they should be cleared.

   *  Other commands - enter the graphics mode, set density, etc.

   Multi-Pass? Don't Forget the Memory.
   ------------------------------------
   In addition to the memory for commands and data, a multi-pass color
   printer must allocate enough buffer space for each of the different
   color passes.

The printer should never be reset during the master initialization case
This will cause problems during multiple dumps. Also, the pointer to the
IODRPReq structure in ct should not be used except for those rare printers
which require it to do the dump themselves. Return the PDERR_TOOKCONTROL
error in that case so that the printer device can exit gracefully.

   PDERR_TOOKCONTROL, An Error in Name Only.
   -----------------------------------------
   The printer device error code, PDERR_TOOKCONTROL, is not an error at
   all, but an internal indicator that the printer driver is doing the
   graphic dump entirely on its own. The printer device can assume the
   dump has been done.  The calling application will not be informed of
   this, but will receive PDERR_NOERR instead.

The example render.c functions listed at the end of this chapter use
double buffering to reduce the dump time which is why the AllocMem() calls
are for BUFSIZE times two, where BUFSIZE represents the amount of memory
for one entire print cycle. However, contrary to the example source code,
allocating the two buffers independently of each other is recommended. A
request for one large block of contiguous memory might be refused.  Two
smaller requests are more likely to be granted.

Putting the pixels in a buffer (Case 1).
----------------------------------------
Parameters:

         ct - pointer to a PrtInfo structure.
         x  - PCM color code (if the printer is PCC_MULTI_PASS).
         y  - printer row # (the range is 0 to pixel height - 1).

In this case, you are passed an entire row of YMCB intensity values
(Yellow, Magenta, Cyan, Black).  To handle this case, you call the
Transfer()
PDERR_NOERR after handling this case. The PCM-defines for the x parameter
from the file devices/prtgfx.h are PCMYELLOW, PCMMAGENTA, PCMCYAN and
PCMBLACK.

Dumping a pixel buffer to the printer (Case 2).
-----------------------------------------------
Parameters:

         ct - 0
         x  - 0
         y  - # of rows sent (the range is 1 to NumRows).

At this point the data can be Run Length Encoded (RLE) if your printer
supports it. If the printer doesn't support RLE, the data should be
white-space stripped. This involves scanning the buffer from end to
beginning for the position of the first occurrence of a non-zero value.
Only the data from the beginning of the buffer to this position should be
sent to the printer.  This will significantly reduce print times.

The value of y can be used to advance the paper the appropriate number of
pixel lines if your printer supports that feature. This helps prevent
white lines from appearing between graphic dumps.

You can also do post-processing on the buffer at this point. For
example, if your printer uses the hexadecimal number $03 as a command and
requires the sequence $03 $03 to send $03 as data, you would probably want
to scan the buffer and expand any $03s to $03 $03 during this case. Of
course, you'll need to allocate space somewhere in order to expand the
buffer.

The error from PWrite() should be returned after this call.

Clearing and initializing the pixel buffer (Case 3)
---------------------------------------------------
Parameters:

         ct - 0
         x  - 0
         y  - 0

The printer driver does not send blank pixels so you must initialize
the buffer to the value your printer uses for blank pixels (usually 0).
Clearing the buffer should be the same for all printers. Initializing the
buffer is  printer specific, and it includes placing the printer-specific
control codes in the buffer before and after the data.

This call is made before each Case 2 call. Clear your active print buffer
- remember you are double buffering - and initialize it if necessary.
After this call, PDERR_NOERR should be returned.

Closing Down (Case 4).
----------------------
Parameters:

         ct - error code
         x  - io_Special flag from the IODRPReq structure
         y  - 0

This call is made at the end of the graphic dump or if the graphic dump
was cancelled for some reason.  At this point you should free the printer
buffer memory. You can determine if memory was allocated by checking that
the value of PD->pd_PrintBuf is not NULL. If memory was allocated, you
must wait for the print buffers to clear (by calling PBothReady) and then
deallocate the memory. If the printer - usually a page oriented
printer - requires a page eject command, it can be given here. Before you
do, though, you should check the SPECIAL_NOFORMFEED bit in x. Don't issue
the command if it is set.

If the error condition in ct is PDERR_CANCEL, you should not PWrite().
This error indicates that the user is trying to cancel the dump for
whatever reason. Each additional PWrite() will generate another printer
trouble requester. Obviously, this is not desirable.

During this render case PWrite() could be used to:

   *  reset the line spacing.  If the printer doesn't have an advance 'n'
      dots command, then you'll probably advance the paper by changing the
      line spacing. If you do, set it back to either 6 or 8 lpi (depending
      on Preferences) when you are finished printing.

   *  set bidirectional mode if you selected unidirectional mode in render
      Case 0.

   *  set black text; some printers print the text in the last color used,
      even if it was in graphics mode.

   *  restore the margins if you cancelled the margins in render Case 0.

   *  any other command needed to exit the graphics mode, eject the page,
      etc.

Either PDERR_NOERR or the error from PWrite() should be returned after
this call.

Switching to the next color (Case 6)
------------------------------------
This call provides support for printers which require that colors be sent
in separate passes. When this call is made, you should instruct the
printer to advance its color panel. This case is only needed for printers
of the type PCC_MULTI_PASS, such as the CalComp ColorMaster.


10 / Writing A Graphics Printer Driver / Transfer()

Transfer() dithers and renders an entire row of pixels passed to
it by the Render() function. When Transfer() gets called, it is passed 5
parameters:

Parameters:

          PInfo     - a pointer to a PrtInfo structure
          y         - the row number
          ptr       - a pointer to the buffer
          colors    - a pointer to the color buffers
          BufOffset - the buffer offset for interleaved printing.


The dithering process of Transfer() might entail thresholding, grey-scale
dithering, or color-dithering each destination pixel.

If PInfo->pi_threshold is non-zero, then the dither value is:

       PInfo->pi_threshold \^15

If PInfo->pi_threshold is zero, then the dither value is computed
by:

       *(PInfo->pi_dmatrix + ((y & 3) * 2) + (x & 3))

where x is initialized to PInfo->pi_xpos and is incremented for each of
the destination pixels.  Since the printer device uses a 4x4 dither
matrix, you must calculate the dither value exactly as given above.
Otherwise, your driver will be non-standard and the results will be
unpredictable.

The Transfer() function renders by putting a pixel in the print buffer
based on the dither value. If the intensity value for the pixel is greater
than the dither value as computed above, then the pixel should be put in
the print buffer. If it is less than, or equal to the dither value, it
should be skipped to process the next pixel.

Printer         Type of
Color Class     Dithering       Rendering logic
-----------     ---------       ---------------
PCC_BW          Thresholding    Test the black value against the threshold
                                value to see if you should render a black
                                pixel.

                Grey Scale      Test the black value against the dither
                                value to see if you should render a black
                                pixel.

                Color           NA

PCC_YMC         Thresholding    Test the black value against the
                                threshold value to see if you should render
                                a black pixel.  Print yellow, magenta and
                                cyan pixel to make black.

                Grey Scale      Test the black value against the dither
                                value to see if you should render a black
                                pixel. Print yellow, magenta and cyan pixel
                                to make black.

                Color           Test the yellow value against the dither
                                value to see if you should render a yellow
                                pixel. Repeat this process for magenta and
                                cyan.

PCC_YMCB        Thresholding    Test the black value against the threshold
                                value to see if you should render a black
                                pixel.

                Grey Scale      Test the black value against the dither
                                value to see if you should render a black
                                pixel.

                Color           Test the black value against the dither
                                value to see if you should render a black
                                pixel. If black is not rendered, then
                                test the yellow value against the dither
                                value to see if you should render a yellow
                                pixel.  Repeat this process for magenta and
                                cyan. (See the EpsonX_transfer.c file)

PCC_YMC_BW      Thresholding    Test the black value against the threshold
                                value to see if you should render a black
                                pixel.

                Grey Scale      Test the black value against the dither
                                value to see if you should render a black
                                pixel.

                Color           Test the yellow value against the dither
                                value to see if you should render a yellow
                                pixel. Repeat this process for magenta and
                                cyan.

In general, if black is rendered for a specific printer dot, then the YMC
values should be ignored, since the combination of YMC is black. It is
recommended that the printer buffer be constructed so that the order of
colors printed is yellow, magenta, cyan and black, to prevent smudging and
minimize color contamination on ribbon color printers.

The example transfer.c files are provided in C for demonstration only.
Writing this module in assembler can cut the time needed for a graphic
dump in half. The EpsonX transfer.asm file is an example of this.


10 / Writing A Graphics Printer Driver / SetDensity()

SetDensity() is a short function which implements multiple densities. It
is called in the Pre-master initialization case of the Render() function.
It is passed the density code in density_code. This is used to select the
desired density or, if the user asked for a higher density than is
supported, the maximum density available.  SetDensity() should also handle
narrow and wide tractor paper sizes.

Densities below 80 dpi should not be supported in SetDensity(), so that a
minimum of 640 dots across for a standard 8.5x11-inch paper is guaranteed.
This results in a 1:1 correspondence of dots on the printer to dots on the
screen in standard screen sizes. The HP LaserJet is an exception to this
rule. Its minimum density is 75 dpi because the original HP LaserJet had
too little memory to output a full page at a higher density.


10 / Writing A Graphics Printer Driver / Printertag.asm

For a graphic printer the printer-specific values that need to be filled
in in printertag.asm are as follows:

MaxXDots
   The maximum number of dots the printer can print across the page.

MaxYDots
   The maximum number of dots the printer can print down the page.
   Generally, if the printer supports roll or form feed paper, this
   value should be 0 indicating that there is no limit. If the printer
   has a definite y dots maximum (as a laser printer does), this number
   should be entered here.

XDotsInch
   The dot density in x (supplied by SetDensity(), if it exists).

YDotsInch
   The dot density in y (supplied by SetDensity(), if it exists).

PrinterClass
   The printer class of the printer.

      PPC_BWALPHA     black&white alphanumeric, no graphics.
      PPC_BWGFX       black&white (only) graphics.
      PPC_COLORALPHA  color alphanumeric, no graphics.
      PPC_COLORGFX    color (and maybe black&white) graphics.

ColorClass
   The color class the printer falls into.

      PCC_BW         Black&White only
      PCC_YMC        Yellow Magenta Cyan only.
      PCC_YMC_BW     Yellow Magenta Cyan or Black&White, but not both
      PCC_YMCB       Yellow Magenta Cyan Black
      PCC_WB         White&Black only, 0 is BLACK
      PCC_BGR        Blue Green Red
      PCC_BGR_WB     Blue Green Red or Black&White
      PCC_BGRW       Blue Green Red White

NumRows
   The number of pixel rows printed by one pass of the print head. This
   number is used by the non-printer-specific code to determine when to
   make a render Case 2 call to you. You have to keep this number in
   mind when determining how big a buffer you'll need to store one print
   cycle's worth of data.


10 / Creating a Printer Driver / Testing The Printer Driver

A printer driver should be thoroughly tested before it is released. Though
labor intensive, the alphanumeric part of a driver can be easily tested.
The graphics part is more difficult.  Following are some recommendations
on how to test this part.

Start with a black and white (threshold 8), grey scale and color dump of
the same picture. The color dump should be in color, of course. The grey
scale dump should be like the color dump, except it will consist of
patterns of black dots. The black and white dump will have solid black and
solid white areas.

Next, do a dump with the DestX and DestY dots set to an even multiple of
the XDotsInch and YDotsInch for the printer. For example, if the printer
has a resolution of 120 x 144 dpi, a 480 x 432 dump could be done. This
should produce a printed picture which covers 4 x 3 inches on paper. If
the width of the picture is off, then the wrong value for XDotsInch has
been put in printertag.asm. If the height of the picture is off, the wrong
value for YDotsInch is in printertag.asm.

Do a color dump as wide as the printer can handle with the density set to
7.

Make sure that the printer doesn't force graphic dumps to be done within
the text margins. This can easily be tested by setting the text margins to
30 and 50, the pitch to 10 cpi and then doing a graphic dump wider than 2
inches. The dump should be left justified and as wide as you instructed.
If the dump starts at character position 30 and is cut off at position 50,
the driver will have to be changed. These changes involve clearing the
margins before the dump (Case 0) and restoring the margins after the dump
(Case 4). An example of this is present in render.c source example listed
at the end of this chapter.

   The Invisible Setup.
   --------------------
   Before the graphic dump, some text must be sent to the printer to
   have the printer device initialize the printer. The "text" sent
   does not have to contain any printable characters (i.e., you can send
   a carriage return or other control characters).

As a final test, construct an image with a white background that has
objects in it surrounded by white space. Dump this as black and white,
grey scale and color. This will test the white-space stripping or RLE, and
the ability of the driver to handle null lines. The white data areas
should be separated by at least as many lines of white space as the
NumRows value in the printertag.asm file.


10 Printer Device / Example Printer Driver Source Code

As an aid in writing printer drivers, source code for two different
classes of printers is supplied. Both drivers have been successfully
generated with Lattice C 5.10 and Lattice Assembler 5.10. The example
drivers are:

   EpsonX       A YMCB, 8 pin, multi-density interleaved printer.
   HP_Laserjet  A black&white, multi-density, page-oriented printer.

All printer drivers use the following include file macros.i for init.asm.

     macros.i 

 EpsonX 
 HP_Laserjet 


10 / Example Printer Driver Source Code / EpsonX

For the EpsonX driver, both the assembly and C version of Transfer() are
supplied. In the Makefile the (faster) assembly version is used to
generate the driver.  The EpsonX driver can be generated with the
included Makefile.

     Makefile            init.asm         transfer.asm 
     macros.i            data.c           transfer.c 
     printertag.asm      dospecial.c      density.c 
     rev.i               render.c 


10 / Example Printer Driver Source Code / HP_Laserjet

The driver for the HP_LaserJet can be generated with the following
Makefile.

     Makefile            init.asm         transfer.asm 
     macros.i            data.c           transfer.c 
     printertag.asm      dospecial.c      density.c 
     hp_rev.i            render.c 


10 Printer Device / Additional Information on the Printer Device

Additional programming information on the printer device can be found in
the include files and the Autodocs for the printer device.   Both are
contained in the Amiga ROM Kernel Reference Manual: Includes and Autodocs.


                       Printer Device Information
                    ---------------------------------
                    INCLUDES        devices/printer.h
                                    devices/printer.i
                                    devices/prtbase.h
                                    devices/prtbase.i
                                    devices/prtgfx.h
                                    devices/prtgfx.i

                    AUTODOCS        printer.doc

Additional printer drivers can be found on Fred Fish Disk #344 under
RKMCompanion.


Converted on 22 Apr 2000 with RexxDoesAmigaGuide2HTML 2.1 by Michael Ranner.