Copyright (c) Hyperion Entertainment and contributors.

Printer Device

From AmigaOS Documentation Wiki
Jump to navigation Jump to search
Warning.png This page is not yet fully updated to AmigaOS 4.x some of the information contained here may not be applicable in part or totally.

Contents

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

Command 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.

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:

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

BPTR file;
 
file = IDOS->Open( "PRT:", MODE_NEWFILE );    /* Open PRT: */
if (file == ZERO)                          /* if the open was unsuccessful */
    return PRINTER_WONT_OPEN;

Writing to PRT:

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

actual_length = IDOS->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.
Note
-1 = STOP! If a -1 is returned by Write(), do not do any additional printing.

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.

IDOS->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.

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 Exec Device I/O 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

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 = IExec->AllocSysObjectTags(ASOT_PORT, TAG_END);
if (PrintMP != NULL)
    union printerIO *PrintIO = IExec->AllocSysObjectTags(ASOT_IOREQUEST,
        ASOIOR_Size, sizeof(union printerIO),
        ASOIOR_ReplyPort, PrintMP,
        TAG_END);
 
    if (PrintIO != NULL)
        if (IExec->OpenDevice("printer.device", 0, (struct IORequest *)PrintIO, 0) )
            IDOS->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.)

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;
IExec->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.

uint8 *outbuffer;
 
