Skip to content

Measure, Align and Wrap Text on BBC Micro MODE 4

Measure and align labels, choose character or word wrapping, and give narrow letters less room with proportional text.

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.

An original READY TO PLAY prompt and PRESS FIRE TO BEGIN line centred within the declared panel on BBC Micro MODE 4.
The complete native reset caller: a 176-pixel panel beginning at x21, two centred lines, and a 12-scanline pitch. Original Code198x font and message; Emu198x 0.41.0.

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 4 gives this caller 320×256 pixels. The 512-byte low/high row tables still handle the interleaved screen. BP stays in zero page; the larger text and layout contexts live in ordinary RAM.

Reserve 64 bytes at TL=$0480, 96 at T=$0400 and 48 at BP=$0040 for this caller. Keep them separate from source, font, bitmap, code and stack. Include the unchanged bitmap-text component before text-layout/6502.inc; the layout uses its aliases and renderer. Keep the existing backend and its helper/table plus 512 bytes of BBC scanline tables. 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

prepare.inc
; BEGIN LAYOUT INPUTS
    lda #<(PANEL_LEFT)
    sta TL+0
    lda #>(PANEL_LEFT)
    sta TL+1
    lda #<(PANEL_TOP)
    sta TL+2
    lda #>(PANEL_TOP)
    sta TL+3
    lda #<(PANEL_WIDTH)
    sta TL+4
    lda #>(PANEL_WIDTH)
    sta TL+5
    lda #<(PANEL_HEIGHT)
    sta TL+6
    lda #>(PANEL_HEIGHT)
    sta TL+7
    lda #<(label_data)
    sta TL+10
    lda #>(label_data)
    sta TL+11
    lda #PANEL_ALIGN
    sta TL+8
    lda #LINE_PITCH
    sta TL+9
    lda #MESSAGE_LENGTH
    sta TL+12
    jsr layout_block
; END LAYOUT INPUTS

Use 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

6502.inc, excerpt
layout_measure:
    jsr layout_reset
    lda #$00
    sta l_measured
    lda #$00
    sta l_measured+1
    lda l_length
    sta l_measured
    asl l_measured
    rol l_measured+1
    asl l_measured
    rol l_measured+1
    asl l_measured
    rol l_measured+1
    lda #$01
    sta l_status
    rts

Five 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

customise.asm
CUSTOM_LAYOUT = 1
; SPDX-License-Identifier: MIT
; Complete caller: change layout inputs without changing the reusable renderer.
PANEL_LEFT = 21
PANEL_TOP = 37
PANEL_WIDTH = 104
PANEL_HEIGHT = 80
PANEL_ALIGN = 2
LINE_PITCH = 16
MESSAGE_LENGTH = 44
    !source "caller.inc"
The custom message wraps into five right-aligned lines inside a narrower panel on BBC Micro MODE 4.
Verified native customisation using the unchanged layout and drawing routines. Width changes the line count; pitch changes the vertical footprint.

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 767 1117
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

6502-width.inc, excerpt
; BEGIN WRONG
    asl l_line_width
    rol l_line_width+1
    asl l_line_width
    rol l_line_width+1
The diagnostic READY label is shifted right because its measured width is too small.
Native width-error symptom. The independent full-memory and pixel checks detect it.

Restore the third shift and check that five codes measure 40 pixels:

Correct: match the renderer's eight-pixel advance

6502-width.inc, excerpt
; BEGIN FIX
    asl l_line_width
    rol l_line_width+1
    asl l_line_width
    rol l_line_width+1
    asl l_line_width
    rol l_line_width+1

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

6502-origin.inc, excerpt
; BEGIN WRONG
    lda l_temp
    sta l_x
    lda l_temp+1
    sta l_x+1
READY moves left because the diagnostic omits the panel's x21 origin.
The same source, font and panel, with the rectangle origin omitted.

Add the panel origin to the alignment offset:

Correct: translate from panel to screen coordinates

6502-origin.inc, excerpt
; BEGIN FIX
    lda l_left
    sta l_x
    lda l_left+1
    sta l_x+1
    clc
    lda l_x
    adc l_temp
    sta l_x
    lda l_x+1
    adc l_temp+1
    sta l_x+1
The corrected READY label begins at x89 within the panel.
Both repaired diagnostic callers reproduce this correct native frame.

Costs, ownership and results

The layout adds 1,867 code bytes and 64 context bytes. The reused text renderer is 2,456 bytes and its backend 526; their 96+48 context bytes, the 512-byte sample font, messages, video allocation plus 512 bytes of BBC scanline tables and stack are separate. Reserve a conservative 80-byte stack budget plus caller/interrupt depth. Keep the 6502 decimal flag clear. An interrupt that changes it breaks the arithmetic contract.

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

6502.inc
; 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 = TL+0
l_top = TL+2
l_width = TL+4
l_height = TL+6
l_align = TL+8
l_pitch = TL+9
l_source = TL+10
l_length = TL+12
l_status = TL+13
l_consumed = TL+14
l_lines = TL+15
l_last_count = TL+16
l_measured = TL+18
l_first_x = TL+20
l_last_x = TL+22
l_last_y = TL+24
l_calls = TL+26
l_writes = TL+28
l_mode = TL+32
l_pass = TL+33
l_i = TL+34
l_start = TL+35
l_count = TL+36
l_columns = TL+37
l_temp = TL+38
l_right = TL+40
l_bottom = TL+42
l_y = TL+44
l_x = TL+46
l_pointer = TL+48
l_code = TL+50
l_line_width = TL+52
l_work = TL+54
layout_measure:
    jsr layout_reset
    lda #$00
    sta l_measured
    lda #$00
    sta l_measured+1
    lda l_length
    sta l_measured
    asl l_measured
    rol l_measured+1
    asl l_measured
    rol l_measured+1
    asl l_measured
    rol l_measured+1
    lda #$01
    sta l_status
    rts
layout_line:
    lda #$00
    sta l_mode
    jmp layout_enter
layout_block:
    lda #$01
    sta l_mode
    jmp layout_enter
layout_reset:
    lda #$00
    sta l_status
    lda #$00
    sta l_consumed
    lda #$00
    sta l_lines
    lda #$00
    sta l_last_count
    lda #$00
    sta l_measured
    lda #$00
    sta l_measured+1
    lda #$00
    sta l_first_x
    lda #$00
    sta l_first_x+1
    lda #$00
    sta l_last_x
    lda #$00
    sta l_last_x+1
    lda #$00
    sta l_last_y
    lda #$00
    sta l_last_y+1
    lda #$00
    sta l_calls
    lda #$00
    sta l_calls+1
    lda #$00
    sta l_writes
    lda #$00
    sta l_writes+1
    lda #$00
    sta l_writes+2
    lda #$00
    sta l_writes+3
    rts
layout_enter:
    jsr layout_reset
; Validate geometry before additions, so huge unsigned values cannot wrap into range.
    lda #$00
    sta g_cmp
    lda l_width+1
    cmp g_cmp
    bcs layout_branch_2
    jmp layout_reject
layout_branch_2:
    beq layout_branch_3
    jmp layout_branch_1
layout_branch_3:
    lda #$08
    sta g_cmp
    lda l_width
    cmp g_cmp
    bcs layout_branch_4
    jmp layout_reject
layout_branch_4:
layout_branch_1:
    lda #$00
    sta g_cmp
    lda l_height+1
    cmp g_cmp
    bcs layout_branch_6
    jmp layout_reject
layout_branch_6:
    beq layout_branch_7
    jmp layout_branch_5
layout_branch_7:
    lda #$08
    sta g_cmp
    lda l_height
    cmp g_cmp
    bcs layout_branch_8
    jmp layout_reject
layout_branch_8:
layout_branch_5:
    lda #<TEXT_WIDTH
    sta l_temp
    lda #>TEXT_WIDTH
    sta l_temp+1
    lda l_temp+1
    sta g_cmp
    lda l_width+1
    cmp g_cmp
    bcs layout_branch_10
    jmp layout_branch_9
layout_branch_10:
    beq layout_branch_11
    jmp layout_reject
layout_branch_11:
    lda l_temp
    sta g_cmp
    lda l_width
    cmp g_cmp
    bcs layout_branch_12
    jmp layout_branch_9
layout_branch_12:
    beq layout_branch_13
    jmp layout_reject
layout_branch_13:
layout_branch_9:
    lda #<TEXT_HEIGHT
    sta l_temp
    lda #>TEXT_HEIGHT
    sta l_temp+1
    lda l_temp+1
    sta g_cmp
    lda l_height+1
    cmp g_cmp
    bcs layout_branch_15
    jmp layout_branch_14
layout_branch_15:
    beq layout_branch_16
    jmp layout_reject
layout_branch_16:
    lda l_temp
    sta g_cmp
    lda l_height
    cmp g_cmp
    bcs layout_branch_17
    jmp layout_branch_14
layout_branch_17:
    beq layout_branch_18
    jmp layout_reject
layout_branch_18:
layout_branch_14:
    lda #<TEXT_WIDTH
    sta l_temp
    lda #>TEXT_WIDTH
    sta l_temp+1
    lda l_temp+1
    sta g_cmp
    lda l_left+1
    cmp g_cmp
    bcs layout_branch_20
    jmp layout_branch_19
layout_branch_20:
    beq layout_branch_21
    jmp layout_reject
layout_branch_21:
    lda l_temp
    sta g_cmp
    lda l_left
    cmp g_cmp
    bcs layout_branch_22
    jmp layout_branch_19
layout_branch_22:
    beq layout_branch_23
    jmp layout_reject
layout_branch_23:
layout_branch_19:
    lda #<TEXT_HEIGHT
    sta l_temp
    lda #>TEXT_HEIGHT
    sta l_temp+1
    lda l_temp+1
    sta g_cmp
    lda l_top+1
    cmp g_cmp
    bcs layout_branch_25
    jmp layout_branch_24
