--- title: "Calligraphy Font" id: 60848 type: "computer_media" slug: "calligraphy-font" url: "http://localhost/computer_media/calligraphy-font/" markdown_url: "http://localhost/computer_media/calligraphy-font.md" published_at: "2025-10-06T12:11:26+00:00" modified_at: "2026-03-30T21:29:22+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2025/10/calig.png" excerpt: "A complete calligraphic bitmap font renderer that places custom 24×24 and 24×30 pixel letters anywhere on screen using pixel-precise coordinates and machine code." 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: "Ben H. Jackson" slug: "ben-h-jackson" taxonomy: "indiv" url: "http://localhost/indiv/ben-h-jackson/" genre: - name: "Font" slug: "font" taxonomy: "genre" url: "http://localhost/type/font/" - name: "Graphics" slug: "graphics" taxonomy: "genre" url: "http://localhost/type/graphics/" media_contents: - id: 60550 title: "Fort Worth TS2068 Club Library Tape" type: "computer_media" url: "http://localhost/computer_media/fort-worth-ts2068-club-library-tape/" media_type: "Program" programmers: - name: "Ben H. Jackson" slug: "ben-h-jackson" taxonomy: "indiv" url: "http://localhost/indiv/ben-h-jackson/" download_url: "https://archive.org/download/timex-sinclair-software-archive/Calig%20%281985%29%28Jackson%2C%20Ben%29%28TS2068%29%28US%29%28Program%29.zip" mediadate: "1985" images: - url: "http://localhost/wp-content/uploads/2025/10/calig.png" media_type_tags: "Font, Graphics" --- Calligraphy font display program that renders a complete custom alphabet in a large bitmap format across the screen. It loads a machine code routine from tape (“olde” CODE at address 54000) that handles the actual pixel-drawing via USR calls, and stores two separate character sets: uppercase letters (72 bytes/character, 24×24 pixels) in the “UPPER DATA” region at 54700–56592, and lowercase letters (90 bytes/character, 24×30 pixels) in “LOWER DATA” at 58000–60360. The program POKEs parameters such as frame reference, bits-per-row, row count, and bytes-per-character directly into the machine code parameter block before each USR 54403 call, and separately configures the display address via POKEs to system variables at 23675–23676. Letters are addressed by index (L=1 to 26, corresponding to a–z) and positioned by pixel coordinates (X, Y) that define the upper-left corner of each character block. *** ## Program Structure The program is divided into clearly separated functional regions: 1. **Initialisation (lines 1–14):** Clears memory with `CLEAR 54000`, loads the machine code block “olde” from tape, prompts the user, then jumps to line 400. 2. **Machine code loader subroutine (lines 100–107):** A generic READ/POKE loop that deposits DATA bytes starting at address `MCST`, terminating when it POKEs a `201` (Z80 RET opcode). 3. **Upper-case draw subroutine (lines 150–154):** POKEs X/Y/L into the parameter block starting at 54700, then calls `USR 54403`. 4. **Lower-case draw subroutine (lines 160–164):** Same pattern but POKEs into the lower-data block starting at 58000. 5. **Upper-case rendering sequence (lines 216–255):** Configures the MC parameter block for 24×24-pixel characters (72 bytes each) and iterates through all 26 uppercase letters. 6. **Lower-case rendering sequence (lines 316–385):** Reconfigures for 24×30-pixel characters (90 bytes each) and renders lowercase a–z. 7. **Help text (line 400):** Explains the coordinate and letter-index system to the user. ## Memory Map | Address range | Contents | | --- | --- | | 54000–54053 | “plot” machine code routine | | 54368–54399 | “GCELL” registers / parameter block | | 54400–54562 | “GCELL” machine code | | 54567–54569 | “SEBIT” registers | | 54570–54697 | “SEBIT” machine code | | 54700–56592 | Upper-case bitmap data (26 × 72 bytes) | | 58000–60360 | Lower-case bitmap data (26 × 90 bytes) | ## Machine Code Interface The single entry point `USR 54403` is used for both upper and lower-case rendering. The machine code distinguishes the two character sets by where the base address and parameters are POKEd. For uppercase, the parameter block begins at address 54700; for lowercase it begins at 58000. Each block carries the same layout: - Offset 0: X pixel coordinate - Offset 1: Y pixel coordinate - Offset 2: frame/bank reference (always 1) - Offset 3: bits per row (24) - Offset 4: rows per character (24 upper / 30 lower) - Offset 5: bytes per character (72 upper / 90 lower) - Offset 6: letter index L (1–26) - Offsets 7–19: zeroed-out working registers The display base address is separately configured via POKEs to system variables `23675`/`23676`: `172,213` for upper (pointing into the upper display area) and `144,226` for lower. ## Notable Techniques The **generic MC loader** at lines 100–107 uses Z80 opcode `201` (RET) as a natural sentinel: the loop terminates as soon as that byte is POKEd, avoiding any need to know the exact code length in advance. This is a clean self-documenting termination strategy. The **FOR/NEXT zero-fill loops** at lines 223–225 and 323–325 clear 14 bytes of working registers in the parameter block before each rendering pass, ensuring no stale values remain from a previous call. The **pixel-coordinate positioning model** is noteworthy: rather than using character-cell addressing, X and Y are raw pixel offsets. The upper-case characters are spaced 30 pixels apart horizontally and arranged in three rows (Y=1, 28, 53, 81), while the lower-case characters use tighter 15-pixel spacing (Y≈81–108), reflecting their proportionally smaller design despite having more rows. Line 143 (`LET X=150: LET L=15: GO TO 150`) appears to be a convenience entry point that pre-loads defaults for the draw subroutine — perhaps a debugging shortcut left in the listing. ## Source Code ``` 1 REM "calig" © by Ben H. Jackson, 1/31/85; ALL RIGHTS RESERVED 5 CLEAR 54000 6 LOAD "olde"CODE 54000,6361 7 GO TO 400 12 REM --MEMORY ALLOCATION 54000--54053 "plot" 54368--54399 "GCELL" reg 54400--54562 "GCELL" 54567--54569 "SEBIT" regs 54570--54697 "SEBIT" 54700--56592 "UPPER DATA" 58000--60360 "LOWER DATA" 13 CLS : PRINT "1. Turn off the tape recorder."'"2. Press any key and ENTER." 14 INPUT z$: CLS 15 REM --VCRIABLES BYTE--MEMORY CELL DATA MCST--MC START ADDR RS--RESTORE STMT NO 99 GO TO 200 100 REM --LOAD MC SUBROUTINE--- 101 RESTORE RS 102 LET I=MCST 103 READ BYTE: POKE I,BYTE 104 IF BYTE=201 THEN GO TO 106 105 LET I=I+1: GO TO 103 106 REM PRINT "LAST CELL USED=";I 107 RETURN 143 LET X=150: LET L=15: GO TO 150 150 REM --DRAW A LETTER SBR---- 151 POKE 54700,X: POKE 54701,Y 152 POKE 54706,L 153 LET K=USR 54403 154 RETURN 160 REM --DRAW A LOWER CASE LETTER" 161 POKE 58000,X: POKE 58001,Y 162 POKE 58006,L 163 LET K=USR 54403 164 RETURN 216 POKE 23675,172 217 POKE 23676,213 219 POKE 54702,001: REM ;set ref to frame 1 220 POKE 54703,024: REM ;24 bits/row 221 POKE 54704,024: REM ;24 rows 222 POKE 54705,072: REM ;72 bytes/character 223 FOR i=1 TO 14 224 POKE 54706+i,0 225 NEXT i 230 LET X=1: LET Y=1: LET L=1: GO SUB 150 231 LET X=30: LET Y=1: LET L=2: GO SUB 150 232 LET X=60: LET Y=1: LET L=3: GO SUB 150 233 LET X=90: LET Y=1: LET L=4: GO SUB 150 234 LET X=120: LET Y=1: LET L=5: GO SUB 150 235 LET X=150: LET Y=1: LET L=6: GO SUB 150 236 LET X=180: LET Y=1: LET L=7: GO SUB 150 237 LET X=210: LET Y=1: LET L=8: GO SUB 150 238 LET X=1: LET Y=28: LET L=9: GO SUB 150 239 LET X=30: LET L=10: GO SUB 150 240 LET X=60: LET L=11: GO SUB 150 241 LET X=90: LET L=12: GO SUB 150 242 LET X=120: LET L=13: GO SUB 150 243 LET X=150: LET L=14: GO SUB 150 244 LET X=180: LET L=15: GO SUB 150 245 LET X=210: LET L=16: GO SUB 150 246 LET X=1: LET Y=53: LET L=17: GO SUB 150 247 LET X=30: LET L=18: GO SUB 150 248 LET X=60: LET L=19: GO SUB 150 249 LET X=90: LET L=20: GO SUB 150 250 LET X=120: LET L=21: GO SUB 150 251 LET X=150: LET L=22: GO SUB 150 252 LET X=180: LET L=23: GO SUB 150 253 LET X=210: LET L=24: GO SUB 150 254 LET X=1: LET Y=81: LET L=25: GO SUB 150 255 LET X=30: LET Y=78: LET L=26: GO SUB 150 316 POKE 23675,144 317 POKE 23676,226 319 POKE 58002,001: REM ;set ref to frame 1 320 POKE 58003,024: REM ;24 bits/row 321 POKE 58004,030: REM ;30 rows 322 POKE 58005,090: REM ;90 bytes/character 323 FOR i=1 TO 14 324 POKE 58006+i,0 325 NEXT i 360 LET X=60: LET L=1: GO SUB 160 361 LET X=75: LET L=2: GO SUB 160 362 LET X=90: LET L=3: GO SUB 160 363 LET X=105: LET L=4: GO SUB 160 364 LET X=120: LET L=5: GO SUB 160 365 LET X=135: LET L=6: GO SUB 160 366 LET X=150: LET L=7: GO SUB 160 367 LET X=165: LET L=8: GO SUB 160 368 LET x=180: LET L=9: GO SUB 160 369 LET X=190: LET L=10: GO SUB 160 370 LET X=205: LET L=11: GO SUB 160 371 LET X=220: LET L=12: GO SUB 160 372 LET X=1: LET Y=106: LET L=13: GO SUB 160 373 LET X=24: LET Y=108: LET L=14: GO SUB 160 374 LET X=40: LET L=15: GO SUB 160 375 LET X=55: LET L=16: GO SUB 160 376 LET X=70: LET L=17: GO SUB 160 377 LET X=85: LET L=18: GO SUB 160 378 LET X=100: LET L=19: GO SUB 160 379 LET X=115: LET L=20: GO SUB 160 380 LET X=130: LET L=21: GO SUB 160 381 LET X=145: LET L=22: GO SUB 160 382 LET X=160: LET L=23: GO SUB 160 383 LET X=175: LET L=24: GO SUB 160 384 LET X=190: LET L=25: GO SUB 160 385 LET X=205: LET L=26: GO SUB 160 386 STOP 400 CLS : PRINT "LIST 230 or 360 to see how LET x = a number and LET y = a number defines the PIXELS of the upper left hand corner of the block where the letter will appear. LET l = a number ( 1 to 26 eg. 1 = a, 2 = b) selects the letter for that block"',,,,"To RUN or re-start GOTO 12" ```