The rendering pipeline

Explanation: how a page becomes bytes on the wire, one stage at a time. Each stage is a pure function over values, which is why nearly all of it runs without a BBC Micro.

Document                                 what is to be said
   │  sextile.content.blocks
   │  sextile.viewdata.typesetting
   ▼
Row  (text, colour, indent)
   │  sextile.formatting, sextile.layout
   │  sextile.viewdata.canvas
   ▼
Frame  (24 × 40 cells)                    what the screen shows
   │  sextile.viewdata.frame
   ▼
bytes                                     the wire stream
   │  sextile.viewdata.encoding

A Document is structural, not typographic: Paragraph, Quote, Code and the rest say what a thing is, and colour is spent telling a quotation from a listing rather than on an italic. typesetting.rows_for flattens it into Rows — a quotation cyan, a listing green, nesting indented — and leaves where a frame ends to the layer that draws them.

A document, drawn

from sextile import Page, PageLayout, PageRequest, PageRouter, Sextile
from sextile.content.blocks import Code, Document, Paragraph
from sextile.formatting import Prose
from sextile.viewdata.typesetting import rows_for

_DOC = Document(blocks=(
    Paragraph(("A page begins as a document.",)),
    Code(("PRINT CHR$(141)",)),
))

router = PageRouter()


@router.page("1", name="page", title="Pipeline")
async def page(request: PageRequest) -> Page:
    return PageLayout(title="PIPELINE", parts=[Prose(entries=rows_for(_DOC))]).build(request)


app = Sextile(name="Demo", pages=[*router])
 PIPELINE                             1a
  
A page begins as a document.            
                                        
 PRINT CHR$(141)                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
                                        
  
 0 index                                

The same frame, on the wire

viewdata.frame is a fixed grid, not a stream of writes, and serialising it is: hide the cursor, clear, home, then the cells that say something. The C0 range holds two namespaces — a bare byte is screen or cursor control, ESC then byte

  • 0x40 a teletext attribute — kept straight in viewdata.encoding and measured in Viewdata encoding. The frame above goes down the wire as:

0000  14 0C 1E 1B 46 50 49 50 45 4C 49 4E 45 20 20 20  |....FPIPELINE   |
0010  20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20  |                |
0020  20 20 20 20 20 20 20 20 20 1B 47 31 61 1B 5A 1B  |         .G1a.Z.|
0030  54 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F  |T...............|
0040  7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F  |................|
0050  7F 7F 7F 7F 7F 0D 0A 41 20 70 61 67 65 20 62 65  |.......A page be|
0060  67 69 6E 73 20 61 73 20 61 20 64 6F 63 75 6D 65  |gins as a docume|
0070  6E 74 2E 0D 0A 0D 0A 1B 42 50 52 49 4E 54 20 43  |nt......BPRINT C|
0080  48 52 24 28 31 34 31 29 0D 0A 0D 0A 0D 0A 0D 0A  |HR$(141)........|
0090  0D 0A 0D 0A 0D 0A 0D 0A 0D 0A 0D 0A 0D 0A 0D 0A  |................|
00A0  0D 0A 0D 0A 0D 0A 0D 0A 0D 0A 0D 0A 1B 5A 1B 54  |.............Z.T|
00B0  7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F  |................|
00C0  7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F 7F  |................|
00D0  7F 7F 7F 7F 0D 0A 1B 43 30 20 69 6E 64 65 78     |.......C0 index|

Economy on the wire

Two measures cut a frame’s bytes without changing a pixel, both settled on a real screen and written up in Viewdata encoding:

  • A colour attribute occupies a cell, and canvas counts it, so a row that changes colour twice has thirty-eight columns for text and no layer above has to know. It is why colour could not have been added later.

  • Trailing blanks are not sent. The frame clears the screen first, so a space at the end of a row overwrites nothing; after the last row with anything on it, nothing is sent at all. Real pages lose between a third and three quarters of their bytes.