Written, never read.
Sixty-eight letters nobody taught, set solid and justified, with an initial at every paragraph. The ink comes and goes like weather, and a few passages are rubricated in signal, as a scribe would mark them. Play it and the caret writes the page again, rubricating other passages as it goes.
The letters are invented each time the plate is drawn, in one of three hands. The pen writes cursive with a broad brush: each letter is one unbroken line that comes in at the join, makes its arches, cups, spikes, ovals and loops, and leaves at the same height, so the letters of a word run together the way a hand writes them. It leans forward, goes heavy across the edge and where it comes down, thins to a hairline along it, and runs dry, the stroke breaking into bristle streaks towards its end. Its loops climb into the line above and hang into the line below, no two letters keep the same x-height, and the words never quite sit on the line: each is a little tilted, a little off, a little bigger or smaller. Now and then it drags a long rule through the line, or throws a word’s last stroke up in a flick.
The tag hand writes the same page with a marker: hard angles, fast loops, a lean like it is running, drips where a stroke turns at the bottom, and overspray. Its initials are throw-ups, fat round letters outlined over a lighter coat, shouldering in over each other, sometimes crowned. The cut hand takes a rounded slab and cuts notches, slits, holes, bites and vees out of it.
The pen
javascript·writing.js
// a letter is one line: in at the join, one to four beats, out at the join a cube past its box,
// where the next letter of the word picks it up
const points = [[0, join]];
for (let i = 0; i < beats; i++) points.push(...random([arch, cup, oval, eLoop, ascender, descender])(x + i * stride));
points.push([w + 1, join]);
// the hand wavers (never at the joins) and leans forward, and passes through every point;
// a letter that leans out of its box is squeezed to fit, never clipped
const path = catmullRom(fit(points.map(([x, y], i) => [x + jitter(i) + (join - y) * slant, y + jitter(i)])));
// a broad edge held at 35°: wide across the edge and where it comes down, a hairline along it
const width = (dir) => 0.1 + 0.9 * (0.7 * abs(cross(dir, edge)) + 0.3 * max(0, dir.y) ** 1.3);
for (const [a, b] of pairs(path)) quad(a, b, smooth(width) * taper * size);
// and it runs dry: bristles drop out towards the end of the stroke, and here and there on the way
for (let n = round(dry * random(3, 8)); n > 0; n--) erase(bristle(path, random(-0.8, 0.8), from(1 - dry * random(0.15, 0.9))));
The tag
javascript·writing.js
// a marker, not a nib: the width hardly moves, and the corners are hard
const path = [join, ...corner(spike), ...corner(stem), ...corner(zigzag), fastLoop, join];
stroke(lean(path, random(0.3, 0.45)), { size: 0.56, floor: 0.78, halo: overspray });
// where a stroke turns at the bottom, the paint runs
for (const p of bottoms(path)) if (random() < odds) drip(p, random(0.4, room(p)));
// a throw-up: clear room for each letter, lay a light coat, outline it, shine it
for (const letter of bubbles) {
erase(letter, fat + line * 1.6); coat(letter, fat, 0.3); outline(letter, fat, line); shine(letter);
}
The cut
javascript·writing.js
slab(roundRect(box, radius));
for (let n = 1 + floor(random(w + 1)); n > 0; n--) {
const side = random(["left", "right", "top", "bottom"]);
erase(random([notch, slit, hole, bite, vee])(side, unit)); // destination-out
}
The page
javascript·writing.js
// no hand keeps to the line: each word sits a little off it, tilted and sized as it came
const lean = { dy: (noise(x / 26, line * 0.37) - 0.5) * 2.4, turn: random(-0.04, 0.04), scale: random(0.94, 1.08) };
// each letter is drawn once, weighed by the ink it holds, and sorted lightest first,
// so the page can be set as halftone: heavier ink, heavier letter of the same width
glyph = hand[w][floor((tone(u, v) * 0.65 + jitter * 0.35) * hand[w].length)];
// rubrics: a few passages, a phrase to a few lines long and always whole words,
// picked anew each time the caret goes round
for (let n = 3 + floor(random(3)); n > 0; n--) {
const from = floor(random(words.length)), to = from + 3 + floor(random() * random() * 28);
for (const slot of slotsOf(words.slice(from, to + 1))) rubric[slot] = true;
}
References