A.N.A.L.O.G. ISSUE 35 / OCTOBER 1985 / PAGE 81

G:
A printing device for Epson
(with Graftrax) or Gemini printers

by Charles F. Johnson

One of the first things you find out about your printer is that it can’t print out many of the “special” characters in the Atari character set (e.g., the cursor characters, clear screen, all the CTRL graphics characters, all inverse characters, and so forth). If you send any of these to your printer, all kinds of odd things start happening.

Many of the special characters are interpreted as control codes by the printer, causing line feeds, form feeds, different fonts, etc. Unfortunately, a lot of Atari BASIC programs use these characters quite liberally, so if you type a simple LIST “P:” command, you might see your listing unexpectedly shift into Japanese katakana in the middle of a program line. Printing pictures from graphics mode 8 or 7 + (15 on XL computers) can be a fairly complicated procedure, as well. Translating the screen data to printer format is not an easy task for a beginning or intermediate programmer.

It can be done!

There’s a way to print any character your Atari can display on-screen; you must use your printer’s graphics mode and convert every character in the text you are sending into the graphics data that will draw that character on the printer. There are several programs on the market that will do this (Printwiz, Megafont II, Lister-Plus), but all of these require that the program (or text) to be printed be stored on disk in an ATASCII file.

These lister programs all read text from this disk file one line at a time, convert it to printer graphics and print it. This disk-based method is rather slow and necessitates an intermediate step in the listing process — making the disk file. Most of these utilities also have some provision for printing pictures.

A new device.

This program represents another approach to the problem. The G: device is loaded into memory at boot-up time as an AUTORUN.SYS file. It will work with BASIC, the Assembler/Editor cartridge, BASIC XL and MAC/65.

You can LOAD, SAVE, ENTER and LIST files to and from disk, edit programs and perform any function in the usual manner with G: present. The difference is that, any time you want to produce a graphics listing of a BASIC or assembly language program, you just type:

LIST "G:"   (BASIC)
LIST #G:    (MAC/65, ASSEMBLER/EDITOR)

This will list whatever you have in memory to the printer, but with all inverse and graphics characters exactly as they appear on-screen. You can list only certain line numbers, in the same way you would to any other device, with a statement such as:

LIST "G:",19,290  or
LIST #G:,1220

The G: device will automatically set the left margin five spaces in and set the skip-over-perforation feature. I recommend positioning the printhead approximately two line feeds below the perforation line to start your listing.

Four flavors.

Now, what would you pay? But wait . . .there’s still more! G: comes in four flavors — G1: (the default), G2:, G3: and G4:. Here’s what the different device numbers do.

Custom fonts and screen dumps, too?

Now, what would you pay? But wait… G: also prints custom character sets! When you’re printing text, G: will use whatever character set memory location 756 (hex $2F4) is pointing to.

Character set modification has been covered in many places, so I won’t go into the whole subject here. There are many public domain and commercial character editors for the Atari, to help in creating any font you can imagine. Create-A-Font by Vince Erceg in ANALOG Computing’s issue 16 is a good one.

G: also has a very flexible XIO frmction, which will print an exact copy of a graphics mode 0, 8 or 7 + (mode E) screen display. A mode screen can be printed with either single- or double-width characters. Mode 8 or 7 + screens can be printed in normal or inverse, three different widths and two different heights!

The ins and outs of XIO.

To print a graphics mode screen, type:

XIO 16,#1,0,0,"G:"

This is probably most useful in the program mode, where you can set up the screen in whatever way you like, then execute the XIO command (say, with a press of the START button). When using XIO with graphics 0, the G: device numbers (G1:, G2:, G3:, and G4:) control only the print size, not the line length. Therefore, G1: and G2: will produce the same printout, as will G3: and G4:.

If the first number after XIO (the command number) is 16, the entire screen will be printed. To print just part of the screen, add the number of lines you want to print to 16 and use that as the XIO command number. For example, if you want to print the first five lines using double-width characters, add 5 to 16 (21), and the XIO command might look like:

XIO 21,#1,0,0,"G4:"

Printing pictures.

To print a graphics 8 or 7+ screen, first set up your hi-res display, then execute the command:

XIO 64,#1,0,0,"G:"

This will print a single-width, single-height picture. To print your screen in inverse (like a photographic negative), set the auxiliary byte (the second number past the channel number) to 255. The XIO command would be:

XIO 64,#1,0,255,"G:"

When you’re printing a hi-res screen, the G: device numbers control the height of the picture. Here’s how they work:

G1: or G2: Single height.
G3: or G4: Double height.

The width of the picture is controlled by the value of the first byte past the channel number in the XIO statement. In the first two examples above, this byte is 0. Here are the width values:

