File: ~\Acorn\HowToWritePDumpers
Author: G.J.Stark
Date: 20 December 1990
Revision:
0.00 GJS 20-12-90 Written first draft
0.01 GJS 23-01-91 Updated and cleaned up

Layout:

  1. PDumper modules - what they are
  2. Standard PDumper module base
  3. Simple ways of writing PDumper modules
  4. Writing a PDumper module from scratch
  5. Layout and handling of the PrData information:
    1. PDumper modules - what they are:

      In the olden days there were four printer driver modules - one each for: Epson printers; Integrex printers; HP LaserJet printers; PostScript printers. The upkeep on such a large amount of source code was tremendous, and, considering that a lot of the code was common between the printers an effort was made to introduce a "common" chunk of source code (printer independent), and printer dependent portions to handle those parts which operate differently on different printers. Hence the split in the standard source code between the large (240K), well documented PDriver source file and the other code in the printer drivers individual directories. This split in source code is handled by having the large independent source file 'GET' the appropriate printer dependent source files at appropriate points in its body code. The files which are included contain code for: colour setting, job management, vdu 5 output, sprite output, font output, draw module output, and so on.

      It was pointed out in January 1990 that most of the printer dependent parts of the Epson, Integrex and HP LaserJet drivers were similar, or indeed identical. The idea of making more common code was mooted, and methods of doing this were discussed. The first option was to do as had been done for the main source code - split it up at the source code level, and assemble a different printer driver module for each printer. This is clearly a very heavyweight option - and it makes it very difficult for Acorn to transmit updates of the main printer driver code and have ISV's update their products.

      Another difficulty with the printer driver source code that Acorn was using in 1990 was that ISV's had to produce complete printer driver modules, which involves understanding over 400K of source code - 20,000 lines, at a rough estimate. Not many ISV's were willing to undertake this task - which meant that Acorn was the main support for printers. Customers were already noting that Acorn did not support the IBM printers, and with Canon introducing new printers which were IBM compatible it was becoming important to produce a printer driver for the IBM printers. Acorn could do without having to follow after the whims of other manufacturers - the ISV's also wanted to handle this kind of project.

      So the PDumper module concept was invented. The idea of the PDumper was to provide a low volume of source code per new printer supported. The code would contain just those items of interest to the printer - colour selection, page handling, and bitmap outputting. The main printer driver module (PDriverDP) would just link into the appropriate PDumper module when it needed to select a colour or whatever. In this way the printer independent code is kept in a different module to the printer dependent code, as opposed to joining the two pieces of code at assembly time. The volume of code required to be understood by someone writing a PDumper modules is just that which makes up the PDumper module - a few tens of K long.

      The PDumper modules were designed by examining the three bitmap printer drivers that Acorn had at the time, and selecting the similart parts of the source code together to form the PDriverDP module. In fact this turned out to be putting hooks into colour setting, bitmap line outputting, page handling, job handling and the service routine. Work was required on the service routine to allow PDumper modules to be resident all the time and declare themselves to PDriverDP when it started up - hence a new PDriverStarting service call was added, and the PDumper modules link in at that time. Also, when a PDumper module starts up it calls a SWI to declare itself to the PDumper module, if there is one present, thus ensuring the PDriverDP module always knows precisely which PDumper modules are present at any time.

      This document is structured in a way to help software designers produce new PDumper modules for new printer models and ranges, starting with a discussion as to how the PDumper modules fit in with the PDriverDP module and the standard PDumper module base, leading on to writing a new PDumper based on another PDumper module, and ending on how to write a PDumper module from scratch.

      2. Standard PDumper module base:


      The standard PDumper module base provides a simple link between the necessary module header data (needed for all relocatable modules) and module call handling (service calls, initialisation, finalisation etc.), and the actual routines which handle the colour setting, bitmap outputting, etc..

      The actual code is held in Common.Header, Common.BranchCode and Common.Macros, with the module workspace layout defined in Common.Workspace. The interface between the printer dumper module and the printer driver module (PDriverDP) is handled in Common.Header, in the initialisation, finalisation and service call handling. The printer dumper declares itself with the PDriver_SetPrinter call with reason code 0. It undeclares itself with PDriver_SetPrinter with reason code 1. During the initialisation and finalisation these calls are used with SWI's. During the service call handling, however, (when PDriverDP has issued SWI OS_Service to produce Service_PDumperStarting or Service_PDumperDying and is initialising or dying and hence not on the module chain) it must perform the call using values passed in by PDriverDP in the service registers.

      The reason codes for PDriver_SetPrinter for PDriverDP are as follows:

      R0 = 0 - Declare PDumper module

        R1 =         { This stuff is unnecessary at the moment - and I need a longer
      
      R2 -> look at the source code }
      R3 ->

      The code specified in the declaration of the PDumper module is very similar to a module's SWI handling code - indeed, the entry conditions are identical and the model used for the handlign code in the PDumper module base, Common.BranchCode is standard SWI handling code.

      The branch code handling is done on more than a per-reason code basis. The reason code for the branch is passed in using register ZZZZ, and this is then handled using the output strip type as well. This means that different routines are called for SetColour to monochrome and SetColour to 256 colours. Obviously the ReadInfo, SetPrinter etc. calls which are strip-type independent are not called in this manner, but the main reason codes are. The routines which are needed per strip type (minimally) are OutputDump, ColourSet, StartJob, StartPage, AbortJob and EndPage. When these are split up between different strip types we get:
      OutputDump_Code_mono - for one bit-per-pixel dumps; OutputDump_Code_greys - for 256 grey-scale dumps; OutputDump_Code_256 - for 256 colour output dumps; OutputDump_Code_24bit - for 24bit colour output dumps (not supporterd yet)

            and so on for each of the routines.
      

      Many of these routines will be the same (StartPage is probably the same for all types of strip, for example), but they are not forced to be by the module base.

      As can be seen from above, the standard PDumper module base provides the interaction between PDriverDP and the output and colour handling routines for each of the four (three currently) possible output types. This is handled in an expandable and easy-to-use manner.

      3. Simple ways of writing PDumper modules:


      Writing a PDumper module can be done in three ways: a) Basing the code on another PDumper module, just rewriting the output routines.
      b) Basing the code on another PDumper module, rewriting colour setting and output routines.
      c) Writing the PDumper module from scratch, just using the standard PDumper module base as a starting place.

      Obviously the simplest of these is option (a), just rewriting the output routine. This will probably be sufficient in most cases, as it has been for writing the Integrex driver from the Epson/IBM driver.

      The only output routine which may requiring changing is OutputDump, kept in the file Sources.PDumperXX.OutputDump. There are in fact three routines in this file, OutputDump_Code_mono, used for monochrome (1 bit-per-pixel) outputs, OutputDump_Code_greys, used for 256 grey-scale outputs, and OutputDump_Code_256 which is used for 256 colour outputs. I will go through these in order:

      OutputDump_Code_Mono:
      Entry:
      R0 -> bits making the strip data
      R1 = file handle of print job
      R2 = strip type, 0 in this case
      R3 = dump width in bits
      R4 = dump height in rows
      R5 = dump width in bytes, >=8*R3
      R6 = x halftone width in byte 0, y halftone height in byte 1 R7 -> job workspace layout, as in Common.Header R8 -> private PDumper word for this job Exit:
      All registers preserved
      Operation:
      This routine will output a monochrome (1 bit-per-pixel) strip to the printer at the current spot, indented by the left margin size. This may be done in many passes to provide interlacing, high resolution, etc. The standard method is this:

           Until all the rows have been printed from the strip
             Do the correct number of vertical interlaces
               Do the correct number of horizontal interlace
                 Output graphic line start data
                 For all the horizontal positions in the chosen strip, accounting for
                 horizontal interlace
                   Work out the data for one print-head high set of rows (24 pin, 9
                   pin, 8 pin, 1 pin, whatever), allowing for vertical interlace
                   Print out the data to the file
                 Move on to the next horizontal position, accounting for interlace,
                 and then loop
               Move along by a horizontal interlace position
               If not finished interlacing print carriage-return and loop for next
               horizontal interlace
             Print correct line ending depending on vertical interlace stage
             If not finished vertical interlacing then loop
           Loop until all the dots in the strip have been handled.
      

      Note:
      i) The strip depth should be a multiple of the print head size * vertical interlaces. This should be ensured by the PrData file, which will be correct provided the correct data is in the !PrChooser's data files. ii) The horizontal and vertical interlace sizes are passed in using R6. This data will have been gleaned from the PrData file, as above. iii) There are standard macros for outputting data to the file. These can be used by including the lines:
      PDumper_EnterRoutine "R0-R9"
      STR R1, file-handle
      PDumper_ResetBuffer
      at the start of your output routine, and PDumper_ErrorHandle
      PDumper_EmptyBuffer
      PDumper_ExitRoutine
      at the end of your routine. The output macros are listed in the file Common.Macros, and include:
      PDumper_OutputReg <register>
      to output the byte value of the register to the file; PDumper_PrintCountedString <source>, <dummy>, <dummy2> to output a byte string from memory of length source[0] from source[1] onwards;
      PDumper_PrintBinaryPair <register>, <dummy> to output lo-byte hi-byte of <register> to the file. These routines cannot be called recursively, that is you may not call a routine which uses PDumper_ResetBuffer from another routine which has called this. Indeed, PDumper_ResetBuffer may only be called from the outer program layer which is called from the main PDumper module base code, due to error-handling restrictions. These macros provide a self-buffering service, to speed up output to the printer instead of using single OS_BPut calls.

      The best way to find out everything fits together is by examining the code for the PDumperJX module. Find the code OutputDump_Code_mono in the file Sources.PDumperJX.OutputDump. Note the call to PDumperEnterRoutine at the start to preserve the registers on entry, and the call of PDumper_ExitRoutine at the end to recall those registers preserved by PDumper_EnterRoutine. Note also the STR R1,filehandle and PDumper_Buffer routines as described in note (iii) above. The actual output is done in the routine output_mono_sprite, about 100 lines below.

      This routine works through the structure we have above. First of all preserve the return address and get R7 to point at the real printer dumper data, not the strings. Then enter the main loop. R2 is the vertical interlace position, starting off at 0. Then enter the vertical interlace loop, and get R5 to be the byte difference between successive rows of the strip to be printed, accounting for the vertical interlace. The start the horizontal interlace loop. Note R9 is the loop counter. Output the start-of-line data, a counted string from the standard workspace defined in Common.Header. Then work out the required lo-byte hi-byte pair to print. This uses the dlm*width+dla formula, where dlm is the data-length-multiplier, and dla is the data-length-adder. This is to allow for Epson printers (dlm=1,dla=0) and IBM printers (dlm=3,dla=1). Then finish off the start-of-line data (null string on EPSON's, but a graphics string on IBM printers). Run through the line of data putting out zero's if on the wrong horizontal interlace line, else find the data from the strip data and output that. (Note this uses dht, data-height, rows up - 1 for 8-pin, 3 for 24-pin printers.) After finishing all the horizontal loop, check to see if there is another horizontal interlace loop to do by decrementing R9. If there is, output a carriage-return (or rather, pd-data-line-return) and repeat the horizontal run of data using the new R9. Else continue by printing the end-of-line data for the specific y-interlace we are on.

      Perhaps the easiest way to write an ouput dump routine is by ignoring the interlace factors to start with, and to add them later.

      Now look at the routine OutputDump_Code_greys. There is a small section marked 'fiddle factor' - this is to cope with outputting dumps which are not multiples of the dump dpeth (print head size * y interlace) high. There follows a call to either:
      dither_grey_sprite
      Used to convert a sprite with pixels in the range 0 to 255 to one using
      on-offs with halftoning. This is used if the halftoning width is > 1.
      OR error_diffuse_grey_sprite
      Used to convert a sprite with pixels in the range 0 to 255 to one using
      on-offs with Floyd-Steinberg error diffusing. This is used if the

              x halftoning width equals 1.
      

      The halftone size chooses between these two methods of handling 256 grey-scale sprites. The input to these routines (which are in Common.Manipulate) is a 256 grey-scale sprite, and the output is the same sprite memory in 8 bits-perpixel mode, but with each pixel converted to either 0 or 255. This gives an on-off effect suitable for the printer.

      The actual output routine for 256 greyscales is output_grey_sprite. This routine can be almost identical to that used for monochrome sprites - the only difference is that instead of testing bits to see which printer pixels need to be set, you check which bytes are 255 or 0. Compare the two routines in the code for PDumperJX.

      Actually, you will notice that there is another additional complication for the 256 grey-scale output. This is because EXACTLY the same routine is used for 256 colour output. I will now explain how this can be done.

      The routine OutputDump_Code_256, for outputting 256 colour sprites, is very similar to OutputDump_Code_greys. Indeed, you can replace every occurrence of 'grey' in the latter with 'colour' to arrive at the former! This means there are two routines, dither_colour_sprite and error_diffuse_colour_sprite which convert from 256 colour sprites to a form the output routine can use. The same method is used for deciding between these two routines - that is the size of the x halftone patterning. The output from these routines suitable for the printing routine is the same sprite memory in 8 bits-per-pixel, but (unlike the grey routines) with up to 4 bits set. These bits are:

      Bit Meaning when set

           0       Use colour ribbon (or ink) 0 = yellow on this pixel
           1       Use colour ribbon (or ink) 1 = magenta on this pixel
           2       Use colour ribbon (or ink) 2 = cyan on this pixel
           3       Use colour ribbon (or ink) 3 = black on this pixel
      

      The output dump routine is then almost exactly the same as that for monchrome sprites, except that there must be four passes per x interlace - to handle the four different colours - and the test to see whether to set a pixel on the printer is done by testing the correct bit in the sprite data. You should see that it is easy for this routine to be used for grey sprites as well, just by setting the number of ribbon passes to 1 and using bit 0 to store the on-off pattern for the black ink of the printer.

      So, writing a new set of output routines for a new printer just involves writing an output dump routine for a simple on-off printout, and then enhancing it to give a new routine which can do many pass output. All the other necessary routines are alerady provided.

      4. Writing a PDumper module from scratch:


      Of course, there may be some people who are not happy with the output produced using the Acorn error-diffusion or halftoning routines. These people can write their own colour handling, output manipulation, or other routines. However, they might as well start with the module base and branch code routines. You should note that this is not necessary - look at PDumperJX.BranchCode. You can replace the LNK with your own branch code handling routines. Similarly with Init, Header, Macros, and all the other files. I would not recommend this, as the standard Acorn routines should suffice.

      The first new possibility is to redefined the manipulation routines - dither_grey_sprite, dither_colour_sprite, error_diffuse_grey_sprite, and so on. This can be done by copying Common.Manipulate and updating it with your own routines - either to use better halftoning, error diffusion or whatever. It could well be that this requires new colour setting code, since the palette and the manipulation routines are very closely linked. So, take a look at PDumperJX.ColourSet. This provides routines for returning a printer sprite colour from a 24-bit true RGB colour.

      First look at ColourSet_Code_mono. This routine calculates the average of the red, green and blue components to give a value in R4 from 0, black, to 255, which is white. This is then inverted to give a range of 0, white, to 255, black. This valus is then reduced to the range 0 to 64, and again to the range 0 to 16, and finally to 0 or 1. The three results are stored in R3 on exit in the form #00LLSSCC (LL = large halftone pattern number, the value from 0 to 64, SS = small halftone pattern number, the value from 0 to 16, and CC being the 'no-halftone' number, 0 or 1). PDriverDP picks out the appropriate one of these and uses it for plotting. You should note that this routine does no colour calibration (or grey-scale calibration) at all.

      Now look at ColourSet_Code_greys. This routine must return a single value in the range 0 to 255 in R3, given a true RGB colour. To do this it uses a standard formula to convert from RGB to luminance: 77*red+150*green+28*blue. This luminance value is then calibrated according to the printer calibration (kept in the palette file at offset 16+64) if the output is error-diffused, or left alone if halftoning (as calibration takes place at a later stage for this - just before outputting). The calibration is done using the ColourTrans SWI, taking R1 = #BBGGRR00 as input and returning R2 = #BBGGRR00 after calibration. This routine can easily be changed to do something different if you truly desire, perhaps using a different calibration method, or a different luminance equation, or something. But bear in mind that the printer does bot reproduce greys in a very wide range, so this routine can be very approximate in its operation.

      Finally look at ColourSet_Code_256. This routine must return a single value in the range 0 to 255 in R3, given a true RGB colour. This it does by searching through its palette of 256 colours for a best-match. The best match is defined as the colour with the least (R-r)*(R-r) + (G-g)*(G-g) + (B-b)*(B-b), where R,G,B are the source colour, r,g,b the matching colour. There is another complication here - all grey colours are matched with colours which are truly grey, i.e. a combination of black and white. This is because the user does not want a grey he has chosen (R=G=B) to appear as a shade of yellowy-brown! This effect was very noticeable before the feature was added. Note that the palette specified will have already been calibrated for the printer - in the same way as screen calibration is handled.

      It is clear that this last routine can be improved upon - perhaps by adding cacheing of colours, dynamic selection of colours from a palette larger than 256, or other fancy features. Especially after you have seen the ouput produced on an Integrex printer. So, enhance it if you like. This may well force you to have different output or manipulation routines, as specified above. Also, you may have to change the workspace layout, defined in PDumperJX.Workspace, and then you may have to change the job workspace allocation in the StartJob source file.

      5.      Layout and handling of the PrData information: