The World Vol 9 · The Village
ch 100 / 105
Chapter 100

The Box the Words Sit In

Six hundred and sixty-six pixels on a ground a hundred and ninety-two wide

Halla's persona begins with a sentence of a hundred and eleven bytes. The font the client has had since chapter 21 puts each byte in a cell six pixels wide, so drawn the only way the renderer knows how to draw a string, the sentence is six hundred and sixty-six pixels long, and the ground she stands on is a hundred and ninety-two pixels from rim to rim. Her face is sixteen pixels square and her name is thirty pixels of the same glyphs. The renderer can put all three on the frame today: a face is four blits through a table, a name is DrawTextIn, and the sentence is the same call, which advances the pen six pixels a byte and stops when the bytes run out, three and a half grounds to the right of where it started.

What is missing is a box. Not a rectangle, which FillRect has drawn since chapter 13, but a box a reader takes as the place words are said from: an edge that stands off ground, water and plants alike, a name on it, a face in it, and inside it a column of text that ends where the box does and goes on to the next line. The frame is one small picture cut into nine pieces, the corners put down once, the edges and the middle repeated until the box is the size the words need; and the words are cut at spaces into lines no wider than a column measured in the font's own pixels, before a glyph of them is drawn.

Two ways of making one are refused before the page starts. A box drawn in code, a filled rectangle and eight lines round it, works, and then every corner, every highlight and every change of style is a change to the drawing code, which is the wrong place for a picture to live. A box saved as a picture at its finished size is one box: a hundred and eighty-four by fifty-nine pixels for five lines of Halla, a second file for two lines of Ander, and ten thousand pixels a file for a box that is mostly one flat colour.

The frame this page draws is twelve pixels square, and the box on the last figure is a hundred and eighty-four by fifty-nine, with a name plate over its top edge made from the same twelve pixels at forty-two by nineteen. Both halves are arithmetic over a measurement: the box measures the words, and the words measure the column. Nothing on the page asks a model anything, opens a window, a socket or a database, and every picture on it is written headlessly by the chapter's own bench and compared by hash.

By the end of the page assets/ui/panel.png and assets/palette/panel-6.png exist, written by a program that holds the frame as twelve rows of characters and compared byte for byte with the pair the book ships; internal/render has a nine-slice blit, a word-wrapper over the six-by-eight font, and a panel that lays a slot, a plate and a column out from a width and a string; and five pictures have been written by the bench, one of them the counterfactual with no wrapper in it, and one of them Halla in her frame over the valley, saying the first sentence of her persona.

Twelve pixels square, six of the sixteen, cut at four

The frame is drawn once, at the smallest size that shows every part of it, and twelve pixels is that size. Cut four pixels in from every edge and the picture falls into nine pieces: four corners of four by four, four edges of four by four, and a middle of four by four. The corners hold everything about the frame that cannot repeat, the rounded turn where the top line meets the left. An edge holds one run of the frame's lines and the field beside them, and it is what the box is made of along its sides, laid down over and over. The middle is the field, and nothing but the field, because the words sit on it and a field with a pattern in it is a field that puts pixels beside every glyph.

Three lines of frame stand between the outside and the field, and six of the valley's sixteen colours draw them. The outermost line is ink all the way round, for the reason chapter 17 gave when the walker got its outline: a closed dark edge is what lets a small thing sit on any background, and the panel is going to sit on soil, on water, on the green of a plant, and on all three at once. Inside that are two lines that are lit on the top and left and shaded on the bottom and right, because the light in this world has come from the upper left since the walker was drawn: bone over rock-m where the light lands, rock-l over rock-d where it does not, with the horizontal lines owning the corners where the two meet. The field is water-d, the darkest step of the water ramp and one entry above ink. The corner pixel of the outer line is left off, so the corner is rounded by one pixel and what is behind the box shows through it, which is the alpha channel doing for a frame what it has done for every sprite since chapter 16.

Two of those six were chosen by measuring and not by looking, and the run that measures them is on the page further down. The field is not ink, though ink would make the words stand off it hardest, because a villager's face is going into this box and every outline on that face is ink: on an ink field the face has no edge. And the lines round the portrait are not ink either, for the same reason, so the slot the face sits in gets a field of its own, the water midtone chapter 99's card stood three faces on, and a ring of rock-m, the frame's own lit inner line.

⚙ Tool — a pixel editor, if you would rather draw the frame than type it

The frame below is twelve rows of twelve characters and it needs no editor. If you would rather see it while you change it, LibreSprite (libresprite.github.io) loads assets/palette/valley-16.gpl as a palette, reduces the colour picker to the sixteen this world allows, and shows a twelve-by-twelve canvas at any zoom; draw it there with a four-pixel grid on, and carry each row into the table a character at a time. The table is what ships, because the table is what a program can rebuild.

▣ Build · stage 1 — the frame, as characters

Create cmd/mkpanel/art.go. Every pixel is a letter and every letter is an entry of the frame's own strip, exactly as chapter 99's parts are; a space is a pixel that is not there. The comment above the table is the key, and the four spaces are the corners.

// cmd/mkpanel/art.go
package main

// The frame, written as characters.
//
// Twelve pixels square, and cut four from each edge into nine pieces:
// four corners that are put down once, four edges that are repeated
// along their sides, and a middle that is repeated across the inside.
// Everything the box is made of is in these twelve rows, and a box of
// any size from eight pixels upward is these rows put down in pieces.
//
// Three lines of frame stand between the outside and the field. The
// outermost is ink all the way round, the closed dark edge that lets
// the box sit on ground, water or a plant without its lines going
// missing; the corner pixel is left off, so the corner is rounded and
// what is behind shows through it. Inside that, two lines that are lit
// on the top and left and shaded on the bottom and right, because the
// light in this world has come from the upper left since the walker:
// bone over rock-m where the light lands, rock-l over rock-d where it
// does not, the horizontal lines owning the corners they meet at. The
// field is water-d, the darkest step of the water ramp and one entry
// above ink, chosen for the words to sit on: bone glyphs on it differ
// in every channel by more than a hundred, and a portrait's ink
// outline never touches it, because the slot has a field of its own.
//
//	o  ink       the outer line, never anything else
//	b  bone      the lit outer line, top and left
//	m  rock-m    the lit inner line, top and left
//	l  rock-l    the shaded outer line, bottom and right
//	d  rock-d    the shaded inner line, bottom and right
//	f  water-d   the field, which is every pixel of the middle piece
//
// A space is a pixel that is not there: the four corners only.
var frame = []string{
	" oooooooooo ",
	"obbbbbbbbbbo",
	"obmmmmmmmmlo",
	"obmffffffdlo",
	"obmffffffdlo",
	"obmffffffdlo",
	"obmffffffdlo",
	"obmffffffdlo",
	"obmffffffdlo",
	"obddddddddlo",
	"ollllllllllo",
	" oooooooooo ",
}

Read it as the nine pieces. Rows 0 to 3 of columns 0 to 3 are the top-left corner: the missing pixel and three of ink, then b and m turning the corner, then one pixel of field. Rows 0 to 3 of columns 4 to 7 are the top edge, one row each: ink, bone, rock-m, field. Rows 4 to 7 of columns 4 to 7 are sixteen pixels of f, the middle. The bottom-right corner is the same shape mirrored, with d and l where the top-left has m and b. The whole frame is a hundred and forty pixels and four holes, and a box of any size is made of nothing else.

▣ Build · stage 2 — the program that writes the pair and compares it

Create cmd/mkpanel/main.go. The first half is chapter 99's mkfolk with a frame in place of a sheet: a key from characters to entries of the sixteen, a painter that refuses a character the key does not hold, and a mode that writes the frame and its strip and, given -to, compares what it wrote with what the book ships. main names every mode of the bench in one switch; the modes are printed where the page uses them, and the file compiles once the last of them is in.

// cmd/mkpanel/main.go
package main

import (
	"bytes"
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"strings"

	"theworld/exemplars"
	"theworld/internal/field"
	"theworld/internal/gene"
	"theworld/internal/render"
	"theworld/internal/scene"
	"theworld/internal/sim"
	"theworld/internal/terra"
	"theworld/internal/village"
)

// Where the two files live, the font the words are set in, and the
// sheet a face is composed from.
const (
	Frame = "assets/ui/panel.png"
	Strip = "assets/palette/panel-6.png"
	Font  = "assets/sprites/font6x8.png"
	Sheet = "assets/sprites/folk.png"
)

// Corner is where the frame is cut: the corners are this many pixels
// square, and the middle piece is what the twelve leave between them.
const Corner = 4

// key is the character each entry of the strip is drawn as, in the
// order the strip lists them. A pixel that is none of them is a
// mistake this program refuses to write out.
var key = []struct {
	ch   byte
	at   int
	what string
}{
	{'o', gene.Ink, "the outer line"},
	{'b', gene.Bone, "lit outer line, top and left"},
	{'m', gene.RockM, "lit inner line, top and left"},
	{'l', gene.RockL, "shaded outer line, bottom and right"},
	{'d', gene.RockD, "shaded inner line, bottom and right"},
	{'f', gene.WaterD, "the field"},
}
// ...
// at is the strip position one character stands for.
func at(ch byte) (int, bool) {
	for i, k := range key {
		if k.ch == ch {
			return i, true
		}
	}
	return 0, false
}

// colour is the valley colour a strip position is drawn in.
func colour(pos int) render.Color { return gene.Sixteen[key[pos].at] }

// letter is the character a frame colour stands for, for printing a
// piece back out as it was authored.
func letter(c render.Color) byte {
	if c == render.Clear {
		return '.'
	}
	for i := range key {
		if colour(i) == c {
			return key[i].ch
		}
	}
	return '?'
}

// build paints the frame and the strip from the characters.
func build() (*render.Buffer, *render.Buffer) {
	if len(frame) != len(frame[0]) {
		die(fmt.Errorf("the frame is %d rows of %d: it has to be square", len(frame), len(frame[0])))
	}
	n := len(frame)
	pic := render.NewBuffer(n, n)
	for y, line := range frame {
		if len(line) != n {
			die(fmt.Errorf("frame row %d is %d characters, want %d", y, len(line), n))
		}
		for x := 0; x < n; x++ {
			ch := line[x]
			if ch == ' ' {
				continue
			}
			pos, ok := at(ch)
			if !ok {
				die(fmt.Errorf("frame row %d column %d: no palette entry for %q", y, x, ch))
			}
			pic.Set(x, y, colour(pos))
		}
	}
	strip := render.NewBuffer(len(key), 1)
	for i := range key {
		strip.Set(i, 0, colour(i))
	}
	return pic, strip
}