PrintIO->ios.io_Length   = strlen(outbuffer);
PrintIO->ios.io_Data     = (APTR)outbuffer;
PrintIO->ios.io_Command  = PRD_RAWWRITE;
IExec->DoIO((struct IORequest *)PrintIO);
IOStdReq Only
I/O requests with CMD_WRITE and PRD_RAWWRITE must use the IOStdReq structure of the union printerIO.

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.

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.

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 <devices/printer.h>
 
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;
IExec->DoIO((struct IORequest *)PrintIO);

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

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
Name Cmd
No.
Escape
Sequence
Function Defined by:
aRIS 0 ESCc Reset ISO
aRIN 1 ESC#1 Initialize Amiga
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 stands for a pair of ASCII digits, 3 followed by any number 0-9 (See ISO Color Table) ISO
aSBC 13 ESC[nm Set background color where n stands for a pair of ASCII digits, 4 followed by any number 0-9 (See ISO Color Table) ISO
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 (sort of)
aDEN5 22 ESC[5"z Shadow print off 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 Amiga
aSUS1 28 ESC[1v Superscript off Amiga
aSUS4 29 ESC[4v Subscript on Amiga
aSUS3 30 ESC[3v Subscript off Amiga
aSUS0 31 ESC[0v Normalize the line Amiga
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 Amiga
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) Amiga
aPROP2 45 ESC[2p Proportional on Amiga
aPROP1 46 ESC[1p Proportional off Amiga
aPROP0 47 ESC[0p Proportional clear Amiga
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) ISO (special)
aJFY1 54 ESC[1 F Word fill(auto center) ISO (special)
aVERP0 55 ESC[0z 1/8" line spacing Amiga
aVERP1 56 ESC[1z 1/6" line spacing Amiga
aSLPP 57 ESC[nt Set form length n DEC
aPERF 58 ESC[nq Perf skip n (n>0) Amiga
aPERF0 59 ESC[0q Perf skip off Amiga
aLMS 60 ESC#9 Left margin set Amiga
aRMS 61 ESC#0 Right margin set Amiga
aTMS 62 ESC#8 Top margin set Amiga
aBMS 63 ESC#2 Bottom margin set Amiga
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 Amiga
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 Amiga
aTBSALL 74 ESC#5 Set default tabs Amiga
aEXTEND 75 ESC[n"x Extended commands Amiga
aRAW 76 ESC[n"r Next n chars are raw Amiga

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.
Amiga
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
0 Black
1 Red
2 Green
3 Yellow
4 Blue
5 Magenta
6 Cyan
7 White
8 NC
9 Default
Suggested Typefaces
0 Default typeface
1 Line Printer or equivalent
2 Pica or equivalent
3 Elite or equivalent
4 Helvetica or equivalent
5 Times Roman or equivalent
6 Gothic or equivalent
7 Script or equivalent
8 Prestige or equivalent
9 Caslon or equivalent
10 Orator or equivalent

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

/*
 * Printer_Data.c
 *
 * Example getting driver specifics.
 *
 * Compiled with SAS C 5.10a. lc -cfist -v -L Printer_Data
 *
 * Run from CLI only
 */

#include <exec/types.h>
#include <exec/ports.h>
#include <devices/printer.h>
#include <devices/prtbase.h>

#include <clib/exec_protos.h>
#include <clib/alib_protos.h>
#include <clib/alib_stdio_protos.h>

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

VOID main(VOID);

VOID main(VOID)
{
struct MsgPort  *PrinterMP;
union printerIO *PIO;
struct PrinterData *PD;
struct PrinterExtendedData *PED;

/* Create non-public messageport. Could use CreateMsgPort() for this, that's
 * V37 specific however.
 */
if (PrinterMP = (struct MsgPort *)CreatePort(0,0))
    {
    /* Allocate printerIO union */
    if (PIO = (union printerIO *)CreateExtIO(PrinterMP, sizeof(union printerIO)))
        {
        /* Open the printer.device */
        if (!(OpenDevice("printer.device",0,(struct IORequest *)PIO,0)))
            {

            /* Now that we've got the device opened, let's see what we've got.
             * First, get a pointer to the printer data.
             */
            PD = (struct PrinterData *)PIO->iodrp.io_Device;
            /* And a pointer to the extended data */
            PED = (struct PrinterExtendedData *)&PD->pd_SegmentData->ps_PED;

            /* See the <devices/prtbase.h> include file for more details on
             * the PrinterData and PrinterExtendedData structures.
             */
            printf("Printername: '%s', Version: %ld, Revision: %ld\n",
            PED->ped_PrinterName, PD->pd_SegmentData->ps_Version,
            PD->pd_SegmentData->ps_Revision);

            printf("PrinterClass: 0x%lx, ColorClass: 0x%lx\n",
            PED->ped_PrinterClass, PED->ped_ColorClass);

            printf("MaxColumns: %ld, NumCharSets: %ld, NumRows: %ld\n",
                PED->ped_MaxColumns, PED->ped_NumCharSets,PED->ped_NumRows);

            printf("MaxXDots: %ld, MaxYDots: %ld, XDotsInch: %ld, YDotsInch: %ld\n",
                PED->ped_MaxXDots, PED->ped_MaxYDots, PED->ped_XDotsInch,
                PED->ped_YDotsInch);

            CloseDevice((struct IORequest *)PIO);
            }
         else
             printf("Can't open printer.device\n");
        DeleteExtIO((struct IORequest *)PIO);
        }
    else
        printf("Can't CreateExtIO\n");
    DeletePort((struct MsgPort *)PrinterMP);
    }
else
   printf("Can't CreatePort\n");
}

Reading and Changing the 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 Preference
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 Preference
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

Set_Prefs.c

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

/*
 * Set_Prefs.c
 *
 * This example changes the printer device's COPY of preferences (as obtained
 * when the printer device was opened by a task via OpenDevice()).  Note that
 * it only changes the printer device's copy of preferences, not the preferences
 * as set by the user via the preference editor(s).
 *
 * Compile with SAS C 5.10: LC -b1 -cfistq -v -y -L
 *
 * Run from CLI only
 */

#include <exec/types.h>
#include <devices/printer.h>
#include <devices/prtbase.h>
#include <intuition/intuition.h>
#include <intuition/screens.h>
#include <intuition/preferences.h>

#include <clib/exec_protos.h>
#include <clib/alib_protos.h>
#include <clib/alib_stdio_protos.h>
#include <clib/graphics_protos.h>
#include <clib/intuition_protos.h>

struct Library *IntuitionBase;
struct Library *GfxBase;

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

struct MsgPort *PrintMP;
union printerIO *pio;

char message[] = "\
This is a test message to see how this looks when printed\n\
using various printer settings.\n\n";

VOID main(VOID);
VOID DoPrinter(VOID);
int DoTest(VOID);

VOID main(VOID)
{

if (IntuitionBase = OpenLibrary("intuition.library",0L))
    {
    if (GfxBase = OpenLibrary("graphics.library",0L))
        {
        DoPrinter();
        CloseLibrary(GfxBase);
        }

    CloseLibrary(IntuitionBase);
    }
}

VOID DoPrinter(VOID)
{

if (PrintMP = CreatePort(0L,0L))
    {
    if (pio = (union printerIO *)CreateExtIO(PrintMP,sizeof(union printerIO)))
        {
        if (!(OpenDevice("printer.device",0L,(struct IORequest *)pio,0L)))
            {
            DoTest();
            CloseDevice((struct IORequest *)pio);
            }
        DeleteExtIO((struct IORequest *)pio);
        }
    DeletePort(PrintMP);
    }
}

DoTest(VOID)
{
struct PrinterData *PD;
struct Preferences *prefs;
UWORD newpitch;
UWORD newspacing;

/* Send INIT sequence - make sure printer is inited - some      */
/* printers may eject the current page if necessary when inited */

pio->ios.io_Command = CMD_WRITE;
pio->ios.io_Data = "\033#1";
pio->ios.io_Length = -1L;

if (DoIO((struct IORequest *)pio))
    return(FALSE);

/* Print some text using the default settings from Preferences */

pio->ios.io_Command = CMD_WRITE;
pio->ios.io_Data = message;
pio->ios.io_Length = -1L;

if(DoIO((struct IORequest *)pio))
   return(FALSE);

/* Now try changing some settings
 * Note that we could just as well send the printer.device escape
 * sequences to change these settings, but this is an example program.
 */

/* Get pointer to printer data  */
PD = (struct PrinterData *) pio->ios.io_Device;

/* Get pointer to printer's copy of preferences
 * Note that the printer.device makes a copy of preferences when
 * the printer.device is successfully opened via OpenDevice(),
 * so we are only going to change the COPY of preferences
 */

prefs = &PD->pd_Preferences;


/* Change a couple of settings                          */

if (prefs->PrintSpacing == SIX_LPI)
    newspacing = EIGHT_LPI;
if (prefs->PrintSpacing == EIGHT_LPI)
    newspacing = SIX_LPI;

if (prefs->PrintPitch == PICA)
    newpitch = ELITE;
if (prefs->PrintPitch == ELITE)
    newpitch = FINE;
if (prefs->PrintPitch == FINE)
    newpitch = PICA;

/* And let's change the margins too for this example */

prefs->PrintLeftMargin = 20;
prefs->PrintRightMargin = 40;

prefs->PrintPitch = newpitch;
prefs->PrintSpacing = newspacing;

/* Send INIT sequence so that these settings are used */

pio->ios.io_Command = CMD_WRITE;
pio->ios.io_Data = "\033#1";
pio->ios.io_Length = -1L;

if(DoIO((struct IORequest *)pio))
   return(FALSE);

pio->ios.io_Command = CMD_WRITE;
pio->ios.io_Data = message;
pio->ios.io_Length = -1L;

if(DoIO((struct IORequest *)pio))
   return(FALSE);

/* Send FormFeed so page is ejected  */

pio->ios.io_Command = CMD_WRITE;
pio->ios.io_Data = "\014";
pio->ios.io_Length = -1L;

if(DoIO((struct IORequest *)pio))
   return(FALSE);

return(TRUE);
}
Do Your Duty
The application program is responsible for range checking if the user is able to change the preferences from within the application.

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;
IExec->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 Function (Parallel Device)
1-parallel, 2-serial

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 (IExec->DoIO((struct IORequest *)PrintIO))
    IDOS->Printf("RAW Write failed.  Error: %ld", 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

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().
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 4,096 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

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.

Printing with 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 1,280*800 (an even multiple of 640*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 Graphics Library for general information on the graphics system.

Demo_Dump.c

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
 *
 * Simple example of dumping a rastport to the printer, changing
 * printer preferences programmatically and handling error codes.
 *
 * Compile with SAS C 5.10a. lc -cfist -v -L Demo_Dump
 *
 * Requires Kickstart V37
 * Run from CLI only
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/ports.h>
#include <devices/printer.h>
#include <devices/prtbase.h>
#include <dos/dos.h>
#include <intuition/intuition.h>
#include <intuition/screens.h>
#include <graphics/displayinfo.h>

#include <clib/exec_protos.h>
#include <clib/alib_protos.h>
#include <clib/alib_stdio_protos.h>
#include <clib/graphics_protos.h>
#include <clib/intuition_protos.h>

struct IntuitionBase *IntuitionBase;
struct GfxBase *GfxBase;

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

struct EasyStruct reqES =
{
    sizeof(struct EasyStruct), 0, "DemoDump",
    "%s",
    NULL,
};

/* Possible printer.device and I/O errors */
static UBYTE *ErrorText[] =
{
    "PDERR_NOERR",
    "PDERR_CANCEL",
    "PDERR_NOTGRAPHICS",
    "INVERTHAM",                /* OBSOLETE */
    "BADDIMENSION",
    "DIMENSIONOVFLOW",  /* OBSOLETE */
    "INTERNALMEMORY",
    "BUFFERMEMORY",
    /* IO_ERRs */
    "IOERR_OPENFAIL",
    "IOERR_ABORTED",
    "IOERR_NOCMD",
    "IOERR_BADLENGTH"
};

/* Requester Action text */
static UBYTE *ActionText[] =
{
    "OK|CANCEL",
    "Continue",
    "Abort",
};

#define OKCANCELTEXT 0
#define CONTINUETEXT 1
#define ABORTTEXT    2

VOID main(VOID);

VOID main(VOID)
{
struct MsgPort  *PrinterMP;
union printerIO *PIO;
struct PrinterData *PD;
struct PrinterExtendedData *PED;
struct Screen *pubscreen;
struct ViewPort *vp;
STRPTR textbuffer;
LONG modeID, i,j;
ULONG dcol[5], drow[5];
ULONG signal;

/* Fails silently if not V37 or greater. Nice thing to do would be to put up
 * a V33 requester of course.
 */

/* Set up once */
reqES.es_GadgetFormat = ActionText[CONTINUETEXT];

if (IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 37))
  {
  /* Using graphics.library to get the displaymodeID of the public screen,
   * which we'll pass to the printer.device.
   */
  if (GfxBase = (struct GfxBase *)OpenLibrary("graphics.library", 37))
    {
    if (textbuffer = (STRPTR)AllocMem(256, MEMF_CLEAR))
      {
      /* Create non-public messageport. Since we depend on V37 already, we'll
       * use the new Exec function.
       */
      if (PrinterMP = CreateMsgPort())
        {
        /* Allocate printerIO union */
        if (PIO = (union printerIO *)CreateExtIO(PrinterMP, sizeof(union printerIO)))
          {
          /* Open the printer.device */
          if (!(OpenDevice("printer.device",0,(struct IORequest *)PIO,0)))
            {
            /* Yahoo, we've got it.
             * We'll use the PrinterData structure to get to the the printer
             * preferences later on. The PrinterExtendedData structure will
             * reflect the changes we'll make to the preferences.
             */

            PD = (struct PrinterData *)PIO->iodrp.io_Device;
            PED = (struct PrinterExtendedData *)&PD->pd_SegmentData->ps_PED;

            /* We're all set. We'll grab the default public screen (normally
             * Workbench) and see what happens when we dump it with different
             * densities.
             * Next we'll put up a nice requester for the user and ask if
             * (s)he wants to actually do the dump.
             */

            if (pubscreen = LockPubScreen(NULL))
              {
              vp = &(pubscreen->ViewPort);
              /* Use graphics.library/GetVPModeID() to get the ModeID of the screen. */
              if ((modeID = GetVPModeID(vp)) != INVALID_ID)
                {
                /* Seems we got a valid ModeID for the default public screen (surprise).
                 * Do some fake screen dumps with densities 1, 3, 5 and 7. Depending on
                 * the driver, one or more may be the same.
                 */

                /* Fill in those parts of the IODRPRequest which won't change */
                PIO->iodrp.io_Command = PRD_DUMPRPORT;
                PIO->iodrp.io_RastPort = &(pubscreen->RastPort);
                PIO->iodrp.io_ColorMap = vp->ColorMap;
                PIO->iodrp.io_Modes = modeID;
                PIO->iodrp.io_SrcX = pubscreen->LeftEdge;
                PIO->iodrp.io_SrcY = pubscreen->TopEdge;
                PIO->iodrp.io_SrcWidth = pubscreen->Width;
                PIO->iodrp.io_SrcHeight = pubscreen->Height;

                for (i = 1,j=0; i < 8; i+=2,j++)
                  {
                  /* On return these will contain the actual dump dimension */
                  PIO->iodrp.io_DestCols = 0;
                  PIO->iodrp.io_DestRows = 0;
                  /* We'll simply change our local copy of the
                   * Preferences structure. Likewise we could change
                   * all printer-related preferences.
                   */
                  PD->pd_Preferences.PrintDensity = i;
                  PIO->iodrp.io_Special = SPECIAL_NOPRINT|SPECIAL_ASPECT;

                  /* No need to do asynchronous I/O here */
                  DoIO((struct IORequest *)PIO);

                  if (PIO->iodrp.io_Error == 0)
                    {
                    dcol[j] = PIO->iodrp.io_DestCols;
                    drow[j] = PIO->iodrp.io_DestRows;
                    }
                  else
                    {
                    j = PIO->iodrp.io_Error;
                    if (j < 0)
                      j = j * -1 + 7;

                    sprintf(textbuffer, "Error: %s\n", ErrorText[j]);
                    reqES.es_GadgetFormat = ActionText[CONTINUETEXT];
                    EasyRequest(NULL, &reqES, NULL, textbuffer);
                    break;
                    }
                  }
                /* Simple, lazy way to check if we encountered any problems */
                if (i == 9)
                  {
                  /* Build an 'intelligent' requester */
                  sprintf(textbuffer,
                    "%s: %5ld x %5ld\n%s: %5ld x %5ld\n%s: %5ld x%5ld\n%s: %5ld x %5ld\n\n%s",
                    "Density 1", dcol[0], drow[0],
                    "Density 3", dcol[1], drow[1],
                    "Density 5", dcol[2], drow[2],
                    "Density 7", dcol[3], drow[3],
                    "Print screen at highest density?");
                  reqES.es_GadgetFormat = ActionText[OKCANCELTEXT];

                  /* Obviously the choice presented to the user here is a very
                   * simple one. To print or not to print. In a real life
                   * application, a requester could be presented, inviting
                   * the user to select density, aspect, dithering etc.
                   * The fun part is, of course, that the user can, to a certain
                   * degree, be informed about the effects of her/his selections.
                   */
                  if (EasyRequest(NULL, &reqES, NULL, textbuffer))
                    {
                    /* We've still got the density preference set to the highest
                     * density, so no need to change that.
                     * All we do here is re-initialize io_DestCols/Rows and remove
                     * the SPECIAL_NOPRINT flag from io_Special.
                     */
                    PIO->iodrp.io_DestCols = 0;
                    PIO->iodrp.io_DestRows = 0;
                    PIO->iodrp.io_Special &= ~SPECIAL_NOPRINT;

                    /* Always give the user a change to abort.
                     * So we'll use SendIO(), instead of DoIO(), to be asynch and
                     * catch a possible user request to abort printing. Normally,
                     * the user would be presented with a nice, fat, ABORT requester.
                     * However, since this example doesn't even open a window, and is
                     * basically a 'GraphicDumpDefaultPubscreen' equivalent, we'll use
                     * CTRL-C as the user-abort. Besides that, got to keep it short.
                     */
                    SendIO((struct IORequest *)PIO);

                    /* Now Wait() for either a user signal (CTRL-C) or a signal from
                     * the printer.device
                     */
                    signal = Wait(1 << PrinterMP->mp_SigBit | SIGBREAKF_CTRL_C);

                    if (signal & SIGBREAKF_CTRL_C)
                      {
                      /* User wants to abort */
                      AbortIO((struct IORequest *)PIO);
                      WaitIO((struct IORequest *)PIO);
                      }

                    if (signal & (1 << PrinterMP->mp_SigBit))
                      {
                      /* printer is either ready or an error has occurred */
                      /* Remove any messages */
                      while(GetMsg(PrinterMP));
                      }
                    /* Check for errors (in this case we count user-abort as an error) */
                    if (PIO->iodrp.io_Error != 0)
                      {
                      j = PIO->iodrp.io_Error;
                      if (j < 0)
                        j = j * -1 + 7;
                      sprintf(textbuffer, "Error: %s\n", ErrorText[j]);
                      reqES.es_GadgetFormat = ActionText[CONTINUETEXT];
                      EasyRequest(NULL, &reqES, NULL, textbuffer);
                      }

                    } /* else user doesn't want to print */
                  }
                }
              else
                /* Say what? */
                EasyRequest(NULL, &reqES, NULL, "Invalid ModeID\n");
              UnlockPubScreen(NULL, pubscreen);
              }
            else
              EasyRequest(NULL, &reqES, NULL, "Can't lock Public Screen\n");

            CloseDevice((struct IORequest *)PIO);
            }
          else
            EasyRequest(NULL, &reqES, NULL, "Can't open printer.device\n");

          DeleteExtIO((struct IORequest *)PIO);
          }
        else
          EasyRequest(NULL, &reqES, NULL, "Can't create Extented I/O Request\n");
        DeleteMsgPort(PrinterMP);
        }
      else
        EasyRequest(NULL, &reqES, NULL, "Can't create Message port\n");
      /* else Out of memory? 256 BYTES? */
      FreeMem(textbuffer,256);
      }
    CloseLibrary(GfxBase);
    } /* else MAJOR confusion */
  CloseLibrary(IntuitionBase);
  }
}

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 io_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 io_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).

Forth 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 io_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).

Additional Notes About 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.

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.

**********************************************************************
*
*       printer device dependent code tag
*
*
*
**********************************************************************

        SECTION         printer

*------ Included Files -----------------------------------------------

        INCLUDE         "exec/types.i"
        INCLUDE         "exec/nodes.i"
        INCLUDE         "exec/strings.i"

        INCLUDE         "epsonX_rev.i"  ; contains VERSION & REVISION

        INCLUDE         "devices/prtbase.i"


*------ Imported Names -----------------------------------------------

        XREF            _Init
        XREF            _Expunge
        XREF            _Open
        XREF            _Close
        XREF            _CommandTable
        XREF            _PrinterSegmentData
        XREF            _DoSpecial
        XREF            _Render
        XREF            _ExtendedCharTable

*------ Exported Names -----------------------------------------------

        XDEF            _PEDData

**********************************************************************
        ; in case anyone tries to execute this
        MOVEQ   #0,D0
        RTS

        DC.W    VERSION        ; must be at least 35 (V1.3 and up)
        DC.W    REVISION       ; your own revision number
_PEDData:
        DC.L    printerName    ; pointer to the printer name
        DC.L    _Init          ; pointer to the initialization code
        DC.L    _Expunge       ; pointer to the expunge code
        DC.L    _Open          ; pointer to the open code
        DC.L    _Close         ; pointer to the close code
        DC.B    PPC_COLORGFX   ; PrinterClass
        DC.B    PCC_YMCB       ; ColorClass
        DC.B    136            ; MaxColumns
        DC.B    10             ; NumCharSets
        DC.W    8              ; NumRows
        DC.L    1632           ; MaxXDots
        DC.L    0              ; MaxYDots
        DC.W    120            ; XDotsInch
        DC.W    72             ; YDotsInch
        DC.L    _CommandTable  ; pointer to Command strings
        DC.L    _DoSpecial     ; pointer to Command Code function
        DC.L    _Render        ; pointer to Graphics Render function
        DC.L    30             ; Timeout

        DC.L    _ExtendedCharTable  ; pointer to 8BitChar table

        DS.L    1              ; Flag for PrintMode (reserve space)
        DC.L    0              ; pointer to ConvFunc (char conversion function)

printerName:
        DC.B    'EpsonX'
        DC.B    0
        END

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 an Alphanumeric Printer Driver

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 CommandTable data table and the DoSpecial() routine.

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.

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;

int
DoSpecial(UWORD *command,UBYTE outputBuffer[],BYTE *vline,
          BYTE *currentVMI,BYTE *crlfFlag,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 "375033@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.

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.

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_Standard. 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 */
};

Character Conversion Routine

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(char *buffer, char 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.

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(void)
{
    return(PDERR_NOTGRAPHICS);
}

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

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: ct, x, y and status. These parameters are described below for each of the seven cases that Render() must handle.

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 c 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.

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:

<start gfx cmd> <data> <end gfx cmd>

The <start gfx cmd> 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 , 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.

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() function in the transfer.c module. You should return 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.

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.

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.

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.

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.

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 4*4 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 Color Class Type of 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 -
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.

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.5*11-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.

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).

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.

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

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.

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*144 dpi, a 480*432 dump could be done. This should produce a printed picture which covers 4*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.

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.

**********************************************************************
*
*       printer device macro definitions
*
**********************************************************************

*------ external definition macros -----------------------------------

XREF_EXE        MACRO
        XREF            _LVO\1
                ENDM

XREF_DOS        MACRO
        XREF            _LVO\1
                ENDM

XREF_GFX        MACRO
        XREF            _LVO\1
                ENDM

XREF_ITU        MACRO
        XREF            _LVO\1
                ENDM

*------ library dispatch macros --------------------------------------

CALLEXE         MACRO
                CALLLIB _LVO\1
                ENDM

LINKEXE         MACRO
                LINKLIB _LVO\1,_SysBase
                ENDM

LINKDOS         MACRO
                LINKLIB _LVO\1,_DOSBase
                ENDM

LINKGFX         MACRO
                LINKLIB _LVO\1,_GfxBase
                ENDM

LINKITU         MACRO
                LINKLIB _LVO\1,_IntuitionBase
                ENDM

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 following Makefile.

LC = lc:lc
ASM = lc:asm
CFLAGS = -iINCLUDE: -b0 -d0 -v
ASMFLAGS = -iINCLUDE:
LINK = lc:blink
LIB = lib:lc.lib+lib:amiga.lib
OBJ = printertag.o+init.o+data.o+dospecial.o+render.o+transfer.o+density.o
TARGET = EpsonX

        @$(LC) $(CFLAGS) $*

$(TARGET): printertag.o init.o data.o dospecial.o render.o density.o transfer.o
        @$(LINK) <WITH <
        FROM $(OBJ)
        TO $(TARGET)
        LIBRARY $(LIB)
        NODEBUG SC SD VERBOSE MAP $(TARGET).map H
        <

init.o: init.asm
        @$(ASM) $(ASMFLAGS) init.asm

printertag.o: printertag.asm epsonX_rev.i
        @$(ASM) $(ASMFLAGS) printertag.asm

transfer.o: transfer.asm
        @$(ASM) $(ASMFLAGS) transfer.asm

dospecial.o: dospecial.c

data.o: data.c

density.o: density.c

render.o: render.c

EpsonX: macros.i

**********************************************************************
*
*       printer device macro definitions
*
**********************************************************************

*------ external definition macros -----------------------------------

XREF_EXE        MACRO
        XREF            _LVO\1
                ENDM

XREF_DOS        MACRO
        XREF            _LVO\1
                ENDM

XREF_GFX        MACRO
        XREF            _LVO\1
                ENDM

XREF_ITU        MACRO
        XREF            _LVO\1
                ENDM

*------ library dispatch macros --------------------------------------

CALLEXE         MACRO
                CALLLIB _LVO\1
                ENDM

LINKEXE         MACRO
                LINKLIB _LVO\1,_SysBase
                ENDM

LINKDOS         MACRO
                LINKLIB _LVO\1,_DOSBase
                ENDM

LINKGFX         MACRO
                LINKLIB _LVO\1,_GfxBase
                ENDM

LINKITU         MACRO
                LINKLIB _LVO\1,_IntuitionBase
                ENDM

EpsonX: printertag.asm

**********************************************************************
*
*       printer device dependent code tag
*
**********************************************************************

        SECTION         printer

*------ Included Files -----------------------------------------------

        INCLUDE         "exec/types.i"
        INCLUDE         "exec/nodes.i"
        INCLUDE         "exec/strings.i"

        INCLUDE         "epsonX_rev.i"

        INCLUDE         "devices/prtbase.i"

*------ Imported Names -----------------------------------------------

        XREF            _Init
        XREF            _Expunge
        XREF            _Open
        XREF            _Close
        XREF            _CommandTable
        XREF            _PrinterSegmentData
        XREF            _DoSpecial
        XREF            _Render
        XREF            _ExtendedCharTable

*------ Exported Names -----------------------------------------------

        XDEF            _PEDData

**********************************************************************

                MOVEQ   #0,D0           ; show error for OpenLibrary()
                RTS
                DC.W    VERSION
                DC.W    REVISION
_PEDData:
                DC.L    printerName
                DC.L    _Init
                DC.L    _Expunge
                DC.L    _Open
                DC.L    _Close
                DC.B    PPC_COLORGFX    ;PrinterClass
                DC.B    PCC_YMCB        ; ColorClass
                DC.B    136             ; MaxColumns
                DC.B    10              ; NumCharSets
                DC.W    8               ; NumRows
                DC.L    1632            ; MaxXDots
                DC.L    0               ; MaxYDots
                DC.W    120             ; XDotsInch
                DC.W    72              ; YDotsInch
                DC.L    _CommandTable   ; Commands
                DC.L    _DoSpecial
                DC.L    _Render
                DC.L    30              ; Timeout
                DC.L    _ExtendedCharTable      ; 8BitChars
                DS.L    1               ; PrintMode (reserve space)
                DC.L    0               ; ptr to char conversion function

printerName:
                dc.b    'EpsonX',0

                END

EpsonX: epsonx_rev.i

VERSION         EQU     35
REVISION        EQU     1

EpsonX: init.asm

**********************************************************************
*
*       printer device functions
*
**********************************************************************

        SECTION         printer

*------ Included Files -----------------------------------------------

        INCLUDE         "exec/types.i"
        INCLUDE         "exec/nodes.i"
        INCLUDE         "exec/lists.i"
        INCLUDE         "exec/memory.i"
        INCLUDE         "exec/ports.i"
        INCLUDE         "exec/libraries.i"

        INCLUDE         "macros.i"

*------ Imported Functions -------------------------------------------

        XREF_EXE        CloseLibrary
        XREF_EXE        OpenLibrary
        XREF            _AbsExecBase

        XREF            _PEDData

*------ Exported Globals ---------------------------------------------

        XDEF            _Init
        XDEF            _Expunge
        XDEF            _Open
        XDEF            _Close
        XDEF            _PD
        XDEF            _PED
        XDEF            _SysBase
        XDEF            _DOSBase
        XDEF            _GfxBase
        XDEF            _IntuitionBase


**********************************************************************
        SECTION         printer,DATA
_PD             DC.L    0
_PED            DC.L    0
_SysBase        DC.L    0
_DOSBase        DC.L    0
_GfxBase        DC.L    0
_IntuitionBase  DC.L    0


**********************************************************************
        SECTION         printer,CODE
_Init:
                MOVE.L  4(A7),_PD
                LEA     _PEDData(PC),A0
                MOVE.L  A0,_PED
                MOVE.L  A6,-(A7)
                MOVE.L  _AbsExecBase,A6
                MOVE.L  A6,_SysBase

*           ;------ open the dos library
                LEA     DLName(PC),A1
                MOVEQ   #0,D0
                CALLEXE OpenLibrary
                MOVE.L  D0,_DOSBase
                BEQ     initDLErr


*           ;------ open the graphics library
                LEA     GLName(PC),A1
                MOVEQ   #0,D0
                CALLEXE OpenLibrary
                MOVE.L  D0,_GfxBase
                BEQ     initGLErr

*           ;------ open the intuition library
                LEA     ILName(PC),A1
                MOVEQ   #0,D0
                CALLEXE OpenLibrary
                MOVE.L  D0,_IntuitionBase
                BEQ     initILErr

                MOVEQ   #0,D0
pdiRts:
                MOVE.L  (A7)+,A6
                RTS

initPAErr:
                MOVE.L  _IntuitionBase,A1
                LINKEXE CloseLibrary

initILErr:
                MOVE.L  _GfxBase,A1
                LINKEXE CloseLibrary

initGLErr:
                MOVE.L  _DOSBase,A1
                LINKEXE CloseLibrary

initDLErr:
                MOVEQ   #-1,D0
                BRA.S   pdiRts

ILName:
                DC.B    'intuition.library'
                DC.B    0
DLName:
                DC.B    'dos.library'
                DC.B    0
GLName:
                DC.B    'graphics.library'
                DC.B    0
                DS.W    0


*---------------------------------------------------------------------
_Expunge:
                MOVE.L  _IntuitionBase,A1
                LINKEXE CloseLibrary

                MOVE.L  _GfxBase,A1
                LINKEXE CloseLibrary

                MOVE.L  _DOSBase,A1
                LINKEXE CloseLibrary


*---------------------------------------------------------------------
_Open:
                MOVEQ   #0,D0
                RTS



*---------------------------------------------------------------------
_Close:
                MOVEQ   #0,D0
                RTS

                END

EpsonX: data.c

/*
        Data.c table for EpsonX driver.
*/

char *CommandTable[] ={
        "\375\033@\375",/* 00 aRIS reset                        */
        "\377",         /* 01 aRIN initialize                   */
        "\012",         /* 02 aIND linefeed                     */
        "\015\012",     /* 03 aNEL CRLF                         */
        "\377",         /* 04 aRI reverse LF                    */

                        /* 05 aSGR0 normal char set             */
        "\0335\033-\376\033F",
        "\0334",        /* 06 aSGR3 italics on                  */
        "\0335",        /* 07 aSGR23 italics off                */
        "\033-\001",    /* 08 aSGR4 underline on                */
        "\033-\376",    /* 09 aSGR24 underline off              */
        "\033E",        /* 10 aSGR1 boldface on                 */
        "\033F",        /* 11 aSGR22 boldface off               */
        "\377",         /* 12 aSFC set foreground color         */
        "\377",         /* 13 aSBC set background color         */

                        /* 14 aSHORP0 normal pitch              */
        "\033P\022\033W\376",
                        /* 15 aSHORP2 elite on                  */
        "\033M\022\033W\376",
        "\033P",        /* 16 aSHORP1 elite off                 */
                        /* 17 aSHORP4 condensed fine on         */
        "\017\033P\033W\376",
        "\022",         /* 18 aSHORP3 condensed fine off        */
        "\033W\001",    /* 19 aSHORP6 enlarge on                */
        "\033W\376",    /* 20 aSHORP5 enlarge off               */

        "\377",         /* 21 aDEN6 shadow print on             */
        "\377",         /* 22 aDEN5 shadow print off            */
        "\033G",        /* 23 aDEN4 double strike on            */
        "\033H",        /* 24 aDEN3 double strike off           */
        "\033x\001",    /* 25 aDEN2 NLQ on                      */
        "\033x\376",    /* 26 aDEN1 NLQ off                     */

        "\033S\376",    /* 27 aSUS2 superscript on              */
        "\033T",        /* 28 aSUS1 superscript off             */
        "\033S\001",    /* 29 aSUS4 subscript on                */
        "\033T",        /* 30 aSUS3 subscript off               */
        "\033T",        /* 31 aSUS0 normalize the line          */
        "\377",         /* 32 aPLU partial line up              */
        "\377",         /* 33 aPLD partial line down            */

        "\033R\376",    /* 34 aFNT0 Typeface 0                  */
        "\033R\001",    /* 35 aFNT1 Typeface 1                  */
        "\033R\002",    /* 36 aFNT2 Typeface 2                  */
        "\033R\003",    /* 37 aFNT3 Typeface 3                  */
        "\033R\004",    /* 38 aFNT4 Typeface 4                  */
        "\033R\005",    /* 39 aFNT5 Typeface 5                  */
        "\033R\006",    /* 40 aFNT6 Typeface 6                  */
        "\033R\007",    /* 41 aFNT7 Typeface 7                  */
        "\033R\010",    /* 42 aFNT8 Typeface 8                  */
        "\033R\011",    /* 43 aFNT9 Typeface 9                  */
        "\033R\012",    /* 44 aFNT10 Typeface 10                */

        "\033p1",       /* 45 aPROP2 proportional on            */
        "\033p0",       /* 46 aPROP1 proportional off           */
        "\377",         /* 47 aPROP0 proportional clear         */
        "\377",         /* 48 aTSS set proportional offset      */
        "\377",         /* 49 aJFY5 auto left justify           */
        "\377",         /* 50 aJFY7 auto right justify          */
        "\377",         /* 51 aJFY6 auto full jusitfy           */
        "\377",         /* 52 aJFY0 auto jusity off             */
        "\377",         /* 53 aJFY3 letter space                */
        "\377",         /* 54 aJFY1 word fill                   */

        "\0330",        /* 55 aVERP0 1/8" line spacing          */
        "\0332",        /* 56 aVERP1 1/6" line spacing          */
        "\033C",        /* 57 aSLPP set form length             */
        "\033N",        /* 58 aPERF perf skip n (n > 0)         */
        "\033O",        /* 59 aPERF0 perf skip off              */

        "\377",         /* 60 aLMS set left margin              */
        "\377",         /* 61 aRMS set right margin             */
        "\377",         /* 62 aTMS set top margin               */
        "\377",         /* 63 aBMS set bottom margin            */
        "\377",         /* 64 aSTBM set T&B margins             */
        "\377",         /* 65 aSLRM set L&R margins             */
        "\377",         /* 66 aCAM clear margins                */

        "\377",         /* 67 aHTS set horiz tab                */
        "\377",         /* 68 aVTS set vert tab                 */
        "\377",         /* 69 aTBC0 clear horiz tab             */
        "\033D\376",    /* 70 aTBC3 clear all horiz tabs        */
        "\377",         /* 71 aTBC1 clear vert tab              */
        "\033B\376",    /* 72 aTBC4 clear all vert tabs         */
                        /* 73 aTBCALL clear all h & v tabs      */
        "\033D\376\033B\376",
                        /* 74 aTBSALL set default tabs          */
        "\033D\010\020\030\040\050\060\070\100\110\120\130\376",

        "\377",         /* 75 aEXTEND extended commands         */
        "\377"          /* 76 aRAW next 'n' chars are raw       */
};

/*
   For each character from character 160 to character 255, there is
   an entry in this table, which is used to print (or simulate printing of)
   the full Amiga character set. (see AmigaDos Developer's Manual, pp A-3)
   This table is used only if there is a valid pointer to this table
   in the PEDData table in the printertag.asm file, and the VERSION is
   33 or greater.  Otherwise, a default table is used instead.
   To place non-printable characters in this table, you can either enter
   them as in C strings (ie \011, where 011 is an octal number, or as
   \\000 where 000 is any decimal number, from 1 to 3 digits.  This is
   usually used  to enter a NUL into the array (C has problems with it
   otherwise.), or if you forgot your octal calculator.  On retrospect,
   was a poor choice for this function, as you must say \\\\ to enter a
   backslash as a backslash.  Live and learn...
*/
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 */
        "-",                                    /* - */

        "\033R\001[\033R\\0",                   /* degrees */
        "+\010_",                               /* +_ */
        "\033S\\0002\033T",                     /* 2 */
        "\033S\\0003\033T",                     /* 3 */
        "'",                                    /* ' */
        "u",                                    /* u */
        "P",                                    /* reverse P */
        "\033S\\000.\033T",                     /* . */

        ",",                                    /* , */
        "\033S\\0001\033T",                     /* 1 */
        "\033R\001[\033R\\0\010-",              /* o_ */
        ">",                                    /* >> */
        "\033S\\0001\033T\010-\010\033S\0014\033T",     /* 1/4 */
        "\033S\\0001\033T\010-\010\033S\0012\033T",     /* 1/2 */
        "\033S\\0003\033T\010-\010\033S\0014\033T",     /* 3/4 */
        "\033R\007]\033R\\0",                   /* upside down ? */

        "A\010`",                               /* ``A */
        "A\010'",                               /* 'A */
        "A\010^",                               /* ^A */
        "A\010~",                               /* ~A */
        "\033R\002[\033R\\0",                   /* "A */
        "\033R\004]\033R\\0",                   /* oA */
        "\033R\004[\033R\\0",                   /* AE */
        "C\010,",                               /* C, */

        "E\010`",                               /* ``E */
        "\033R\011@\033R\\0",                   /* 'E */
        "E\010^",                               /* ^E */
        "E\010\033R\001~\033R\\0",              /* "E */
        "I\010`",                               /* ``I */
        "I\010`",                               /* 'I */
        "I\010^",                               /* ^I */
        "I\010\033R\001~\033R\\0",              /* "I */

        "D\010-",                               /* -D */
        "\033R\007\\\\\033R\\0",                /* ~N */
        "O\010`",                               /* ``O */
        "O\010'",                               /* 'O */
        "O\010^",                               /* ^O */
        "O\010~",                               /* ~O */
        "\033R\002\\\\\033R\\0",                /* "O */
        "x",                                    /* x */

        "\033R\004\\\\\033R\\0",                /* 0 */
        "U\010`",                               /* ``U */
        "U\010'",                               /* 'U */
        "U\010^",                               /* ^U */
        "\033R\002]\033R\\0",                   /* "U */
        "Y\010'",                               /* 'Y */
        "T",                                    /* Thorn */
        "\033R\002~\033R\\0",                   /* B */

        "\033R\001@\033R\\0",                   /* ``a */
        "a\010'",                               /* 'a */
        "a\010^",                               /* ^a */
        "a\010~",                               /* ~a */
        "\033R\002{\033R\\0",                   /* "a */
        "\033R\004}\033R\\0",                   /* oa */
        "\033R\004{\033R\\0",                   /* ae */
        "\033R\001\\\\\033R\\0",                /* c, */

        "\033R\001}\033R\\0",                   /* ``e */
        "\033R\001{\033R\\0",                   /* 'e */
        "e\010^",                               /* ^e */
        "e\010\033R\001~\033R\\0",              /* "e */
        "\033R\006~\033R\\0",                   /* ``i */
        "i\010'",                               /* 'i */
        "i\010^",                               /* ^i */
        "i\010\033R\001~\033R\\0",              /* "i */

        "d",                                    /* d */
        "\033R\007|\033R\\0",                   /* ~n */
        "\033R\006|\033R\\0",                   /* ``o */
        "o\010'",                               /* 'o */
        "o\010^",                               /* ^o */
        "o\010~",                               /* ~o */
        "\033R\002|\033R\\0",                   /* "o */
        ":\010-"                                /* :- */

        "\033R\004|\033R\\0",                   /* o/ */
        "\033R\001|\033R\\0",                   /* ``u */
        "u\010'",                               /* 'u */
        "u\010^",                               /* ^u */
        "\033R\002}\033R\\0",                   /* "u */
        "y\010'",                               /* 'y */
        "t",                                    /* thorn */
        "y\010\033R\001~\033R\\0"               /* "y */
};

EpsonX: dospecial.c

/*
        DoSpecial for EpsonX driver.
*/

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

#define LMARG   3
#define RMARG   6
#define MARGLEN 8

#define CONDENSED       7
#define PITCH           9
#define QUALITY         17
#define LPI             24
#define INITLEN         26

DoSpecial(command, outputBuffer, vline, currentVMI, crlfFlag, Parms)
char outputBuffer[];
UWORD *command;
BYTE *vline;
BYTE *currentVMI;
BYTE *crlfFlag;
UBYTE Parms[];
{
        extern struct PrinterData *PD;

        int x = 0, y = 0;
        /*
                00-00   \375    wait
                01-03   \033lL  set left margin
                04-06   \033Qq  set right margin
                07-07   \375    wait
        */

        static char initMarg[MARGLEN+1] = "\375\033lL\033Qq\375";
        /*
                00-01   \0335           italics off
                02-04   \033-\000       underline off
                05-06   \033F           boldface off
                07-07   \022            cancel condensed mode
                08-09   \033P           select pica (10 cpi)
                10-12   \033W\000       enlarge off
                13-14   \033H           doublestrike off
                15-17   \033x\000       draft
                18-19   \033T           super/sub script off
                20-22   \033p0          proportional off
                23-24   \0332           6 lpi
                25-25   \015            carriage return
        */
        static char initThisPrinter[INITLEN+1] =
        "\0335\033-\000\033F\022\033P\033W\000\033H\033x\000\033T\033p0\0332\015";

        static BYTE ISOcolorTable[10] = {0, 5, 6, 4, 3, 1, 2, 0};

        if (*command == aRIN) {
                while (x < INITLEN) {
                        outputBuffer[x] = initThisPrinter[x];
                        x++;
                }

                if (PD->pd_Preferences.PrintQuality == LETTER) {
                        outputBuffer[QUALITY] = 1;
                }

                *currentVMI = 36; /* assume 1/6 line spacing (36/216 => 1/6) */
                if (PD->pd_Preferences.PrintSpacing == EIGHT_LPI) {
                        outputBuffer[LPI] = '0';
                        *currentVMI = 27; /* 27/216 => 1/8 */
                }

                if (PD->pd_Preferences.PrintPitch == ELITE) {
                        outputBuffer[PITCH] = 'M';
                }
                else if (PD->pd_Preferences.PrintPitch == FINE) {
                        outputBuffer[CONDENSED] = '\017'; /* condensed */
                        outputBuffer[PITCH] = 'P'; /* pica condensed */
                }

                Parms[0] = PD->pd_Preferences.PrintLeftMargin;
                Parms[1] = PD->pd_Preferences.PrintRightMargin;
                *command = aSLRM;
        }

        if (*command == aCAM) { /* cancel margins */
                y = PD->pd_Preferences.PaperSize == W_TRACTOR ? 136 : 80;
                if (PD->pd_Preferences.PrintPitch == PICA) {
                        Parms[1] = (10 * y) / 10;
                }
                else if (PD->pd_Preferences.PrintPitch == ELITE) {
                        Parms[1] = (12 * y) / 10;
                }
                else { /* fine */
                        Parms[1] = (17 * y) / 10;
                }
                Parms[0] = 1;
                y = 0;
                *command = aSLRM;
        }

        if (*command == aSLRM) { /* set left and right margins */
                PD->pd_PWaitEnabled = 253;
                if (Parms[0] == 0) {
                        initMarg[LMARG] = 0;
                }
                else {
                        initMarg[LMARG] = Parms[0] - 1;
                }
                initMarg[RMARG] = Parms[1];
                while (y < MARGLEN) {
                        outputBuffer[x++] = initMarg[y++];
                }
                return(x);
        }

        if (*command == aPLU) {
                if (*vline == 0) {
                        *vline = 1;
                        *command = aSUS2;
                        return(0);
                }
                if (*vline < 0) {
                        *vline = 0;
                        *command = aSUS3;
                        return(0);
                }
                return(-1);
        }

        if (*command == aPLD) {
                if (*vline == 0) {
                        *vline = -1;
                        *command = aSUS4;
                        return(0);
                }
                if (*vline > 0) {
                        *vline = 0;
                        *command = aSUS1;
                        return(0);
                }
                return(-1);
        }

        if (*command == aSUS0) {
                *vline = 0;
        }

        if (*command == aSUS1) {
                *vline = 0;
        }

        if (*command == aSUS2) {
                *vline = 1;
        }

        if (*command == aSUS3) {
                *vline = 0;
        }

        if (*command == aSUS4) {
                *vline = -1;
        }

        if (*command == aVERP0) {
                *currentVMI = 27;
        }

        if (*command == aVERP1) {
                *currentVMI = 36;
        }

        if (*command == aIND) { /* lf */
                outputBuffer[x++] = '\033';
                outputBuffer[x++] = 'J';
                outputBuffer[x++] = *currentVMI;
                return(x);
        }

        if (*command == aRI) { /* reverse lf */
                outputBuffer[x++] = '\033';
                outputBuffer[x++] = 'j';
                outputBuffer[x++] = *currentVMI;
                return(x);
        }

        if (*command == aSFC) {
                if (Parms[0] == 39) {
                        Parms[0] = 30; /* set defaults */
                }
                if (Parms[0] > 37) {
                        return(0); /* ni or background color change */
                }
                outputBuffer[x++] = '\033';
                outputBuffer[x++] = 'r';
                outputBuffer[x++] = ISOcolorTable[Parms[0] - 30];
                /*
                Kludge to get this to work on a CBM_MPS-1250  which interprets
                'ESCr' as go into reverse print mode.  The 'ESCt' tells it to
                get out of reverse print mode.  The 'NULL' is ignored by the
                CBM_MPS-1250 and required by all Epson printers as the
                terminator for the 'ESCtNULL' command which means select
                normal char set (which has no effect).
                */
                outputBuffer[x++] = '\033';
                outputBuffer[x++] = 't';
                outputBuffer[x++] = 0;
                return(x);
        }

        if (*command == aRIS) {
                PD->pd_PWaitEnabled = 253;
        }

        return(0);
}

EpsonX: render.c

/*
        EpsonX (EX/FX/JX/LX/MX/RX) driver.
*/

#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <exec/memory.h>
#include "devices/printer.h"
#include "devices/prtbase.h"

#define NUMSTARTCMD     7       /* # of cmd bytes before binary data */
#define NUMENDCMD       1       /* # of cmd bytes after binary data */
#define NUMTOTALCMD     (NUMSTARTCMD + NUMENDCMD)       /* total of above */
#define NUMLFCMD        4       /* # of cmd bytes for linefeed */
#define MAXCOLORBUFS    4       /* max # of color buffers */

#define STARTLEN        19
#define PITCH           1
#define CONDENSED       2
#define LMARG           8
#define RMARG           11
#define DIREC           15

static ULONG TwoBufSize;
static UWORD RowSize, ColorSize, NumColorBufs, dpi_code, spacing;
static UWORD colorcodes[MAXCOLORBUFS];

Render(ct, x, y, status)
long ct, x, y, status;
{
        extern void *AllocMem(), FreeMem();

        extern struct PrinterData *PD;
        extern struct PrinterExtendedData *PED;

        UBYTE *CompactBuf();
        static ULONG BufSize, TotalBufSize, dataoffset;
        static UWORD spacing, colors[MAXCOLORBUFS];
        /*
                00-01   \003P           set pitch (10 or 12 cpi)
                02-02   \022            set condensed fine (on or off)
                03-05   \033W\000       enlarge off
                06-08   \033ln          set left margin to n
                09-11   \033Qn          set right margin to n
                12-12   \015            carriage return
                13-15   \033U1          set uni-directional mode
                16-18   \033t\000       see kludge note below
                Kludge to get this to work on a CBM_MPS-1250  which interprets
                'ESCr' as go into reverse print mode.  The 'ESCt' tells it to
                get out of reverse print mode.  The 'NULL' is ignored by the
                CBM_MPS-1250 and required by all Epson printers as the
                terminator for the 'ESCtNULL' command which means select
                normal char set (which has no effect).
        */

        static UBYTE StartBuf[STARTLEN+1] =
                "\033P\022\033W\000\033ln\033Qn\015\033U1\033t\000";

        UBYTE *ptr, *ptrstart;
        int err;

        switch(status) {
                case 0 : /* Master Initialization */
                        /*
                                ct      - pointer to IODRPReq structure.
                                x       - width of printed picture in pixels.
                                y       - height of printed picture in pixels.
                        */
                        RowSize = x;
                        ColorSize = RowSize + NUMTOTALCMD;
                        if (PD->pd_Preferences.PrintShade == SHADE_COLOR) {
                                NumColorBufs = MAXCOLORBUFS;
                                colors[0] = ColorSize * 3; /* Black */
                                colors[1] = ColorSize * 0; /* Yellow */
                                colors[2] = ColorSize * 1; /* Magenta */
                                colors[3] = ColorSize * 2; /* Cyan */
                                colorcodes[0] = 4; /* Yellow */
                                colorcodes[1] = 1; /* Magenta */
                                colorcodes[2] = 2; /* Cyan */
                                colorcodes[3] = 0; /* Black */
                        }
                        else { /* grey-scale or black&white */
                                NumColorBufs = 1;
                                colors[0] = ColorSize * 0; /* Black */
                                colorcodes[0] = 0; /* Black */
                        }
                        BufSize = ColorSize * NumColorBufs + NUMLFCMD;
                        if (PED->ped_YDotsInch == 216) {
                                TwoBufSize = BufSize * 3;
                                TotalBufSize = BufSize * 6;
                        }
                        else if (PED->ped_YDotsInch == 144) {
                                TwoBufSize = BufSize * 2;
                                TotalBufSize = BufSize * 4;
                        }
                        else {
                                TwoBufSize = BufSize * 1;
                                TotalBufSize = BufSize * 2;
                        }
                        PD->pd_PrintBuf = AllocMem(TotalBufSize, MEMF_PUBLIC);
                        if (PD->pd_PrintBuf == NULL) {
                                err = PDERR_BUFFERMEMORY;
                        }
                        else {
                                dataoffset = NUMSTARTCMD;
                                /*
                                        This printer prints graphics within its
                                        text margins.  This code makes sure the
                                        printer is in 10 cpi and then sets the
                                        left and right margins to their minimum
                                        and maximum values (respectively).  A
                                        carriage return is sent so that the
                                        print head is at the leftmost position
                                        as this printer starts printing from
                                        the print head's position.  The printer
                                        is put into unidirectional mode to
                                        reduce wavy vertical lines.
                                */
                                StartBuf[PITCH] = 'P'; /* 10 cpi */
                                StartBuf[CONDENSED] = '\022'; /* off */
                                /* left margin of 1 */
                                StartBuf[LMARG] = 0;
                                /* right margin of 80 or 136 */
                                StartBuf[RMARG] = PD->pd_Preferences.
                                        PaperSize == W_TRACTOR ? 136 : 80;
                                /* uni-directional mode */
                                StartBuf[DIREC] = '1';
                                err = (*(PD->pd_PWrite))(StartBuf, STARTLEN);
                        }
                        break;

                case 1 : /* Scale, Dither and Render */
                        /*
                                ct      - pointer to PrtInfo structure.
                                x       - 0.
                                y       - row # (0 to Height - 1).
                        */
                        Transfer(ct, y, &PD->pd_PrintBuf[dataoffset], colors,
                                BufSize);
                        err = PDERR_NOERR; /* all ok */
                        break;

                case 2 : /* Dump Buffer to Printer */
                        /*
                                ct      - 0.
                                x       - 0.
                                y       - # of rows sent (1 to NumRows).

                        */
                        /* white-space strip */
                        ptrstart = &PD->pd_PrintBuf[dataoffset - NUMSTARTCMD];
                        if (PED->ped_YDotsInch == 72) {
                                /* y range : 1 to 8 */
                                y = y * 3 - spacing;
                                ptr = CompactBuf(ptrstart + NUMSTARTCMD,
                                        ptrstart, y, 1);
                        }
                        else if (PED->ped_YDotsInch == 144) {
                                /* y range : 1 to 16 */
                                ptr = CompactBuf(ptrstart + NUMSTARTCMD,
                                        ptrstart, 2, 1);
                                if (y > 1) {
                                        ptr = CompactBuf(&PD->pd_PrintBuf[
                                                dataoffset + BufSize],
                                                ptr, y * 3 / 2 - 2, 0);
                                }
                        }
                        else if (PED->ped_YDotsInch == 216) {
                                /* y range : 1 to 24 */
                                ptr = CompactBuf(ptrstart + NUMSTARTCMD,
                                        ptrstart, 1, 1);
                                if (y > 1) {
                                        ptr = CompactBuf(&PD->pd_PrintBuf[
                                                dataoffset + BufSize],
                                                ptr, 1, 0);
                                }
                                if (y > 2) {
                                        ptr = CompactBuf(&PD->pd_PrintBuf[
                                                dataoffset + BufSize * 2],
                                                ptr, y - 2, 0);
                                }
                        }
                        err = (*(PD->pd_PWrite))(ptrstart, ptr - ptrstart);
                        if (err == PDERR_NOERR) {
                                dataoffset = (dataoffset == NUMSTARTCMD ?
                                        TwoBufSize : 0) + NUMSTARTCMD;
                        }
                        break;

                case 3 : /* Clear and Init Buffer */
                        /*
                                ct      - 0.
                                x       - 0.
                                y       - 0.
                        */
                        ClearAndInit(&PD->pd_PrintBuf[dataoffset]);
                        err = PDERR_NOERR;
                        break;

                case 4 : /* Close Down */
                        /*
                                ct      - error code.
                                x       - io_Special flag from IODRPReq.
                                y       - 0.
                        */
                        err = PDERR_NOERR; /* assume all ok */
                        /* if user did not cancel print */
                        if (ct != PDERR_CANCEL) {
                                /* restore preferences pitch and margins */
                                if (PD->pd_Preferences.PrintPitch == ELITE) {
                                        StartBuf[PITCH] = 'M'; /* 12 cpi */
                                }
                                else if (PD->pd_Preferences.PrintPitch == FINE) {
                                        StartBuf[CONDENSED] = '\017'; /* on */
                                }
                                StartBuf[LMARG] =
                                        PD->pd_Preferences.PrintLeftMargin - 1;
                                StartBuf[RMARG] =
                                        PD->pd_Preferences.PrintRightMargin;
                                StartBuf[DIREC] = '0'; /* bi-directional */
                                err = (*(PD->pd_PWrite))(StartBuf, STARTLEN);
                        }
                        (*(PD->pd_PBothReady))();
                        if (PD->pd_PrintBuf != NULL) {
                                FreeMem(PD->pd_PrintBuf, TotalBufSize);
                        }
                        break;

                case 5 :  /* Pre-Master Initialization */
                        /*
                                ct      - 0 or pointer to IODRPReq structure.
                                x       - io_Special flag from IODRPReq.
                                y       - 0.
                        */
                        /* kludge for sloppy tractor mechanism */
                        spacing = PD->pd_Preferences.PaperType == SINGLE ?
                                1 : 0;
                        dpi_code = SetDensity(x & SPECIAL_DENSITYMASK);
                        err = PDERR_NOERR;
                        break;
        }
        return(err);
}


UBYTE *CompactBuf(ptrstart, ptr2start, y, flag)
UBYTE *ptrstart, *ptr2start;
long y;
int flag; /* 0 - not first pass, !0 - first pass */
{
        static int x;
        UBYTE *ptr, *ptr2;
        long ct;
        int i;

        ptr2 = ptr2start; /* where to put the compacted data */
        if (flag) {
                x = 0; /* flag no transfer required yet */
        }

        for (ct=0; ct<NumColorBufs; ct++, ptrstart += ColorSize) {
                i = RowSize;
                ptr = ptrstart + i - 1;
                while (i > 0 && *ptr == 0) {
                        i--;
                        ptr--;
                }

                if (i != 0) { /* if data */
                        *(++ptr) = 13;                  /* <cr> */
                        ptr = ptrstart - NUMSTARTCMD;
                        *ptr++ = 27;
                        *ptr++ = 'r';
                        *ptr++ = colorcodes[ct];        /* color */
                        *ptr++ = 27;
                        *ptr++ = dpi_code;              /* density */
                        *ptr++ = i & 0xff;
                        *ptr++ = i >> 8;                /* size */
                        i += NUMTOTALCMD;
                        if (x != 0) { /* if must transfer data */
                                /* get src start */
                                ptr = ptrstart - NUMSTARTCMD;
                                do { /* transfer and update dest ptr */
                                        *ptr2++ = *ptr++;
                                } while (--i);
                        }

                        else { /* no transfer required */
                                ptr2 += i; /* update dest ptr */
                        }
                }

                if (i != RowSize + NUMTOTALCMD) { /* if compacted or 0 */
                        x = 1; /* flag that we need to transfer next time */
                }
        }

        *ptr2++ = 13; /* cr */
        *ptr2++ = 27;
        *ptr2++ = 'J';
        *ptr2++ = y; /* y/216 lf */
        return(ptr2);
}


ClearAndInit(ptr)
UBYTE *ptr;
{
        ULONG *lptr, i, j;

        /*
                Note : Since 'NUMTOTALCMD + NUMLFCMD' is > 3 bytes if is safe
                to do the following to speed things up.
        */
        i = TwoBufSize - NUMTOTALCMD - NUMLFCMD;
        j = (ULONG)ptr;
        if (!(j & 1)) { /* if on a word boundary, clear by longs */
                i = (i + 3) / 4;
                lptr = (ULONG *)ptr;
                do {
                        *lptr++ = 0;
                } while (--i);
        }
        else { /* clear by bytes */
                do {
                        *ptr++ = 0;
                } while (--i);
        }
        return(0);
}

EpsonX: transfer.asm

**********************************************************************
*
* Transfer routine for EpsonX
*
**********************************************************************

        INCLUDE "exec/types.i"

        INCLUDE "intuition/intuition.i"
        INCLUDE "devices/printer.i"
        INCLUDE "devices/prtbase.i"
        INCLUDE "devices/prtgfx.i"

        XREF    _PD
        XREF    _PED
        XREF    _LVODebug
        XREF    _AbsExecBase

        XDEF    _Transfer

        SECTION         printer,CODE
_Transfer:
; Transfer(PInfo, y, ptr, colors, BufOffset)
; struct PrtInfo *PInfo         4-7
; UWORD y;                      8-11
; UBYTE *ptr;                   12-15
; UWORD *colors;                16-19
; ULONG BufOffset               20-23

        movem.l d2-d6/a2-a4,-(sp)       ;save regs used

        movea.l 36(sp),a0               ;a0 = PInfo
        move.l  40(sp),d0               ;d0 = y
        movea.l 44(sp),a1               ;a1 = ptr
        movea.l 48(sp),a2               ;a2 = colors
        move.l  52(sp),d1               ;d1 = BufOffset

        move.l  d0,d3                   ;save y
        moveq.l #3,d2
        and.w   d0,d2                   ;d2 = y & 3
        lsl.w   #2,d2                   ;d2 = (y & 3) << 2
        movea.l pi_dmatrix(a0),a3       ;a3 = dmatrix
        adda.l  d2,a3                   ;a3 = dmatrix + ((y & 3) << 2)

        movea.l _PED,a4                 ;a4 = ptr to PED
        cmpi.w  #216,ped_YDotsInch(a4)  ;triple interleaving?
        bne.s   10$                     ;no
        divu.w  #3,d0                   ;y /= 3
        swap.w  d0                      ;d0 = y % 3
        mulu.w  d0,d1                   ;BufOffset *= y % 3
        swap.w  d0                      ;d0 = y / 3
        bra.s   30$

10$:    cmpi.w  #144,ped_YDotsInch(a4)  ;double interleaving?
        bne.s   20$                     ;no, clear BufOffset
        asr.w   #1,d0                   ;y /= 2
        btst    #0,d3                   ;odd pass?
        bne.s   30$                     ;no, dont clear BufOffset

20$:    moveq.l #0,d1                   ;BufOffset = 0

30$:    move.w  d0,d6
        not.b   d6                      ;d6 = bit to set
        adda.l  d1,a1                   ;ptr += BufOffset

        movea.l _PD,a4                  ;a4 = ptr to PD
        cmpi.w  #SHADE_COLOR,pd_Preferences+pf_PrintShade(a4)   ;color dump?
        bne     not_color               ;no

color:

; a0 - PInfo
; a1 - ptr (ptr + BufOffset)
; a2 - colors
; a3 - dmatrix ptr
; d0 - y
; d1 - BufOffset
; d6 - bit to set

        movem.l d7/a5-a6,-(sp)          ;save regs used

        movea.l a1,a4
        movea.l a1,a5
        movea.l a1,a6
        adda.w  (a2)+,a1                ;a1 = ptr + colors[0] (bptr)
        adda.w  (a2)+,a4                ;a4 = ptr + colors[1] (yptr)
        adda.w  (a2)+,a5                ;a5 = ptr + colors[2] (mptr)
        adda.w  (a2)+,a6                ;a6 = ptr + colors[3] (cptr)

;       move.l  a6,-(sp)
;       move.l  _AbsExecBase,a6
;       jsr     _LVODebug(a6)
;       move.l  (sp)+,a6

        movea.l pi_ColorInt(a0),a2      ;a2 = ColorInt ptr
        move.w  pi_width(a0),width      ;# of pixels to do
        move.w  pi_xpos(a0),d2          ;d2 = x
        movea.l pi_ScaleX(a0),a0        ;a0 = ScaleX (sxptr)
        move.b  d6,d7                   ;d7 = bit to set

; a0 - sxptr
; a1 - bptr
; a2 - ColorInt ptr
; a3 - dmatrix ptr
; a4 - yptr
; a5 - mptr
; a6 - cptr
; d1 - Black
; d2 - x
; d3 - dvalue (dmatrix[x & 3])
; d4 - Yellow
; d5 - Magenta
; d6 - Cyan
; d7 - bit to set

cwidth_loop:
        move.b  PCMBLACK(a2),d1         ;d1 = Black
        move.b  PCMYELLOW(a2),d4        ;d4 = Yellow
        move.b  PCMMAGENTA(a2),d5       ;d5 = Magenta
        move.b  PCMCYAN(a2),d6          ;d6 = Cyan
        addq.l  #ce_SIZEOF,a2           ;advance to next entry

        move.w  (a0)+,sx                ;# of times to use this pixel

csx_loop:
        moveq.l #3,d3
        and.w   d2,d3                   ;d3 = x & 3
        move.b  0(a3,d3.w),d3           ;d3 = dmatrix[x & 3]

black:
        cmp.b   d3,d1                   ;render black?
        ble.s   yellow                  ;no, try ymc
        bset.b  d7,0(a1,d2.w)           ;set black pixel
        bra.s   csx_end

yellow:
        cmp.b   d3,d4                   ;render yellow pixel?
        ble.s   magenta                 ;no.
        bset.b  d7,0(a4,d2.w)           ;set yellow pixel

magenta:
        cmp.b   d3,d5                   ;render magenta pixel?
        ble.s   cyan                    ;no.
        bset.b  d7,0(a5,d2.w)           ;set magenta pixel

cyan:
        cmp.b   d3,d6                   ;render cyan pixel?
        ble.s   csx_end                 ;no, skip to next pixel.
        bset.b  d7,0(a6,d2.w)           ;clear cyan pixel

csx_end:
        addq.w  #1,d2                   ;x++
        subq.w  #1,sx                   ;sx--
        bne.s   csx_loop
        subq.w  #1,width                ;width--
        bne.s   cwidth_loop

        movem.l (sp)+,d7/a5-a6          ;restore regs used
        bra     exit

not_color:
; a0 - PInfo
; a1 - ptr
; a2 - colors
; a3 - dmatrix ptr
; d0 - y
; d6 - bit to set

        adda.w  (a2),a1                 ;a1 = ptr + colors[0]
        move.w  pi_width(a0),d1         ;d1 = width
        subq.w  #1,d1                   ;adjust for dbra

        move.w  pi_threshold(a0),d3     ;d3 = threshold, thresholding?
        beq.s   grey_scale              ;no, grey-scaling

threshold:
; a0 - PInfo
; a1 - ptr
; a3 - dmatrix ptr
; d1 - width-1
; d3 - threshold
; d6 - bit to set

        eori.b  #15,d3                  ;d3 = dvalue
        movea.l pi_ColorInt(a0),a2      ;a2 = ColorInt ptr
        move.w  pi_xpos(a0),d2          ;d2 = x
        movea.l pi_ScaleX(a0),a0        ;a0 = ScaleX (sxptr)
        adda.w  d2,a1                   ;ptr += x

; a0 - sxptr
; a1 - ptr
; a2 - ColorInt ptr
; a3 - dmatrix ptr (NOT USED)
; d1 - width
; d3 - dvalue
; d4 - Black
; d5 - sx
; d6 - bit to set

twidth_loop:
        move.b  PCMBLACK(a2),d4         ;d4 = Black
        addq.l  #ce_SIZEOF,a2           ;advance to next entry

        move.w  (a0)+,d5                ;d5 = # of times to use this pixel

        cmp.b   d3,d4                   ;render this pixel?
        ble.s   tsx_end                 ;no, skip to next pixel.
        subq.w  #1,d5                   ;adjust for dbra

tsx_render:                             ;yes, render this pixel sx times
        bset.b  d6,(a1)                 ;*(ptr) |= bit;

        adda.w  #1,a1                   ;ptr++
        dbra    d5,tsx_render           ;sx--
        dbra    d1,twidth_loop          ;width--
        bra.s   exit                    ;all done

tsx_end:
        adda.w  d5,a1                   ;ptr += sx
        dbra    d1,twidth_loop          ;width--
        bra.s   exit

grey_scale:
; a0 - PInfo
; a1 - ptr
; a3 - dmatrix ptr
; d0 - y
; d1 - width-1
; d6 - bit to set

        movea.l pi_ColorInt(a0),a2      ;a2 = ColorInt ptr
        move.w  pi_xpos(a0),d2          ;d2 = x
        movea.l pi_ScaleX(a0),a0        ;a0 = ScaleX (sxptr)

; a0 - sxptr
; a1 - ptr
; a2 - ColorInt ptr
; a3 - dmatrix ptr
; d1 - width
; d2 - x
; d3 - dvalue (dmatrix[x & 3])
; d4 - Black
; d5 - sx
; d6 - bit to set

gwidth_loop:
        move.b  PCMBLACK(a2),d4         ;d4 = Black
        addq.l  #ce_SIZEOF,a2           ;advance to next entry

        move.w  (a0)+,d5                ;d5 = # of times to use this pixel
        subq.w  #1,d5                   ;adjust for dbra

gsx_loop:
        moveq.l #3,d3
        and.w   d2,d3                   ;d3 = x & 3
        move.b  0(a3,d3.w),d3           ;d3 = dmatrix[x & 3]

        cmp.b   d3,d4                   ;render this pixel?
        ble.s   gsx_end                 ;no, skip to next pixel.

        bset.b  d6,0(a1,d2.w)           ;*(ptr + x) |= bit

gsx_end
        addq.w  #1,d2                   ;x++
        dbra    d5,gsx_loop             ;sx--
        dbra    d1,gwidth_loop          ;width--

exit:
        movem.l (sp)+,d2-d6/a2-a4       ;restore regs used
        moveq.l #0,d0                   ;flag all ok
        rts                             ;goodbye

sx      dc.w    0
width   dc.w    0

        END

EpsonX: transfer.c

/*
        C-language Transfer routine for EpsonX driver.
 */

#include <exec/types.h>
#include <devices/printer.h>
#include <devices/prtbase.h>
#include <devices/prtgfx.h>

Transfer(PInfo, y, ptr, colors, BufOffset)
struct PrtInfo *PInfo;
UWORD y;                /* row # */
UBYTE *ptr;             /* ptr to buffer */
UWORD *colors;          /* indexes to color buffers */
ULONG BufOffset;        /* used for interleaved printing */
{
        extern struct PrinterData *PD;
        extern struct PrinterExtendedData *PED;

        static UWORD bit_table[8] = {128, 64, 32, 16, 8, 4, 2, 1};
        union colorEntry *ColorInt;
        UBYTE *bptr, *yptr, *mptr, *cptr, Black, Yellow, Magenta, Cyan;
        UBYTE *dmatrix, dvalue, threshold;
        UWORD x, width, sx, *sxptr, color, bit, x3;

        /* printer non-specific, MUST DO FOR EVERY PRINTER */
        x = PInfo->pi_xpos;
        ColorInt = PInfo->pi_ColorInt;
        sxptr = PInfo->pi_ScaleX;
        width = PInfo->pi_width;

        /* printer specific */
        if (PED->ped_YDotsInch == 216)
        {
                BufOffset *= y % 3;
                y /= 3;
        }
        else if (PED->ped_YDotsInch == 144)
        {
                BufOffset *= y & 1;
                y /= 2;
        }
        else
        {
                BufOffset = 0;
        }
        bit = bit_table[y & 7];
        bptr = ptr + colors[0] + BufOffset;
        yptr = ptr + colors[1] + BufOffset;
        mptr = ptr + colors[2] + BufOffset;
        cptr = ptr + colors[3] + BufOffset;

        /* pre-compute threshold; are we thresholding? */
        if (threshold = PInfo->pi_threshold)
        { /* thresholding */
                dvalue = threshold ^ 15;
                bptr += x;
                do { /* for all source pixels */
                        /* pre-compute intensity values for Black component */
                        Black = ColorInt->colorByte[PCMBLACK];
                        ColorInt++;

                        sx = *sxptr++;

                        do { /* use this pixel 'sx' times */
                                if (Black > dvalue)
                                {
                                        *bptr |= bit;
                                }
                                bptr++; /* done 1 more printer pixel */
                        } while (--sx);
                } while (--width);
        }

        else
        { /* not thresholding, pre-compute ptr to dither matrix */
                dmatrix = PInfo->pi_dmatrix + ((y & 3) << 2);
                if (PD->pd_Preferences.PrintShade == SHADE_GREYSCALE)
                {
                        do { /* for all source pixels */
                                /* pre-compute intensity values for Black */
                                Black = ColorInt->colorByte[PCMBLACK];
                                ColorInt++;

                                sx = *sxptr++;

                                do { /* use this pixel 'sx' times */
                                        if (Black > dmatrix[x & 3])
                                        {
                                                *(bptr + x) |= bit;
                                        }
                                        x++; /* done 1 more printer pixel */
                                } while (--sx);
                        } while (--width);
                }
                else
                { /* color */
                        do { /* for all source pixels */
                                /* compute intensity values for each color */
                                Black = ColorInt->colorByte[PCMBLACK];
                                Yellow = ColorInt->colorByte[PCMYELLOW];
                                Magenta = ColorInt->colorByte[PCMMAGENTA];
                                Cyan = ColorInt->colorByte[PCMCYAN];
                                ColorInt++;

                                sx = *sxptr++;

                                do { /* use this pixel 'sx' times */
                                        x3 = x >> 3;
                                        dvalue = dmatrix[x & 3];
                                        if (Black > dvalue)
                                        {
                                                *(bptr + x) |= bit;
                                        }
                                        else
                                        { /* black not rendered */
                                                if (Yellow > dvalue)
                                                {
                                                        *(yptr + x) |= bit;
                                                }
                                                if (Magenta > dvalue)
                                                {
                                                        *(mptr + x) |= bit;
                                                }
                                                if (Cyan > dvalue)
                                                {
                                                        *(cptr + x) |= bit;
                                                }
                                        }
                                        ++x; /* done 1 more printer pixel */
                                } while (--sx);
                        } while (--width);
                }
        }
}

EpsonX: density.c

/*
        Density module for EpsonX driver.
*/

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

SetDensity(density_code)
ULONG density_code;
{
        extern struct PrinterData *PD;
        extern struct PrinterExtendedData *PED;

        /* SPECIAL_DENSITY     0    1    2    3    4    5    6    7 */
        static int XDPI[8] = {120, 120, 120, 240, 120, 240, 240, 240};
        static int YDPI[8] = {72, 72, 144, 72, 216, 144, 216, 216};
        static char codes[8] = {'L', 'L', 'L', 'Z', 'L', 'Z', 'Z', 'Z'};

        PED->ped_MaxColumns =
                PD->pd_Preferences.PaperSize == W_TRACTOR ? 136 : 80;
        density_code /= SPECIAL_DENSITY1;
        /* default is 80 chars (8.0 in.), W_TRACTOR is 136 chars (13.6 in.) */
        PED->ped_MaxXDots =
                (XDPI[density_code] * PED->ped_MaxColumns) / 10;
        PED->ped_XDotsInch = XDPI[density_code];
        PED->ped_YDotsInch = YDPI[density_code];
        if ((PED->ped_YDotsInch = YDPI[density_code]) == 216) {
                PED->ped_NumRows = 24;
        }
        else if (PED->ped_YDotsInch == 144) {
                PED->ped_NumRows = 16;
        }
        else {
                PED->ped_NumRows = 8;
        }
        return((int)codes[density_code]);
}

HP_Laserjet

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

LC = lc:lc
ASM = lc:asm
CFLAGS = -iINCLUDE: -b0 -d0 -v
ASMFLAGS = -iINCLUDE:
LINK = lc:blink
LIB = lib:amiga.lib+lib:lc.lib
OBJ = printertag.o+init.o+data.o+dospecial.o+render.o+transfer.o+density.o
TARGET = hp_laserjet

        @$(LC) $(CFLAGS) $*

$(TARGET): printertag.o init.o data.o dospecial.o render.o density.o transfer.o
        @$(LINK) <WITH <
        FROM $(OBJ)
        TO $(TARGET)
        LIBRARY $(LIB)
        NODEBUG SC SD VERBOSE MAP $(TARGET).map H
        <

init.o: init.asm
        @$(ASM) $(ASMFLAGS) init.asm

printertag.o: printertag.asm hp_rev.i
        @$(ASM) $(ASMFLAGS) printertag.asm

transfer.o: transfer.asm
        @$(ASM) $(ASMFLAGS) transfer.asm

dospecial.o: dospecial.c

data.o: data.c

density.o: density.c

render.o: render.c

HP_Laserjet: macros.i

**********************************************************************
*
*       printer device macro definitions
*
**********************************************************************

*------ external definition macros -----------------------------------

XREF_EXE        MACRO
        XREF            _LVO\1
                ENDM

XREF_DOS        MACRO
        XREF            _LVO\1
                ENDM

XREF_GFX        MACRO
        XREF            _LVO\1
                ENDM

XREF_ITU        MACRO
        XREF            _LVO\1
                ENDM

*------ library dispatch macros --------------------------------------

CALLEXE         MACRO
                CALLLIB _LVO\1
                ENDM

LINKEXE         MACRO
                LINKLIB _LVO\1,_SysBase
                ENDM

LINKDOS         MACRO
                LINKLIB _LVO\1,_DOSBase
                ENDM

LINKGFX         MACRO
                LINKLIB _LVO\1,_GfxBase
                ENDM

LINKITU         MACRO
                LINKLIB _LVO\1,_IntuitionBase
                ENDM

HP_Laserjet: printertag.asm

**********************************************************************
*
*       printer device dependent code tag
*
**********************************************************************

        SECTION         printer

*------ Included Files -----------------------------------------------

        INCLUDE         "exec/types.i"
        INCLUDE         "exec/nodes.i"
        INCLUDE         "exec/strings.i"

        INCLUDE         "hp_rev.i"

        INCLUDE         "devices/prtbase.i"


*------ Imported Names -----------------------------------------------

        XREF            _Init
        XREF            _Expunge
        XREF            _Open
        XREF            _Close
        XREF            _CommandTable
        XREF            _PrinterSegmentData
        XREF            _DoSpecial
        XREF            _Render
        XREF            _ExtendedCharTable
        XREF            _ConvFunc

*------ Exported Names -----------------------------------------------

        XDEF            _PEDData


**********************************************************************

                MOVEQ   #0,D0           ; show error for OpenLibrary()
                RTS
                DC.W    VERSION
                DC.W    REVISION
_PEDData:
                DC.L    printerName
                DC.L    _Init
                DC.L    _Expunge
                DC.L    _Open
                DC.L    _Close
                DC.B    PPC_BWGFX       ; PrinterClass
                DC.B    PCC_BW          ; ColorClass
                DC.B    0               ; MaxColumns
                DC.B    0               ; NumCharSets
                DC.W    1               ; NumRows
                DC.L    600             ; MaxXDots
                DC.L    795             ; MaxYDots
                DC.W    75              ; XDotsInch
                DC.W    75              ; YDotsInch
                DC.L    _CommandTable   ; Commands
                DC.L    _DoSpecial
                DC.L    _Render
                DC.L    30              ; Timeout
                DC.L    _ExtendedCharTable      ; 8BitChars
                DS.L    1               ; PrintMode (reserve space)
                DC.L    _ConvFunc       ; ptr to char conversion function

printerName:
                dc.b    'HP_LaserJet',0

                END

HP_Laserjet: hp_rev.i

VERSION         EQU     35
REVISION        EQU     1

HP_Laserjet: init.asm

**********************************************************************
*
*       printer device functions
*
**********************************************************************

        SECTION         printer

*------ Included Files -----------------------------------------------

        INCLUDE         "exec/types.i"
        INCLUDE         "exec/nodes.i"
        INCLUDE         "exec/lists.i"
        INCLUDE         "exec/memory.i"
        INCLUDE         "exec/ports.i"
        INCLUDE         "exec/libraries.i"

        INCLUDE         "macros.i"

*------ Imported Functions -------------------------------------------

        XREF_EXE        CloseLibrary
        XREF_EXE        OpenLibrary
        XREF            _AbsExecBase

        XREF            _PEDData

*------ Exported Globals ---------------------------------------------

        XDEF            _Init
        XDEF            _Expunge
        XDEF            _Open
        XDEF            _PD
        XDEF            _PED
        XDEF            _SysBase
        XDEF            _DOSBase
        XDEF            _GfxBase
        XDEF            _IntuitionBase

**********************************************************************
        SECTION         printer,DATA
_PD             DC.L    0
_PED            DC.L    0
_SysBase        DC.L    0
_DOSBase        DC.L    0
_GfxBase        DC.L    0
_IntuitionBase  DC.L    0

**********************************************************************
        SECTION         printer,CODE
_Init:
                MOVE.L  4(A7),_PD
                LEA     _PEDData(PC),A0
                MOVE.L  A0,_PED
                MOVE.L  A6,-(A7)
                MOVE.L  _AbsExecBase,A6
                MOVE.L  A6,_SysBase

*           ;------ open the dos library
                LEA     DLName(PC),A1
                MOVEQ   #0,D0
                CALLEXE OpenLibrary
                MOVE.L  D0,_DOSBase
                BEQ     initDLErr

*           ;------ open the graphics library
                LEA     GLName(PC),A1
                MOVEQ   #0,D0
                CALLEXE OpenLibrary
                MOVE.L  D0,_GfxBase
                BEQ     initGLErr

*           ;------ open the intuition library
                LEA     ILName(PC),A1
                MOVEQ   #0,D0
                CALLEXE OpenLibrary
                MOVE.L  D0,_IntuitionBase
                BEQ     initILErr

                MOVEQ   #0,D0
pdiRts:
                MOVE.L  (A7)+,A6
                RTS

initPAErr:
                MOVE.L  _IntuitionBase,A1
                LINKEXE CloseLibrary
initILErr:
                MOVE.L  _GfxBase,A1
                LINKEXE CloseLibrary
initGLErr:
                MOVE.L  _DOSBase,A1
                LINKEXE CloseLibrary
initDLErr:
                MOVEQ   #-1,D0
                BRA.S   pdiRts

ILName:
                DC.B    'intuition.library'
                DC.B    0
DLName:
                DC.B    'dos.library'
                DC.B    0
GLName:
                DC.B    'graphics.library'
                DC.B    0
                DS.W    0

*---------------------------------------------------------------------
_Expunge:
                MOVE.L  _IntuitionBase,A1
                LINKEXE CloseLibrary

                MOVE.L  _GfxBase,A1
                LINKEXE CloseLibrary

                MOVE.L  _DOSBase,A1
                LINKEXE CloseLibrary

*---------------------------------------------------------------------
_Open:
                MOVEQ   #0,D0
                RTS

                END

HP_Laserjet: data.c

/*
        Data.c table for HP_LaserJet (Plus and II compatible) driver.
*/

char *CommandTable[] = {
        "\375\033E\375",/* 00 aRIS reset                        */
        "\377",         /* 01 aRIN initialize                   */
        "\012",         /* 02 aIND linefeed                     */
        "\015\012",     /* 03 aNEL CRLF                         */
        "\033&a-1R",    /* 04 aRI reverse LF                    */

                        /* 05 aSGR0 normal char set             */
        "\033&d@\033(sbS",
        "\033(s1S",     /* 06 aSGR3 italics on                  */
        "\033(sS",      /* 07 aSGR23 italics off                */
        "\033&dD",      /* 08 aSGR4 underline on                */
        "\033&d@",      /* 09 aSGR24 underline off              */
        "\033(s5B",     /* 10 aSGR1 boldface on                 */
        "\033(sB",      /* 11 aSGR22 boldface off               */
        "\377",         /* 12 aSFC set foreground color         */
        "\377",         /* 13 aSBC set background color         */

        "\033(s10h1T",  /* 14 aSHORP0 normal pitch              */
        "\033(s12h2T",  /* 15 aSHORP2 elite on                  */
        "\033(s10h1T",  /* 16 aSHORP1 elite off                 */
        "\033(s15H",    /* 17 aSHORP4 condensed fine on         */
        "\033(s10H",    /* 18 aSHORP3 condensed fine off        */
        "\377",         /* 19 aSHORP6 enlarge on                */
        "\377",         /* 20 aSHORP5 enlarge off               */

        "\033(s7B",     /* 21 aDEN6 shadow print on             */
        "\033(sB",      /* 22 aDEN5 shadow print off            */
        "\033(s3B",     /* 23 aDEN4 double strike on            */
        "\033(sB",      /* 24 aDEN3 double strike off           */
        "\377",         /* 25 aDEN2 NLQ on                      */
        "\377",         /* 26 aDEN1 NLQ off                     */

        "\377",         /* 27 aSUS2 superscript on              */
        "\377",         /* 28 aSUS1 superscript off             */
        "\377",         /* 29 aSUS4 subscript on                */
        "\377",         /* 30 aSUS3 subscript off               */
        "\377",         /* 31 aSUS0 normalize the line          */
        "\033&a-.5R",   /* 32 aPLU partial line up              */
        "\033=",        /* 33 aPLD partial line down            */

        "\033(s3T",     /* 34 aFNT0 Typeface 0                  */
        "\033(s0T",     /* 35 aFNT1 Typeface 1                  */
        "\033(s1T",     /* 36 aFNT2 Typeface 2                  */
        "\033(s2T",     /* 37 aFNT3 Typeface 3                  */
        "\033(s4T",     /* 38 aFNT4 Typeface 4                  */
        "\033(s5T",     /* 39 aFNT5 Typeface 5                  */
        "\033(s6T",     /* 40 aFNT6 Typeface 6                  */
        "\033(s7T",     /* 41 aFNT7 Typeface 7                  */
        "\033(s8T",     /* 42 aFNT8 Typeface 8                  */
        "\033(s9T",     /* 43 aFNT9 Typeface 9                  */
        "\033(s10T",    /* 44 aFNT10 Typeface 10                */

        "\033(s1P",     /* 45 aPROP2 proportional on            */
        "\033(sP",      /* 46 aPROP1 proportional off           */
        "\033(sP",      /* 47 aPROP0 proportional clear         */
        "\377",         /* 48 aTSS set proportional offset      */
        "\377",         /* 49 aJFY5 auto left justify           */
        "\377",         /* 50 aJFY7 auto right justify          */
        "\377",         /* 51 aJFY6 auto full jusitfy           */
        "\377",         /* 52 aJFY0 auto jusity off             */
        "\377",         /* 53 aJFY3 letter space                */
        "\377",         /* 54 aJFY1 word fill                   */

        "\033&l8D",     /* 55 aVERP0 1/8" line spacing          */
        "\033&l6D",     /* 56 aVERP1 1/6" line spacing          */
        "\377",         /* 57 aSLPP set form length             */
        "\033&l1L",     /* 58 aPERF perf skip n (n > 0)         */
        "\033&lL",      /* 59 aPERF0 perf skip off              */

        "\377",         /* 60 aLMS set left margin              */
        "\377",         /* 61 aRMS set right margin             */
        "\377",         /* 62 aTMS set top margin               */
        "\377",         /* 63 aBMS set bottom margin            */
        "\377",         /* 64 aSTBM set T&B margins             */
        "\377",         /* 65 aSLRM set L&R margins             */
        "\0339\015",    /* 66 aCAM clear margins                */

        "\377",         /* 67 aHTS set horiz tab                */
        "\377",         /* 68 aVTS set vert tab                 */
        "\377",         /* 69 aTBC0 clear horiz tab             */
        "\377",         /* 70 aTBC3 clear all horiz tabs        */
        "\377",         /* 71 aTBC1 clear vert tab              */
        "\377",         /* 72 aTBC4 clear all vert tabs         */
        "\377",         /* 73 aTBCALL clear all h & v tabs      */
        "\377",         /* 74 aTBSALL set default tabs          */

        "\377",         /* 75 aEXTEND extended commands         */
        "\377"          /* 76 aRAW next 'n' chars are raw       */
};

char *ExtendedCharTable[] = {
/*
    " ", "!", "c", "L", "o", "Y", "|", "S",

    "\"", "c", "a", "<", "~", "-", "r", "-",

    "*", "+", "2", "3", "'", "u", "P", ".",

    ",", "1", "o", ">", "/", "/", "/", "?",

    "A", "A", "A", "A", "A", "A", "A", "C",

    "E", "E", "E", "E", "I", "I", "I", "I",

    "D", "N", "O", "O", "O", "O", "O", "x",

    "O", "U", "U", "U", "U", "Y", "P", "B",

    "a", "a", "a", "a", "a", "a", "a", "c",

    "e", "e", "e", "e", "i", "i", "i", "i",

    "d", "n", "o", "o", "o", "o", "o", "/",

    "o", "u", "u", "u", "u", "y", "p", "y"
*/

        " ", "\270", "\277", "\273", "\272", "\274", "|", "\275",
        "\253", "c", "\371", "\373", "~", "\366", "r", "\260",
        "\263", "\376", "2", "3", "\250", "\363", "\364", "\362",
        ",", "1", "\372", "\375", "\367", "\370", "\365", "\271",
        "\241", "\340", "\242", "\341", "\330", "\320", "\323", "\264",
        "\243", "\334", "\244", "\245", "\346", "\345", "\246", "\247",
        "\343", "\266", "\350", "\347", "\337", "\351", "\332", "x",
        "\322", "\255", "\355", "\256", "\333", "\261", "\360", "\336",
        "\310", "\304", "\300", "\342", "\314", "\324", "\327", "\265",
        "\311", "\305", "\301", "\315", "\331", "\325", "\321", "\335",
        "\344", "\267", "\312", "\306", "\302", "\352", "\316", "-\010:",
        "\326", "\313", "\307", "\303", "\317", "\262", "\361", "\357"
};

HP_Laserjet: dospecial.c

/*
        DoSpecial for HP_LaserJet driver.
*/

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

#define LPI             7
#define CPI             15
#define QUALITY         17
#define INIT_LEN        30
#define LPP             7
#define FORM_LEN        11
#define LEFT_MARG       3
#define RIGHT_MARG      7
#define MARG_LEN        12

DoSpecial(command, outputBuffer, vline, currentVMI, crlfFlag, Parms)
char outputBuffer[];
UWORD *command;
BYTE *vline;
BYTE *currentVMI;
BYTE *crlfFlag;
UBYTE Parms[];
{
        extern struct PrinterData *PD;
        extern struct PrinterExtendedData *PED;

        static UWORD textlength, topmargin;
        int x, y, j;
        static char initThisPrinter[INIT_LEN] =
                "\033&d@\033&l6D\033(s0b10h1q0p0s3t0u12V";
        static char initForm[FORM_LEN] = "\033&l002e000F";
        static char initMarg[MARG_LEN] = "\033&a000l000M\015";
        static char initTMarg[] = "\033&l000e000F";

        x = y = j = 0;

        if (*command == aRIN) {
                while(x < INIT_LEN) {
                        outputBuffer[x] = initThisPrinter[x];
                        x++;
                }
                outputBuffer[x++] = '\015';

                if (PD->pd_Preferences.PrintSpacing == EIGHT_LPI) {
                        outputBuffer[LPI] = '8';
                }

                if (PD->pd_Preferences.PrintPitch == ELITE) {
                        outputBuffer[CPI] = '2';
                }
                else if (PD->pd_Preferences.PrintPitch == FINE) {
                        outputBuffer[CPI] = '5';
                }

                if (PD->pd_Preferences.PrintQuality == LETTER) {
                        outputBuffer[QUALITY] = '2';
                }

                j = x; /* set the formlength = textlength, top margin = 2 */
                textlength = PD->pd_Preferences.PaperLength;
                topmargin = 2;

                while (y < FORM_LEN) {
                        outputBuffer[x++] = initForm[y++];
                }
                numberString(textlength, j + LPP, outputBuffer);

                Parms[0] = PD->pd_Preferences.PrintLeftMargin;
                Parms[1] = PD->pd_Preferences.PrintRightMargin;
                *command = aSLRM;
        }

        if (*command == aSLRM) {
                j = x;
                y = 0;
                while(y < MARG_LEN) {
                        outputBuffer[x++] = initMarg[y++];
                }
                numberString(Parms[0] - 1, j + LEFT_MARG, outputBuffer);
                numberString(Parms[1] - 1, j + RIGHT_MARG, outputBuffer);
                return(x);
        }

        if ((*command == aSUS2) && (*vline == 0)) {
                *command = aPLU;
                *vline = 1;
                return(0);
        }

        if ((*command == aSUS2) && (*vline < 0)) {
                *command = aRI;
                *vline = 1;
                return(0);
        }

        if ((*command == aSUS1) && (*vline > 0)) {
                *command = aPLD;
                *vline = 0;
                return(0);
        }

        if ((*command == aSUS4) && (*vline == 0)) {
                *command = aPLD;
                *vline = -1;
                return(0);
        }

        if ((*command == aSUS4) && (*vline > 0)) {
                *command = aIND;
                *vline = -1;
                return(0);
        }

        if ((*command == aSUS3) && (*vline < 0)) {
                *command = aPLU;
                *vline = 0;
                return(0);
        }

        if(*command == aSUS0) {
                if (*vline > 0) {
                        *command = aPLD;
                }
                if (*vline < 0) {
                        *command = aPLU;
                }
                *vline = 0;
                return(0);
        }

        if (*command == aPLU) {
                (*vline)++;
                return(0);
        }

        if (*command == aPLD){
                (*vline)--;
                return(0);
        }

        if (*command == aSTBM) {
                if (Parms[0] == 0) {
                        Parms[0] = topmargin;
                }
                else {
                        topmargin = --Parms[0];
                }

                if (Parms[1] == 0) {
                        Parms[1] = textlength;
                }
                else {
                        textlength=Parms[1];
                }
                while (x < 11) {
                        outputBuffer[x] = initTMarg[x];
                        x++;
                }
                numberString(Parms[0], 3, outputBuffer);
                numberString(Parms[1] - Parms[0], 7, outputBuffer);
                return(x);
        }

        if (*command == aSLPP) {
                while(x < 11) {
                        outputBuffer[x] = initForm[x];
                        x++;
                }
                /*restore textlength, margin*/
                numberString(topmargin, 3, outputBuffer);
                numberString(textlength, 7, outputBuffer);
                return(x);
        }

        if (*command == aRIS) {
                PD->pd_PWaitEnabled = 253;
        }

        return(0);
}

numberString(Param, x, outputBuffer)
UBYTE Param;
int x;
char outputBuffer[];
{
        if (Param > 199) {
                outputBuffer[x++] = '2';
                Param -= 200;
        }
        else if (Param > 99) {
                outputBuffer[x++] = '1';
                Param -= 100;
        }
        else {
                outputBuffer[x++] = '0'; /* always return 3 digits */
        }

        if (Param > 9) {
                outputBuffer[x++] = Param / 10 + '0';
        }
        else {
                outputBuffer[x++] = '0';
        }

        outputBuffer[x++] = Param % 10 + '0';
}

ConvFunc(buf, c, flag)
char *buf, c;
int flag; /* expand lf into lf/cr flag (0-yes, else no ) */
{
        if (c == '\014') { /* if formfeed (page eject) */
                PED->ped_PrintMode = 0; /* no data to print */
        }
        return(-1); /* pass all chars back to the printer device */
}

Close(ior)
struct printerIO *ior;
{
        if (PED->ped_PrintMode) { /* if data has been printed */
                (*(PD->pd_PWrite))("\014",1); /* eject page */
                (*(PD->pd_PBothReady))(); /* wait for it to finish */
                PED->ped_PrintMode = 0; /* no data to print */
        }
        return(0);
}

HP_Laserjet: render.c

/*
        HP_LaserJet driver.
*/

#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <exec/memory.h>
#include <devices/prtbase.h>
#include <devices/printer.h>

#define NUMSTARTCMD     7       /* # of cmd bytes before binary data */
#define NUMENDCMD       0       /* # of cmd bytes after binary data */
#define NUMTOTALCMD (NUMSTARTCMD + NUMENDCMD)   /* total of above */

extern SetDensity();
/*
        00-04   \033&l0L        perf skip mode off
        05-11   \033*t075R      set raster graphics resolution (dpi)
        12-16   \033*r0A        start raster graphics
*/
char StartCmd[18] = "\033&l0L\033*t075R\033*r0A";

Render(ct, x, y, status)
long ct, x, y, status;
{
        extern void *AllocMem(), FreeMem();

        extern struct PrinterData *PD;
        extern struct PrinterExtendedData *PED;

        static UWORD RowSize, BufSize, TotalBufSize, dataoffset;
        static UWORD huns, tens, ones; /* used to program buffer size */
        UBYTE *ptr, *ptrstart;
        int i, err;

        err=PDERR_NOERR;
        switch(status) {
                case 0 : /* Master Initialization */
                        /*
                                ct      - pointer to IODRPReq structure.
                                x       - width of printed picture in pixels.
                                y       - height of printed picture in pixels.
                        */
                        RowSize = (x + 7) / 8;
                        BufSize = RowSize + NUMTOTALCMD;
                        TotalBufSize = BufSize * 2;
                        PD->pd_PrintBuf = AllocMem(TotalBufSize, MEMF_PUBLIC);
                        if (PD->pd_PrintBuf == NULL) {
                                err = PDERR_BUFFERMEMORY; /* no mem */
                        }
                        else {
                                ptr = PD->pd_PrintBuf;
                                *ptr++ = 27;
                                *ptr++ = '*';
                                *ptr++ = 'b';   /* transfer raster graphics */
                                *ptr++ = huns | '0';
                                *ptr++ = tens | '0';
                                *ptr++ = ones | '0';    /* printout width */
                                *ptr = 'W';             /* terminator */
                                ptr = &PD->pd_PrintBuf[BufSize];
                                *ptr++ = 27;
                                *ptr++ = '*';
                                *ptr++ = 'b';   /* transfer raster graphics */
                                *ptr++ = huns | '0';
                                *ptr++ = tens | '0';
                                *ptr++ = ones | '0';    /* printout width */
                                *ptr = 'W';             /* terminator */
                                dataoffset = NUMSTARTCMD;
                        /* perf skip mode off, set dpi, start raster gfx */
                                err = (*(PD->pd_PWrite))(StartCmd, 17);
                        }
                        break;

                case 1 : /* Scale, Dither and Render */
                        /*
                                ct      - pointer to PrtInfo structure.
                                x       - 0.
                                y       - row # (0 to Height - 1).
                        */
                        Transfer(ct, y, &PD->pd_PrintBuf[dataoffset]);
                        err = PDERR_NOERR; /* all ok */
                        break;

                case 2 : /* Dump Buffer to Printer */
                        /*
                                ct      - 0.
                                x       - 0.
                                y       - # of rows sent (1 to NumRows).
                                White-space strip.
                        */
                        i = RowSize;
                        ptrstart = &PD->pd_PrintBuf[dataoffset - NUMSTARTCMD];
                        ptr = ptrstart + NUMSTARTCMD + i - 1;
                        while (i > 0 && *ptr == 0) {
                                i--;
                                ptr--;
                        }
                        ptr = ptrstart + 3; /* get ptr to density info */
                        *ptr++ = (huns = i / 100) | '0';
                        *ptr++ = (i - huns * 100) / 10 | '0';
                        *ptr = i % 10 | '0'; /* set printout width */
                        err = (*(PD->pd_PWrite))(ptrstart, i + NUMTOTALCMD);
                        if (err == PDERR_NOERR) {
                                dataoffset = (dataoffset == NUMSTARTCMD ?
                                        BufSize : 0) + NUMSTARTCMD;
                        }
                        break;

                case 3 : /* Clear and Init Buffer */
                        /*
                                ct      - 0.
                                x       - 0.
                                y       - 0.
                        */
                        ptr = &PD->pd_PrintBuf[dataoffset];
                        i = RowSize;
                        do {
                                *ptr++ = 0;
                        } while (--i);
                        break;

                case 4 : /* Close Down */
                        /*
                                ct      - error code.
                                x       - io_Special flag from IODRPReq struct
                                y       - 0.
                        */
                        err = PDERR_NOERR; /* assume all ok */
                        /* if user did not cancel the print */
                        if (ct != PDERR_CANCEL) {
                                /* end raster graphics, perf skip mode on */
                                if ((err = (*(PD->pd_PWrite))
                                        ("\033*rB\033&l1L", 9)) == PDERR_NOERR) {
                                        /* if want to unload paper */
                                        if (!(x & SPECIAL_NOFORMFEED)) {
                                                /* eject paper */
                                                err = (*(PD->pd_PWrite))
                                                        ("\014", 1);
                                        }
                                }
                        }
                        /*
                                flag that there is no alpha data waiting that
                                needs a formfeed (since we just did one)
                        */
                        PED->ped_PrintMode = 0;
                         /* wait for both buffers to empty */
                        (*(PD->pd_PBothReady))();
                        if (PD->pd_PrintBuf != NULL) {
                                FreeMem(PD->pd_PrintBuf, TotalBufSize);
                        }
                        break;

                case 5 : /* Pre-Master Initialization */
                        /*
                                ct      - 0 or pointer to IODRPReq structure.
                                x       - io_Special flag from IODRPReq struct
                                y       - 0.
                        */
                        /* select density */
                        SetDensity(x & SPECIAL_DENSITYMASK);
                        break;
        }
        return(err);
}

HP_Laserjet: density.c

/*
        Density module for HP_LaserJet
*/

#include <exec/types.h>
#include <devices/printer.h>
#include <devices/prtbase.h>

SetDensity(density_code)
ULONG density_code;
{
        extern struct PrinterData *PD;
        extern struct PrinterExtendedData *PED;
        extern char StartCmd[];

        /* SPECIAL_DENSITY     0   1   2    3    4    5    6    7 */
        static int XDPI[8] = {75, 75, 100, 150, 300, 300, 300, 300};
        static char codes[8][3] = {
        {'0','7','5'},{'0','7','5'},{'1','0','0'},{'1','5','0'},
        {'3','0','0'},{'3','0','0'},{'3','0','0'},{'3','0','0'},
        };

        density_code /= SPECIAL_DENSITY1;
        PED->ped_MaxXDots = XDPI[density_code] * 8; /* 8 inches */

        /* default is 10.0, US_LEGAL is 14.0 */
        PED->ped_MaxYDots =
                PD->pd_Preferences.PaperSize == US_LEGAL ? 14 : 10;
        PED->ped_MaxYDots *= XDPI[density_code];

        PED->ped_XDotsInch = PED->ped_YDotsInch = XDPI[density_code];
        StartCmd[8] = codes[density_code][0];
        StartCmd[9] = codes[density_code][1];
        StartCmd[10] = codes[density_code][2];
}

HP_Laserjet transfer.c

/*
        Example transfer routine for HP_LaserJet driver.

        Transfer() should be written in assembly code for speed
*/

#include <exec/types.h>
#include <devices/prtgfx.h>

Transfer(PInfo, y, ptr)
struct PrtInfo *PInfo;
UWORD y;        /* row # */
UBYTE *ptr;     /* ptr to buffer */
{
        static UBYTE bit_table[] = {128, 64, 32, 16, 8, 4, 2, 1};
        UBYTE *dmatrix, Black, dvalue, threshold;
        union colorEntry *ColorInt;
        UWORD x, width, sx, *sxptr, bit;

        /* pre-compute */
        /* printer non-specific, MUST DO FOR EVERY PRINTER */
        x = PInfo->pi_xpos; /* get starting x position */
        ColorInt = PInfo->pi_ColorInt; /* get ptr to color intensities */
        sxptr = PInfo->pi_ScaleX;
        width = PInfo->pi_width; /* get # of source pixels */

        /* pre-compute threshold; are we thresholding? */
        if (threshold = PInfo->pi_threshold) { /* thresholding */
                dvalue = threshold ^ 15; /* yes, so pre-compute dither value */
                do { /* for all source pixels */
                        /* pre-compute intensity value for Black */
                        Black = ColorInt->colorByte[PCMBLACK];
                        ColorInt++; /* bump ptr for next time */

                        sx = *sxptr++;

                        /* dither and render pixel */
                        do { /* use this pixel 'sx' times */
                                /* if we should render Black */
                                if (Black > dvalue) {
                                        /* set bit */
                                        *(ptr + (x >> 3)) |= bit_table[x & 7];
                                }
                                ++x; /* done 1 more printer pixel */
                        } while (--sx);
                } while (--width);
        }
        else { /* not thresholding, pre-compute ptr to dither matrix */
                dmatrix = PInfo->pi_dmatrix + ((y & 3) << 2);
                do { /* for all source pixels */
                        /* pre-compute intensity value for Black */
                        Black = ColorInt->colorByte[PCMBLACK];
                        ColorInt++; /* bump ptr for next time */

                        sx = *sxptr++;

                        /* dither and render pixel */
                        do { /* use this pixel 'sx' times */
                                /* if we should render Black */
                                if (Black > dmatrix[x & 3]) {
                                        /* set bit */
                                        *(ptr + (x >> 3)) |= bit_table[x & 7];
                                }
                                ++x; /* done 1 more printer pixel */
                        } while (--sx);
                } while (--width);
        }
}

HP_Laserjet transfer.asm

**********************************************************************
*
* Transfer routine for HP_LaserJet
*
**********************************************************************

        INCLUDE "exec/types.i"

        INCLUDE "intuition/intuition.i"
        INCLUDE "devices/printer.i"
        INCLUDE "devices/prtbase.i"
        INCLUDE "devices/prtgfx.i"

        XREF    _PD

        XDEF    _Transfer

        SECTION         printer,CODE
_Transfer:
; Transfer(PInfo, y, ptr)
; struct PrtInfo *PInfo         4-7
; UWORD y;                      8-11
; UBYTE *ptr;                   12-15
;

        movem.l d2-d6/a2-a3,-(sp)       ;save regs used

        movea.l 32(sp),a0               ;a0 = PInfo
        move.l  36(sp),d0               ;d0 = y
        movea.l 40(sp),a1               ;a1 = ptr

        move.w  pi_width(a0),d1         ;d1 = width
        subq.w  #1,d1                   ;adjust for dbra

        move.w  pi_threshold(a0),d3     ;d3 = threshold, thresholding?
        beq.s   grey_scale              ;no, grey-scale

threshold:
; a0 - PInfo
; a1 - ptr
; d0 - y
; d1 - width
; d3 - threshold

        eori.b  #15,d3                  ;d3 = dvalue
        movea.l pi_ColorInt(a0),a2      ;a2 = ColorInt ptr
        move.w  pi_xpos(a0),d2          ;d2 = x
        movea.l pi_ScaleX(a0),a0        ;a0 = ScaleX (sxptr)

; a0 - sxptr
; a1 - ptr
; a2 - ColorInt ptr
; a3 - dmatrix ptr (NOT USED)
; d0 - byte to set (x >> 3)
; d1 - width
; d2 - x
; d3 - dvalue
; d4 - Black
; d5 - sx
; d6 - bit to set

twidth_loop:
        move.b  PCMBLACK(a2),d4         ;d4 = Black
        addq.l  #ce_SIZEOF,a2           ;advance to next entry

        move.w  (a0)+,d5                ;d5 = # of times to use this pixel (sx)

        cmp.b   d3,d4                   ;render this pixel?
        ble.s   tsx_end                 ;no, skip to next pixel.
        subq.w  #1,d5                   ;adjust for dbra

tsx_render:                             ;yes, render this pixel sx times
        move.w  d2,d0
        lsr.w   #3,d0                   ;compute byte to set
        move.w  d2,d6
        not.w   d6                      ;compute bit to set
        bset.b  d6,0(a1,d0.w)           ;*(ptr + x >> 3) |= 2 ^ x

        addq.w  #1,d2                   ;x++
        dbra    d5,tsx_render           ;sx--
        dbra    d1,twidth_loop          ;width--
        bra.s   exit                    ;all done

tsx_end:
        add.w   d5,d2                   ;x += sx
        dbra    d1,twidth_loop          ;width--
        bra.s   exit

grey_scale:
; a0 - PInfo
; a1 - ptr
; d0 - y
; d1 - width

        movea.l pi_ColorInt(a0),a2      ;a2 = ColorInt ptr
        moveq.l #3,d2
        and.w   d0,d2                   ;d2 = y & 3
        lsl.w   #2,d2                   ;d2 = (y & 3) << 2
        movea.l pi_dmatrix(a0),a3       ;a3 = dmatrix
        adda.l  d2,a3                   ;a3 = dmatrix + ((y & 3) << 2)
        move.w  pi_xpos(a0),d2          ;d2 = x
        movea.l pi_ScaleX(a0),a0        ;a0 = ScaleX (sxptr)

; a0 - sxptr
; a1 - ptr
; a2 - ColorInt ptr
; a3 - dmatrix ptr
; d0 - byte to set (x >> 3)
; d1 - width
; d2 - x
; d3 - dvalue (dmatrix[x & 3])
; d4 - Black
; d5 - sx
; d6 - bit to set

gwidth_loop:
        move.b  PCMBLACK(a2),d4         ;d4 = Black
        addq.l  #ce_SIZEOF,a2           ;advance to next entry

        move.w  (a0)+,d5                ;d5 = # of times to use this pixel (sx)
        subq.w  #1,d5                   ;adjust for dbra

gsx_loop:
        moveq.l #3,d3
        and.w   d2,d3                   ;d3 = x & 3
        move.b  0(a3,d3.w),d3           ;d3 = dmatrix[x & 3]

        cmp.b   d3,d4                   ;render this pixel?
        ble.s   gsx_end                 ;no, skip to next pixel.

        move.w  d2,d0
        lsr.w   #3,d0                   ;compute byte to set
        move.w  d2,d6
        not.w   d6                      ;compute bit to set
        bset.b  d6,0(a1,d0.w)           ;*(ptr + x >> 3) |= 2 ^ x

gsx_end
        addq.w  #1,d2                   ;x++
        dbra    d5,gsx_loop             ;sx--
        dbra    d1,gwidth_loop          ;width--

exit:
        movem.l (sp)+,d2-d6/a2-a3       ;restore regs used
        moveq.l #0,d0                   ;flag all ok
        rts                             ;goodbye

        END

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 SDK.

Includes
devices/printer.h
devices/prtbase.h
devices/prtgfx.h
AutoDocs
printer.doc

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