Olivetti Desktop Publisher

Developer(s): Charles Stelding
Date: 1987
Type: Program
Platform(s): TS 2068

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 LDIR at offset 9) from address 15616 to 49230, used to transfer font data. Called via RANDOMIZE USR 50000.
  • 65336–65366 (lines 9003–9004): 31 bytes. Uses LDIR to copy 8 bytes from address 0x4000 (screen) into the printer buffer region. This is the Winkjet printer interface routine, invoked at line 9119 via RANDOMIZE 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 from g$.
  • L(z) — a 160-element numeric array mapping page slot numbers (1–160) to h$ 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:

KeyOffsetDestinationFunction
R99032… (via 9034)Read REM text
L79030Load SCREEN$
S89031Save program
C1029125Edit/compose text
W929115Winkjet output
A239046Arrange columns
V709093View/print page
E69029Erase data
TU (=1)9024Text entry help
F119034Format/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=0 and U=1 are 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 via h$(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 DELETE keyword 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>z with 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 mn is set to 9021 (line 9014) but line 9021 exists in the listing, so GO TO mn references are valid. However, GO TO MN (uppercased) at line 9029 suggests case inconsistency — in Sinclair BASIC, variable names are case-sensitive and MN would be an undefined variable; this is likely a listing transcription artifact.
  • Similarly, mixed-case variable references such as STOR vs stor, TEXT vs text, H$ vs h$, Z vs z appear in lines 9029 and 9087–9088, suggesting these uppercased forms are transcription artifacts rather than intentional distinct variables.

Content

Appears On

Related Products

Written in BASIC, the text of a column is stored in REM statements at the beginning of the program. Allows...

Related Articles

Related Content

Image Gallery

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.

Scroll to Top