// nine is the frame cut at Corner, from the characters.
func nine() *render.Nine {
	pic, _ := build()
	n, err := render.NewNine(pic, Corner, "the frame")
	if err != nil {
		die(err)
	}
	return n
}

func main() {
	mode := flag.String("mode", "frame", "frame, slices, boxes, wrap, fit, contrast, pads, pitches or panel")
	to := flag.String("to", "", "frame: write the pair into this directory and compare them with the shipped pair")
	w := flag.Int("w", 184, "the box's width in pixels: slices, wrap, fit, pads, pitches, panel")
	h := flag.Int("h", 40, "slices: the box's height in pixels")
	pad := flag.Int("pad", 3, "pixels between the frame's inner line and the content")
	pitch := flag.Int("pitch", 10, "pixels from the top of one line of text to the next")
	gap := flag.Int("gap", 4, "pixels between the slot's ring and the first glyph")
	plate := flag.Int("plate", Corner, "pixels of the name plate that lie over the box's top edge")
	who := flag.String("who", "Halla", "panel: the villager whose face and name are drawn")
	text := flag.String("text", "", "the words; blank takes the villager's persona, or the sentence -sentence names")
	sentence := flag.Int("sentence", 1, "which sentence of the persona is the words; 0 is all of it")
	memory := flag.Int("memory", 0, "panel: take the words from this memory of the committed stream instead")
	nowrap := flag.Bool("nowrap", false, "panel: draw the words as one line, wrapped to nothing")
	seed := flag.Uint64("seed", 1, "panel: the seed the ground behind the panel is generated from")
	shot := flag.String("shot", "", "boxes, pads, pitches, panel: write the picture to this file, scaled")
	scale := flag.Int("scale", 4, "how many times a picture is scaled before it is written")
	flag.Parse()

	switch *mode {
	case "frame":
		frameMode(*to)
	case "slices":
		slicesMode(*w, *h)
	case "boxes":
		boxesMode(*shot, *scale)
	case "wrap":
		wrapMode(panel(*pad, *pitch, *gap, *plate), *w, words(*who, *text, *sentence, *memory))
	case "fit":
		fitMode(*w, *gap, *plate, words(*who, *text, *sentence, *memory))
	case "contrast":
		contrastMode()
	case "pads":
		padsMode(*shot, *scale, *w, *pitch, *gap, *plate, *who, words(*who, *text, *sentence, *memory))
	case "pitches":
		pitchesMode(*shot, *scale, *w, *pad, *gap, *plate, *who, words(*who, *text, *sentence, *memory))
	case "panel":
		panelMode(*shot, *scale, panel(*pad, *pitch, *gap, *plate), *w, *who, words(*who, *text, *sentence, *memory), *nowrap, *seed)
	default:
		die(fmt.Errorf("-mode %q: frame, slices, boxes, wrap, fit, contrast, pads, pitches or panel", *mode))
	}
}

// frameMode writes the pair and reports what is on them.
func frameMode(to string) {
	pic, strip := build()

	framePath, stripPath := Frame, Strip
	if to != "" {
		framePath = filepath.Join(to, filepath.Base(Frame))
		stripPath = filepath.Join(to, filepath.Base(Strip))
	}
	write(pic, framePath)
	write(strip, stripPath)

	n, err := render.NewNine(pic, Corner, framePath)
	if err != nil {
		die(err)
	}
	tw, th := n.Tile()
	fmt.Printf("mkpanel: %s, %dx%d, cut %d from each edge: corners %dx%d, edges and middle tiled at %dx%d, %d bytes\n",
		framePath, pic.W, pic.H, Corner, Corner, Corner, tw, th, size(framePath))
	fmt.Printf("  pixels sha256 %s\n", pic.Hash())

	fmt.Printf("\n  the six entries the frame is drawn in, by their index in the valley's sixteen\n")
	for i, k := range key {
		c := colour(i)
		fmt.Printf("    %c  entry %2d  %02X%02X%02X  %s\n", k.ch, k.at, c.R(), c.G(), c.B(), k.what)
	}

	fmt.Printf("\n  the nine pieces, as characters, each one cut from the picture above\n")
	for j := 0; j < 3; j++ {
		for y := n.Slice(0, j).Y0; y < n.Slice(0, j).Y1; y++ {
			fmt.Print("    ")
			for i := 0; i < 3; i++ {
				r := n.Slice(i, j)
				if i > 0 {
					fmt.Print("  ")
				}
				for x := r.X0; x < r.X1; x++ {
					fmt.Printf("%c", letter(pic.At(x, y)))
				}
			}
			fmt.Println()
		}
		fmt.Println()
	}

	// Nothing outside the six may reach the frame. The check is here
	// and not in a test because this is the program that could put it
	// there.
	loose, clear := 0, 0
	for _, c := range pic.Pix {
		if c == render.Clear {
			clear++
			continue
		}
		if !onStrip(strip, c) {
			loose++
		}
	}
	fmt.Printf("mkpanel: %s, %dx%d, %d entries, %d bytes\n", stripPath, strip.W, strip.H, len(key), size(stripPath))
	fmt.Printf("  pixels sha256 %s\n", strip.Hash())
	fmt.Printf("  pixels on the frame the strip does not hold: %d; pixels not there: %d\n", loose, clear)
	if loose != 0 {
		os.Exit(1)
	}

	if to != "" {
		same(framePath, Frame)
		same(stripPath, Strip)
	}
}
// ...
$ go run ./cmd/mkpanel -to .
mkpanel: panel.png, 12x12, cut 4 from each edge: corners 4x4, edges and middle tiled at 4x4, 159 bytes
  pixels sha256 c111d3e64b3e49468441d7451ff8344280236a29c0ae661ff1a5dd7905fdfd27

  the six entries the frame is drawn in, by their index in the valley's sixteen
    o  entry  0  181A29  the outer line
    b  entry 15  F5EBD7  lit outer line, top and left
    m  entry  5  5A6470  lit inner line, top and left
    l  entry  6  8A979E  shaded outer line, bottom and right
    d  entry  4  323847  shaded inner line, bottom and right
    f  entry  7  243E6B  the field

  the nine pieces, as characters, each one cut from the picture above
    .ooo  oooo  ooo.
    obbb  bbbb  bbbo
    obmm  mmmm  mmlo
    obmf  ffff  fdlo

    obmf  ffff  fdlo
    obmf  ffff  fdlo
    obmf  ffff  fdlo
    obmf  ffff  fdlo

    obmf  ffff  fdlo
    obdd  dddd  ddlo
    olll  llll  lllo
    .ooo  oooo  ooo.

mkpanel: panel-6.png, 6x1, 6 entries, 89 bytes
  pixels sha256 076ea86026d6adf62e82f6e2fac77387cdb4274b5bd41d5b577a5bd7d622ffbe
  pixels on the frame the strip does not hold: 0; pixels not there: 4
  panel.png is byte for byte assets/ui/panel.png
  panel-6.png is byte for byte assets/palette/panel-6.png

A hundred and fifty-nine bytes for the frame and eighty-nine for the strip, and the last two lines are the reason the frame is characters: the program writes the pair beside the module and says whether it is the pair the book ships, every time it runs, so nobody has to trust that the committed file is what the table makes. The nine pieces are printed as they are cut, and the print is the check a reader can do by eye. Every corner is four by four with its outer pixel missing; every edge is one run of the lines; the middle is field alone. The loose-pixel count is the same refusal mkfolk makes: a colour on the frame that the strip does not hold is a mistake, and the program exits before it becomes a file.

▣ Build · stage 3 — four tests over the characters, pinned

Create cmd/mkpanel/art_test.go. The tests hold what the prose claimed about the table: the frame is three corners' worth of rows, so every piece is a corner's size; the outermost ring is ink except at the four corners, and no pixel inside it is ink, so nothing in the frame can be mistaken for a face's outline; the middle piece is field and nothing else; and the two lines on the top and left are brighter, summed over red, green and blue, than the two on the bottom and right. Each was shown to fail before the frame was right: ink in the middle piece fails the second and the third at once.

$ go test -count=1 ./cmd/mkpanel/ -run 'TestTheFrameIsTwelveSquareAndDrawnInTheStrip|TestTheOuterLineIsInkAllRoundButTheCorners|TestTheMiddlePieceIsFieldAlone|TestTheLightIsFromTheUpperLeft' -v
=== RUN   TestTheFrameIsTwelveSquareAndDrawnInTheStrip
--- PASS: TestTheFrameIsTwelveSquareAndDrawnInTheStrip (0.00s)
=== RUN   TestTheOuterLineIsInkAllRoundButTheCorners
--- PASS: TestTheOuterLineIsInkAllRoundButTheCorners (0.00s)
=== RUN   TestTheMiddlePieceIsFieldAlone
--- PASS: TestTheMiddlePieceIsFieldAlone (0.00s)
=== RUN   TestTheLightIsFromTheUpperLeft
--- PASS: TestTheLightIsFromTheUpperLeft (0.00s)
PASS
ok  	theworld/cmd/mkpanel	0.003s

Fifty-five blits for a box of forty-two by nineteen

The blit that makes a box from the frame is chapter 16's Blit called in a loop with the clip narrowed, and nothing more. Each of the nine pieces has a source rectangle on the frame and a destination rectangle on the box. A corner's destination is the box's corner, four by four, so a corner is copied once. An edge's destination is the run between two corners, four pixels wide or tall and as long as the box leaves it, so the edge piece is copied at its own size starting at one end and stepping by its own width until the run is covered. The middle's destination is everything inside the four runs, and the middle piece is copied across it the same way, row after row. Wherever the run does not divide by four, the last copy in that direction would spill past the box, and the clip cuts it: the clip is set to the destination rectangle before each piece is laid down and put back afterwards, so the loop can step freely and nothing lands outside the box.

The pieces are the frame addressed by arithmetic, which is chapter 21's font one more time. A glyph was cell i of a sheet, found by subtracting, dividing and multiplying; a piece is column i and row j of a frame, each 0, 1 or 2, and the four boundaries in each direction are 0, the corner, the picture's width less the corner, and the picture's width. The corner size is the only number the cut needs, and it is the one number a frame carries beside its pixels.

▣ Build · stage 4 — the cut, in the package that owns pixels

Create internal/render/nine.go. It lives in render beside the sprite and the font because it is the same kind of thing: a picture and a rule for cutting it. A frame with no room between its corners is refused at load, because a loop that repeats a piece of no width steps by nothing.

// internal/render/nine.go
package render

import "fmt"