0 or 1 Single width.
2 Double width.
3 Triple width.

I’ll give several examples to illustrate the use of the XIO 64 command.

XIO 64,#1,0,255,"G4" -- Single width, inverse, double height.
XIO 64,#1,2,0,"G2:" --- Double width, normal, single height.
XIO 64,#2,3,255,"G3:" - Triple width, inverse, double height.
XIO 64,#1,1,0,"G:" ---- Single width, normal, single height.

As you can see, there are quite a few ways to print a hi-res picture with the G: device. Some experimentation will probably be necessary to find the best way to print each picture.

And, by the way, the G: device automatically centers your picture on the page (on 80-column printers). Now, how much would you pay?

MAC/65 or Assembler/Editor.

If you use MAC/65 or the Assembler/Editor cartridge, you can also send assembly listings to G: with the ASM command, or print to G: (no line numbers) with the PRINT command. The syntax would be:

ASM,#G: or
PRINT #G:

BASIC or BASIC XL.

In BASIC or BASIC XL, you can open a channel to G: with a statement like:

OPEN #1,8,0,"G:"

and then treat it exactly as any other output device — print strings, numbers, etc. with PUT # or PRINT # commands. This enables you to set up special title pages for documents with mixed print modes and mixed character fonts, draw borders, graph lines. . .or whatever you wish!

You can open more than one channel to G: at a time (e.g., G1: and G4:) and print alternate lines in different character widths. Unfortunately, at this point there’s no way to change print modes on the same line. The channel numbers that you use must be between one and seven, as with any other device.

G: even provides a way for you to control your printer’s line spacing. There are three preset line feed values, and you may also set the line feed to n/72 inches, n being a number between 3 and 127. The preset values are:

0 (default)   8/72 (1/9) inch line feed.
1             9/72 (1/8) inch line feed.
2            12/72 (1/G) inch line feed.

In addition to these three presets, any number (n) greater than 2 is taken to mean a line feed of n/72 inches. We use the OPEN auxiliary byte to pass the line feed value to the G: driver, like this:

OPEN #1,8,2,"G4:" or
XIO 16,#1,0,10,"G:"

The auxiliary byte is the second number past the channel number in both examples (the same one we use for inverse with the XIO function). In the first example, it’s 2. This will set the printer to 1/6 inch line feeds. In the second example, we’re telling the printer we want line feeds of 10/72 inch.

The G: device uses the serial bus to send data to the printer, through SIOV at $E459. This means that if you have some kind of printer interface utilizing the joystick ports (as I used to), you can’t use this version of G:.

It’s possible to modify G: to use an IOCB channel to access the printer, but then the G: device will actually use two IOCB channels while it’s open, and you could no longer have more than one channel open to G: simultaneously.

SYSTEM RESET-proof!

G: is protected from SYSTEM RESET; it will remain available to you until you turn your computer off (or type DOS). You can go to DOS in the usual manner, but, if you do, G: will no longer work when you return to the cartridge.

G: doesn’t touch the much-abused page 6. Instead, G: reserves about 10 pages (2560 bytes) of low memory and sets the MEMLO pointer past itself, so that it can’t be overwritten. The reason G: uses so much memory is that, for every 1 character byte we want to print, we must send 8 bytes of graphics data. This means a large buffer to hold the converted graphics string. (The program itself is a little over 4 pages long, while the buffer is 5 pages — 1280 bytes!)

If you have any very large programs, it’s possible that there may no longer be enough free RAM to load them. This should be a rare occurrence; if it happens, you can always break your program into two parts and list them separately.

The version of G: presented here should work with any DOS, including Happy Warp DOS. It is assembled at an origin of $25D0. If you want to change this (perhaps to free up some more memory), you must enter in the source code with MAC/65 and reassemble with the different origin.

One last word … In the text mode, G: is a line-oriented device; in other words, it expects to be sent a line of text terminated by a RETURN (ATASCII 155). This means that you shouldn’t use PRINT # statements that end in a semi-colon, because G: won’t send anything to the printer until it sees a RETURN. Similarly, if you use PUT # commands to send data to G:, nothing will be printed until you send a 155 ($9B).

Typing it in.

The BASIC program, with all those DATA statements that accompany this article, creates an AUTORUN.SYS file on disk that will automatically load and initialize the G: driver.

Type in the BASIC listing and SAVE it to disk before you RUN it. Then RUN the program, and your AUTORUN.SYS file will be created. When this is done, the G: device will be automatically installed whenever you boot up with this disk.

If you have MAC/65, you can type in the assembly listing and create the AUTORUN.SYS file with the command ASM, #-,#D: AUTORUN.SYS.


