This program installs a custom italic character set for use in BASIC programs by POKEing 42 bytes of Z80 machine code into address 30000, then executing it with RANDOMIZE USR. The machine code (lines 30–40) copies and transforms the ROM character set from address 15616 (0x3D00) to address 30208 (0x7600), applying two right-rotation operations (RLC→RRC, i.e. CB 63 twice) to each byte to produce an italic slant effect. The resulting 768-byte block (three pages covering 96 characters) is then saved to tape as a named CODE file. After saving, the user redirects the system font pointer via POKEs to addresses 23606 and 23607 (CHARS system variable) in their own program to activate the italic set at any load address.
Program Analysis
Program Structure
The program is a one-shot utility that builds, executes, and saves a machine-code font transformer. Its flow is strictly linear:
- Lines 1–5: REM documentation block explaining usage and the CHARS system variable POKEs.
- Line 20:
LIST— displays the program listing so the user can read the instructions before anything runs. - Line 30: Loads 42 bytes of Z80 machine code into RAM at address 30000.
- Line 40: The
DATAstatement containing those 42 bytes. - Line 50:
RANDOMIZE USR 30000— calls the machine code routine. - Lines 60–80: Asks for a filename, then saves the generated font block.
- Lines 90–100: Rewind prompt and
VERIFY. - Lines 110–120: Completion beep and
NEWto clean up.
Machine Code Routine (address 30000)
The 42 data bytes disassemble to a Z80 routine that copies the ROM character set and applies a bit-rotation to each byte to produce an italic effect:
| Offset | Bytes (hex) | Mnemonic | Notes |
|---|---|---|---|
| 0 | 21 00 3D | LD HL,3D00h | Source: ROM charset (char 32 = space) |
| 3 | 11 00 76 | LD DE,7600h | Destination: 30208 decimal |
| 6 | 01 00 03 | LD BC,0300h | 768 bytes = 96 characters × 8 rows |
| 9 | ED B0 | LDIR | Block copy ROM font to RAM |
| 11 | 21 00 76 | LD HL,7600h | Point to start of copied font |
| 14 | 0E 60 | LD C,60h | Outer loop: 96 characters |
| 16 | 06 02 | LD B,02h | Inner loop A: 2 rows per pass (top portion) |
| 18 | 7E | LD A,(HL) | Load byte |
| 19 | CB 3F | SRL A | Shift right logical (italic slant) |
| 21 | CB 3F | SRL A | Second right shift |
| 23 | 77 | LD (HL),A | Write back |
| 24 | 23 | INC HL | Next row |
| 25 | 10 F7 | DJNZ 18 | Loop B times |
| 27 | 06 04 | LD B,04h | Inner loop B: 4 rows (middle) |
| 29 | 7E | LD A,(HL) | Load byte |
| 30 | CB 3F | SRL A | Single right shift |
| 32 | 77 | LD (HL),A | Write back |
| 33 | 23 | INC HL | Next row |
| 34 | 10 F9 | DJNZ 29 | Loop B times |
| 36 | 23 | INC HL | Skip remaining 2 rows (bottom = unshifted) |
| 37 | 23 | INC HL | |
| 38 | 0D | DEC C | Decrement character counter |
| 39 | C8 | RET Z | Return when all 96 chars processed |
| 40 | 18 E6 | JR 14 | Loop back to outer character loop |
The italic effect is achieved by dividing each 8-row character bitmap into three zones: the top 2 rows are shifted right by 2 bits (SRL twice), the middle 4 rows by 1 bit, and the bottom 2 rows are left unchanged. This creates a stepped rightward lean that mimics italic slant when rendered on screen.
Font Relocation and the CHARS System Variable
The generated font is saved as a raw CODE block of 768 bytes starting at 30208 (0x7600). To use it, the host program must update the CHARS system variable at addresses 23606–23607, which normally points 256 bytes before the ROM font. The REM in line 5 gives the correct formula:
POKE 23606, addr - 256*INT(addr/256)— low byte of (addr − 256)POKE 23607, INT(addr/256)— high byte
Because CHARS is indexed from character 0 (not character 32), the system subtracts 256 bytes (32 chars × 8 rows) from the supplied address, so the caller must account for this offset.
Key BASIC Idioms and Notable Techniques
LINE N$in theINPUTstatement (line 60) accepts the full input as a string without string-quote processing, allowing filenames with spaces or special characters.RANDOMIZE USR 30000is the standard idiom for calling machine code; the return value from the routine (0 fromRET Z) is discarded harmlessly.NEWat line 120 clears both the BASIC program and variables after saving, leaving a clean slate — appropriate for a utility intended to run exactly once.- The
LISTat line 20 serves as an in-program documentation display, ensuring the user reads the REM instructions before any code executes.
Anomalies and Observations
- The outer loop counter in C is loaded as 0x60 (96), covering the printable ASCII range (characters 32–127), which matches the 768-byte save length exactly.
- The bottom 2 rows of each character are skipped entirely (two
INC HLinstructions), so descenders and baseline pixels are not shifted — this preserves legibility at the cost of a slight discontinuity at the character baseline. - The routine does not preserve registers, but since it is called via
USRand returns withRET Zafter the loop naturally exhausts, this is benign.
Content
Source Code
1 REM "CHAR SET2"
2 REM 'ITALICS'
3 REM By Ben Stagnell
4 REM ZX Computing Monthly
5 REM To use in any memory addr CLEAR addr-1, LOAD "code name" CODE addr, play tape then type or include as pgm lines POKE 23606,addr -256*INT (addr/256): POKE 23607,INT (addr/256) Where "addr" is any address you wish to put the code at.
20 LIST
30 FOR N=30000 TO 30041: READ A: POKE N,A: NEXT N
40 DATA 33,0,61,17,0,118,1,0,3,237,176,33,0,118,14,96,6,2,126,203,63,203,63,119,35,16,247,6,4,126,203,63,119,35,16,249,35,35,13,200,24,230
50 RANDOMIZE USR 30000
60 INPUT "NAME TO SAVE >) "; LINE N$
70 CLS : PRINT "SAVING :";N$
80 SAVE N$CODE 30208,768
90 PRINT ''"REWIND TAPE TO VERIFY"
100 VERIFY N$CODE
110 BEEP .1,10: PRINT "SAVE COMPLETE"
120 NEW
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.
