ACCESSING PRINTER_DAT
=====================

by Dilwyn Jones

This article sets out to explain the file structure of the PRINTER_DAT
printer driver used by Quill, Archive and Abacus. A program is
presented which prints to the screen all of the codes used in the
driver and in so doing, shows how you can access the driver for use in
your own programs, for example, or to write an alternative printer
driver editor. The program listed works with the Xchange versions of
these printer drivers too, since the file format appears to be the
same.

The program listed uses no toolkit commands, it should work on all
versions of the QL. If you have Toolkit 2 or other basic extensions
toolkit, it should be possible to rewrite the program to use some
extensions such as string and byte fetching extensions to greatly
simplify the program.

First, I shall explain the file format used. It was quite tricky to
work out the file format and I hope I got it right since I do not have
formal documentation on Quill.

4 bytes  "prt1"   characters used to identify printer driver
1 byte            total of lengths of code strings below, but if length
                  bytes are 255, this value is not added to this byte
10 bytes          driver name, which is always 10 characters long,
                  filled with extra spaces to make it 10 characters

1 byte            port type
     if port type = 1 or 2:  ser1 or ser2 respectively
         1 byte   parity code (0=none,1=space,2=mark,3=odd,4=even
         1 word   baudrate, stored as 2 byte integer, MSB first
         15 bytes spaces to pad to file position 34
     if port type = 0 : parallel or other non-serial port
         1 byte   length of name of printer port (e.g. 3 for PAR)
         x bytes  spaces to pad out to file position 34, where x is
                  17-(length of name of printer port)

1 byte            number of lines per page
1 byte            number of characters per line
1 byte            forms type, 1 for continuous, 0 for cut sheets
4 bytes           appear to be spare, filled with spaces or nulls.

The following data all consist of strings containing codes for various
printer functions. The format is unusual in that the string consists of
one byte for the length of the string rather than 2 bytes normally used
on the QL. If the length byte is 0 or 255 (hex FF), there are no other
codes. 0 appears to represent NO CODES, or NONE as it appears in
INSTALL_BAS. 255 appears to represent an inbuilt default value.

string            end of line codes
string            preamble codes
string            postamble codes
string            bold on codes
string            bold off codes
string            underline on codes
string            underline off codes
string            subscript on codes
string            subscript off codes
string            superscript on codes
string            superscript off codes

Next comes the translate character strings. The format of the strings
is the same (byte for length, followed by that number of codes). The
first character after the length byte is the character code translated
from, and the remainder of the characters indicate what that first
character is translated to.

10 strings        translate 1 to 10

1 byte            usually a linefeed code.

Note that the strings are in an unusual format. One byte indicates how
many characters to follow in that string. If that length byte is 0 it
means 'no codes for this function'. If the length byte is 255 it means
use an inbuilt default value, used for only a few cases. This does not
get added to the 'total of lengths of code string' byte.

That lot was a bit of a mouthful. Now for the easy bit, the program to
decode it all. I cannot guarantee this will work on every printer_dat
and xchange_dat file, but it seems to work for the ones I have tried
with it. It asks for the filename of the printer driver file (if you
just press ENTER it uses the default of 'printer_dat' on FLP1_). It
reads it and then prints it to the screen. Since there is a long list
to print, it might scroll off the screen. To pause it to view the firt
half, press CTRL F5 to freeze the screen momentarily.

You can use similar techniques to read printer_dat files for your own
programs. The data ends up in strings in this program and by reading
the codes into strings in your programs, you can simply send the codes
to the printer with a PRINT statement when required. For example,
"PRINT #printer_channel,bold_on$;" to turn bold printing on.

HINT: The Psion programs allow you to specify a parallel printer port
in place of the usual SER1 or SER2. This program will print that out,
of course. But some versions of the Psion programs seem to have
difficulty with the name PAR - they require this to be entered as 2PAR
for some reason, I don't know why the number TWO is required before the
name. Of course, if you use serial ports, you will not care (and not
know) about this.

Within Quill itself, if the driver is configured for, say, SER1 or
SER2, you can still print to another device such as PAR or network by
preceding the device name with an underscore '_' character, e.g. F3
Print, Current, Whole, To _PAR.

100 REMark PRINTER_DAT reader by Dilwyn Jones 1994
110 CLS : CLS #0
120 INPUT#0,'Enter name of printer driver (default PRINTER_DAT):';driver$
130 IF driver$ = '' : driver$ = 'FLP1_PRINTER_DAT'
140 OPEN_IN #3,driver$
150 ident$ = '    ' : REMark 4 spaces
160 FOR a = 1 TO 4 : ident$(a) = INKEY$(#3)
170 IF ident$ <> 'prt1' : PRINT #0,'Unsuitable file' : STOP
180 code_strings_length = CODE(INKEY$(#3))
190 driver_name$ = FILL$(' ',10)
200 FOR a = 1 TO 10 : driver_name$(a) = INKEY$(#3)
210 PRINT'DRIVER NAME         : ';driver_name$
220 :
230 REMark get port details
240 port_type = CODE(INKEY$(#3))
250 IF port_type = 1 OR port_type = 2 THEN
260   REMark serial port
270   PRINT 'PRINTER PORT        : SER';port_type
280   parity = CODE(INKEY$(#3)) : PRINT 'PARITY              : ';
290   SELect ON parity
300     =0:PRINT'NONE'
310     =1:PRINT'SPACE'
320     =2:PRINT'MARK'
330     =3:PRINT'ODD'
340     =4:PRINT'EVEN'
350   END SELect
360   baudrate = 256*CODE(INKEY$(#3))+CODE(INKEY$(#3))
370   PRINT 'BAUDRATE            : ';baudrate
380   FOR a = 1 TO 15 : temp$ = INKEY$(#3) : REMark skip the spaces
390 ELSE
400   REMark parallel or other type of port
410   PRINT'PRINTER PORT        : ';
420   portlen=CODE(INKEY$(#3)) : REMark name of port
430   FOR a = 1 TO portlen : PRINT INKEY$(#3);
440   PRINT
450   FOR a = 1 TO 17-portlen : temp$ = INKEY$(#3) : REMark skip spaces
460 END IF
470 lines_per_page = CODE(INKEY$(#3))
480 PRINT'LINES PER PAGE      : ';lines_per_page
490 chars_per_line = CODE(INKEY$(#3))
500 PRINT'CHARACTERS PER LINE : ';chars_per_line
510 forms_type = CODE(INKEY$(#3))
520 PRINT'FORMS TYPE          : ';
530 IF forms_type = 1 THEN
540   PRINT'Continuous'
550 ELSE
560   PRINT'Cut'
570 END IF
580 FOR a = 1 TO 4 : temp$ = INKEY$(#3) : REMark skip spaces
590 :
600 REMark read code strings for end of line, pre/postamble, bold etc
610 PRINT'END OF LINE CODE      : ';
620 eol$ = FETCH_CODE_STRING$ : CODE_STRING eol$
630 PRINT'PREAMBLE CODES        : ';
640 preamble$ = FETCH_CODE_STRING$ : CODE_STRING preamble$
650 PRINT'POSTAMBLE CODES       : ';
660 postamble$ = FETCH_CODE_STRING$ : CODE_STRING postamble$
670 PRINT'BOLD ON CODES         : ';
680 bold_on$ = FETCH_CODE_STRING$ : CODE_STRING bold_on$
690 PRINT'BOLD OFF CODES        : ';
700 bold_off$ = FETCH_CODE_STRING$ : CODE_STRING bold_off$
710 PRINT'UNDERLINE ON CODES    : ';
720 under_on$ = FETCH_CODE_STRING$ : CODE_STRING under_on$
730 PRINT'UNDERLINE OFF CODES   : ';
740 under_off$ = FETCH_CODE_STRING$ : CODE_STRING under_off$
750 PRINT'SUBSCRIPT ON CODES    : ';
760 sub_on$ = FETCH_CODE_STRING$ : CODE_STRING sub_on$
770 PRINT'SUBSCRIPT OFF CODES   : ';
780 sub_off$ = FETCH_CODE_STRING$ : CODE_STRING sub_off$
790 PRINT'SUPERSCRIPT ON CODES  : ';
800 sup_on$ = FETCH_CODE_STRING$ : CODE_STRING sup_on$
810 PRINT'SUPERSCRIPT OFF CODES : ';
820 sup_off$ = FETCH_CODE_STRING$ : CODE_STRING sup_off$
830 :
840 REMark translates 1 - 10
850 FOR tr = 1 TO 10
860   PRINT'TRANSLATE ';tr;': ';
870   t$ = FETCH_CODE_STRING$
880   IF LEN(t$)=1 THEN
890     CODE_STRING t$
900   ELSE
910     PRINT'FROM ';CODE(t$);' (';t$(1);') TO ';
920     CODE_STRING t$(2 TO LEN(t$))
930   END IF 940 END FOR tr
950 CLOSE #3
960 :
970 DEFine PROCedure CODE_STRING (str)
980   LOCal a
990   IF str = CHR$(255) THEN PRINT'DEFAULT' : RETurn
1000   IF str = CHR$(0) THEN PRINT'NONE' : RETurn
1010   FOR a = 1 TO LEN(str)
1020     PRINT CODE(str(a));
1030     IF a < LEN(str) THEN PRINT','; : ELSE PRINT : END IF
1040   END FOR a
1050 END DEFine CODE_STRING
1060 :
1070 DEFine FuNction FETCH_CODE_STRING$
1080   LOCal a,cd_length,temp$
1090   cd_length = CODE(INKEY$(#3))
1100   IF cd_length = 0 THEN RETurn CHR$(0)
1110   IF cd_length = 255 THEN RETurn CHR$(255)
1120   temp$ = FILL$(' ',cd_length)
1130   FOR a = 1 TO cd_length : temp$(a) = INKEY$(#3)
1140   RETurn temp$
1150 END DEFine FETCH_CODE_STRING$

    Source: geocities.com/svenqhj