Charles F. Johnson is a musician by trade, currently working for Al Jarreau. A self-taught guitarist and programmer, he grew up in Hawaii and has been programming for three and a half years. This is his first published program.

Listing 1.
BASIC listing.

10 REM *** G: GRAPHICS PRINTER ***
20 DATA 0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0,0,10,11,12,13,14,15
30 DIM DAT$(91),HEX(22):FOR X=0 TO 22:READ N:HEX(X)=N:NEXT X:LINE=990:RESTORE 1000:TRAP 110:? "CHECKING DATA"
40 LINE=LINE+10:? "LINE:";LINE:READ DAT$:IF LEN(DAT$)<>90 THEN 150
50 DATLIN=PEEK(183)+PEEK(184)*256:IF DATLIN<>LINE THEN ? "LINE ";LINE;" MISSING!":END 
60 FOR X=1 TO 89 STEP 2:D1=ASC(DAT$(X))−48:D2=ASC(DAT$(X+1))−48:BYTE=HEX(D1)*16+HEX(D2)
70 IF PASS=2 THEN PUT #1,BYTE:NEXT X:READ CHKSUM:GOTO 40
80 TOTAL=TOTAL+BYTE:IF TOTAL>999 THEN TOTAL=TOTAL−1000
90 NEXT X:READ CHKSUM:IF TOTAL=CHKSUM THEN 40
100 GOTO 150
110 IF PEEK(195)<>6 THEN 150
120 IF PASS=2 THEN END 
130 ? "INSERT DISK WITH DOS, PRESS RETURN";:DIM IN$(1):INPUT IN$:OPEN #1,8,0,"D:AUTORUN.SYS"
140 ? :? "WRITING FILE":PASS=2:LINE=990:RESTORE 1000:TRAP 110:GOTO 40
150 ? "BAD DATA: LINE ";LINE:END 
1000 DATA FFFFD025F52520FFFFA50A8D8B29A50B8D8C29A50C8DD1258D8D29A50D8DD2258D8E29A9D0850CA925850D60E2,546
1010 DATA 02E302D325D3258A29A200BD1A03F005E8E8E8D0F6A9479D1A03A9019D1B03A9269D1C03A910850AA926850BA9,253
1020 DATA FC8DE702A92E8DE802601C2687269E269F269C26CD274C9D26A203BD8B29950ACA10F86C0A00088A4A4A4A4AA8,793
1030 DATA 883004C0079004A086D057A621CA8EA929BDDF2999D829A52BC9FFD002A900C903B004AABDE3298DF729A522C9,416
1040 DATA 40D017A52AC902B004A91CD00AC903B004A90ED002A9018DF4292036298DAB29A20ABDED299DC003CA10F72041,122
1050 DATA 293005201029A0012860203629A91B8DC003A9408DC103A99B8DC203204129A00160088EA52948A207B5D49D9B,190
1060 DATA 29CA10F8ADA32985D4ADA42985D568ACAB29F013C040D016A52B8DA829A9B885D6A92985D7D038C99BD0034C5D,192
1070 DATA 27A0008CA82984D70A6EA8294AACAB29D00FC920B00469409007C960B00338E920A0030A26D788D0FA85D6ADF4,411
1080 DATA 0205D785D7A0078CA629B1D6ACA829F00249FFA0074A48B1D46A91D4688810F5CEA629ACA62910E2A007B1D4C9,294
1090 DATA 9BD004A99791D48810F318A5D469088DA32985D49005EEA429E6D4EEFA29AEA929F06BADFA29DDE629D063A000,654
1100 DATA A99B91D48CAA29A2030EFA292EFB29CAD0F7ADAB29C940D004A94CD00CADA5294A4A4A4AAACABDD8298DF929A9,571
1110 DATA F885DAA92985DB203629A8B1DA99C003C99BD0058DAA29F010C8C028D0ED18A5DA692885DA9002E6DB204129AD,633
1120 DATA AA29F0D6201029A207BD9B2995D4CA10F828A001608EA529201D26C001D051A9048DA929A55885D88DB2298DB0,797
1130 DATA 29A55985D98DB3298DB129A5228DAB29C940F032C911B004A918D002E9108DAD29A9008DAC29ACAC29B1D8AEA5,314
1140 DATA 2920A026EEAC29ADAC29C928D0EB204F29CEAD29D0DE4C8826A52AC902900DC903B004A905D002A9068DA929A5,384
1150 DATA 21C903900B8DB729A2A0A000A930D00BA9008DB729A240A001A9188EB4298CB5298DB629A9008DAE29A9008DAF,272
1160 DATA 29ADB22985D8ADB32985D9A200A000B1D89DB829ACB729F0039DB929204F29E8C0039001E8E008D0E4A52AC902,950
1170 DATA 9030205B29A207BDC0299DB829CA10F720A026A207BDC8299DB829CA10F720A026A52AC903D00EA207BDD0299D,47
1180 DATA B829CA10F720A026EEB229D003EEB329EEAF29ADAF29C928D08D18ADB0296DB4298DB0298DB229ADB1296DB529,710
1190 DATA 8DB1298DB329EEAE29ADAE29CDB629F0034C6928A9058DF4294C8826A900AA9DFC299DFC2A9DFC2B9DFC2C9DFC,567
1200 DATA 2DE8D0EE8DFA298DFB29A9FC8DA329A9298DA42960A227A9009DC003CA10FA60A20BBD8F299D0003CA10F74C59,195
1210 DATA E418A5D8692885D89002E6D960A217A9009DC029CA10FAA207A9078DA729BDB8294A48A42A087EC0297EC8297E,543
1220 DATA D0292888D0F268CEA72910E8CA10DD608F29FB2940015780C003050028004E0000000000000000000000000000,793
1230 DATA 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000,793
1240 DATA 000000000000000000004C4C4B4B08090C0026263C2850781B401B4E051B4D051B41081B000000E002E102D325,638

