This article is concerned with how you go about writing a printer dumper module. This is a useful thing to be able to do, because the dumper interface unlocks most of the power in the bitmap side of RISC OS printer drivers. You can write dumpers for new printers, for sending printouts over a network or for driving novel equipment. Possibly the existing dumpers could be improved, providing new features.
This is necessarily a technical article, aimed at more experienced programmers. If you want a more accessible introduction to the way the RISC OS printer driver system works, refer to the companion article in this issue.
Printer Dumpers are described in chapter 65 (page 3-663) of the Programmer's Reference Manual (PRM). The next chapter, on the PDumperSupport module, is also required reading.
The overall picture is that a dumper module registers itself with RISC OS, providing an entry point which will be called when various actions need to be performed. Code is ready-written to provide many of these actions in the PDumperSupport module, so often your code will simply pass on requests to that module.
One problem with this exercise is that no example is typical; unlike most dumpers, the SPrinter dumper wants to preserve as much of the raw data it is passed as possible. Also, unlike most dumpers, it has another program waiting to process its output, so the SPrinter dumper can be very simple, passing on the complexities to the front-end program. I will try to point out where your dumper is likely to be different, comparing the SPrinter code with that of another dumper module I wrote for ArcFax.
These are the most important files in the source code directory:
| c.main | All the dumper-related code |
| c.map | An oddity for this particular dumper. Because in some situations we want to convert 24-bit colour into 256 colours, this file contains a ready-calculated table to do that. |
| cmhg.PDumperSP | This is a file that tells the Acorn CMHG (C Module Header Generator) how to make an o.PDumperSP file for the module. |
| s.Interface | Read on... |
One problem is that RISC OS expects to call an address in the dumper to tell it to do things. However, a program written in C wants the C stack etc. to be set up. How, then, do we get from the raw call to C? The technique used here is based on the model filing system source code that Acorn once made available to developers. This file of Assembler is the glue between a raw code call and calling a C function. The C function called is dumper_entry(); the code address called is veneer_dumper_entry.
This is not the only approach to this problem; in fact, maybe it's not the best. CMHG allows you to set up similar pairs of functions in your CMHG file to handle interrupts and call-backs, and the same feature can be used for this purpose. Perhaps the one thing in favour of the s.Interface file is that it will work with all versions of CMHG.
If you are lucky enough to have CMHG version 5.10 or later (I have 5.20) then, as it says in the documentation,
a new type of veneer has been added: the "generic veneer". These are designed as a replacement for the common use of IRQ-handlers as general-purpose hooks onto OS_AddCallBack etc. They are specified in the same way as IRQ-handlers, but using the keyword "generic-veneers:". They work in the same way, except that they either return preserving processor flags, or set V to return an error.
So I can have something like:
generic-veneers: upcall_entry/upcall_handler
in the CMHG file.
Turning to look at the files in more detail, this is the CMHG file:
initialisation-code: PDumperSP_initialise
service-call-handler: PDumperSP_service
title-string: PDumperSP
help-string: PDumperSP 0.00
PDumperSP_initialise is a routine that is called when the module is loaded. PDumperSP_service is the module service call handler.
Again, a couple of things could be done differently. Firstly, with RISC OS 4, fast service call handling came in: service calls are only passed to modules when needed.
This is the sort of CMHG command we need, with the service calls that are actually used detailed after the function name (again, it won't work on older CMHGs):
service-call-handler: PDumperSP_service 0x66, 0x67
Secondly, the dumper module needs a routine to be called when it is killed. This routine could be set up in the CMHG file like so:
finalisation-code: PDumperSP_finalise
But in this case we'll rely on a different technique.
From this CMHG code it is obvious that there are two entry-points into the C code: the initialisation routine and the service call handler.
This is the (pared down) initialisation code:
_kernel_oserror *PDumperSP_initialise(char *cmd_tail,int podule_base,void *private_word)
{
declare_Dumper(private_word);
atexit(Dumper_finalise);
}
This contains a routine to declare the dumper module to RISC OS, and a call to the C library to declare a function to be called when the module is killed. This is the alternative method of setting up a finalisation routine just discussed.
The service call handler is even simpler, and just calls the declare_Dumper() function when the Service_PDumperStarting service call is seen.
The declare_Dumper function is as follows:
static _kernel_oserror * declare_Dumper(void *private_word)
{
_kernel_swi_regs r;
r.r[0]=0x80000000;
r.r[1]=DUMPER;
r.r[2]=300;
r.r[3]=(int)private_word;
r.r[4]=(int)veneer_dumper_entry;
r.r[5]=0xFF; /* accept reason codes 0..7 */
r.r[6]=0x27; /* accept strip types 0,1,2,5 */
return(_kernel_swi(PDriver_MiscOp,&r,&r));
}
This is a single SWI call to declare the module's intentions to RISC OS. We tell it our unique centrally allocated dumper number (DUMPER). For SPrinter I recalled that Acorn had an unreleased sprite dumper module, and pinched its number.
In R6 and R5 you state the sort of strips that your module can handle and the actions it can perform (a strip, fairly obviously, is a bitmap chunk of the image that is being printed).
We tell RISC OS that the routine to call is veneer_dumper_entry(): as a result, the C function dumper_entry() will be called by the s.interface code, and we can set up a simple switch statement to route the various dumper actions to separate functions.
These actions are all documented in the PRMs and cover fairly obvious things like the start and end of the print job. You get all the information you would desire, a pointer to the PDF file contents, a file handle to use to write the result of the dumping process, information on the print job (such as the resolution), and where on the page the dump starts.
I don't see any point in reproducing all the PRM material here. Instead I'd like to cover some of the points which are not obvious in the PRM. As explained previously, many of the dumper actions map onto routines provided by RISC OS already, so your code only needs to call a SWI and return.
This is the startjob() function of the ArcFax dumper:
static _kernel_oserror * startjob(_kernel_swi_regs *r)
{
_kernel_oserror * err;
rx.r[0]=r->r[0]; /* (int)&anchor; */
rx.r[1]=0;
rx.r[2]=(int)palette;
err=_kernel_swi(PDumper_StartJob,&rx,&rx);
if(!err) err=link_alloc(ZWIDTH,r->r[0]);
return(err);
}
Notice that I call PDumper_StartJob. The interesting thing is the memory management provided for the dumper: on entry, R0 points to a private word for the print job, the meaning of which is defined by the dumper. However, here I pass it on to PDumper_StartJob, where it is used as an anchor word for memory allocated by the dumper. This private or anchor word is passed to all the dumper functions.
This is the memory allocation routine link_alloc():
static _kernel_oserror * link_alloc(int width,int anchor)
{
_kernel_oserror * err;
rx.r[0]=anchor;
rx.r[3]=XSIZE;
rx.r[4]=MYTAG;
err=_kernel_swi(PDumper_Claim,&rx,&rx);
if(!err) err=link_setup(anchor);
return(err);
}
MYTAG is an arbitrary tag value for the block. The PRM recommends not using the same value for two lots of memory, so just think of a number... XSIZE is how much memory is wanted.
Note: in my original version of the SPrinter dumper module, I wrote to the output file a block of information from the start-job entry point. Whilst tidying up the source code for this article it became apparent that no data was actually being written. This experience, and discussion with RISCOS Ltd, leads me to suggest that the file handle passed in R1 for this entry cannot be trusted.
Having allocated memory, we have to remember to deallocate it at the end of the print job. This is the routine to deallocate memory:
static _kernel_oserror * link_dealloc(int anchor)
{
_kernel_oserror * err;
rx.r[0]=anchor;
rx.r[2]=MYTAG;
err=_kernel_swi(PDumper_Find,&rx,&rx);
rx.r[0]=anchor;
err=_kernel_swi(PDumper_Free,&rx,&rx);
return(err);
}
It is called when the print job ends, and also if the print job is aborted:
static _kernel_oserror * abortjob(_kernel_swi_regs *r)
{
_kernel_swi_regs rx;
_kernel_oserror * err;
err=NULL;
if(r->r[3]) /* r3!=0 means end of doc */
{
link_dealloc(r->r[0]);
rx.r[0]=r->r[0];
rx.r[1]=0;
rx.r[2]=r->r[3];
err=_kernel_swi(PDumper_TidyJob,&rx,&rx);
}
return(err);
}
During operations in the dumper, when we want to use our allocated memory, code like this is used to find a pointer to it:
static _kernel_oserror * link_setup(int anchor)
{
_kernel_oserror * err;
rx.r[0]=anchor;
rx.r[2]=MYTAG;
err=_kernel_swi(PDumper_Find,&rx,&rx);
mychunk=(char*)rx.r[2];
...
}
One of the key operations is dumping a strip. For SPrinter, the game is to just output the raw data, but normally you'd use the supplied function PDumper_PrepareStrip to halftone the strip. Again, the following code is from the ArcFax dumper. For ArcFax, the point is to get everything down to 1bpp (black and white) and then output it. In that way, the ArcFax dumper is much like a real printer dumper. The ArcFax dumper actually applies Fax Group 4 2 dimensional coding inside the dumper, resulting in a much smaller output file to the original approach of handling a printer-style file of escape sequences and raw data.
The relevant ArcFax dumper code is as follows:
static _kernel_oserror * dumpstrip(_kernel_swi_regs *r)
{
_kernel_swi_regs rx;
char * p;
int i;
link_setup(r->r[8]); /* find our memory */
if(r->r[2] & 0x1) /* Greyscale 8bpp */
{
rx.r[0]=r->r[8]; /* anchor word */
rx.r[1]=r->r[0]; /* data */
if((r->r[6] & 0xFF)>1) rx.r[2]=0; /* code copied from LJ Dumper !*/
else rx.r[2]=1; /* output format */
rx.r[3]=r->r[3]; /* width in pixels */
rx.r[4]=r->r[4]; /* height */
rx.r[5]=r->r[5]; /* width in bytes */
rx.r[6]=r->r[6]; /* halftone info */
_kernel_swi(PDumper_PrepareStrip,&rx,&rx);
p=(char*)r->r[0];
/* now we have 1bpp data we can process ourselves */
}
else
{ /* 1bpp anyway */
/* we can process this direct */
}
return(NULL);
}
Here in the dumpstrip() function we have access to the raw rendered bitmap: just think of the possibilities!
For the SPrinter dumper, most of the dumper entry points output the data passed to the dumper module in a form that the front-end program can decode later. If you have the option of processing the data later, this is a reasonable approach.
For example, this is how the startpage entry point works:
static _kernel_oserror * startpage(_kernel_swi_regs *r)
{
_kernel_oserror * err;
int block[8];
block[0]=StartPage;
block[1]=6*sizeof(int);
block[2]=r->r[2];
block[3]=r->r[3];
block[4]=r->r[6];
block[5]=r->r[7];
err=fs_write(r->r[1],block,block[1]);
r->r[3]=0;
return(err);
}
We just pack the registers supplied to the dumper into a block of memory and then write it to the output file, sticking a made-up number (StartPage) in the first word, which will allow the front-end program to see what the block of data represents.
SPrinter is not bothered about where on the page the dump is taking place (in fact, it is useful to strip off as many blank pixels as possible) so R3 (which shows the number of blank pixel rows) is ignored and set to zero on return.
One area which the SPrinter dumper does not handle in the way I would wish is colour conversion. There are facilities in the Dumper interface to handle the palette files which come with !Printers. However, I've never been able to obtain documentation on how these work.
As a result, they can only be used if you just pass on the colour-mapping calls. This is how the ArcFax dumper does it:
static _kernel_oserror * matchcolour(_kernel_swi_regs * r)
{
_kernel_swi_regs rx;
_kernel_oserror * err;
rx.r[0]=r->r[3];
rx.r[1]=r->r[0];
rx.r[2]=r->r[2];
rx.r[4]=r->r[4];
err=_kernel_swi(PDumper_SetColour,&rx,&rx);
r->r[3]=rx.r[3];
return(err);
}
But they're no use if you want to do something more specific with colours. For example, with SPrinter it would be nice to use this code when converting a 24bpp strip into a 256-colour sprite. As you can see from the SPrinter code, I abandoned this and used my own code in c.map to do the work.
I know that Ace (who at one time enhanced !Printers, providing its background-printing facilities and adding palette file support), had a palette file editor. However, for now, all knowledge of palette files seems to have gone missing.
The palette filename is held in the PDF and, as you can see from the code, is passed to the dumper and so on.
To make use of my idiosyncratic way of linking and making, you should open a task window and set the current directory to the directory with the c, s, o and h directories in it.
Then, running the Obey file l will link the module. It uses the file list to specify the o files to link.
Running the mk Obey file will perform a make using my make program, mkcommand.
The mk file displays a fairly standard set of C compiler flags for producing a module.
Honestly, I did once study and understand the Acorn Make program, and I came to the conclusion that it couldn't do everything I needed and would be more complicated to use!