--- title: "ExFont 1.1" id: 51179 type: "computer_media" slug: "exfont-1-1" url: "http://localhost/computer_media/exfont-1-1/" markdown_url: "http://localhost/computer_media/exfont-1-1.md" published_at: "2023-07-15T23:17:05+00:00" modified_at: "2026-03-31T08:04:08+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2023/07/ExFont-screen-3.png" excerpt: "A pixel-level font editor lets you redesign any character in the main or user-defined set, then save your custom font as a loadable CODE block." category: - name: "Archived Media" slug: "archived-media" taxonomy: "category" url: "http://localhost/category/archived-media/" post_tag: - name: "Downloadable" slug: "downloadable" taxonomy: "post_tag" url: "http://localhost/tag/downloadable/" - name: "TS 2068" slug: "ts2068" taxonomy: "post_tag" url: "http://localhost/tag/ts2068/" model: - name: "Timex/Sinclair 2068" slug: "ts-2068" taxonomy: "model" url: "http://localhost/model/ts-2068/" indiv: - name: "Randy Moulder" slug: "randy-moulder" taxonomy: "indiv" url: "http://localhost/indiv/randy-moulder/" genre: - name: "User Defined Graphics" slug: "user-defined-graphics" taxonomy: "genre" url: "http://localhost/type/user-defined-graphics/" media_type: "Program" programmers: - name: "Randy Moulder" slug: "randy-moulder" taxonomy: "indiv" url: "http://localhost/indiv/randy-moulder/" download_url: "https://archive.org/download/timex-sinclair-software-archive/ExFont%201.1%20%281985%29%28Moulder%2C%20R.%20L.%29%28US%29%28TS2068%29%28Program%29.zip" mediadate: "1985" images: - url: "http://localhost/wp-content/uploads/2023/07/ExFont-screen-3.png" - url: "http://localhost/wp-content/uploads/2023/07/ExFont-screen-1.png" - url: "http://localhost/wp-content/uploads/2023/07/ExFont-screen-2.png" - url: "http://localhost/wp-content/uploads/2023/07/ExFont-screen-4.png" - url: "http://localhost/wp-content/uploads/2023/07/ExFont-cassette.jpg" article_media: - id: 39170 title: "The Fame & The Glory" type: "article" url: "http://localhost/article/the-fame-the-glory/" media_type_tags: "User Defined Graphics" --- # ExFont 1.1 ExFont 1.1 is a character set editor that allows users to redesign both the main character set (CHR$ 32–127) and user-defined graphics (CHR$ 144–164) by directly POKEing new bitmap data into memory. The program stores a replacement font as a binary CODE block loaded at address 64600, and switches between the built-in and custom character sets by POKEing the system variable at addresses 23606–23607 (the CHARS pointer). Navigation within the 8×8 pixel grid editor is handled via the STICK command, with FIRE toggling individual bits between set (█) and clear (space). Each row of a character is represented as an 8-character string using block graphics for 1-bits, and subroutines at lines 100 and 200 convert between these binary string representations and decimal byte values for PEEK/POKE operations. A short ascending BEEP sequence at line 9100 serves as an audio prompt before each INPUT statement. *** ## Program Analysis ### Program Structure The program is organized into clearly labeled subroutine blocks, each preceded by a REM banner. Execution begins at line 8000 (initialization), which loads the CODE block, sets up variables, displays notes, and falls through to the main menu at line 8100. The broad structure is: 1. Lines 100–190: Binary string → decimal byte conversion (bin→dec) subroutine 2. Lines 200–400: Decimal byte → binary string conversion (dec→bin) subroutine 3. Lines 1000–1260: Character range selection loop 4. Lines 1500–1620: Per-character display and editing driver 5. Lines 3000–3950: Interactive pixel editor (STICK-based) 6. Lines 5002–5050: Commit edits, POKE bytes back, refresh display 7. Lines 8000–8090: Initialization 8. Lines 8100–8300: Main menu 9. Lines 8400–8430: SAVE routines 10. Lines 8500–8640: Notes/startup screen 11. Lines 9000–9070: Character set toggle 12. Lines 9100–9150: Audio prompt subroutine 13. Lines 9200–9450: Print character set comparison screen ### Memory and Font Addressing The program exploits the TS2068/Spectrum system variable at addresses 23606–23607, which holds a pointer to the character set bitmap data (offset by 256 from CHR$ 32). Two font bases are used: | Font | CHARS pointer value | Base address | | --- | --- | --- | | Built-in ROM font | 15360 (POKE 23606,0 : POKE 23607,60) | 15616 | | Custom font (RAM) | 64344 (POKE 23606,88 : POKE 23607,251) | 64600 | The custom CODE block occupies 936 bytes starting at 64600, covering CHR$ 32–127 (768 bytes) and UDGs 144–164 (168 bytes). Address calculation at line 1520 handles UDGs separately: `add=64472+((c-32)*8)`, while the main set uses `add=64600+((c-32)*8)` at line 1525. `CLEAR 64599` at line 8010 protects this area from BASIC’s heap. ### Binary String Representation Each 8×8 character is held in a two-dimensional string array `N$(8,8)`. Each row is an 8-character string where a set pixel is represented by the block graphic █ (`\::`, character 143) and a clear pixel by UDG `\a` (character 144, displayed as a space-equivalent). The dec→bin subroutine (line 200) extracts bits with repeated halving and remainder testing; the bin→dec subroutine (line 100) reconstructs bytes by scanning the string and treating the block graphic as “1” and the UDG as “0” via `CODE(B$(p))-48` — though this relies on the specific code values of those characters rather than a true ASCII “0”/”1″ comparison. ### The Pixel Editor The interactive editor (lines 3200–3950) places an 8×8 grid at screen rows 14–21, columns 15–22. A flashing cursor character `\d` (UDG d, code 157) marks the current cell. The editor polls `STICK(1,1)` for directional movement and `STICK(2,1)` for the fire button simultaneously with `INKEY$` for ENTER to quit. Movement is handled by four labeled goto targets (3600=up, 3700=down, 3800=left, 3900=right), each printing the underlying cell content before repositioning the cursor and wrapping at boundaries. Bit toggling at lines 3400 and 3500 spins on `STICK(2,1)>0` to wait for button release before returning to the main poll loop, preventing repeated triggers. The cursor position in screen coordinates `(x,y)` maps to the `N$` array via `N$(x-13, y-14)`: row index `x-13` (range 1–8) and column index `y-14` (range 1–8). ### Notable Techniques - **CHARS pointer toggling:** The toggle routine at line 9000 reads the current pointer value and swaps between the two known states, allowing live comparison of ROM and custom glyphs. - **Side-by-side font comparison (line 9200):** For each character, the routine temporarily POKEs the ROM base, prints the ROM glyph, then POKEs the custom base and prints the custom glyph, yielding a two-column comparison in a single loop pass. - **Audio prompt (line 9100):** An eight-step ascending BEEP sequence (semitones 2,5,7,10,12,15,17,20) plays before every INPUT, providing consistent audio feedback without interrupting screen layout. - **Separator string `Z$`:** Initialized at line 8020 as 32 cursor-left characters (`\b` = CHR$ 8), used as a horizontal rule by overprinting the current line. - **Skipping undefined codes:** Lines 1512–1515 skip codes 124, 126, and 128–143 (the block graphics range), since these are not user-redefinable in the same way, and the loop variable `c` is advanced directly rather than using a conditional skip flag. ### Bugs and Anomalies - **Missing line 5000:** Line 3280 executes `GO TO 5000` on ENTER, but line 5000 does not exist; execution continues at line 5002. This is an intentional technique — the interpreter advances to the next available line. - **CODE K$ at line 8215:** The condition `CODE INKEY$>5 OR CODE INKEY$<1` calls `INKEY$` twice in a single statement with no guarantee the same key is read both times; however, since this line has no effect (it falls through to 8220 regardless), it is functionally harmless dead code. - **bin→dec code arithmetic:** Line 130 uses `CODE(B$(p))-48`. After substitution, `B$(p)` is either “1” (CODE 49, giving 1) or “0” (CODE 48, giving 0), so the arithmetic is correct — but only because the subroutine first replaces the graphic characters with literal digit characters at lines 122–123. - **UDG address formula:** Line 1520 uses `64472+((c-144)*8+8*112)` simplified to `64472+((c-32)*8)`. Since UDGs start at code 144 and their bitmaps are placed after the 96 main-set characters (96×8=768 bytes from 64600 = 65368), the formula actually points 896 bytes past 64600 for code 144, which is 65368 — matching a layout where UDG data immediately follows the main set. This is consistent with the 936-byte save length (768+168=936). ## Source Code ``` 1 REM \b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b existo:ray software 85 \b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b 2 REM ExFont 1.1 3 REM R. L. Moulder 4 REM 206 Yeonas Dr. SW 5 REM Vienna, Va 22180 100 REM \c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c bin->dec 108 FOR x=1 TO 8 109 LET B$=N$(x) 110 LET n(2,x)=0 120 FOR p=1 TO 8 122 IF B$(p)="\::" THEN LET B$(p)="1" 123 IF B$(p)="\a" THEN LET B$(p)="0" 130 LET n(2,x)=n(2,x)*2+(CODE (B$(p))-48) 140 NEXT p 145 POKE n(1,x),n(2,x) 150 NEXT x 190 RETURN 200 REM \c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c dec->bin 210 DIM B$(8) 220 LET p=8 230 LET B$="\a\a\a\a\a\a\a\a" 240 LET d=n(2,x) 250 LET n=INT (d/2) 260 IF d-2*n=1 THEN LET B$(p)="\::" 280 LET d=n 290 LET p=p-1 300 IF d>0 THEN GO TO 250 390 LET N$(x)=B$ 400 RETURN 1000 REM \c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c set CHR$ and loop 1010 CLS 1020 PRINT AT 3,0;" Enter code # of FIRST and LAST characters to be re-designed." 1030 INK 3: PRINT AT 5,0;Z$: INK 5 1040 PRINT AT 6,0;" Main Character Set 32 \g\h 127 User Graphics 144 \g\h 164" 1045 PRINT AT 10,0;" NOTE: Program skips code #'s 128 thru 143, 124 & 126 automatically" 1050 PRINT AT 13,0;" To change one character only use its code # for both inputs." 1060 INK 3: PRINT AT 15,0;Z$ 1200 GO SUB 9100: INPUT "Enter First Code # ";c1 1205 IF c1<32 OR c1>164 THEN GO TO 1200 1210 INK 5: PRINT AT 17,1;"CODE ";c1;" (";CHR$ c1;")";" thru "; 1220 GO SUB 9100: INPUT "Enter Second Code # ";c2 1225 IF c2<32 OR c2>164 OR c2127 AND c<144 THEN LET c=144 1520 IF c>=144 THEN LET add=64472+((c-32)*8) 1525 IF c<=127 THEN LET add=64600+((c-32)*8) 1528 INK 6: PRINT AT 1,3;"CHR$ CODE #": PRINT AT 1,14;c: INK 6: PAPER 1: PRINT AT 1,18;CHR$ c: INK 3: PAPER 0 1530 FOR x=1 TO 8 1535 LET n(1,x)=add+x-1: LET n(2,x)=PEEK (add+x-1) 1540 PRINT AT 2+x,1;n(1,x);TAB 8;n(2,x); 1550 GO SUB 200 1555 PRINT TAB 15;N$(x) 1560 NEXT x 1570 GO SUB 3000 1605 CLS 1610 NEXT c 1620 GO TO 8100 3000 REM \c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c change CHR$ ? 3005 GO SUB 9100 3010 INK 5: PRINT AT 15,0;"Change character? (y/n or ENTER)": INK 3 3020 LET K$=INKEY$ 3030 IF K$="y" THEN GO TO 3200 3040 IF K$="n" THEN RETURN 3045 IF CODE K$=13 THEN RETURN 3050 GO TO 3020 3200 REM \c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c Redesign CHR$ 3205 INK 6: PRINT AT 15,0;" " 3210 PRINT AT 13,0;"Use STICK to";AT 14,0;"move cursor." 3211 PRINT AT 16,0;"Press FIRE";AT 17,0;"button to";AT 18,0;"flip bits." 3212 PRINT AT 20,0;"Press ENTER";AT 21,0;"to quit." 3215 INK 3: PRINT AT 13,15;"76543210": INK 5 3220 FOR x=1 TO 8 3230 INK 3: PRINT AT 13+x,14;x-1: INK 5: PRINT AT 13+x,15;N$(x) 3240 NEXT x 3250 LET x=14: LET y=15: FLASH 1: PRINT AT x,y;"\d": FLASH 0 3260 LET st= STICK (1,1) 3270 LET bt= STICK (2,1) 3280 IF CODE INKEY$=13 THEN GO TO 5000 3290 IF bt=1 AND N$(x-13,y-14)="\a" THEN GO TO 3500 3300 IF bt=1 AND N$(x-13,y-14)="\::" THEN GO TO 3400 3310 IF st=1 THEN GO TO 3600 3320 IF st=2 THEN GO TO 3700 3330 IF st=4 THEN GO TO 3800 3340 IF st=8 THEN GO TO 3900 3390 GO TO 3260 3400 LET N$(x-13,y-14)="\a" 3410 FLASH 1: PRINT AT x,y;"\d": FLASH 0 3420 IF STICK (2,1)>0 THEN GO TO 3420 3430 GO TO 3260 3500 LET N$(x-13,y-14)="\::" 3510 FLASH 1: PRINT AT x,y;"\d": FLASH 0 3520 IF STICK (2,1)>0 THEN GO TO 3520 3530 GO TO 3260 3600 PRINT AT x,y;N$(x-13,y-14) 3610 LET x=x-1 3620 IF x<14 THEN LET x=21 3640 FLASH 1: PRINT AT x,y;"\d": FLASH 0 3650 GO TO 3260 3700 PRINT AT x,y;N$(x-13,y-14) 3710 LET x=x+1 3720 IF x>21 THEN LET x=14 3740 FLASH 1: PRINT AT x,y;"\d": FLASH 0 3750 GO TO 3260 3800 PRINT AT x,y;N$(x-13,y-14) 3810 LET y=y-1 3820 IF y<15 THEN LET y=22 3840 FLASH 1: PRINT AT x,y;"\d": FLASH 0 3850 GO TO 3260 3900 PRINT AT x,y;N$(x-13,y-14) 3910 LET y=y+1 3920 IF y>22 THEN LET y=15 3940 FLASH 1: PRINT AT x,y;"\d": FLASH 0 3950 GO TO 3260 5002 FOR x=12 TO 21 5004 PRINT AT x,0;" " 5006 NEXT x 5008 GO SUB 100 5020 FOR x=1 TO 8 5030 PRINT AT 13+x,1;n(1,x);" ";n(2,x) 5040 NEXT x 5045 PAUSE 45 5050 RETURN 8000 REM \c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c initialize variables 8005 BORDER 0: PAPER 0: INK 5 8010 CLEAR 64599 8015 LOAD "cd"CODE 64600 8020 LET Z$="\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b" 8030 DIM N$(8,8) 8040 DIM n(2,8) 8050 POKE 23606,88: POKE 23607,251 8090 GO SUB 8500 8100 REM \c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c print menu 8105 CLS 8110 PRINT AT 4,0;"existo:ray software 1 9 8 5" 8115 INK 3: PRINT AT 3,0;Z$: INK 5 8117 PRINT AT 2,0;"ExFont 1.1 2 0 6 8" 8120 INK 3: PRINT AT 7,2;"1.": INK 5: PRINT AT 7,4;" Design Main Character Set CHR$ 32 thru 127" 8130 PRINT AT 9,5;"Or User Graphics CHR$ 144 thru 164" 8140 INK 3: PRINT AT 11,2;"2.": INK 5: PRINT AT 11,4;" Print Definable Characters" 8150 INK 3: PRINT AT 12,2;"3.": INK 5: PRINT AT 12,4;" SAVE Program and Code" 8160 INK 3: PRINT AT 13,2;"4.": INK 5: PRINT AT 13,4;" SAVE Code Only (64600,936)" 8170 INK 3: PRINT AT 14,2;"5.": INK 5: PRINT AT 14,4;" Toggle Character Sets" 8200 FLASH 1: INK 3: PRINT AT 17,2;"\g\h": FLASH 0: INK 5: PRINT AT 17,5;"Enter # 1 thru 5" 8210 GO SUB 9100 8215 IF CODE INKEY$>5 OR CODE INKEY$<1 THEN GO TO 8220 8220 IF INKEY$="1" THEN GO TO 1000 8230 IF INKEY$="2" THEN GO TO 9200 8240 IF INKEY$="3" THEN GO TO 8400 8250 IF INKEY$="4" THEN GO TO 8420 8260 IF INKEY$="5" THEN GO TO 9000 8300 GO TO 8220 8400 REM \c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c SAVE 8405 PRINT AT 17,0;" ": FLASH 1: PRINT AT 17,7;"SAVING": FLASH 0 8410 SAVE "ef" LINE 8000 8420 SAVE "cd"CODE 64600,936 8430 CLS : GO TO 8100 8500 REM \c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c NOTES 8505 CLS 8510 PRINT AT 4,0;"existo:ray software 1 9 8 5" 8520 PRINT AT 2,0;"ExFont 1.1 2 0 6 8" 8530 INK 3: PRINT AT 3,0;Z$ 8540 PRINT AT 0,0;" notes notes notes notes notes": INK 5 8550 PRINT AT 7,2;"Use to restart." 8560 PRINT AT 9,2;"To use a new character set in other programs: CLEAR 64599 LOAD \fcd\fCODE 64600 POKE 23606,88 POKE 23607,251" 8570 PRINT AT 16,2;"To return to TS characters: POKE 23606,0 POKE 23607,60" 8600 PRINT AT 20,0;" to copy any other key to continue" 8610 LET I$=INKEY$ 8620 IF I$="c" THEN COPY 8630 IF I$="" THEN GO TO 8610 8640 RETURN 9000 REM \c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c POKE CHARS 9010 LET p=PEEK 23606+256*PEEK 23607 9020 IF p=64344 THEN GO TO 9040 9030 IF p=15360 THEN GO TO 9060 9040 POKE 23606,0: POKE 23607,60 9050 CLS : GO TO 8100 9060 POKE 23606,88: POKE 23607,251 9070 CLS : GO TO 8100 9100 REM \c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c INPUT PROMPT BEEP 9105 BEEP .01,2 9110 BEEP .01,5 9115 BEEP .01,7 9120 BEEP .01,10 9125 BEEP .01,12 9130 BEEP .01,15 9135 BEEP .01,17 9140 BEEP .01,20 9150 RETURN 9200 REM \c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c\c PRINT character set 9201 CLS 9210 PRINT "MAIN CHARACTER SET": PRINT "\e\e\e\e\e\e\e\e\e\e\e\e\e\e\e\e\e\e" 9220 FOR x=32 TO 127 9222 IF x=124 THEN LET x=125 9223 IF x=126 THEN LET x=127 9225 POKE 23606,0: POKE 23607,60 9230 PRINT CHR$ x; 9240 POKE 23606,88: POKE 23607,251 9250 PRINT CHR$ x;" "; 9260 NEXT x 9265 PRINT : PRINT : PRINT "USER GRAPHICS": PRINT "\e\e\e\e\e\e\e\e\e\e\e\e\e" 9270 FOR x=144 TO 164 9272 POKE 23606,0: POKE 23607,60 9273 PRINT CHR$ (x-79); 9275 POKE 23606,88: POKE 23607,251 9280 PRINT CHR$ x;" "; 9290 NEXT x 9295 GO SUB 9100 9300 PRINT : PRINT : PRINT "to COPY any other key to ret" 9400 LET I$=INKEY$ 9410 IF I$="c" THEN COPY 9420 IF I$="" THEN GO TO 9400 9450 CLS : GO TO 8100 ```