// Nine is a frame drawn once at its smallest and cut into nine pieces:
// four corners, four edges and a middle. A box of any size from two
// corners upward is made from it by putting the corners down unchanged,
// repeating the edge pieces along the sides, and repeating the middle
// piece across the inside. The picture is one small file, and the box
// it makes is as wide as the words need.
//
// The cut is one number, C. The corners are C pixels square, so the
// edges and the middle are whatever the picture has left between them:
// on a picture W pixels wide the top edge piece is W-2C wide and C
// tall, and that width is the tile the edge repeats at.
type Nine struct {
	Pix *Buffer // the frame as drawn, every piece in place
	C   int     // the corner size in pixels; the middle tile is what is left

	sheet *Sheet // Pix wrapped for Blit, which copies rectangles of sheets
}

// LoadNine reads a frame and cuts it at c. A picture with no room for a
// middle piece is refused: a frame whose corners meet has no edge to
// repeat, and the loop that repeats one would step by nothing.
func LoadNine(path string, c int) (*Nine, error) {
	pix, err := loadStraight(path)
	if err != nil {
		return nil, err
	}
	return NewNine(pix, c, path)
}

// NewNine cuts a frame already in memory; what is the picture's name in
// a complaint.
func NewNine(pix *Buffer, c int, what string) (*Nine, error) {
	if c < 1 || pix.W <= 2*c || pix.H <= 2*c {
		return nil, fmt.Errorf("%s is %dx%d, which leaves nothing between corners of %d", what, pix.W, pix.H, c)
	}
	return &Nine{Pix: pix, C: c, sheet: &Sheet{Pix: pix, Cell: 1}}, nil
}

// Tile is the size of the piece the edges and the middle repeat at:
// the picture's width and height with the two corners taken off.
func (n *Nine) Tile() (int, int) { return n.Pix.W - 2*n.C, n.Pix.H - 2*n.C }

// Slice is the source rectangle of one of the nine pieces, column i
// and row j counted from the top left, each 0, 1 or 2. Column 0 is C
// wide, column 1 is the tile, column 2 is C wide again; the rows are
// cut the same way.
func (n *Nine) Slice(i, j int) Rect {
	tw, th := n.Tile()
	xs := [4]int{0, n.C, n.C + tw, n.Pix.W}
	ys := [4]int{0, n.C, n.C + th, n.Pix.H}
	return Rect{X0: xs[i], Y0: ys[j], X1: xs[i+1], Y1: ys[j+1]}
}

// Tiles is how many times the middle piece is put down across and down
// a box, the last one cut short where the box does not divide. A box
// with no room between its corners has no middle and counts nothing.
func (n *Nine) Tiles(box Rect) (across, down int) {
	tw, th := n.Tile()
	w, h := box.X1-box.X0-2*n.C, box.Y1-box.Y0-2*n.C
	if w <= 0 || h <= 0 {
		return 0, 0
	}
	return (w + tw - 1) / tw, (h + th - 1) / th
}
// ...

Tiles is the count the interlude below works by hand and the bench prints: how many times the middle piece goes down across and down a box, the division rounded up because a run of thirty-four pixels needs nine tiles of four, the ninth of them cut to two. It is here, and not left to the bench, so that a test can ask the frame how many blits a box costs without drawing it.

∑ Math Interlude — the plate's box, worked by hand

The name plate on the last figure is a box forty-two pixels wide and nineteen tall, from a frame twelve square cut at four, and every number in laying it down comes from those four. The tile is the picture less two corners: 12 − 2 × 4 = 4, in both directions. The top edge's run is the box less two corners: 42 − 8 = 34 pixels. Thirty-four is 8 × 4 + 2, so the edge piece goes down nine times along the top, eight of them whole and the ninth cut to two pixels wide by the clip. The left edge's run is 19 − 8 = 11 pixels, which is 2 × 4 + 3: three copies, the third cut to three tall. The middle is 34 by 11, so 9 × 3 = 27 copies of the middle piece, and the copy in the far corner of it is cut both ways, to two wide and three tall.

Counting the blits: four corners once each, the top and bottom edges nine each, the left and right edges three each, and twenty-seven in the middle, which is 4 + 18 + 6 + 27 = 55 copies of a four-by-four piece to make a box of 42 × 19 = 798 pixels, out of a picture of 144. The column inside the box is the panel's business and the section after next measures it, but the arithmetic is the same kind: a glyph is six wide, and a column of a hundred and fifty pixels holds 150 ÷ 6 = 25 of them with nothing left over.

Cthe corner size in pixels, 4 here; the one number the cut needs
tilethe piece the edges and the middle repeat at: the picture's side less two corners, 12 − 8 = 4
runthe length of an edge between its corners: the box's side less two corners, 42 − 8 = 34
tilescopies of a piece along a run, rounded up: 34 ÷ 4 = 8 and 2 left over, so 9
cuthow much of the last copy the clip keeps: the run less the whole copies, 34 − 8 × 4 = 2
blits4 corners + 2 × (tiles across) + 2 × (tiles down) + (across × down) copies
columnglyphs that stand side by side in a width: the width divided by the glyph's advance, 150 ÷ 6 = 25
▣ Build · stage 5 — the blit that makes a box

Extend internal/render/nine.go with the loop. The nine destinations are built from the box the same way the nine sources are built from the frame, so the pairs line up by index, and the only branch is a box with no room for its corners, which draws nothing instead of a frame folded over itself.