Listing 2.
Assembly listing.

; ==================================
;
;      A GRAPHICS PRINTING DEVICE
;  G:  FOR EPSON OR GEMINI PRINTERS
;
; ==================================
;
;
; (c) 1985 by CHARLES JOHNSON
; and Little Green Footballs
;
; G1: --- 80 Column Normal
; G2: --- 40 Column Normal
; G3: --- 40 Column Double-Width
; G4: --- 60 Column Double-Width
;
; XIO 16,#1,0,0,"G:" ---
; print a GRAPHICS 0 screen.
; Any XIO command # larger than
; 16 will be used as the # of
; screen lines to print +16.
;
; XIO 64,#1,0,0,"G:" ---
; print a GRAPHICS 8 or 7+ (E)
; screen.
;
; Auxiliary byte 2 values:
; (These go in ICAX2)
;
; 0 - 8/72 in. line feed
; 1 - 9/72 in. LF
; 2 - 12/72 in. LF
; 255 - INVERSE PRINT (XIO 64)
;
; Any number (n) larger than 2
; will be used as n/72 in. LF
;
;
; --------------
; System equates
; --------------
;
DOSVEC	=	$0A
DOSINI	=	$0C
ICDNOZ	=	$21
ICCOMZ	=	$22
ICAX1Z	=	$2A
ICAX2Z	=	$2B
SAVMSC	=	$58
ZPOUTP	=	$D4
FNTPTR	=	$D6
SCRPTR	=	$D8
POINTR	=	$DA
;
RUNAD	=	$02E0
INITAD	=	$02E2
MEMLO	=	$02E7
CHBAS	=	$02F4
;
DDEVIC	=	$0300
HATABS	=	$031A
PRNBUF	=	$03C0
SIOV	=	$E459
;
; -----------------------------
; Make this routine RESET-proof
; -----------------------------
;
	*=	$25D0
;
START	JSR	$FFFF
PILFER	LDA	DOSVEC
	STA	DVSAVE
	LDA	DOSVEC+1
	STA	DVSAVE+1
	LDA	DOSINI	;Steal the DOS
	STA	START+1	;init vectors
	STA	DVSAVE+2	;and put them
	LDA	DOSINI+1	;in my code
	STA	START+2
	STA	DVSAVE+3
	LDA	#<START	;Put address
	STA	DOSINI	;of G: init code
	LDA	#>START	;in DOSINI
	STA	DOSINI+1
	RTS
;
	*=	INITAD
;
	.WORD	PILFER
;
; ---------------------
; Install the G: device
; ---------------------
;
	*=	PILFER
;
INSTAL	LDX	#0
SEARCH	LDA	HATABS,X	;Look for the
	BEQ	ADDG	;end of the
	INX		;handler table
	INX
	INX
	BNE	SEARCH
;
ADDG	LDA	#'G	;Add G: to the
	STA	HATABS,X	;device table
	LDA	#<GDRIVER
	STA	HATABS+1,X
	LDA	#>GDRIVER
	STA	HATABS+2,X
	LDA	#<GDVEC	;Reset DOS
	STA	DOSVEC	;vectors
	LDA	#>GDVEC
	STA	DOSVEC+1
	LDA	#<PND	;Reset the MEMLO
	STA	MEMLO	;pointer
	LDA	#>PND
	STA	MEMLO+1
	RTS
