The RISC OS Printing System (part 5)

In this part, I am going to describe how to use the printer SWIs to produce a printed page. I will use Basic since everyone has this in their machines. Those of you who like C should be able to conve t the program. I will try and make the routine as simple yet general as possible without 'cutting corners'.

Many books explain how to produce printed output but the routines described are only useful as demos. I hope the application on this month's disc will be useful as well as instructive.

The application produces a printed output of all the known file$types with their sprites. If the sprite has not been seen, a white rectangle is plotted. Clicking <menu> on the application's ico on the iconbar produces a small menu. Selecting the Print option produces output on the printer, if the printer is connected, or in a Wimp window on the screen. It should be compatible with RISC&nbs ;OS 3.1 upwards. This project includes the printing of fonts and sprites, both of which require colour translations, and the use of graphic plotting techniques.

The structure of the program is as follows:

Initialise variables
Startup Wimp
Load Window Templates
While still running
poll wimp
Endwhile
Closedown wimp

The main procedures are:

PROCinit sets up all the required global variables and blocks of static memory. It also initialises the WIMP, reads in window templates, places the icon on the iconbar and declares the fonts.
PROCpoll - Wimp poll, i.e. rest of main program.
PROCmenuinit and other menu functions and procedures produce the iconbar menu and its required actions.
FNerror takes care of the Wimp error and message window calls.
PROCsetdefaultpals sets up a block of memory containing sprite default palettes.

Then come the relevant procedures.

PROCstartprint starts up the printing protocol (see later).
PROCprinterr takes care of any possible printing errors.
PROCdeclarefont does just that (PostScript only).
PROCprintpage starts up the printer job, produces the printed output and closes the printer job.

You can look at the listing to see how all of the above work. I will only describe the printing procedures in detail.

In previous articles, I said that printing used a similar programming structure to drawing in Wimp windows. This is true for the most part but with some extra facilities added which sometimes cause c nfusion. The page to be printed is defined in rectangles (usually just one) using SWI "PDriver_ GiveRectangle". The registers for this SWI need careful describing.

R0 is the rectangle number - usually zero if there is only one.
R1 points to a 4-word (16 byte) memory location which contains the coordinates of the rectangle being defined.
R2 points to a 4-word memory location which contains the translational information on how to print the page.
R3 points to a 2-word memory location which contains the bottom left coordinates where the rectangle is to be plotted on the page.
R4 is the colour for the background (usually white) in &BBGGRR00 palette entry format.

Referring to the diagram above right, consider the page to have one rectangle the size of the printable area; the bottom left coordinate must be the lowest most leftward printable location on the pag (0,0). All printers have differing margin sizes so how do we get the required data? Luckily, there is SWI "PDriver_PageSize" which returns the following: (Note the data starts at R1 not R0!)

R1 x size of paper (including margins)
R2 y size of paper (including margins)
R3 left edge of printable area
R4 bottom edge of printable area
R5 right edge of printable area
R6 top edge of printable area

All these measurements are in units called millipoints (see later).

Using this data, we can determine the size and position of the printable area. Once this is done, all graphic plotting has an origin relative to the bottom left corner of this rectangle. This or gin is not the same as the origin used in window drawing (usually top left), so I plot from an origin at top left by defining xo% as left margin position (in OS units!) and yo% as the top e ge. This means the same code can be used for printing as well as window drawing.

There is just one problem in all this - the units. The page size and 'plot at' data are in millipoints whereas the dimensions of the rectangle are in OS units. Luckily, 400 millipoints = 1 OS unit, s conversion is easy. The relevant code to do all this is in PROCprintwindow, the main lines being:

SYS"PDriver_PageSize" TO ,xpg%,ypg%, ledg%,bedg%,redg%,tedg%
rect%!0=0:rect%!4=0:rect%!8=(redg%-ledg%)DIV400:rect%!12=(tedg% bedg%)DIV400
plotat%!0=ledg%+8:plotat%!4=bedg%+8
trans%!0=1<<16:trans%!4=0
trans%!8=0:trans%!12=1<<16
xo%=4:yo%=(tedg%-bedg%)DIV400-4
REM xo%,yo% top left of rectangle
REM to plot as per window drawing.

Note: The actual 'plot at' position and the (xo%,yo%) origin are moved slightly into the rectangle. This is to allow for some printers clipping the edges of the printout.

Now for the translation

Formulae are used to calculate actual plotting positions from the given plotting positions. The formulae are

actual_x_position = (given_x*trans%!0 + given_y*trans%!8)/2^16
actual_y_position = (given_x*trans%!4 + given_y*trans%!12)/2^16
(2^16 is 2 to the power 16 which equals 65536)

This means the printed image can be scaled and rotated as required, but be careful because some printer drivers only allow rotations in multiples of 90 degrees. In my example the formulae reduce to

actual_x_position = (given_x*2^16 + given_y*0)/2^16 = given_x
actual_y_position = (given_x*0 + given_y*2^16)/2^16 = given_y

Hence there is no translation. You can alter the values and see the effects!!!

Now for the printing...

First, check the features of the selected printer.

SYS "PDriver_CheckFeatures" TO ,,,n%
psf%=((n% AND (1<<29))<>0)

Here we check the flag for PostScript printers, i.e. does the printer support PDriver_DeclareFont? Next open the printer file and select the job.

