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.
106 lines
4.3 KiB
Markdown
106 lines
4.3 KiB
Markdown
# Layout invariants
|
|
|
|
Everything on the canvas is a bounding box: tensors, full offset stacks, brackets,
|
|
operators, arrow labels, annotations, symbols, shape labels, stage headings, the meaning
|
|
box, the signature. **Tangency counts as collision.**
|
|
|
|
## Place by cursor, not by coordinate
|
|
|
|
The invariants below are *statements about the finished figure*; the flow layout in
|
|
`api.md` is how you get them without checking each one by hand. Use it by default:
|
|
`\ststage` / `\strow` … `\strowend` / `\stcol` … `\stcolend`, with an empty coordinate
|
|
argument on every face.
|
|
|
|
The failure it removes is specific. With hand-written offsets, each gap is a magic number
|
|
tuned against the *current* content, so the day a label grows by two characters it silently
|
|
lands on the next tensor — the figure still compiles and still looks clean. With the
|
|
cursor, every object reserves its own width, so growing one object can only push the rest
|
|
apart. And because a band declares its height, an object that does not fit becomes a
|
|
`Package supertensor Warning`, which `build.sh` turns into a failed build.
|
|
|
|
Hand placement remains available for the cases the cursor cannot express. When you use it,
|
|
wrap the node in `\sttrack` so the bounding box and the vertical cursor still see it.
|
|
|
|
## Gutters
|
|
|
|
Define one base gutter `g ≥ 1 em`; that is what `\stgutter` (6 mm) is. Unrelated boxes stay
|
|
at least `g` apart; stage bands at least `1.5g` (`\strowgap`, `\stblockgap`). Set them once
|
|
at the top of the figure rather than per call — a per-call `gap=` is for stating a
|
|
*relationship* (`gap=0pt` = these shards tile), not for nudging.
|
|
|
|
Overlap is allowed only inside one declared composite:
|
|
|
|
- tiles inside their own face,
|
|
- shards tiling a parent,
|
|
- outline sheets in one `\ststack`,
|
|
- a bracket around its own tensor,
|
|
- a connector endpoint touching its source/target border.
|
|
|
|
Every other intersection or occlusion is forbidden.
|
|
|
|
## Lanes
|
|
|
|
Reserve separate vertical lanes and never put anything else in them:
|
|
|
|
```
|
|
stage heading
|
|
(connector annotations)
|
|
tensor / operator row
|
|
symbols <- \stcaption arg 2
|
|
shapes <- \stcaption arg 3
|
|
```
|
|
|
|
Faces of different heights would otherwise hang their captions at different depths. Fix it
|
|
with a shared baseline — `\strowend` arms one automatically:
|
|
|
|
```tex
|
|
\strow{rowA}{T}
|
|
...
|
|
\strowend
|
|
\stcaption{Q}{$\mathbf Q^{(i)}$}{$h\times T\times d_h$}
|
|
```
|
|
|
|
Under absolute placement, arm it yourself with `\stlane{rowA}` … `\stnolane`. Either way
|
|
every `\stcaption` inside hangs from the bottom of `rowA`, so symbols and shapes form two
|
|
flat lanes.
|
|
|
|
Stage headings share one left rail. `\ststage` puts them there: the rail is a single stored
|
|
`x` (`\stleftrail` to move it), and the `y` is derived from the lowest ink drawn so far, so
|
|
a heading can neither drift right nor collide with the row above it.
|
|
|
|
Explanatory prose belongs in the stage subtitle, the bottom box, or above its own
|
|
connector. Never drop a floating commentary card between two operands unless it is a real
|
|
operation node (`st comm`).
|
|
|
|
## Layers
|
|
|
|
The package declares three: `stbg` (connectors), `main` (tensors, operators), `stfg`
|
|
(text). `\starrow` and `\starrowlabel` route on `stbg` automatically, so a connector can
|
|
never cover a face. Two consequences you still own:
|
|
|
|
- A connector may not cross a box that is not one of its endpoints. Move the row, don't
|
|
route over.
|
|
- A label's white underlay may cover only its own connector — never a tensor, never
|
|
another label. This is the single most common failure after a first draft, and `\stlink`
|
|
is the fix: it makes the label itself a flow object and draws the arrow to whatever
|
|
lands on either side of it, so the label cannot be wider than its connector.
|
|
`\starrowlabel` between two hand-placed nodes still has the old failure mode.
|
|
|
|
## Stacks
|
|
|
|
`\ststack` includes its offset sheets in the bounding box, so neighbours can be spaced
|
|
against the real extent. Back sheets are outline-only and must carry no semantic content
|
|
of their own. If individual slices need to be read, use separate panels instead of overlap.
|
|
|
|
## When it does not fit
|
|
|
|
In this order:
|
|
|
|
1. shorten or remove secondary annotation,
|
|
2. widen the natural crop,
|
|
3. increase row spacing,
|
|
4. move the whole stage to another row.
|
|
|
|
Never solve crowding by shrinking below the type hierarchy, closing the gutter, or covering
|
|
another object.
|