;
; ----------------------
; The table of addresses
; ----------------------
;
GDRIVER	.WORD	GOPEN-1
	.WORD	GCLOSE-1
	.WORD	GGETB-1
	.WORD	GPUTB-1
	.WORD	GSTAT-1
	.WORD	GXIO-1
	JMP	GINIT
;
; DOS commands go through here
;
GDVEC	LDX	#3
GDV2	LDA	DVSAVE,X	;Restore DOS
	STA	DOSVEC,X	;vectors
	DEX
	BPL	GDV2
	JMP	(DOSVEC)	;Go to DOS!
;
; =======================
; THE G: HANDLER ROUTINES
; =======================
;
; ----------------
; The OPEN routine
; ----------------
;
GOPEN	PHP
	TXA		;Calling IOCB #
	LSR	A	;Divide by 16
	LSR	A	;to use as index
	LSR	A
	LSR	A
	TAY		;Move to Y
	DEY		;-1
	BMI	SETERR	;<0 = error
	CPY	#7	;>6 = error
	BCC	GO2
SETERR	LDY	#$86	;Bad IOCB #
	BNE	GORTS
;
GO2	LDX	ICDNOZ	;Get device #
	DEX		;Subtract 1
	STX	SPLIT	;Store it
	LDA	GRNUM,X	;Put gr mode in
	STA	GRTBL,Y	;table
	LDA	ICAX2Z	;Get LF value
	CMP	#$FF	;Inverse pic?
	BNE	GO3	;No, skip
	LDA	#0	;Reset for LF
GO3	CMP	#3	;>=3?
	BCS	SETLFT	;Yes, skip
	TAX		;Move index to X
	LDA	LFNUM,X	;Get preset LF
SETLFT	STA	LNFEED	;Put LF in init
	LDA	ICCOMZ	;Get command #
	CMP	#64	;Picture dump?
	BNE	CLP1	;No, skip
	LDA	ICAX1Z	;Get width
	CMP	#2	;Single width?
	BCS	NOTSNG	;No, skip
	LDA	#28	;Margin at 28
	BNE	SETMGN
;
NOTSNG	CMP	#3	;Double width?
	BCS	NOTDBL	;No, skip
	LDA	#14	;Margin at 14
	BNE	SETMGN
;
NOTDBL	LDA	#1	;Margin at 1
SETMGN	STA	MARGN	;Set margin
CLP1	JSR	CLPBUF	;Clear buffer
	STA	XIO?	;Clear XIO flag
	LDX	#ICLEN-1
CCODES	LDA	ICODES,X	;Copy init
	STA	PRNBUF,X	;codes to SIO
	DEX		;buffer
	BPL	CCODES
	JSR	DOSIO	;Send init codes
	BMI	GORTS	;Error, skip
	JSR	INIT
	LDY	#1
GORTS	PLP
	RTS
;
; -----------------
; The CLOSE routine
; -----------------
;
GCLOSE	JSR	CLPBUF
	LDA	#27	;Reset printer
	STA	PRNBUF	;and send one
	LDA	#64	;line feed
	STA	PRNBUF+1
	LDA	#155
	STA	PRNBUF+2
	JSR	DOSIO
GSTAT
GINIT	LDY	#1
GGETB	RTS
;
; ----------------
; PUT BYTE routine
; ----------------
;
GPUTB	PHP
	STX	XSAVE
	PHA
	LDX	#7
GP1	LDA	ZPOUTP,X	;Save zero page
	STA	ZPSAVE,X	;locations
	DEX
	BPL	GP1
	LDA	OUTPTR	;Set zero page
	STA	ZPOUTP	;pointer to
	LDA	OUTPTR+1	;gr buffer
	STA	ZPOUTP+1
	PLA
	LDY	XIO?	;Is this XIO?
	BEQ	GP1.2	;No, skip
	CPY	#64	;Gr 8 dump?
	BNE	GP2	;No, skip
	LDA	ICAX2Z	;Set inverse
	STA	INVERS	;flag
	LDA	#<GRBUF	;Set pointer to
	STA	FNTPTR	;graphics buffer
	LDA	#>GRBUF
	STA	FNTPTR+1
	BNE	BYTE1
;
GP1.2	CMP	#$9B	;End of line?
	BNE	GP2	;No, skip
	JMP	DOPRINT	;Go print it!
;
GP2	LDY	#0
	STY	INVERS
	STY	FNTPTR+1
	ASL	A	;Clear inverse
	ROR	INVERS	;bit and save
	LSR	A
	LDY	XIO?
	BNE	CONVERT
	CMP	#$20	;Convert from
	BCS	CK2	;ATASCII to
	ADC	#$40	;internal code
	BCC	CONVERT
CK2	CMP	#$60
	BCS	CONVERT
	SEC
	SBC	#$20