layout_branch_25:
    beq layout_branch_26
    jmp layout_reject
layout_branch_26:
    lda l_temp
    sta g_cmp
    lda l_top
    cmp g_cmp
    bcs layout_branch_27
    jmp layout_branch_24
layout_branch_27:
    beq layout_branch_28
    jmp layout_reject
layout_branch_28:
layout_branch_24:
    lda l_left
    sta l_right
    lda l_left+1
    sta l_right+1
    clc
    lda l_right
    adc l_width
    sta l_right
    lda l_right+1
    adc l_width+1
    sta l_right+1
    lda #<TEXT_WIDTH
    sta l_temp
    lda #>TEXT_WIDTH
    sta l_temp+1
    lda l_temp+1
    sta g_cmp
    lda l_right+1
    cmp g_cmp
    bcs layout_branch_30
    jmp layout_branch_29
layout_branch_30:
    beq layout_branch_31
    jmp layout_reject
layout_branch_31:
    lda l_temp
    sta g_cmp
    lda l_right
    cmp g_cmp
    bcs layout_branch_32
    jmp layout_branch_29
layout_branch_32:
    beq layout_branch_33
    jmp layout_reject
layout_branch_33:
layout_branch_29:
    lda l_top
    sta l_bottom
    lda l_top+1
    sta l_bottom+1
    clc
    lda l_bottom
    adc l_height
    sta l_bottom
    lda l_bottom+1
    adc l_height+1
    sta l_bottom+1
    lda #<TEXT_HEIGHT
    sta l_temp
    lda #>TEXT_HEIGHT
    sta l_temp+1
    lda l_temp+1
    sta g_cmp
    lda l_bottom+1
    cmp g_cmp
    bcs layout_branch_35
    jmp layout_branch_34
layout_branch_35:
    beq layout_branch_36
    jmp layout_reject
layout_branch_36:
    lda l_temp
    sta g_cmp
    lda l_bottom
    cmp g_cmp
    bcs layout_branch_37
    jmp layout_branch_34
layout_branch_37:
    beq layout_branch_38
    jmp layout_reject
layout_branch_38:
layout_branch_34:
    lda #$02
    sta g_cmp
    lda l_align
    cmp g_cmp
    bcs layout_branch_40
    jmp layout_branch_39
layout_branch_40:
    beq layout_branch_41
    jmp layout_reject
layout_branch_41:
layout_branch_39:
    lda #$08
    sta g_cmp
    lda l_pitch
    cmp g_cmp
    bcs layout_branch_43
    jmp layout_reject
layout_branch_43:
layout_branch_42:
    lda #$00
    sta l_temp
    lda #$00
    sta l_temp+1
    lda l_length
    sta l_temp
    clc
    lda l_temp
    adc l_source
    sta l_temp
    lda l_temp+1
    adc l_source+1
    sta l_temp+1
    lda #$00
    sta g_cmp
    lda l_temp+1
    cmp g_cmp
    beq layout_branch_46
    jmp layout_branch_45
layout_branch_46:
    lda #$00
    sta g_cmp
    lda l_temp
    cmp g_cmp
    beq layout_branch_47
    jmp layout_branch_45
layout_branch_47:
    jmp layout_branch_44
layout_branch_45:
    lda l_source+1
    sta g_cmp
    lda l_temp+1
    cmp g_cmp
    bcs layout_branch_49
    jmp layout_reject
layout_branch_49:
    beq layout_branch_50
    jmp layout_branch_48
layout_branch_50:
    lda l_source
    sta g_cmp
    lda l_temp
    cmp g_cmp
    bcs layout_branch_51
    jmp layout_reject
layout_branch_51:
layout_branch_48:
layout_branch_44:
    lda l_width
    sta l_temp
    lda l_width+1
    sta l_temp+1
    lsr l_temp+1
    ror l_temp
    lsr l_temp+1
    ror l_temp
    lsr l_temp+1
    ror l_temp
    lda l_temp
    sta l_columns
; An empty renderer call validates font, pen and source declarations without drawing.
    lda l_left
    sta t_x
    lda l_left+1
    sta t_x+1
    lda l_top
    sta t_y
    lda l_top+1
    sta t_y+1
    lda l_source
    sta t_source
    lda l_source+1
    sta t_source+1
    lda l_left
    sta t_clip_l
    lda l_left+1
    sta t_clip_l+1
    lda l_top
    sta t_clip_t
    lda l_top+1
    sta t_clip_t+1
    lda l_right
    sta t_clip_r
    lda l_right+1
    sta t_clip_r+1
    lda l_bottom
    sta t_clip_b
    lda l_bottom+1
    sta t_clip_b+1
    lda #$00
    sta t_length
    jsr text_string
    lda #$00
    sta g_cmp
    lda t_status
    cmp g_cmp
    beq layout_branch_53
    jmp layout_branch_52
layout_branch_53:
    jmp layout_reject
layout_branch_52:
    lda #$00
    sta l_pass
layout_pass:
    lda #$00
    sta l_i
    lda l_top
    sta l_y
    lda l_top+1
    sta l_y+1
    lda #$00
    sta l_lines
    lda #$00
    sta l_measured
    lda #$00
    sta l_measured+1
layout_next:
    lda l_length
    sta g_cmp
    lda l_i
    cmp g_cmp
    bcs layout_branch_55
    jmp layout_branch_54
layout_branch_55:
    jmp layout_pass_done
layout_branch_54:
    lda l_y
    sta l_temp
    lda l_y+1
    sta l_temp+1
    clc
    lda l_temp
    adc #$08
    sta l_temp
    lda l_temp+1
    adc #$00
    sta l_temp+1
    lda l_bottom+1
    sta g_cmp
    lda l_temp+1
    cmp g_cmp
    bcs layout_branch_57
    jmp layout_branch_56
layout_branch_57:
    beq layout_branch_58
    jmp layout_reject
layout_branch_58:
    lda l_bottom
    sta g_cmp
    lda l_temp
    cmp g_cmp
    bcs layout_branch_59
    jmp layout_branch_56
layout_branch_59:
    beq layout_branch_60
    jmp layout_reject
layout_branch_60:
layout_branch_56:
    lda l_i
    sta l_start
    lda #$00
    sta l_count
    lda #$00
    sta g_cmp
    lda l_mode
    cmp g_cmp
    beq layout_branch_62
    jmp layout_scan
layout_branch_62:
layout_branch_61:
    lda l_length
    sta l_count
    lda l_length
    sta l_i
    jmp layout_position
; Character wrapping: consume LF after a full chunk too; never invent a trailing empty row.
layout_scan:
    lda l_length
    sta g_cmp
    lda l_i
    cmp g_cmp
    bcs layout_branch_64
    jmp layout_branch_63
layout_branch_64:
    jmp layout_after_scan
layout_branch_63:
    lda l_columns
    sta g_cmp
    lda l_count
    cmp g_cmp
    bcs layout_branch_66
    jmp layout_branch_65
layout_branch_66:
    jmp layout_after_scan
layout_branch_65:
    lda #$00
    sta l_pointer
    lda #$00
    sta l_pointer+1
    lda l_i
    sta l_pointer
    clc
    lda l_pointer
    adc l_source
    sta l_pointer
    lda l_pointer+1
    adc l_source+1
    sta l_pointer+1
    lda l_pointer
    sta BP+46
    lda l_pointer+1
    sta BP+47
    ldy #0
    lda (BP+46),y
    sta l_code
    lda #$0a
    sta g_cmp
    lda l_code
    cmp g_cmp
    beq layout_branch_68
    jmp layout_branch_67
layout_branch_68:
    jmp layout_after_scan
layout_branch_67:
    clc
    lda l_count
    adc #$01
    sta l_count
    clc
    lda l_i
    adc #$01
    sta l_i
    jmp layout_scan
layout_after_scan:
    lda l_length
    sta g_cmp
    lda l_i
    cmp g_cmp
    bcs layout_branch_70
    jmp layout_branch_69
layout_branch_70:
    jmp layout_position
layout_branch_69:
    lda #$00
    sta l_pointer
    lda #$00
    sta l_pointer+1
    lda l_i
    sta l_pointer
    clc
    lda l_pointer
    adc l_source
    sta l_pointer
    lda l_pointer+1
    adc l_source+1
    sta l_pointer+1
    lda l_pointer
    sta BP+46
    lda l_pointer+1
    sta BP+47
    ldy #0
    lda (BP+46),y
    sta l_code
    lda #$0a
    sta g_cmp
    lda l_code
    cmp g_cmp
    beq layout_branch_72
    jmp layout_position
layout_branch_72:
layout_branch_71:
    clc
    lda l_i
    adc #$01
    sta l_i
layout_position:
    lda #$00
    sta l_line_width
    lda #$00
    sta l_line_width+1
    lda l_count
    sta l_line_width
; BEGIN WIDTH FIX: eight pixels of advance for every counted glyph.
    asl l_line_width
    rol l_line_width+1
    asl l_line_width
    rol l_line_width+1
    asl l_line_width
    rol l_line_width+1
; END WIDTH FIX
    lda l_width+1
    sta g_cmp
    lda l_line_width+1
    cmp g_cmp
    bcs layout_branch_74
    jmp layout_branch_73
layout_branch_74:
    beq layout_branch_75
    jmp layout_reject
layout_branch_75:
    lda l_width
    sta g_cmp
    lda l_line_width
    cmp g_cmp
    bcs layout_branch_76
    jmp layout_branch_73
layout_branch_76:
    beq layout_branch_77
    jmp layout_reject
layout_branch_77:
layout_branch_73:
    lda l_measured+1
    sta g_cmp
    lda l_line_width+1
    cmp g_cmp
    bcs layout_branch_80
    jmp layout_branch_78
layout_branch_80:
    beq layout_branch_81
    jmp layout_branch_79
layout_branch_81:
    lda l_measured
    sta g_cmp
    lda l_line_width
    cmp g_cmp
    bcs layout_branch_82
    jmp layout_branch_78
