Skip to content

Draw Text Beyond the Character Grid

Reusable 8×8 bitmap characters and counted strings on Spectrum, BBC Micro, Dragon, CPC and Amiga, with clipping and honest storage/work trade-offs.

A score need not live on the character grid. Put a number above an enemy, centre a room name or slide a message into view. These entries give you the code to draw a fixed-width glyph or counted string into your bitmap, using a font and memory you own.

Choose the actual display

Machine Maintained text component Existing drawing backend
Spectrum 48K Z80, 256×192 Mono spans and the Spectrum pixel-address helper
BBC Micro MODE 4 6502, 320×256 Interleaved bitmap and 512-byte row table
Dragon 32 PMODE 4 6809, 256×192 Linear scanlines, native big-endian words
CPC 464 MODE 1 Z80, 320×200 Four pixels per byte and four palette pens
Amiga A500 single plane 68000, 320×256 CPU Chip RAM spans; distinct 32-bit-pointer context

Each entry begins with a real direct call, shows the reusable include, and explains its inputs, outputs, clipping and ownership. All five ran separately in native emulators. Sharing a CPU does not qualify every display or machine for the same code.

A cell, its ink and its advance

An eight-by-eight glyph cell at origin (21,37), with local dot (3,5) translated to screen (24,42), and the next cell starting at X29
Moving the origin moves every dot by the same amount. Blank columns still occupy part of the eight-pixel advance.

A glyph is eight row bytes. Bit 7 is leftmost. Translate local pixels by the cell origin, clip against an exclusive scissor, then draw foreground runs with the existing span routine. Transparent mode preserves zero bits; opaque paints each clipped cell background before its foreground.

The API accepts signed origins −512..511 and counted strings up to 255 codes. No terminator, newline handling or wrapping is hidden inside it. Unsupported codes use an explicit fallback. Blank or clipped glyphs still advance by eight. Move a glyph, keep its shape works through the maths with actual screen positions.

Choose how much work to buy

The original optional 64-glyph font costs 512 bytes. Ten digits need only 80. A full 256-glyph contiguous font needs 2,048. These are font-data choices; the sample design is original MIT-licensed Code198x work, not a ROM extraction.

Ten-code PIXEL TEXT Transparent X24 / X31 Opaque X24 / X31
Spectrum, BBC, Dragon 77 / 77 byte writes 157 / 237
CPC MODE 1 90 / 84 250 / 324

These are observed destination stores, not cycles or a cross-machine speed ranking. Glyph gaps matter as well as cell alignment. Opaque drawing revisits foreground bytes after its background spans. This simple composition reuses a checked renderer; a dedicated masked glyph blitter could do less work.

For a character-grid display, Spectrum character printing or writable characters may be the right tool. For bitmap text, masked sprites show KEEP/image composition, while pre-shifted phases trade asset space for draw-time work. Amiga’s existing word-aligned sprite paths offer another comparison; the Amiga text component now supplies an independently checked CPU path with a distinct 32-bit-pointer context.

Text layout builds on this contract with measurement, proportional widths and wrapping. The selectable enlarged renderer below changes the glyph dimensions and needs layout that agrees with them. The current component reserves 96 text bytes plus the backend’s 48, owns no game loop and changes no interrupt policy. The machine pages spell out the display and stack obligations.

Across the five fixtures, 397 calls completed, with full bitmap/source/guard comparisons and selected native-image comparisons. Ten completed faulty builds were detected. Read each entry’s evidence and limits before changing its display allocation or OS integration.

Make the letters bigger

The four span-backed implementations above now include a selectable integer-enlarged renderer. Each original font pixel becomes a rectangle: factors 2×2 turn an 8×8 cell into 16×16; 3×2 makes it 24×16. X and Y factors are independently 1..4. The font still stores eight bytes per glyph.

Keep the existing machine backend, original font and 96-byte T/48-byte BP allocations. Include either bitmap-text or scaled-text, set both additional scale inputs at T+68/+69, then call text_char or text_string. Counted source, fallback, pens and physical scissor stay explicit. The advance is now 8×scaleX; glyph height is 8×scaleY. These direct calls do not make the existing eight-pixel layout wrappers scale-aware.