;
CONVERT	LDY	#3
GETINDEX	ASL	A	;Get index into
	ROL	FNTPTR+1	;char set table
	DEY
	BNE	GETINDEX
;
	STA	FNTPTR	;Set pointer to
	LDA	CHBAS	;char storage
	ORA	FNTPTR+1
	STA	FNTPTR+1
;
BYTE1	LDY	#7	;Eight bytes
	STY	BYTCNT	;per character
BYTELOOP	LDA	(FNTPTR),Y	;Get a byte
	LDY	INVERS	;Inverse char?
	BEQ	B2	;No, skip
	EOR	#$FF	;Reverse bits
B2	LDY	#7	;8 bits per byte
BITLOOP	LSR	A	;Bit to carry
	PHA		;Save byte
	LDA	(ZPOUTP),Y	;Roll bit
	ROR	A	;sideways into
	STA	(ZPOUTP),Y	;each byte
	PLA		;Restore byte
	DEY		;Next bit
	BPL	BITLOOP
;
	DEC	BYTCNT	;Count bytes
	LDY	BYTCNT	;More?
	BPL	BYTELOOP	;Yes, go back
;
	LDY	#7
CKRET	LDA	(ZPOUTP),Y
	CMP	#$9B	;Check for EOLs
	BNE	NOTRET	;in output
	LDA	#$97	;Replace
	STA	(ZPOUTP),Y
NOTRET	DEY
	BPL	CKRET
;
	CLC
	LDA	ZPOUTP	;Increment ptr
	ADC	#8	;to print buffer
	STA	OUTPTR	;by 8 bytes
	STA	ZPOUTP
	BCC	SKIP
	INC	OUTPTR+1
	INC	ZPOUTP
SKIP	INC	OUTCNT	;Count chars
	LDX	SPLIT	;Is this 80-col?
	BEQ	EXIT	;Yes, exit
	LDA	OUTCNT	;Have we done
	CMP	SPVAL,X	;one line?
	BNE	EXIT	;No, more bytes
;
DOPRINT	LDY	#0	;Put EOL at end
	LDA	#$9B	;of print buffer
	STA	(ZPOUTP),Y
	STY	DONE	;Clear done flag
	LDX	#3
MULT8	ASL	OUTCNT	;Multiply the #
	ROL	OUTCNT+1	;of characters
	DEX		;by 8 to get the
	BNE	MULT8	;# of gr bytes
	LDA	XIO?
	CMP	#64
	BNE	NOTPIC
	LDA	#76
	BNE	SETM
;
NOTPIC	LDA	XSAVE	;Get IOCB #
	LSR	A	;Divide by 16
	LSR	A
	LSR	A
	LSR	A
	TAX		;Move to X
	DEX		;-1
	LDA	GRTBL,X	;Get gr mode
SETM	STA	GRMODE	;Put in header
	LDA	#<HEADER	;Set pointer
	STA	POINTR	;to start of
	LDA	#>HEADER	;header and
	STA	POINTR+1	;print buffer
SENDEM	JSR	CLPBUF	;Clear SIO buf
	TAY
SEND2	LDA	(POINTR),Y	;Move chars
	STA	PRNBUF,Y	;40 at a time
	CMP	#$9B	;to SIO buffer
	BNE	SEND3	;(or until EOL)
	STA	DONE	;Set done flag
	BEQ	PRNT	;Skip
;
SEND3	INY
	CPY	#40
	BNE	SEND2
	CLC		;Increment zero
	LDA	POINTR	;page pointer by
	ADC	#40	;40
	STA	POINTR
	BCC	PRNT
	INC	POINTR+1
PRNT	JSR	DOSIO	;Send 40 bytes
	LDA	DONE	;Done?
	BEQ	SENDEM	;No, do the rest
	JSR	INIT	;Re-init
EXIT	LDX	#7
EX1	LDA	ZPSAVE,X	;Restore zero
	STA	ZPOUTP,X	;page locations
	DEX
	BPL	EX1
	PLP
	LDY	#1	;=Successful
	RTS
;
; ---------------------------
; The XIO screen dump routine
; ---------------------------
;
GXIO	STX	XSAVE	;Save IOCB #
	JSR	GOPEN	;Open G:
	CPY	#1	;Open OK?
	BNE	GXXIT	;No, exit
	LDA	#4	;Set line length
	STA	SPLIT
	LDA	SAVMSC	;Set pointers
	STA	SCRPTR	;to start of
	STA	COLPTR	;screen memory
	STA	ROWPTR
	LDA	SAVMSC+1
	STA	SCRPTR+1
	STA	COLPTR+1
	STA	ROWPTR+1
	LDA	ICCOMZ	;Get command #
	STA	XIO?	;Store it
	CMP	#64	;Gr 8 dump?
	BEQ	GR8	;Yes, go to it
	CMP	#17	;Full screen?
	BCS	SHORT	;No, skip
	LDA	#24	;Set 24 lines
	BNE	SETLIN	;Skip