layout_branch_82:
    bne layout_branch_83
    jmp layout_branch_78
layout_branch_83:
layout_branch_79:
    lda l_line_width
    sta l_measured
    lda l_line_width+1
    sta l_measured+1
layout_branch_78:
    lda l_width
    sta l_temp
    lda l_width+1
    sta l_temp+1
    sec
    lda l_temp
    sbc l_line_width
    sta l_temp
    lda l_temp+1
    sbc l_line_width+1
    sta l_temp+1
    lda #$00
    sta g_cmp
    lda l_align
    cmp g_cmp
    beq layout_branch_87
    jmp layout_branch_86
layout_branch_87:
    jmp layout_branch_84
layout_branch_86:
    lda #$02
    sta g_cmp
    lda l_align
    cmp g_cmp
    beq layout_branch_89
    jmp layout_branch_88
layout_branch_89:
    jmp layout_branch_85
layout_branch_88:
    lsr l_temp+1
    ror l_temp
    jmp layout_branch_85
layout_branch_84:
    lda #$00
    sta l_temp
    lda #$00
    sta l_temp+1
layout_branch_85:
; BEGIN ORIGIN FIX: the unused-space offset is relative to the rectangle.
    lda l_left
    sta l_x
    lda l_left+1
    sta l_x+1
    clc
    lda l_x
    adc l_temp
    sta l_x
    lda l_x+1
    adc l_temp+1
    sta l_x+1
; END ORIGIN FIX
    lda #$00
    sta g_cmp
    lda l_lines
    cmp g_cmp
    beq layout_branch_92
    jmp layout_branch_90
layout_branch_92:
layout_branch_91:
    lda l_x
    sta l_first_x
    lda l_x+1
    sta l_first_x+1
layout_branch_90:
    lda l_x
    sta l_last_x
    lda l_x+1
    sta l_last_x+1
    clc
    lda l_last_x
    adc l_line_width
    sta l_last_x
    lda l_last_x+1
    adc l_line_width+1
    sta l_last_x+1
    lda l_y
    sta l_last_y
    lda l_y+1
    sta l_last_y+1
    lda l_count
    sta l_last_count
    clc
    lda l_lines
    adc #$01
    sta l_lines
    lda #$00
    sta g_cmp
    lda l_pass
    cmp g_cmp
    beq layout_branch_94
    jmp layout_branch_93
layout_branch_94:
    jmp layout_advance
layout_branch_93:
    lda l_x
    sta t_x
    lda l_x+1
    sta t_x+1
    lda l_y
    sta t_y
    lda l_y+1
    sta t_y+1
    lda #$00
    sta t_source
    lda #$00
    sta t_source+1
    lda l_start
    sta t_source
    clc
    lda t_source
    adc l_source
    sta t_source
    lda t_source+1
    adc l_source+1
    sta t_source+1
    lda l_count
    sta t_length
    jsr text_string
    clc
    lda l_calls
    adc t_calls
    sta l_calls
    lda l_calls+1
    adc t_calls+1
    sta l_calls+1
    clc
    lda l_writes
    adc t_writes
    sta l_writes
    lda l_writes+1
    adc t_writes+1
    sta l_writes+1
    lda l_writes+2
    adc t_writes+2
    sta l_writes+2
    lda l_writes+3
    adc t_writes+3
    sta l_writes+3
layout_advance:
    lda #$00
    sta l_temp
    lda #$00
    sta l_temp+1
    lda l_pitch
    sta l_temp
    clc
    lda l_y
    adc l_temp
    sta l_y
    lda l_y+1
    adc l_temp+1
    sta l_y+1
    jmp layout_next
layout_pass_done:
    lda #$00
    sta g_cmp
    lda l_pass
    cmp g_cmp
    beq layout_branch_97
    jmp layout_branch_95
layout_branch_97:
layout_branch_96:
    lda #$01
    sta l_pass
    jmp layout_pass
layout_branch_95:
    lda l_length
    sta l_consumed
    lda #$01
    sta l_status
    rts
layout_reject:
    jsr layout_reset
    rts
layout_end:

The source and retained records contain 48 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/6502.inc variant over the same bitmap-text renderer. It adds 105 code bytes (1,972 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 8 cells. 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

prepare.inc
; BEGIN LAYOUT INPUTS
    lda #<(PANEL_LEFT)
    sta TL+0
    lda #>(PANEL_LEFT)
    sta TL+1
    lda #<(PANEL_TOP)
    sta TL+2
    lda #>(PANEL_TOP)
    sta TL+3
    lda #<(PANEL_WIDTH)
    sta TL+4
    lda #>(PANEL_WIDTH)
    sta TL+5
    lda #<(PANEL_HEIGHT)
    sta TL+6
    lda #>(PANEL_HEIGHT)
    sta TL+7
    lda #<(label_data)
    sta TL+10
    lda #>(label_data)
    sta TL+11
    lda #PANEL_ALIGN
    sta TL+8
    lda #LINE_PITCH
    sta TL+9
    lda #MESSAGE_LENGTH
    sta TL+12
    jsr layout_block
; END LAYOUT INPUTS

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. 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

customise.asm
CUSTOM_LAYOUT = 1
; SPDX-License-Identifier: MIT
; Complete caller: change layout inputs without changing the reusable renderer.
PANEL_LEFT = 21
PANEL_TOP = 37
PANEL_WIDTH = 104
PANEL_HEIGHT = 80
PANEL_ALIGN = 2
LINE_PITCH = 16
MESSAGE_LENGTH = 44
    !source "caller.inc"
The custom message wraps into five right-aligned lines inside a narrower panel on BBC Micro MODE 4.
Verified native customisation using the unchanged layout and drawing routines. The narrow panel keeps words together; pitch controls the vertical footprint.

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

6502-break.inc, excerpt
; BEGIN WRONG
    jmp layout_position ; WRONG: discard the saved word boundary
ONE THREE FIVE is cut into ONE THR and EE FIVE despite a usable word boundary.
The faulty word-break branch completes its native call but splits words unnecessarily.

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

6502-break.inc, excerpt
; BEGIN FIX
    lda l_break_count
    cmp g_cmp
    beq layout_branch_82
    jmp layout_branch_81
layout_branch_82:
    jmp layout_position
layout_branch_81:
    lda l_break_count
    sta l_count
    lda l_break_i
    sta l_i
ONE, THREE and FIVE occupy three complete centred lines.
The repaired native caller keeps each word whole. The full bitmap and outputs match the independent expectation.

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,117 to 1,085. 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 63 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

6502.inc
; 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 = TL+0
l_top = TL+2
l_width = TL+4
l_height = TL+6
l_align = TL+8
l_pitch = TL+9
l_source = TL+10
l_length = TL+12
l_status = TL+13
l_consumed = TL+14
l_lines = TL+15
l_last_count = TL+16
l_measured = TL+18
l_first_x = TL+20
l_last_x = TL+22
l_last_y = TL+24
l_calls = TL+26
l_writes = TL+28
l_mode = TL+32
l_pass = TL+33
l_i = TL+34
l_start = TL+35
l_count = TL+36
l_columns = TL+37
l_temp = TL+38
l_right = TL+40
l_bottom = TL+42
l_y = TL+44
l_x = TL+46
l_pointer = TL+48
l_code = TL+50
l_line_width = TL+52
l_work = TL+54
l_break_i = TL+58
l_break_count = TL+59
layout_measure:
    jsr layout_reset
    lda #$00
    sta l_measured
    lda #$00
    sta l_measured+1
    lda l_length
    sta l_measured
    asl l_measured
    rol l_measured+1
    asl l_measured
    rol l_measured+1
    asl l_measured
    rol l_measured+1
    lda #$01
    sta l_status
    rts
layout_line:
    lda #$00
    sta l_mode
    jmp layout_enter
layout_block:
    lda #$01
    sta l_mode
    jmp layout_enter
layout_reset:
    lda #$00
    sta l_status
    lda #$00
    sta l_consumed
    lda #$00
    sta l_lines
    lda #$00
    sta l_last_count
    lda #$00
    sta l_measured
    lda #$00
    sta l_measured+1
    lda #$00
    sta l_first_x
    lda #$00
    sta l_first_x+1
    lda #$00
    sta l_last_x
    lda #$00
    sta l_last_x+1
    lda #$00
    sta l_last_y
    lda #$00
    sta l_last_y+1
    lda #$00
    sta l_calls
    lda #$00
    sta l_calls+1
    lda #$00
    sta l_writes
    lda #$00
    sta l_writes+1
    lda #$00
    sta l_writes+2
    lda #$00
    sta l_writes+3
    rts
layout_enter:
    jsr layout_reset
; Validate geometry before additions, so huge unsigned values cannot wrap into range.
    lda #$00
    sta g_cmp
    lda l_width+1
    cmp g_cmp
    bcs layout_branch_2
    jmp layout_reject
layout_branch_2:
    beq layout_branch_3
    jmp layout_branch_1
layout_branch_3:
    lda #$08
    sta g_cmp
    lda l_width
    cmp g_cmp
    bcs layout_branch_4
    jmp layout_reject
layout_branch_4:
layout_branch_1:
    lda #$00
    sta g_cmp
    lda l_height+1
    cmp g_cmp
    bcs layout_branch_6
    jmp layout_reject
layout_branch_6:
    beq layout_branch_7
    jmp layout_branch_5
layout_branch_7:
    lda #$08
    sta g_cmp
    lda l_height
    cmp g_cmp
    bcs layout_branch_8
    jmp layout_reject
layout_branch_8:
layout_branch_5:
    lda #<TEXT_WIDTH
    sta l_temp
    lda #>TEXT_WIDTH
    sta l_temp+1
    lda l_temp+1
    sta g_cmp
    lda l_width+1
    cmp g_cmp
    bcs layout_branch_10
    jmp layout_branch_9
layout_branch_10:
    beq layout_branch_11
    jmp layout_reject
layout_branch_11:
    lda l_temp
    sta g_cmp
    lda l_width
    cmp g_cmp
    bcs layout_branch_12
    jmp layout_branch_9
layout_branch_12:
    beq layout_branch_13
    jmp layout_reject
layout_branch_13:
layout_branch_9:
    lda #<TEXT_HEIGHT
    sta l_temp
    lda #>TEXT_HEIGHT
    sta l_temp+1
    lda l_temp+1
    sta g_cmp
    lda l_height+1
    cmp g_cmp
    bcs layout_branch_15
    jmp layout_branch_14
layout_branch_15:
    beq layout_branch_16
    jmp layout_reject
layout_branch_16:
    lda l_temp
    sta g_cmp
    lda l_height
    cmp g_cmp
    bcs layout_branch_17
    jmp layout_branch_14
layout_branch_17:
    beq layout_branch_18
    jmp layout_reject
layout_branch_18:
layout_branch_14:
    lda #<TEXT_WIDTH
    sta l_temp
    lda #>TEXT_WIDTH
    sta l_temp+1
    lda l_temp+1
    sta g_cmp
    lda l_left+1
    cmp g_cmp
    bcs layout_branch_20
    jmp layout_branch_19
layout_branch_20:
    beq layout_branch_21
    jmp layout_reject
layout_branch_21:
    lda l_temp
    sta g_cmp
    lda l_left
    cmp g_cmp
    bcs layout_branch_22
    jmp layout_branch_19
layout_branch_22:
    beq layout_branch_23
    jmp layout_reject
layout_branch_23:
layout_branch_19:
    lda #<TEXT_HEIGHT
    sta l_temp
    lda #>TEXT_HEIGHT
    sta l_temp+1
    lda l_temp+1
    sta g_cmp
    lda l_top+1
    cmp g_cmp
    bcs layout_branch_25
    jmp layout_branch_24
layout_branch_25:
    beq layout_branch_26
    jmp layout_reject
layout_branch_26:
    lda l_temp
    sta g_cmp
    lda l_top
    cmp g_cmp
    bcs layout_branch_27
    jmp layout_branch_24
layout_branch_27:
    beq layout_branch_28
    jmp layout_reject
layout_branch_28:
layout_branch_24:
    lda l_left
    sta l_right
    lda l_left+1
    sta l_right+1
    clc
    lda l_right
    adc l_width
    sta l_right
    lda l_right+1
    adc l_width+1
    sta l_right+1
    lda #<TEXT_WIDTH
    sta l_temp
    lda #>TEXT_WIDTH
    sta l_temp+1
    lda l_temp+1
    sta g_cmp
    lda l_right+1
    cmp g_cmp
    bcs layout_branch_30
    jmp layout_branch_29
layout_branch_30:
    beq layout_branch_31
    jmp layout_reject
layout_branch_31:
    lda l_temp
    sta g_cmp
    lda l_right
    cmp g_cmp
    bcs layout_branch_32
    jmp layout_branch_29
layout_branch_32:
    beq layout_branch_33
    jmp layout_reject
layout_branch_33:
layout_branch_29:
    lda l_top
    sta l_bottom
    lda l_top+1
    sta l_bottom+1
    clc
    lda l_bottom
    adc l_height
    sta l_bottom
    lda l_bottom+1
    adc l_height+1
    sta l_bottom+1
    lda #<TEXT_HEIGHT
    sta l_temp
    lda #>TEXT_HEIGHT
    sta l_temp+1
    lda l_temp+1
    sta g_cmp
    lda l_bottom+1
    cmp g_cmp
    bcs layout_branch_35
    jmp layout_branch_34
layout_branch_35:
    beq layout_branch_36
    jmp layout_reject
layout_branch_36:
    lda l_temp
    sta g_cmp
    lda l_bottom
    cmp g_cmp
    bcs layout_branch_37
    jmp layout_branch_34
layout_branch_37:
    beq layout_branch_38
    jmp layout_reject
layout_branch_38:
layout_branch_34:
    lda #$02
    sta g_cmp
    lda l_align
    cmp g_cmp
    bcs layout_branch_40
    jmp layout_branch_39
layout_branch_40:
    beq layout_branch_41
    jmp layout_reject
layout_branch_41:
layout_branch_39:
    lda #$08
    sta g_cmp
    lda l_pitch
    cmp g_cmp
    bcs layout_branch_43
    jmp layout_reject
layout_branch_43:
layout_branch_42:
    lda #$00
    sta l_temp
    lda #$00
    sta l_temp+1
    lda l_length
    sta l_temp
    clc
    lda l_temp
    adc l_source
    sta l_temp
    lda l_temp+1
    adc l_source+1
    sta l_temp+1
    lda #$00
    sta g_cmp
    lda l_temp+1
    cmp g_cmp
    beq layout_branch_46
    jmp layout_branch_45
layout_branch_46:
    lda #$00
    sta g_cmp
    lda l_temp
    cmp g_cmp
    beq layout_branch_47
    jmp layout_branch_45
layout_branch_47:
    jmp layout_branch_44
layout_branch_45:
    lda l_source+1
    sta g_cmp
    lda l_temp+1
    cmp g_cmp
    bcs layout_branch_49
    jmp layout_reject
layout_branch_49:
    beq layout_branch_50
    jmp layout_branch_48
layout_branch_50:
    lda l_source
    sta g_cmp
    lda l_temp
    cmp g_cmp
    bcs layout_branch_51
    jmp layout_reject
layout_branch_51:
layout_branch_48:
layout_branch_44:
    lda l_width
    sta l_temp
    lda l_width+1
    sta l_temp+1
    lsr l_temp+1
    ror l_temp
    lsr l_temp+1
    ror l_temp
    lsr l_temp+1
    ror l_temp
    lda l_temp
    sta l_columns
; An empty renderer call validates font, pen and source declarations without drawing.
    lda l_left
    sta t_x
    lda l_left+1
    sta t_x+1
    lda l_top
    sta t_y
    lda l_top+1
    sta t_y+1
    lda l_source
    sta t_source
    lda l_source+1
    sta t_source+1
    lda l_left
    sta t_clip_l
    lda l_left+1
    sta t_clip_l+1
    lda l_top
    sta t_clip_t
    lda l_top+1
    sta t_clip_t+1
    lda l_right
    sta t_clip_r
    lda l_right+1
    sta t_clip_r+1
    lda l_bottom
    sta t_clip_b
    lda l_bottom+1
    sta t_clip_b+1
    lda #$00
    sta t_length
    jsr text_string
    lda #$00
    sta g_cmp
    lda t_status
    cmp g_cmp
    beq layout_branch_53
    jmp layout_branch_52
layout_branch_53:
    jmp layout_reject
layout_branch_52:
    lda #$00
    sta l_pass
layout_pass:
    lda #$00
    sta l_i
    lda l_top
    sta l_y
    lda l_top+1
    sta l_y+1
    lda #$00
    sta l_lines
    lda #$00
    sta l_measured
    lda #$00
    sta l_measured+1
layout_next:
    lda l_length
    sta g_cmp
    lda l_i
    cmp g_cmp
    bcs layout_branch_55
    jmp layout_branch_54
layout_branch_55:
    jmp layout_pass_done
layout_branch_54:
    lda l_y
    sta l_temp
    lda l_y+1
    sta l_temp+1
    clc
    lda l_temp
    adc #$08
    sta l_temp
    lda l_temp+1
    adc #$00
    sta l_temp+1
    lda l_bottom+1
    sta g_cmp
    lda l_temp+1
    cmp g_cmp
    bcs layout_branch_57
    jmp layout_branch_56
layout_branch_57:
    beq layout_branch_58
    jmp layout_reject
layout_branch_58:
    lda l_bottom
    sta g_cmp
    lda l_temp
    cmp g_cmp
    bcs layout_branch_59
    jmp layout_branch_56
layout_branch_59:
    beq layout_branch_60
    jmp layout_reject
layout_branch_60:
layout_branch_56:
    lda l_i
    sta l_start
    lda #$00
    sta l_count
    lda #$00
    sta l_break_count
    lda #$00
    sta g_cmp
    lda l_mode
    cmp g_cmp
    beq layout_branch_62
    jmp layout_scan
layout_branch_62:
layout_branch_61:
    lda l_length
    sta l_count
    lda l_length
    sta l_i
    jmp layout_position
; Scan a word-aware row: consume LF after a full chunk too; never invent a trailing empty row.
layout_scan:
    lda l_length
    sta g_cmp
    lda l_i
    cmp g_cmp
    bcs layout_branch_64
    jmp layout_branch_63
layout_branch_64:
    jmp layout_after_scan
layout_branch_63:
    lda l_columns
    sta g_cmp
    lda l_count
    cmp g_cmp
    bcs layout_branch_66
    jmp layout_branch_65
layout_branch_66:
    jmp layout_after_scan
layout_branch_65:
    lda #$00
    sta l_pointer
    lda #$00
    sta l_pointer+1
    lda l_i
    sta l_pointer
    clc
    lda l_pointer
    adc l_source
    sta l_pointer
    lda l_pointer+1
    adc l_source+1
    sta l_pointer+1
    lda l_pointer
    sta BP+46
    lda l_pointer+1
    sta BP+47
    ldy #0
    lda (BP+46),y
    sta l_code
    lda #$0a
    sta g_cmp
    lda l_code
    cmp g_cmp
    beq layout_branch_68
    jmp layout_branch_67
layout_branch_68:
    jmp layout_after_scan
layout_branch_67:
    lda #$20
    sta g_cmp
    lda l_code
    cmp g_cmp
    beq layout_branch_71
    jmp layout_branch_69
layout_branch_71:
layout_branch_70:
    lda #$00
    sta g_cmp
    lda l_count
    cmp g_cmp
    beq layout_branch_73
    jmp layout_branch_72
layout_branch_73:
    jmp layout_branch_69
layout_branch_72:
    lda l_i
    sta l_break_i
    lda l_count
    sta l_break_count
layout_branch_69:
    clc
    lda l_count
    adc #$01
    sta l_count
    clc
    lda l_i
    adc #$01
    sta l_i
    jmp layout_scan
layout_after_scan:
    lda l_length
    sta g_cmp
    lda l_i
    cmp g_cmp
    bcs layout_branch_75
    jmp layout_branch_74
layout_branch_75:
    jmp layout_position
layout_branch_74:
    lda #$00
    sta l_pointer
    lda #$00
    sta l_pointer+1
    lda l_i
    sta l_pointer
    clc
    lda l_pointer
    adc l_source
    sta l_pointer
    lda l_pointer+1
    adc l_source+1
    sta l_pointer+1
    lda l_pointer
    sta BP+46
    lda l_pointer+1
    sta BP+47
    ldy #0
    lda (BP+46),y
    sta l_code
    lda #$0a
    sta g_cmp
    lda l_code
    cmp g_cmp
    beq layout_branch_78
    jmp layout_branch_77
layout_branch_78:
    jmp layout_branch_76
layout_branch_77:
    lda #$20
    sta g_cmp
    lda l_code
    cmp g_cmp
    beq layout_branch_80
    jmp layout_branch_79
layout_branch_80:
    jmp layout_branch_76
layout_branch_79:
; Backtrack to the last eligible space; otherwise split an overlong word.
    lda #$00
    sta g_cmp
; BEGIN BREAK FIX
    lda l_break_count
    cmp g_cmp
    beq layout_branch_82
    jmp layout_branch_81
layout_branch_82:
    jmp layout_position
layout_branch_81:
    lda l_break_count
    sta l_count
    lda l_break_i
    sta l_i
; END BREAK FIX
layout_branch_76:
    clc
    lda l_i
    adc #$01
    sta l_i
layout_position:
    lda #$00
    sta l_line_width
    lda #$00
    sta l_line_width+1
    lda l_count
    sta l_line_width
; BEGIN WIDTH FIX: eight pixels of advance for every counted glyph.
    asl l_line_width
    rol l_line_width+1
    asl l_line_width
    rol l_line_width+1
    asl l_line_width
    rol l_line_width+1
; END WIDTH FIX
    lda l_width+1
    sta g_cmp
    lda l_line_width+1
    cmp g_cmp
    bcs layout_branch_84
    jmp layout_branch_83
layout_branch_84:
    beq layout_branch_85
    jmp layout_reject
layout_branch_85:
    lda l_width
    sta g_cmp
    lda l_line_width
    cmp g_cmp
    bcs layout_branch_86
    jmp layout_branch_83
layout_branch_86:
    beq layout_branch_87
    jmp layout_reject
layout_branch_87:
layout_branch_83:
    lda l_measured+1
    sta g_cmp
    lda l_line_width+1
    cmp g_cmp
    bcs layout_branch_90
    jmp layout_branch_88
layout_branch_90:
    beq layout_branch_91
    jmp layout_branch_89
layout_branch_91:
    lda l_measured
    sta g_cmp
    lda l_line_width
    cmp g_cmp
    bcs layout_branch_92
    jmp layout_branch_88
layout_branch_92:
    bne layout_branch_93
    jmp layout_branch_88
layout_branch_93:
layout_branch_89:
    lda l_line_width
    sta l_measured
    lda l_line_width+1
    sta l_measured+1
layout_branch_88:
    lda l_width
    sta l_temp
    lda l_width+1
    sta l_temp+1
    sec
    lda l_temp
    sbc l_line_width
    sta l_temp
    lda l_temp+1
    sbc l_line_width+1
    sta l_temp+1
    lda #$00
    sta g_cmp
    lda l_align
    cmp g_cmp
    beq layout_branch_97
    jmp layout_branch_96
layout_branch_97:
    jmp layout_branch_94
layout_branch_96:
    lda #$02
    sta g_cmp
    lda l_align
    cmp g_cmp
    beq layout_branch_99
    jmp layout_branch_98
layout_branch_99:
    jmp layout_branch_95
layout_branch_98:
    lsr l_temp+1
    ror l_temp
    jmp layout_branch_95
layout_branch_94:
    lda #$00
    sta l_temp
    lda #$00
    sta l_temp+1
layout_branch_95:
; BEGIN ORIGIN FIX: the unused-space offset is relative to the rectangle.
    lda l_left
    sta l_x
    lda l_left+1
    sta l_x+1
    clc
    lda l_x
    adc l_temp
    sta l_x
    lda l_x+1
    adc l_temp+1
    sta l_x+1
; END ORIGIN FIX
    lda #$00
    sta g_cmp
    lda l_lines
    cmp g_cmp
    beq layout_branch_102
    jmp layout_branch_100
layout_branch_102:
layout_branch_101:
    lda l_x
    sta l_first_x
    lda l_x+1
    sta l_first_x+1
layout_branch_100:
    lda l_x
    sta l_last_x
    lda l_x+1
    sta l_last_x+1
    clc
    lda l_last_x
    adc l_line_width
    sta l_last_x
    lda l_last_x+1
    adc l_line_width+1
    sta l_last_x+1
    lda l_y
    sta l_last_y
    lda l_y+1
    sta l_last_y+1
    lda l_count
    sta l_last_count
    clc
    lda l_lines
    adc #$01
    sta l_lines
    lda #$00
    sta g_cmp
    lda l_pass
    cmp g_cmp
    beq layout_branch_104
    jmp layout_branch_103
layout_branch_104:
    jmp layout_advance
layout_branch_103:
    lda l_x
    sta t_x
    lda l_x+1
    sta t_x+1
    lda l_y
    sta t_y
    lda l_y+1
    sta t_y+1
    lda #$00
    sta t_source
    lda #$00
    sta t_source+1
    lda l_start
    sta t_source
    clc
    lda t_source
    adc l_source
    sta t_source
    lda t_source+1
    adc l_source+1
    sta t_source+1
    lda l_count
    sta t_length
    jsr text_string
    clc
    lda l_calls
    adc t_calls
    sta l_calls
    lda l_calls+1
    adc t_calls+1
    sta l_calls+1
    clc
    lda l_writes
    adc t_writes
    sta l_writes
    lda l_writes+1
    adc t_writes+1
    sta l_writes+1
    lda l_writes+2
    adc t_writes+2
    sta l_writes+2
    lda l_writes+3
    adc t_writes+3
    sta l_writes+3
layout_advance:
    lda #$00
    sta l_temp
    lda #$00
    sta l_temp+1
    lda l_pitch
    sta l_temp
    clc
    lda l_y
    adc l_temp
    sta l_y
    lda l_y+1
    adc l_temp+1
    sta l_y+1
    jmp layout_next
layout_pass_done:
    lda #$00
    sta g_cmp
    lda l_pass
    cmp g_cmp
    beq layout_branch_107
    jmp layout_branch_105
layout_branch_107:
layout_branch_106:
    lda #$01
    sta l_pass
    jmp layout_pass
layout_branch_105:
    lda l_length
    sta l_consumed
    lda #$01
    sta l_status
    rts
layout_reject:
    jsr layout_reset
    rts
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.

The original two-line prompt uses narrower, proportional letter spacing.
The same 33-byte prompt, with glyph-specific advances. Original Code198x font, cropped from the maintained MIT design; verified native reset capture.

Choose proportional-text/6502.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

prepare.inc
; BEGIN LAYOUT INPUTS
    lda #<(PANEL_LEFT)
    sta TL+0
    lda #>(PANEL_LEFT)
    sta TL+1
    lda #<(PANEL_TOP)
    sta TL+2
    lda #>(PANEL_TOP)
    sta TL+3
    lda #<(PANEL_WIDTH)
    sta TL+4
    lda #>(PANEL_WIDTH)
    sta TL+5
    lda #<(PANEL_HEIGHT)
    sta TL+6
    lda #>(PANEL_HEIGHT)
    sta TL+7
    lda #<(label_data)
    sta TL+10
    lda #>(label_data)
    sta TL+11
    lda #PANEL_ALIGN
    sta TL+8
    lda #LINE_PITCH
    sta TL+9
    lda #MESSAGE_LENGTH
    sta TL+12
    lda #<metric_data
    sta TL+64
    lda #>metric_data
    sta TL+65
    lda #LETTER_SPACING
    sta TL+66
    jsr layout_block
; END LAYOUT INPUTS

Measurement 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

customise.asm
LETTER_SPACING = 2
CUSTOM_LAYOUT = 1
; SPDX-License-Identifier: MIT
; Complete caller: change layout inputs without changing the reusable renderer.
PANEL_LEFT = 21
PANEL_TOP = 37
PANEL_WIDTH = 104
PANEL_HEIGHT = 80
PANEL_ALIGN = 2
LINE_PITCH = 16
MESSAGE_LENGTH = 44
    !source "caller.inc"
The custom message uses wider letter spacing in the narrow right-aligned panel.
Executed customisation: the width table stays the same; spacing, panel and message change.

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

6502-advance.inc, excerpt
; BEGIN WRONG
    clc
    lda l_x
    adc #8
    sta l_x
    lda l_x+1
    adc #0
    sta l_x+1
I! WIDE spreads across fixed eight-pixel positions despite its smaller measured width.
The faulty native call completes. Its whole bitmap differs from the correct proportional expectation.

Restore the very advance used by the planner:

Correct: advance by this glyph's width plus spacing

6502-advance.inc, excerpt
; BEGIN FIX
    clc
    lda l_x
    adc l_advance
    sta l_x
    lda l_x+1
    adc l_advance+1
    sta l_x+1
The repaired I! WIDE label fits its measured proportional advance.
The repaired caller executes and reproduces the original working fixture bytes.

What the smaller letters cost

This layout variant uses 2,898 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,456 and 526 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 682 952

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 88 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 acorn-bbc-micro 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

6502.inc
; 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 = TL+0
l_top = TL+2
l_width = TL+4
l_height = TL+6
l_align = TL+8
l_pitch = TL+9
l_source = TL+10
l_length = TL+12
l_status = TL+13
l_consumed = TL+14
l_lines = TL+15
l_last_count = TL+16
l_measured = TL+18
l_first_x = TL+20
l_last_x = TL+22
l_last_y = TL+24
l_calls = TL+26
l_writes = TL+28
l_mode = TL+32
l_pass = TL+33
l_i = TL+34
l_start = TL+35
l_count = TL+36
l_columns = TL+37
l_temp = TL+38
l_right = TL+40
l_bottom = TL+42
l_y = TL+44
l_x = TL+46
l_pointer = TL+48
l_code = TL+50
l_line_width = TL+52
l_work = TL+54
l_break_i = TL+58
l_break_count = TL+59
l_metrics = TL+64
l_spacing = TL+66
l_advance = TL+68
l_scan_width = TL+70
l_break_width = TL+72
l_glyph = TL+74
l_metric_i = TL+75
l_draw_i = TL+77
layout_measure:
    lda #$02
    sta l_mode
    jmp layout_enter
layout_line:
    lda #$00
    sta l_mode
    jmp layout_enter
layout_block:
    lda #$01
    sta l_mode
    jmp layout_enter
layout_reset:
    lda #$00
    sta l_status
    lda #$00
    sta l_consumed
    lda #$00
    sta l_lines
    lda #$00
    sta l_last_count
    lda #$00
    sta l_measured
    lda #$00
    sta l_measured+1
    lda #$00
    sta l_first_x
    lda #$00
    sta l_first_x+1
    lda #$00
    sta l_last_x
    lda #$00
    sta l_last_x+1
    lda #$00
    sta l_last_y
    lda #$00
    sta l_last_y+1
    lda #$00
    sta l_calls
    lda #$00
    sta l_calls+1
    lda #$00
    sta l_writes
    lda #$00
    sta l_writes+1
    lda #$00
    sta l_writes+2
    lda #$00
    sta l_writes+3
    rts
layout_enter:
    jsr layout_reset
; Validate geometry before additions, so huge unsigned values cannot wrap into range.
    lda #$00
    sta g_cmp
    lda l_width+1
    cmp g_cmp
    bcs layout_branch_2
    jmp layout_reject
layout_branch_2:
    beq layout_branch_3
    jmp layout_branch_1
layout_branch_3:
    lda #$08
    sta g_cmp
    lda l_width
    cmp g_cmp
    bcs layout_branch_4
    jmp layout_reject
layout_branch_4:
layout_branch_1:
    lda #$00
    sta g_cmp
    lda l_height+1
    cmp g_cmp
    bcs layout_branch_6
    jmp layout_reject
layout_branch_6:
    beq layout_branch_7
    jmp layout_branch_5
layout_branch_7:
    lda #$08
    sta g_cmp
    lda l_height
    cmp g_cmp
    bcs layout_branch_8
    jmp layout_reject
layout_branch_8:
layout_branch_5:
    lda #<TEXT_WIDTH
    sta l_temp
    lda #>TEXT_WIDTH
    sta l_temp+1
    lda l_temp+1
    sta g_cmp
    lda l_width+1
    cmp g_cmp
    bcs layout_branch_10
    jmp layout_branch_9
layout_branch_10:
    beq layout_branch_11
    jmp layout_reject
layout_branch_11:
    lda l_temp
    sta g_cmp
    lda l_width
    cmp g_cmp
    bcs layout_branch_12
    jmp layout_branch_9
layout_branch_12:
    beq layout_branch_13
    jmp layout_reject
layout_branch_13:
layout_branch_9:
    lda #<TEXT_HEIGHT
    sta l_temp
    lda #>TEXT_HEIGHT
    sta l_temp+1
    lda l_temp+1
    sta g_cmp
    lda l_height+1
    cmp g_cmp
    bcs layout_branch_15
    jmp layout_branch_14
layout_branch_15:
    beq layout_branch_16
    jmp layout_reject
layout_branch_16:
    lda l_temp
    sta g_cmp
    lda l_height
    cmp g_cmp
    bcs layout_branch_17
    jmp layout_branch_14
layout_branch_17:
    beq layout_branch_18
    jmp layout_reject
layout_branch_18:
layout_branch_14:
    lda #<TEXT_WIDTH
    sta l_temp
    lda #>TEXT_WIDTH
    sta l_temp+1
    lda l_temp+1
    sta g_cmp
    lda l_left+1
    cmp g_cmp
    bcs layout_branch_20
    jmp layout_branch_19
layout_branch_20:
    beq layout_branch_21
    jmp layout_reject
layout_branch_21:
    lda l_temp
    sta g_cmp
    lda l_left
    cmp g_cmp
    bcs layout_branch_22
    jmp layout_branch_19
layout_branch_22:
    beq layout_branch_23
    jmp layout_reject
layout_branch_23:
layout_branch_19:
    lda #<TEXT_HEIGHT
    sta l_temp
    lda #>TEXT_HEIGHT
    sta l_temp+1
    lda l_temp+1
    sta g_cmp
    lda l_top+1
    cmp g_cmp
    bcs layout_branch_25
    jmp layout_branch_24
layout_branch_25:
    beq layout_branch_26
    jmp layout_reject
layout_branch_26:
    lda l_temp
    sta g_cmp
    lda l_top
    cmp g_cmp
    bcs layout_branch_27
    jmp layout_branch_24
layout_branch_27:
    beq layout_branch_28
    jmp layout_reject
layout_branch_28:
layout_branch_24:
    lda l_left
    sta l_right
    lda l_left+1
    sta l_right+1
    clc
    lda l_right
    adc l_width
    sta l_right
    lda l_right+1
    adc l_width+1
    sta l_right+1
    lda #<TEXT_WIDTH
    sta l_temp
    lda #>TEXT_WIDTH
    sta l_temp+1
    lda l_temp+1
    sta g_cmp
    lda l_right+1
    cmp g_cmp
    bcs layout_branch_30
    jmp layout_branch_29
layout_branch_30:
    beq layout_branch_31
    jmp layout_reject
layout_branch_31:
    lda l_temp
    sta g_cmp
    lda l_right
    cmp g_cmp
    bcs layout_branch_32
    jmp layout_branch_29
layout_branch_32:
    beq layout_branch_33
    jmp layout_reject
layout_branch_33:
layout_branch_29:
    lda l_top
    sta l_bottom
    lda l_top+1
    sta l_bottom+1
    clc
    lda l_bottom
    adc l_height
    sta l_bottom
    lda l_bottom+1
    adc l_height+1
    sta l_bottom+1
    lda #<TEXT_HEIGHT
    sta l_temp
    lda #>TEXT_HEIGHT
    sta l_temp+1
    lda l_temp+1
    sta g_cmp
    lda l_bottom+1
    cmp g_cmp
    bcs layout_branch_35
    jmp layout_branch_34
layout_branch_35:
    beq layout_branch_36
    jmp layout_reject
layout_branch_36:
    lda l_temp
    sta g_cmp
    lda l_bottom
    cmp g_cmp
    bcs layout_branch_37
    jmp layout_branch_34
layout_branch_37:
    beq layout_branch_38
    jmp layout_reject
layout_branch_38:
layout_branch_34:
    lda #$02
    sta g_cmp
    lda l_align
    cmp g_cmp
    bcs layout_branch_40
    jmp layout_branch_39
layout_branch_40:
    beq layout_branch_41
    jmp layout_reject
layout_branch_41:
layout_branch_39:
    lda #$08
    sta g_cmp
    lda l_pitch
    cmp g_cmp
    bcs layout_branch_43
    jmp layout_reject
layout_branch_43:
layout_branch_42:
    lda #$00
    sta l_temp
    lda #$00
    sta l_temp+1
    lda l_length
    sta l_temp
    clc
    lda l_temp
    adc l_source
    sta l_temp
    lda l_temp+1
    adc l_source+1
    sta l_temp+1
    lda #$00
    sta g_cmp
    lda l_temp+1
    cmp g_cmp
    beq layout_branch_46
    jmp layout_branch_45
layout_branch_46:
    lda #$00
    sta g_cmp
    lda l_temp
    cmp g_cmp
    beq layout_branch_47
    jmp layout_branch_45
layout_branch_47:
    jmp layout_branch_44
layout_branch_45:
    lda l_source+1
    sta g_cmp
    lda l_temp+1
    cmp g_cmp
    bcs layout_branch_49
    jmp layout_reject
layout_branch_49:
    beq layout_branch_50
    jmp layout_branch_48
layout_branch_50:
    lda l_source
    sta g_cmp
    lda l_temp
    cmp g_cmp
    bcs layout_branch_51
    jmp layout_reject
layout_branch_51:
layout_branch_48:
layout_branch_44:
    lda l_width
    sta l_temp
    lda l_width+1
    sta l_temp+1
    lsr l_temp+1
    ror l_temp
    lsr l_temp+1
    ror l_temp
    lsr l_temp+1
    ror l_temp
    lda l_temp
    sta l_columns
; An empty renderer call validates font, pen and source declarations without drawing.
    lda l_left
    sta t_x
    lda l_left+1
    sta t_x+1
    lda l_top
    sta t_y
    lda l_top+1
    sta t_y+1
    lda l_source
    sta t_source
    lda l_source+1
    sta t_source+1
    lda l_left
    sta t_clip_l
    lda l_left+1
    sta t_clip_l+1
    lda l_top
    sta t_clip_t
    lda l_top+1
    sta t_clip_t+1
    lda l_right
    sta t_clip_r
    lda l_right+1
    sta t_clip_r+1
    lda l_bottom
    sta t_clip_b
    lda l_bottom+1
    sta t_clip_b+1
    lda #$00
    sta t_length
    jsr text_string
    lda #$00
    sta g_cmp
    lda t_status
    cmp g_cmp
    beq layout_branch_53
    jmp layout_branch_52
layout_branch_53:
    jmp layout_reject
layout_branch_52:
    lda #$02
    sta g_cmp
    lda l_spacing
    cmp g_cmp
    bcs layout_branch_55
    jmp layout_branch_54
layout_branch_55:
    beq layout_branch_56
    jmp layout_reject
layout_branch_56:
layout_branch_54:
    lda l_metrics
    sta l_temp
    lda l_metrics+1
    sta l_temp+1
    clc
    lda l_temp
    adc t_count
    sta l_temp
    lda l_temp+1
    adc t_count+1
    sta l_temp+1
    lda #$00
    sta g_cmp
    lda l_temp+1
    cmp g_cmp
    beq layout_branch_59
    jmp layout_branch_58
layout_branch_59:
    lda #$00
    sta g_cmp
    lda l_temp
    cmp g_cmp
    beq layout_branch_60
    jmp layout_branch_58
layout_branch_60:
    jmp layout_branch_57
layout_branch_58:
    lda l_metrics+1
    sta g_cmp
    lda l_temp+1
    cmp g_cmp
    bcs layout_branch_62
    jmp layout_reject
layout_branch_62:
    beq layout_branch_63
    jmp layout_branch_61
layout_branch_63:
    lda l_metrics
    sta g_cmp
    lda l_temp
    cmp g_cmp
    bcs layout_branch_64
    jmp layout_reject
layout_branch_64:
layout_branch_61:
layout_branch_57:
    lda #$00
    sta l_metric_i
    lda #$00
    sta l_metric_i+1
layout_validate_metric:
    lda t_count+1
    sta g_cmp
    lda l_metric_i+1
    cmp g_cmp
    bcs layout_branch_66
    jmp layout_branch_65
layout_branch_66:
    beq layout_branch_67
    jmp layout_metrics_ready
layout_branch_67:
    lda t_count
    sta g_cmp
    lda l_metric_i
    cmp g_cmp
    bcs layout_branch_68
    jmp layout_branch_65
layout_branch_68:
    jmp layout_metrics_ready
layout_branch_65:
    lda l_metrics
    sta l_pointer
    lda l_metrics+1
    sta l_pointer+1
    clc
    lda l_pointer
    adc l_metric_i
    sta l_pointer
    lda l_pointer+1
    adc l_metric_i+1
    sta l_pointer+1
    jsr layout_read_pointer
    sta l_glyph
    lda #$01
    sta g_cmp
    lda l_glyph
    cmp g_cmp
    bcs layout_branch_70
    jmp layout_reject
layout_branch_70:
layout_branch_69:
    lda #$00
    sta l_temp
    lda #$00
    sta l_temp+1
    lda l_glyph
    sta l_temp
    lda #$00
    sta l_advance
    lda #$00
    sta l_advance+1
    lda l_spacing
    sta l_advance
    clc
    lda l_temp
    adc l_advance
    sta l_temp
    lda l_temp+1
    adc l_advance+1
    sta l_temp+1
    lda #$00
    sta g_cmp
    lda l_temp+1
    cmp g_cmp
    bcs layout_branch_72
    jmp layout_branch_71
layout_branch_72:
    beq layout_branch_73
    jmp layout_reject
layout_branch_73:
    lda #$08
    sta g_cmp
    lda l_temp
    cmp g_cmp
    bcs layout_branch_74
    jmp layout_branch_71
layout_branch_74:
    beq layout_branch_75
    jmp layout_reject
layout_branch_75:
layout_branch_71:
    clc
    lda l_metric_i
    adc #$01
    sta l_metric_i
    lda l_metric_i+1
    adc #$00
    sta l_metric_i+1
    jmp layout_validate_metric
layout_metrics_ready:
    lda #$00
    sta l_pass
layout_pass:
    lda #$00
    sta l_i
    lda l_top
    sta l_y
    lda l_top+1
    sta l_y+1
    lda #$00
    sta l_lines
    lda #$00
    sta l_measured
    lda #$00
    sta l_measured+1
layout_next:
    lda l_length
    sta g_cmp
    lda l_i
    cmp g_cmp
    bcs layout_branch_77
    jmp layout_branch_76
layout_branch_77:
    jmp layout_pass_done
layout_branch_76:
    lda l_y
    sta l_temp
    lda l_y+1
    sta l_temp+1
    clc
    lda l_temp
    adc #$08
    sta l_temp
    lda l_temp+1
    adc #$00
    sta l_temp+1
    lda l_bottom+1
    sta g_cmp
    lda l_temp+1
    cmp g_cmp
    bcs layout_branch_79
    jmp layout_branch_78
layout_branch_79:
    beq layout_branch_80
    jmp layout_reject
layout_branch_80:
    lda l_bottom
    sta g_cmp
    lda l_temp
    cmp g_cmp
    bcs layout_branch_81
    jmp layout_branch_78
layout_branch_81:
    beq layout_branch_82
    jmp layout_reject
layout_branch_82:
layout_branch_78:
    lda l_i
    sta l_start
    lda #$00
    sta l_count
    lda #$00
    sta l_break_count
    lda #$00
    sta l_line_width
    lda #$00
    sta l_line_width+1
layout_scan:
    lda l_length
    sta g_cmp
    lda l_i
    cmp g_cmp
    bcs layout_branch_84
    jmp layout_branch_83
layout_branch_84:
    jmp layout_after_scan
layout_branch_83:
    lda #$00
    sta l_pointer
    lda #$00
    sta l_pointer+1
    lda l_i
    sta l_pointer
    clc
    lda l_pointer
    adc l_source
    sta l_pointer
    lda l_pointer+1
    adc l_source+1
    sta l_pointer+1
    lda l_pointer
    sta BP+46
    lda l_pointer+1
    sta BP+47
    ldy #0
    lda (BP+46),y
    sta l_code
    lda #$01
    sta g_cmp
    lda l_mode
    cmp g_cmp
    beq layout_branch_87
    jmp layout_branch_85
layout_branch_87:
layout_branch_86:
    lda #$0a
    sta g_cmp
    lda l_code
    cmp g_cmp
    beq layout_branch_89
    jmp layout_branch_88
layout_branch_89:
    jmp layout_after_scan
layout_branch_88:
layout_branch_85:
    jsr layout_glyph_advance
    lda l_line_width
    sta l_scan_width
    lda l_line_width+1
    sta l_scan_width+1
    clc
    lda l_scan_width
    adc l_advance
    sta l_scan_width
    lda l_scan_width+1
    adc l_advance+1
    sta l_scan_width+1
    lda #$01
    sta g_cmp
    lda l_mode
    cmp g_cmp
    beq layout_branch_92
    jmp layout_branch_90
layout_branch_92:
layout_branch_91:
    lda l_width+1
    sta g_cmp
    lda l_scan_width+1
    cmp g_cmp
    bcs layout_branch_94
    jmp layout_branch_93
layout_branch_94:
    beq layout_branch_95
    jmp layout_after_scan
layout_branch_95:
    lda l_width
    sta g_cmp
    lda l_scan_width
    cmp g_cmp
    bcs layout_branch_96
    jmp layout_branch_93
layout_branch_96:
    beq layout_branch_97
    jmp layout_after_scan
layout_branch_97:
layout_branch_93:
layout_branch_90:
    lda #$20
    sta g_cmp
    lda l_code
    cmp g_cmp
    beq layout_branch_100
    jmp layout_branch_98
layout_branch_100:
layout_branch_99:
    lda #$00
    sta g_cmp
    lda l_count
    cmp g_cmp
    beq layout_branch_102
    jmp layout_branch_101
layout_branch_102:
    jmp layout_branch_98
layout_branch_101:
    lda l_i
    sta l_break_i
    lda l_count
    sta l_break_count
    lda l_line_width
    sta l_break_width
    lda l_line_width+1
    sta l_break_width+1
layout_branch_98:
    lda l_scan_width
    sta l_line_width
    lda l_scan_width+1
    sta l_line_width+1
    clc
    lda l_count
    adc #$01
    sta l_count
    clc
    lda l_i
    adc #$01
    sta l_i
    jmp layout_scan
layout_after_scan:
    lda #$01
    sta g_cmp
    lda l_mode
    cmp g_cmp
    beq layout_branch_104
    jmp layout_position
layout_branch_104:
layout_branch_103:
    lda l_length
    sta g_cmp
    lda l_i
    cmp g_cmp
    bcs layout_branch_106
    jmp layout_branch_105
layout_branch_106:
    jmp layout_position
layout_branch_105:
    lda #$00
    sta l_pointer
    lda #$00
    sta l_pointer+1
    lda l_i
    sta l_pointer
    clc
    lda l_pointer
    adc l_source
    sta l_pointer
    lda l_pointer+1
    adc l_source+1
    sta l_pointer+1
    lda l_pointer
    sta BP+46
    lda l_pointer+1
    sta BP+47
    ldy #0
    lda (BP+46),y
    sta l_code
    lda #$0a
    sta g_cmp
    lda l_code
    cmp g_cmp
    beq layout_branch_109
    jmp layout_branch_108
layout_branch_109:
    jmp layout_branch_107
layout_branch_108:
    lda #$20
    sta g_cmp
    lda l_code
    cmp g_cmp
    beq layout_branch_111
    jmp layout_branch_110
layout_branch_111:
    jmp layout_branch_107
layout_branch_110:
; Backtrack to the last eligible space; otherwise split an overlong word.
    lda #$00
    sta g_cmp
    lda l_break_count
    cmp g_cmp
    beq layout_branch_113
    jmp layout_branch_112
layout_branch_113:
    jmp layout_position
layout_branch_112:
    lda l_break_count
    sta l_count
    lda l_break_i
    sta l_i
    lda l_break_width
    sta l_line_width
    lda l_break_width+1
    sta l_line_width+1
layout_branch_107:
    clc
    lda l_i
    adc #$01
    sta l_i
layout_position:
    lda l_start
    sta g_cmp
    lda l_i
    cmp g_cmp
    beq layout_branch_116
    jmp layout_branch_114
layout_branch_116:
layout_branch_115:
    lda #$00
    sta g_cmp
    lda l_count
    cmp g_cmp
    beq layout_branch_118
    jmp layout_branch_117
layout_branch_118:
    jmp layout_reject
layout_branch_117:
layout_branch_114:
    lda #$02
    sta g_cmp
    lda l_mode
    cmp g_cmp
    beq layout_branch_120
    jmp layout_branch_119
layout_branch_120:
    jmp layout_measure_done
layout_branch_119:
    lda l_width+1
    sta g_cmp
    lda l_line_width+1
    cmp g_cmp
    bcs layout_branch_122
    jmp layout_branch_121
layout_branch_122:
    beq layout_branch_123
    jmp layout_reject
layout_branch_123:
    lda l_width
    sta g_cmp
    lda l_line_width
    cmp g_cmp
    bcs layout_branch_124
    jmp layout_branch_121
layout_branch_124:
    beq layout_branch_125
    jmp layout_reject
layout_branch_125:
layout_branch_121:
    lda l_measured+1
    sta g_cmp
    lda l_line_width+1
    cmp g_cmp
    bcs layout_branch_128
    jmp layout_branch_126
layout_branch_128:
    beq layout_branch_129
    jmp layout_branch_127
layout_branch_129:
    lda l_measured
    sta g_cmp
    lda l_line_width
    cmp g_cmp
    bcs layout_branch_130
    jmp layout_branch_126
layout_branch_130:
    bne layout_branch_131
    jmp layout_branch_126
layout_branch_131:
layout_branch_127:
    lda l_line_width
    sta l_measured
    lda l_line_width+1
    sta l_measured+1
layout_branch_126:
    lda l_width
    sta l_temp
    lda l_width+1
    sta l_temp+1
    sec
    lda l_temp
    sbc l_line_width
    sta l_temp
    lda l_temp+1
    sbc l_line_width+1
    sta l_temp+1
    lda #$00
    sta g_cmp
    lda l_align
    cmp g_cmp
    beq layout_branch_135
    jmp layout_branch_134
layout_branch_135:
    jmp layout_branch_132
layout_branch_134:
    lda #$02
    sta g_cmp
    lda l_align
    cmp g_cmp
    beq layout_branch_137
    jmp layout_branch_136
layout_branch_137:
    jmp layout_branch_133
layout_branch_136:
    lsr l_temp+1
    ror l_temp
    jmp layout_branch_133
layout_branch_132:
    lda #$00
    sta l_temp
    lda #$00
    sta l_temp+1
layout_branch_133:
; BEGIN ORIGIN FIX: the unused-space offset is relative to the rectangle.
    lda l_left
    sta l_x
    lda l_left+1
    sta l_x+1
    clc
    lda l_x
    adc l_temp
    sta l_x
    lda l_x+1
    adc l_temp+1
    sta l_x+1
; END ORIGIN FIX
    lda #$00
    sta g_cmp
    lda l_lines
    cmp g_cmp
    beq layout_branch_140
    jmp layout_branch_138
layout_branch_140:
layout_branch_139:
    lda l_x
    sta l_first_x
    lda l_x+1
    sta l_first_x+1
layout_branch_138:
    lda l_x
    sta l_last_x
    lda l_x+1
    sta l_last_x+1
    clc
    lda l_last_x
    adc l_line_width
    sta l_last_x
    lda l_last_x+1
    adc l_line_width+1
    sta l_last_x+1
    lda l_y
    sta l_last_y
    lda l_y+1
    sta l_last_y+1
    lda l_count
    sta l_last_count
    clc
    lda l_lines
    adc #$01
    sta l_lines
    lda #$00
    sta g_cmp
    lda l_pass
    cmp g_cmp
    beq layout_branch_142
    jmp layout_branch_141
layout_branch_142:
    jmp layout_advance
layout_branch_141:
    lda l_start
    sta l_draw_i
layout_draw_glyph:
    lda #$00
    sta l_temp
    lda #$00
    sta l_temp+1
    lda l_draw_i
    sta l_temp
    lda #$00
    sta l_pointer
    lda #$00
    sta l_pointer+1
    lda l_start
    sta l_pointer
    lda #$00
    sta l_advance
    lda #$00
    sta l_advance+1
    lda l_count
    sta l_advance
    clc
    lda l_pointer
    adc l_advance
    sta l_pointer
    lda l_pointer+1
    adc l_advance+1
    sta l_pointer+1
    lda l_pointer+1
    sta g_cmp
    lda l_temp+1
    cmp g_cmp
    bcs layout_branch_144
    jmp layout_branch_143
layout_branch_144:
    beq layout_branch_145
    jmp layout_advance
layout_branch_145:
    lda l_pointer
    sta g_cmp
    lda l_temp
    cmp g_cmp
    bcs layout_branch_146
    jmp layout_branch_143
layout_branch_146:
    jmp layout_advance
layout_branch_143:
    lda l_source
    sta l_pointer
    lda l_source+1
    sta l_pointer+1
    clc
    lda l_pointer
    adc l_temp
    sta l_pointer
    lda l_pointer+1
    adc l_temp+1
    sta l_pointer+1
    jsr layout_read_pointer
    sta l_code
    jsr layout_glyph_advance
    lda l_x
    sta t_x
    lda l_x+1
    sta t_x+1
    lda l_y
    sta t_y
    lda l_y+1
    sta t_y+1
    lda l_x
    sta t_clip_l
    lda l_x+1
    sta t_clip_l+1
    lda l_x
    sta t_clip_r
    lda l_x+1
    sta t_clip_r+1
    clc
    lda t_clip_r
    adc l_advance
    sta t_clip_r
    lda t_clip_r+1
    adc l_advance+1
    sta t_clip_r+1
    lda l_glyph
    sta t_code
    jsr text_char
    clc
    lda l_calls
    adc t_calls
    sta l_calls
    lda l_calls+1
    adc t_calls+1
    sta l_calls+1
    clc
    lda l_writes
    adc t_writes
    sta l_writes
    lda l_writes+1
    adc t_writes+1
    sta l_writes+1
    lda l_writes+2
    adc t_writes+2
    sta l_writes+2
    lda l_writes+3
    adc t_writes+3
    sta l_writes+3
; BEGIN ADVANCE FIX: move by this glyph metric plus letter spacing.
    clc
    lda l_x
    adc l_advance
    sta l_x
    lda l_x+1
    adc l_advance+1
    sta l_x+1
; END ADVANCE FIX
    clc
    lda l_draw_i
    adc #$01
    sta l_draw_i
    jmp layout_draw_glyph
layout_advance:
    lda #$00
    sta l_temp
    lda #$00
    sta l_temp+1
    lda l_pitch
    sta l_temp
    clc
    lda l_y
    adc l_temp
    sta l_y
    lda l_y+1
    adc l_temp+1
    sta l_y+1
    jmp layout_next
layout_pass_done:
    lda #$00
    sta g_cmp
    lda l_pass
    cmp g_cmp
    beq layout_branch_149
    jmp layout_branch_147
layout_branch_149:
layout_branch_148:
    lda #$01
    sta l_pass
    jmp layout_pass
layout_branch_147:
    lda l_length
    sta l_consumed
    lda #$01
    sta l_status
    rts
layout_measure_done:
    lda l_line_width
    sta l_measured
    lda l_line_width+1
    sta l_measured+1
    lda #$01
    sta l_status
    rts
layout_reject:
    jsr layout_reset
    rts
layout_glyph_advance:
    lda l_code
    sta l_glyph
    lda t_first
    sta g_cmp
    lda l_glyph
    cmp g_cmp
    bcs layout_branch_153
    jmp layout_branch_151
layout_branch_153:
layout_branch_152:
    lda #$00
    sta l_temp
    lda #$00
    sta l_temp+1
    lda l_glyph
    sta l_temp
    lda #$00
    sta l_pointer
    lda #$00
    sta l_pointer+1
    lda t_first
    sta l_pointer
    sec
    lda l_temp
    sbc l_pointer
    sta l_temp
    lda l_temp+1
    sbc l_pointer+1
    sta l_temp+1
    lda t_count+1
    sta g_cmp
    lda l_temp+1
    cmp g_cmp
    bcs layout_branch_155
    jmp layout_branch_150
layout_branch_155:
    beq layout_branch_156
    jmp layout_branch_154
layout_branch_156:
    lda t_count
    sta g_cmp
    lda l_temp
    cmp g_cmp
    bcs layout_branch_157
    jmp layout_branch_150
layout_branch_157:
layout_branch_154:
layout_branch_151:
    lda t_fallback
    sta l_glyph
    lda #$00
    sta l_temp
    lda #$00
    sta l_temp+1
    lda l_glyph
    sta l_temp
    lda #$00
    sta l_pointer
    lda #$00
    sta l_pointer+1
    lda t_first
    sta l_pointer
    sec
    lda l_temp
    sbc l_pointer
    sta l_temp
    lda l_temp+1
    sbc l_pointer+1
    sta l_temp+1
layout_branch_150:
    lda l_metrics
    sta l_pointer
    lda l_metrics+1
    sta l_pointer+1
    clc
    lda l_pointer
    adc l_temp
    sta l_pointer
    lda l_pointer+1
    adc l_temp+1
    sta l_pointer+1
    jsr layout_read_pointer
    sta l_advance
    lda #$00
    sta l_advance+1
    lda #$00
    sta l_temp
    lda #$00
    sta l_temp+1
    lda l_spacing
    sta l_temp
    clc
    lda l_advance
    adc l_temp
    sta l_advance
    lda l_advance+1
    adc l_temp+1
    sta l_advance+1
    rts
layout_read_pointer:
    lda l_pointer
    sta BP+46
    lda l_pointer+1
    sta BP+47
    ldy #0
    lda (BP+46),y
    rts
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.