Each entry includes the original and changed-scale callers, a native wrong/fixed advance example, exact code/work costs and captures. A large bitmap font is not a new character mode: BBC retains zero-page BP and scanline tables, Spectrum attributes remain separately owned, Dragon fields are big-endian, and CPC retains packed four-pen pixels. Equal enlargement factors preserve native pixel proportions rather than correcting non-square physical pixels.

The independent oracle samples source coordinates with integer division, while the native routines scale foreground runs and repeat rows. Division and remainders explain sampling; local-to-screen coordinates explain placement before clipping. A partially clipped glyph keeps its sampling phase.

Larger cells paint more pixels. Compare the same W! workload across scales in each entry; byte crossings and repeated row work differ by backend. Span/write counts are not cycle timings. Pre-rendered sizes, pre-shifted/masked glyphs and precomposed character cells offer other CPU/asset trade-offs. Scale-aware layout remains a further variant; the bounded shrinking component below changes ink dimensions within an eight-byte cell. The separate glyph transform below supplies flips and quarter-turn rotation for these four backends.

Turn or reflect a glyph

The four span-backed entries now supply a separate reusable transform for 6502, Z80 and 6809. Give it eight row bytes, an eight-byte destination and flags for a clockwise quarter-turn plus optional horizontal reflection. It snapshots the input, so partial and exact overlap are supported within a single call. It draws nothing: the existing bitmap renderer consumes the result as a one-glyph font.

A direction arrow, small icon or sideways letter is a useful starting point. Turning a glyph differs from rotating a whole label: character order, advance and proportional metrics remain the caller’s job. Four turns return every bit to its original position. The mapping (x,y) → (7−y,x) follows from coordinates 0..7 with Y down. Swapping X/Y alone produces a diagonal reflection, shown as a completed wrong/fixed native example in each entry.

The shared component uses 32 scratch bytes, plus a separate two-byte zero-page pointer on 6502. Each cached orientation needs eight bytes. Storing all eight takes 64 bytes per glyph rather than eight; transforming instead buys back asset space at the cost of runtime work. Each call snapshots eight bytes, clears eight result bytes and scans 64 source pixels. Destination read/modify/write work depends on the number of set bits; these are not complete instruction or cycle measurements.

All four machines ran independently: 2,384 native observations, including every source pixel in every orientation, overlap, rejection, normal/customised callers and deliberately wrong/repaired mappings. The renderer and display differences in the table above remain visible. Amiga’s existing bitmap text entry is still a separate implementation; this batch does not claim a 68000 transform port.

Local-to-screen coordinates place the transformed result; the Maths overview keeps planned transform coverage visible. Larger images, arbitrary angles and scale-aware layout remain separately scoped work; bounded glyph shrinking is supplied below.

Shrink the ink, keep the cell

The four span-backed entries supply a separate glyph_resize component for 6502, Z80 and 6809. Choose useful width and height from 1..8. It samples an 8×8 source using (x*8 div width, y*8 div height), then zero-pads the result to the same eight-byte format. The existing renderer draws it; text advance and layout remain separate responsibilities. Transparent drawing leaves zero padding alone; opaque drawing still paints the whole padded cell background. Sampling includes the source cell’s blank margins, so the requested rectangle is not a measured tight ink box.

At width four, columns 0,2,4,6 survive and a one-pixel stroke in column one disappears. That loss is part of the chosen sampling policy. A hand-designed small glyph may be clearer; averaging or pixel-centre sampling would be different implementations. The native code uses error accumulators while the independent oracle uses division.

Each entry has include/init/call, a 5×7 to 8×4 customisation, native wrong-crop/fixed-resampling captures and explicit ownership, overlap, bounds and work costs. 2,172 native observations cover the four machines separately. Source snapshots and complete memory/guard/frame checks keep correct return status distinct from the intended picture. Amiga’s separate 68000 renderer has not gained this resize port.

Runtime shrinking keeps one source and eight bytes per cached result. All 64 dimension pairs, including identity, take 512 bytes per glyph if stored. Those asset costs differ from runtime work: eight source reads, eight clears and width×height samples per call, with extra writes for set pixels. They are not complete CPU or cycle counts. The component preserves an 8×8 cell; it does not pack variable-size assets, update proportional metrics or guarantee a frame budget.

Division and remainders explain sampling, while local-to-screen coordinates place the result. Larger images, targets above eight, arbitrary angles and scale-aware layout remain separately scoped work.