;
SHORT	SBC	#16	;Subtract 16
SETLIN	STA	LINNUM	;Set # of lines
;
STLINE	LDA	#0	;Clear byte
	STA	LINIX	;index
PRSCRN	LDY	LINIX	;Get byte from
	LDA	(SCRPTR),Y	;screen mem
	LDX	XSAVE
	JSR	GPUTB	;Send to G:
	INC	LINIX	;Inc index
	LDA	LINIX
	CMP	#40	;Done one line?
	BNE	PRSCRN	;No, go back
	JSR	NEXTLN	;Inc pointer
	DEC	LINNUM	;Count lines
	BNE	STLINE	;Not done yet
GXXIT	JMP	GCLOSE
;
GR8	LDA	ICAX1Z	;Get width
	CMP	#2	;Single?
	BCC	CKHT	;Yes, skip
	CMP	#3	;Double width?
	BCS	CKW2	;No, skip
	LDA	#5	;Length index
	BNE	CKW3	;Skip
;
CKW2	LDA	#6	;Length index
CKW3	STA	SPLIT
CKHT	LDA	ICDNOZ
	CMP	#3	;>2=Dbl height
	BCC	NOT4	;Not dbl, skip
	STA	DBLHT	;Set flag
	LDX	#<4*40	;Set offset to
	LDY	#>4*40	;next row
	LDA	#48	;Set # of rows
	BNE	SETG8
;
NOT4	LDA	#0	;Not dbl height
	STA	DBLHT	;Clear flag
	LDX	#<8*40	;Offset
	LDY	#>8*40
	LDA	#24	;# of rows
SETG8	STX	ROWADD
	STY	ROWADD+1
	STA	ENDROW
	LDA	#0	;Clear row count
	STA	ROWCNT
GR8.1	LDA	#0	;Set column to 0
	STA	COLCNT
GR8.2	LDA	COLPTR	;Set pointer to
	STA	SCRPTR	;screen
	LDA	COLPTR+1
	STA	SCRPTR+1
	LDX	#0
GET8B	LDY	#0
	LDA	(SCRPTR),Y	;Move bytes
	STA	GRBUF,X	;to buffer
	LDY	DBLHT	;Double height?
	BEQ	GET8.1	;No, skip
	STA	GRBUF+1,X	;Double it!
GET8.1	JSR	NEXTLN	;Inc pointer
	INX
	CPY	#3	;Double height?
	BCC	GET8.2	;No, skip
	INX
GET8.2	CPX	#8	;Done 8 bytes?
	BNE	GET8B	;No, do the rest
	LDA	ICAX1Z	;Get width
	CMP	#2	;>1?
	BCC	NOTDBW	;No, skip
	JSR	EXPAND	;Expand 'em
	LDX	#7
DBW1	LDA	WIDBUF,X	;These sections
	STA	GRBUF,X	;copy the wide
	DEX		;buffer to the
	BPL	DBW1	;gr print buffer
	JSR	GPUTB	;& send it to G:
	LDX	#7
DBW2	LDA	WIDBUF+8,X
	STA	GRBUF,X
	DEX
	BPL	DBW2
	JSR	GPUTB
	LDA	ICAX1Z
	CMP	#3
	BNE	NOTDB2
	LDX	#7
DBW3	LDA	WIDBUF+16,X
	STA	GRBUF,X
	DEX
	BPL	DBW3
NOTDBW	JSR	GPUTB	;Send to G:
NOTDB2	INC	COLPTR	;Move pointer to
	BNE	GR8.3	;next column
	INC	COLPTR+1
GR8.3	INC	COLCNT	;Count columns
	LDA	COLCNT
	CMP	#40	;Done 40?
	BNE	GR8.2	;No. Do the rest
	CLC
	LDA	ROWPTR	;Set row pointer
	ADC	ROWADD	;to the next row
	STA	ROWPTR
	STA	COLPTR	;Also store here
	LDA	ROWPTR+1
	ADC	ROWADD+1
	STA	ROWPTR+1
	STA	COLPTR+1
	INC	ROWCNT	;Count rows
	LDA	ROWCNT
	CMP	ENDROW	;Done all?
	BEQ	GR8XIT	;Yes, skip
	JMP	GR8.1
;
GR8XIT	LDA	#5	;Reset margin
	STA	MARGN
	JMP	GCLOSE
