(c) Copyright 1989-1999 Amiga, Inc. All rights reserved. The information contained herein is subject to change without notice, and is provided "as is" without warranty of any kind, either expressed or implied. The entire risk as to the use of this information is assumed by the user. Writing a V1.3 Graphics Printer Driver by David Berezowski Under V1.3 of the Amiga system software, the printer device has been enhanced. This article gives you an overview of the changes and provides some of the information you will need to bring you V1.2 drivers up to date. Do not use this article alone as a 'how-to' reference. You should also review the source code for the V1.3 printer drivers on the devcon disk. These are good examples to work from and show the changes needed from previous Workbench releases. Additionally, the "ROM Kernel Reference Manual: Libraries and Devices" (chapter 15, Printer Device) should be used as reference material. Use this article as a supplement. Printer Driver Source --------------------- To help you in developing your own V1.3 custom printer drivers for the Amiga, the source files to the Workbench printer drivers are included on the devcon disk. All the Workbench printer drivers include six modules: data.c, dospecial.c, init.asm, printertag.asm, render.c, and transfer.c. Additional source code files may be required to handle the special capabilities of the printer you are working with. The chart below shows the extra files needed with the Workbench printer drivers: Printer Extra Source Files Printer Type ------- ------------------ ---------------------- ColorMaster prtready.c YMC_BW, multi-pass, page oriented ColorMaster2 prtready.c YMC_BW, page oriented EpsonQ density.c YMCB, 24 pin, multi-density EpsonX density.c YMCB, 8 pin, multi-density, interleaved densities HP_LaserJet density.c BW, multi-density, page oriented Okimate_20 none YMC_BW Xerox_4020 none YMCB, ink jet, uses run-length encoding V1.3 RENDER.C ------------- Render.c is the main module of a graphic printer driver. The original documentation for this module appears in the "ROM Kernel Manual:Libraries and Devices". Be sure to refer to the original docs as well as this article when writing you printer driver. Render.c now has seven cases. Before we consider the details of each case, there are two miscellaneous items not covered in the RKM that I want to mention. First, be sure the arguments to render.c - ct, x, y, status - are declared as longs. Second, when you call PWrite, check the return code. If it isn't PDERR_NOERR you must exit immediately. If you do not exit immediately, the user may not be able to cancel the printing. Case 5: Pre-Master Initialization --------------------------------- This is a new case and the first call you will get. The render.c parameters will be: 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 it makes this call with ct=0. This gives the driver a chance to to set up the density values before the actual graphic dump is called in a manner compatible with pre-1.3 software. The x parameter will contain the density and other SPECIAL flags which are defined in the file devices/printer.h. Currently the only flags that you will be interested in are DENSITY1 to DENSITY7. These are explained further under case 1 below. The flag SPECIAL_CENTER should be ignored as it will always be cleared. This flag is handled by the printer device. Never call PWrite during this case, unless you want to crash. When you are done handling this call, you should return PDERR_NOERR. Case 0: Master Initialization ----------------------------- The parameters for this case are: ct - a pointer to a IODRPReq structure. x - width (in pixels) of printed picture. y - height (in pixels) of printed picture. As under V1.2, you should allocate the memory that you will need to store the printer data at this stage. If the allocation fails, you should return a PDERR_BUFFERMEMORY. If the allocation succeeds, return PDERR_NOERR. This is also a good time to do any special one-time initializations to your printer or buffer. Some typical initialization codes you might send to the printer are: a) Carriage Return - some printers start printing graphics from where ever the printhead was last. Sending a CR assures that printing will always start from the left edge. b) Uni-Directional - if you allow the printer to print graphics in Mode bi-directional mode you'll find that it will tend to produce wavy vertical lines as the two passes won't put dots in quite the same place. c) Clear Margins - if your printer forces graphic dumps within text margins, you will have to clear the margins (set left to minimum, right to maximum) before doing the dump. Refer to the EpsonX or EpsonQ source for an example. d) Any other - enter graphics mode, set density, etc. set-up Do not reset the printer during master initialization since this will cause problems during multiple dumps. Also, do not use the value in ct unless your printer is one of those rare kinds for which a render function cannot be written. With the addition of the Case 6 call and the PCC_MULTI_PASS color class type, there are no known printers that need this value. But if you do use the value in ct to do the entire dump yourself then you must return an error of PDERR_TOOKCONTROL which tells the printer device that the dump has already been done and to exit gracefully. Case 1: Put Pixels in Buffer ---------------------------- ct - pointer to a PrtInfo structure. x - PCM color code for this pass if the printer is PCC_MULTI_PASS. The PCM defines from the file devices/prtgfx.h are PCMYELLOW, PCMMAGENTA, PCMCYAN and PCMBLACK. y - printer row # (range is 0 to height-1). This case has changed completely from the V1.2 spec in order to facilitate shorter dump times. In the call you are passed an entire row of YMCB (Yellow/Magenta/Cyan/Black) intensity values. To handle this case, you should call transfer.c, which you may remember is one of the required driver modules. This is described in more detail below. You should return PDERR_NOERR after handling this case. Case 2: Dump Buffer To Printer ------------------------------ The parameters for this case are: ct - 0. x - 0. y - # of rows sent (the range is 1 to NumRows). This call has changed slightly from the V1.2 spec and has some new recommendations. If your printer supports RLE (Run Length Encoding), now is the time to RLE your data. If your printer doesn't support RLE then your should white-space strip your data. This involves scanning your buffer from back to front for the first occurrence of a non-zero value. You should send only the data up to the first non-zero value. In other words, do not send trailing white space data to the printer. Use of either of these techniques will significantly reduce print times. Use the y parameter to determine how many pixels to advance the paper if your printer supports this feature. Implementing this feature will allow successive graphic dumps to butt up against each other. You should return the error from PWrite after this call. Case 3: Clear and Init Buffer ----------------------------- ct - 0. x - 0. y - 0. This call is made before each case 2 call. Clear your active print buffer - remember you are double buffering - and initialize it if necessary. You should return PDERR_NOERR after this call. Case 4: Close Down ------------------ ct - error code. x - io_Special flag from the IODRPReq structure. y - 0. This call is made at the very end of a graphic dump or if the graphic dump was canceled for some reason. You should free the printer's buffer memory at this point, but be careful. The print buffer's memory should only be freed if you allocated it. If PD->pd_PrintBuf is not NULL then you have allocated the memory, and must free it up. If your printer - usually a page oriented printer - requires a page eject command, do it here. But first check for the SPECIAL_NOFORMFEED bit in x. If it is set then you don't send the page eject command. If the error condition in ct is PDERR_CANCEL, then you should not PWrite. This code indicates that the user is trying to cancel the print due to a printer problem, paper out condition, etc. And for each PWrite you call, another printer trouble requester is generated. Obviously we want to avoid this. You should return either PDERR_NOERR or the error returned by PWrite if you called it. Typical reasons for calling PWrite in this case are: a) reset line spacing - if your printer doesn't have an advance n dots command, then you'll probably advance the paper by changing the line spacing. If you do, then you should set it back to either 6 or 8 lpi (depending on Preferences). b) bi-directional mode - set it back if you selected uni-directional printing in case 0. c) black text - some printers print the text in the last color used - even if it was in graphics mode. If you don't set the color to black, the text color after a graphic dump will be a surprise. d) any other one time - exit graphics mode, eject paper, etc. commands e) If you cancelled the margins in case 0, you should restore them here. Refer to the EpsonX or EpsonQ source code for an example. Case 6: Switch to Next Color ---------------------------- ct - 0. x - 0. y - 0. This is a new call provided to support 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. You will not have to handle this case unless you are writing a driver for a printer type of PCC_MULTI_PASS, such as the CalComp ColorMaster. PRINTERTAG.ASM -------------- Printertag.asm is a standard printer description file you should create for all your printer drivers. Here is a listing of the file and some typical values: MOVEQ #0,D0 ;in case any one tries to run us RTS DC.W VERSION ;must be 35 (for V1.3) DC.W REVISION ;your own revision number _PEDData: DC.L printerName ;ptr to name DC.L _Init ;ptr to initialization code DC.L _Expunge ;ptr to expunge code DC.L _Open ;ptr to open code DC.L _Close ;ptr to 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 ;ptr to Command Strings DC.L _DoSpecial ;ptr to Command Code DC.L _Render ;ptr to Graphics Render DC.L 30 ;Timeout DC.L _ExtendedCharTable ;ptr to 8BitChars table($a0 to $ff) DS.L 1 ;PrintMode (reserve space) DC.L 0 ;ConvFunc (ptr to char conversion function) printerName: STRING 'EpsonX' Be sure to set the VERSION field of printertag.asm to 35. This identifies it as a V1.3 driver. It is also important that you declare enough storage for this file. Notice that three new fields have been added since V1.2. These new fields are 8BitChars, PrintMode and ConvFunc. The 8BitChars field should contain a pointer to a table of chararacters for the ascii codes $a0-$ff. The symbols for these codes are shown in the "Amiga DOS Developer's Manual" Appendix. Make this field null (DC.L 0) if you don't provide the table. If you make the pointer null or use a VERSION number less than 33, a default table will be used instead. Creating your own table to handle codes $a0-$ff can be tricky 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-3 digit decimal number. To enter an actual backslash in the table requires the very awkward \\\\. Shown below are the first few entries from the EpsonX[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 */ /* more entries etc. */ }; The PrintMode field is a flag set by the printer device and used by you in DoSpecial and Render if you are working with a page oriented printer such as the HP-LaserJet. This field requires 4 bytes of storage (ie. DS.L 1). The ConvFunc field contains a pointer to a character conversion function that allows you to selectively translate any character to a combination of any other characters. If you don't have a translation function - most printers won't - then you must declare the pointer to be null (ie. DC.L 0). ConvFunc's arguments are a pointer to a buffer, the character in question, and a CR/LF flag. Your ConvFunc routine should return a -1 if you didn't do any conversion (ie. you want the character sent to the printer as is). Return 0 if you do not want the character passed to the printer. Or return the number of characters that you translated the original character into. The code for the conversion function can be put virtually anywhere (ie. data.c, dospecial.c, etc.). Here is an example of a simple conversion function: ConvFunc(buf, c, flag) char *buf, c; int flag; /* expand lf into lf/cr flag (0-yes, else no ) */ { int err; if (c == 0x7f) { /* change DELETE to */ *buf++ = '<'; *buf++ = 'D'; *buf++ = 'E'; *buf++ = 'L'; *buf++ = '>'; err = 5; /* I added this many chars to buffer */ } else { err = -1; /* I didn't do anything with the char */ } return(err); } Another possible use for this routine would be to support underlining on a printer that doesn't do automatic underlining. You could have an underline flag that would tell you if underlining was on or off. If off, your routine would do nothing and return -1. If on, your routine would put the character, backspace, and the underline character in the buffer and return 3. When expanding one character into several chars with ConvFunc, try to keep the expansions to a minimum. This will help throughput and reduce the possibility of data overrunning the printer.device buffer. One other note, I recommended that you pass 0x1b (ESC) and 0x9b (CSI) back to the printer device without processing them. Simply return -1. DENSITY.C --------- Density.c is a new printer driver module which implements multiple densities The handling of densities has changed quite a bit from V1.2. Look at the Workbench printer driver source files on the devcon disk for examples of how to create this module. Density.c gets called by render.c during pre-master initialization. The density.c code should select either the desired density or, if the user asked for a density higher than you support, the maximum density. For example, if your driver supports four densities, DENSITY1 through DENSITY4, and the user selects DENSITY6, then your code should select DENSITY4. The density.c code is also responsible for handling narrow and wide tractor paper. Refer to the EpsonX or EpsonQ density.c source code for an example of this. Density.c should not support densities below 80 dpi. This convention results in a minimum of 640 dots across for a standard 8.5 x 11 inch piece of paper (8 inches x 80 dpi = 640 dots) and gives a 1-1 correspondence of dots on the printer to dots on the screen in x under standard screen sizes. The HP LaserJet driver is a minor exception to this rule. Its minimum density is 75 dpi. This was done as the HP LaserJet (not the Plus or II) has so little internal memory that 75 dpi is the only density at which it is able to output a full page. TRANSFER.C ---------- This is the heart of the new printer drivers which consists of 2 parts - dithering and then rendering an entire row of source pixels. Dithering is the process of thresholding, grey-scale dithering, or color dithering each destination pixel. If PInfo->pi_threshold is non-zero, then the dither value is PInfo->pi_threshold ^ 15. If PInfo->pi_threshold is zero, then the dither value is computed by *(PInfo->pi_dmatrix + ((y & 3) * 2) + (x & 3)) where x is initialized to PInfo->pi_xpos and is incremented for each of the destination pixels. Since the printer device uses a 4x4 dither matrix, you must calculate the dither value exactly as given above. If you don't, you will be writing a non-standard driver and the results will be unpredictable. Rendering is the process of putting the 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 printer buffer. If it is less than the dither value, skip it and process the next pixel. Printer Type of ColorClass Dithering Rendering Logic ---------- ------------ ----------------------------------- PCC_BW Thresholding Test the black value against the threshold value to see if you should render a black pixel. Grey Scale Test the black value against the dither value to see if you should render a black pixel. Color NA (see HP_LaserJet transfer.c file) PCC_YMC Thresholding Test the black value against the threshold value to see if you should render a black pixel. Print a 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 a 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. (see CalComp_ColorMaster2 transfer.c file) 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 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 and repeat for magenta and cyan. (see EpsonQ, EpsonX or Xerox_4020 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 for magenta and cyan. (see Okimate_20 transfer.c file) 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. Also I recommend that you construct your print buffer so that the order of colors printed is yellow, magenta, cyan, black. This is most important on color ribbon printers because it reduces the amount of ribbon color contamination. If you print black and then yellow, the yellow ribbon will get some black ink on it. The bulk of the execution time of a graphic dump is spent in this call. So this module is an excellent candidate for down-coding to 68000 assembler. Experiments here at Commodore have shown that down coding transfer.c can halve the time for a graphic dump. DOSPECIAL.C ----------- The dospecial.c module implements the special ANSI functions which cannot be handled by substitution. For instance, if you are writing a driver for a page oriented printer such as the HP LaserJet, you should delete the dummy Close routine from the init.asm file and insert a real Close routine in dospecial.c. The Close routine is responsible for ejecting the paper after text has been sent to the printer and the printer has been closed. Some printers lose data when sent their own reset command. For this reason, I recommend that if you are using the printer's own reset command that you define the variable PD->pd_PWaitEnabled to be a character that the printer will not print. Put this character in the reset string in data.c before and after the reset character(s). For example, in the EpsonX dospecial.c source code you will see the line: if (*command == aRIS) { /* reset command */ PD->pd_PWaitEnabled = 253; /* same as \375 */ } and in the data.c file the string for reset is "\375\033@\375". This means that when the printer device goes to output the reset string "\033@", it will first eat the "\375" and wait one second. The "\033@" will be passed and the printer will reset. Finally, the printer device will eat the second "\375" and wait one more second. This insures that no data will be lost if a reset command is imbedded within a string as in "Hello \033#1world", where \033#1 is the code for reset. DATA.C ------ The data.c module implements the special ANSI functions which can be handled by simple substitution. It also contains the extended character translation table if you choose to include it. Data.c has been changed a little for V1.3. Typefaces --------- There is now support for selecting different typefaces. Due to the extended character translation table, the character set selection commands (aFNT0 - aFNT10) are no longer really needed. If you want your driver to support different typefaces you can use the FNT commands to select typefaces 0 through 10. Please include comments in the data.c file that tell which typefaces are selected by which commands. The recommended standard for for selecting typefaces can be found in devices/printer.h. Raw Mode: --------- There is now an escape sequence command (aRAW) which sets the printer device into raw mode for a given number of characters. In raw mode, no translation of the data is done. This command can be used to send commands directly to the printer from the CLI. The format is ESC[Pn"r where Pn is an ascii digit argument specifying that the next Pn characters are not to be translated, but sent directly to the printer. For example, ESC[5"r means that the next 5 characters are to be sent directly to the printer. For future compatibility, you should also add a dummy entry of \377 in the data.c table. TESTING ------- A comprehensive test of your printer driver is an important step and I have some recommendations. First, do a black and white (threshold = 7), grey scale, and color dump of the same picture. The color dump should be in color. The grey scale dump should look like the color dump except it will consist of patterns of black dots - sort of like looking at a BW television. The black and white dump will have areas of solid black and areas of solid white. Next do a dump with the DestX and DestY dots set to an even multiple of the XDotsInch and YDotsInch for your printer. For example, if your printer has a resolution of 120 x 144 dpi, do a 480 x 432 dump. The printed picture should be 4 x 3 inches on the paper. If the width of the picture is off then you have put the wrong value for XDotsInch in printertag.asm. If the height of the picture is off then you have put the wrong value for YDotsInch in printertag.asm. Test your driver at its maximums. Do a color dump as wide as the printer can handle with the density set to 7. Use the PrinterTest program on the devcon disk to give the alpha commands a real workout. Check to make sure that your printer doesn't force graphic dumps to exist within text margins. An easy test would be to set the text margins to 30 and 50, pitch to 10 cpi and then do a graphic dump that would be wider than 2 inches. If the printout is left justified and is as wide as you asked for then all is fine. If the printout starts at character position 30 and is cut off at character position 50 then you'll need to make some changes to your driver. The changes involve clearing the margins before the dump (case 0) and restoring the margins after the dump (case 4). Refer to the any of of driver's render.c source code for an example. Note that before the graphic dump you must send some text (even just a carriage return) to the printer to get the printer device to initialize the printer. Optionally, you could invoke the InitPrinter program before the graphic dump. As a final test, construct an image with a white background that has objects in it surrounded by white space. Dump this as a BW, grey scale, and color printout. This will test your white-space stripping or run-length encoding and your ability to handle null lines. Note that the non-white data areas should be separated by at least as many lines of white space as the NumRows value in your printertag.asm file. The diagram below should help. Display Area --------------------------------------------------------------- | | | ----------------------- | | | | | | | non-white data | | | | | | | ----------------------- | | | | white background | | | | ----------------------- | | | | | | | non-white data | | | | | | | ----------------------- | | | ---------------------------------------------------------------