Olivetti Desktop Publisher is a two-column page layout program that reads text from REM statements embedded in the BASIC program itself, formats it into a two-column layout, and outputs it via the COPY command to a Winkjet-driven Olivetti 2300 inkjet printer. Text lines are stored in a 160-element string array `h$` with 34 characters each; the first two characters of each entry encode a memory address for font selection, allowing both the built-in font and a user-defined font (loaded at address 49230) to be mixed on the same page. Machine code routines are POKEd into memory at addresses 50000, 65336, and 65400 during initialization; the routine at 65336 handles the Winkjet interface while the block at 65400 contains UDG bitmap data for custom arrow and symbol characters. A companion “Font Maker” utility is included in the same listing (lines 10–9999 at the end), which loads separate machine code and provides a font-design environment.
Program Analysis
Program Structure
The listing contains two distinct programs. The main desktop publisher occupies lines 9000–9999 (with initialization at 9001–9020, main menu dispatch at 9023, and functional routines from 9024 onward). A separate “Font Maker” utility occupies lines 10–9999 at the end of the listing, saved independently via SAVE "Font Maker" LINE 1. Lines 9996–9999 handle the save/load bootstrap sequence for the main program.
Initialization and Machine Code
Three machine code blocks are installed at startup:
- 50000–50029 (lines 9001–9002): 30 bytes copied from DATA. Appears to perform a block memory copy (Z80
LDIRat offset 9) from address 15616 to 49230, used to transfer font data. Called viaRANDOMIZE USR 50000. - 65336–65366 (lines 9003–9004): 31 bytes. Uses
LDIRto copy 8 bytes from address 0x4000 (screen) into the printer buffer region. This is the Winkjet printer interface routine, invoked at line 9119 viaRANDOMIZE USR 65336. - 65400–65463 (lines 9005–9011): 64 bytes of UDG bitmap data for eight custom characters (arrows, symbols, geometric shapes), each 8 bytes.
The font switching mechanism uses subroutine 9121, which POKEs the font base address into system variables at 23606–23607 (the UDG/CHARS pointer), effectively redirecting character rendering to either the ROM font at 15616, the user font at 49230, or custom bitmaps at 49230.
Data Model
All page content is tracked through two parallel structures:
h$(z,34)— a 160×34 string array. Each row stores one line of text. Bytes 1–2 encode a font address (low byte, high byte as CHR$), byte 3 onward holds the printable text padded with spaces fromg$.L(z)— a 160-element numeric array mapping page slot numbers (1–160) toh$row indices. Slots 1–80 represent column 1; slots 81–160 represent column 2.
When a line begins with a space (h$(b,1)=" "), it is treated as plain body text and printed directly. Otherwise, bytes 1–2 are decoded as a 16-bit address (little-endian: CODE h$(b,1) + CODE h$(b,2)*256) to select a font, and the text from byte 3 onward is rendered in that font.
Text Input from REM Statements
The “Read REM” path (lines 9034–9045) walks through the program’s own memory starting at address 26710, which is the typical ZX Spectrum location for the start of the BASIC program area offset. It scans for the line structure, skipping the line header bytes, and reads character data byte-by-byte until it finds a CHR$ 13 (newline) or exhausts 30 characters. Each REM line becomes one entry in h$. This is a well-known technique for embedding large amounts of text in a program without using string variables.
Page Layout and Display
The preview display (subroutine 9150–9163) renders both columns side by side on screen. Column 1 occupies print positions 0–14 and column 2 occupies positions 15–31, with a PAPER 6 separator character between them. The layout loop iterates stop rows (14 or 20 depending on context) and handles the end-of-column sentinel at line=80 by printing a “(bottom of page)” marker. Subroutine 9147 resets the display state for a fresh page view.
Printing
Lines 9093–9111 handle the actual print pass. The program iterates through all 160 page slots in column order (14 + 3×22 = 80 lines per column), calling subroutine 9102 for each slot. Subroutine 9107 pauses with an INPUT prompt so the user can issue a COPY command (line 9110) at each page segment, driving the Winkjet interface. The COPY keyword triggers the machine code at 65336 to transmit the screen bitmap to the Olivetti 2300.
Menu Dispatch
Line 9023 uses a computed GO TO expression — a sum of terms of the form (offset AND INKEY$="key") — to branch to different line numbers depending on which key is pressed. This is a compact multi-way branch idiom. The offsets added to the base address 9023 give destinations:
| Key | Offset | Destination | Function |
|---|---|---|---|
| R | 9 | 9032… (via 9034) | Read REM text |
| L | 7 | 9030 | Load SCREEN$ |
| S | 8 | 9031 | Save program |
| C | 102 | 9125 | Edit/compose text |
| W | 92 | 9115 | Winkjet output |
| A | 23 | 9046 | Arrange columns |
| V | 70 | 9093 | View/print page |
| E | 6 | 9029 | Erase data |
| T | U (=1) | 9024 | Text entry help |
| F | 11 | 9034 | Format/read REM |
Winkjet / Printer Interface
Lines 9115–9120 implement a loop that calls subroutine 9118 repeatedly, controlled by data from addresses 49147 (line count) and 49179 (a “Y”=89 sentinel confirming Winkjet software is present). Each call to 9118 POKEs the low and high bytes of the current storage address into 23728–23729, then calls RANDOMIZE USR 65336 to transmit 8 bytes of screen data to the printer, then advances stor by 256. This byte-by-byte transfer loop is how the Winkjet driver receives bitmap data.
Notable Techniques
- Constants
O=0andU=1are used throughout as substitutes for the literals 0 and 1, saving a byte per occurrence in the tokenized program. g$(line 9018) holds the printable ASCII character set from space to “?”, used as a 32-character blank/padding string viah$(b,3 TO )=g$.b$(line 9013) is a 32-character blank string used as a blank-line placeholder throughout display routines.l$(line 9016) is a 32-underscore separator line used in print previews.- Line 9040 POKEs 23692 (the scroll counter) with 255 to suppress the “scroll?” prompt during bulk text rendering.
- The
DELETEkeyword at line 9996 (DELETE 9997,) removes lines from the BASIC program at runtime — a non-standard keyword available on some extended BASICs, used here to clean up the bootstrap loader lines after loading.
Bugs and Anomalies
- Line 9088 checks
IF J>zwith an identical condition to line 9087, making it unreachable dead code. - Line 9097 contains a double colon (
GO SUB 9102::) which is syntactically harmless but redundant. - The variable
mnis set to 9021 (line 9014) but line 9021 exists in the listing, soGO TO mnreferences are valid. However,GO TO MN(uppercased) at line 9029 suggests case inconsistency — in Sinclair BASIC, variable names are case-sensitive andMNwould be an undefined variable; this is likely a listing transcription artifact. - Similarly, mixed-case variable references such as
STORvsstor,TEXTvstext,H$vsh$,Zvszappear in lines 9029 and 9087–9088, suggesting these uppercased forms are transcription artifacts rather than intentional distinct variables.
Content
Source Code
9001 RESTORE 9001: FOR j=50000 TO 50029: READ a: POKE j,a: NEXT j: RANDOMIZE USR 50000
9002 DATA 33,0,61,17,78,192,1,0,3,237,176,33,78,192,17,0,3,126,79,203,63,177,119,35,27,122,179,200,24,243
9003 FOR j=65336 TO 65366: READ b: POKE j,b: NEXT j
9004 DATA 17,0,64,42,176,92,213,6,8,26,119,20,35,16,250,209,19,229,33,32,64,175,237,82,225,32,235,205,57,9,201
9005 FOR j=65400 TO 65463: READ a: POKE j,a: NEXT j
9006 DATA 16,32,124,252,124,32,16,0
9007 DATA 8,4,126,127,126,4,8,0
9008 DATA 0,56,56,56,186,124,56,16
9009 DATA 16,56,124,186,56,56,56,0
9010 DATA 127,224,206,195,195,231,126,0,0,192,96,48,24,12,3,0,126,231,195,195,115,7,254,0
9011 DATA 8,20,34,73,34,20,8,0
9012 LET X=256: LET z=160: LET O=0: LET u=1
9013 DIM h$(z,34): DIM L(z): DIM b$(32)
9014 LET mn=9021: LET stor=50000
9015 LET text=o: LET top=o: LET p$=CHR$ 13+CHR$ 13+CHR$ 13+"(Press Key)"
9016 LET l$="________________________________"
9017 LET m$="Menu ENTER=Scroll "
9018 LET g$=" !""#$%&'()*+,-./0123456789:;<=>?"
9019 LET n$="No more room..."
9020 LET cp=23658: LET y$=" Y/N ": RETURN
9021 LET ADDR=15616: GO SUB 9121: BORDER 6: PAPER 7: INK o: RANDOMIZE USR 46270
9023 POKE cp,8: GO TO (9 AND INKEY$="R")+(7 AND INKEY$="L")+(8 AND INKEY$="S")+(102 AND INKEY$="C")+(92 AND INKEY$="W")+(23 AND INKEY$="A")+(70 AND INKEY$="V")+(6 AND INKEY$="E")+(U AND INKEY$="T")+(11 AND INKEY$="F")+9023
9024 CLS : PRINT "Put text in REM statements"''" 1 REM Text goes here."'" 10 REM (New paragraph) Text goes here."'"---"'"---"'"---"''''''"When finished type:"'"GOTO 9000": STOP
9029 PRINT #U;"Erase data?";y$: PAUSE o: LET i$=INKEY$: IF i$="Y" THEN PRINT #u;"Data erased": PAUSE 50: DIM H$(Z,34): DIM L(Z): LET STOR=50000: LET TEXT=O: LET TOP=O: GO TO MN
9030 CLS : PRINT "Type: LOAD """"SCREEN$ : GO TO 9122": STOP
9031 CLS : PRINT " TO RELOAD TYPE:"''" CLEAR 46269"'" LOAD """""'" LOAD """"CODE "'" LOAD """"CODE "''" GO TO 9000": SAVE "prog": SAVE "m/c"CODE 46270,stor-46270: SAVE "UDG"CODE 65336,199
9032 STOP
9034 CLS : LET text=top: LET ad=26710
9035 LET ad=ad+5: IF PEEK (ad-u)<>234 THEN BEEP .5,.20: PRINT ''"Room for ";z-text;" more lines"'p$: PAUSE o: GO TO mn
9036 LET beg=ad: LET end=beg+30: IF PEEK end<>32 THEN GO TO 9043
9037 IF text=z THEN CLS : PRINT n$: BEEP u,20: STOP
9038 LET text=text+u: LET h$(text)=b$: LET pos=u: FOR j=beg TO end: LET ad=ad+u: IF PEEK j=13 THEN PRINT : GO TO 9035
9039 LET pos=pos+u: IF pos<32 THEN LET h$(text,pos)=CHR$ PEEK j: PRINT CHR$ PEEK J;
9040 NEXT j: POKE 23692,X-U: PRINT
9041 IF PEEK ad=32 THEN LET ad=ad+u: GO TO 9041
9042 GO TO 9036
9043 LET end=end-u: IF PEEK end=13 THEN GO TO 9037
9044 IF PEEK end<>32 THEN GO TO 9043
9045 GO TO 9037
9046 CLS : LET flag=o
9047 LET addr=15616: GO SUB 9121: LET k=u: FOR a=u TO 8: PRINT AT u,9;"(Source File)": FOR j=u+u TO 21
9048 IF h$(k,u)=" " THEN PRINT AT j,u+u;h$(k, TO 30): GO TO 9050
9049 LET addr=CODE h$(k,u)+CODE h$(k,u+u)*X: GO SUB 9121: LET h$(k,3 TO )=g$: PRINT AT j,3;h$(k,3 TO 31)
9050 LET addr=49230: GO SUB 9121: PRINT AT j,o; PAPER 6;k: IF k<10 THEN PRINT AT j,u;" "
9051 IF k<100 THEN PRINT AT j,u+u;" "
9052 LET k=k+u: NEXT j
9053 IF flag THEN GO TO 9061
9054 INPUT AT o,o;(m$;"View page"'"Begin text block at line # "); LINE i$
9055 IF i$="V" THEN LET line=o: GO TO 9140
9056 IF i$="M" THEN GO TO mn
9057 IF i$="" THEN NEXT a: GO TO 9047
9058 LET i=VAL i$
9059 IF i<u OR i>z THEN GO TO 9054
9060 LET start=i
9061 INPUT AT o,o;(m$'"End text block at line # "); LINE i$
9062 IF i$="" THEN LET flag=u: NEXT a: GO TO 9047
9063 IF i$="M" THEN GO TO 9021
9064 LET i=VAL i$
9065 LET end=i: IF i<u OR i>z OR end<start THEN GO TO 9061
9066 LET i=u+end-start
9067 GO SUB 9147: GO SUB 9150
9068 INPUT AT o,o;("Put ";i;" lines in column 1 or 2? ");col
9069 IF COL<u OR COL>u+u THEN GO TO 9068
9070 LET c=8: LET cline=u
9071 LET flag=o: LET C$=CHR$ 148: IF COL=u+u THEN LET C$=CHR$ 149
9072 PRINT AT C,15; PAPER 6;C$: PRINT #u;AT o,o;"\h\g\e\f Putting ";i;" lines in col ";col'"Do it Source file Erase Menu"
9073 PRINT AT o,15;cline-81;" ";AT o,11;"Line="
9074 PAUSE o: LET I$=INKEY$: IF I$="7" AND c=u+u THEN GO TO 9073
9075 IF I$="5" THEN LET COL=u
9076 IF I$="8" THEN LET COL=u+u
9077 IF I$="7" AND cline>u THEN LET cline=cline-u: PRINT AT c,15; PAPER 6;" ": LET c=c-u
9078 IF I$="6" THEN LET cline=cline+u: PRINT AT c,15; PAPER 6;" ": LET c=c+u
9079 IF I$="6" AND c=22 THEN LET c=u+u: LET stop=20: LET beg=u+u: GO SUB 9150
9080 IF I$="E" THEN GO SUB 9112: LET L(A)=o: GO SUB 9092: GO TO 9071
9081 IF I$="M" THEN GO TO mn
9082 IF I$="S" THEN GO TO 9046
9083 IF I$="D" THEN GO TO 9086
9084 IF cline>80 THEN GO SUB 9147: GO SUB 9150: LET cline=u
9085 GO TO 9071
9086 GO SUB 9112
9087 FOR j=a TO a+i-u: IF j>z THEN CLS : PRINT n$'p$: PAUSE o: GO TO 9046
9088 IF J>z THEN CLS : PRINT AT 20,o;N$: PAUSE 60: GO TO 9046
9089 LET l(j)=start: LET start=start+u: IF J=z THEN GO TO 9091
9090 IF flag=o AND l(J+u)<>o THEN LET flag=u: INPUT AT o,o;("Overlay what's already on page?"'y$); LINE i$: IF i$="N" THEN GO SUB 9092: GO TO 9071
9091 NEXT J: GO SUB 9092: GO TO 9071
9092 LET line=line1: LET stop=stop1: LET beg=beg1: GO SUB 9150: RETURN
9093 CLS : INPUT ("User font?";y$); LINE i$: LET addr=15616: IF i$="Y" THEN LET addr=49230
9094 LET D=ADDR: GO SUB 9121: CLS : LET K=O: PRINT "Top of Col. 1"'l$''''''': FOR j=u TO 14: GO SUB 9102: NEXT j: GO SUB 9107
9095 FOR a=u TO 3: FOR j=u TO 22: GO SUB 9102: NEXT j: GO SUB 9107: NEXT a
9096 PRINT ''''''l$;"Bottom of Col. 1"'"--------------------------------Top of Col. 2"'l$'''''''
9097 FOR j=u TO 5: GO SUB 9102:: NEXT j: GO SUB 9107
9098 FOR j=u TO 3: FOR a=u TO 22: GO SUB 9102: NEXT a: GO SUB 9107: NEXT j
9099 FOR j=u TO 9: GO SUB 9102: NEXT j
9100 PRINT ''''''l$;"Bottom of Col. 2"'''''': GO SUB 9107
9101 GO TO mn
9102 LET k=k+u: LET b=l(k): IF b=o THEN PRINT b$: RETURN
9103 IF h$(b,u)=" " THEN GO TO 9105
9104 LET addr=CODE h$(b,u)+CODE h$(b,2)*X: GO SUB 9121: LET h$(b,3 TO )=g$: PRINT h$(b,3 TO ): LET addr=D: GO SUB 9121: RETURN
9105 PRINT h$(b,u+u TO 32)
9106 RETURN
9107 POKE cp,8: INPUT ("Copy ";m$); LINE i$: IF i$="C" THEN GO TO 9110
9108 IF i$="M" THEN GO TO mn
9109 RETURN
9110 COPY
9111 CLS : RETURN
9112 IF COL=u THEN LET A=CLINE
9113 IF COL=u+u THEN LET A=CLINE+80
9114 RETURN
9115 RANDOMIZE USR 46725
9116 IF PEEK 49179<>89 THEN GO TO mn
9117 FOR j=u TO PEEK 49147: GO SUB 9118: NEXT j: GO TO mn
9118 IF stor>64000 THEN CLS : PRINT n$'p$: PAUSE u: GO TO mn
9119 LET top=top+u: LET text=top: POKE 23728,stor-X*INT (stor/X): LET h$(top,u)=CHR$ PEEK 23728: POKE 23729,INT (stor/X): LET h$(top,u+u)=CHR$ PEEK 23729: RANDOMIZE USR 65336: LET stor=stor+X
9120 RETURN
9121 LET addr=addr-X: POKE 23606,addr-X*INT (addr/X): POKE 23607,INT (addr/X): LET addr=addr+X: RETURN
9122 PRINT #u;AT o,o;"Press ENTER until all of SCREEN$is scrolled up. STOP when ready"
9123 PAUSE o: IF INKEY$=" STOP " THEN GO TO mn
9124 GO SUB 9118: GO TO 9123
9125 CLS : PRINT "If you re-format REM text, your text editing here will be erased": LET k=top
9126 FOR a=u TO 8: FOR j=u TO 20
9127 LET addr=49230: GO SUB 9121: LET k=k+u: IF k>z THEN LET k=top+u
9128 PRINT PAPER 6;k;TAB 3; PAPER 7;h$(k,u+u TO 30)
9129 NEXT j
9130 POKE cp,8: INPUT AT o,o;(m$'"Enter text at line # "); LINE i$
9131 IF i$="M" THEN GO TO mn
9132 IF i$="" THEN CLS : NEXT a: GO TO 9125
9133 LET i=VAL i$
9134 CLS
9135 POKE CP,O: INPUT AT o,o;("Room for ";z-i;" lines."''"Enter text for Line #";i'"Press STOP when finished"''"Line #";i;" now is:"'' PAPER 7;h$(i,u+u TO 32);AT 7,31;"\e";AT 4,o;)''' LINE i$: IF LEN i$>30 THEN PRINT #1;AT o,o;"Line too long": PAUSE 60: GO TO 9135
9136 IF i$=" STOP " THEN GO TO mn
9137 LET h$(i,u+u TO )=b$: LET h$(i,u+u TO )=i$: PRINT i$
9138 LET I=I+u: IF i>z THEN CLS : PRINT n$: PAUSE 60: GO TO mn
9139 GO TO 9135
9140 IF line=o THEN GO SUB 9147
9141 GO SUB 9150
9142 POKE cp,8: INPUT (m$;"Edit page "); LINE i$
9143 IF i$="M" THEN GO TO mn
9144 IF i$="E" THEN GO TO 9046
9145 IF i$="" THEN LET stop=20: LET beg=u+u: GO TO 9140
9146 GO TO 9142
9147 CLS : PRINT " Col 1 Col 2"''" (top of page)"
9148 LET line=o: LET beg=8: LET stop=14
9149 RETURN
9150 LET addr=49230: GO SUB 9121: IF line<>o THEN PRINT AT o,u+u;b$
9151 LET line1=line: LET stop1=stop: LET beg1=beg
9152 PRINT AT beg,o;
9153 FOR j=u TO stop: IF line=80 THEN FOR k=u TO 12: PRINT b$: NEXT k: PRINT TAB 7;"(bottom of page)";b$( TO 9): PRINT B$: LET line=o: RETURN
9154 LET line=line+u: LET d=l(line): IF d=o THEN PRINT b$( TO 15);: GO TO 9158
9155 IF h$(d,u)=" " THEN GO TO 9157
9156 LET addr=CODE h$(d,u)+CODE h$(d,u+u)*X: GO SUB 9121: LET h$(d,3 TO )=g$: PRINT h$(d,3 TO 17);: LET addr=49230: GO SUB 9121: GO TO 9158
9157 PRINT h$(d,u+u TO 16);
9158 PRINT TAB 15; PAPER 6;" "; PAPER 7;: LET b=l(line+80): IF b=0 THEN PRINT b$( TO 16): GO TO 9162
9159 IF h$(b,u)=" " THEN GO TO 9161
9160 LET addr=CODE h$(b,u)+CODE h$(b,u+u)*X: GO SUB 9121: LET h$(b,3 TO )=g$: PRINT h$(b,3 TO 18): LET addr=49230: GO SUB 9121: GO TO 9162
9161 PRINT h$(b,u+u TO 17)
9162 NEXT j
9163 RETURN
9996 DELETE 9997,: GO TO mn
9997 CLEAR 46269: LOAD ""CODE
9998 INPUT "Backup? y/n ";i$: IF i$="n" THEN GO SUB 9000: DELETE 9000,9020: GO TO 9996
9999 SAVE "DESKTOP" LINE 9997: SAVE "46270,2873"CODE 46270,2873: GO TO 9998
10 RESTORE : FOR j=65368 TO 65431: READ x: POKE j,x: NEXT j
20 CLEAR 59476: LOAD ""CODE : RANDOMIZE USR 59477
30 STOP
100 DATA 0,0,0,28,28,28,0,0,0,127,127,127,127,127,127,127,255,128,128,128,128,128,128,128,128,128
150 DATA 0,0,0,0,0,0
200 DATA BIN 00010000
201 DATA BIN 00111000
202 DATA BIN 01111100
203 DATA BIN 11111110
204 DATA BIN 00111000
205 DATA BIN 00111000
206 DATA BIN 00111000
207 DATA BIN 00000000
208 REM
210 DATA BIN 00000000
211 DATA BIN 00010000
212 DATA BIN 00110000
213 DATA BIN 01111110
214 DATA BIN 11111110
215 DATA BIN 01111110
216 DATA BIN 00110000
217 DATA BIN 00010000
218 REM
220 DATA BIN 00000000
221 DATA BIN 00001000
222 DATA BIN 00001100
223 DATA BIN 01111110
224 DATA BIN 01111111
225 DATA BIN 01111110
226 DATA BIN 00001100
227 DATA BIN 00001000
228 REM
230 DATA BIN 00000000
231 DATA BIN 00111000
232 DATA BIN 00111000
233 DATA BIN 00111000
234 DATA BIN 11111110
235 DATA BIN 01111100
236 DATA BIN 00111000
237 DATA BIN 00010000
9999 SAVE "Font Maker" LINE 1: SAVE "59477,3987"CODE 59477,3987
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