;
; -----------
; Subroutines
; -----------
;
INIT	LDA	#0	;Clear the
	TAX		;output buffer
CLRBUF	STA	OUTBUF,X
	STA	OUTBUF+$0100,X
	STA	OUTBUF+$0200,X
	STA	OUTBUF+$0300,X
	STA	OUTBUF+$0400,X
	INX
	BNE	CLRBUF
	STA	OUTCNT	;Clear character
	STA	OUTCNT+1	;counter
	LDA	#<OUTBUF	;Set pointer
	STA	OUTPTR	;to buffer start
	LDA	#>OUTBUF
	STA	OUTPTR+1
	RTS
;
CLPBUF	LDX	#39	;Clear the SIO
	LDA	#0	;print buffer
CLP2	STA	PRNBUF,X
	DEX
	BPL	CLP2
	RTS
;
DOSIO	LDX	#$0B
COPY	LDA	PRNCOM,X	;Copy print
	STA	DDEVIC,X	;commands to
	DEX		;the DCB
	BPL	COPY
	JMP	SIOV
;
NEXTLN	CLC		;Increment scrn
	LDA	SCRPTR	;pointer by one
	ADC	#40	;line; 40 bytes
	STA	SCRPTR
	BCC	NLN2
	INC	SCRPTR+1
NLN2	RTS
;
EXPAND	LDX	#23	;Clear the wide
	LDA	#0	;buffer
CLWBUF	STA	WIDBUF,X
	DEX
	BPL	CLWBUF
	LDX	#7	;Expand 8 bytes
EXP1	LDA	#7	;7 bits per byte
	STA	BITCNT
	LDA	GRBUF,X	;Get byte
EXP2	LSR	A	;Shift bit
	PHA		;Save byte
	LDY	ICAX1Z	;Get width
EXP3	PHP		;Save status reg
	ROR	WIDBUF,X	;Roll carry
	ROR	WIDBUF+8,X	;thru 3 bytes
	ROR	WIDBUF+16,X
	PLP		;Restore status
	DEY		;Width count
	BNE	EXP3	;Not done
	PLA		;Restore byte
	DEC	BITCNT	;Count bits
	BPL	EXP2	;Not done yet
	DEX		;Count bytes
	BPL	EXP1	;Not done yet!
	RTS		;DONE!!!
;
; ------------------
; Miscellaneous data
; ------------------
;
DVSAVE	.DS	4	;DOS vectors
PRNCOM	.BYTE	$40	;Printer
	.BYTE	1	;Device number
	.BYTE	$57	;Write
	.BYTE	$80	;Output
	.WORD	PRNBUF	;Buffer address
	.WORD	5	;Timeout
	.WORD	$28	;Buffer length
	.BYTE	$4E	;Normal print
	.BYTE	0	;Unused
ZPSAVE	.WORD	0,0,0,0
OUTPTR	.WORD	0	;Ptr to buffer
XSAVE	.BYTE	0	;Calling IOCB #
BYTCNT	.BYTE	0	;Byte counter
BITCNT	.BYTE	0	;Bit counter
INVERS	.BYTE	0	;Inverse flag
SPLIT	.BYTE	0	;Length index
DONE	.BYTE	0	;Line done flag
XIO?	.BYTE	0	;XIO flag
LINIX	.BYTE	0	;Screen index
LINNUM	.BYTE	0	;# of lines
ROWCNT	.BYTE	0	;Row counter
COLCNT	.BYTE	0	;Column counter
ROWPTR	.WORD	0	;Row pointer
COLPTR	.WORD	0	;Column pointer
ROWADD	.WORD	0	;Offset to row
ENDROW	.BYTE	0	;# of rows
DBLHT	.BYTE	0	;Dbl height flag
GRBUF	.BYTE	0,0,0,0,0,0,0,0
WIDBUF	.WORD	0,0,0,0,0,0,0,0
	.WORD	0,0,0,0
GRTBL	.BYTE	0,0,0,0,0,0,0
GRNUM	.BYTE	76,76,75,75
LFNUM	.BYTE	8,9,12
SPVAL	.BYTE	0,38,38,60,40,80,120
ICODES	.BYTE	27,64	;Init printer
	.BYTE	27,78,5	;Skip perf
	.BYTE	27,77
MARGN	.BYTE	5	;Left margin
	.BYTE	27,65
LNFEED	.BYTE	8	;LF (n/72 in.)
;
ICLEN	=	*-ICODES
;
HEADER	.BYTE	27
GRMODE	.BYTE	0
OUTCNT	.WORD	0
;
OUTBUF	.DS	$0500	;Print buffer
;
PND	=	*
;
	*=	RUNAD
	.WORD	INSTAL
	.END