Beginner's Guide to WIMP Programming
Everything you need to know to start writing your own applications.
19: Getting Into Print
The previous section referred to font handling as one of the superior features of RISC OS. Another is its standard printer driver interface. Any piece of software which runs under RISC OS can print on any type of printer for which you have the appropriate printer driver software. The printing process is the same whether you're using a dot matrix, laser or PostScript printer or even an X-Y plotter!
So how do we go about writing a procedure to send our lines, squares and rectangles, not to mention text, to a printer? The answer is that we don't need to; we already have one!
The process of printing basically consists of telling the printer driver that we wish to print the contents of a particular rectangle, expressed in screen coordinates. It doesn't matter where on the screen it is - it doesn't have to be actually on the screen at all. We also tell it where on the printed page we want it to appear and what size it should be. We can even say which way round we want it on the paper!
The printer driver replies, telling us to print a particular rectangle. It may be the whole of the area we specified or just a strip of it. A laser printer will probably ask for an entire page in one go but a dot matrix printer may well ask for it in several strips so that it can build up a bit-mapped image of the page and send it to the printer without using too much memory.
The actual process of sending graphic details to the printer is exactly the same as plotting them on the screen, except that they don't appear on the screen - they go to the printer driver instead. The printer driver calls for a rectangle to be plotted, we plot it and it may call for another one. The process continues until there are no more rectangles to be plotted.
This process may seem familiar; it's the same technique that the Wimp uses to redraw windows! Moreover, because we use the same commands for printing as for drawing on the screen, we already have the necessary software to do it - we can use PROCdraw without any modifications!
The processes involved in printing a page are as follows:
Errors When PrintingWe have to be especially careful about error handling while printing. Any error usually results in a message appearing on the screen, but all screen output is being intercepted by the printer driver, which will try to print the error message. This could lead to a repeat of the error, putting us in an endless loop.
If an error occurs, therefore, the first thing we must do is abort the printing operation and restore output to the screen, before reporting the error message. We do this by calling PDriver_AbortJob, then closing the file. We have to do this for any error during the printing operation, whether it is a bug in our Basic programming or an error returned from an SWI.
We could deal with this situation with a local error handler, but this might present problems in getting back to the main Wimp_Poll loop in the event of a survivable error - we don't want our application to close down just because the printer driver doesn't like something we send it. Instead, we will set a global variable, called printing%, which will modify the action of our main error handler.
First, then, we need to declare this variable and set it to FALSE:
1570 DEFPROCinit 1580 REM initialisation before polling loop starts 1590 DIM b% 255,ws% 2047,menspc% 2047,stack% 1023,list% 2047,ptsize% 12,fontname% 50 1600 $ptsize%="" 1610 $fontname%="Trinity.Medium" 1620 wsend%=ws%+2048:stackend%=stack%+1024:stackptr%=stack%:menend%=menspc%+2048:fontlist%=list%+1024 1630 quit%=FALSE:printing%=FALSE 1640 !list%=-1:!fontlist%=-1 1650 colsel%=7 etc.
If an error occurs while printing% is TRUE, we need to take additional action at the beginning of our error handling routine:
3710 DEFFNerror 3720 IF printing%:SYS "XPDriver_AbortJob",pfile%:SYS "Hourglass_Off":CLOSE#pfile%:printing%=FALSE 3730 !b%=ERR 3740 CASE !b% OF 3750 WHEN 1<<30:err_str$="":box%=3 3760 OTHERWISE:err_str$=" at line "+STR$ERL:box%=2 3770 ENDCASE 3780 $(b%+4)=REPORT$+err_str$+CHR$0 3790 SYS "Wimp_ReportError",b%,box%,"Shapes" TO ,response% 3800 =(response%=2) 3810 :
The first thing we must do is abort the print job, which we do by calling PDriver_AbortJob. The variable pfile% in R0 is the file handle of the 'printer:' file. We turn the hourglass on at the beginning of the printing operation. This is because it can take a long time and, as we do not call Wimp_Poll while it's happening, multi-tasking is suspended. If an error occurs, we need to turn the hourglass off again. Finally, we close the file and set printing% to FALSE. The error handling can now proceed as usual.
The Printing ProcedureTo start printing, we need an additional item on our main window menu:
2840 DEFPROCwindow_menu 2850 RESTORE +1 2860 DATA Shapes,Options,Clear,Save,Font_M,Font size,Print,* 2870 wmenu%=FNmake_menu 2880 ENDPROC 2890 :
and an addition to PROCmenuclick to handle it:
2100 DEFPROCmenuclick 2110 REM handles mouse clicks on menu in response to Wimp_Poll reason code 9 2120 LOCAL c%,adj% 2130 c%=FNstack(20) 2140 SYS "Wimp_GetPointerInfo",,c% 2150 adj%=(c%!8 AND 1) 2160 SYS "Wimp_DecodeMenu",,topmenu%,b%,c% 2170 CASE $c% OF 2180 WHEN "Quit":quit%=TRUE 2190 WHEN "Clear":PROCclear 2200 WHEN "Save":PROCchecksave 2210 WHEN "Print":PROCprint 2220 OTHERWISE 2230 IF LEFT$($c%,5)="Font.":PROCpick_font 2240 ENDCASE etc.
Most of the printing steps described earlier are contained in one procedure:
6230 DEFPROCprint 6240 printxpos%=93675:printypos%=216855 6250 transx_to_x%=1<<16:transx_to_y%=0 6260 transy_to_x%=0:transy_to_y%=1<<16 6270 SYS "XPDriver_Info" TO err%,,,fea%;flags% 6280 IF (flags% AND 1)<>0 !err%=1<<30:SYS "OS_GenerateError",err% 6290 SYS "Hourglass_On" 6300 pfile%=OPENOUT"printer:" 6310 printing%=TRUE 6320 SYS "XPDriver_SelectJob",pfile% TO err%;flags% 6330 IF (flags% AND 1)<>0 !err%=1<<30:SYS "OS_GenerateError",err% 6340 xorig%=0:yorig%=0 6350 !b%=xorig%:b%!4=yorig%-1020:b%!8=xorig%+1020:b%!12=yorig% 6360 b%!16=transx_to_x%:b%!20=transx_to_y% 6370 b%!24=transy_to_x%:b%!28=transy_to_y% 6380 b%!32=printxpos%:b%!36=printypos% 6390 SYS "XPDriver_GiveRectangle",0,b%,b%+16,b%+32,&FFFFFF00 TO err%;flags% 6400 IF (flags% AND 1)<>0 !err%=1<<30:SYS "OS_GenerateError",err% 6410 SYS "XPDriver_DrawPage",1,b%+28 TO more%;flags% 6420 IF (flags% AND 1)<>0 !more%=1<<30:SYS "OS_GenerateError",more% 6430 WHILE more%<>0 6440 PROCdraw(b%,xorig%,yorig%) 6450 SYS "XPDriver_GetRectangle",,b%+28 TO more%;flags% 6460 IF (flags% AND 1)<>0 !more%=1<<30:SYS "OS_GenerateError",more% 6470 ENDWHILE 6480 SYS "XPDriver_EndJob",pfile% TO err%;flags% 6490 IF (flags% AND 1)<>0 !err%=1<<30:SYS "OS_GenerateError",err% 6500 printing%=FALSE 6510 CLOSE#pfile% 6520 SYS "Hourglass_Off" 6530 ENDPROC 6540 :
The listing to this point can be found as page_210.
We've left out one line for now which calls a further procedure to declare any font we've used so, if you are using a PostScript printer driver, don't try to print a picture containing fonts yet.
The variables which we create in lines 6240 to 6260 are used to determine the position and size of the picture on the printed page (more about this shortly).
You will notice that we've made extensive use of 'X-Form' SWIs so that errors in the printing process are survivable. The first of these is the call to PDriver_Info which will return an error if you choose Print from the window menu without loading your printer driver. You wouldn't want the application to close down under these circumstances!
The chief purpose of this call is to check that the printer driver is actually present. The call returns a considerable amount of information about the printer, such as whether or not it can print in colour, handle filled shapes and overwrite one colour with another (simple X-Y plotters usually can't do any of these). Most of these details are contained in the bits of the features word in R3 which line 6270 puts into variable fea%. Full details of all this information is in the Programmer's Reference Manual. The only feature which we are interested in is whether or not we should declare any fonts we use to the printer driver.
The printing process is likely to take some time, during which multi-tasking is suspended because we don't call Wimp_Poll, so we turn on the hourglass. Lines 6300 and 6310 open the 'printer:' file and set printing% to TRUE, and the following line calls PDriver_SelectJob with the file handle in R0 (these last four actions are reversed by the error handler if it is called while printing% is TRUE). If there is already a print job selected, it is suspended and ours takes over. This is unlikely to happen as multi-tasking is usually suspended during printing, so our application wouldn't gain control.
Printing in RectanglesThe next step is to specify the rectangle where we're going to draw our picture. We will do this relative to the variables which hold the coordinates of the window origin, xorig% and yorig%, to keep PROCdraw happy. In practice, it doesn't matter where this origin is because we're not actually drawing the window on the screen, just sending the output to the printer, so we'll set the coordinates to (0,0). This puts our 'picture' just below the bottom edge of the screen. You can, of course, set it to anything you like.
The next call, PDriver_GiveRectangle, requires a lot of data from us. This is the call where we tell the printer driver which rectangle we want to print and how we wish it to appear on the paper. Register R0 can contain a reference number for the rectangle which will be repeated back to us when we're asked to plot it - useful if we're printing several rectangles on one page. We're not using this feature, so we set it to zero. R1 has the address of a four-word block containing the coordinates of the rectangle, R2 that of another four-word block containing a transformation table, which we will deal with shortly, and R3 a two-word block with the x and y position of the rectangle on the page. R4 contains the background colour of the rectangle in the form BBGGRR00, where BB, GG and RR are the amounts of blue, green and red respectively, with eight bits allocated to each.
It would clearly be wasteful to DIM separate blocks for each of these so we will put the R1 block at b%, R2 at b%+16 and R3 at b%+32. Line 6390 shows the SWI being called with the appropriate numbers in each register.
Setting the Page PositionDealing with the R3 block first, the first word has the x coordinate of the bottom left-hand corner of the rectangle on the page and the second has the y coordinate. These coordinates are expressed in millipoints. In case you didn't encounter them in relation to fonts in Section 18, there are 72 points to the inch, or 72,000 millipoints.
We'll centre our picture on the printed page, both vertically and horizontally. If you load up your printer driver and go into Basic, you can ask it the size of the page by typing:
This call returns the width and height in R1 and R2 respectively in millipoints. Incidentally, R3 to R6 contain the distance of the printable area from each edge. Assuming that your printer driver is set for A4 paper, you will find that the width is 595,350 millipoints and the height 841,711. Our picture is 1020 graphics units square. In RISC OS, there are deemed to be 180 graphics units to the inch, which makes the width and height of our picture 408,000 millipoints. To centre the picture on the page, its bottom left-hand corner would have to be 93,675 millipoints in from the left and 216,855 millipoints up from the bottom. We've specified these explicitly as the values of printxpos% and printypos% in line 6240 and incorporated them in the block at b%+32 in line 6380.
The block for R1 at b% holds the minimum and maximum x and y coordinates of the rectangle in the usual order. You will recall that we set our window to be 1020 graphics units square when we started to write this application, so line 6350 derives the four coordinates from xorig% and yorig%.
The second block, starting at b%+16, holds the transformation matrix that tells the printer driver how large to make the printout and which way round to put it. Each point in the rectangle to be printed is put on the page in a position determined by a formula:
In case this looks rather daunting, consider its parts separately: x´ and y´ are the coordinates of the actual position of a point on paper; x and y are its original coordinates in the rectangle being printed. The expressions R2!0 to R2!12 refer to the numbers in the four words of the data block whose address is in register R2. The system allows both x´ and y´ each to be determined by both the original x and original y coordinates.
If we want our picture to be printed in portrait mode (upright) at full size, we want x´ to equal x and y´ to equal y. The y coordinate plays no part in determining x´ and x plays no part in determining y´, so R2!8 and R2!4 will both be zero. Note that both lines are divided by 216, or 1<<16. This means that R2!0 and R2!12 will both have to be 1<<16 to get the answer we want. The equation will then read:
In other words, x´=x and y´=y.
We've set up four variables with appropriate names to hold the values of this transform matrix in lines 6250 and 6260 and put them into the data block in lines 6360 and 6370. The use of all these variables makes it easier to change things.
Incidentally, line 6390 sets up R4 for maximum blue, green and red, which gives a white background (assuming you're printing on white paper, of course!), but our application window has a light grey background, having been defined as Wimp colour 1. You can, of course, edit the template file to change it to white, if you prefer. You will also have to modify the font background colour in PROCtext.
Having defined the rectangle to be printed, we tell the printer driver to go ahead and print the page. Note that the call to PDriver_DrawPage is very similar to the call to Wimp_RedrawWindow. It returns a number in R0 which is non-zero if there is a rectangle to be drawn. We also tell it how many copies of the page to print, in R0.
The loop that follows should look very familiar if you remember PROCredraw. We call PROCdraw to plot the picture, as if it were going to the screen, then call PDriver_GetRectangle, which again returns a number which is non-zero if there is another rectangle to be printed, or zero if we've finished.
Both PDriver_DrawPage and PDriver_GetRectangle return with a four-word block pointed to by R1, which contains the coordinates of the rectangle to be printed this time round the loop. You may wish to make use of this to improve the efficiency of your PROCdraw routine, and we will see how to do this in the next section. PROCdraw also handles data returned by Wimp_RedrawWindow and Wimp_GetRectangle, both of which put the coordinates of the rectangle to be redrawn in the four words starting at b%+28. We need to do the same thing here, which is why we put b%+28 into R1 when we call PDriver_DrawPage and PDriver_GetRectangle.
If either of these calls results in an error, the variable more%, being in R0, becomes the pointer to the error block.
When all the rectangles have been printed, we call PDriver_EndJob, passing it the file handle of the 'printer:' file, close the file and turn off the hourglass.
Printing a Landscape ViewIt probably occurred to you when we were examining the transformation matrix equations that, because the x and y coordinates of a point were each derived from both the x and the y coordinates of the original, we should be able to print in landscape mode, just by changing round the numbers. This is why we used variables for everything and set them up at the start of the procedure. By changing the first few lines, you can print sideways, with the right-hand side of the picture emerging from the printer first:
6230 DEFPROCprint 6240 printxpos%=93675+408000:printypos%=216855 6250 transx_to_x%=0:transx_to_y%=1<<16 6260 transy_to_x%=-1<<16:transy_to_y%=0 6270 SYS "XPDriver_Info" TO err%,,,fea%;flags%
The effect of these changes is to rotate the picture anti-clockwise by 90°, so that its bottom left-hand corner is now the bottom right-hand. The coordinates in the block in R1 still refer to this point, however, so we must add the length of the side to the x coordinate in line 6240.
Both the x-to-x and y-to-y numbers are now zero and their counterparts dictate the size of the picture. Because the y coordinate is now along the x axis from right to left, transy_to_x% must be a negative number.
Now you should be able to print, changing the size and orientation of your picture as you wish. If you replace the '1' passed to R0 when calling PDriver_DrawPage with a variable, you could use it to control the number of copies you print.
The best way of setting up these variables is, of course, with a dialogue box, reached via the Print menu item. Now that you've found out how they work, you should be able to design one yourself. You could use radio icons for 'portrait' and 'landscape' (using FNicon_state to detect which of them is selected) and a writable icon for the number of copies. If you're feeling adventurous, you could include another one to set the scaling. Finally, you will need an icon labelled 'Print' which, when clicked on, calls PROCprint.
The 'landscape' version of the listing can be found as page_214.
Declaring FontsThere is one more job to do. We left out the line from PROCprint which calls a procedure that declares the names of any fonts we use. This is particularly important if you are using a PostScript driver, either to print directly to a PostScript printer or to produce a file for sending to one. It is worth including this procedure in your applications even if you don't own a PostScript printer, as PostScript files can be sent to other people or turned into other formats.
Other drivers produce a bit-mapped image of the page, including text produced by the outline font manager, and send it to the printer, which operates in its graphics mode. PostScript, on the other hand, sends an ASCII file containing the actual text, together with the font information. Any fonts used in the document have to be declared to the PostScript driver so that they can be incorporated in the preamble at the beginning of the file.
We can tell whether or not to carry out this exercise from the information we get back from the call to Wimp_PDriverInfo. Register R3 contains a 32-bit features word, all the details of which are in the Programmer's Reference Manual. Bit 29 is set if the driver accepts font declarations.
The correct place to add a line to call the new procedure is just after the call to PDriver_SelectJob:
6230 DEFPROCprint 6240 printxpos%=93675:printypos%=216855 6250 transx_to_x%=1<<16:transx_to_y%=0 6260 transy_to_x%=0:transy_to_y%=1<<16 6270 SYS "XPDriver_Info" TO err%,,,fea%;flags% 6280 IF (flags% AND 1)<>0 !err%=1<<30:SYS "OS_GenerateError",err% 6290 SYS "Hourglass_On" 6300 pfile%=OPENOUT"printer:" 6310 printing%=TRUE 6320 SYS "XPDriver_SelectJob",pfile% TO err%;flags% 6330 IF (flags% AND 1)<>0 !err%=1<<30:SYS "OS_GenerateError",err% 6340 IF (fea% AND 1<<29)<>0 PROCdeclare_fonts 6350 xorig%=0:yorig%=0 etc.
The printer driver only needs to know the name of the font, not the point size, colour or any other information. The call PDriver_DeclareFont allows us to declare a font in one of two ways; with the font handle in R0 or a pointer to the font name in R1. In each case, the unused register contains zero.
Our new procedure uses the latter method, making use of the list of font names stored at fontlist%:
6560 DEFPROCdeclare_fonts 6570 LOCAL n% 6580 n%=fontlist% 6590 WHILE !n%<>-1 AND n%<fontlist%+1024 6600 SYS "XPDriver_DeclareFont",0,n%+8,0 TO err%;flags% 6610 IF (flags% AND 1)<>0 SYS "XPDriver_AbortJob",pfile%:!err%=1<<30:SYS "OS_GenerateError",err% 6620 n%+=8 6630 WHILE ?n%>=32:n%+=1:ENDWHILE 6640 n%+=1 6650 WHILE n% MOD 4<>0:n%+=1:ENDWHILE 6660 ENDWHILE 6670 SYS "XPDriver_DeclareFont",0,0,0 TO err%;flags% 6680 IF (flags% AND 1)<>0 SYS "XPDriver_AbortJob",pfile%:!err%=1<<30:SYS "OS_GenerateError",err% 6690 ENDPROC 6700 :
Lines 6590 and 6620 to 6660 work through the font list, setting n% to the start of each font entry. In each case, the actual font name begins eight bytes on from the start and line 6600 puts this address into R1 before calling the SWI.
When all the font names have been declared, we make a further call to PDriver_DeclareFont with zero in both R0 and R1. This tells the driver that we have finished the list.
The result of this procedure is that the PostScript file contains the names of just the fonts in the font list. If you use a font which is not on the printer driver's list of fonts resident in the printer, the font outlines will be downloaded to the file.
This PostScript-friendly version of the listing can be found as page_216.