Change a prompt and let the code find its position. A short label can sit in the middle of a panel; a longer message can continue on the next line. This component adds those calculations to the existing bitmap-text renderer.

The capture shows the emulator’s native raster, including its border and horizontal sampling. It does not establish a physical television aspect ratio. Layout uses logical bitmap pixels.
Include it, initialise it, call it
MODE 1 gives 320×200 logical pixels and four pen indices. Each byte contains four pixels in the CPC packing order; its rows are interleaved too. The existing backend owns that work and preserves the unused bytes in the screen allocation.
Reserve 64 bytes at TL=$4480, 96 at T=$4400 and 48 at BP=$4300 for this caller. Keep them separate from source, font, bitmap, code and stack. Include the unchanged bitmap-text component before text-layout/z80.inc; the layout uses its aliases and renderer. Keep the existing backend and its helper/table. The complete source shows every include and allocation.
T supplies the font pointer, first code/count, fallback, transparent/opaque mode and foreground/background pens. Layout takes ownership of T and BP while running, including the scissor. Initialise those font/pen choices as in caller.inc, then supply TL:
| Offset | Input |
|---|---|
| 0, 2 | Words: panel left, top |
| 4, 6 | Words: panel width, height |
| 8 | Byte: alignment, 0 left / 1 centre / 2 right |
| 9 | Byte: line pitch, 8..255 scanlines |
| 10 | Word: source address |
| 12 | Byte: source length, 0..255 |
Words are little-endian. Scratch needs no initialisation; inputs do. These are the maintained setup calls used by the working example:
Set the panel and call layout_block
; BEGIN LAYOUT INPUTS
ld hl,PANEL_LEFT
ld (TL+0),hl
ld hl,PANEL_TOP
ld (TL+2),hl
ld hl,PANEL_WIDTH
ld (TL+4),hl
ld hl,PANEL_HEIGHT
ld (TL+6),hl
ld hl,label_data
ld (TL+10),hl
ld a,PANEL_ALIGN
ld (TL+8),a
ld a,LINE_PITCH
ld (TL+9),a
ld a,MESSAGE_LENGTH
ld (TL+12),a
call layout_block
; END LAYOUT INPUTSUse layout_line for one counted label or layout_block for LF-separated, character-wrapped text. TL+13 returns 1 when accepted and 0 when rejected. Inputs TL+0..12 survive; T/BP do not.
Measure before subtracting
layout_measure needs only TL+12. It returns 8*length as a word at TL+18, accepted=1, and zeroes the other documented outputs. It reads no string or font. A 255-code line therefore measures 2,040 pixels rather than wrapping into a byte.
Counted-line measurement without a string scan
layout_measure:
call layout_reset
ld hl,$0000
ld (l_measured),hl
ld a,(l_length)
ld (l_measured),a
ld hl,l_measured
sla (hl)
inc hl
rl (hl)
ld hl,l_measured
sla (hl)
inc hl
rl (hl)
ld hl,l_measured
sla (hl)
inc hl
rl (hl)
ld a,$01
ld (l_status),a
retFive glyph cells need 40 pixels. A 177-pixel panel has 137 spare pixels. Centre alignment uses left + floor(137/2): at left 21, the first cell begins at 89, leaving the odd spare pixel on the right. Right alignment starts at 158. These are cell bounds; the visible ink can have blank columns inside them. Coordinates and remainders and glyph-local positions explain the calculations.
A line wider than its panel rejects before drawing. Do not subtract first and hope an unsigned result stays sensible. A block also plans every row before rendering: a message too tall for its panel leaves the previous bitmap intact. Supply readable, stable, non-overlapping source/font allocations and keep mappings unchanged throughout both passes.
Decide what a line break means
Block mode uses width div 8 cells per line. It keeps spaces and can split a word. Byte 10 is LF; byte zero remains a character code and uses the configured fallback if unsupported. Line mode instead treats every byte, including 10, as a glyph slot. This counted-line rule is also what layout_measure measures.
A full chunk consumes an immediately following LF as its own break. Consecutive LFs retain interior empty lines. A final LF ends its line without creating another trailing row. Empty input has zero lines and no drawing. The whole rectangle must fit the physical bitmap and be at least 8×8; an empty request still validates its declaration.
Pitch is the distance between row origins. Five rows at pitch 16 need (5-1)*16+8=72 scanlines. Opaque mode paints actual glyph cells, not the unused width or an empty line. Clear the panel you own before replacing a longer message, or old letters may remain. Use the word-aware variant below when words should stay together. Incremental reveal is a separate technique.
Make it your own
Build customise.asm with the same includes. It narrows the panel from 176 to 104 pixels, selects right alignment, changes pitch from 12 to 16 and supplies another original counted message. The result has five lines inside the existing 80-scanline height. PANEL splits at the 13-column boundary; that is the chosen character-wrap policy in action.
Change the panel, alignment, pitch and counted message
CUSTOM_LAYOUT equ 1
; SPDX-License-Identifier: MIT
; Complete caller: change layout inputs without changing the reusable renderer.
PANEL_LEFT equ 21
PANEL_TOP equ 37
PANEL_WIDTH equ 104
PANEL_HEIGHT equ 80
PANEL_ALIGN equ 2
LINE_PITCH equ 16
MESSAGE_LENGTH equ 44
include "caller.inc"
Edit customise-message.inc and update MESSAGE_LENGTH together. The routine has no terminator scan to rescue an incorrect count. More rows or wider text can increase drawing work, even though code and context allocations stay the same. The proportional option below changes glyph advances. Another glyph size or video mode still needs its own implementation and verification.
For these exact original messages, native counters report:
| Work | Two-line demo (33 source bytes) | Narrow custom panel (44 source bytes) |
|---|---|---|
| Logical lines | 2 | 5 |
| Backend spans | 511 | 709 |
| Destination-byte writes | 1075 | 1453 |
| Added layout RAM | 64 bytes | 64 bytes |
Both the message and layout changed. This compares those two workloads, not the isolated cost of right alignment or narrowing a panel. The code and font remain the same.
Common mistakes and their symptoms
The following are deliberately broken diagnostic fragments, kept outside the reusable implementation. Both complete bounded native calls. Their repairs also execute and assemble to the original working cartridge bytes.
The label moves right
A missing shift measures four pixels per code while the renderer still advances eight. The calculation leaves too much supposedly unused space on the left. This is a disagreement between measurement and rendering, rather than a font or video-address problem.
Deliberately wrong: stop multiplying at four
; BEGIN WRONG
ld hl,l_line_width
sla (hl)
inc hl
rl (hl)
ld hl,l_line_width
sla (hl)
inc hl
rl (hl)
Restore the third shift and check that five codes measure 40 pixels:
Correct: match the renderer's eight-pixel advance
; BEGIN FIX
ld hl,l_line_width
sla (hl)
inc hl
rl (hl)
ld hl,l_line_width
sla (hl)
inc hl
rl (hl)
ld hl,l_line_width
sla (hl)
inc hl
rl (hl)The panel offset disappears
The alignment calculation returns an offset within the panel. Writing that offset as a screen coordinate forgets the panel’s left edge. Here the label moves 21 pixels left; other inputs can also make the scissor cut letters off.
Deliberately wrong: use a local offset as a screen coordinate
; BEGIN WRONG
ld hl,(l_temp)
ld (l_x),hl
Add the panel origin to the alignment offset:
Correct: translate from panel to screen coordinates
; BEGIN FIX
ld hl,(l_left)
ld (l_x),hl
ld hl,(l_x)
ld de,(l_temp)
add hl,de
ld (l_x),hl
Costs, ownership and results
The layout adds 1,628 code bytes and 64 context bytes. The reused text renderer is 2,268 bytes and its backend 578; their 96+48 context bytes, the 512-byte sample font, messages, video allocation and stack are separate. Reserve a conservative 80-byte stack budget plus caller/interrupt depth. A different ink value changes a palette choice, not the layout arithmetic. The caller owns Gate Array, CRTC and palette setup.
Block mode makes two planning passes, then the renderer adds source/font reads and glyph/span work. The second pass interleaves planning and rendering after the complete first pass has qualified the request. TL+26 reports total span calls; TL+28 reports a 32-bit destination-byte-write count. These are work counts, not cycle measurements. Byte alignment, pen packing, glyph shapes and opacity affect them. Static labels can be cheaper to position or render once.
Accepted calls return consumed bytes at TL+14, logical lines at +15, last-line glyph count at +16, maximum logical width at +18, first origin X at +20, last line end X at +22 and its origin Y at +24. Rejected calls return zero for all documented outputs and leave the bitmap untouched. T/BP scratch may change. Blank lines affect line count and position but cause no glyph/background writes.
The component is non-re-entrant. It supplies no frame wait, OS negotiation, interrupt protection or video synchronisation. The reset caller owns the machine; a real game must choose safe display access and coordinate other users of T/BP, video, banks and interrupts. Native completion does not establish stock-OS loading, live tearing behaviour, original-hardware timing or a frame budget.
Read the reusable layout component
Reusable layout over the existing text renderer
; SPDX-License-Identifier: MIT
; Bounded counted 8x8 text layout over the unchanged bitmap-text component.
; Include bitmap-text once; reserve TL=64, T=96 and BP=48 separate bytes.
; TL inputs 0..12 survive. T/BP are owned workspaces. See README.md.
l_left equ TL+0
l_top equ TL+2
l_width equ TL+4
l_height equ TL+6
l_align equ TL+8
l_pitch equ TL+9
l_source equ TL+10
l_length equ TL+12
l_status equ TL+13
l_consumed equ TL+14
l_lines equ TL+15
l_last_count equ TL+16
l_measured equ TL+18
l_first_x equ TL+20
l_last_x equ TL+22
l_last_y equ TL+24
l_calls equ TL+26
l_writes equ TL+28
l_mode equ TL+32
l_pass equ TL+33
l_i equ TL+34
l_start equ TL+35
l_count equ TL+36
l_columns equ TL+37
l_temp equ TL+38
l_right equ TL+40
l_bottom equ TL+42
l_y equ TL+44
l_x equ TL+46
l_pointer equ TL+48
l_code equ TL+50
l_line_width equ TL+52
l_work equ TL+54
layout_measure:
call layout_reset
ld hl,$0000
ld (l_measured),hl
ld a,(l_length)
ld (l_measured),a
ld hl,l_measured
sla (hl)
inc hl
rl (hl)
ld hl,l_measured
sla (hl)
inc hl
rl (hl)
ld hl,l_measured
sla (hl)
inc hl
rl (hl)
ld a,$01
ld (l_status),a
ret
layout_line:
ld a,$00
ld (l_mode),a
jp layout_enter
layout_block:
ld a,$01
ld (l_mode),a
jp layout_enter
layout_reset:
ld a,$00
ld (l_status),a
ld a,$00
ld (l_consumed),a
ld a,$00
ld (l_lines),a
ld a,$00
ld (l_last_count),a
ld hl,$0000
ld (l_measured),hl
ld hl,$0000
ld (l_first_x),hl
ld hl,$0000
ld (l_last_x),hl
ld hl,$0000
ld (l_last_y),hl
ld hl,$0000
ld (l_calls),hl
ld a,$00
ld (l_writes),a
ld a,$00
ld (l_writes+1),a
ld a,$00
ld (l_writes+2),a
ld a,$00
ld (l_writes+3),a
ret
layout_enter:
call layout_reset
; Validate geometry before additions, so huge unsigned values cannot wrap into range.
ld a,$00
ld (g_cmp),a
ld a,(l_width+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_reject
jp nz,layout_branch_1
ld a,$08
ld (g_cmp),a
ld a,(l_width)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_reject
layout_branch_1:
ld a,$00
ld (g_cmp),a
ld a,(l_height+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_reject
jp nz,layout_branch_2
ld a,$08
ld (g_cmp),a
ld a,(l_height)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_reject
layout_branch_2:
ld hl,TEXT_WIDTH
ld (l_temp),hl
ld a,(l_temp+1)
ld (g_cmp),a
ld a,(l_width+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_3
jp nz,layout_reject
ld a,(l_temp)
ld (g_cmp),a
ld a,(l_width)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_3
jp nz,layout_reject
layout_branch_3:
ld hl,TEXT_HEIGHT
ld (l_temp),hl
ld a,(l_temp+1)
ld (g_cmp),a
ld a,(l_height+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_4
jp nz,layout_reject
ld a,(l_temp)
ld (g_cmp),a
ld a,(l_height)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_4
jp nz,layout_reject
layout_branch_4:
ld hl,TEXT_WIDTH
ld (l_temp),hl
ld a,(l_temp+1)
ld (g_cmp),a
ld a,(l_left+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_5
jp nz,layout_reject
ld a,(l_temp)
ld (g_cmp),a
ld a,(l_left)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_5
jp nz,layout_reject
layout_branch_5:
ld hl,TEXT_HEIGHT
ld (l_temp),hl
ld a,(l_temp+1)
ld (g_cmp),a
ld a,(l_top+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_6
jp nz,layout_reject
ld a,(l_temp)
ld (g_cmp),a
ld a,(l_top)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_6
jp nz,layout_reject
layout_branch_6:
ld hl,(l_left)
ld (l_right),hl
ld hl,(l_right)
ld de,(l_width)
add hl,de
ld (l_right),hl
ld hl,TEXT_WIDTH
ld (l_temp),hl
ld a,(l_temp+1)
ld (g_cmp),a
ld a,(l_right+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_7
jp nz,layout_reject
ld a,(l_temp)
ld (g_cmp),a
ld a,(l_right)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_7
jp nz,layout_reject
layout_branch_7:
ld hl,(l_top)
ld (l_bottom),hl
ld hl,(l_bottom)
ld de,(l_height)
add hl,de
ld (l_bottom),hl
ld hl,TEXT_HEIGHT
ld (l_temp),hl
ld a,(l_temp+1)
ld (g_cmp),a
ld a,(l_bottom+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_8
jp nz,layout_reject
ld a,(l_temp)
ld (g_cmp),a
ld a,(l_bottom)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_8
jp nz,layout_reject
layout_branch_8:
ld a,$02
ld (g_cmp),a
ld a,(l_align)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_9
jp nz,layout_reject
layout_branch_9:
ld a,$08
ld (g_cmp),a
ld a,(l_pitch)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_reject
layout_branch_10:
ld hl,$0000
ld (l_temp),hl
ld a,(l_length)
ld (l_temp),a
ld hl,(l_temp)
ld de,(l_source)
add hl,de
ld (l_temp),hl
ld a,$00
ld (g_cmp),a
ld a,(l_temp+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_12
ld a,$00
ld (g_cmp),a
ld a,(l_temp)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_12
jp layout_branch_11
layout_branch_12:
ld a,(l_source+1)
ld (g_cmp),a
ld a,(l_temp+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_reject
jp nz,layout_branch_13
ld a,(l_source)
ld (g_cmp),a
ld a,(l_temp)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_reject
layout_branch_13:
layout_branch_11:
ld hl,(l_width)
ld (l_temp),hl
ld hl,l_temp+1
srl (hl)
dec hl
rr (hl)
ld hl,l_temp+1
srl (hl)
dec hl
rr (hl)
ld hl,l_temp+1
srl (hl)
dec hl
rr (hl)
ld a,(l_temp)
ld (l_columns),a
; An empty renderer call validates font, pen and source declarations without drawing.
ld hl,(l_left)
ld (t_x),hl
ld hl,(l_top)
ld (t_y),hl
ld hl,(l_source)
ld (t_source),hl
ld hl,(l_left)
ld (t_clip_l),hl
ld hl,(l_top)
ld (t_clip_t),hl
ld hl,(l_right)
ld (t_clip_r),hl
ld hl,(l_bottom)
ld (t_clip_b),hl
ld a,$00
ld (t_length),a
call text_string
ld a,$00
ld (g_cmp),a
ld a,(t_status)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_14
jp layout_reject
layout_branch_14:
ld a,$00
ld (l_pass),a
layout_pass:
ld a,$00
ld (l_i),a
ld hl,(l_top)
ld (l_y),hl
ld a,$00
ld (l_lines),a
ld hl,$0000
ld (l_measured),hl
layout_next:
ld a,(l_length)
ld (g_cmp),a
ld a,(l_i)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_15
jp layout_pass_done
layout_branch_15:
ld hl,(l_y)
ld (l_temp),hl
ld hl,(l_temp)
ld de,$0008
add hl,de
ld (l_temp),hl
ld a,(l_bottom+1)
ld (g_cmp),a
ld a,(l_temp+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_16
jp nz,layout_reject
ld a,(l_bottom)
ld (g_cmp),a
ld a,(l_temp)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_16
jp nz,layout_reject
layout_branch_16:
ld a,(l_i)
ld (l_start),a
ld a,$00
ld (l_count),a
ld a,$00
ld (g_cmp),a
ld a,(l_mode)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_scan
layout_branch_17:
ld a,(l_length)
ld (l_count),a
ld a,(l_length)
ld (l_i),a
jp layout_position
; Character wrapping: consume LF after a full chunk too; never invent a trailing empty row.
layout_scan:
ld a,(l_length)
ld (g_cmp),a
ld a,(l_i)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_18
jp layout_after_scan
layout_branch_18:
ld a,(l_columns)
ld (g_cmp),a
ld a,(l_count)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_19
jp layout_after_scan
layout_branch_19:
ld hl,$0000
ld (l_pointer),hl
ld a,(l_i)
ld (l_pointer),a
ld hl,(l_pointer)
ld de,(l_source)
add hl,de
ld (l_pointer),hl
ld hl,(l_pointer)
ld a,(hl)
ld (l_code),a
ld a,$0a
ld (g_cmp),a
ld a,(l_code)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_20
jp layout_after_scan
layout_branch_20:
or a
ld a,$01
ld b,a
ld a,(l_count)
adc a,b
ld (l_count),a
or a
ld a,$01
ld b,a
ld a,(l_i)
adc a,b
ld (l_i),a
jp layout_scan
layout_after_scan:
ld a,(l_length)
ld (g_cmp),a
ld a,(l_i)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_21
jp layout_position
layout_branch_21:
ld hl,$0000
ld (l_pointer),hl
ld a,(l_i)
ld (l_pointer),a
ld hl,(l_pointer)
ld de,(l_source)
add hl,de
ld (l_pointer),hl
ld hl,(l_pointer)
ld a,(hl)
ld (l_code),a
ld a,$0a
ld (g_cmp),a
ld a,(l_code)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_position
layout_branch_22:
or a
ld a,$01
ld b,a
ld a,(l_i)
adc a,b
ld (l_i),a
layout_position:
ld hl,$0000
ld (l_line_width),hl
ld a,(l_count)
ld (l_line_width),a
; BEGIN WIDTH FIX: eight pixels of advance for every counted glyph.
ld hl,l_line_width
sla (hl)
inc hl
rl (hl)
ld hl,l_line_width
sla (hl)
inc hl
rl (hl)
ld hl,l_line_width
sla (hl)
inc hl
rl (hl)
; END WIDTH FIX
ld a,(l_width+1)
ld (g_cmp),a
ld a,(l_line_width+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_23
jp nz,layout_reject
ld a,(l_width)
ld (g_cmp),a
ld a,(l_line_width)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_23
jp nz,layout_reject
layout_branch_23:
ld a,(l_measured+1)
ld (g_cmp),a
ld a,(l_line_width+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_24
jp nz,layout_branch_25
ld a,(l_measured)
ld (g_cmp),a
ld a,(l_line_width)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_24
jp z,layout_branch_24
layout_branch_25:
ld hl,(l_line_width)
ld (l_measured),hl
layout_branch_24:
ld hl,(l_width)
ld (l_temp),hl
ld hl,(l_temp)
ld de,(l_line_width)
or a
sbc hl,de
ld (l_temp),hl
ld a,$00
ld (g_cmp),a
ld a,(l_align)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_28
jp layout_branch_26
layout_branch_28:
ld a,$02
ld (g_cmp),a
ld a,(l_align)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_29
jp layout_branch_27
layout_branch_29:
ld hl,l_temp+1
srl (hl)
dec hl
rr (hl)
jp layout_branch_27
layout_branch_26:
ld hl,$0000
ld (l_temp),hl
layout_branch_27:
; BEGIN ORIGIN FIX: the unused-space offset is relative to the rectangle.
ld hl,(l_left)
ld (l_x),hl
ld hl,(l_x)
ld de,(l_temp)
add hl,de
ld (l_x),hl
; END ORIGIN FIX
ld a,$00
ld (g_cmp),a
ld a,(l_lines)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_30
layout_branch_31:
ld hl,(l_x)
ld (l_first_x),hl
layout_branch_30:
ld hl,(l_x)
ld (l_last_x),hl
ld hl,(l_last_x)
ld de,(l_line_width)
add hl,de
ld (l_last_x),hl
ld hl,(l_y)
ld (l_last_y),hl
ld a,(l_count)
ld (l_last_count),a
or a
ld a,$01
ld b,a
ld a,(l_lines)
adc a,b
ld (l_lines),a
ld a,$00
ld (g_cmp),a
ld a,(l_pass)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_32
jp layout_advance
layout_branch_32:
ld hl,(l_x)
ld (t_x),hl
ld hl,(l_y)
ld (t_y),hl
ld hl,$0000
ld (t_source),hl
ld a,(l_start)
ld (t_source),a
ld hl,(t_source)
ld de,(l_source)
add hl,de
ld (t_source),hl
ld a,(l_count)
ld (t_length),a
call text_string
ld hl,(l_calls)
ld de,(t_calls)
add hl,de
ld (l_calls),hl
or a
ld a,(t_writes)
ld b,a
ld a,(l_writes)
adc a,b
ld (l_writes),a
ld a,(t_writes+1)
ld b,a
ld a,(l_writes+1)
adc a,b
ld (l_writes+1),a
ld a,(t_writes+2)
ld b,a
ld a,(l_writes+2)
adc a,b
ld (l_writes+2),a
ld a,(t_writes+3)
ld b,a
ld a,(l_writes+3)
adc a,b
ld (l_writes+3),a
layout_advance:
ld hl,$0000
ld (l_temp),hl
ld a,(l_pitch)
ld (l_temp),a
ld hl,(l_y)
ld de,(l_temp)
add hl,de
ld (l_y),hl
jp layout_next
layout_pass_done:
ld a,$00
ld (g_cmp),a
ld a,(l_pass)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_33
layout_branch_34:
ld a,$01
ld (l_pass),a
jp layout_pass
layout_branch_33:
ld a,(l_length)
ld (l_consumed),a
ld a,$01
ld (l_status),a
ret
layout_reject:
call layout_reset
ret
layout_end:The source and retained records contain 64 positive native calls for this machine, the custom caller, two completed faults and both repairs. The checks compare the whole bitmap allocation, public results, guards, unchanged source/font and selected entire native frames. Asm198x 0.0.71 and released Emu198x 0.41.0 are pinned. Retained replay and corruption tests support CI; they do not replace fresh execution on another machine or configuration.
Compare the four implementations and use the existing bitmap-text entry for the font, backend and original hardware sources.
Keep words together
For a paragraph or message panel, word-aware wrapping can be easier to read. Choose the maintained word-wrap/z80.inc variant over the same bitmap-text renderer. It adds 113 code bytes (1,741 total) and keeps the same 64-byte TL allocation and public contract. The original character-wrap component above remains useful for compact labels and fixed grids.
In seven columns, ONE THREE FIVE becomes ONE / THREE / FIVE. In two columns, ABCDE still becomes AB / CD / E: a word wider than the panel has to split if the routine is to make progress.
The exact policy matters when preparing messages:
- Fit at most
width div 8cells. If an LF or space follows a full line, consume that delimiter as the break. - Otherwise, break at the last space after the first byte of the current line, omitting that one separator. If there is none, split at the cell limit.
- Preserve every other space. Leading, repeated and trailing spaces remain data; this is not whitespace normalisation. Three spaces in a one-column panel occupy two rows because the middle space becomes a break.
- Byte 10 is LF in block mode. Consecutive LFs retain interior empty lines; a final LF adds no extra trailing row. Byte zero is a glyph code, not a terminator.
layout_line and layout_measure retain their counted-line behaviour: LF occupies a glyph cell there. Unsupported codes use the configured fallback. Empty input has zero rows and no drawing but still validates the declaration.
Both layout components deliberately export the same entry labels. Include one of them, after bitmap-text, rather than including both. The TL contract and single-line calls stay the same. Words can require more rows than character wrapping: the complete first pass checks the height before drawing, so a panel that is now too short rejects without partial text.
Pitch measures the distance between row origins. Five rows at pitch 16 need (5-1)*16+8=72 scanlines. Opaque mode paints actual glyph cells, not the unused panel width or empty rows. Clear the panel you own before replacing a longer message, or old letters may remain.
Try a word-aware panel
Use the complete word-aware caller. The include replaces text-layout; keep the same T/BP/font/backend setup. The normal call still looks like this:
Use the same TL contract with word-aware layout
; BEGIN LAYOUT INPUTS
ld hl,PANEL_LEFT
ld (TL+0),hl
ld hl,PANEL_TOP
ld (TL+2),hl
ld hl,PANEL_WIDTH
ld (TL+4),hl
ld hl,PANEL_HEIGHT
ld (TL+6),hl
ld hl,label_data
ld (TL+10),hl
ld a,PANEL_ALIGN
ld (TL+8),a
ld a,LINE_PITCH
ld (TL+9),a
ld a,MESSAGE_LENGTH
ld (TL+12),a
call layout_block
; END LAYOUT INPUTSBuild customise.asm with the same includes. It narrows the panel from 176 to 104 pixels, selects right alignment, changes pitch from 12 to 16 and supplies another original counted message. The result has five lines inside the existing 80-scanline height. The rows are A NARROW / PANEL / WRAPS THIS / MESSAGE / PRESS FIRE. PANEL and MESSAGE stay whole; character wrapping split both words in the earlier example.
Change the panel, alignment, pitch and counted message
CUSTOM_LAYOUT equ 1
; SPDX-License-Identifier: MIT
; Complete caller: change layout inputs without changing the reusable renderer.
PANEL_LEFT equ 21
PANEL_TOP equ 37
PANEL_WIDTH equ 104
PANEL_HEIGHT equ 80
PANEL_ALIGN equ 2
LINE_PITCH equ 16
MESSAGE_LENGTH equ 44
include "caller.inc"
Edit customise-message.inc and update MESSAGE_LENGTH together. The routine has no terminator scan to rescue an incorrect count. More rows or wider text can increase drawing work, even though code and context allocations stay the same. The proportional option below changes glyph advances; different glyph sizes, whitespace rules or video modes need their own qualification.
A word breaks despite a usable space
The scan has found a space, but the caller discards that saved boundary and cuts at capacity. ONE THREE FIVE becomes ONE THR / EE FIVE in a seven-column panel. The count is bounded, yet the intended word policy is lost.
Deliberately wrong: discard the saved word boundary
; BEGIN WRONG
jp layout_position ; WRONG: discard the saved word boundary
Restore both the drawn count and the source resume position. The common delimiter step then consumes the selected space exactly once:
Correct: resume at the saved separator
; BEGIN FIX
ld a,(l_break_count)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_28
jp layout_position
layout_branch_28:
ld a,(l_break_count)
ld (l_count),a
ld a,(l_break_i)
ld (l_i),a
What does word wrapping cost?
The extra work is scanning for a separator and possibly rescanning the start of a word on the next row, in both planning passes. Every row consumes at least one source byte; counted input is bounded at 255. There is no extra context or font allocation. This is not proportional text, kerning or whitespace normalisation.
For the same custom message and panel, changed line breaks and two omitted spaces reduce span calls from 709 to 693. Destination-byte writes change from 1,453 to 1,405. Those are observed drawing-work counts, not a total-speed comparison: planning costs more, and placement changes byte phase too. Measure the complete call before claiming a frame budget.
The word-aware component retains 79 positive native calls, the customisation and three faults with executed repairs on this machine. Full memory, inputs/outputs, guards, unchanged source/font and selected native frames match independent expectations. The four-machine total is 320 observations with Asm198x 0.0.71 and Emu198x 0.41.0. Source and records include the resource contract and reproduction commands.
Read the reusable word-aware component
Selectable word-aware variant; include instead of text-layout
; SPDX-License-Identifier: MIT
; Bounded counted 8x8 text layout over the unchanged bitmap-text component.
; Include bitmap-text once; reserve TL=64, T=96 and BP=48 separate bytes.
; TL inputs 0..12 survive. T/BP are owned workspaces. See README.md.
l_left equ TL+0
l_top equ TL+2
l_width equ TL+4
l_height equ TL+6
l_align equ TL+8
l_pitch equ TL+9
l_source equ TL+10
l_length equ TL+12
l_status equ TL+13
l_consumed equ TL+14
l_lines equ TL+15
l_last_count equ TL+16
l_measured equ TL+18
l_first_x equ TL+20
l_last_x equ TL+22
l_last_y equ TL+24
l_calls equ TL+26
l_writes equ TL+28
l_mode equ TL+32
l_pass equ TL+33
l_i equ TL+34
l_start equ TL+35
l_count equ TL+36
l_columns equ TL+37
l_temp equ TL+38
l_right equ TL+40
l_bottom equ TL+42
l_y equ TL+44
l_x equ TL+46
l_pointer equ TL+48
l_code equ TL+50
l_line_width equ TL+52
l_work equ TL+54
l_break_i equ TL+58
l_break_count equ TL+59
layout_measure:
call layout_reset
ld hl,$0000
ld (l_measured),hl
ld a,(l_length)
ld (l_measured),a
ld hl,l_measured
sla (hl)
inc hl
rl (hl)
ld hl,l_measured
sla (hl)
inc hl
rl (hl)
ld hl,l_measured
sla (hl)
inc hl
rl (hl)
ld a,$01
ld (l_status),a
ret
layout_line:
ld a,$00
ld (l_mode),a
jp layout_enter
layout_block:
ld a,$01
ld (l_mode),a
jp layout_enter
layout_reset:
ld a,$00
ld (l_status),a
ld a,$00
ld (l_consumed),a
ld a,$00
ld (l_lines),a
ld a,$00
ld (l_last_count),a
ld hl,$0000
ld (l_measured),hl
ld hl,$0000
ld (l_first_x),hl
ld hl,$0000
ld (l_last_x),hl
ld hl,$0000
ld (l_last_y),hl
ld hl,$0000
ld (l_calls),hl
ld a,$00
ld (l_writes),a
ld a,$00
ld (l_writes+1),a
ld a,$00
ld (l_writes+2),a
ld a,$00
ld (l_writes+3),a
ret
layout_enter:
call layout_reset
; Validate geometry before additions, so huge unsigned values cannot wrap into range.
ld a,$00
ld (g_cmp),a
ld a,(l_width+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_reject
jp nz,layout_branch_1
ld a,$08
ld (g_cmp),a
ld a,(l_width)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_reject
layout_branch_1:
ld a,$00
ld (g_cmp),a
ld a,(l_height+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_reject
jp nz,layout_branch_2
ld a,$08
ld (g_cmp),a
ld a,(l_height)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_reject
layout_branch_2:
ld hl,TEXT_WIDTH
ld (l_temp),hl
ld a,(l_temp+1)
ld (g_cmp),a
ld a,(l_width+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_3
jp nz,layout_reject
ld a,(l_temp)
ld (g_cmp),a
ld a,(l_width)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_3
jp nz,layout_reject
layout_branch_3:
ld hl,TEXT_HEIGHT
ld (l_temp),hl
ld a,(l_temp+1)
ld (g_cmp),a
ld a,(l_height+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_4
jp nz,layout_reject
ld a,(l_temp)
ld (g_cmp),a
ld a,(l_height)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_4
jp nz,layout_reject
layout_branch_4:
ld hl,TEXT_WIDTH
ld (l_temp),hl
ld a,(l_temp+1)
ld (g_cmp),a
ld a,(l_left+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_5
jp nz,layout_reject
ld a,(l_temp)
ld (g_cmp),a
ld a,(l_left)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_5
jp nz,layout_reject
layout_branch_5:
ld hl,TEXT_HEIGHT
ld (l_temp),hl
ld a,(l_temp+1)
ld (g_cmp),a
ld a,(l_top+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_6
jp nz,layout_reject
ld a,(l_temp)
ld (g_cmp),a
ld a,(l_top)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_6
jp nz,layout_reject
layout_branch_6:
ld hl,(l_left)
ld (l_right),hl
ld hl,(l_right)
ld de,(l_width)
add hl,de
ld (l_right),hl
ld hl,TEXT_WIDTH
ld (l_temp),hl
ld a,(l_temp+1)
ld (g_cmp),a
ld a,(l_right+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_7
jp nz,layout_reject
ld a,(l_temp)
ld (g_cmp),a
ld a,(l_right)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_7
jp nz,layout_reject
layout_branch_7:
ld hl,(l_top)
ld (l_bottom),hl
ld hl,(l_bottom)
ld de,(l_height)
add hl,de
ld (l_bottom),hl
ld hl,TEXT_HEIGHT
ld (l_temp),hl
ld a,(l_temp+1)
ld (g_cmp),a
ld a,(l_bottom+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_8
jp nz,layout_reject
ld a,(l_temp)
ld (g_cmp),a
ld a,(l_bottom)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_8
jp nz,layout_reject
layout_branch_8:
ld a,$02
ld (g_cmp),a
ld a,(l_align)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_9
jp nz,layout_reject
layout_branch_9:
ld a,$08
ld (g_cmp),a
ld a,(l_pitch)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_reject
layout_branch_10:
ld hl,$0000
ld (l_temp),hl
ld a,(l_length)
ld (l_temp),a
ld hl,(l_temp)
ld de,(l_source)
add hl,de
ld (l_temp),hl
ld a,$00
ld (g_cmp),a
ld a,(l_temp+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_12
ld a,$00
ld (g_cmp),a
ld a,(l_temp)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_12
jp layout_branch_11
layout_branch_12:
ld a,(l_source+1)
ld (g_cmp),a
ld a,(l_temp+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_reject
jp nz,layout_branch_13
ld a,(l_source)
ld (g_cmp),a
ld a,(l_temp)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_reject
layout_branch_13:
layout_branch_11:
ld hl,(l_width)
ld (l_temp),hl
ld hl,l_temp+1
srl (hl)
dec hl
rr (hl)
ld hl,l_temp+1
srl (hl)
dec hl
rr (hl)
ld hl,l_temp+1
srl (hl)
dec hl
rr (hl)
ld a,(l_temp)
ld (l_columns),a
; An empty renderer call validates font, pen and source declarations without drawing.
ld hl,(l_left)
ld (t_x),hl
ld hl,(l_top)
ld (t_y),hl
ld hl,(l_source)
ld (t_source),hl
ld hl,(l_left)
ld (t_clip_l),hl
ld hl,(l_top)
ld (t_clip_t),hl
ld hl,(l_right)
ld (t_clip_r),hl
ld hl,(l_bottom)
ld (t_clip_b),hl
ld a,$00
ld (t_length),a
call text_string
ld a,$00
ld (g_cmp),a
ld a,(t_status)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_14
jp layout_reject
layout_branch_14:
ld a,$00
ld (l_pass),a
layout_pass:
ld a,$00
ld (l_i),a
ld hl,(l_top)
ld (l_y),hl
ld a,$00
ld (l_lines),a
ld hl,$0000
ld (l_measured),hl
layout_next:
ld a,(l_length)
ld (g_cmp),a
ld a,(l_i)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_15
jp layout_pass_done
layout_branch_15:
ld hl,(l_y)
ld (l_temp),hl
ld hl,(l_temp)
ld de,$0008
add hl,de
ld (l_temp),hl
ld a,(l_bottom+1)
ld (g_cmp),a
ld a,(l_temp+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_16
jp nz,layout_reject
ld a,(l_bottom)
ld (g_cmp),a
ld a,(l_temp)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_16
jp nz,layout_reject
layout_branch_16:
ld a,(l_i)
ld (l_start),a
ld a,$00
ld (l_count),a
ld a,$00
ld (l_break_count),a
ld a,$00
ld (g_cmp),a
ld a,(l_mode)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_scan
layout_branch_17:
ld a,(l_length)
ld (l_count),a
ld a,(l_length)
ld (l_i),a
jp layout_position
; Scan a word-aware row: consume LF after a full chunk too; never invent a trailing empty row.
layout_scan:
ld a,(l_length)
ld (g_cmp),a
ld a,(l_i)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_18
jp layout_after_scan
layout_branch_18:
ld a,(l_columns)
ld (g_cmp),a
ld a,(l_count)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_19
jp layout_after_scan
layout_branch_19:
ld hl,$0000
ld (l_pointer),hl
ld a,(l_i)
ld (l_pointer),a
ld hl,(l_pointer)
ld de,(l_source)
add hl,de
ld (l_pointer),hl
ld hl,(l_pointer)
ld a,(hl)
ld (l_code),a
ld a,$0a
ld (g_cmp),a
ld a,(l_code)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_20
jp layout_after_scan
layout_branch_20:
ld a,$20
ld (g_cmp),a
ld a,(l_code)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_21
layout_branch_22:
ld a,$00
ld (g_cmp),a
ld a,(l_count)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_23
jp layout_branch_21
layout_branch_23:
ld a,(l_i)
ld (l_break_i),a
ld a,(l_count)
ld (l_break_count),a
layout_branch_21:
or a
ld a,$01
ld b,a
ld a,(l_count)
adc a,b
ld (l_count),a
or a
ld a,$01
ld b,a
ld a,(l_i)
adc a,b
ld (l_i),a
jp layout_scan
layout_after_scan:
ld a,(l_length)
ld (g_cmp),a
ld a,(l_i)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_24
jp layout_position
layout_branch_24:
ld hl,$0000
ld (l_pointer),hl
ld a,(l_i)
ld (l_pointer),a
ld hl,(l_pointer)
ld de,(l_source)
add hl,de
ld (l_pointer),hl
ld hl,(l_pointer)
ld a,(hl)
ld (l_code),a
ld a,$0a
ld (g_cmp),a
ld a,(l_code)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_26
jp layout_branch_25
layout_branch_26:
ld a,$20
ld (g_cmp),a
ld a,(l_code)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_27
jp layout_branch_25
layout_branch_27:
; Backtrack to the last eligible space; otherwise split an overlong word.
ld a,$00
ld (g_cmp),a
; BEGIN BREAK FIX
ld a,(l_break_count)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_28
jp layout_position
layout_branch_28:
ld a,(l_break_count)
ld (l_count),a
ld a,(l_break_i)
ld (l_i),a
; END BREAK FIX
layout_branch_25:
or a
ld a,$01
ld b,a
ld a,(l_i)
adc a,b
ld (l_i),a
layout_position:
ld hl,$0000
ld (l_line_width),hl
ld a,(l_count)
ld (l_line_width),a
; BEGIN WIDTH FIX: eight pixels of advance for every counted glyph.
ld hl,l_line_width
sla (hl)
inc hl
rl (hl)
ld hl,l_line_width
sla (hl)
inc hl
rl (hl)
ld hl,l_line_width
sla (hl)
inc hl
rl (hl)
; END WIDTH FIX
ld a,(l_width+1)
ld (g_cmp),a
ld a,(l_line_width+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_29
jp nz,layout_reject
ld a,(l_width)
ld (g_cmp),a
ld a,(l_line_width)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_29
jp nz,layout_reject
layout_branch_29:
ld a,(l_measured+1)
ld (g_cmp),a
ld a,(l_line_width+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_30
jp nz,layout_branch_31
ld a,(l_measured)
ld (g_cmp),a
ld a,(l_line_width)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_30
jp z,layout_branch_30
layout_branch_31:
ld hl,(l_line_width)
ld (l_measured),hl
layout_branch_30:
ld hl,(l_width)
ld (l_temp),hl
ld hl,(l_temp)
ld de,(l_line_width)
or a
sbc hl,de
ld (l_temp),hl
ld a,$00
ld (g_cmp),a
ld a,(l_align)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_34
jp layout_branch_32
layout_branch_34:
ld a,$02
ld (g_cmp),a
ld a,(l_align)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_35
jp layout_branch_33
layout_branch_35:
ld hl,l_temp+1
srl (hl)
dec hl
rr (hl)
jp layout_branch_33
layout_branch_32:
ld hl,$0000
ld (l_temp),hl
layout_branch_33:
; BEGIN ORIGIN FIX: the unused-space offset is relative to the rectangle.
ld hl,(l_left)
ld (l_x),hl
ld hl,(l_x)
ld de,(l_temp)
add hl,de
ld (l_x),hl
; END ORIGIN FIX
ld a,$00
ld (g_cmp),a
ld a,(l_lines)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_36
layout_branch_37:
ld hl,(l_x)
ld (l_first_x),hl
layout_branch_36:
ld hl,(l_x)
ld (l_last_x),hl
ld hl,(l_last_x)
ld de,(l_line_width)
add hl,de
ld (l_last_x),hl
ld hl,(l_y)
ld (l_last_y),hl
ld a,(l_count)
ld (l_last_count),a
or a
ld a,$01
ld b,a
ld a,(l_lines)
adc a,b
ld (l_lines),a
ld a,$00
ld (g_cmp),a
ld a,(l_pass)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_38
jp layout_advance
layout_branch_38:
ld hl,(l_x)
ld (t_x),hl
ld hl,(l_y)
ld (t_y),hl
ld hl,$0000
ld (t_source),hl
ld a,(l_start)
ld (t_source),a
ld hl,(t_source)
ld de,(l_source)
add hl,de
ld (t_source),hl
ld a,(l_count)
ld (t_length),a
call text_string
ld hl,(l_calls)
ld de,(t_calls)
add hl,de
ld (l_calls),hl
or a
ld a,(t_writes)
ld b,a
ld a,(l_writes)
adc a,b
ld (l_writes),a
ld a,(t_writes+1)
ld b,a
ld a,(l_writes+1)
adc a,b
ld (l_writes+1),a
ld a,(t_writes+2)
ld b,a
ld a,(l_writes+2)
adc a,b
ld (l_writes+2),a
ld a,(t_writes+3)
ld b,a
ld a,(l_writes+3)
adc a,b
ld (l_writes+3),a
layout_advance:
ld hl,$0000
ld (l_temp),hl
ld a,(l_pitch)
ld (l_temp),a
ld hl,(l_y)
ld de,(l_temp)
add hl,de
ld (l_y),hl
jp layout_next
layout_pass_done:
ld a,$00
ld (g_cmp),a
ld a,(l_pass)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_39
layout_branch_40:
ld a,$01
ld (l_pass),a
jp layout_pass
layout_branch_39:
ld a,(l_length)
ld (l_consumed),a
ld a,$01
ld (l_status),a
ret
layout_reject:
call layout_reset
ret
layout_end:Captures preserve native borders and horizontal sampling. Use logical pixel coordinates for layout calculations; these are not calibrated CRT aspect-ratio demonstrations.
Give narrow letters less room
An I does not need the same space as a W. The proportional variant crops our original font to its ink and supplies one width byte per glyph. Add the chosen letter spacing to get the advance: the distance to the next glyph. At spacing 1, six Is need 24 pixels and six Ws need 36. Both needed 48 in the fixed-cell version.

Choose proportional-text/z80.inc instead of either fixed-width layout include. Keep the existing bitmap-text component and this machine’s backend. Reserve 80 bytes for TL, rather than 64; T and BP still need 96 and 48 bytes. Their addresses and video obligations remain as above. Use the complete proportional caller for the new font and metric includes as well as the ordinary setup call.
The first 13 TL input bytes and output offsets remain familiar. Add a native-order word metric pointer at TL+64 and byte letter spacing at TL+66. The table has one width byte per declared glyph, indexed by resolved code − first code. Resolve an unsupported code to the configured fallback before looking up its width. Each width is 1..8, spacing is 0..2, and their sum must be at most 8. Inputs TL+0..12 and +64..66 survive; the rest of the 80-byte context is scratch or output.
Supply the metric table and letter spacing before layout_block
; BEGIN LAYOUT INPUTS
ld hl,PANEL_LEFT
ld (TL+0),hl
ld hl,PANEL_TOP
ld (TL+2),hl
ld hl,PANEL_WIDTH
ld (TL+4),hl
ld hl,PANEL_HEIGHT
ld (TL+6),hl
ld hl,label_data
ld (TL+10),hl
ld a,PANEL_ALIGN
ld (TL+8),a
ld a,LINE_PITCH
ld (TL+9),a
ld a,MESSAGE_LENGTH
ld (TL+12),a
ld hl,metric_data
ld (TL+64),hl
ld a,LETTER_SPACING
ld (TL+66),a
call layout_block
; END LAYOUT INPUTSMeasurement now reads the string, fallback and widths. Initialise the whole declaration before layout_measure; the old count-only call cannot measure this font. It returns the sum at TL+18, even if that sum exceeds panel width. layout_line checks fit before subtracting for alignment. The same advances decide where a word fits in block mode, then place its glyphs during rendering. This is an advance sum, not optical centring: the final spacing slot is included too.
The source is still counted bytes, the glyph is still eight rows, and top is a cell origin rather than a baseline. LF only breaks in block mode; elsewhere it is a glyph or fallback. Word wrapping keeps the explicit space/LF policy described above, but tests pixel advances rather than width div 8. No kerning pairs, shaping, bearings or case conversion are supplied.
Change the spacing deliberately
The maintained custom caller uses spacing 2 instead of 1, a 104-pixel right-aligned panel, pitch 16 and the 44-byte message used above. It produces 4 rows, with a maximum advance of 94 pixels. The glyphs keep their shapes; their origins move further apart. I! WIDE measures 32 pixels at spacing 1 and 39 at spacing 2.
Change letter spacing alongside the panel and message
LETTER_SPACING equ 2
CUSTOM_LAYOUT equ 1
; SPDX-License-Identifier: MIT
; Complete caller: change layout inputs without changing the reusable renderer.
PANEL_LEFT equ 21
PANEL_TOP equ 37
PANEL_WIDTH equ 104
PANEL_HEIGHT equ 80
PANEL_ALIGN equ 2
LINE_PITCH equ 16
MESSAGE_LENGTH equ 44
include "caller.inc"
Edit a glyph’s eight row bytes and its width together. Keep ink inside the declared width and width + spacing ≤ 8. A smaller width crops pixels; it does not shrink the letter. A space has width 3 in this original font. A different font needs readable, stable rows and a matching table for its declared range. Those allocations must not overlap the bitmap, scratch, code or stack.
Letters spread beyond their measured end
This next fragment is deliberately wrong. Measurement uses proportional advances, but drawing still moves eight pixels after each glyph. I! WIDE starts at the planned position, then spreads out. Changing the panel origin would hide one symptom while leaving the disagreement in place.
Deliberately wrong: retain a fixed eight-pixel movement
; BEGIN WRONG
ld hl,(l_x)
ld de,8
add hl,de
ld (l_x),hl
Restore the very advance used by the planner:
Correct: advance by this glyph's width plus spacing
; BEGIN FIX
ld hl,(l_x)
ld de,(l_advance)
add hl,de
ld (l_x),hl
What the smaller letters cost
This layout variant uses 2,478 code bytes and 80 TL bytes, compared with the fixed alternatives above. The cropped font still uses 512 bytes; its width table adds 64. The unchanged renderer and backend still cost 2,268 and 578 code bytes. BBC row tables, video, source and stack remain separate allocations. Keep the conservative 80-byte stack budget plus your caller/interrupt depth.
| Exact workload | Normal prompt | Narrow custom panel |
|---|---|---|
| Source bytes | 33 | 44 |
| Logical rows | 2 | 4 |
| Maximum line advance | 104 pixels | 94 pixels |
| Backend spans | 511 | 701 |
| Destination-byte writes | 867 | 1281 |
Smaller advances can reduce opaque background work, but also change byte phase and line breaks. The component validates the table, reads glyph widths, may rescan words and calls the baseline renderer once per glyph. These drawing counts do not establish a speed gain or frame budget. Measure static labels once where useful; pre-rendering trades another asset for less runtime work.
The complete planning pass validates the declaration and height before drawing. Rejection leaves zero public results and the bitmap intact. Source, font, metrics, pens and mappings must remain stable through both passes. Opaque text clears each glyph’s advance, including spacing, for eight rows; unused panel space and blank lines still need your own clearing. Transparent zero bits preserve the picture underneath.
The component owns T/BP and their scissor, is non-re-entrant, and supplies no OS or interrupt protection, bank switching or safe display interval. CPU clobbers remain those of the renderer above. The 104 positive native calls, custom caller and wrong/fixed observations compare advance sums, row placement, complete destination memory, neighbours, unchanged source/font/metrics and selected whole frames. Tests cover all eight horizontal phases, empty/255-code strings, fallback, spacing limits, malformed metrics and atomic rejection. The exact amstrad-cpc reset/display configuration is qualified with pinned Asm198x 0.0.71 and Emu198x 0.41.0; stock-OS loading, live tearing, original hardware and other modes remain separate checks.
Read the reusable proportional component
Proportional measurement and word-aware layout over the existing renderer
; SPDX-License-Identifier: MIT
; Bounded proportional layout over the unchanged eight-row bitmap-text component.
; Include bitmap-text once; reserve TL=80, T=96 and BP=48 separate bytes.
; TL inputs 0..12 survive. TL+64 metric pointer, TL+66 spacing are additional preserved inputs. T/BP are owned workspaces. See README.md.
l_left equ TL+0
l_top equ TL+2
l_width equ TL+4
l_height equ TL+6
l_align equ TL+8
l_pitch equ TL+9
l_source equ TL+10
l_length equ TL+12
l_status equ TL+13
l_consumed equ TL+14
l_lines equ TL+15
l_last_count equ TL+16
l_measured equ TL+18
l_first_x equ TL+20
l_last_x equ TL+22
l_last_y equ TL+24
l_calls equ TL+26
l_writes equ TL+28
l_mode equ TL+32
l_pass equ TL+33
l_i equ TL+34
l_start equ TL+35
l_count equ TL+36
l_columns equ TL+37
l_temp equ TL+38
l_right equ TL+40
l_bottom equ TL+42
l_y equ TL+44
l_x equ TL+46
l_pointer equ TL+48
l_code equ TL+50
l_line_width equ TL+52
l_work equ TL+54
l_break_i equ TL+58
l_break_count equ TL+59
l_metrics equ TL+64
l_spacing equ TL+66
l_advance equ TL+68
l_scan_width equ TL+70
l_break_width equ TL+72
l_glyph equ TL+74
l_metric_i equ TL+75
l_draw_i equ TL+77
layout_measure:
ld a,$02
ld (l_mode),a
jp layout_enter
layout_line:
ld a,$00
ld (l_mode),a
jp layout_enter
layout_block:
ld a,$01
ld (l_mode),a
jp layout_enter
layout_reset:
ld a,$00
ld (l_status),a
ld a,$00
ld (l_consumed),a
ld a,$00
ld (l_lines),a
ld a,$00
ld (l_last_count),a
ld hl,$0000
ld (l_measured),hl
ld hl,$0000
ld (l_first_x),hl
ld hl,$0000
ld (l_last_x),hl
ld hl,$0000
ld (l_last_y),hl
ld hl,$0000
ld (l_calls),hl
ld a,$00
ld (l_writes),a
ld a,$00
ld (l_writes+1),a
ld a,$00
ld (l_writes+2),a
ld a,$00
ld (l_writes+3),a
ret
layout_enter:
call layout_reset
; Validate geometry before additions, so huge unsigned values cannot wrap into range.
ld a,$00
ld (g_cmp),a
ld a,(l_width+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_reject
jp nz,layout_branch_1
ld a,$08
ld (g_cmp),a
ld a,(l_width)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_reject
layout_branch_1:
ld a,$00
ld (g_cmp),a
ld a,(l_height+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_reject
jp nz,layout_branch_2
ld a,$08
ld (g_cmp),a
ld a,(l_height)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_reject
layout_branch_2:
ld hl,TEXT_WIDTH
ld (l_temp),hl
ld a,(l_temp+1)
ld (g_cmp),a
ld a,(l_width+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_3
jp nz,layout_reject
ld a,(l_temp)
ld (g_cmp),a
ld a,(l_width)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_3
jp nz,layout_reject
layout_branch_3:
ld hl,TEXT_HEIGHT
ld (l_temp),hl
ld a,(l_temp+1)
ld (g_cmp),a
ld a,(l_height+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_4
jp nz,layout_reject
ld a,(l_temp)
ld (g_cmp),a
ld a,(l_height)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_4
jp nz,layout_reject
layout_branch_4:
ld hl,TEXT_WIDTH
ld (l_temp),hl
ld a,(l_temp+1)
ld (g_cmp),a
ld a,(l_left+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_5
jp nz,layout_reject
ld a,(l_temp)
ld (g_cmp),a
ld a,(l_left)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_5
jp nz,layout_reject
layout_branch_5:
ld hl,TEXT_HEIGHT
ld (l_temp),hl
ld a,(l_temp+1)
ld (g_cmp),a
ld a,(l_top+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_6
jp nz,layout_reject
ld a,(l_temp)
ld (g_cmp),a
ld a,(l_top)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_6
jp nz,layout_reject
layout_branch_6:
ld hl,(l_left)
ld (l_right),hl
ld hl,(l_right)
ld de,(l_width)
add hl,de
ld (l_right),hl
ld hl,TEXT_WIDTH
ld (l_temp),hl
ld a,(l_temp+1)
ld (g_cmp),a
ld a,(l_right+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_7
jp nz,layout_reject
ld a,(l_temp)
ld (g_cmp),a
ld a,(l_right)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_7
jp nz,layout_reject
layout_branch_7:
ld hl,(l_top)
ld (l_bottom),hl
ld hl,(l_bottom)
ld de,(l_height)
add hl,de
ld (l_bottom),hl
ld hl,TEXT_HEIGHT
ld (l_temp),hl
ld a,(l_temp+1)
ld (g_cmp),a
ld a,(l_bottom+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_8
jp nz,layout_reject
ld a,(l_temp)
ld (g_cmp),a
ld a,(l_bottom)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_8
jp nz,layout_reject
layout_branch_8:
ld a,$02
ld (g_cmp),a
ld a,(l_align)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_9
jp nz,layout_reject
layout_branch_9:
ld a,$08
ld (g_cmp),a
ld a,(l_pitch)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_reject
layout_branch_10:
ld hl,$0000
ld (l_temp),hl
ld a,(l_length)
ld (l_temp),a
ld hl,(l_temp)
ld de,(l_source)
add hl,de
ld (l_temp),hl
ld a,$00
ld (g_cmp),a
ld a,(l_temp+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_12
ld a,$00
ld (g_cmp),a
ld a,(l_temp)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_12
jp layout_branch_11
layout_branch_12:
ld a,(l_source+1)
ld (g_cmp),a
ld a,(l_temp+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_reject
jp nz,layout_branch_13
ld a,(l_source)
ld (g_cmp),a
ld a,(l_temp)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_reject
layout_branch_13:
layout_branch_11:
ld hl,(l_width)
ld (l_temp),hl
ld hl,l_temp+1
srl (hl)
dec hl
rr (hl)
ld hl,l_temp+1
srl (hl)
dec hl
rr (hl)
ld hl,l_temp+1
srl (hl)
dec hl
rr (hl)
ld a,(l_temp)
ld (l_columns),a
; An empty renderer call validates font, pen and source declarations without drawing.
ld hl,(l_left)
ld (t_x),hl
ld hl,(l_top)
ld (t_y),hl
ld hl,(l_source)
ld (t_source),hl
ld hl,(l_left)
ld (t_clip_l),hl
ld hl,(l_top)
ld (t_clip_t),hl
ld hl,(l_right)
ld (t_clip_r),hl
ld hl,(l_bottom)
ld (t_clip_b),hl
ld a,$00
ld (t_length),a
call text_string
ld a,$00
ld (g_cmp),a
ld a,(t_status)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_14
jp layout_reject
layout_branch_14:
ld a,$02
ld (g_cmp),a
ld a,(l_spacing)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_15
jp nz,layout_reject
layout_branch_15:
ld hl,(l_metrics)
ld (l_temp),hl
ld hl,(l_temp)
ld de,(t_count)
add hl,de
ld (l_temp),hl
ld a,$00
ld (g_cmp),a
ld a,(l_temp+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_17
ld a,$00
ld (g_cmp),a
ld a,(l_temp)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_17
jp layout_branch_16
layout_branch_17:
ld a,(l_metrics+1)
ld (g_cmp),a
ld a,(l_temp+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_reject
jp nz,layout_branch_18
ld a,(l_metrics)
ld (g_cmp),a
ld a,(l_temp)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_reject
layout_branch_18:
layout_branch_16:
ld hl,$0000
ld (l_metric_i),hl
layout_validate_metric:
ld a,(t_count+1)
ld (g_cmp),a
ld a,(l_metric_i+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_19
jp nz,layout_metrics_ready
ld a,(t_count)
ld (g_cmp),a
ld a,(l_metric_i)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_19
jp layout_metrics_ready
layout_branch_19:
ld hl,(l_metrics)
ld (l_pointer),hl
ld hl,(l_pointer)
ld de,(l_metric_i)
add hl,de
ld (l_pointer),hl
call layout_read_pointer
ld (l_glyph),a
ld a,$01
ld (g_cmp),a
ld a,(l_glyph)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_reject
layout_branch_20:
ld hl,$0000
ld (l_temp),hl
ld a,(l_glyph)
ld (l_temp),a
ld hl,$0000
ld (l_advance),hl
ld a,(l_spacing)
ld (l_advance),a
ld hl,(l_temp)
ld de,(l_advance)
add hl,de
ld (l_temp),hl
ld a,$00
ld (g_cmp),a
ld a,(l_temp+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_21
jp nz,layout_reject
ld a,$08
ld (g_cmp),a
ld a,(l_temp)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_21
jp nz,layout_reject
layout_branch_21:
ld hl,(l_metric_i)
ld de,$0001
add hl,de
ld (l_metric_i),hl
jp layout_validate_metric
layout_metrics_ready:
ld a,$00
ld (l_pass),a
layout_pass:
ld a,$00
ld (l_i),a
ld hl,(l_top)
ld (l_y),hl
ld a,$00
ld (l_lines),a
ld hl,$0000
ld (l_measured),hl
layout_next:
ld a,(l_length)
ld (g_cmp),a
ld a,(l_i)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_22
jp layout_pass_done
layout_branch_22:
ld hl,(l_y)
ld (l_temp),hl
ld hl,(l_temp)
ld de,$0008
add hl,de
ld (l_temp),hl
ld a,(l_bottom+1)
ld (g_cmp),a
ld a,(l_temp+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_23
jp nz,layout_reject
ld a,(l_bottom)
ld (g_cmp),a
ld a,(l_temp)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_23
jp nz,layout_reject
layout_branch_23:
ld a,(l_i)
ld (l_start),a
ld a,$00
ld (l_count),a
ld a,$00
ld (l_break_count),a
ld hl,$0000
ld (l_line_width),hl
layout_scan:
ld a,(l_length)
ld (g_cmp),a
ld a,(l_i)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_24
jp layout_after_scan
layout_branch_24:
ld hl,$0000
ld (l_pointer),hl
ld a,(l_i)
ld (l_pointer),a
ld hl,(l_pointer)
ld de,(l_source)
add hl,de
ld (l_pointer),hl
ld hl,(l_pointer)
ld a,(hl)
ld (l_code),a
ld a,$01
ld (g_cmp),a
ld a,(l_mode)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_25
layout_branch_26:
ld a,$0a
ld (g_cmp),a
ld a,(l_code)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_27
jp layout_after_scan
layout_branch_27:
layout_branch_25:
call layout_glyph_advance
ld hl,(l_line_width)
ld (l_scan_width),hl
ld hl,(l_scan_width)
ld de,(l_advance)
add hl,de
ld (l_scan_width),hl
ld a,$01
ld (g_cmp),a
ld a,(l_mode)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_28
layout_branch_29:
ld a,(l_width+1)
ld (g_cmp),a
ld a,(l_scan_width+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_30
jp nz,layout_after_scan
ld a,(l_width)
ld (g_cmp),a
ld a,(l_scan_width)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_30
jp nz,layout_after_scan
layout_branch_30:
layout_branch_28:
ld a,$20
ld (g_cmp),a
ld a,(l_code)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_31
layout_branch_32:
ld a,$00
ld (g_cmp),a
ld a,(l_count)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_33
jp layout_branch_31
layout_branch_33:
ld a,(l_i)
ld (l_break_i),a
ld a,(l_count)
ld (l_break_count),a
ld hl,(l_line_width)
ld (l_break_width),hl
layout_branch_31:
ld hl,(l_scan_width)
ld (l_line_width),hl
or a
ld a,$01
ld b,a
ld a,(l_count)
adc a,b
ld (l_count),a
or a
ld a,$01
ld b,a
ld a,(l_i)
adc a,b
ld (l_i),a
jp layout_scan
layout_after_scan:
ld a,$01
ld (g_cmp),a
ld a,(l_mode)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_position
layout_branch_34:
ld a,(l_length)
ld (g_cmp),a
ld a,(l_i)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_35
jp layout_position
layout_branch_35:
ld hl,$0000
ld (l_pointer),hl
ld a,(l_i)
ld (l_pointer),a
ld hl,(l_pointer)
ld de,(l_source)
add hl,de
ld (l_pointer),hl
ld hl,(l_pointer)
ld a,(hl)
ld (l_code),a
ld a,$0a
ld (g_cmp),a
ld a,(l_code)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_37
jp layout_branch_36
layout_branch_37:
ld a,$20
ld (g_cmp),a
ld a,(l_code)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_38
jp layout_branch_36
layout_branch_38:
; Backtrack to the last eligible space; otherwise split an overlong word.
ld a,$00
ld (g_cmp),a
ld a,(l_break_count)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_39
jp layout_position
layout_branch_39:
ld a,(l_break_count)
ld (l_count),a
ld a,(l_break_i)
ld (l_i),a
ld hl,(l_break_width)
ld (l_line_width),hl
layout_branch_36:
or a
ld a,$01
ld b,a
ld a,(l_i)
adc a,b
ld (l_i),a
layout_position:
ld a,(l_start)
ld (g_cmp),a
ld a,(l_i)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_40
layout_branch_41:
ld a,$00
ld (g_cmp),a
ld a,(l_count)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_42
jp layout_reject
layout_branch_42:
layout_branch_40:
ld a,$02
ld (g_cmp),a
ld a,(l_mode)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_43
jp layout_measure_done
layout_branch_43:
ld a,(l_width+1)
ld (g_cmp),a
ld a,(l_line_width+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_44
jp nz,layout_reject
ld a,(l_width)
ld (g_cmp),a
ld a,(l_line_width)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_44
jp nz,layout_reject
layout_branch_44:
ld a,(l_measured+1)
ld (g_cmp),a
ld a,(l_line_width+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_45
jp nz,layout_branch_46
ld a,(l_measured)
ld (g_cmp),a
ld a,(l_line_width)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_45
jp z,layout_branch_45
layout_branch_46:
ld hl,(l_line_width)
ld (l_measured),hl
layout_branch_45:
ld hl,(l_width)
ld (l_temp),hl
ld hl,(l_temp)
ld de,(l_line_width)
or a
sbc hl,de
ld (l_temp),hl
ld a,$00
ld (g_cmp),a
ld a,(l_align)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_49
jp layout_branch_47
layout_branch_49:
ld a,$02
ld (g_cmp),a
ld a,(l_align)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_50
jp layout_branch_48
layout_branch_50:
ld hl,l_temp+1
srl (hl)
dec hl
rr (hl)
jp layout_branch_48
layout_branch_47:
ld hl,$0000
ld (l_temp),hl
layout_branch_48:
; BEGIN ORIGIN FIX: the unused-space offset is relative to the rectangle.
ld hl,(l_left)
ld (l_x),hl
ld hl,(l_x)
ld de,(l_temp)
add hl,de
ld (l_x),hl
; END ORIGIN FIX
ld a,$00
ld (g_cmp),a
ld a,(l_lines)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_51
layout_branch_52:
ld hl,(l_x)
ld (l_first_x),hl
layout_branch_51:
ld hl,(l_x)
ld (l_last_x),hl
ld hl,(l_last_x)
ld de,(l_line_width)
add hl,de
ld (l_last_x),hl
ld hl,(l_y)
ld (l_last_y),hl
ld a,(l_count)
ld (l_last_count),a
or a
ld a,$01
ld b,a
ld a,(l_lines)
adc a,b
ld (l_lines),a
ld a,$00
ld (g_cmp),a
ld a,(l_pass)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_53
jp layout_advance
layout_branch_53:
ld a,(l_start)
ld (l_draw_i),a
layout_draw_glyph:
ld hl,$0000
ld (l_temp),hl
ld a,(l_draw_i)
ld (l_temp),a
ld hl,$0000
ld (l_pointer),hl
ld a,(l_start)
ld (l_pointer),a
ld hl,$0000
ld (l_advance),hl
ld a,(l_count)
ld (l_advance),a
ld hl,(l_pointer)
ld de,(l_advance)
add hl,de
ld (l_pointer),hl
ld a,(l_pointer+1)
ld (g_cmp),a
ld a,(l_temp+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_54
jp nz,layout_advance
ld a,(l_pointer)
ld (g_cmp),a
ld a,(l_temp)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_54
jp layout_advance
layout_branch_54:
ld hl,(l_source)
ld (l_pointer),hl
ld hl,(l_pointer)
ld de,(l_temp)
add hl,de
ld (l_pointer),hl
call layout_read_pointer
ld (l_code),a
call layout_glyph_advance
ld hl,(l_x)
ld (t_x),hl
ld hl,(l_y)
ld (t_y),hl
ld hl,(l_x)
ld (t_clip_l),hl
ld hl,(l_x)
ld (t_clip_r),hl
ld hl,(t_clip_r)
ld de,(l_advance)
add hl,de
ld (t_clip_r),hl
ld a,(l_glyph)
ld (t_code),a
call text_char
ld hl,(l_calls)
ld de,(t_calls)
add hl,de
ld (l_calls),hl
or a
ld a,(t_writes)
ld b,a
ld a,(l_writes)
adc a,b
ld (l_writes),a
ld a,(t_writes+1)
ld b,a
ld a,(l_writes+1)
adc a,b
ld (l_writes+1),a
ld a,(t_writes+2)
ld b,a
ld a,(l_writes+2)
adc a,b
ld (l_writes+2),a
ld a,(t_writes+3)
ld b,a
ld a,(l_writes+3)
adc a,b
ld (l_writes+3),a
; BEGIN ADVANCE FIX: move by this glyph metric plus letter spacing.
ld hl,(l_x)
ld de,(l_advance)
add hl,de
ld (l_x),hl
; END ADVANCE FIX
or a
ld a,$01
ld b,a
ld a,(l_draw_i)
adc a,b
ld (l_draw_i),a
jp layout_draw_glyph
layout_advance:
ld hl,$0000
ld (l_temp),hl
ld a,(l_pitch)
ld (l_temp),a
ld hl,(l_y)
ld de,(l_temp)
add hl,de
ld (l_y),hl
jp layout_next
layout_pass_done:
ld a,$00
ld (g_cmp),a
ld a,(l_pass)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp nz,layout_branch_55
layout_branch_56:
ld a,$01
ld (l_pass),a
jp layout_pass
layout_branch_55:
ld a,(l_length)
ld (l_consumed),a
ld a,$01
ld (l_status),a
ret
layout_measure_done:
ld hl,(l_line_width)
ld (l_measured),hl
ld a,$01
ld (l_status),a
ret
layout_reject:
call layout_reset
ret
layout_glyph_advance:
ld a,(l_code)
ld (l_glyph),a
ld a,(t_first)
ld (g_cmp),a
ld a,(l_glyph)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_58
layout_branch_59:
ld hl,$0000
ld (l_temp),hl
ld a,(l_glyph)
ld (l_temp),a
ld hl,$0000
ld (l_pointer),hl
ld a,(t_first)
ld (l_pointer),a
ld hl,(l_temp)
ld de,(l_pointer)
or a
sbc hl,de
ld (l_temp),hl
ld a,(t_count+1)
ld (g_cmp),a
ld a,(l_temp+1)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_57
jp nz,layout_branch_60
ld a,(t_count)
ld (g_cmp),a
ld a,(l_temp)
ld b,a
ld a,(g_cmp)
ld c,a
ld a,b
cp c
jp c,layout_branch_57
layout_branch_60:
layout_branch_58:
ld a,(t_fallback)
ld (l_glyph),a
ld hl,$0000
ld (l_temp),hl
ld a,(l_glyph)
ld (l_temp),a
ld hl,$0000
ld (l_pointer),hl
ld a,(t_first)
ld (l_pointer),a
ld hl,(l_temp)
ld de,(l_pointer)
or a
sbc hl,de
ld (l_temp),hl
layout_branch_57:
ld hl,(l_metrics)
ld (l_pointer),hl
ld hl,(l_pointer)
ld de,(l_temp)
add hl,de
ld (l_pointer),hl
call layout_read_pointer
ld (l_advance),a
ld a,$00
ld (l_advance+1),a
ld hl,$0000
ld (l_temp),hl
ld a,(l_spacing)
ld (l_temp),a
ld hl,(l_advance)
ld de,(l_temp)
add hl,de
ld (l_advance),hl
ret
layout_read_pointer:
ld hl,(l_pointer)
ld a,(hl)
ret
layout_end:Advance sums and rounding explain why measuring and drawing must agree; local-to-screen coordinates explain how those advances become glyph origins. Integer enlargement and other transforms remain further variants.