feat: add superfig paper-figure toolkit

Standalone LaTeX/TikZ skill for non-tensor paper figures: node/edge
macros, lint-on-warning build, golden examples, and negative fixtures.
This commit is contained in:
dela
2026-08-17 09:40:12 +08:00
commit db5598fbf7
39 changed files with 1732 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
# Anti-patterns
Each of these can compile cleanly and still teach the reader something false.
Rendered pairs: `examples/antipatterns.tex`.
- **A decorative arrow.** The arrow has no data/control/dependency meaning. Remove it or
make the relationship explicit.
- **A group that is only a visual border.** The outline does not correspond to a real
module/composite in the paper.
- **One node doing two jobs.** "attention + layer norm + residual" in one block hides the
mechanism the figure was meant to explain.
- **A new hue for a slightly different view of the same object.** Reuse the role or use
lightness, not a fresh color family.
- **A caption repeated in the meaning box.** Captions name the objects; the meaning box
explains what they do and why.
- **Solving crowding by shrinking type.** The type hierarchy is a hard floor. Move the
stage to another row or split the figure.
- **Magic offsets.** Every hand-tuned coordinate is valid only for the current label. The
next edit will land a longer label on a neighbor.
- **A floating commentary card between objects.** If it is not an object or edge, it belongs
in the stage subtitle, meaning box, or one `\sfcallout` beside a finished band.
+122
View File
@@ -0,0 +1,122 @@
# superfig.sty API
```tex
\documentclass[border=10pt]{standalone}
\usepackage[cjk]{superfig} % cjk: ctex + fandol (XeLaTeX). en: English rail labels.
```
Build with `./scripts/build.sh fig.tex` — it puts `assets/` on `TEXINPUTS`.
The build first runs `scripts/lint.py`. It rejects ledger changes, more than four
active hues, raw `\draw`/`\fill`/`\path`, a formula placed before the last row, a
callout that is not an aside on a `\sfrow` band, a second callout, and a group
with fewer than two members. Intentional galleries may put
`% superfig-lint: allow-raw-tikz, allow-multiple-callouts` near the top; do not
add an exemption to a deliverable merely to make it pass.
Rules for *when* to use each primitive live in `grammar.md` and `layout.md`.
## Ledgers
```tex
\sfsetrole{input}{sfTeal} % role -> color. Macros take a ROLE, never a color.
\sfsetlabels{A}{O}{M} % override the three meaning-box rail labels
\sfsetrail{5.4em} % width of the bold label rail (`[en]` defaults wider)
```
Colors: `sfTeal sfOrange sfCoral sfViolet sfGray sfInk`. An unknown role falls
back to gray **and emits a package warning**, which `build.sh` turns into a
failed build. Redeclaring a role with a different color warns and keeps the
original mapping.
Lengths: `\sfgutter` (6 mm), `\sfrowgap` (3.5 mm), `\sfblockgap` (9 mm),
`\sflinklen` (10 mm).
## Flow layout
Empty `at` places the object at the cursor. Hand-written offsets are for
genuinely non-linear topology.
```tex
\sfstage{SA}{stage heading}
\sfrow{R1}{16mm}
\sfnode[role=input]{x}{输入 $x$}{16mm}{12mm}
\sfconn{e1}{预处理}
\sfnode[role=model]{f}{模型 $f_\theta$}{18mm}{12mm}
\sfrowend
\sflane{R1}
\sfcaption{x}{$x$}{原始输入}
```
| macro | does |
|---|---|
| `\sfstage{name}{text}` | stage heading on the left rail, below all ink so far |
| `\sfrow{name}{height}` | open a band; `height` is a length |
| `\sfrowend` | `fit` the band into `name` |
| `\sfnode[keys]{name}{label}{w}{h}` | one semantic object |
| `\sfop{name}{glyph}` | operator in the flow; reserves its own width |
| `\sfconn{name}{label}` | connector; empty label reserves `\sflinklen` of bare arrow |
| `\sfgap{4mm}` / `\sfvgap{4mm}` | extra space, horizontal / vertical |
| `\sfleftrail{x}` | move the left rail |
| `\sfbbox{all}` | everything drawn so far, as one node |
| `\sftopformula{F}{math}` | claim/formula, centered on what was actually drawn |
| `\sftrack{node}` | fold a hand-placed TikZ node into the bbox and vertical cursor |
| `\sflayoutreset` | start over |
`\sfnode` keys: `role=` (default `neutral`), `level=` (`1/2/3` → `!30/!55/!80`),
`gap=` (space *before* this object in a row), `bracket=` (matrix-style arms),
`at={}` (empty = cursor; a coordinate bypasses the row).
Call `\sftopformula` **after** the last `\sfrowend`. A formula placed first is
centered on a figure whose width is not yet known.
## Fixed topology
```tex
\sfnode[role=enc, at={(0,-22mm)}]{in}{输入}{16mm}{10mm}
\sfarrow{in.east}{attn.west}
\sfarrowlabel[bend left=18]{in.north}{add.north}{残差}
```
`\sfarrow` / `\sfarrowlabel` take the same `to[]` options as TikZ (`bend left`,
`out=south, in=north`). The label uses `auto` so a vertical edge does not sit
the text on the shaft. Route is on the background layer.
Keep `at=` on a coarse grid or derive it from existing anchors
(`at={($(te.east)!0.5!(ve.east)+(20mm,0)$)}`).
## Groups, captions, callouts
```tex
\sfgroup[role=attn]{block}{(attn)(ffn)}{} % empty overlay; caption the group
\sflane{R1}
\sfcaption{block}{主路模块}{注意力 + 前馈}
\sfcallout{N1}{38mm}{R1}{title}{body}
```
- `\sfgroup` needs at least two members in the fit list. A single-node outline
is a package warning. After a row, the group refits that row so `\sflane{R1}`
hangs captions below the outline.
- Prefer an empty overlay caption and `\sfcaption{group}{...}{...}`. An overlay
sits on the north-west corner and collides with skip edges.
- `\sfcallout{name}{width}{band}{title}{body}` hangs off a **finished** `\sfrow`
band, one per figure. Inside an open row it is a package error; a non-band
anchor or a second card is a warning.
- `\sfcaptiontop{name}{text}` is the occasional label above a node.
- `\sfnolane` turns the shared caption baseline off.
## Bottom
```tex
\sfbbox{all}
\sfmeaningbox{mb}{96mm}{all}{idea}{objects}{mechanism}
\sfsignature{subject}{mb} % optional; subject only
```
Arg 2 is the total box width. An empty `{}` row is dropped. `\usepackage[en]`
switches the rails to Concept / Objects / Mechanism.
## Lint exemptions
`% superfig-lint: allow-raw-tikz, allow-multiple-callouts, allow-single-group`
near the top of the source. Deliverables normally have none.
+40
View File
@@ -0,0 +1,40 @@
# Delivery checklist
A clean build only proves TeX and package invariants. Inspect the figure before delivery.
## Semantics
- [ ] Every node is one object with one role.
- [ ] Every edge is data flow, control flow, dependency, or causality.
- [ ] Every group names a real composite, binds at least two members, and all members belong to it.
- [ ] At most one `\sfcallout`, hanging off a finished band, not off a single node.
- [ ] Captions explain objects or relationships, not decoration.
## Layout
- [ ] No node covers another node, caption, edge label, or group border.
- [ ] Flow rows start on the same left rail.
- [ ] Edge labels sit on their own edge and do not cover unrelated objects.
- [ ] Fixed coordinates are on a coarse grid, not tuned against one label width.
- [ ] Captions in a row share one baseline.
- [ ] Meaning box is one column, at most three rows, no overflow.
## Style
- [ ] One role keeps one hue across the whole figure.
- [ ] At most four active hue families plus gray.
- [ ] Three visibly separated lightness levels where objects need hierarchy; no uniform pastel.
- [ ] Borders are neutral; group outlines carry their members' role hue.
- [ ] No saturated primaries, shadows, banners, or decorative cards.
## Thumbnail
- [ ] Open `*-thumb.png` (360 px). The main claim is still legible.
- [ ] Hues remain distinct and muted; text has not collapsed into gray mush.
## Delivery
- [ ] PNG preview shown.
- [ ] One-paragraph mechanism explanation in prose.
- [ ] `.tex` source and PDF/SVG linked.
- [ ] Any degraded path or inferred relationship stated explicitly.
+17
View File
@@ -0,0 +1,17 @@
# Fallback without LaTeX
Use this path only when `scripts/preflight.sh` exits 2. State explicitly that the
`superfig.sty` guarantees — role registry, cursor layout, package warnings, and the build
pipeline — are unavailable.
## Preserve manually
1. Build the semantics ledger before drawing.
2. One hue per role; at most four hues plus gray; three lightness levels.
3. Every node is one object; every edge has one relationship; groups are real composites.
4. Derive placement from previous bounding boxes plus one gutter constant, not per-label
tuning.
5. Export SVG and PNG, then inspect full-size and at 360 px using `checklist.md`.
Prefer SVG for editability. Do not imitate package compliance in the delivery: name the
fallback renderer and list any inferred relationship or reduced guarantee.
+48
View File
@@ -0,0 +1,48 @@
# Visual grammar
Choose the smallest grammar that exposes the paper's mechanism. Adding a second grammar
must add information.
| Fact to expose | Grammar |
|---|---|
| one semantic object | `\sfnode` |
| linear sequence inside one band | `\sfstage` + `\sfrow` … `\sfrowend` |
| an edge between two objects | `\sfconn` for flow, `\sfarrow` for fixed topology |
| an edge label | `\sfconn{name}{label}` or `\sfarrowlabel` |
| a real composite (module, subsystem, shared owner) | `\sfgroup` |
| an operator inside a flow row | `\sfop` |
| a side note about a finished band | `\sfcallout` |
| the bottom explanation | `\sfmeaningbox` |
## Node semantics
One block is one semantic object. If you need two verbs in one block, split it.
- Label is the object's name or role, not a sentence.
- Fill color encodes a role, not importance.
- Width/height may differ to reflect a visual hierarchy, but do not use size to invent
quantitative meaning unless the figure says so.
## Edge semantics
Every arrow must be one of:
- **data flow** — the output of A becomes the input of B.
- **control flow** — A decides whether or when B runs.
- **dependency** — B needs A to exist or to have run.
- **causality** — A causes B.
A decorative arrow is an error. If an edge does not carry one of those meanings, remove it
or replace it with a grouping/caption.
## Group semantics
`\sfgroup` names a composite object. The members inside must actually belong to that
composite in the paper. Do not draw an outline around nearby nodes merely because it looks
balanced. The fit list is explicit: `{(node1)(node2)}`.
## Captions and meaning box
- `\sfcaption{name}{symbol}{detail}`: one short symbol line plus one muted detail line.
- `\sfmeaningbox`: at most three rows — idea, objects, mechanism. Prefer removing content
over shrinking type or adding columns.
+52
View File
@@ -0,0 +1,52 @@
# Layout rules
## Cursor flow
Inside `\sfrow` … `\sfrowend`, objects are placed from left to right by the cursor.
The first object starts on the left rail; every later object reserves the standing gutter.
- Declare the band height once in `\sfrow`. An object that overflows it is a build warning.
- Use `\sfconn` for a labelled edge in the flow; the label reserves its own width.
- Do not add magic-number `xshift`s between flow objects.
## Fixed topology
Use `\sfnode[at={(x,y)}]{...}` only when the diagram is genuinely non-linear: branches,
loops, vertical/horizontal stacks, skip edges.
- Keep coordinates on a coarse grid; prefer multiples of `\sfgutter` or explicit anchors.
- Use `\sfarrow` between placed nodes. Route edges on the background layer.
- Put edge labels on the edge itself, not floating nearby.
## Captions
Use `\sflane{row}` after `\sfrowend` to give all captions in that row one shared baseline.
Symbol and detail lines are two reserved lanes; do not place other text between them.
## Groups
A group outline adds inner padding, so place it after its members. It must not cover unrelated
nodes. Members must be adjacent in the semantic sense; the explicit fit list prevents the
package from inventing a group around whatever is near. A group must bind at least two
members.
After a `\sfrow`, `\sfgroup` refits that row so `\sflane{row}` hangs captions below the
outline. Prefer an empty overlay caption and `\sfcaption{group}{...}{...}`: an overlay at
the north-west corner lands on skip edges. A connector that should leave the composite
starts on the group node (`\sfarrow{block.east}{next.west}`), not on a member — otherwise
the shaft crosses an outline that is not its endpoint.
## Formula
Call `\sftopformula` after the last `\sfrowend`. The line is centered on `\sfbbox`, whose
width is only known once the bands exist.
## Callouts
`\sfcallout` hangs off a finished `\sfrow` band, one per figure. It is not an object in
the flow and must not be anchored to a single node.
## Meaning box
The meaning box hangs below the full figure bbox (`\sfbbox`). It is one column, at most
three rows. If it is too long, remove content; do not widen it past the figure or shrink type.
+52
View File
@@ -0,0 +1,52 @@
# House style
The package ships these defaults; this file explains what to preserve and what must still
be decided.
## Canvas
White background, natural `standalone` crop. No forced 16:9. No title by default. The top
holds at most two compact claim/formula lines.
Each stage is one horizontal row with a shared baseline. Use the fewest stages that preserve
the primary claim. No unrelated branches, no dashboard panels.
## Type hierarchy
| element | size | macro / style |
|---|---|---|
| top formula/claim | `\large` | `\sftopformula` |
| stage label | `\small\bfseries`, muted | `\sfstage` |
| node label | `\small` | `\sfnode` |
| caption symbol | `\small` | `\sfcaption` |
| caption detail | `\scriptsize`, muted | `\sfcaption` |
| edge label | `\scriptsize`, muted | `\sfconn` / `\sfarrowlabel` |
| meaning box | `\small` | `\sfmeaningbox` |
| optional signature | `\scriptsize`, low contrast | `\sfsignature` |
Never shrink below this hierarchy to make something fit. Move it to another row instead.
## Color
Palette: `sfTeal #4F8FA5`, `sfOrange #EE995B`, `sfCoral #C95B5B`, `sfViolet #8A74B5`,
`sfGray #85898F`. Muted, mid-chroma, paper-like. Do not add saturated primaries.
- One semantic color per role, held everywhere: `\sfsetrole{input}{sfTeal}`.
- Drawing macros take a **role**, never a color.
- At most four active hue families per figure, plus gray.
- Contrast comes from lightness separation, not saturation. Use `level=1/2/3` for
`role!30 / role!55 / role!80`; do not use uniform pastel fills.
- If sign matters, use hue for sign and intensity for magnitude.
## Edges and nodes
- Nodes are rounded rectangles with thin neutral borders; no saturated colored outlines.
- Group outlines use `role!65` because the outline itself names an object.
- Edges are thin neutral arrows on the background layer; text stays on the foreground layer.
- Keep all text in LaTeX or CJK text, never rasterized labels.
## Never
Metric insets not present in the primary claim. Decorative pills, banners, shadows,
repeated separators, extra explanatory cards beyond one `\sfcallout`. Raw TikZ `\draw` /
`\fill` / `\path` unless the lint directive allows it.