This is the third JonesForth-style commentary pass over the resident editor. It follows the lifecycle and command-transaction layers into the rendering system: how one packed row becomes proportional text, pictures, cursor, page/block marks, a scrolled or zoomed Spectrum bitmap band, and finally an information line.
The corresponding live comments are concentrated in:
src/desktop/editor/core/commands/line-header.asm
src/desktop/editor/core/workspaces/helpers.asm
src/desktop/editor/core/layout/proportional-layout.asm
src/desktop/editor/core/workspaces/input-and-raster.asm
src/desktop/editor/core/pictures/picture-codec.asm
src/desktop/editor/core/rendering/raster-renderer.asm
src/desktop/editor/core/rendering/raster-cursor.asm
src/desktop/editor/core/pictures/keyboard-and-projection.asm
src/desktop/editor/core/document/current-line-storage.asm
src/desktop/editor/core/rendering/status-line.asm
Every packed ordinary row begins with a header. Its fields are orthogonal:
bits 0..1 initial font, 0..3
bits 2..3 alignment: left, justified, centred, right
bit 4 temporary/deletion state used by structural commands
bits 5..6 picture placement: none, left, right, centred-picture-only
bit 7 ordinary-row anchor/state bit
The rendering path copies the header into working state before interpreting the rest of the row. Alignment and picture placement are not embedded as invisible text commands; they alter the horizontal coordinate system for the whole row.
Picture-bearing records carry a two-byte picture reference immediately after the header. Text follows that reference except for the centred-picture-only mode, which renders the picture and returns without scanning text.
Bytes below $04 inside the expanded text are font selectors, not printable
glyphs. A row therefore behaves like a tiny program:
font = header.initial_font
for byte in text:
if byte < 4:
font = byte
else:
measure_or_render(byte, font)
normalize_font_controls canonicalizes this stream after editing:
- a leading selector is absorbed into header bits 0..1;
- a selector equal to the already active font is removed;
- consecutive selectors collapse to the final effective selection;
- the cursor pointer is repaired whenever bytes disappear.
This normalization matters beyond compactness. ENTER split copies the active font at the split point into the new right-row header. Delete and Backspace can remove a selector and thereby make later text wider, which is why those commands must measure a candidate before publishing it.
The editor has four font slots at a fixed $0666-byte stride. select_font
starts one slot before font zero and advances font+1 times. It then publishes
two related biases:
glyph bias = $DF00 + $0666 * (font + 1)
width bias = glyph bias + $00E2
The word “bias” is intentional. Character codes are added directly to these bases; they are not necessarily the first physical byte of a table. The selected operands are shared by measurement, justification, drawing and cursor-width capture, ensuring that all four stages use the same font interpretation.
A glyph contributes two independent things:
- twelve bitmap bytes, one per canonical scanline;
- one proportional advance from the width table.
The bitmap need not occupy the full advance, and two successive glyphs can share one destination byte at different bit phases.
The nominal row interval is bounded by left_origin and active_right. A side
picture consumes part of that interval before text is measured:
left picture: text_left += picture_width_bytes * 8
right picture: text_right -= picture_width_bytes * 8
centred picture-only: place picture in available byte width; render no text
This is why picture width participates in fit checks. A row can contain the same text bytes yet fit in “no picture” mode and overflow beside a picture.
The picture decoder writes six vertical 16-bit pairs per byte column. Its seemingly obscure next-column constant has a simple geometrical derivation:
rows written at x: 0/1, 2/3, 4/5, 6/7, 8/9, 10/11
row stride: 97 bytes
position after last pair: x + 11*97
next column origin: x + 1
correction: 1 - 11*97 = -1066 = $FBD6
Thus 11*97 + $FBD6 = 1.
The proportional engine first interprets the stream without drawing it. Starting at the adjusted left boundary, it adds each active font's width and obtains the ordinary text end. Slack is then:
slack = adjusted_right_boundary - measured_text_end
Negative slack is overflow. Otherwise alignment chooses a start position:
left x = adjusted_left
centred x = adjusted_left + floor(slack / 2)
right x = adjusted_left + slack
justified x = adjusted_left; distribute slack over eligible spaces
The same font selectors and width biases are used during drawing, so the second pass follows the geometry proved by the first.
Justification does not replace ordinary spaces with computed gaps. It renders each space at its normal font width and inserts an additional offset before that space.
Leading spaces are not expansion points. After the first non-space, all spaces
are eligible. If there are n eligible gaps:
q, r = divmod(slack, n)
first r gaps receive q + 1 extra pixels
the remaining gaps receive q extra pixels
then the ordinary space glyph advance is applied
The quotient and remainder are stored in self-modified operands consumed by the second pass. This is a compact state machine, not unexplained arithmetic noise.
DESKTOP's drawing surface is not the 256-pixel Spectrum screen. It is a 12-scanline by 97-byte raster:
97 bytes * 8 pixels = 776 pixels wide
12 scanlines = one editor row high
storage = 1,164 bytes at $D10C
Every row is composed at full width. The raster is the authority for:
- proportional text;
- decoded picture slices;
- cursor placement;
- page and block marks;
- horizontal scrolling;
- zoom conversion;
- printer and plotter paths.
Screen projection is therefore a final view of page geometry, not the place where layout happens.
The renderer tracks horizontal position as a byte pointer plus phase 0..7. A glyph's twelve bytes are dispatched to one of eight phase-specialized routines. At phase zero the source byte replaces the destination byte. At nonzero phases, its bits are split across the current and following destination bytes and composed with existing pixels.
After a glyph, the width table advances the coordinate:
total = old_phase + width
byte_pointer += total // 8
new_phase = total & 7
This is also how cursor position becomes a proportional pixel coordinate. During active-row rendering, a callback notices the exact workspace byte under the cursor and captures the current raster pointer, phase and active-font width.
The text cursor is not persistent data and not a separate sprite. A width-indexed 16-bit mask is shifted by the captured phase into three bytes, then XORed into all twelve canonical scanlines. Applying the same routine again restores the underlying row exactly.
The mask table caps the widest cursor cell at ten pixels. The three-byte form is required because a wide cell starting late in a byte can spill into two following bytes.
The cursor is toggled after row composition and before final projection. It is therefore subject to the same zoom and viewport transformations as the glyphs it marks.
project_canonical_raster_to_screen applies transformations in this order:
text + pictures already composed
-> page boundary dash on bottom scanline
-> block membership mark at left edge
-> optional in-place zoom reduction
-> copy 32 bytes from each scanline to Spectrum bitmap
The page dash writes $66 into the first 96 bytes of the final scanline, leaving
the 97th spill byte untouched. The block marker toggles the high bit of the first
byte of every scanline. Because block inversion follows the page dash, their
intersection is $66 XOR $80 = $E6.
The visible Spectrum band is 32 bytes (256 pixels) from each canonical scanline.
configure_horizontal_viewport patches both:
- the source address used by the 12-row copy;
- the glyph renderer's left and right visibility thresholds.
The clip interval includes one spill byte beyond the visible window because a glyph beginning just outside the viewport can shift visible bits across the edge. Glyphs outside the window are still measured; only drawing is suppressed.
Higher-level horizontal movement changes the viewport in eight-byte, 64-pixel steps. The low-level primitive itself accepts any byte-aligned origin.
Zoom mode does not interpolate. It selects one of two in-place reducers based on active row width:
width <= 512 pixels -> retain every second source pixel (2:1)
width >= 513 pixels -> retain every third source pixel (3:1)
Each 97-byte scanline is reduced into its first 32 bytes. The normal viewport copy then projects those 32 bytes. The conversion destroys the composed raster, but only after all consumers that need the full-width form have finished.
The status line is not part of the document raster. It is rebuilt from editor state after the active row is projected:
logical row number
proportional cursor x
active row width
INS/OVR mode
EXT mode
CAPS mode
graphics mode
free bytes between text and picture heap fronts
It uses the Spectrum ROM 8x8 font, not the four document fonts. Each source glyph is converted into a fixed 12-scanline inverse cell:
solid border,
ROM rows 0,1,
blend(1,2),
rows 2,3,4,5,
blend(5,6),
rows 6,7,
solid border
This separate fixed-width projection gives state fields stable screen columns regardless of the current document typography.
the reconstructed source independently models:
- row-header decoding and command cycles;
- font-control normalization and cursor repair;
- font-slot biases;
- proportional measurement, pictures, alignment and justification;
- glyph phase composition and advance;
- picture pair destinations;
- page/block raster decoration;
- cursor XOR involution;
- viewport configuration and copy;
- 2:1/3:1 zoom sampling;
- status-cell and state-field projection.
the public verification checks those models against the exact assembled symbols, binary geometry and live-source explanations. The model does not replace the assembly; it makes the assembly's compact contracts reviewable and regression-testable.