This program generates large-format banner text on a printer by reading character bitmaps directly from the ROM font table and scaling them up to one of three sizes: 8×8 (1×), 16×16 (2×), or 32×32 (4×). For each character in the user’s message, the program calculates the ROM address of its 8-byte glyph starting at address 15360 (with a correction for UDG-range codes above 143), extracts each bit via repeated integer division, and stores the result in a 2D string array B$(8,8). The bitmap is then rotated 90 degrees during output by iterating columns of the matrix in reverse row order, converting set bits to printer block characters and clear bits to spaces. Output scaling is achieved by repeating the block character string and the LPRINT line itself the appropriate number of times per row.
Program Analysis
Program Structure
The program is organized into a clear pipeline: setup and user input (lines 1–11), character processing loop (lines 13–510), and repeat/exit logic (lines 530–550). A subroutine-like block at lines 200–400 handles bit extraction, lines 451–510 handle rotation and printing, and lines 1000–1020 are documentary REMs noting memory usage.
- Lines 1–9: Screen setup, instructions, and size selection (
S= 1, 2, or 3). - Lines 10–11: Message input, truncated to 32 characters.
- Line 13: Outer loop over each character; ROM address calculation.
- Lines 200–400: Inner loops extract 8 scan lines × 8 bits into
B$(8,8). - Lines 451–510: Rotation and scaled LPRINT output.
- Lines 530–550: Prompt to repeat with same or different size.
ROM Font Address Calculation
Line 13 computes the base address of a character’s glyph with:
LET L=15359+(CODE C$(Z)*8)
The ZX Spectrum ROM character set begins at address 15360, with each character occupying 8 bytes. Multiplying the character code by 8 and adding 15359 gives the byte just before the first scan line, since the inner loop immediately increments L before PEEKing. A correction handles UDG characters (codes 144–164):
IF L>16383 THEN LET L=65367+8*(CODE C$(Z)-144)
This redirects to the UDG area in RAM (normally at 65368 for the Spectrum), though the program’s own instructions warn users not to use UDGs or graphics characters for banners.
Bit Extraction Technique
Lines 360–390 use the classic repeated-halving idiom to unpack each byte into its 8 constituent bits, storing "0" or "1" into B$(X, 9-A). Storing to 9-A reverses the bit order so that bit 7 (MSB) ends up at column 8 and bit 0 (LSB) at column 1, correctly mapping to left-to-right pixel order.
LET B$(X,9-A)=CHR$ (CODE "0"+D-2*INT (D/2)) LET D=INT (D/2)
D-2*INT(D/2) is equivalent to D MOD 2, which BASIC on this platform does not provide as a direct operator.
90-Degree Rotation
Lines 460–510 implement a 90-degree clockwise rotation by iterating X (column index) from 1 to 8 in the outer loop and Y (row index) from 8 down to 1 in the inner loop. Each column of the original bitmap becomes a printed row. This is necessary because the ROM stores horizontal scan lines, but the banner requires characters printed sideways — each LPRINT call outputs one rotated “column slice” of the character.
Scaling Method
Scaling is handled at two levels: horizontal expansion (within a row) and vertical expansion (repeating rows). Horizontal expansion is done by padding the block character with spaces:
| Size (S) | Set pixel string | Clear pixel string | LPRINT repeats |
|---|---|---|---|
| 1 (8×8) | "\ " (1 block) | " " (1 space) | 1× |
| 2 (16×16) | "\ \ " (2 blocks) | " " (2 spaces) | 2× |
| 3 (32×32) | "\ \ \ \ " (4 blocks) | " " (4 spaces) | 4× |
Vertical repetition is achieved by repeating the LPRINT A$ statement using the ' (newline-then-print) shorthand. For size 3, line 500 uses LPRINT A$'A$'A$'A$ to print the same row four times.
Note that at size 2 each character is 16 columns wide and 16 rows tall, and at size 3, 32×32 — but the total banner width for a 32-character message at size 1 would be 256 printer columns, which may exceed the printer’s line width.
User Interface Details
The program uses color cycling (PAPER changes between 0, 1, 2, 3, and 7) for visual feedback. A FLASH 1 message at line 491 indicates printer activity for each column slice output. The PAPER 2 stripe at line 8 creates a color divider between instruction text and the menu. Screen coordinates are managed with PRINT AT rather than CLS for status messages, allowing partial screen updates.
Bugs and Anomalies
- Line 13 prints the characters of the message to the screen with
PRINT AT 10,Z-1;C$(Z), but this only works correctly for messages up to 32 characters before wrapping; the column indexZ-1goes from 0 to 31, which is fine within the 32-column display. - The UDG address correction on line 13 targets address 65368 (for code 144), consistent with the Spectrum’s default UDG base, but the program explicitly tells users not to use UDGs — so this branch is a safety measure that may not function correctly if the UDG area has been moved.
- The
B$array is dimensioned once at line 1 and reused for every character without re-dimensioning, which is correct since it is always fully overwritten in the extraction loop. - Line 530 tests
A$(1), which will cause an error if the user presses Enter without typing anything (empty string). This is a common INPUT validation omission. - At size 1, the block character used for set pixels (
"\ "— a block graphic followed by a space) actually produces a half-width block plus a space, so each pixel is rendered as two printer columns regardless of size, making “size 1” equivalent to 16 columns wide per character rather than 8.
Content
Image Gallery
Source Code
1 DIM B$(8,8)
2 PAPER 0: BORDER 0: INK 7: CLS
3 PRINT AT 10,4;"BANNER BY CHRIS RAYNAK"
4 PAUSE 250
5 PAPER 1: BORDER 1: CLS
6 PRINT "WHEN ENTERING YOUR MESSAGES YOU MUST REMEMBER THAT YOU ARE LIMITED TO 32 CHARACTERS."
7 PRINT "ALSO, YOU CANNOT USE USER DEFINED GRAPHICS OR REGULAR GRAPHICS FOR BANNERS. PLEASE SELECT ONE OF THE LETTER SIZES FROM BELOW."
8 PAPER 2: PRINT " ": PAPER 1
9 PRINT "1) 8X8"'"2) 16X16"'"3) 32X32": INPUT "YOUR SELECTION?";S: CLS
10 PRINT "PLEASE ENTER YOUR MESSAGE."
11 INPUT C$: IF LEN C$>32 THEN LET C$=C$(1 TO 32)
12 PAPER 3: PRINT AT 8,0;"YOUR MESSAGE IS BEING PROCESSED": PAPER 1
13 FOR Z=1 TO LEN C$: PRINT AT 10,Z-1;C$(Z): LET L=15359+(CODE C$(Z)*8): IF L>16383 THEN LET L=65367+8*(CODE C$(Z)-144)
14 REM ^FINDS ADDRESS OF FIRST SCAN LINE.
15 REM *************************
16 REM CONVERTS DECIMAL #
17 REM OF THE SCAN LINE OF
18 REM CORRESPONDING CHARACTER
19 REM IN THE ROM TO BINARY.
20 REM *************************
200 FOR X=1 TO 8
210 LET L=L+1: LET D=PEEK L
360 FOR A=1 TO 8
370 LET B$(X,9-A)=CHR$ (CODE "0"+D-2*INT (D/2))
380 LET D=INT (D/2)
390 NEXT A
400 NEXT X
451 LET A$=""
452 REM ************************ ROTATES LETTER 90 DEGREES TO THE RIGHT. ************************
460 FOR X=1 TO 8
470 FOR Y=8 TO 1 STEP -1
480 IF B$(Y,X)="1" THEN GO TO 486
481 IF S=1 THEN LET A$=A$+" "
482 IF S=2 THEN LET A$=A$+" "
483 IF S=3 THEN LET A$=A$+" "
484 GO TO 490
486 IF S=1 THEN LET A$=A$+"\ "
487 IF S=2 THEN LET A$=A$+"\ \ "
488 IF S=3 THEN LET A$=A$+"\ \ \ \ "
490 NEXT Y
491 FLASH 1: INK 0: PAPER 7: PRINT AT 12,3;"**PRINTER IN OPERATION**": FLASH 0
492 IF S=1 THEN LPRINT A$
493 IF S=2 THEN LPRINT A$'A$
500 IF S=3 THEN LPRINT A$'A$'A$'A$
501 INK 7: PAPER 1: PRINT AT 12,3;" "
502 LET A$=""
510 NEXT X
520 NEXT Z
530 PRINT AT 13,0;"WOULD YOU LIKE TO PRINT ANOTHER MESSAGE?": INPUT A$: IF A$(1)="N" OR A$(1)="n" THEN GO TO 1000
540 INPUT "SAME LETTER SIZE?";A$: IF A$(1)="N" OR A$(1)="n" THEN CLS : GO TO 8
550 CLS : GO TO 10
1000 REM ***********************
1010 REM MEMORY USED=2040 BYTES*
1020 REM ***********************
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.