// internal/render/nine.go
// DrawNine makes a box from the frame. Each of the nine pieces has a
// destination: the corners are the box's corners, C square; the edges
// are the runs between them; the middle is what is left. Every piece
// is blitted at its own size, starting at its destination's top left
// and stepping by its own width and height until the destination is
// covered, with the clip narrowed to the destination so the last tile
// in each direction is cut where the box ends. A corner's destination
// is exactly one tile, so a corner is put down once and never cut.
//
// The pixels go through the same Blit every sprite goes through, so a
// pixel the frame does not hold leaves the buffer alone: that is how a
// corner is rounded, and how the box sits on whatever is behind it.
//
// The edges are repeated and not stretched. Stretching a four-pixel
// edge across two hundred reads each source pixel fifty times in a
// row, and a mark drawn one pixel wide comes out fifty wide; repeating
// puts the mark down at the size it was drawn, every time.
func (b *Buffer) DrawNine(n *Nine, box Rect) {
	if box.X1-box.X0 < 2*n.C || box.Y1-box.Y0 < 2*n.C {
		return
	}
	// Where each column and row of the box begins and ends, in the
	// same terms Slice cuts the source: corner, run, corner.
	xs := [4]int{box.X0, box.X0 + n.C, box.X1 - n.C, box.X1}
	ys := [4]int{box.Y0, box.Y0 + n.C, box.Y1 - n.C, box.Y1}

	was := b.Clip()
	for j := 0; j < 3; j++ {
		for i := 0; i < 3; i++ {
			src := n.Slice(i, j)
			dst := Rect{X0: xs[i], Y0: ys[j], X1: xs[i+1], Y1: ys[j+1]}
			if dst.Empty() {
				continue
			}
			b.SetClip(dst.Intersect(was))
			sw, sh := src.X1-src.X0, src.Y1-src.Y0
			for y := dst.Y0; y < dst.Y1; y += sh {
				for x := dst.X0; x < dst.X1; x += sw {
					b.Blit(n.sheet, src, x, y)
				}
			}
		}
	}
// ...
$ go run ./cmd/mkpanel -mode slices -w 42 -h 19
mkpanel: a 12x12 frame cut at 4, making a box 42 by 19

  piece          from           to               put down
  top left       0,0 4x4        0,0 4x4          once, 4x4
  top middle     4,0 4x4        4,0 34x4         9 across, 1 down, the last cut to 2x4
  top right      8,0 4x4        38,0 4x4         once, 4x4
  middle left    0,4 4x4        0,4 4x11         1 across, 3 down, the last cut to 4x3
  middle middle  4,4 4x4        4,4 34x11        9 across, 3 down, the last cut to 2x3
  middle right   8,4 4x4        38,4 4x11        1 across, 3 down, the last cut to 4x3
  bottom left    0,8 4x4        0,15 4x4         once, 4x4
  bottom middle  4,8 4x4        4,15 34x4        9 across, 1 down, the last cut to 2x4
  bottom right   8,8 4x4        38,15 4x4        once, 4x4

  the box is 42 pixels wide: two corners of 4 leave 34, which is 9 tiles of 4
  and 19 tall: two corners leave 11, which is 3 tiles of 4
  27 middle tiles, 24 edge tiles, 4 corners: 55 blits for a box of 798 pixels, from a picture of 144

The bench's -mode slices is the interlude with the numbers filled in by the frame instead of by hand, and the two agree line for line: nine across with the last cut to two, three down with the last cut to three, fifty-five blits. The mode is Slice, Tiles and a table.

// cmd/mkpanel/main.go
// slicesMode prints the nine-slice arithmetic for one box: where each
// piece comes from, where it goes, and how many times the repeated
// pieces are put down.
func slicesMode(w, h int) {
	n := nine()
	tw, th := n.Tile()
	box := render.Rect{X0: 0, Y0: 0, X1: w, Y1: h}
	names := [3]string{"left", "middle", "right"}
	rows := [3]string{"top", "middle", "bottom"}
	fmt.Printf("mkpanel: a %dx%d frame cut at %d, making a box %d by %d\n\n", n.Pix.W, n.Pix.H, n.C, w, h)
	fmt.Printf("  %-14s %-14s %-16s %s\n", "piece", "from", "to", "put down")
	xs := [4]int{0, n.C, w - n.C, w}
	ys := [4]int{0, n.C, h - n.C, h}
	for j := 0; j < 3; j++ {
		for i := 0; i < 3; i++ {
			src := n.Slice(i, j)
			dst := render.Rect{X0: xs[i], Y0: ys[j], X1: xs[i+1], Y1: ys[j+1]}
			sw, sh := src.X1-src.X0, src.Y1-src.Y0
			dw, dh := dst.X1-dst.X0, dst.Y1-dst.Y0
			across, down := (dw+sw-1)/sw, (dh+sh-1)/sh
			how := fmt.Sprintf("once, %dx%d", sw, sh)
			if dst.Empty() {
				how = "nowhere: the corners leave it no room"
			} else if i == 1 || j == 1 {
				how = fmt.Sprintf("%d across, %d down", across, down)
				cutW, cutH := dw-(across-1)*sw, dh-(down-1)*sh
				if cutW != sw || cutH != sh {
					how += fmt.Sprintf(", the last cut to %dx%d", cutW, cutH)
				}
			}
			fmt.Printf("  %-14s %-14s %-16s %s\n", rows[j]+" "+names[i],
				fmt.Sprintf("%d,%d %dx%d", src.X0, src.Y0, sw, sh),
				fmt.Sprintf("%d,%d %dx%d", dst.X0, dst.Y0, dw, dh), how)
		}
	}
	a, d := n.Tiles(box)
	fmt.Printf("\n  the box is %d pixels wide: two corners of %d leave %d, which is %d tiles of %d\n",
		w, n.C, w-2*n.C, a, tw)
	fmt.Printf("  and %d tall: two corners leave %d, which is %d tiles of %d\n", h, h-2*n.C, d, th)
	fmt.Printf("  %d middle tiles, %d edge tiles, 4 corners: %d blits for a box of %d pixels, from a picture of %d\n",
		a*d, 2*a+2*d, a*d+2*a+2*d+4, w*h, n.Pix.W*n.Pix.H)
}
// ...

Run it for the box Halla's sentence needs and the counts grow without the frame changing: forty-four tiles across, thirteen down with the last cut to three, five hundred and seventy-two copies of the middle and six hundred and ninety blits in all for a box of ten thousand eight hundred and fifty-six pixels, still from a picture of a hundred and forty-four.

$ go run ./cmd/mkpanel -mode slices -w 184 -h 59
mkpanel: a 12x12 frame cut at 4, making a box 184 by 59

  piece          from           to               put down
  top left       0,0 4x4        0,0 4x4          once, 4x4
  top middle     4,0 4x4        4,0 176x4        44 across, 1 down
  top right      8,0 4x4        180,0 4x4        once, 4x4
  middle left    0,4 4x4        0,4 4x51         1 across, 13 down, the last cut to 4x3
  middle middle  4,4 4x4        4,4 176x51       44 across, 13 down, the last cut to 4x3
  middle right   8,4 4x4        180,4 4x51       1 across, 13 down, the last cut to 4x3
  bottom left    0,8 4x4        0,55 4x4         once, 4x4
  bottom middle  4,8 4x4        4,55 176x4       44 across, 1 down
  bottom right   8,8 4x4        180,55 4x4       once, 4x4

  the box is 184 pixels wide: two corners of 4 leave 176, which is 44 tiles of 4
  and 59 tall: two corners leave 51, which is 13 tiles of 4
  572 middle tiles, 114 edge tiles, 4 corners: 690 blits for a box of 10856 pixels, from a picture of 144

The edges are repeated and not stretched, and the choice was made on this frame's arithmetic and not its looks, because on this frame it makes no visible difference. Chapter 68's BlitMap stretches: drawing the top edge's four pixels across a run of a hundred and seventy-six, it reads source column x·4/176 for every destination column, so source column 0 is read forty-four times in a row, then column 1 forty-four times. On four pixels of unbroken bone that comes out as a hundred and seventy-six pixels of unbroken bone, the same as repeating. Put one mark on the edge, a single pixel of rock-l at column 2 as a stitch, and stretching draws it forty-four pixels long while repeating draws it forty-four times at one pixel each. The frame the page ships has no stitch, and the blit is written for the frame a reader draws next, which may.

▣ Build · stage 6 — the same frame at twenty by twelve and at eighty-eight by twenty-eight

-mode boxes draws two boxes from the one frame on a field of the water midtone and writes the card at four times. The small one has room for three middle tiles across and one down; the large one for twenty and five. Neither has a glyph in it, so what is on the card is the frame and nothing else.

$ go run ./cmd/mkpanel -mode boxes -shot assets/frames/panel-boxes.png
mkpanel: one frame, two boxes, on a card 124 by 36
  small 20 by 12 at 4,4: middle tiles 3 across, 1 down
  large 88 by 28 at 32,4: middle tiles 20 across, 5 down
  the small box, row by row, one character a pixel; a dot is the card showing through a corner
    .oooooooooooooooooo.
    obbbbbbbbbbbbbbbbbbo
    obmmmmmmmmmmmmmmmmlo
    obmffffffffffffffdlo
    obmffffffffffffffdlo
    obmffffffffffffffdlo
    obmffffffffffffffdlo
    obmffffffffffffffdlo
    obmffffffffffffffdlo
    obddddddddddddddddlo
    ollllllllllllllllllo
    .oooooooooooooooooo.
  the card 124 by 36 pixels, 1224c01bf41cb379e2871d60f6e0a3d5535c168cf11b38fce8370dcf0297beaf
  wrote assets/frames/panel-boxes.png at 4 times, 496 by 144
Two empty pixel-art boxes side by side on a mid-blue field. Both have the same frame: a dark outline with the corner pixels rounded off, a cream line inside it along the top and left, a light grey line along the bottom and right, a darker grey line inside those, and a deep navy field in the middle. The left box is small and squat, twenty by twelve pixels; the right box is wide and taller, eighty-eight by twenty-eight, and its lines are exactly the same thickness as the small box's.
Figure 100.1assets/frames/panel-boxes.png: one frame, two boxes, at four pixels to one. The lines are the same thickness on both because the edges are repeated and not stretched, and the corners are the same four pixels on both because a corner is copied once whatever the box's size.
▣ Build · stage 7 — three tests over the cut, pinned

Create internal/render/nine_test.go. The frame under test is not the shipped one but a twelve-square picture whose nine pieces are nine colours, one each, with the outer corner pixels left off, so that a test can read which piece any pixel of a box came from. The first test draws a box that no piece divides evenly and checks every pixel of the buffer, inside the box against the piece it should have come from and outside it against the field; the second draws every box from eight square up to twenty-one square and refuses one seven wide; the third holds the nine source rectangles to the picture, each pixel of it in exactly one.

$ go test -count=1 ./internal/render/ -run 'TestACornerIsPutDownOnceAndTheEdgesRepeat|TestOneFrameMakesABoxOfAnySizeFromTwoCornersUp|TestTheNinePiecesTileTheFrame' -v
=== RUN   TestACornerIsPutDownOnceAndTheEdgesRepeat
--- PASS: TestACornerIsPutDownOnceAndTheEdgesRepeat (0.00s)
=== RUN   TestOneFrameMakesABoxOfAnySizeFromTwoCornersUp
--- PASS: TestOneFrameMakesABoxOfAnySizeFromTwoCornersUp (0.00s)
=== RUN   TestTheNinePiecesTileTheFrame
--- PASS: TestTheNinePiecesTileTheFrame (0.00s)
PASS
ok  	theworld/internal/render	0.004s

Twenty words into five lines of twenty-five glyphs

Before the wrapper is written, the panel is run without one. The bench's -mode panel draws the box, the slot with Halla's face in it and the plate with her name, over the valley the village stands on, and then puts her sentence down with DrawTextIn as chapter 21 left it, one line, from where the first line of the column would start. Behind -nowrap that is all it does; the flag is the counterfactual, and it is run first because the picture makes the case for the rest of the section faster than a paragraph about legibility would.

⚠ Worked failure — a line that walks off the right edge, run
$ go run ./cmd/mkpanel -mode panel -nowrap -shot assets/frames/panel-nowrap.png
mkpanel: the ground from seed 1 at tick 901, 192 by 128 pixels, 11 plants standing
  Halla, home 8,5, the face off the row; the words: 111 bytes, 20 words
  a box 184 wide, pad 3, pitch 10, gap 4, the plate 4 over the edge: the column is 150 pixels, 25 glyphs
  wrapped to nothing: one line of 111 glyphs, 666 pixels wide, from x 32 to 698
  the box ends at x 188 and the picture at 192: 516 pixels of the line cross the frame and 506 are off the picture
  25 glyphs of 111 are wholly inside the column
  the picture 192 by 128 pixels, ff57ee113dfadaa38a0f20fc4886510e4e8bf58376c606e02d30cd0c62855318
  wrote assets/frames/panel-nowrap.png at 4 times, 768 by 512
A pixel-art valley: a grey rock rim round brown soil, a blue pond on the right, a few thin green plants. Along the bottom is a framed navy panel with a name plate reading HALLA over its top-left edge and a small portrait of a woman with long brown hair in a lighter blue square on the left. Beside the portrait a single line of cream capitals reads HALLA CAME TO FIRSTLIGHT BE and runs straight through the panel's right-hand frame and off the edge of the picture.
Figure 100.2assets/frames/panel-nowrap.png: the box, the slot, the plate, and a hundred and eleven glyphs drawn as one line. Twenty-five of them are inside the column; the rest cross the frame and leave the picture.

The symptom is on the figure and in the third line of the run: one line of a hundred and eleven glyphs, six hundred and sixty-six pixels wide, starting at x 32 and ending at 698 on a picture that ends at 192. Five hundred and six pixels of it are drawn to nowhere, the clip in drawGlyph throwing each of them away, and before that the line crosses the panel's right-hand frame, because the clip was the whole picture and nothing between DrawTextIn and the frame knew the frame was there. Work back from the symptom. The pen advanced six pixels a byte and was never asked whether it had room; the font can measure a string, TextWidth has been there since chapter 21, and no caller measured. Narrowing the clip to the box's inside is the obvious fix and it is not one: the line then stops at the frame instead of crossing it, and eighty-six of the hundred and eleven glyphs are still never seen. A clip decides what is drawn. The question here is what is said, and that has to be decided before the first glyph goes down, by measuring the column and cutting the string to fit it.

The cut is at spaces, because a space is the one place in prose where a break costs nothing. The wrapper walks the words and keeps a line; a word joins the line if the line, a space and the word together are no wider than the column, measured with the font's own widths, and starts the next line otherwise. That leaves one case: a word wider than the column on its own, which fits on no line and would otherwise stall the loop. It is cut at the last glyph that fits, and the rest of it starts the next line, cut again if it has to be. The alternative, letting it run past the column for the clip to take, puts the end of the word where nobody can read it; a word broken across two lines with nothing inserted at the break is still every one of its letters, in order. The wrapper inserts no hyphen, because a hyphen is a byte the string did not have and the text drawn is the text given.

▣ Build · stage 8 — the wrapper, over the font it measures with

Create internal/render/wrap.go. It is a method on Font because the column is in pixels and only the font knows how wide a string comes out; a wrapper that counted characters would be right for this font and wrong for the first proportional one. Column is the count the wrapper cannot exceed on any line, and Tall is read off the sheet rather than written down beside the cell size: it walks the cells from the bottom row up and stops at the first row with ink on it, which for chapter 21's sheet is the seventh of eight.

// internal/render/wrap.go
package render

import "strings"

// Wrap breaks s into lines no wider than width pixels, measured with
// this font's own widths. Words are what spaces separate, and a line
// breaks only at a space: a word is put on the current line if the line
// with a space and the word still fits, and starts the next line if it
// does not. Runs of spaces count as one, and a line never begins or
// ends with one.
//
// A word wider than the column on its own is cut at the last glyph that
// fits, and the rest of it starts the next line, cut again if it has
// to be. The alternative, letting it run past the column, puts glyphs
// where the clip throws them away, and a word with its end cut off is
// a word the reader cannot read; a word broken across two lines is
// still every one of its letters. Nothing is inserted at the break, so
// the text drawn is the text given, byte for byte, in more lines.
//
// A column narrower than one glyph still makes progress: every line
// takes at least one glyph, so the loop cannot stall on a word.
func (f *Font) Wrap(s string, width int) []string {
	var lines []string
	line := ""
	for _, word := range strings.Fields(s) {
		for word != "" {
			switch {
			case line == "" && f.TextWidth(word) <= width:
				line = word
				word = ""
			case line == "":
				// Too wide for a line of its own: take what fits.
				n := f.fits(word, width)
				line, word = word[:n], word[n:]
				lines = append(lines, line)
				line = ""
			case f.TextWidth(line+" "+word) <= width:
				line += " " + word
				word = ""
			default:
				lines = append(lines, line)
				line = ""
			}
		}
	}
	if line != "" {
		lines = append(lines, line)
	}
	return lines
}

// fits is how many leading bytes of word fit in width pixels, and never
// fewer than one.
func (f *Font) fits(word string, width int) int {
	n := 1
	for n < len(word) && f.TextWidth(word[:n+1]) <= width {
		n++
	}
	return n
}

// Column is how many glyphs of this font stand side by side in width
// pixels: the count the wrapper cannot exceed on any line.
func (f *Font) Column(width int) int { return width / f.Advance }

// Tall is how many rows of a glyph cell the sheet actually inks: the
// cell's height less the blank rows at the bottom of every cell. A
// six-by-eight cell with a gap row under every glyph is seven tall.
// It is read off the sheet, once, and not written down beside the
// cell size, because the sheet is the authority on where its ink is.
func (f *Font) Tall() int {
	pix := f.Sheet.Pix
	for tall := f.GH; tall > 0; tall-- {
		for cell := 0; cell < f.Count; cell++ {
			r := f.Glyph(byte(int(f.First) + cell))
			y := r.Y0 + tall - 1
			for x := r.X0; x < r.X1; x++ {
				if pix.At(x, y).A() != 0 {
					return tall
				}
			}
		}
	}
	return 0
}

-mode wrap builds the panel of the next section from the flags, takes the column it leaves in a box of the width asked for, and prints every line with its width in glyphs and in pixels.

// cmd/mkpanel/main.go
// wrapMode wraps the words to the column a box w wide leaves and
// prints every line with its width.
func wrapMode(p *render.Panel, w int, text string) {
	col := p.Column(w)
	f := p.Font
	fmt.Printf("mkpanel: a box %d wide, pad %d, a slot of %d with its ring, a gap of %d: the column is %d pixels, %d glyphs of %d\n",
		w, p.Pad, p.Slot, p.Gap, col, f.Column(col), f.Advance)
	fmt.Printf("  the words: %d bytes, %d words\n", len(text), len(strings.Fields(text)))
	lines := f.Wrap(text, col)
	fmt.Printf("  wrapped to %d lines, none wider than %d\n\n", len(lines), col)
	fmt.Printf("  %-4s %6s %6s  %s\n", "line", "glyphs", "pixels", "the line")
	for i, l := range lines {
		fmt.Printf("  %-4d %6d %6d  %s\n", i+1, len(l), f.TextWidth(l), l)
	}
	fmt.Printf("\n  %d lines at a pitch of %d and %d rows of ink on the last: the box is %d tall\n",
		len(lines), p.Pitch, f.Tall(), p.Height(w, text))
}
// ...
$ go run ./cmd/mkpanel -mode wrap
mkpanel: a box 184 wide, pad 3, a slot of 16 with its ring, a gap of 4: the column is 150 pixels, 25 glyphs of 6
  the words: 111 bytes, 20 words
  wrapped to 5 lines, none wider than 150

  line glyphs pixels  the line
  1        24    144  Halla came to Firstlight
  2        18    108  before there was a
  3        22    132  Firstlight and put her
  4        21    126  house on the flattest
  5        22    132  ground she could find.

  5 lines at a pitch of 10 and 7 rows of ink on the last: the box is 59 tall

Twenty-five glyphs is the column: a box a hundred and eighty-four wide, less six of inset on each side, less the slot's eighteen and the gap's four, is a hundred and fifty pixels, and a hundred and fifty is twenty-five sixes with nothing over. Halla's twenty words go into five lines of it, the widest a hundred and forty-four pixels, and every break is at a space. The first line could not take before, because twenty-four glyphs, a space and six more is thirty-one; the second stops at a because Firstlight is ten and eighteen, a space and ten is twenty-nine. Five lines at a pitch of ten, with seven rows of ink on the last, and the box is fifty-nine tall, which is the number the interlude's second box came from.

$ go run ./cmd/mkpanel -mode wrap -text "and the abcdefghijklmnopqrstuvwxyz is one word"
mkpanel: a box 184 wide, pad 3, a slot of 16 with its ring, a gap of 4: the column is 150 pixels, 25 glyphs of 6
  the words: 46 bytes, 6 words
  wrapped to 3 lines, none wider than 150

  line glyphs pixels  the line
  1         7     42  and the
  2        25    150  abcdefghijklmnopqrstuvwxy
  3        13     78  z is one word

  3 lines at a pitch of 10 and 7 rows of ink on the last: the box is 39 tall

The alphabet is twenty-six letters and the column is twenty-five, so it is the word the rule was written for. It does not join the first line, because and the plus a space plus twenty-six is thirty-four; it does not fit a line of its own; so it is cut at the twenty-fifth glyph and z starts the third line, where is one word joins it. Nothing was added, and one byte was spent: the three lines hold forty-five glyphs, spaces included, and the forty-sixth byte is the space between the and the alphabet, which is where the first break was made. A break at a space costs the space; the break inside the alphabet cost nothing, because there was nothing there to spend.

$ go run ./cmd/mkpanel -mode wrap -sentence 0
mkpanel: a box 184 wide, pad 3, a slot of 16 with its ring, a gap of 4: the column is 150 pixels, 25 glyphs of 6
  the words: 217 bytes, 40 words
  wrapped to 10 lines, none wider than 150

  line glyphs pixels  the line
  1        24    144  Halla came to Firstlight
  2        18    108  before there was a
  3        22    132  Firstlight and put her
  4        21    126  house on the flattest
  5        22    132  ground she could find.
  6        25    150  She keeps more grain than
  7        23    138  she needs and gives the
  8        24    144  difference away, and she
  9        25    150  has never once been asked
  10        4     24  why.

  10 lines at a pitch of 10 and 7 rows of ink on the last: the box is 109 tall

The whole persona is two hundred and seventeen bytes, ten lines at this column, and a box of a hundred and nine pixels to hold them on a ground a hundred and twenty-eight tall: the box would cover the valley from the rim down. The wrapper is doing its job, and the number is the measurement of what a spoken line can be at this width: a persona is authored prose in the register of the founding document, and a line said from a face is one breath, which at twenty-five glyphs a line is three lines and seventy-five glyphs at the most. The panel draws whatever it is given and grows to fit it; what it is given is a decision made by whoever writes the words, with this table in front of them.

▣ Build · stage 9 — three tests over the wrapper, pinned

Create internal/render/wrap_test.go. The font under test is built in memory with chapter 21's cell and a glyph inked to the seventh row in every cell, so the tests need no file. The first wraps Halla's sentence at every column from one glyph to the whole sentence and holds three things at each: no line wider than the column, every glyph present in order, and no line that had room for the first word of the next and did not take it. The second is the alphabet, at twenty-five glyphs, at one, and at a column of nothing, which still gives twenty-six lines because every line takes a glyph. The third reads seven off the sheet and eight after one pixel is put on the gap row.

$ go test -count=1 ./internal/render/ -run 'TestWrapBreaksOnSpacesAndNeverPastTheColumn|TestAWordWiderThanTheColumnIsCutAtTheLastGlyphThatFits|TestTallIsReadOffTheSheet' -v
=== RUN   TestWrapBreaksOnSpacesAndNeverPastTheColumn
--- PASS: TestWrapBreaksOnSpacesAndNeverPastTheColumn (0.00s)
=== RUN   TestAWordWiderThanTheColumnIsCutAtTheLastGlyphThatFits
--- PASS: TestAWordWiderThanTheColumnIsCutAtTheLastGlyphThatFits (0.00s)
=== RUN   TestTallIsReadOffTheSheet
--- PASS: TestTallIsReadOffTheSheet (0.00s)
PASS
ok  	theworld/internal/render	0.004s

Three pixels of pad and ten of pitch, measured

The panel is the frame, the font, the wrapper and five distances, and the distances are the design. The inset is how far the content stands from the box's edge: the frame's three lines, which the panel reads off the corner size instead of being told twice, and then a pad. The gap is what stands between the slot's ring and the first glyph. The pitch is from the top of one line to the top of the next. The plate lies some pixels over the box's top edge. And the slot is chapter 99's sixteen with a one-pixel ring round it. The pad and the pitch were tried at several values with the same words in the box, by a bench mode that draws them one under the next, and the values the page keeps are the ones that survived being looked at through the inspector at one, two and four pixels to one; the gap and the plate's overlap were set once, at four, the corner's size, and read against the same pictures.

▣ Build · stage 10 — the panel: a width and a string in, a box and a slot out

Create internal/render/panel.go. Draw takes the box's top-left corner and its width and works the height out from the words, because the words decide the box and not the other way round; it returns the slot's inside for the caller to draw a face into, since render knows a rectangle and does not know a person. The words are drawn with the clip narrowed to the inset, so a wrapper fault cannot reach the frame, and the plate goes down last, over the top edge, as the same nine pieces at another size.

// internal/render/panel.go
package render

// Panel is the box words are said from: a frame made from a Nine, a
// slot for a portrait on the left, a plate over the top edge carrying
// a name, and lines of text wrapped to the column that is left. It is
// screen space, drawn after the world in raw pixels; nothing in it
// goes through a camera.
//
// Every distance is a whole number of pixels and every one is a design
// decision the page measures: the pad between the frame's inner line
// and anything inside, the gap between the slot and the words, and the
// pitch from one line of text to the next.
type Panel struct {
	Frame *Nine // the box, and the plate: one picture at two sizes
	Font  *Font // the six-by-eight glyphs
	Text  Color // what the words are drawn in
	Pitch int   // pixels from the top of one line to the top of the next
	Pad   int   // pixels between the frame's inner line and the content
	Slot  int   // the portrait's side in pixels; 0 draws no slot
	Ring  Color // the one-pixel line round the slot
	Field Color // what the slot is filled with, under the portrait
	Gap   int   // pixels between the slot's ring and the first glyph
	Plate int   // pixels the plate rises above the box's top edge
}

// Rim is how many pixels of the frame stand between the box's edge and
// the field: the corner less the one field pixel the corner piece
// carries. It is a fact about the picture the frame was drawn as, and
// the panel reads it off the corner instead of being told twice.
func (p *Panel) Rim() int { return p.Frame.C - 1 }

// Inset is the distance from the box's edge to the content: the rim,
// then the pad.
func (p *Panel) Inset() int { return p.Rim() + p.Pad }

// Column is the width in pixels the words may take in a box w wide:
// the box less two insets, the slot with its ring, and the gap.
func (p *Panel) Column(w int) int {
	col := w - 2*p.Inset()
	if p.Slot > 0 {
		col -= p.Slot + 2 + p.Gap
	}
	return col
}

// Lines wraps text to the column a box w wide leaves it.
func (p *Panel) Lines(w int, text string) []string {
	return p.Font.Wrap(text, p.Column(w))
}

// Height is how tall a box w wide has to be to hold text: two insets
// round whichever is taller, the wrapped lines or the slot with its
// ring. The last line takes the font's inked height and not a whole
// pitch, because the pitch is the distance to a next line and there is
// none, and a cell's blank gap row would sit under the last line as a
// pad the top does not have.
func (p *Panel) Height(w int, text string) int {
	lines := p.Lines(w, text)
	inner := 0
	if len(lines) > 0 {
		inner = (len(lines)-1)*p.Pitch + p.Font.Tall()
	}
	if p.Slot > 0 && p.Slot+2 > inner {
		inner = p.Slot + 2
	}
	return 2*p.Inset() + inner
}

// Draw puts the panel on the buffer with its top-left corner at (x, y)
// and its width w, sized to text, and returns the slot's inside, the
// square the caller draws a portrait into, and the box it drew. The
// box goes down first, then the slot's ring and field, then the words,
// then the plate over the top edge with the name on it; the portrait
// is the caller's, because the panel knows a rectangle and not a
// person. With no name, no plate is drawn.
func (p *Panel) Draw(b *Buffer, x, y, w int, name, text string) (slot, box Rect) {
	h := p.Height(w, text)
	box = Rect{X0: x, Y0: y, X1: x + w, Y1: y + h}
	b.DrawNine(p.Frame, box)

	in := p.Inset()
	cx := x + in
	if p.Slot > 0 {
		ring := Rect{X0: cx, Y0: y + in, X1: cx + p.Slot + 2, Y1: y + in + p.Slot + 2}
		b.FillRect(ring, p.Ring)
		slot = Rect{X0: ring.X0 + 1, Y0: ring.Y0 + 1, X1: ring.X1 - 1, Y1: ring.Y1 - 1}
		b.FillRect(slot, p.Field)
		cx = ring.X1 + p.Gap
	}

	was := b.Clip()
	b.SetClip(Rect{X0: x + in, Y0: y + in, X1: x + w - in, Y1: y + h - in}.Intersect(was))
	ty := y + in
	for _, line := range p.Lines(w, text) {
		b.DrawTextIn(p.Font, cx, ty, line, p.Text)
		ty += p.Pitch
	}
	b.SetClip(was)

	if name != "" {
		p.plate(b, x+in, y+p.Plate-p.PlateHeight(), name)
	}
	return slot, box
}

// PlateHeight is how tall the plate is: the font's inked rows and two
// insets, the same inset the box keeps.
func (p *Panel) PlateHeight() int { return 2*p.Inset() + p.Font.Tall() }

// plate draws the name in a second box from the same frame: as wide as
// the name and two insets, as tall as the font's ink and two insets,
// with its top-left corner at (x, y). It is the same nine pieces at another
// size, and nothing about the frame knows which box it is making.
func (p *Panel) plate(b *Buffer, x, y int, name string) Rect {
	in := p.Inset()
	box := Rect{X0: x, Y0: y, X1: x + 2*in + p.Font.TextWidth(name), Y1: y + p.PlateHeight()}
	b.DrawNine(p.Frame, box)
	b.DrawTextIn(p.Font, x+in, y+in, name, p.Text)
	return box
}

Height gives the last line its inked rows and not a whole pitch, for a reason the plate makes visible: a glyph cell is eight tall and the eighth row is blank, so a box that counted the cell would carry one more pixel of pad under the words than over them, and on a plate three pixels of pad the extra one shows. The slot sets a floor on the height, so a box with one line of words is still tall enough for the face, and with no words at all it is the slot and the insets, which is the box the failure above drew.

▣ Build · stage 11 — the pad and the pitch, tried and measured

-mode fit is the table: for each pad, the inset, the column in pixels and in glyphs, the lines Halla's sentence needs, and the box's height at four pitches. -mode pads and -mode pitches draw the rows of that table, one panel under the next, at three pixels to one.

$ go run ./cmd/mkpanel -mode fit
mkpanel: a box 184 wide holding 111 bytes in 20 words, at a slot of 16 and a gap of 4

  pad  inset  column  glyphs  lines   box height at a pitch of 8, 9, 10, 12
  1    4      154     25      5        47  51  55  63
  2    5      152     25      5        49  53  57  65
  3    6      150     25      5        51  55  59  67
  4    7      148     24      5        53  57  61  69
  5    8      146     24      5        55  59  63  71

  a glyph is 6 wide and 8 tall with 7 rows of ink; at a pitch of 8 one blank row stands between lines, at 10 three, at 12 five
$ go run ./cmd/mkpanel -mode pads -shot assets/frames/panel-pads.png -scale 3
mkpanel: Halla's words in a box 184 wide at a pitch of 10, once at each pad from 1 to 5
  pad 1: the box 184 by 55 at 4,15, 5 lines
  pad 2: the box 184 by 57 at 4,87, 5 lines
  pad 3: the box 184 by 59 at 4,163, 5 lines
  pad 4: the box 184 by 61 at 4,243, 5 lines
  pad 5: the box 184 by 63 at 4,327, 5 lines
  the card 192 by 394 pixels, 1d6ce9bf5215e17fedabfe3c79c39a4b787fec80ef869a643579c54b662e778d
  wrote assets/frames/panel-pads.png at 3 times, 576 by 1182
Five copies of the same framed dialogue panel stacked vertically on a mid-blue field, each with a HALLA name plate, a small portrait on the left, and five lines of cream capitals reading HALLA CAME TO FIRSTLIGHT BEFORE THERE WAS A FIRSTLIGHT AND PUT HER HOUSE ON THE FLATTEST GROUND SHE COULD FIND. From top to bottom the space between the frame's inner line and the text grows by one pixel each time: in the top panel the letters nearly touch the frame and the name plate sits on the portrait's ring; in the bottom panel there is a wide margin all round.
Figure 100.3assets/frames/panel-pads.png: pads 1 to 5, top to bottom, at three pixels to one. At pad 1 the plate's bottom line lands on the slot's ring; at pad 3 there are two pixels between them, and the glyphs stand three pixels off the inner line, which is half a glyph's width.

Pad 3 is kept. At pad 1 the inset is four, the plate, which lies four pixels over the edge, ends exactly where the slot's ring begins, and the first glyph of every line of glyphs stands one pixel under the rock-m line, which is the same gap as between two glyphs, so the frame reads as one more row of letters. Pad 2 clears the ring by a pixel and the glyphs by two. Pad 3 puts two pixels between plate and ring and three between line and glyph, half a glyph's width, and the column is still twenty-five glyphs. Pads 4 and 5 lose a glyph from the column, twenty-four, for a margin nobody asked for, and cost two and four pixels of height on every box.

$ go run ./cmd/mkpanel -mode pitches -shot assets/frames/panel-pitches.png -scale 3
mkpanel: Halla's words in a box 184 wide at a pad of 3, once at each pitch of 8, 9, 10 and 12
  pitch 8: the box 184 by 51 at 4,19, 5 lines
  pitch 9: the box 184 by 55 at 4,89, 5 lines
  pitch 10: the box 184 by 59 at 4,163, 5 lines
  pitch 12: the box 184 by 67 at 4,241, 5 lines
  the card 192 by 312 pixels, 8a72deab3d6ba59ea6371049f5392f34fc8244e777ccc707086ba359f2c8712a
  wrote assets/frames/panel-pitches.png at 3 times, 576 by 936
Four copies of the same framed dialogue panel stacked vertically, each with a HALLA name plate, a portrait, and the same five lines of cream capitals. From top to bottom the lines spread apart: in the top panel the rows of letters nearly touch and read as a solid block; in the second there is a thin gap; in the third the lines are clearly separate; in the bottom panel they are widely spaced and the box is noticeably taller.
Figure 100.4assets/frames/panel-pitches.png: pitches 8, 9, 10 and 12, top to bottom. The glyph is seven rows of ink in a cell of eight, so the pitch less seven is the blank rows between lines: one, two, three and five.

Pitch 10 is kept, and it is the pitch chapter 21's readout has used since it was written. At 8 the blank row between lines is one, the same as the blank column between glyphs, and the block reads as a grid of letters and not as lines; at 9 it is two, and the lines are lines, but at one pixel to one the eye still has to find them; at 10 the three blank rows are close to half a glyph's height and the lines read at one to one; at 12 five rows cost eight pixels over five lines and the words start to float. The whole cost of the choice is on the last column of the table: fifty-nine pixels for five lines at 10 against fifty-one at 8, and a box eight pixels taller is the price of a box that reads.

▣ Build · stage 12 — the colours that meet, measured

-mode contrast prints how far apart each pair of colours that touch on the panel are, channel by channel, which is the measurement chapter 21 made when bone landed on gold and the water count vanished.

$ go run ./cmd/mkpanel -mode contrast
mkpanel: pairs of colours that meet on the panel, and how far apart they are in red, green and blue

  one     other      r    g    b  where they meet
  F5EBD7  243E6B   209  173  108  the words on the field
  5A6470  243E6B    54   38    5  the lit inner line on the field
  323847  243E6B    14    6   36  the shaded inner line on the field
  F5EBD7  5A6470   155  135  103  the lit outer line on the lit inner line
  8A979E  323847    88   95   87  the shaded outer line on the shaded inner line
  181A29  3D7799    37   93  112  a face's outline on the slot's field
  181A29  243E6B    12   36   66  a face's outline on the box's field, had the slot no field of its own
  5A6470  3D7799    29   19   41  the slot's ring on the slot's field
  5A6470  243E6B    54   38    5  the slot's ring on the box's field
  181A29  6B5A3E    83   64   21  the outer line on the painter's soil
  181A29  4A90D2    50  118  169  the outer line on the painter's water
  181A29  4C7841    52   94   24  the outer line on a plant's midtone

The first line is the one the panel exists for: bone on water-d differs by 209, 173 and 108, and the readout's shadow, drawn under every glyph in chapter 21 because the world behind the words could be anything, is not needed here, because the field behind the words is the panel's own and is one flat colour. The sixth and seventh lines are why the slot has a field. A face's ink outline on the box's field would differ by 12, 36 and 66; on the water midtone it differs by 37, 93 and 112, and the face has an edge. The fourth and fifth are the bevel: the lit outer line stands off the lit inner one by 155, 135, 103, and the shaded pair are 88, 95, 87 apart, and then the shaded inner line, rock-d, is 14, 6 and 36 from the field, which is to say it falls into the field, and that is what a shadow does. The last three are the outer line on the three grounds the panel stands on in the figure below: never closer than 21 in every channel, and on water never closer than 50.

▣ Build · stage 13 — Halla, in the box, over the valley

The rest of cmd/mkpanel/main.go: the panel with its distances off the flags, the words from the roll or the committed stream, the ground, and -mode panel. The ground is the village bench's own: seed 1, the same air and soil, taken to the boundary the village is founded at and painted by volume 6's painter, so the picture is of the ground the volume runs on and not of a flat field. Nobody stands on it, because nothing in this module draws a body on the map. The words are the first sentence of Halla's persona, which is authored prose in the founding register and the only in-world text there is to say; no model is asked for a line and no line on this page came from one.

// cmd/mkpanel/main.go
// font loads the six-by-eight glyphs.
func font() *render.Font {
	f, err := render.LoadFont(Font, 6, 8, ' ')
	if err != nil {
		die(err)
	}
	return f
}

// panel is the panel with its distances off the flags.
func panel(pad, pitch, gap, plate int) *render.Panel {
	return &render.Panel{
		Frame: nine(),
		Font:  font(),
		Text:  gene.Sixteen[Words],
		Pitch: pitch,
		Pad:   pad,
		Slot:  village.Cell,
		Ring:  gene.Sixteen[SlotRing],
		Field: gene.Sixteen[SlotField],
		Gap:   gap,
		Plate: plate,
	}
}
// ...
// person finds one villager in the shipped roll.
func person(who string) village.Person {
	roll, err := village.Read()
	if err != nil {
		die(err)
	}
	for _, p := range roll.People {
		if p.Name == who {
			return p
		}
	}
	die(fmt.Errorf("nobody called %q in the roll", who))
	return village.Person{}
}

// words is what the panel says: the text given, a memory of the
// committed stream, or the villager's own persona, whole or one
// sentence of it. A sentence ends at a full stop followed by a space
// or the end.
func words(who, text string, sentence, memory int) string {
	if text != "" {
		return text
	}
	if memory != 0 {
		rows, err := exemplars.Stream()
		if err != nil {
			die(err)
		}
		for _, m := range rows {
			if int(m.ID) == memory {
				return m.Text
			}
		}
		die(fmt.Errorf("no memory %d in the committed stream", memory))
	}
	persona := person(who).Persona
	if sentence == 0 {
		return persona
	}
	sentences := sentencesOf(persona)
	if sentence > len(sentences) {
		die(fmt.Errorf("%s's persona has %d sentences, not %d", who, len(sentences), sentence))
	}
	return sentences[sentence-1]
}

// sentencesOf cuts prose at every full stop that ends a sentence.
func sentencesOf(s string) []string {
	var out []string
	start := 0
	for i := 0; i < len(s); i++ {
		if s[i] == '.' && (i+1 == len(s) || s[i+1] == ' ') {
			out = append(out, strings.TrimSpace(s[start:i+1]))
			start = i + 1
		}
	}
	if rest := strings.TrimSpace(s[start:]); rest != "" {
		out = append(out, rest)
	}
	return out
}
// ...
// ground is the valley the panel is drawn over: the village bench's
// ground from its seed, taken to the boundary the village is founded
// at, painted by volume 6's painter. Nobody stands on it: nothing in
// this module draws a body on the map.
func ground(seed uint64) (*render.Buffer, *terra.Valley) {
	g := sim.Generate(12, 8, seed)
	a := terra.NewAir(scene.Wind(g), air, g.Count(sim.Soil)*90, seed)
	v := terra.NewValley(g, seed, soil, 12, a)
	v.Fall, v.Sun, v.Decay = 0.60, 0.05, 0.01
	v.Wear = 1
	v.Now = terra.Quarter / 2
	v.Found(3)
	for {
		v.Tick()
		if v.Now%terra.Year == terra.Quarter+1 {
			break
		}
	}
	return scene.Paint(v), v
}

// panelMode draws the panel over the valley, a villager's face in the
// slot and the name on the plate, and writes it out scaled. Behind the
// flag the words are drawn as one line, wrapped to nothing, from where
// the first line would start, with the clip left at the whole picture:
// what a panel is without a wrapper.
func panelMode(shot string, scale int, p *render.Panel, w int, who, text string, nowrap bool, seed uint64) {
	sh, err := render.LoadSheet(Sheet, village.Cell)
	if err != nil {
		die(err)
	}
	pic, v := ground(seed)
	per := person(who)
	const edge = 4
	x := (pic.W - w) / 2
	h := p.Height(w, text)
	if nowrap {
		h = 2*p.Inset() + p.Slot + 2
	}
	y := pic.H - edge - h

	fmt.Printf("mkpanel: the ground from seed %d at tick %d, %d by %d pixels, %d plants standing\n",
		seed, v.Now, pic.W, pic.H, len(v.Stands))
	fmt.Printf("  %s, home %d,%d, the face off the row; the words: %d bytes, %d words\n",
		per.Name, per.Home.X, per.Home.Y, len(text), len(strings.Fields(text)))
	fmt.Printf("  a box %d wide, pad %d, pitch %d, gap %d, the plate %d over the edge: the column is %d pixels, %d glyphs\n",
		w, p.Pad, p.Pitch, p.Gap, p.Plate, p.Column(w), p.Font.Column(p.Column(w)))

	if nowrap {
		// The box is drawn for the slot alone, and the words go down
		// in one line with nothing measuring them.
		box := render.Rect{X0: x, Y0: y, X1: x + w, Y1: y + h}
		slot, _ := p.Draw(pic, x, y, w, per.Name, "")
		village.FaceOf(per).Draw(pic, sh, slot.X0, slot.Y0)
		start := slot.X1 + 1 + p.Gap
		end := pic.DrawTextIn(p.Font, start, y+p.Inset(), text, p.Text)
		wide := p.Font.TextWidth(text)
		fmt.Printf("  wrapped to nothing: one line of %d glyphs, %d pixels wide, from x %d to %d\n", len(text), wide, start, end)
		fmt.Printf("  the box ends at x %d and the picture at %d: %d pixels of the line cross the frame and %d are off the picture\n",
			box.X1, pic.W, end-(box.X1-p.Inset()), end-pic.W)
		fmt.Printf("  %d glyphs of %d are wholly inside the column\n", p.Column(w)/p.Font.Advance, len(text))
	} else {
		lines := p.Lines(w, text)
		slot, box := p.Draw(pic, x, y, w, per.Name, text)
		village.FaceOf(per).Draw(pic, sh, slot.X0, slot.Y0)
		fmt.Printf("  %d lines; the box %d by %d at %d,%d, the slot at %d,%d, the plate %d wide\n",
			len(lines), box.X1-box.X0, box.Y1-box.Y0, box.X0, box.Y0, slot.X0, slot.Y0,
			2*p.Inset()+p.Font.TextWidth(per.Name))
		for i, l := range lines {
			fmt.Printf("    %d  %s\n", i+1, l)
		}
	}
	fmt.Printf("  the picture %d by %d pixels, %s\n", pic.W, pic.H, pic.Hash())
	save(pic, shot, scale)
}
// ...
$ go run ./cmd/mkpanel -mode panel -shot assets/frames/panel-halla.png
mkpanel: the ground from seed 1 at tick 901, 192 by 128 pixels, 11 plants standing
  Halla, home 8,5, the face off the row; the words: 111 bytes, 20 words
  a box 184 wide, pad 3, pitch 10, gap 4, the plate 4 over the edge: the column is 150 pixels, 25 glyphs
  5 lines; the box 184 by 59 at 4,65, the slot at 11,72, the plate 42 wide
    1  Halla came to Firstlight
    2  before there was a
    3  Firstlight and put her
    4  house on the flattest
    5  ground she could find.
  the picture 192 by 128 pixels, 251258b4343ba2438a6a623d50b11f9c7f79bd260c87d9eb2fcc59c47e4d36f3
  wrote assets/frames/panel-halla.png at 4 times, 768 by 512
A pixel-art valley: a grey rock rim, brown soil, a blue pond on the right and a few thin green plants. Across the bottom half is a framed navy dialogue panel with rounded corners, a cream and grey bevelled edge, and a small name plate in the same frame reading HALLA standing over its top-left edge. Inside, on the left, a lighter blue square holds a portrait of a woman with a tan face, long brown hair and a flat mouth. To the right of the portrait five lines of cream capitals read HALLA CAME TO FIRSTLIGHT, BEFORE THERE WAS A, FIRSTLIGHT AND PUT HER, HOUSE ON THE FLATTEST, GROUND SHE COULD FIND.
Figure 100.5assets/frames/panel-halla.png: the box, a hundred and eighty-four by fifty-nine, over the valley at four pixels to one. The plate is the same frame at forty-two by nineteen; the face is chapter 99's, composed off her row into the slot the panel handed back; the five lines are the wrapper's, at twenty-five glyphs and a pitch of ten. Every pixel of the panel is one of the valley's sixteen.

The bar every asset in this book is held to is chapter 17's, and it applies to a box as much as to a walker: would you take a screenshot of this world without being asked to. The panel has to earn that on a ground a hundred and ninety-two pixels wide, where five lines of a sentence cover half the valley, and what it has going for it is measured, not felt. Its edge stands off soil by 83, 64, 21 and off water by 50, 118, 169. Its words stand off their field by more than a hundred in every channel and stand three pixels off the frame. Its face has an edge because the slot's field was chosen for the face's outline and not for the words. And its corners are the same four pixels on the plate as on the box, because a corner is drawn once. What it costs is the five lines: at this width a breath is three, and a sentence of a persona is not a breath.

▣ Build · stage 14 — two tests over the panel, pinned

Create internal/render/panel_test.go. The first holds the height to its arithmetic: an inset of six, a column of a hundred and fifty, five lines for the sentence, fifty-nine for the box, and the slot's floor for one line or none. The second draws the panel twice, once with the words in the field's own colour so that they change nothing, and checks that every pixel the words changed lies inside the inset, that the slot is where Draw says, and that the plate is pixel for pixel a box of its size drawn alone.

$ go test -count=1 ./internal/render/ -run 'TestTheBoxIsAsTallAsTheWordsNeed|TestNothingIsDrawnOutsideTheInset' -v
=== RUN   TestTheBoxIsAsTallAsTheWordsNeed
--- PASS: TestTheBoxIsAsTallAsTheWordsNeed (0.00s)
=== RUN   TestNothingIsDrawnOutsideTheInset
--- PASS: TestNothingIsDrawnOutsideTheInset (0.00s)
PASS
ok  	theworld/internal/render	0.004s

Why words set the box size

Two measurements run in opposite directions through this page, and the whole design is that neither one is skipped. The column is measured from the box: a width, less insets, a slot and a gap, in pixels, by the font. The box is measured from the words: the wrapper cuts them to that column, counts the lines, and the height is lines, a pitch and an inset apiece. Draw either without the other and the result is the failure box or a box with empty field under two lines of words. Chapter 21's readout could skip both because its three lines were ten characters each by construction; a persona is not, and a line a villager says is not, so the measuring moved into code, where it is the same for every string.

The frame underneath is chapter 19's tile map turned inward. A map was a small set of cells addressed by arithmetic and laid down in a grid as large as the world; a nine-slice is nine cells addressed by the same arithmetic and laid down in a grid as large as the box, with the one difference that the first and last column and row are fixed and only the middle repeats. Nothing in DrawNine knows it is drawing a frame. It knows nine rectangles and a step, and it would make a box of shadow, of parchment or of water from any twelve-square picture with the same four-pixel cut, which is what lets the frame be data in assets/ and the blit be code in render, on either side of the same seam every sheet in this book has crossed.

The frame's nine pieces and where each goes on the box On the left, a square labelled the picture, twelve by twelve, cut into a three-by-three grid: the four corner cells are marked once, the four edge cells are marked repeat, and the middle cell is marked repeat. On the right, a wide rectangle labelled the box, a hundred and eighty-four by fifty-nine, with its corners marked once, its top and bottom edges marked forty-four across, its left and right edges marked thirteen down with the last cut to three, and its middle marked forty-four by thirteen. Gold arrows run from the picture's corner, edge and middle cells to the matching regions of the box. NINE PIECES, ONE CUT, ANY BOX FROM TWO CORNERS UP THE PICTURE 12 x 12, C = 4 once edge once edge middle edge once edge once each piece 4 x 4 tile: 12 - 2C = 4 corner: once edge: along its run middle: the inside THE BOX 184 x 59, still C = 4 top edge: 44 across, none cut middle: 44 across, 13 down the last row cut to 3 tall bottom edge: 44 across 13 down 13 down 4 + 88 + 26 + 572 = 690 blits THE CLIP CUTS THE LAST TILE; NOTHING IS STRETCHED
Figure 100.6 — the nine pieces and where they go. The corners are copied once into the box's corners; each edge is copied along its run at its own size; the middle is copied across the inside; and the clip, narrowed to each destination in turn, cuts whichever copy would cross the box's edge. Change the box and the arrows do not move, only the counts.

What the design costs is on the page too. Six hundred and ninety blits of sixteen pixels each for a box of ten thousand eight hundred and fifty-six, which is every pixel of the box written once and the clip consulted six hundred and ninety times, and a client that draws the panel every frame pays it every frame; the frame is small, the box is not, and the cost is the box's. A word wider than the column is cut plainly, so Firstlight in a column of nine glyphs comes out as Firstligh and t, which is legible and ugly, and the page prefers ugly and legible to a hyphen the text did not contain. The font has one case, so the panel speaks in capitals whatever is typed at it; the wrapper measures bytes and the fold to capitals happens in Glyph, so the widths are the same either way. And a box is only ever as wide as it is told: on a ground a hundred and ninety-two pixels across, a hundred and eighty-four is the widest a box can be with any ground showing beside it, twenty-five glyphs is the column that width buys, and a line meant to be said from a face is written to that column or it is not one line.

✓ Checkpoint — a frame, a cut, a column and a box
  • Cut a frame of a stated size at a stated corner into its nine source rectangles on paper, and say which piece a given pixel of the picture is in.
  • Work out, for a box of any width and height, how many times each edge and the middle are copied and how much of the last copy the clip keeps, and check the counts against -mode slices.
  • Say why the edges are repeated rather than stretched, with the arithmetic of what a one-pixel mark on a four-pixel edge becomes each way.
  • Say why the field and the slot's ring are not ink, and give the two channel differences that decided the slot's field.
  • Wrap a sentence to a column by hand, breaking at spaces, and say what the wrapper does with a word wider than the column and why it inserts nothing at the cut.
  • Say what pitch 8 does to five lines of a seven-row glyph, what pitch 10 costs in pixels over it, and where the readout has used ten since.
⚡ Exercises — try first, then reveal
Exercise 1 — Ander, saying a memory. Memory 12 of the committed stream is Ander's: took 40 parcels from Halla, 20.0 grams, thirty-eight bytes. Work out the lines and the box's height before running -mode panel -who Ander -memory 12.

Twenty-five glyphs a line: took 40 parcels from is twenty, and Halla, would make twenty-seven, so it starts the second line with 20.0 grams. Two lines is one pitch and seven rows of ink, seventeen, plus two insets of six, twenty-nine; the slot's floor is eighteen and two insets, thirty, and the floor wins, so the box is thirty tall and the second line sits level with the bottom of the face. The plate is forty-two wide for the second time, because Ander is five letters like Halla.

$ go run ./cmd/mkpanel -mode panel -who Ander -memory 12
mkpanel: the ground from seed 1 at tick 901, 192 by 128 pixels, 11 plants standing
  Ander, home 9,5, the face off the row; the words: 38 bytes, 7 words
  a box 184 wide, pad 3, pitch 10, gap 4, the plate 4 over the edge: the column is 150 pixels, 25 glyphs
  2 lines; the box 184 by 30 at 4,94, the slot at 11,101, the plate 42 wide
    1  took 40 parcels from
    2  Halla, 20.0 grams
  the picture 192 by 128 pixels, f3ec6ab0474ae2a107567598de2b3f26f4f25c1b356dcb6423b944756774c8d3
Exercise 2 — the smallest box. Run -mode slices -w 8 -h 8 and say, before you do, which pieces are put down and how many blits a box of exactly two corners costs. Then say what a box seven wide draws.

Four blits. Two corners of four leave nothing, so every edge's run and the middle are zero pixels wide or tall, and the loop over them puts down nothing; the four corners are copied once each and meet, so the box is four corners and no field at all. Seven wide is under two corners, and DrawNine returns before it draws anything, instead of laying two corners over each other by a pixel; the second test in stage 7 holds exactly that for a box seven wide.

$ go run ./cmd/mkpanel -mode slices -w 8 -h 8
mkpanel: a 12x12 frame cut at 4, making a box 8 by 8

  piece          from           to               put down
  top left       0,0 4x4        0,0 4x4          once, 4x4
  top middle     4,0 4x4        4,0 0x4          nowhere: the corners leave it no room
  top right      8,0 4x4        4,0 4x4          once, 4x4
  middle left    0,4 4x4        0,4 4x0          nowhere: the corners leave it no room
  middle middle  4,4 4x4        4,4 0x0          nowhere: the corners leave it no room
  middle right   8,4 4x4        4,4 4x0          nowhere: the corners leave it no room
  bottom left    0,8 4x4        0,4 4x4          once, 4x4
  bottom middle  4,8 4x4        4,4 0x4          nowhere: the corners leave it no room
  bottom right   8,8 4x4        4,4 4x4          once, 4x4

  the box is 8 pixels wide: two corners of 4 leave 0, which is 0 tiles of 4
  and 8 tall: two corners leave 0, which is 0 tiles of 4
  0 middle tiles, 0 edge tiles, 4 corners: 4 blits for a box of 64 pixels, from a picture of 144
Exercise 3 — a column of nine. Run -mode wrap -w 88. Say first how wide the column is in glyphs, which word of Halla's sentence is cut and where, and whether the box fits on the ground.

Eighty-eight less twelve of inset less eighteen of slot less four of gap is fifty-four pixels, nine glyphs. Firstlight is ten, so it is cut both times it occurs, at the ninth glyph, and t starts the next line with whatever follows it; every other word fits a line of its own. Fourteen lines at a pitch of ten with seven rows of ink on the last is a hundred and thirty-seven, and with two insets the box is a hundred and forty-nine tall, twenty-one more than the ground. The panel does not refuse it; drawn at the bottom of the picture its top would start above the picture, and the clip would take the first two lines. A box taller than the ground is a measurement the caller has to make before asking for it, and the height is there to be asked for.

$ go run ./cmd/mkpanel -mode wrap -w 88
mkpanel: a box 88 wide, pad 3, a slot of 16 with its ring, a gap of 4: the column is 54 pixels, 9 glyphs of 6
  the words: 111 bytes, 20 words
  wrapped to 14 lines, none wider than 54

  line glyphs pixels  the line
  1         5     30  Halla
  2         7     42  came to
  3         9     54  Firstligh
  4         8     48  t before
  5         9     54  there was
  6         1      6  a
  7         9     54  Firstligh
  8         9     54  t and put
  9         9     54  her house
  10        6     36  on the
  11        8     48  flattest
  12        6     36  ground
  13        9     54  she could
  14        5     30  find.

  14 lines at a pitch of 10 and 7 rows of ink on the last: the box is 149 tall

A face in a slot, a name on a plate, and a sentence wrapped to twenty-five glyphs a line: the box exists, and what is in it is the first sentence of a persona, because that is the only in-world text a villager has that is meant to be read. The console has no verb that puts a person's words in front of a villager, a stream of any villager holds no row of the heard kind, and the model that would answer is not called on this page; the box was measured for a breath, and nothing in the module has one to give it.