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
PMODE 4 uses a 256×192 one-bit bitmap at $0600 in this reset caller. Its linear rows keep the backend small. Public words are big-endian: a left coordinate of 21 is stored as $00,$15.
Reserve 64 bytes at TL=$0480, 96 at T=$0400 and 48 at BP=$0380 for this caller. Keep them separate from source, font, bitmap, code and stack. Include the unchanged bitmap-text component before text-layout/6809.inc; the layout uses its aliases and renderer. Keep the existing backend and its helper/table. The complete source shows every include and allocation.
T supplies the font pointer, first code/count, fallback, transparent/opaque mode and foreground/background pens. Layout takes ownership of T and BP while running, including the scissor. Initialise those font/pen choices as in caller.inc, then supply TL:
| Offset | Input |
|---|---|
| 0, 2 | Words: panel left, top |
| 4, 6 | Words: panel width, height |
| 8 | Byte: alignment, 0 left / 1 centre / 2 right |
| 9 | Byte: line pitch, 8..255 scanlines |
| 10 | Word: source address |
| 12 | Byte: source length, 0..255 |
Words are big-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
ldd #PANEL_LEFT
std TL+0
ldd #PANEL_TOP
std TL+2
ldd #PANEL_WIDTH
std TL+4
ldd #PANEL_HEIGHT
std TL+6
ldd #label_data
std TL+10
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
ldd #$0000
std l_measured
lda l_length
sta l_measured+1
lsl l_measured+1
rol l_measured
lsl l_measured+1
rol l_measured
lsl l_measured+1
rol l_measured
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 equ 1
; SPDX-License-Identifier: MIT
; Complete caller: change layout inputs without changing the reusable renderer.
PANEL_LEFT equ 21
PANEL_TOP equ 37
PANEL_WIDTH equ 104
PANEL_HEIGHT equ 80
PANEL_ALIGN equ 2
LINE_PITCH equ 16
MESSAGE_LENGTH equ 44
include "caller.inc"
Edit customise-message.inc and update MESSAGE_LENGTH together. The routine has no terminator scan to rescue an incorrect count. More rows or wider text can increase drawing work, even though code and context allocations stay the same. The proportional option below changes glyph advances. Another glyph size or video mode still needs its own implementation and verification.
For these exact original messages, native counters report:
| Work | Two-line demo (33 source bytes) | Narrow custom panel (44 source bytes) |
|---|---|---|
| Logical lines | 2 | 5 |
| Backend spans | 511 | 709 |
| Destination-byte writes | 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
lsl l_line_width+1
rol l_line_width
lsl l_line_width+1
rol l_line_width
Restore the third shift and check that five codes measure 40 pixels:
Correct: match the renderer's eight-pixel advance
; BEGIN FIX
lsl l_line_width+1
rol l_line_width
lsl l_line_width+1
rol l_line_width
lsl l_line_width+1
rol l_line_widthThe 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
ldd l_temp
std l_x
Add the panel origin to the alignment offset:
Correct: translate from panel to screen coordinates
; BEGIN FIX
ldd l_left
std l_x
ldd l_x
addd l_temp
std l_x
Costs, ownership and results
The layout adds 1,463 code bytes and 64 context bytes. The reused text renderer is 2,058 bytes and its backend 470; their 96+48 context bytes, the 512-byte sample font, messages, video allocation and stack are separate. Reserve a conservative 80-byte stack budget plus caller/interrupt depth. The 6809 keeps U and DP; D, X, Y and condition codes may change.
Block mode makes two planning passes, then the renderer adds source/font reads and glyph/span work. The second pass interleaves planning and rendering after the complete first pass has qualified the request. TL+26 reports total span calls; TL+28 reports a 32-bit destination-byte-write count. These are work counts, not cycle measurements. Byte alignment, pen packing, glyph shapes and opacity affect them. Static labels can be cheaper to position or render once.
Accepted calls return consumed bytes at TL+14, logical lines at +15, last-line glyph count at +16, maximum logical width at +18, first origin X at +20, last line end X at +22 and its origin Y at +24. Rejected calls return zero for all documented outputs and leave the bitmap untouched. T/BP scratch may change. Blank lines affect line count and position but cause no glyph/background writes.
The component is non-re-entrant. It supplies no frame wait, OS negotiation, interrupt protection or video synchronisation. The reset caller owns the machine; a real game must choose safe display access and coordinate other users of T/BP, video, banks and interrupts. Native completion does not establish stock-OS loading, live tearing behaviour, original-hardware timing or a frame budget.
Read the reusable layout component
Reusable layout over the existing text renderer
; SPDX-License-Identifier: MIT
; Bounded counted 8x8 text layout over the unchanged bitmap-text component.
; Include bitmap-text once; reserve TL=64, T=96 and BP=48 separate bytes.
; TL inputs 0..12 survive. T/BP are owned workspaces. See README.md.
l_left equ TL+0
l_top equ TL+2
l_width equ TL+4
l_height equ TL+6
l_align equ TL+8
l_pitch equ TL+9
l_source equ TL+10
l_length equ TL+12
l_status equ TL+13
l_consumed equ TL+14
l_lines equ TL+15
l_last_count equ TL+16
l_measured equ TL+18
l_first_x equ TL+20
l_last_x equ TL+22
l_last_y equ TL+24
l_calls equ TL+26
l_writes equ TL+28
l_mode equ TL+32
l_pass equ TL+33
l_i equ TL+34
l_start equ TL+35
l_count equ TL+36
l_columns equ TL+37
l_temp equ TL+38
l_right equ TL+40
l_bottom equ TL+42
l_y equ TL+44
l_x equ TL+46
l_pointer equ TL+48
l_code equ TL+50
l_line_width equ TL+52
l_work equ TL+54
layout_measure
jsr layout_reset
ldd #$0000
std l_measured
lda l_length
sta l_measured+1
lsl l_measured+1
rol l_measured
lsl l_measured+1
rol l_measured
lsl l_measured+1
rol l_measured
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
ldd #$0000
std l_measured
ldd #$0000
std l_first_x
ldd #$0000
std l_last_x
ldd #$0000
std l_last_y
ldd #$0000
std l_calls
lda #$00
sta l_writes+3
lda #$00
sta l_writes+2
lda #$00
sta l_writes+1
lda #$00
sta l_writes
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
cmpa g_cmp
lblo layout_reject
lbne layout_branch_1
lda #$08
sta g_cmp
lda l_width+1
cmpa g_cmp
lblo layout_reject
layout_branch_1
lda #$00
sta g_cmp
lda l_height
cmpa g_cmp
lblo layout_reject
lbne layout_branch_2
lda #$08
sta g_cmp
lda l_height+1
cmpa g_cmp
lblo layout_reject
layout_branch_2
ldd #TEXT_WIDTH
std l_temp
lda l_temp
sta g_cmp
lda l_width
cmpa g_cmp
lblo layout_branch_3
lbne layout_reject
lda l_temp+1
sta g_cmp
lda l_width+1
cmpa g_cmp
lblo layout_branch_3
lbne layout_reject
layout_branch_3
ldd #TEXT_HEIGHT
std l_temp
lda l_temp
sta g_cmp
lda l_height
cmpa g_cmp
lblo layout_branch_4
lbne layout_reject
lda l_temp+1
sta g_cmp
lda l_height+1
cmpa g_cmp
lblo layout_branch_4
lbne layout_reject
layout_branch_4
ldd #TEXT_WIDTH
std l_temp
lda l_temp
sta g_cmp
lda l_left
cmpa g_cmp
lblo layout_branch_5
lbne layout_reject
lda l_temp+1
sta g_cmp
lda l_left+1
cmpa g_cmp
lblo layout_branch_5
lbne layout_reject
layout_branch_5
ldd #TEXT_HEIGHT
std l_temp
lda l_temp
sta g_cmp
lda l_top
cmpa g_cmp
lblo layout_branch_6
lbne layout_reject
lda l_temp+1
sta g_cmp
lda l_top+1
cmpa g_cmp
lblo layout_branch_6
lbne layout_reject
layout_branch_6
ldd l_left
std l_right
ldd l_right
addd l_width
std l_right
ldd #TEXT_WIDTH
std l_temp
lda l_temp
sta g_cmp
lda l_right
cmpa g_cmp
lblo layout_branch_7
lbne layout_reject
lda l_temp+1
sta g_cmp
lda l_right+1
cmpa g_cmp
lblo layout_branch_7
lbne layout_reject
layout_branch_7
ldd l_top
std l_bottom
ldd l_bottom
addd l_height
std l_bottom
ldd #TEXT_HEIGHT
std l_temp
lda l_temp
sta g_cmp
lda l_bottom
cmpa g_cmp
lblo layout_branch_8
lbne layout_reject
lda l_temp+1
sta g_cmp
lda l_bottom+1
cmpa g_cmp
lblo layout_branch_8
lbne layout_reject
layout_branch_8
lda #$02
sta g_cmp
lda l_align
cmpa g_cmp
lblo layout_branch_9
lbne layout_reject
layout_branch_9
lda #$08
sta g_cmp
lda l_pitch
cmpa g_cmp
lblo layout_reject
layout_branch_10
ldd #$0000
std l_temp
lda l_length
sta l_temp+1
ldd l_temp
addd l_source
std l_temp
lda #$00
sta g_cmp
lda l_temp
cmpa g_cmp
lbne layout_branch_12
lda #$00
sta g_cmp
lda l_temp+1
cmpa g_cmp
lbne layout_branch_12
jmp layout_branch_11
layout_branch_12
lda l_source
sta g_cmp
lda l_temp
cmpa g_cmp
lblo layout_reject
lbne layout_branch_13
lda l_source+1
sta g_cmp
lda l_temp+1
cmpa g_cmp
lblo layout_reject
layout_branch_13
layout_branch_11
ldd l_width
std l_temp
lsr l_temp
ror l_temp+1
lsr l_temp
ror l_temp+1
lsr l_temp
ror l_temp+1
lda l_temp+1
sta l_columns
; An empty renderer call validates font, pen and source declarations without drawing.
ldd l_left
std t_x
ldd l_top
std t_y
ldd l_source
std t_source
ldd l_left
std t_clip_l
ldd l_top
std t_clip_t
ldd l_right
std t_clip_r
ldd l_bottom
std t_clip_b
lda #$00
sta t_length
jsr text_string
lda #$00
sta g_cmp
lda t_status
cmpa g_cmp
lbne layout_branch_14
jmp layout_reject
layout_branch_14
lda #$00
sta l_pass
layout_pass
lda #$00
sta l_i
ldd l_top
std l_y
lda #$00
sta l_lines
ldd #$0000
std l_measured
layout_next
lda l_length
sta g_cmp
lda l_i
cmpa g_cmp
lblo layout_branch_15
jmp layout_pass_done
layout_branch_15
ldd l_y
std l_temp
ldd l_temp
addd #$0008
std l_temp
lda l_bottom
sta g_cmp
lda l_temp
cmpa g_cmp
lblo layout_branch_16
lbne layout_reject
lda l_bottom+1
sta g_cmp
lda l_temp+1
cmpa g_cmp
lblo layout_branch_16
lbne layout_reject
layout_branch_16
lda l_i
sta l_start
lda #$00
sta l_count
lda #$00
sta g_cmp
lda l_mode
cmpa g_cmp
lbne layout_scan
layout_branch_17
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
cmpa g_cmp
lblo layout_branch_18
jmp layout_after_scan
layout_branch_18
lda l_columns
sta g_cmp
lda l_count
cmpa g_cmp
lblo layout_branch_19
jmp layout_after_scan
layout_branch_19
ldd #$0000
std l_pointer
lda l_i
sta l_pointer+1
ldd l_pointer
addd l_source
std l_pointer
ldx l_pointer
lda ,x
sta l_code
lda #$0a
sta g_cmp
lda l_code
cmpa g_cmp
lbne layout_branch_20
jmp layout_after_scan
layout_branch_20
lda l_count
adda #$01
sta l_count
lda l_i
adda #$01
sta l_i
jmp layout_scan
layout_after_scan
lda l_length
sta g_cmp
lda l_i
cmpa g_cmp
lblo layout_branch_21
jmp layout_position
layout_branch_21
ldd #$0000
std l_pointer
lda l_i
sta l_pointer+1
ldd l_pointer
addd l_source
std l_pointer
ldx l_pointer
lda ,x
sta l_code
lda #$0a
sta g_cmp
lda l_code
cmpa g_cmp
lbne layout_position
layout_branch_22
lda l_i
adda #$01
sta l_i
layout_position
ldd #$0000
std l_line_width
lda l_count
sta l_line_width+1
; BEGIN WIDTH FIX: eight pixels of advance for every counted glyph.
lsl l_line_width+1
rol l_line_width
lsl l_line_width+1
rol l_line_width
lsl l_line_width+1
rol l_line_width
; END WIDTH FIX
lda l_width
sta g_cmp
lda l_line_width
cmpa g_cmp
lblo layout_branch_23
lbne layout_reject
lda l_width+1
sta g_cmp
lda l_line_width+1
cmpa g_cmp
lblo layout_branch_23
lbne layout_reject
layout_branch_23
lda l_measured
sta g_cmp
lda l_line_width
cmpa g_cmp
lblo layout_branch_24
lbne layout_branch_25
lda l_measured+1
sta g_cmp
lda l_line_width+1
cmpa g_cmp
lblo layout_branch_24
lbeq layout_branch_24
layout_branch_25
ldd l_line_width
std l_measured
layout_branch_24
ldd l_width
std l_temp
ldd l_temp
subd l_line_width
std l_temp
lda #$00
sta g_cmp
lda l_align
cmpa g_cmp
lbne layout_branch_28
jmp layout_branch_26
layout_branch_28
lda #$02
sta g_cmp
lda l_align
cmpa g_cmp
lbne layout_branch_29
jmp layout_branch_27
layout_branch_29
lsr l_temp
ror l_temp+1
jmp layout_branch_27
layout_branch_26
ldd #$0000
std l_temp
layout_branch_27
; BEGIN ORIGIN FIX: the unused-space offset is relative to the rectangle.
ldd l_left
std l_x
ldd l_x
addd l_temp
std l_x
; END ORIGIN FIX
lda #$00
sta g_cmp
lda l_lines
cmpa g_cmp
lbne layout_branch_30
layout_branch_31
ldd l_x
std l_first_x
layout_branch_30
ldd l_x
std l_last_x
ldd l_last_x
addd l_line_width
std l_last_x
ldd l_y
std l_last_y
lda l_count
sta l_last_count
lda l_lines
adda #$01
sta l_lines
lda #$00
sta g_cmp
lda l_pass
cmpa g_cmp
lbne layout_branch_32
jmp layout_advance
layout_branch_32
ldd l_x
std t_x
ldd l_y
std t_y
ldd #$0000
std t_source
lda l_start
sta t_source+1
ldd t_source
addd l_source
std t_source
lda l_count
sta t_length
jsr text_string
ldd l_calls
addd t_calls
std l_calls
lda l_writes+3
adda t_writes+3
sta l_writes+3
lda l_writes+2
adca t_writes+2
sta l_writes+2
lda l_writes+1
adca t_writes+1
sta l_writes+1
lda l_writes
adca t_writes
sta l_writes
layout_advance
ldd #$0000
std l_temp
lda l_pitch
sta l_temp+1
ldd l_y
addd l_temp
std l_y
jmp layout_next
layout_pass_done
lda #$00
sta g_cmp
lda l_pass
cmpa g_cmp
lbne layout_branch_33
layout_branch_34
lda #$01
sta l_pass
jmp layout_pass
layout_branch_33
lda l_length
sta l_consumed
lda #$01
sta l_status
rts
layout_reject
jsr layout_reset
rts
layout_endThe 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/6809.inc variant over the same bitmap-text renderer. It adds 101 code bytes (1,564 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
ldd #PANEL_LEFT
std TL+0
ldd #PANEL_TOP
std TL+2
ldd #PANEL_WIDTH
std TL+4
ldd #PANEL_HEIGHT
std TL+6
ldd #label_data
std TL+10
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 equ 1
; SPDX-License-Identifier: MIT
; Complete caller: change layout inputs without changing the reusable renderer.
PANEL_LEFT equ 21
PANEL_TOP equ 37
PANEL_WIDTH equ 104
PANEL_HEIGHT equ 80
PANEL_ALIGN equ 2
LINE_PITCH equ 16
MESSAGE_LENGTH equ 44
include "caller.inc"
Edit customise-message.inc and update MESSAGE_LENGTH together. The routine has no terminator scan to rescue an incorrect count. More rows or wider text can increase drawing work, even though code and context allocations stay the same. The proportional option below changes glyph advances; different glyph sizes, whitespace rules or video modes need their own qualification.
A word breaks despite a usable space
The scan has found a space, but the caller discards that saved boundary and cuts at capacity. ONE THREE FIVE becomes ONE THR / EE FIVE in a seven-column panel. The count is bounded, yet the intended word policy is lost.
Deliberately wrong: discard the saved word boundary
; BEGIN WRONG
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
cmpa g_cmp
lbne layout_branch_28
jmp layout_position
layout_branch_28
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 equ TL+0
l_top equ TL+2
l_width equ TL+4
l_height equ TL+6
l_align equ TL+8
l_pitch equ TL+9
l_source equ TL+10
l_length equ TL+12
l_status equ TL+13
l_consumed equ TL+14
l_lines equ TL+15
l_last_count equ TL+16
l_measured equ TL+18
l_first_x equ TL+20
l_last_x equ TL+22
l_last_y equ TL+24
l_calls equ TL+26
l_writes equ TL+28
l_mode equ TL+32
l_pass equ TL+33
l_i equ TL+34
l_start equ TL+35
l_count equ TL+36
l_columns equ TL+37
l_temp equ TL+38
l_right equ TL+40
l_bottom equ TL+42
l_y equ TL+44
l_x equ TL+46
l_pointer equ TL+48
l_code equ TL+50
l_line_width equ TL+52
l_work equ TL+54
l_break_i equ TL+58
l_break_count equ TL+59
layout_measure
jsr layout_reset
ldd #$0000
std l_measured
lda l_length
sta l_measured+1
lsl l_measured+1
rol l_measured
lsl l_measured+1
rol l_measured
lsl l_measured+1
rol l_measured
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
ldd #$0000
std l_measured
ldd #$0000
std l_first_x
ldd #$0000
std l_last_x
ldd #$0000
std l_last_y
ldd #$0000
std l_calls
lda #$00
sta l_writes+3
lda #$00
sta l_writes+2
lda #$00
sta l_writes+1
lda #$00
sta l_writes
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
cmpa g_cmp
lblo layout_reject
lbne layout_branch_1
lda #$08
sta g_cmp
lda l_width+1
cmpa g_cmp
lblo layout_reject
layout_branch_1
lda #$00
sta g_cmp
lda l_height
cmpa g_cmp
lblo layout_reject
lbne layout_branch_2
lda #$08
sta g_cmp
lda l_height+1
cmpa g_cmp
lblo layout_reject
layout_branch_2
ldd #TEXT_WIDTH
std l_temp
lda l_temp
sta g_cmp
lda l_width
cmpa g_cmp
lblo layout_branch_3
lbne layout_reject
lda l_temp+1
sta g_cmp
lda l_width+1
cmpa g_cmp
lblo layout_branch_3
lbne layout_reject
layout_branch_3
ldd #TEXT_HEIGHT
std l_temp
lda l_temp
sta g_cmp
lda l_height
cmpa g_cmp
lblo layout_branch_4
lbne layout_reject
lda l_temp+1
sta g_cmp
lda l_height+1
cmpa g_cmp
lblo layout_branch_4
lbne layout_reject
layout_branch_4
ldd #TEXT_WIDTH
std l_temp
lda l_temp
sta g_cmp
lda l_left
cmpa g_cmp
lblo layout_branch_5
lbne layout_reject
lda l_temp+1
sta g_cmp
lda l_left+1
cmpa g_cmp
lblo layout_branch_5
lbne layout_reject
layout_branch_5
ldd #TEXT_HEIGHT
std l_temp
lda l_temp
sta g_cmp
lda l_top
cmpa g_cmp
lblo layout_branch_6
lbne layout_reject
lda l_temp+1
sta g_cmp
lda l_top+1
cmpa g_cmp
lblo layout_branch_6
lbne layout_reject
layout_branch_6
ldd l_left
std l_right
ldd l_right
addd l_width
std l_right
ldd #TEXT_WIDTH
std l_temp
lda l_temp
sta g_cmp
lda l_right
cmpa g_cmp
lblo layout_branch_7
lbne layout_reject
lda l_temp+1
sta g_cmp
lda l_right+1
cmpa g_cmp
lblo layout_branch_7
lbne layout_reject
layout_branch_7
ldd l_top
std l_bottom
ldd l_bottom
addd l_height
std l_bottom
ldd #TEXT_HEIGHT
std l_temp
lda l_temp
sta g_cmp
lda l_bottom
cmpa g_cmp
lblo layout_branch_8
lbne layout_reject
lda l_temp+1
sta g_cmp
lda l_bottom+1
cmpa g_cmp
lblo layout_branch_8
lbne layout_reject
layout_branch_8
lda #$02
sta g_cmp
lda l_align
cmpa g_cmp
lblo layout_branch_9
lbne layout_reject
layout_branch_9
lda #$08
sta g_cmp
lda l_pitch
cmpa g_cmp
lblo layout_reject
layout_branch_10
ldd #$0000
std l_temp
lda l_length
sta l_temp+1
ldd l_temp
addd l_source
std l_temp
lda #$00
sta g_cmp
lda l_temp
cmpa g_cmp
lbne layout_branch_12
lda #$00
sta g_cmp
lda l_temp+1
cmpa g_cmp
lbne layout_branch_12
jmp layout_branch_11
layout_branch_12
lda l_source
sta g_cmp
lda l_temp
cmpa g_cmp
lblo layout_reject
lbne layout_branch_13
lda l_source+1
sta g_cmp
lda l_temp+1
cmpa g_cmp
lblo layout_reject
layout_branch_13
layout_branch_11
ldd l_width
std l_temp
lsr l_temp
ror l_temp+1
lsr l_temp
ror l_temp+1
lsr l_temp
ror l_temp+1
lda l_temp+1
sta l_columns
; An empty renderer call validates font, pen and source declarations without drawing.
ldd l_left
std t_x
ldd l_top
std t_y
ldd l_source
std t_source
ldd l_left
std t_clip_l
ldd l_top
std t_clip_t
ldd l_right
std t_clip_r
ldd l_bottom
std t_clip_b
lda #$00
sta t_length
jsr text_string
lda #$00
sta g_cmp
lda t_status
cmpa g_cmp
lbne layout_branch_14
jmp layout_reject
layout_branch_14
lda #$00
sta l_pass
layout_pass
lda #$00
sta l_i
ldd l_top
std l_y
lda #$00
sta l_lines
ldd #$0000
std l_measured
layout_next
lda l_length
sta g_cmp
lda l_i
cmpa g_cmp
lblo layout_branch_15
jmp layout_pass_done
layout_branch_15
ldd l_y
std l_temp
ldd l_temp
addd #$0008
std l_temp
lda l_bottom
sta g_cmp
lda l_temp
cmpa g_cmp
lblo layout_branch_16
lbne layout_reject
lda l_bottom+1
sta g_cmp
lda l_temp+1
cmpa g_cmp
lblo layout_branch_16
lbne layout_reject
layout_branch_16
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
cmpa g_cmp
lbne layout_scan
layout_branch_17
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
cmpa g_cmp
lblo layout_branch_18
jmp layout_after_scan
layout_branch_18
lda l_columns
sta g_cmp
lda l_count
cmpa g_cmp
lblo layout_branch_19
jmp layout_after_scan
layout_branch_19
ldd #$0000
std l_pointer
lda l_i
sta l_pointer+1
ldd l_pointer
addd l_source
std l_pointer
ldx l_pointer
lda ,x
sta l_code
lda #$0a
sta g_cmp
lda l_code
cmpa g_cmp
lbne layout_branch_20
jmp layout_after_scan
layout_branch_20
lda #$20
sta g_cmp
lda l_code
cmpa g_cmp
lbne layout_branch_21
layout_branch_22
lda #$00
sta g_cmp
lda l_count
cmpa g_cmp
lbne layout_branch_23
jmp layout_branch_21
layout_branch_23
lda l_i
sta l_break_i
lda l_count
sta l_break_count
layout_branch_21
lda l_count
adda #$01
sta l_count
lda l_i
adda #$01
sta l_i
jmp layout_scan
layout_after_scan
lda l_length
sta g_cmp
lda l_i
cmpa g_cmp
lblo layout_branch_24
jmp layout_position
layout_branch_24
ldd #$0000
std l_pointer
lda l_i
sta l_pointer+1
ldd l_pointer
addd l_source
std l_pointer
ldx l_pointer
lda ,x
sta l_code
lda #$0a
sta g_cmp
lda l_code
cmpa g_cmp
lbne layout_branch_26
jmp layout_branch_25
layout_branch_26
lda #$20
sta g_cmp
lda l_code
cmpa g_cmp
lbne layout_branch_27
jmp layout_branch_25
layout_branch_27
; Backtrack to the last eligible space; otherwise split an overlong word.
lda #$00
sta g_cmp
; BEGIN BREAK FIX
lda l_break_count
cmpa g_cmp
lbne layout_branch_28
jmp layout_position
layout_branch_28
lda l_break_count
sta l_count
lda l_break_i
sta l_i
; END BREAK FIX
layout_branch_25
lda l_i
adda #$01
sta l_i
layout_position
ldd #$0000
std l_line_width
lda l_count
sta l_line_width+1
; BEGIN WIDTH FIX: eight pixels of advance for every counted glyph.
lsl l_line_width+1
rol l_line_width
lsl l_line_width+1
rol l_line_width
lsl l_line_width+1
rol l_line_width
; END WIDTH FIX
lda l_width
sta g_cmp
lda l_line_width
cmpa g_cmp
lblo layout_branch_29
lbne layout_reject
lda l_width+1
sta g_cmp
lda l_line_width+1
cmpa g_cmp
lblo layout_branch_29
lbne layout_reject
layout_branch_29
lda l_measured
sta g_cmp
lda l_line_width
cmpa g_cmp
lblo layout_branch_30
lbne layout_branch_31
lda l_measured+1
sta g_cmp
lda l_line_width+1
cmpa g_cmp
lblo layout_branch_30
lbeq layout_branch_30
layout_branch_31
ldd l_line_width
std l_measured
layout_branch_30
ldd l_width
std l_temp
ldd l_temp
subd l_line_width
std l_temp
lda #$00
sta g_cmp
lda l_align
cmpa g_cmp
lbne layout_branch_34
jmp layout_branch_32
layout_branch_34
lda #$02
sta g_cmp
lda l_align
cmpa g_cmp
lbne layout_branch_35
jmp layout_branch_33
layout_branch_35
lsr l_temp
ror l_temp+1
jmp layout_branch_33
layout_branch_32
ldd #$0000
std l_temp
layout_branch_33
; BEGIN ORIGIN FIX: the unused-space offset is relative to the rectangle.
ldd l_left
std l_x
ldd l_x
addd l_temp
std l_x
; END ORIGIN FIX
lda #$00
sta g_cmp
lda l_lines
cmpa g_cmp
lbne layout_branch_36
layout_branch_37
ldd l_x
std l_first_x
layout_branch_36
ldd l_x
std l_last_x
ldd l_last_x
addd l_line_width
std l_last_x
ldd l_y
std l_last_y
lda l_count
sta l_last_count
lda l_lines
adda #$01
sta l_lines
lda #$00
sta g_cmp
lda l_pass
cmpa g_cmp
lbne layout_branch_38
jmp layout_advance
layout_branch_38
ldd l_x
std t_x
ldd l_y
std t_y
ldd #$0000
std t_source
lda l_start
sta t_source+1
ldd t_source
addd l_source
std t_source
lda l_count
sta t_length
jsr text_string
ldd l_calls
addd t_calls
std l_calls
lda l_writes+3
adda t_writes+3
sta l_writes+3
lda l_writes+2
adca t_writes+2
sta l_writes+2
lda l_writes+1
adca t_writes+1
sta l_writes+1
lda l_writes
adca t_writes
sta l_writes
layout_advance
ldd #$0000
std l_temp
lda l_pitch
sta l_temp+1
ldd l_y
addd l_temp
std l_y
jmp layout_next
layout_pass_done
lda #$00
sta g_cmp
lda l_pass
cmpa g_cmp
lbne layout_branch_39
layout_branch_40
lda #$01
sta l_pass
jmp layout_pass
layout_branch_39
lda l_length
sta l_consumed
lda #$01
sta l_status
rts
layout_reject
jsr layout_reset
rts
layout_endCaptures 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/6809.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
ldd #PANEL_LEFT
std TL+0
ldd #PANEL_TOP
std TL+2
ldd #PANEL_WIDTH
std TL+4
ldd #PANEL_HEIGHT
std TL+6
ldd #label_data
std TL+10
lda #PANEL_ALIGN
sta TL+8
lda #LINE_PITCH
sta TL+9
lda #MESSAGE_LENGTH
sta TL+12
ldd #metric_data
std TL+64
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 equ 2
CUSTOM_LAYOUT equ 1
; SPDX-License-Identifier: MIT
; Complete caller: change layout inputs without changing the reusable renderer.
PANEL_LEFT equ 21
PANEL_TOP equ 37
PANEL_WIDTH equ 104
PANEL_HEIGHT equ 80
PANEL_ALIGN equ 2
LINE_PITCH equ 16
MESSAGE_LENGTH equ 44
include "caller.inc"
Edit a glyph’s eight row bytes and its width together. Keep ink inside the declared width and width + spacing ≤ 8. A smaller width crops pixels; it does not shrink the letter. A space has width 3 in this original font. A different font needs readable, stable rows and a matching table for its declared range. Those allocations must not overlap the bitmap, scratch, code or stack.
Letters spread beyond their measured end
This next fragment is deliberately wrong. Measurement uses proportional advances, but drawing still moves eight pixels after each glyph. I! WIDE starts at the planned position, then spreads out. Changing the panel origin would hide one symptom while leaving the disagreement in place.
Deliberately wrong: retain a fixed eight-pixel movement
; BEGIN WRONG
ldd l_x
addd #8
std l_x
Restore the very advance used by the planner:
Correct: advance by this glyph's width plus spacing
; BEGIN FIX
ldd l_x
addd l_advance
std l_x
What the smaller letters cost
This layout variant uses 2,230 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,058 and 470 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 dragon reset/display configuration is qualified with pinned Asm198x 0.0.71 and Emu198x 0.41.0; stock-OS loading, live tearing, original hardware and other modes remain separate checks.
Read the reusable proportional component
Proportional measurement and word-aware layout over the existing renderer
; SPDX-License-Identifier: MIT
; Bounded proportional layout over the unchanged eight-row bitmap-text component.
; Include bitmap-text once; reserve TL=80, T=96 and BP=48 separate bytes.
; TL inputs 0..12 survive. TL+64 metric pointer, TL+66 spacing are additional preserved inputs. T/BP are owned workspaces. See README.md.
l_left equ TL+0
l_top equ TL+2
l_width equ TL+4
l_height equ TL+6
l_align equ TL+8
l_pitch equ TL+9
l_source equ TL+10
l_length equ TL+12
l_status equ TL+13
l_consumed equ TL+14
l_lines equ TL+15
l_last_count equ TL+16
l_measured equ TL+18
l_first_x equ TL+20
l_last_x equ TL+22
l_last_y equ TL+24
l_calls equ TL+26
l_writes equ TL+28
l_mode equ TL+32
l_pass equ TL+33
l_i equ TL+34
l_start equ TL+35
l_count equ TL+36
l_columns equ TL+37
l_temp equ TL+38
l_right equ TL+40
l_bottom equ TL+42
l_y equ TL+44
l_x equ TL+46
l_pointer equ TL+48
l_code equ TL+50
l_line_width equ TL+52
l_work equ TL+54
l_break_i equ TL+58
l_break_count equ TL+59
l_metrics equ TL+64
l_spacing equ TL+66
l_advance equ TL+68
l_scan_width equ TL+70
l_break_width equ TL+72
l_glyph equ TL+74
l_metric_i equ TL+75
l_draw_i equ TL+77
layout_measure
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
ldd #$0000
std l_measured
ldd #$0000
std l_first_x
ldd #$0000
std l_last_x
ldd #$0000
std l_last_y
ldd #$0000
std l_calls
lda #$00
sta l_writes+3
lda #$00
sta l_writes+2
lda #$00
sta l_writes+1
lda #$00
sta l_writes
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
cmpa g_cmp
lblo layout_reject
lbne layout_branch_1
lda #$08
sta g_cmp
lda l_width+1
cmpa g_cmp
lblo layout_reject
layout_branch_1
lda #$00
sta g_cmp
lda l_height
cmpa g_cmp
lblo layout_reject
lbne layout_branch_2
lda #$08
sta g_cmp
lda l_height+1
cmpa g_cmp
lblo layout_reject
layout_branch_2
ldd #TEXT_WIDTH
std l_temp
lda l_temp
sta g_cmp
lda l_width
cmpa g_cmp
lblo layout_branch_3
lbne layout_reject
lda l_temp+1
sta g_cmp
lda l_width+1
cmpa g_cmp
lblo layout_branch_3
lbne layout_reject
layout_branch_3
ldd #TEXT_HEIGHT
std l_temp
lda l_temp
sta g_cmp
lda l_height
cmpa g_cmp
lblo layout_branch_4
lbne layout_reject
lda l_temp+1
sta g_cmp
lda l_height+1
cmpa g_cmp
lblo layout_branch_4
lbne layout_reject
layout_branch_4
ldd #TEXT_WIDTH
std l_temp
lda l_temp
sta g_cmp
lda l_left
cmpa g_cmp
lblo layout_branch_5
lbne layout_reject
lda l_temp+1
sta g_cmp
lda l_left+1
cmpa g_cmp
lblo layout_branch_5
lbne layout_reject
layout_branch_5
ldd #TEXT_HEIGHT
std l_temp
lda l_temp
sta g_cmp
lda l_top
cmpa g_cmp
lblo layout_branch_6
lbne layout_reject
lda l_temp+1
sta g_cmp
lda l_top+1
cmpa g_cmp
lblo layout_branch_6
lbne layout_reject
layout_branch_6
ldd l_left
std l_right
ldd l_right
addd l_width
std l_right
ldd #TEXT_WIDTH
std l_temp
lda l_temp
sta g_cmp
lda l_right
cmpa g_cmp
lblo layout_branch_7
lbne layout_reject
lda l_temp+1
sta g_cmp
lda l_right+1
cmpa g_cmp
lblo layout_branch_7
lbne layout_reject
layout_branch_7
ldd l_top
std l_bottom
ldd l_bottom
addd l_height
std l_bottom
ldd #TEXT_HEIGHT
std l_temp
lda l_temp
sta g_cmp
lda l_bottom
cmpa g_cmp
lblo layout_branch_8
lbne layout_reject
lda l_temp+1
sta g_cmp
lda l_bottom+1
cmpa g_cmp
lblo layout_branch_8
lbne layout_reject
layout_branch_8
lda #$02
sta g_cmp
lda l_align
cmpa g_cmp
lblo layout_branch_9
lbne layout_reject
layout_branch_9
lda #$08
sta g_cmp
lda l_pitch
cmpa g_cmp
lblo layout_reject
layout_branch_10
ldd #$0000
std l_temp
lda l_length
sta l_temp+1
ldd l_temp
addd l_source
std l_temp
lda #$00
sta g_cmp
lda l_temp
cmpa g_cmp
lbne layout_branch_12
lda #$00
sta g_cmp
lda l_temp+1
cmpa g_cmp
lbne layout_branch_12
jmp layout_branch_11
layout_branch_12
lda l_source
sta g_cmp
lda l_temp
cmpa g_cmp
lblo layout_reject
lbne layout_branch_13
lda l_source+1
sta g_cmp
lda l_temp+1
cmpa g_cmp
lblo layout_reject
layout_branch_13
layout_branch_11
ldd l_width
std l_temp
lsr l_temp
ror l_temp+1
lsr l_temp
ror l_temp+1
lsr l_temp
ror l_temp+1
lda l_temp+1
sta l_columns
; An empty renderer call validates font, pen and source declarations without drawing.
ldd l_left
std t_x
ldd l_top
std t_y
ldd l_source
std t_source
ldd l_left
std t_clip_l
ldd l_top
std t_clip_t
ldd l_right
std t_clip_r
ldd l_bottom
std t_clip_b
lda #$00
sta t_length
jsr text_string
lda #$00
sta g_cmp
lda t_status
cmpa g_cmp
lbne layout_branch_14
jmp layout_reject
layout_branch_14
lda #$02
sta g_cmp
lda l_spacing
cmpa g_cmp
lblo layout_branch_15
lbne layout_reject
layout_branch_15
ldd l_metrics
std l_temp
ldd l_temp
addd t_count
std l_temp
lda #$00
sta g_cmp
lda l_temp
cmpa g_cmp
lbne layout_branch_17
lda #$00
sta g_cmp
lda l_temp+1
cmpa g_cmp
lbne layout_branch_17
jmp layout_branch_16
layout_branch_17
lda l_metrics
sta g_cmp
lda l_temp
cmpa g_cmp
lblo layout_reject
lbne layout_branch_18
lda l_metrics+1
sta g_cmp
lda l_temp+1
cmpa g_cmp
lblo layout_reject
layout_branch_18
layout_branch_16
ldd #$0000
std l_metric_i
layout_validate_metric
lda t_count
sta g_cmp
lda l_metric_i
cmpa g_cmp
lblo layout_branch_19
lbne layout_metrics_ready
lda t_count+1
sta g_cmp
lda l_metric_i+1
cmpa g_cmp
lblo layout_branch_19
jmp layout_metrics_ready
layout_branch_19
ldd l_metrics
std l_pointer
ldd l_pointer
addd l_metric_i
std l_pointer
jsr layout_read_pointer
sta l_glyph
lda #$01
sta g_cmp
lda l_glyph
cmpa g_cmp
lblo layout_reject
layout_branch_20
ldd #$0000
std l_temp
lda l_glyph
sta l_temp+1
ldd #$0000
std l_advance
lda l_spacing
sta l_advance+1
ldd l_temp
addd l_advance
std l_temp
lda #$00
sta g_cmp
lda l_temp
cmpa g_cmp
lblo layout_branch_21
lbne layout_reject
lda #$08
sta g_cmp
lda l_temp+1
cmpa g_cmp
lblo layout_branch_21
lbne layout_reject
layout_branch_21
ldd l_metric_i
addd #$0001
std l_metric_i
jmp layout_validate_metric
layout_metrics_ready
lda #$00
sta l_pass
layout_pass
lda #$00
sta l_i
ldd l_top
std l_y
lda #$00
sta l_lines
ldd #$0000
std l_measured
layout_next
lda l_length
sta g_cmp
lda l_i
cmpa g_cmp
lblo layout_branch_22
jmp layout_pass_done
layout_branch_22
ldd l_y
std l_temp
ldd l_temp
addd #$0008
std l_temp
lda l_bottom
sta g_cmp
lda l_temp
cmpa g_cmp
lblo layout_branch_23
lbne layout_reject
lda l_bottom+1
sta g_cmp
lda l_temp+1
cmpa g_cmp
lblo layout_branch_23
lbne layout_reject
layout_branch_23
lda l_i
sta l_start
lda #$00
sta l_count
lda #$00
sta l_break_count
ldd #$0000
std l_line_width
layout_scan
lda l_length
sta g_cmp
lda l_i
cmpa g_cmp
lblo layout_branch_24
jmp layout_after_scan
layout_branch_24
ldd #$0000
std l_pointer
lda l_i
sta l_pointer+1
ldd l_pointer
addd l_source
std l_pointer
ldx l_pointer
lda ,x
sta l_code
lda #$01
sta g_cmp
lda l_mode
cmpa g_cmp
lbne layout_branch_25
layout_branch_26
lda #$0a
sta g_cmp
lda l_code
cmpa g_cmp
lbne layout_branch_27
jmp layout_after_scan
layout_branch_27
layout_branch_25
jsr layout_glyph_advance
ldd l_line_width
std l_scan_width
ldd l_scan_width
addd l_advance
std l_scan_width
lda #$01
sta g_cmp
lda l_mode
cmpa g_cmp
lbne layout_branch_28
layout_branch_29
lda l_width
sta g_cmp
lda l_scan_width
cmpa g_cmp
lblo layout_branch_30
lbne layout_after_scan
lda l_width+1
sta g_cmp
lda l_scan_width+1
cmpa g_cmp
lblo layout_branch_30
lbne layout_after_scan
layout_branch_30
layout_branch_28
lda #$20
sta g_cmp
lda l_code
cmpa g_cmp
lbne layout_branch_31
layout_branch_32
lda #$00
sta g_cmp
lda l_count
cmpa g_cmp
lbne layout_branch_33
jmp layout_branch_31
layout_branch_33
lda l_i
sta l_break_i
lda l_count
sta l_break_count
ldd l_line_width
std l_break_width
layout_branch_31
ldd l_scan_width
std l_line_width
lda l_count
adda #$01
sta l_count
lda l_i
adda #$01
sta l_i
jmp layout_scan
layout_after_scan
lda #$01
sta g_cmp
lda l_mode
cmpa g_cmp
lbne layout_position
layout_branch_34
lda l_length
sta g_cmp
lda l_i
cmpa g_cmp
lblo layout_branch_35
jmp layout_position
layout_branch_35
ldd #$0000
std l_pointer
lda l_i
sta l_pointer+1
ldd l_pointer
addd l_source
std l_pointer
ldx l_pointer
lda ,x
sta l_code
lda #$0a
sta g_cmp
lda l_code
cmpa g_cmp
lbne layout_branch_37
jmp layout_branch_36
layout_branch_37
lda #$20
sta g_cmp
lda l_code
cmpa g_cmp
lbne layout_branch_38
jmp layout_branch_36
layout_branch_38
; Backtrack to the last eligible space; otherwise split an overlong word.
lda #$00
sta g_cmp
lda l_break_count
cmpa g_cmp
lbne layout_branch_39
jmp layout_position
layout_branch_39
lda l_break_count
sta l_count
lda l_break_i
sta l_i
ldd l_break_width
std l_line_width
layout_branch_36
lda l_i
adda #$01
sta l_i
layout_position
lda l_start
sta g_cmp
lda l_i
cmpa g_cmp
lbne layout_branch_40
layout_branch_41
lda #$00
sta g_cmp
lda l_count
cmpa g_cmp
lbne layout_branch_42
jmp layout_reject
layout_branch_42
layout_branch_40
lda #$02
sta g_cmp
lda l_mode
cmpa g_cmp
lbne layout_branch_43
jmp layout_measure_done
layout_branch_43
lda l_width
sta g_cmp
lda l_line_width
cmpa g_cmp
lblo layout_branch_44
lbne layout_reject
lda l_width+1
sta g_cmp
lda l_line_width+1
cmpa g_cmp
lblo layout_branch_44
lbne layout_reject
layout_branch_44
lda l_measured
sta g_cmp
lda l_line_width
cmpa g_cmp
lblo layout_branch_45
lbne layout_branch_46
lda l_measured+1
sta g_cmp
lda l_line_width+1
cmpa g_cmp
lblo layout_branch_45
lbeq layout_branch_45
layout_branch_46
ldd l_line_width
std l_measured
layout_branch_45
ldd l_width
std l_temp
ldd l_temp
subd l_line_width
std l_temp
lda #$00
sta g_cmp
lda l_align
cmpa g_cmp
lbne layout_branch_49
jmp layout_branch_47
layout_branch_49
lda #$02
sta g_cmp
lda l_align
cmpa g_cmp
lbne layout_branch_50
jmp layout_branch_48
layout_branch_50
lsr l_temp
ror l_temp+1
jmp layout_branch_48
layout_branch_47
ldd #$0000
std l_temp
layout_branch_48
; BEGIN ORIGIN FIX: the unused-space offset is relative to the rectangle.
ldd l_left
std l_x
ldd l_x
addd l_temp
std l_x
; END ORIGIN FIX
lda #$00
sta g_cmp
lda l_lines
cmpa g_cmp
lbne layout_branch_51
layout_branch_52
ldd l_x
std l_first_x
layout_branch_51
ldd l_x
std l_last_x
ldd l_last_x
addd l_line_width
std l_last_x
ldd l_y
std l_last_y
lda l_count
sta l_last_count
lda l_lines
adda #$01
sta l_lines
lda #$00
sta g_cmp
lda l_pass
cmpa g_cmp
lbne layout_branch_53
jmp layout_advance
layout_branch_53
lda l_start
sta l_draw_i
layout_draw_glyph
ldd #$0000
std l_temp
lda l_draw_i
sta l_temp+1
ldd #$0000
std l_pointer
lda l_start
sta l_pointer+1
ldd #$0000
std l_advance
lda l_count
sta l_advance+1
ldd l_pointer
addd l_advance
std l_pointer
lda l_pointer
sta g_cmp
lda l_temp
cmpa g_cmp
lblo layout_branch_54
lbne layout_advance
lda l_pointer+1
sta g_cmp
lda l_temp+1
cmpa g_cmp
lblo layout_branch_54
jmp layout_advance
layout_branch_54
ldd l_source
std l_pointer
ldd l_pointer
addd l_temp
std l_pointer
jsr layout_read_pointer
sta l_code
jsr layout_glyph_advance
ldd l_x
std t_x
ldd l_y
std t_y
ldd l_x
std t_clip_l
ldd l_x
std t_clip_r
ldd t_clip_r
addd l_advance
std t_clip_r
lda l_glyph
sta t_code
jsr text_char
ldd l_calls
addd t_calls
std l_calls
lda l_writes+3
adda t_writes+3
sta l_writes+3
lda l_writes+2
adca t_writes+2
sta l_writes+2
lda l_writes+1
adca t_writes+1
sta l_writes+1
lda l_writes
adca t_writes
sta l_writes
; BEGIN ADVANCE FIX: move by this glyph metric plus letter spacing.
ldd l_x
addd l_advance
std l_x
; END ADVANCE FIX
lda l_draw_i
adda #$01
sta l_draw_i
jmp layout_draw_glyph
layout_advance
ldd #$0000
std l_temp
lda l_pitch
sta l_temp+1
ldd l_y
addd l_temp
std l_y
jmp layout_next
layout_pass_done
lda #$00
sta g_cmp
lda l_pass
cmpa g_cmp
lbne layout_branch_55
layout_branch_56
lda #$01
sta l_pass
jmp layout_pass
layout_branch_55
lda l_length
sta l_consumed
lda #$01
sta l_status
rts
layout_measure_done
ldd l_line_width
std l_measured
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
cmpa g_cmp
lblo layout_branch_58
layout_branch_59
ldd #$0000
std l_temp
lda l_glyph
sta l_temp+1
ldd #$0000
std l_pointer
lda t_first
sta l_pointer+1
ldd l_temp
subd l_pointer
std l_temp
lda t_count
sta g_cmp
lda l_temp
cmpa g_cmp
lblo layout_branch_57
lbne layout_branch_60
lda t_count+1
sta g_cmp
lda l_temp+1
cmpa g_cmp
lblo layout_branch_57
layout_branch_60
layout_branch_58
lda t_fallback
sta l_glyph
ldd #$0000
std l_temp
lda l_glyph
sta l_temp+1
ldd #$0000
std l_pointer
lda t_first
sta l_pointer+1
ldd l_temp
subd l_pointer
std l_temp
layout_branch_57
ldd l_metrics
std l_pointer
ldd l_pointer
addd l_temp
std l_pointer
jsr layout_read_pointer
sta l_advance+1
lda #$00
sta l_advance
ldd #$0000
std l_temp
lda l_spacing
sta l_temp+1
ldd l_advance
addd l_temp
std l_advance
rts
layout_read_pointer
ldx l_pointer
lda ,x
rts
layout_endAdvance 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.