f%=OPENOUT("printer:")
SYS"PDriver_SelectJob",f%,bk%+44 TO prvjob%

R0 is the file handle
R1 points to a block of memory that contains a title (not printed and not essential) for the job.

The data returned in R0 is the file handle of any previous job (usually zero denoting no previous job).

Now declare any fonts (if required). This is done by calling SWI"PDriver_DeclareFont" with the following data:

R0 is the font handle, or zero if using a font name in R1
R1 points to a font name or zero if using a font handle in R0
R3 flag word bit 0 set means don't download font and bit 1 set means use kerning with this font. All other bits must be zero.

This SWI must be called for each font to be used and then called with all registers zero to denote the end of the list.

IF psf% THEN PROCdeclarefont("Trinity .Medium"):PROCdeclarefont("")

Now we allow for any errors that might occur when printing. This can be tricky, but I have found the following works in most cases. Set up a local error handler which restores the error handler, call a procedure, then exits the printing procedure.

LOCAL ERROR
ON ERROR LOCAL:RESTORE ERROR: PROCprinterr:ENDPROC

In the error procedure do the following: abort the printing, send characters 12 and 13 (form feed and flush the printer buffer), then close the printer file and report the error.

SYS"PDriver_AbortJob",f%
BPUT#f%,12:BPUT#f%,13
CLOSE#f%
perr$=REPORT$+"("+STR$(ERL)+")"
q%=FNerror(perr$,0,TRUE,TRUE,TRUE)

Now we set up the printable rectangle as described previously.

SYS"PDriver_GiveRectangle",0,rect%,trans%,plotat%,&FFFFFF00

Next is the start of the printing loop using SWI"PDriver_DrawPage" where

R0 is the number of printed copies required.
R1 points to a block of memory to store the 'sub rectangles'.
R2 is the page sequence number (=1-n for an n page document).
R3 points to a string for a page number to be printed (no spaces allowed).

In this example, R2 and R3 are set to zero denoting no page sequence number or page number. This SWI divides the defined printable area into smaller rectangles. The data returned in R0 is non zero if more 'sub rectangles' require to be printed.

The printing loop contains all the plotting commands required for the printed page.

WHILE more%
SYS"ColourTrans_SetFontColours",fonthnd%,&FFFFFF00,&0,14
SYS"Font_Paint",titlefonthnd%,"Known File Types",16,xo%+12,yo%-64
SYS"Font_Paint",titlefonthnd%,"Number known="+STR$(noics%),16,xo%+400,yo%-64
SYS"Font_Paint",titlefonthnd%,"Page "+STR$(page%)+"/"+ STR$(nopages%),16,xo%+800,yo%-64
PROCprinticons((page%-1)*66,xo%,yo%+64,66)
SYS"PDriver_GetRectangle",,bk% TO more%
ENDWHILE

This loop will be repeated until no more 'sub rectangles' are required. SWI"PDriver_ GetRectangle" checks this condition in a similar way to SWI"Wimp_GetRectangle" in WIMP window drawing. This SWI al o returns, in R2, the rectangle identification word, containing the rectangle (not used).

Note that any Colourtrans SWIs must be placed inside the loop since the tables are corrupted in the printing process!! Other than this, any legal graphics output SWIs may be used as long as they make sense for printing (see previous article on SWIs).

PROCprinticons plots each file icon with its file$type number and its filetype string. If the file icon has not been seen, a white rectangle with a black border is plotted using the standard RECTANGL FILL and RECTANGLE commands.

This procedure is quite involved since sprites of different modes and user defined palettes have to be catered for as well as sprites without palettes. The method used here is to find the a dress of the required sprite (if it exists) in the Wimp sprite area. Then use the standard ColourTrans_SelectTable and OS_Sprite SWIs (see PRM vols 3 and 1).

The loop is exited when the printing is completed, so end the job, close the file and restore the error handler.

SYS"PDriver_EndJob",f%
SYS&406C1
CLOSE#f%
RESTORE ERROR
Other hints

I have often wondered why Acorn omitted code to detect the printer connections before any printing starts. There is a simple way to detect the connection and it is included in this applicat on. Consider the following code.

DEF FNprinter_not_connected
LOCAL Jb%,Jc%
Jb%=ADVAL(-4)
VDU2,1,0,1,0,1,0
TIME=0
REPEAT
UNTIL TIME>20
Jc%=(ADVAL(-4)<Jb%)
OSCLI("FX21,3")
VDU 3:=Jc%

This first finds the number of bytes free in the printer buffer then writes three zero bytes to the buffer (zero bytes are safe since they do nothing to the printer). After allowing a short time for the printer to accept them (if connected) check the number of bytes free again. If the zeros have been accepted (connection OK) then the answer is the same as before. If not, the answer is less t an before. The variable Jc% is set to TRUE for no connection and FALSE for OK connection.

The printer buffer is then flushed. This method does not detect, for all printers, if the printer is off-line or if there is a fault with the printer, but it is simple and is better than no check at ll.

Well, that's about it for this month! You can try modifying the code in the loop to produce different output (if anything really goes wrong, pressing <escape> should abort the printing).

Next time, I will look at a general printer connection checker extension for the RISC OS printing system.


Source: Archive Magazine 13.8
Publication: Archive Magazine
Contributor: Brian Pickard