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 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
; 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 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:
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
rtsFive 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 = 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"
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
; BEGIN WRONG
asl l_line_width
rol l_line_width+1
asl l_line_width
rol l_line_width+1
Restore the third shift and check that five codes measure 40 pixels:
Correct: match the renderer's eight-pixel advance
; 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+1The 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
lda l_temp
sta l_x
lda l_temp+1
sta l_x+1
Add the panel origin to the alignment offset:
Correct: translate from panel to screen coordinates
; 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
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
; 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 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
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 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 = 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"
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
jmp 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
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
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
; 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.

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
; 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 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 = 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"
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
clc
lda l_x
adc #8
sta l_x
lda l_x+1
adc #0
sta l_x+1
Restore the very advance used by the planner:
Correct: advance by this glyph's width plus spacing
; BEGIN FIX
clc
lda l_x
adc l_advance
sta l_x
lda l_x+1
adc l_advance+1
sta l_x+1
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
; 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.