Add flow layout: cursor placement, left rail, declared band heights

Figures were positioned by hand-written offsets. Every gap was a magic
number tuned against the content that happened to be there, so a label
that grew two characters landed on the next tensor, and two stages
started from two different x shared no rail. Both failures compile
cleanly.

Replace it with a cursor. Objects placed with an empty coordinate
argument reserve their own width -- including a stack's offset sheets
and a bracket's overhang -- and gaps are declared once (\stgutter,
\strowgap, \stblockgap). The gap belongs to the object that follows it
and the first object in a band gets none, so every band starts flush on
a shared rail and gap=0pt states that two shards tile exactly.

\stlink makes a connector's label a flow object, which is what removes
the label-wider-than-its-arrow failure entirely. \stcol is a vertical
sub-flow for a split along the contracted axis. \strow declares its
height, so an object that does not fit -- or a column that does not add
up to what it declared, and is therefore drawn off-center -- becomes a
package warning, which build.sh fails on.

Absolute placement is unchanged: passing a coordinate takes the original
code path, and \sttrack folds a hand-placed node back into the cursor.

All three golden examples and the new tests/flow.tex are converted and
build clean.
This commit is contained in:
dela
2026-08-05 15:55:21 +08:00
parent 7a22bef9e3
commit 7b59c81d02
11 changed files with 672 additions and 223 deletions
+11 -5
View File
@@ -15,9 +15,9 @@ dimension. **Fix:** swap the arguments — `\ststack{KT}{...}{dh}{T}{3}`. See `g
## 2. Shards that do not tile their parent
Two shards drawn with a gap, or stretched to fill a parent whose other shards were elided.
Both assert a width that the tensor does not have. **Fix:** place each shard from the
previous one's edge (`($(W1a.east)+(2*\stunit,0)$)`), and draw an ellipsis for anything
omitted. See `geometry.md` §5–6.
Both assert a width that the tensor does not have. **Fix:** draw the shards in one band
and give every shard after the first `gap=0pt`, which *states* that they are adjacent
instead of arranging for it; draw an ellipsis for anything omitted. See `geometry.md` §5–6.
## 3. An index drawn as a heatmap
@@ -41,12 +41,18 @@ See `style.md`.
reads the resulting stripe as real structure. `pattern=dense` avoids it; if you write
your own filler, check that rows 1, 2, 4, 5 of a tall face are not identical.
- **A label wider than its connector.** The white underlay then covers the target tensor.
Shorten the label or widen the gap — never let it sit on a face. See `layout.md`.
`\stlink` makes this unrepresentable: the label reserves its own width in the band and
the arrow is drawn to whatever lands beside it. See `layout.md`.
- **A new hue for a regrouped view of the same data.** `X` and the per-expert buffers
gathered out of `X` are the same object in a different order; a second hue claims they
are different tensors.
- **Captions hanging at different depths** because the faces in a row have different
heights. Use `\stlane`.
heights. Use `\strow`/`\strowend`, which arms `\stlane` for you.
- **Hand-tuned offsets.** Each one is a magic number valid only for the content that was
there when you tuned it; the figure that breaks is the *next* one, when a label grows two
characters and lands on a face. Use the cursor. See `layout.md`.
- **A formula line placed first.** It can only be centered on a figure whose width is not
known yet, so it ends up visibly off-center. Call `\sttopformula` after the bands.
- **A floating commentary card between two operands.** If it is not a real operation, it
belongs in the stage subtitle or the bottom box.
- **A meaning box that repeats the shapes.** The shapes are already under every block. The